root/src/modules/snmp.c

Revision c04dcf560cc5f0df021d665247f1b6d34352c166, 16.0 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

hoy cow, reminded of how not simple SNMP is

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <errno.h>
11 #include <assert.h>
12 #include <math.h>
13
14 #include "noit_module.h"
15 #include "noit_check.h"
16 #include "noit_check_tools.h"
17 #include "utils/noit_log.h"
18 #include "utils/noit_hash.h"
19
20 #include <net-snmp/net-snmp-config.h>
21 #include <net-snmp/net-snmp-includes.h>
22
23 static noit_log_stream_t nlerr = NULL;
24 static noit_log_stream_t nldeb = NULL;
25 static noit_hash_table target_sessions = NOIT_HASH_EMPTY;
26
27 struct target_session {
28   void *sess_handle;
29   eventer_t timeoutevent;
30   int fd;
31   int in_table;
32   int refcnt;
33 };
34
35 struct snmp_check_closure {
36   noit_module_t *self;
37   noit_check_t *check;
38 };
39
40 struct check_info {
41   int reqid;
42   int timedout;
43   struct {
44      char *confname;
45      char *oidname;
46      oid oid[MAX_OID_LEN];
47      size_t oidlen;
48   } *oids;
49   int noids;
50   eventer_t timeoutevent;
51   noit_module_t *self;
52   noit_check_t *check;
53 };
54
55 /* We hold struct check_info's in there key's by their reqid.
56  *   If they timeout, we remove them.
57  *
58  *   When SNMP queries complete, we look them up, if we find them
59  *   then we know we can remove the timeout and  complete the check.
60  *   If we don't find them, the timeout fired and removed the check.
61  */
62 noit_hash_table active_checks = NOIT_HASH_EMPTY;
63 static void add_check(struct check_info *c) {
64   noit_hash_store(&active_checks, (char *)&c->reqid, sizeof(c->reqid), c);
65 }
66 static struct check_info *get_check(int reqid) {
67   struct check_info *c;
68   if(noit_hash_retrieve(&active_checks, (char *)&reqid, sizeof(reqid),
69                         (void **)&c))
70     return c;
71   return NULL;
72 }
73 static void remove_check(struct check_info *c) {
74   noit_hash_delete(&active_checks, (char *)&c->reqid, sizeof(c->reqid),
75                    NULL, NULL);
76 }
77
78 static int noit_snmp_recur_handler(eventer_t e, int mask, void *closure,
79                                    struct timeval *now);
80
81 static int noit_snmp_init(noit_module_t *self) {
82   register_mib_handlers();
83   read_premib_configs();
84   read_configs();
85   netsnmp_init_mib();
86   init_snmp("noitd");
87   return 0;
88 }
89
90 /* Handling of results */
91 static void noit_snmp_log_results(noit_module_t *self, noit_check_t *check,
92                                   struct snmp_pdu *pdu) {
93   struct check_info *info = check->closure;
94   struct variable_list *vars;
95   struct timeval duration;
96   char buff[128];
97   stats_t current;
98   int nresults = 0;
99
100   noit_check_stats_clear(&current);
101
102   for(vars = pdu->variables; vars; vars = vars->next_variable) {
103     nresults++;
104   }
105
106   gettimeofday(&current.whence, NULL);
107   sub_timeval(current.whence, check->last_fire_time, &duration);
108   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
109   current.available = pdu ? NP_AVAILABLE : NP_UNAVAILABLE;
110   current.state = (nresults == info->noids) ? NP_GOOD : NP_BAD;
111   snprintf(buff, sizeof(buff), "%d/%d gets", nresults, info->noids);
112   current.status = buff;
113
114   /* manipulate the information ourselves */
115   nresults = 0;
116   for(vars = pdu->variables; vars; vars = vars->next_variable) {
117     char *sp;
118     int oid_idx;
119     double float_conv;
120     u_int64_t u64;
121     int64_t i64;
122     char *endptr;
123     char varbuff[256];
124
125     /* find the oid to which this is the response */
126     oid_idx = nresults; /* our current idx is the most likely */
127     if(info->oids[oid_idx].oidlen != vars->name_length ||
128        memcmp(info->oids[oid_idx].oid, vars->name,
129               vars->name_length * sizeof(oid))) {
130       /* Not the most obvious guess */
131       for(oid_idx = info->noids - 1; oid_idx >= 0; oid_idx--) {
132         if(info->oids[oid_idx].oidlen == vars->name_length &&
133            memcmp(info->oids[oid_idx].oid, vars->name,
134                   vars->name_length * sizeof(oid))) break;
135       }
136     }
137     if(oid_idx < 0) {
138       snprint_variable(varbuff, sizeof(varbuff),
139                        vars->name, vars->name_length, vars);
140       noitL(nlerr, "Unexpected oid results to %s`%s`%s: %s\n",
141             check->target, check->module, check->name, varbuff);
142       nresults++;
143       continue;
144     }
145    
146 #define SETM(a,b) noit_stats_set_metric(&current, \
147                                         info->oids[oid_idx].confname, a, b)
148     switch(vars->type) {
149       case ASN_OCTET_STR:
150         sp = malloc(1 + vars->val_len);
151         memcpy(sp, vars->val.string, vars->val_len);
152         sp[vars->val_len] = '\0';
153         SETM(METRIC_STRING, sp);
154         free(sp);
155         break;
156       case ASN_INTEGER:
157       case ASN_GAUGE:
158         SETM(METRIC_INT32, vars->val.integer);
159         break;
160       case ASN_TIMETICKS:
161       case ASN_COUNTER:
162         SETM(METRIC_UINT32, vars->val.integer);
163         break;
164       case ASN_INTEGER64:
165         printI64(varbuff, vars->val.counter64);
166         i64 = strtoll(varbuff, &endptr, 10);
167         SETM(METRIC_INT64, (varbuff == endptr) ? NULL : &i64);
168         break;
169       case ASN_COUNTER64:
170         printU64(varbuff, vars->val.counter64);
171         u64 = strtoull(varbuff, &endptr, 10);
172         SETM(METRIC_UINT64, (varbuff == endptr) ? NULL : &u64);
173         break;
174       case ASN_FLOAT:
175         if(vars->val.floatVal) float_conv = *(vars->val.floatVal);
176         SETM(METRIC_DOUBLE, vars->val.floatVal ? &float_conv : NULL);
177         break;
178       case ASN_DOUBLE:
179         SETM(METRIC_DOUBLE, vars->val.doubleVal);
180         break;
181       default:
182         snprint_variable(varbuff, sizeof(varbuff), vars->name, vars->name_length, vars);
183         printf("%s!\n", varbuff);
184         /* Advance passed the first space and use that unless there
185          * is no space or we have no more string left.
186          */
187         sp = strchr(varbuff, ' ');
188         if(sp) sp++;
189         SETM(METRIC_STRING, (sp && *sp) ? sp : NULL);
190     }
191     nresults++;
192   }
193   noit_check_set_stats(self, check, &current);
194 }
195
196 struct target_session *
197 _get_target_session(char *target) {
198   struct target_session *ts;
199   if(!noit_hash_retrieve(&target_sessions,
200                          target, strlen(target), (void **)&ts)) {
201     ts = calloc(1, sizeof(*ts));
202     ts->fd = -1;
203     ts->refcnt = 0;
204     ts->in_table = 1;
205     noit_hash_store(&target_sessions,
206                     strdup(target), strlen(target), ts);
207   }
208   return ts;
209 }
210
211 static int noit_snmp_session_cleanse(struct target_session *ts) {
212   if(ts->refcnt == 0 && ts->sess_handle) {
213     eventer_remove_fd(ts->fd);
214     if(ts->timeoutevent) eventer_remove(ts->timeoutevent);
215     ts->timeoutevent = NULL;
216     snmp_sess_close(ts->sess_handle);
217     ts->sess_handle = NULL;
218     if(!ts->in_table) {
219       free(ts);
220     }
221     return 1;
222   }
223   return 0;
224 }
225
226 static int noit_snmp_session_timeout(eventer_t e, int mask, void *closure,
227                                      struct timeval *now) {
228   struct target_session *ts = closure;
229   snmp_sess_timeout(ts->sess_handle);
230   noit_snmp_session_cleanse(ts);
231   return 0;
232 }
233
234 static int noit_snmp_check_timeout(eventer_t e, int mask, void *closure,
235                                    struct timeval *now) {
236   struct check_info *info = closure;
237   info->timedout = 1;
238   remove_check(info);
239   if(info->timeoutevent) {
240     eventer_remove(info->timeoutevent);
241     eventer_free(info->timeoutevent);
242     info->timeoutevent = NULL;
243   }
244   /* Log our findings */
245   noit_snmp_log_results(info->self, info->check, NULL);
246   info->check->flags &= ~NP_RUNNING;
247   return 0;
248 }
249
250 static void _set_ts_timeout(struct target_session *ts, struct timeval *t) {
251   struct timeval now;
252   eventer_t e = NULL;
253   if(ts->timeoutevent) e = eventer_remove(ts->timeoutevent);
254   ts->timeoutevent = NULL;
255   if(!t) return;
256
257   gettimeofday(&now, NULL);
258   if(!e) e = eventer_alloc();
259   e->callback = noit_snmp_session_timeout;
260   e->closure = ts;
261   e->mask = EVENTER_TIMER;
262   add_timeval(now, *t, &e->whence);
263   ts->timeoutevent = e;
264   eventer_add(e);
265 }
266
267 static int noit_snmp_handler(eventer_t e, int mask, void *closure,
268                              struct timeval *now) {
269   fd_set fdset;
270   int fds, block = 0;
271   struct timeval timeout;
272   struct target_session *ts = closure;
273   FD_ZERO(&fdset);
274   FD_SET(e->fd, &fdset);
275   fds = e->fd + 1;
276   snmp_sess_read(ts->sess_handle, &fdset);
277   if(noit_snmp_session_cleanse(ts))
278     return 0;
279   snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
280   _set_ts_timeout(ts, block ? &timeout : NULL);
281   return EVENTER_READ | EVENTER_EXCEPTION;
282 }
283 static int noit_snmp_asynch_response(int operation, struct snmp_session *sp,
284                                      int reqid, struct snmp_pdu *pdu,
285                                      void *magic) {
286   struct check_info *info;
287   struct target_session *ts = magic;
288
289   /* We don't deal with refcnt hitting zero here.  We could only be hit from
290    * the snmp read/timeout stuff.  Handle it there.
291    */
292   ts->refcnt--;
293
294   info = get_check(reqid);
295   if(!info) return 1;
296   remove_check(info);
297   if(info->timeoutevent) {
298     eventer_remove(info->timeoutevent);
299     eventer_free(info->timeoutevent);
300     info->timeoutevent = NULL;
301   }
302
303   /* Log our findings */
304   noit_snmp_log_results(info->self, info->check, pdu);
305   info->check->flags &= ~NP_RUNNING;
306   return 1;
307 }
308
309 static void noit_snmp_sess_open(struct target_session *ts,
310                                 noit_check_t *check) {
311   const char *community;
312   struct snmp_session sess;
313   snmp_sess_init(&sess);
314   sess.version = SNMP_VERSION_2c;
315   sess.peername = check->target;
316   if(!noit_hash_retrieve(check->config, "community", strlen("community"),
317                          (void **)&community)) {
318     community = "public";
319   }
320   sess.community = (unsigned char *)community;
321   sess.community_len = strlen(community);
322   sess.callback = noit_snmp_asynch_response;
323   sess.callback_magic = ts;
324   ts->sess_handle = snmp_sess_open(&sess);
325 }
326
327 static int noit_snmp_fill_req(struct snmp_pdu *req, noit_check_t *check) {
328   int i, klen;
329   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
330   const char *name, *value;
331   struct check_info *info = check->closure;
332   noit_hash_table check_attrs_hash = NOIT_HASH_EMPTY;
333
334   /* Toss the old set and bail if we have zero */
335   if(info->oids) {
336     for(i=0; i<info->noids;i++) {
337       if(info->oids[i].confname) free(info->oids[i].confname);
338       if(info->oids[i].oidname) free(info->oids[i].oidname);
339     }
340     free(info->oids);
341   }
342   info->noids = 0;
343   info->oids = NULL;
344
345   /* Figure our how many. */
346   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value)) {
347     if(!strncasecmp(name, "oid_", 4)) {
348       info->noids++;
349     }
350   }
351
352   if(info->noids == 0) return 0;
353
354   /* Create a hash of important check attributes */
355 #define CA_STORE(a,b) noit_hash_store(&check_attrs_hash, a, strlen(a), b)
356   CA_STORE("target", check->target);
357   CA_STORE("name", check->name);
358   CA_STORE("module", check->module);
359
360   /* Fill out the new set of required oids */
361   info->oids = calloc(info->noids, sizeof(*info->oids));
362   memset(&iter, 0, sizeof(iter));
363   i = 0;
364   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value)) {
365     if(!strncasecmp(name, "oid_", 4)) {
366       char oidbuff[128];
367       name += 4;
368       info->oids[i].confname = strdup(name);
369       noit_check_interpolate(oidbuff, sizeof(oidbuff), value,
370                              &check_attrs_hash, check->config);
371       info->oids[i].oidname = strdup(oidbuff);
372       info->oids[i].oidlen = MAX_OID_LEN;
373       get_node(oidbuff, info->oids[i].oid, &info->oids[i].oidlen);
374       read_objid(oidbuff, info->oids[i].oid, &info->oids[i].oidlen);
375       snmp_add_null_var(req, info->oids[i].oid, info->oids[i].oidlen);
376       i++;
377     }
378   }
379   assert(info->noids == i);
380   noit_hash_destroy(&check_attrs_hash, NULL, NULL);
381   return info->noids;
382 }
383 static int noit_snmp_send(noit_module_t *self, noit_check_t *check) {
384   struct snmp_pdu *req;
385   struct target_session *ts;
386   struct check_info *info = check->closure;
387
388   info->self = self;
389   info->check = check;
390   info->timedout = 0;
391
392   check->flags |= NP_RUNNING;
393   ts = _get_target_session(check->target);
394   if(!ts->refcnt) {
395     eventer_t newe;
396     int fds, block;
397     struct timeval timeout;
398     fd_set fdset;
399     noit_snmp_sess_open(ts, check);
400     block = 0;
401     fds = 0;
402     FD_ZERO(&fdset);
403     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
404     assert(fds > 0);
405     ts->fd = fds-1;
406     newe = eventer_alloc();
407     newe->fd = ts->fd;
408     newe->callback = noit_snmp_handler;
409     newe->closure = ts;
410     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
411     eventer_add(newe);
412   }
413   if(!ts->sess_handle) {
414     /* Error */
415   }
416   ts->refcnt++; /* Increment here, decrement when this check completes */
417
418   req = snmp_pdu_create(SNMP_MSG_GET);
419   noit_snmp_fill_req(req, check);
420   /* Setup out snmp requests */
421   if(ts->sess_handle &&
422      (info->reqid = snmp_sess_send(ts->sess_handle, req)) != 0) {
423     struct timeval when, to;
424     info->timeoutevent = eventer_alloc();
425     info->timeoutevent->callback = noit_snmp_check_timeout;
426     info->timeoutevent->closure = info;
427     info->timeoutevent->mask = EVENTER_TIMER;
428
429     gettimeofday(&when, NULL);
430     to.tv_sec = check->timeout / 1000;
431     to.tv_usec = (check->timeout % 1000) * 1000;
432     add_timeval(when, to, &info->timeoutevent->whence);
433     eventer_add(info->timeoutevent);
434     add_check(info);
435   }
436   else {
437     ts->refcnt--;
438      noit_snmp_session_cleanse(ts);
439     /* Error */
440     snmp_free_pdu(req);
441     /* Log our findings */
442     noit_snmp_log_results(self, check, NULL);
443     check->flags &= ~NP_RUNNING;
444   }
445   return 0;
446 }
447
448 static int noit_snmp_schedule_next(noit_module_t *self,
449                                    struct timeval *last_check,
450                                    noit_check_t *check,
451                                    struct timeval *now) {
452   eventer_t newe;
453   struct timeval period, earliest;
454   struct snmp_check_closure *scc;
455
456   if(check->period == 0) return 0;
457   if(NOIT_CHECK_DISABLED(check) || NOIT_CHECK_KILLED(check)) return 0;
458
459   /* If we have an event, we know when we intended it to fire.  This means
460    * we should schedule that point + period.
461    */
462   if(now)
463     memcpy(&earliest, now, sizeof(earliest));
464   else
465     gettimeofday(&earliest, NULL);
466   period.tv_sec = check->period / 1000;
467   period.tv_usec = (check->period % 1000) * 1000;
468
469   newe = eventer_alloc();
470   memcpy(&newe->whence, last_check, sizeof(*last_check));
471   add_timeval(newe->whence, period, &newe->whence);
472   if(compare_timeval(newe->whence, earliest) < 0)
473     memcpy(&newe->whence, &earliest, sizeof(earliest));
474   newe->mask = EVENTER_TIMER;
475   newe->callback = noit_snmp_recur_handler;
476   scc = calloc(1, sizeof(*scc));
477   scc->self = self;
478   scc->check = check;
479   newe->closure = scc;
480
481   eventer_add(newe);
482   check->fire_event = newe;
483   return 0;
484 }
485
486 static int noit_snmp_recur_handler(eventer_t e, int mask, void *closure,
487                                    struct timeval *now) {
488   struct snmp_check_closure *cl = closure;
489   cl->check->fire_event = NULL;
490   noit_snmp_schedule_next(cl->self, &e->whence, cl->check, now);
491   noit_snmp_send(cl->self, cl->check);
492   free(cl);
493   return 0;
494 }
495
496 static int noit_snmp_initiate_check(noit_module_t *self, noit_check_t *check,
497                                     int once, noit_check_t *cause) {
498   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
499   if(once) {
500     noit_snmp_send(self, check);
501     return 0;
502   }
503   if(!check->fire_event) {
504     struct timeval epoch;
505     noit_check_fake_last_check(check, &epoch, NULL);
506     noit_snmp_schedule_next(self, &epoch, check, NULL);
507   }
508   return 0;
509 }
510
511 static int noit_snmp_config(noit_module_t *self, noit_hash_table *config) {
512   return 0;
513 }
514 static int noit_snmp_onload(noit_module_t *self) {
515   nlerr = noit_log_stream_find("error/noit_snmp");
516   nldeb = noit_log_stream_find("debug/noit_snmp");
517   if(!nlerr) nlerr = noit_stderr;
518   if(!nldeb) nldeb = noit_debug;
519   eventer_name_callback("noit_snmp/recur_handler", noit_snmp_recur_handler);
520   eventer_name_callback("noit_snmp/check_timeout", noit_snmp_check_timeout);
521   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
522   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
523   return 0;
524 }
525 noit_module_t snmp = {
526   NOIT_MODULE_MAGIC,
527   NOIT_MODULE_ABI_VERSION,
528   "snmp",
529   "SNMP collection",
530   noit_snmp_onload,
531   noit_snmp_config,
532   noit_snmp_init,
533   noit_snmp_initiate_check,
534   NULL /* noit_snmp_cleanup */
535 };
536
Note: See TracBrowser for help on using the browser.