diff options
Diffstat (limited to 'daemon/snmp-engine.c')
-rw-r--r-- | daemon/snmp-engine.c | 727 |
1 files changed, 0 insertions, 727 deletions
diff --git a/daemon/snmp-engine.c b/daemon/snmp-engine.c deleted file mode 100644 index e79c6c2..0000000 --- a/daemon/snmp-engine.c +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright (c) 2005, Stefan Walter - * 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 - * Stef Walter <stef@memberwebs.com> - * - */ - -#include "usuals.h" -#include <sys/types.h> -#include <sys/socket.h> -#include <errno.h> -#include <unistd.h> -#include <syslog.h> -#include <err.h> -#include <arpa/inet.h> - -#include <bsnmp/asn1.h> -#include <bsnmp/snmp.h> - -#include "rrdbotd.h" -#include "server-mainloop.h" -#include "async-resolver.h" - -/* The socket to use */ -static int snmp_socket = -1; - -/* The last request id */ -static uint32_t snmp_request = 100000; - -/* Since we only deal with one packet at a time, global buffer */ -static unsigned char snmp_buffer[0x1000]; - -/* ----------------------------------------------------------------------------- - * REQUESTS - */ - -typedef struct _rb_request -{ - /* The SNMP request identifier */ - uint32_t id; - - mstime next_retry; /* Time of the next retry */ - mstime last_sent; /* Time last sent */ - mstime interval; /* How long between retries */ - mstime timeout; /* When this request times out */ - uint 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* arequests; - rb_request* req = NULL; - uint num; - int i, overlap = 0; - - if(nrequests) - { - /* We allocate in a loop starting after the last allocation. */ - for(i = reqhigh; !overlap || 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; - arequests = (rb_request*)realloc(requests, sizeof(rb_request) * num); - if(!arequests) - { - /* Note we leave old requests allocated */ - errno = ENOMEM; - return NULL; - } - - /* Clear out the new ones */ - requests = arequests; - memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests)); - - /* We return the next one */ - req = requests + nrequests; - - nrequests = num; - - if(reqhigh == -1) - reqhigh = 0; - if(reqlow == -1) - reqlow = nrequests - 1; - } - - /* 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, mstime 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; - - /* And send off our collection of values */ - rb_rrd_update(poll); -} - -static void -send_req(rb_request* req, mstime when) -{ - struct asn_buf b; - ssize_t ret; - - /* Update our bookkeeping */ - req->sent++; - if(req->sent <= g_state.retries) - req->next_retry = when + req->interval; - else - req->next_retry = 0; - req->last_sent = when; - - /* No sending if no address */ - if(!req->host->is_resolved) - { - if(req->sent <= 1) - rb_messagex(LOG_DEBUG, "skipping snmp request: host not resolved: %s", - req->host->hostname); - return; - } - - 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->hostname); - else - rb_messagex(LOG_DEBUG, "sent request #%d to: %s", req->id, req->host->hostname); - } -} - -static void -timeout_req(rb_request* req, mstime 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->vtype = VALUE_UNSET; - it->req = NULL; - } - - else if(it->req) - incomplete = 1; - } - - /* For timeouts we use the time the last request was sent */ - when = req->last_sent; - - free_req(req); - - if(!incomplete) - finish_poll(poll, when); -} - -static void -check_req(rb_request* req, mstime 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, mstime when) -{ - struct snmp_value* value; - rb_poller* poll = req->poll; - rb_item* item; - 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->vtype = VALUE_UNSET; - break; - case SNMP_SYNTAX_INTEGER: - item->v.i_value = value->v.integer; - item->vtype = VALUE_REAL; - break; - case SNMP_SYNTAX_COUNTER: - case SNMP_SYNTAX_GAUGE: - case SNMP_SYNTAX_TIMETICKS: - item->v.i_value = value->v.uint32; - item->vtype = VALUE_REAL; - break; - case SNMP_SYNTAX_COUNTER64: - item->v.i_value = value->v.counter64; - item->vtype = VALUE_REAL; - 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 = "field not available on snmp server: %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 if(item->vtype == VALUE_REAL) - rb_messagex(LOG_DEBUG, "got value for field '%s': %lld", - item->rrdfield, item->v.i_value); - else if(item->vtype == VALUE_FLOAT) - rb_messagex(LOG_DEBUG, "got value for field '%s': %.4lf", - item->rrdfield, item->v.f_value); - else - rb_messagex(LOG_DEBUG, "got value for field '%s': U", - item->rrdfield); - - /* Mark this value as done */ - item->req = NULL; - break; - } - } - } - - /* We're done with this request */ - free_req(req); - - /* 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(mstime 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 <= 2000) ? 200L : 600L; - - /* Timeout is for the last packet sent, not first */ - req->timeout = when + (req->interval * ((mstime)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->vtype = VALUE_UNSET; - } - - 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; - const char* msg; - 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]"); - - /* 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; - } - - /* It needs to match something we're waiting for */ - req = find_req(pdu.request_id); - if(!req) - { - rb_messagex(LOG_DEBUG, "received extra or delayed packet from: %s", hostname); - return; - } - - /* Check for errors */ - if(pdu.error_status != SNMP_ERR_NOERROR) - { - msg = snmp_get_errmsg (pdu.error_status); - if(msg) - rb_messagex(LOG_ERR, "snmp error from host '%s': %s", - hostname, msg); - else - rb_messagex(LOG_ERR, "unknown snmp error from host '%s': %d", - hostname, pdu.error_status); - return; - } - - if(pdu.version != req->pdu.version) - rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname); - - /* Dispatch the packet */ - rb_messagex(LOG_DEBUG, "response to request #%d from: %s", req->id, hostname); - respond_req(req, &pdu, server_get_time()); -} - -static int -resend_timer(mstime when, void* arg) -{ - int i, first; - - /* Search backwards through the scrolling window */ - for(i = reqhigh, first = 1; first || i != reqlow; - i = (i ? i : nrequests) - 1, first = 0) - { - if(requests[i].id) - check_req(&(requests[i]), when); - } - - return 1; -} - -static int -prep_timer(mstime 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; -} - -static void -resolve_cb(int ecode, struct addrinfo* ai, void* arg) -{ - rb_host* host = (rb_host*)arg; - - if(ecode) - { - rb_messagex(LOG_WARNING, "couldn't resolve hostname: %s: %s", host->hostname, - gai_strerror(ecode)); - return; - } - - /* A successful resolve */ - memcpy(&SANY_ADDR(host->address), ai->ai_addr, ai->ai_addrlen); - SANY_LEN(host->address) = ai->ai_addrlen; - host->last_resolved = server_get_time(); - host->is_resolved = 1; - - rb_messagex(LOG_DEBUG, "resolved host: %s", host->hostname); -} - -static int -resolve_timer(mstime when, void* arg) -{ - rb_host* host; - struct addrinfo hints; - - /* Go through hosts and see which ones need resolving */ - for(host = g_state.hosts; host; host = host->next) - { - /* No need to resolve? */ - if(!host->resolve_interval) - continue; - - if(when - host->resolve_interval > host->last_resolve_try) - { - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - - /* Automatically strips port number */ - rb_messagex(LOG_DEBUG, "resolving host: %s", host->hostname); - async_resolver_queue(host->hostname, "161", &hints, resolve_cb, host); - host->last_resolve_try = when; - } - - /* When the last 3 resolves have failed, set to unresolved */ - if(when - (host->resolve_interval * 3) > host->last_resolved) - host->is_resolved = 0; - } - - return 1; -} - -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"); - - /* resolve timer goes once per second */ - if(server_timer(1000, resolve_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; - } -} |