root/src/modules/snmp.c

Revision 4368e685b9fcfafaed0820cde6b5f842c275db34, 16.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 7 years ago)

double remove of timeout

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