/* * Copyright (c) 2005, Nate Nielsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Nate Nielsen * */ #include "usuals.h" #include #include #include #include #include "stringx.h" #include "rrdbotd.h" #include "server-mainloop.h" /* The socket to use */ int snmp_socket = -1; /* The last request: start at a strange number */ uint32_t snmp_request = 0x0A0A0A0A; /* Since we only deal with one packet at a time, global buffer */ unsigned char snmp_buffer[0x1000]; /* ----------------------------------------------------------------------------- * REQUESTS */ /* rb_request waaaaayyyyy too big */ typedef struct _rb_request { /* The SNMP request identifier */ uint32_t id; uint64_t next_retry; /* Time of the next retry */ uint32_t interval; /* How long between retries */ uint64_t timeout; /* When this request times out */ uint32_t sent; /* How many times we've sent */ /* The poller and host associated with this request */ rb_poller* poll; const rb_host* host; /* The actual request data */ struct snmp_pdu pdu; } rb_request; /* a scrolling window on a loop */ static rb_request* requests = NULL; static int reqhigh = -1; static int reqlow = -1; static uint nrequests = 0; static rb_request* new_req() { rb_request* req = NULL; uint num; int i, first, overlap = 0; if(nrequests) { /* We allocate in a loop starting after the last allocation. */ for(i = (reqhigh + 1) % nrequests; i != reqhigh; i = (i + 1) % nrequests) { /* * We can overlap past reqlow, but in that case no * updating reqhigh. This can happen after reallocating. */ if(i == reqlow) overlap = 1; if(requests[i].id == 0) { req = &(requests[i]); if(!overlap) reqhigh = i; break; } } } if(!req) { /* * A note about the scrolling window and extending allocations... * The only reason this works is because whenever we reallocate * reqhigh and reqlow are the same. */ ASSERT(reqlow == reqhigh); /* Reallocate the request block */ /* TODO: Once we use less memory this can be higher */ num = nrequests ? nrequests * 2 : 32; fprintf(stderr, "REALLOCATING: %d!!!\n", nrequests); requests = (rb_request*)realloc(requests, sizeof(rb_request) * num); if(!requests) { /* Note we leave old requests allocated */ errno = ENOMEM; return NULL; } /* Clear out the new ones */ fprintf(stderr, "clearing: %d %d\n", sizeof(rb_request), num - nrequests); memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests)); /* We return the next one */ req = requests + nrequests; nrequests = num; } /* A incrementing counter for each request */ req->id = snmp_request++; return req; } static rb_request* find_req(uint32_t id) { int i, first; if(!nrequests) return NULL; /* * Search backwards from the in the scrolling window. This gives * us as high performance for the high performing pollers and * less performance for the low ones. */ for(i = reqhigh, first = 1; first || i != reqlow; i = (i ? i : nrequests) - 1) { if(id == requests[i].id) return &(requests[i]); first = 0; } return NULL; } static void free_req(rb_request* req) { int i; memset(req, 0, sizeof(*req)); /* Update the bottom of the scrolling loop */ for(i = reqlow; i != reqhigh; i = (i + 1) % nrequests) { /* If used then done */ if(requests[i].id) break; /* reqlow is not inclusive */ reqlow = i; } } /* ----------------------------------------------------------------------------- * PACKET HANDLING */ static void finish_poll(rb_poller* poll, uint64_t when) { #ifdef _DEBUG { rb_item* it; for(it = poll->items; it; it = it->next) ASSERT(!it->req); } #endif /* Update the book-keeping */ poll->last_polled = when; rb_messagex(LOG_DEBUG, "collected poll values. sending them to rrd"); /* And send off our collection of values */ // rb_rrd_update(poll); } static void send_req(rb_request* req, uint64_t when) { struct asn_buf b; ssize_t ret; b.asn_ptr = snmp_buffer; b.asn_len = sizeof(snmp_buffer); if(snmp_pdu_encode(&(req->pdu), &b)) rb_message(LOG_CRIT, "couldn't encode snmp buffer"); else { ret = sendto(snmp_socket, snmp_buffer, b.asn_ptr - snmp_buffer, 0, &SANY_ADDR(req->host->address), SANY_LEN(req->host->address)); if(ret == -1) rb_message(LOG_ERR, "couldn't send snmp packet to: %s", req->host->name); else rb_messagex(LOG_DEBUG, "sent request '%d' to: %s", req->id, req->host->name); } /* And update our bookkeeping */ req->sent++; if(req->sent <= g_state.retries) req->next_retry = when + req->interval; } static void timeout_req(rb_request* req, uint64_t when) { rb_poller* poll = req->poll; int incomplete = 0; rb_item* it; ASSERT(poll); /* * Marks of this requests items as unknown. Request is * over, free. See if poller is done */ for(it = poll->items; it; it = it->next) { if(it->req == req) { rb_messagex(LOG_DEBUG, "value for field '%s' timed out", it->rrdfield); it->value = RB_UNKNOWN; it->req = NULL; } else if(it->req) incomplete = 1; } if(!incomplete) finish_poll(poll, when); } static void check_req(rb_request* req, uint64_t when) { ASSERT(req->id); /* See if it's timed out */ if(when <= req->timeout) timeout_req(req, when); if(!req->next_retry) return; /* Resend if necessary */ if(when <= req->next_retry) send_req(req, when); } static void respond_req(rb_request* req, struct snmp_pdu* pdu, uint64_t when) { struct snmp_value* value; rb_poller* poll = req->poll; rb_item* item; int incomplete = 0; int i; ASSERT(req->id == pdu->request_id); for(i = 0; i < pdu->nbindings; i++) { value = &(pdu->bindings[i]); for(item = poll->items; item; item = item->next) { if(asn_compare_oid(&(value->var), &(item->snmpfield.var)) == 0) { const char *msg = NULL; switch(value->syntax) { case SNMP_SYNTAX_NULL: item->value = RB_UNKNOWN; break; case SNMP_SYNTAX_INTEGER: item->value = value->v.integer; break; case SNMP_SYNTAX_COUNTER: case SNMP_SYNTAX_GAUGE: case SNMP_SYNTAX_TIMETICKS: item->value = value->v.uint32; break; case SNMP_SYNTAX_COUNTER64: item->value = value->v.counter64; break; case SNMP_SYNTAX_OCTETSTRING: case SNMP_SYNTAX_OID: case SNMP_SYNTAX_IPADDRESS: msg = "snmp server returned non numeric value for field: %s"; break; case SNMP_SYNTAX_NOSUCHOBJECT: case SNMP_SYNTAX_NOSUCHINSTANCE: case SNMP_SYNTAX_ENDOFMIBVIEW: msg = "value not available on snmp server for field: %s"; break; default: msg = "snmp server returned invalid or unsupported value for field: %s"; break; } if(msg) rb_messagex(LOG_WARNING, msg, item->rrdfield); else rb_messagex(LOG_DEBUG, "got value for field '%s': %0.2f", item->rrdfield, item->value); /* Mark this value as done */ item->req = NULL; break; } } } /* Now see if the entire request is done */ for(item = poll->items; item; item = item->next) { if(item->req) return; } /* And if so then hand off */ finish_poll(poll, when); } static int poller_timer(uint64_t when, void* arg) { rb_poller* poll = (rb_poller*)arg; const rb_host* last_host = NULL; rb_request* req = NULL; rb_item* it; /* * If the previous poll has not completed, then we count it * as a timeout. */ for(it = poll->items; it; it = it->next) { if(it->req) { ASSERT(it->req->poll == poll); timeout_req(it->req, when); } /* timeout_req above should have cleared this */ ASSERT(!it->req); } for(it = poll->items; it; it = it->next) { /* * We assume that the polled items are sorted by host. Done * in config.c. This allows us to fire off the least amount * of requests. Generate new requests when: * * - first or new host * - too many items in the same request */ if(!req || it->host != last_host || req->pdu.nbindings >= SNMP_MAX_BINDINGS) { /* Send off last request ... */ if(req) send_req(req, when); /* ... and make a new one */ req = new_req(); if(!req) { rb_message(LOG_CRIT, "couldn't allocate a new snmp request"); return 1; } req->poll = poll; req->host = it->host; rb_messagex(LOG_DEBUG, "preparing request '%d' for: %s@%s", req->id, req->host->community, req->host->name); /* Setup the packet */ strlcpy(req->pdu.community, req->host->community, sizeof(req->pdu.community)); req->pdu.request_id = req->id; req->pdu.version = req->host->version; req->pdu.type = SNMP_PDU_GET; req->pdu.error_status = 0; req->pdu.error_index = 0; req->pdu.nbindings = 0; /* Send interval is 200 ms when poll interval is below 2 seconds */ req->interval = (poll->interval < 3) ? 200 : 600; /* Timeout is for the last packet sent, not first */ req->timeout = when + (req->interval * g_state.retries) + poll->timeout; req->sent = 0; last_host = it->host; } /* Add an item to this request */ req->pdu.bindings[req->pdu.nbindings].var = it->snmpfield.var; req->pdu.bindings[req->pdu.nbindings].syntax = it->snmpfield.syntax; req->pdu.nbindings++; /* Mark item as active by this request */ it->req = req; it->value = RB_UNKNOWN; } if(req) send_req(req, when); return 1; } static void receive_resp(int fd, int type, void* arg) { char hostname[MAXPATHLEN]; struct sockaddr_any from; struct snmp_pdu pdu; struct asn_buf b; rb_request* req; int len, ret; int32_t ip; ASSERT(snmp_socket == fd); /* Read in the packet */ SANY_LEN(from) = sizeof(from); len = recvfrom(snmp_socket, snmp_buffer, sizeof(snmp_buffer), 0, &SANY_ADDR(from), &SANY_LEN(from)); if(len < 0) { if(errno != EAGAIN && errno != EWOULDBLOCK) rb_message(LOG_ERR, "error receiving snmp packet from network"); return; } if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1) strcpy(hostname, "[UNKNOWN]"); rb_messagex(LOG_DEBUG, "received packet from: %s", hostname); /* Now parse the packet */ b.asn_ptr = snmp_buffer; b.asn_len = len; ret = snmp_pdu_decode(&b, &pdu, &ip); if(ret != SNMP_CODE_OK) { rb_message(LOG_WARNING, "invalid snmp packet received from: %s", hostname); return; } /* Lookup and check the request properly */ req = find_req(pdu.request_id); if(!req) return; if(pdu.error_status != SNMP_ERR_NOERROR) { /* TODO: Textual errors */ rb_message(LOG_ERR, "snmp error '%d' from host: %s", pdu.error_status, hostname); return; } if(pdu.version != req->pdu.version) rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname); rb_messagex(LOG_DEBUG, "response to request '%d' from: %s", req->id, hostname); respond_req(req, &pdu, server_get_time()); } static int resend_timer(uint64_t when, void* arg) { int i; /* Search forwards through the scrolling window */ for(i = (reqlow + 1) % nrequests; i <= reqhigh; i = (i + 1) % nrequests) { if(requests[i].id) check_req(&(requests[i]), when); } return 1; } static int prep_timer(uint64_t when, void* arg) { /* * We don't prepare all timers at exactly the same time * but we sort of randomly start the various timers. We're * going to be hitting these over and over again, so there's * lots of benefits to spreading them out randomly over a * few seconds. */ rb_poller* poll; int next; /* All done? */ if(!arg) return 0; poll = (rb_poller*)arg; if(server_timer(poll->interval, poller_timer, poll) == -1) rb_message(LOG_CRIT, "couldn't setup poller timer"); /* Setup the next poller anywhere between 0 and 750 ms */ next = rand() % 750; server_oneshot(next, prep_timer, poll->next); return 0; } void rb_snmp_engine_init() { struct sockaddr_in addr; rb_request* req; ASSERT(snmp_socket == -1); snmp_socket = socket(PF_INET, SOCK_DGRAM, 0); if(snmp_socket < 0) err(1, "couldn't open snmp socket"); /* Get a random IPv4 UDP socket for client use */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; if(bind(snmp_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) err(1, "couldn't listen on port"); if (server_watch(snmp_socket, SERVER_READ, receive_resp, NULL) == -1) err(1, "couldn't listen on socket"); /* Allocate some requests to make sure we have memory */ req = new_req(); if(!req) err(1, "out of memory"); free_req(req); /* Start the preparation timers for setting up randomly */ if(server_oneshot(100, prep_timer, g_state.polls) == -1) err(1, "couldn't setup timer"); /* We fire off the resend timer every 1/5 second */ if(server_timer(200, resend_timer, NULL) == -1) err(1, "couldn't setup timer"); } void rb_snmp_engine_uninit() { if(snmp_socket != -1) { server_unwatch(snmp_socket); close(snmp_socket); snmp_socket = -1; } if(requests) { free(requests); nrequests = 0; reqhigh = 0; reqlow = 0; } }