root/src/modules/snmp.c

Revision 0500158dc42e74f1ff76353fc2ff80cdb4ee5eb5, 36.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

fixes #209

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <assert.h>
39 #include <math.h>
40 #include <ctype.h>
41
42 #include <net-snmp/net-snmp-config.h>
43 #include <net-snmp/net-snmp-includes.h>
44
45 #include "noit_module.h"
46 #include "noit_check.h"
47 #include "noit_check_tools.h"
48 #include "utils/noit_log.h"
49 #include "utils/noit_hash.h"
50
51 static noit_log_stream_t nlerr = NULL;
52 static noit_log_stream_t nldeb = NULL;
53 static int __snmp_initialize_once = 0;
54
55 #define SNMPV2_TRAPS_PREFIX     SNMP_OID_SNMPMODULES,1,1,5
56 oid trap_prefix[]    = { SNMPV2_TRAPS_PREFIX };
57 oid cold_start_oid[] = { SNMPV2_TRAPS_PREFIX, 1 };  /* SNMPv2-MIB */
58 oid warm_start_oid[] = { SNMPV2_TRAPS_PREFIX, 2 };  /* SNMPv2-MIB */
59 oid link_down_oid[]  = { SNMPV2_TRAPS_PREFIX, 3 };  /* IF-MIB */
60 oid link_up_oid[]    = { SNMPV2_TRAPS_PREFIX, 4 };  /* IF-MIB */
61 oid auth_fail_oid[]  = { SNMPV2_TRAPS_PREFIX, 5 };  /* SNMPv2-MIB */
62 oid egp_xxx_oid[]    = { SNMPV2_TRAPS_PREFIX, 99 }; /* ??? */
63
64 #define SNMPV2_TRAP_OBJS_PREFIX SNMP_OID_SNMPMODULES,1,1,4
65 oid snmptrap_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 1, 0 };
66 size_t snmptrap_oid_len = OID_LENGTH(snmptrap_oid);
67 oid snmptrapenterprise_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 3, 0 };
68 size_t snmptrapenterprise_oid_len = OID_LENGTH(snmptrapenterprise_oid);
69 oid sysuptime_oid[] = { SNMP_OID_MIB2, 1, 3, 0 };
70 size_t sysuptime_oid_len = OID_LENGTH(sysuptime_oid);
71
72 #define SNMPV2_COMM_OBJS_PREFIX SNMP_OID_SNMPMODULES,18,1
73 oid agentaddr_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 3, 0 };
74 size_t agentaddr_oid_len = OID_LENGTH(agentaddr_oid);
75 oid community_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 4, 0 };
76 size_t community_oid_len = OID_LENGTH(community_oid);
77
78 #define RECONNOITER_PREFIX     SNMP_OID_ENTERPRISES,32863,1
79 oid reconnoiter_oid[] = { RECONNOITER_PREFIX };
80 size_t reconnoiter_oid_len = OID_LENGTH(reconnoiter_oid);
81 oid reconnoiter_check_prefix_oid[] = { RECONNOITER_PREFIX,1,1 };
82 size_t reconnoiter_check_prefix_oid_len =
83   OID_LENGTH(reconnoiter_check_prefix_oid);
84 size_t reconnoiter_check_oid_len = OID_LENGTH(reconnoiter_check_prefix_oid) + 8;
85 oid reconnoiter_metric_prefix_oid[] = { RECONNOITER_PREFIX,1,2 };
86 size_t reconnoiter_metric_prefix_oid_len =
87   OID_LENGTH(reconnoiter_metric_prefix_oid);
88
89 oid reconnoiter_check_status_oid[] = { RECONNOITER_PREFIX,1,3};
90 size_t reconnoiter_check_status_oid_len =
91   OID_LENGTH(reconnoiter_check_status_oid);
92 oid reconnoiter_check_state_oid[] = { RECONNOITER_PREFIX,1,3,1};
93 size_t reconnoiter_check_state_oid_len =
94   OID_LENGTH(reconnoiter_check_state_oid);
95 oid reconnoiter_check_state_unknown_oid[] = { RECONNOITER_PREFIX,1,3,1,0};
96 oid reconnoiter_check_state_good_oid[] = { RECONNOITER_PREFIX,1,3,1,1};
97 oid reconnoiter_check_state_bad_oid[] = { RECONNOITER_PREFIX,1,3,1,2};
98 size_t reconnoiter_check_state_val_len =
99   OID_LENGTH(reconnoiter_check_state_unknown_oid);
100 /* Boolean */
101 oid reconnoiter_check_available_oid[] = { RECONNOITER_PREFIX,1,3,2};
102 size_t reconnoiter_check_available_oid_len =
103   OID_LENGTH(reconnoiter_check_available_oid);
104 oid reconnoiter_check_available_unknown_oid[] = { RECONNOITER_PREFIX,1,3,2,0};
105 oid reconnoiter_check_available_yes_oid[] = { RECONNOITER_PREFIX,1,3,2,1};
106 oid reconnoiter_check_available_no_oid[] = { RECONNOITER_PREFIX,1,3,2,2};
107 size_t reconnoiter_check_available_val_len =
108   OID_LENGTH(reconnoiter_check_available_unknown_oid);
109 /* timeticks? gauge/unsigned? */
110 oid reconnoiter_check_duration_oid[] = { RECONNOITER_PREFIX,1,3,3};
111 size_t reconnoiter_check_duration_oid_len =
112   OID_LENGTH(reconnoiter_check_duration_oid);
113 /* string */
114 oid reconnoiter_check_status_msg_oid[] = { RECONNOITER_PREFIX,1,3,4};
115 size_t reconnoiter_check_status_msg_oid_len =
116   OID_LENGTH(reconnoiter_check_status_msg_oid);
117
118 typedef struct _mod_config {
119   noit_hash_table *options;
120   noit_hash_table target_sessions;
121 } snmp_mod_config_t;
122
123 struct target_session {
124   void *sess_handle;
125   noit_module_t *self;
126   char *target;
127   eventer_t timeoutevent;
128   int fd;
129   int in_table;
130   int refcnt;
131 };
132
133 struct snmp_check_closure {
134   noit_module_t *self;
135   noit_check_t *check;
136 };
137
138 struct check_info {
139   int reqid;
140   int timedout;
141   struct {
142      char *confname;
143      char *oidname;
144      oid oid[MAX_OID_LEN];
145      size_t oidlen;
146   } *oids;
147   int noids;
148   eventer_t timeoutevent;
149   noit_module_t *self;
150   noit_check_t *check;
151 };
152
153 /* We hold struct check_info's in there key's by their reqid.
154  *   If they timeout, we remove them.
155  *
156  *   When SNMP queries complete, we look them up, if we find them
157  *   then we know we can remove the timeout and  complete the check.
158  *   If we don't find them, the timeout fired and removed the check.
159  */
160 noit_hash_table active_checks = NOIT_HASH_EMPTY;
161 static void add_check(struct check_info *c) {
162   noit_hash_store(&active_checks, (char *)&c->reqid, sizeof(c->reqid), c);
163 }
164 static struct check_info *get_check(int reqid) {
165   void *vc;
166   if(noit_hash_retrieve(&active_checks, (char *)&reqid, sizeof(reqid), &vc))
167     return (struct check_info *)vc;
168   return NULL;
169 }
170 static void remove_check(struct check_info *c) {
171   noit_hash_delete(&active_checks, (char *)&c->reqid, sizeof(c->reqid),
172                    NULL, NULL);
173 }
174
175 struct target_session *
176 _get_target_session(noit_module_t *self, char *target) {
177   void *vts;
178   struct target_session *ts;
179   snmp_mod_config_t *conf;
180   conf = noit_module_get_userdata(self);
181   if(!noit_hash_retrieve(&conf->target_sessions,
182                          target, strlen(target), &vts)) {
183     ts = calloc(1, sizeof(*ts));
184     ts->self = self;
185     ts->fd = -1;
186     ts->refcnt = 0;
187     ts->target = strdup(target);
188     ts->in_table = 1;
189     noit_hash_store(&conf->target_sessions,
190                     ts->target, strlen(ts->target), ts);
191     vts = ts;
192   }
193   return (struct target_session *)vts;
194 }
195
196 /* Handling of results */
197 static void noit_snmp_log_results(noit_module_t *self, noit_check_t *check,
198                                   struct snmp_pdu *pdu) {
199   struct check_info *info = check->closure;
200   struct variable_list *vars;
201   struct timeval duration;
202   char buff[128];
203   stats_t current;
204   int nresults = 0;
205
206   noit_check_stats_clear(&current);
207
208   if(pdu)
209     for(vars = pdu->variables; vars; vars = vars->next_variable)
210       nresults++;
211
212   gettimeofday(&current.whence, NULL);
213   sub_timeval(current.whence, check->last_fire_time, &duration);
214   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
215   current.available = pdu ? NP_AVAILABLE : NP_UNAVAILABLE;
216   current.state = (nresults == info->noids) ? NP_GOOD : NP_BAD;
217   snprintf(buff, sizeof(buff), "%d/%d gets", nresults, info->noids);
218   current.status = buff;
219
220   /* We have no results over which to iterate. */
221   if(!pdu) {
222     noit_check_set_stats(self, check, &current);
223     return;
224   }
225
226   /* manipulate the information ourselves */
227   nresults = 0;
228   for(vars = pdu->variables; vars; vars = vars->next_variable) {
229     char *sp;
230     int oid_idx;
231     double float_conv;
232     u_int64_t u64;
233     int64_t i64;
234     char *endptr;
235     char varbuff[256];
236
237     /* find the oid to which this is the response */
238     oid_idx = nresults; /* our current idx is the most likely */
239     if(info->oids[oid_idx].oidlen != vars->name_length ||
240        memcmp(info->oids[oid_idx].oid, vars->name,
241               vars->name_length * sizeof(oid))) {
242       /* Not the most obvious guess */
243       for(oid_idx = info->noids - 1; oid_idx >= 0; oid_idx--) {
244         if(info->oids[oid_idx].oidlen == vars->name_length &&
245            memcmp(info->oids[oid_idx].oid, vars->name,
246                   vars->name_length * sizeof(oid))) break;
247       }
248     }
249     if(oid_idx < 0) {
250       snprint_variable(varbuff, sizeof(varbuff),
251                        vars->name, vars->name_length, vars);
252       noitL(nlerr, "Unexpected oid results to %s`%s`%s: %s\n",
253             check->target, check->module, check->name, varbuff);
254       nresults++;
255       continue;
256     }
257    
258 #define SETM(a,b) noit_stats_set_metric(&current, \
259                                         info->oids[oid_idx].confname, a, b)
260     switch(vars->type) {
261       case ASN_OCTET_STR:
262         sp = malloc(1 + vars->val_len);
263         memcpy(sp, vars->val.string, vars->val_len);
264         sp[vars->val_len] = '\0';
265         SETM(METRIC_STRING, sp);
266         free(sp);
267         break;
268       case ASN_INTEGER:
269       case ASN_GAUGE:
270         SETM(METRIC_INT32, vars->val.integer);
271         break;
272       case ASN_TIMETICKS:
273       case ASN_COUNTER:
274         SETM(METRIC_UINT32, vars->val.integer);
275         break;
276       case ASN_INTEGER64:
277         printI64(varbuff, vars->val.counter64);
278         i64 = strtoll(varbuff, &endptr, 10);
279         SETM(METRIC_INT64, (varbuff == endptr) ? NULL : &i64);
280         break;
281       case ASN_COUNTER64:
282         printU64(varbuff, vars->val.counter64);
283         u64 = strtoull(varbuff, &endptr, 10);
284         SETM(METRIC_UINT64, (varbuff == endptr) ? NULL : &u64);
285         break;
286       case ASN_FLOAT:
287         if(vars->val.floatVal) float_conv = *(vars->val.floatVal);
288         SETM(METRIC_DOUBLE, vars->val.floatVal ? &float_conv : NULL);
289         break;
290       case ASN_DOUBLE:
291         SETM(METRIC_DOUBLE, vars->val.doubleVal);
292         break;
293       case SNMP_NOSUCHOBJECT:
294       case SNMP_NOSUCHINSTANCE:
295         SETM(METRIC_STRING, NULL);
296         break;
297       default:
298         snprint_variable(varbuff, sizeof(varbuff), vars->name, vars->name_length, vars);
299         /* Advance passed the first space and use that unless there
300          * is no space or we have no more string left.
301          */
302         sp = strchr(varbuff, ' ');
303         if(sp) sp++;
304         SETM(METRIC_STRING, (sp && *sp) ? sp : NULL);
305     }
306     nresults++;
307   }
308   noit_check_set_stats(self, check, &current);
309 }
310
311 static int noit_snmp_session_cleanse(struct target_session *ts) {
312   if(ts->refcnt == 0 && ts->sess_handle) {
313     eventer_remove_fd(ts->fd);
314     if(ts->timeoutevent) {
315       eventer_remove(ts->timeoutevent);
316       ts->timeoutevent = NULL;
317     }
318     snmp_sess_close(ts->sess_handle);
319     ts->sess_handle = NULL;
320     if(!ts->in_table) {
321       free(ts);
322     }
323     return 1;
324   }
325   return 0;
326 }
327
328 static int noit_snmp_session_timeout(eventer_t e, int mask, void *closure,
329                                      struct timeval *now) {
330   struct target_session *ts = closure;
331   snmp_sess_timeout(ts->sess_handle);
332   noit_snmp_session_cleanse(ts);
333   if(ts->timeoutevent == e)
334     ts->timeoutevent = NULL; /* this will be freed on return */
335   return 0;
336 }
337
338 static int noit_snmp_check_timeout(eventer_t e, int mask, void *closure,
339                                    struct timeval *now) {
340   struct check_info *info = closure;
341   info->timedout = 1;
342   remove_check(info);
343   /* Log our findings */
344   noit_snmp_log_results(info->self, info->check, NULL);
345   info->check->flags &= ~NP_RUNNING;
346   return 0;
347 }
348
349 static void _set_ts_timeout(struct target_session *ts, struct timeval *t) {
350   struct timeval now;
351   eventer_t e = NULL;
352   if(ts->timeoutevent) {
353     e = eventer_remove(ts->timeoutevent);
354     ts->timeoutevent = NULL;
355   }
356   if(!t) return;
357
358   gettimeofday(&now, NULL);
359   if(!e) e = eventer_alloc();
360   e->callback = noit_snmp_session_timeout;
361   e->closure = ts;
362   e->mask = EVENTER_TIMER;
363   add_timeval(now, *t, &e->whence);
364   ts->timeoutevent = e;
365   eventer_add(e);
366 }
367
368 static int noit_snmp_handler(eventer_t e, int mask, void *closure,
369                              struct timeval *now) {
370   fd_set fdset;
371   int fds, block = 0;
372   struct timeval timeout = { 0, 0 };
373   struct target_session *ts = closure;
374   FD_ZERO(&fdset);
375   FD_SET(e->fd, &fdset);
376   fds = e->fd + 1;
377   snmp_sess_read(ts->sess_handle, &fdset);
378   if(noit_snmp_session_cleanse(ts))
379     return 0;
380   snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
381   _set_ts_timeout(ts, block ? &timeout : NULL);
382   return EVENTER_READ | EVENTER_EXCEPTION;
383 }
384
385 /* This 'convert_v1pdu_to_v2' was cribbed directly from netsnmp */
386 static netsnmp_pdu *
387 convert_v1pdu_to_v2( netsnmp_pdu* template_v1pdu ) {
388   netsnmp_pdu *template_v2pdu;
389   netsnmp_variable_list *first_vb;
390   netsnmp_variable_list *var;
391   oid enterprise[MAX_OID_LEN];
392   size_t enterprise_len;
393
394   /*
395    * Make a copy of the v1 Trap PDU
396    *   before starting to convert this
397    *   into a v2 Trap PDU.
398    */
399   template_v2pdu = snmp_clone_pdu( template_v1pdu);
400   if(!template_v2pdu) {
401     snmp_log(LOG_WARNING,
402              "send_trap: failed to copy v2 template PDU\n");
403     return NULL;
404   }
405   template_v2pdu->command = SNMP_MSG_TRAP2;
406   first_vb = template_v2pdu->variables;
407
408   /*
409    * Insert an snmpTrapOID varbind before the original v1 varbind list
410    *   either using one of the standard defined trap OIDs,
411    *   or constructing this from the PDU enterprise & specific trap fields
412    */
413   if(template_v1pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
414     memcpy(enterprise, template_v1pdu->enterprise,
415            template_v1pdu->enterprise_length*sizeof(oid));
416     enterprise_len = template_v1pdu->enterprise_length;
417     enterprise[enterprise_len++] = 0;
418     enterprise[enterprise_len++] = template_v1pdu->specific_type;
419   } else {
420     memcpy(enterprise, cold_start_oid, sizeof(cold_start_oid));
421     enterprise[9]  = template_v1pdu->trap_type+1;
422     enterprise_len = sizeof(cold_start_oid)/sizeof(oid);
423   }
424
425   var = NULL;
426   if(!snmp_varlist_add_variable(&var,
427                                 snmptrap_oid, snmptrap_oid_len,
428                                 ASN_OBJECT_ID,
429                                 (u_char*)enterprise,
430                                 enterprise_len*sizeof(oid))) {
431     noitL(nlerr, "send_trap: failed to insert copied snmpTrapOID varbind\n");
432     snmp_free_pdu(template_v2pdu);
433     return NULL;
434   }
435   var->next_variable        = template_v2pdu->variables;
436   template_v2pdu->variables = var;
437
438   /*
439    * Insert a sysUptime varbind at the head of the v2 varbind list
440    */
441   var = NULL;
442   if(!snmp_varlist_add_variable(&var,
443                                 sysuptime_oid, sysuptime_oid_len,
444                                 ASN_TIMETICKS,
445                                 (u_char*)&(template_v1pdu->time),
446                                 sizeof(template_v1pdu->time))) {
447     noitL(nlerr, "send_trap: failed to insert copied sysUptime varbind\n");
448     snmp_free_pdu(template_v2pdu);
449     return NULL;
450   }
451   var->next_variable = template_v2pdu->variables;
452   template_v2pdu->variables = var;
453
454   /*
455    * Append the other three conversion varbinds,
456    *  (snmpTrapAgentAddr, snmpTrapCommunity & snmpTrapEnterprise)
457    *  if they're not already present.
458    *  But don't bomb out completely if there are problems.
459    */
460   var = find_varbind_in_list(template_v2pdu->variables,
461                              agentaddr_oid, agentaddr_oid_len);
462   if(!var && (template_v1pdu->agent_addr[0]
463               || template_v1pdu->agent_addr[1]
464               || template_v1pdu->agent_addr[2]
465               || template_v1pdu->agent_addr[3])) {
466     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
467                                   agentaddr_oid, agentaddr_oid_len,
468                                   ASN_IPADDRESS,
469                                   (u_char*)&(template_v1pdu->agent_addr),
470                                   sizeof(template_v1pdu->agent_addr)))
471       noitL(nlerr, "send_trap: failed to append snmpTrapAddr varbind\n");
472   }
473   var = find_varbind_in_list(template_v2pdu->variables,
474                              community_oid, community_oid_len);
475   if(!var && template_v1pdu->community) {
476     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
477                                   community_oid, community_oid_len,
478                                   ASN_OCTET_STR,
479                                   template_v1pdu->community,
480                                   template_v1pdu->community_len))
481       noitL(nlerr, "send_trap: failed to append snmpTrapCommunity varbind\n");
482   }
483   var = find_varbind_in_list(template_v2pdu->variables,
484                              snmptrapenterprise_oid,
485                              snmptrapenterprise_oid_len);
486   if(!var &&
487      template_v1pdu->trap_type != SNMP_TRAP_ENTERPRISESPECIFIC) {
488     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
489                                   snmptrapenterprise_oid,
490                                   snmptrapenterprise_oid_len,
491                                   ASN_OBJECT_ID,
492                                   (u_char*)template_v1pdu->enterprise,
493                                   template_v1pdu->enterprise_length*sizeof(oid)))
494       noitL(nlerr, "send_trap: failed to append snmpEnterprise varbind\n");
495   }
496   return template_v2pdu;
497 }
498
499 static int noit_snmp_oid_to_checkid(oid *o, int l, uuid_t checkid, char *out) {
500   int i;
501   char _uuid_str[UUID_STR_LEN+1], *cp, *uuid_str;
502
503   uuid_str = out ? out : _uuid_str;
504   if(l != reconnoiter_check_oid_len) {
505     noitL(nlerr, "unsupported (length) trap recieved\n");
506     return -1;
507   }
508   if(netsnmp_oid_equals(o,
509                         reconnoiter_check_prefix_oid_len,
510                         reconnoiter_check_prefix_oid,
511                         reconnoiter_check_prefix_oid_len) != 0) {
512     noitL(nlerr, "unsupported (wrong namespace) trap recieved\n");
513     return -1;
514   }
515   /* encode this as a uuid */
516   cp = uuid_str;
517   for(i=0;
518       i < reconnoiter_check_oid_len - reconnoiter_check_prefix_oid_len;
519       i++) {
520     oid v = o[i + reconnoiter_check_prefix_oid_len];
521     if(v < 0 || v > 0xffff) {
522       noitL(nlerr, "trap target oid [%ld] out of range\n", v);
523       return -1;
524     }
525     snprintf(cp, 5, "%04x", (unsigned short)(v & 0xffff));
526     cp += 4;
527     /* hyphens after index 1,2,3,4 */
528     if(i > 0 && i < 5) *cp++ = '-';
529   }
530   if(uuid_parse(uuid_str, checkid) != 0) {
531     noitL(nlerr, "unexpected error decoding trap uuid '%s'\n", uuid_str);
532     return -1;
533   }
534   return 0;
535 }
536
537 #define isoid(a,b,c,d) (netsnmp_oid_equals(a,b,c,d) == 0)
538 #define isoidprefix(a,b,c,d) (netsnmp_oid_equals(a,MIN(b,d),c,d) == 0)
539 #define setstatus(st,soid,sv) \
540   if(isoid(o,l,soid,reconnoiter_check_state_val_len)) current->st = sv
541
542 static int
543 noit_snmp_trapvars_to_stats(stats_t *current, netsnmp_variable_list *var) {
544   if(isoidprefix(var->name, var->name_length, reconnoiter_check_status_oid,
545                  reconnoiter_check_status_oid_len)) {
546     if(var->type == ASN_OBJECT_ID) {
547       if(isoid(var->name, var->name_length,
548                reconnoiter_check_state_oid, reconnoiter_check_state_oid_len)) {
549         oid *o = var->val.objid;
550         size_t l = var->val_len / sizeof(*o);
551         setstatus(state, reconnoiter_check_state_unknown_oid, NP_UNKNOWN);
552         else setstatus(state, reconnoiter_check_state_good_oid, NP_GOOD);
553         else setstatus(state, reconnoiter_check_state_bad_oid, NP_BAD);
554         else return -1;
555       }
556       else if(isoid(var->name, var->name_length,
557                     reconnoiter_check_available_oid,
558                     reconnoiter_check_available_oid_len)) {
559         oid *o = var->val.objid;
560         size_t l = var->val_len / sizeof(*o);
561         setstatus(available, reconnoiter_check_available_unknown_oid, NP_UNKNOWN);
562         else setstatus(available, reconnoiter_check_available_yes_oid, NP_AVAILABLE);
563         else setstatus(available, reconnoiter_check_available_no_oid, NP_UNAVAILABLE);
564         else return -1;
565       }
566       else {
567         /* We don't unerstand any other OBJECT_ID types */
568         return -1;
569       }
570     }
571     else if(var->type == ASN_UNSIGNED) {
572       /* This is only for the duration (in ms) */
573       if(isoid(var->name, var->name_length,
574                reconnoiter_check_duration_oid,
575                reconnoiter_check_duration_oid_len)) {
576         current->duration = *(var->val.integer);
577       }
578       else
579         return -1;
580     }
581     else if(var->type == ASN_OCTET_STR) {
582       /* This is only for the status message */
583       if(isoid(var->name, var->name_length,
584                reconnoiter_check_status_msg_oid,
585                reconnoiter_check_status_msg_oid_len)) {
586         current->status = malloc(var->val_len + 1);
587         memcpy(current->status, var->val.string, var->val_len);
588         current->status[var->val_len] = '\0';
589       }
590       else
591         return -1;
592     }
593     else {
594       /* I don't understand any other type of status message */
595       return -1;
596     }
597   }
598   else if(isoidprefix(var->name, var->name_length,
599                       reconnoiter_metric_prefix_oid,
600                       reconnoiter_metric_prefix_oid_len)) {
601     /* decode the metric and store the value */
602     int i, len;
603     u_int64_t u64;
604     double doubleVal;
605     char metric_name[128], buff[128], *cp;
606     if(var->name_length <= reconnoiter_metric_prefix_oid_len) return -1;
607     len = var->name[reconnoiter_metric_prefix_oid_len];
608     if(var->name_length != (reconnoiter_metric_prefix_oid_len + 1 + len) ||
609        len > sizeof(metric_name) - 1) {
610       noitL(nlerr, "snmp trap, malformed metric name\n");
611       return -1;
612     }
613     for(i=0;i<len;i++) {
614       ((unsigned char *)metric_name)[i] =
615         (unsigned char)var->name[reconnoiter_metric_prefix_oid_len + 1 + i];
616       if(!isprint(metric_name[i])) {
617         noitL(nlerr, "metric_name contains unprintable characters\n");
618         return -1;
619       }
620     }
621     metric_name[i] = '\0';
622     switch(var->type) {
623       case ASN_INTEGER:
624       case ASN_UINTEGER:
625       case ASN_TIMETICKS:
626       case ASN_INTEGER64:
627         noit_stats_set_metric(current, metric_name,
628                               METRIC_INT64, var->val.integer);
629         break;
630       case ASN_COUNTER64:
631         u64 = ((u_int64_t)var->val.counter64->high) << 32;
632         u64 |= var->val.counter64->low;
633         noit_stats_set_metric(current, metric_name,
634                               METRIC_UINT64, &u64);
635         break;
636       case ASN_OPAQUE_FLOAT:
637         doubleVal = (double)*var->val.floatVal;
638         noit_stats_set_metric(current, metric_name,
639                               METRIC_DOUBLE, &doubleVal);
640         break;
641       case ASN_OPAQUE_DOUBLE:
642         noit_stats_set_metric(current, metric_name,
643                               METRIC_DOUBLE, var->val.doubleVal);
644         break;
645       case ASN_OCTET_STR:
646         snprint_value(buff, sizeof(buff), var->name, var->name_length, var);
647         /* Advance passed the first space and use that unless there
648          * is no space or we have no more string left.
649          */
650         cp = strchr(buff, ' ');
651         if(cp) {
652           char *ecp;
653           cp++;
654           if(*cp == '"') {
655             ecp = cp + strlen(cp) - 1;
656             if(*ecp == '"') {
657               cp++; *ecp = '\0';
658             }
659           }
660         }
661         noit_stats_set_metric(current, metric_name,
662                               METRIC_STRING, (cp && *cp) ? cp : NULL);
663         break;
664       default:
665         noitL(nlerr, "snmp trap unsupport data type %d\n", var->type);
666     }
667     noitL(nldeb, "metric_name -> '%s'\n", metric_name);
668   }
669   else {
670     /* No idea what this is */
671     return -1;
672   }
673   return 0;
674 }
675 static int noit_snmp_trapd_response(int operation, struct snmp_session *sp,
676                                     int reqid, struct snmp_pdu *pdu,
677                                     void *magic) {
678   /* the noit pieces */
679   noit_check_t *check;
680   struct target_session *ts = magic;
681   snmp_mod_config_t *conf;
682   const char *community = NULL;
683   stats_t current;
684   int success = 0;
685
686   /* parsing destination */
687   char uuid_str[UUID_STR_LEN + 1];
688   uuid_t checkid;
689
690   /* snmp oid parsing helper vars */
691   netsnmp_pdu *newpdu = pdu;
692   netsnmp_variable_list *var;
693
694   conf = noit_module_get_userdata(ts->self);
695
696   if(pdu->version == SNMP_VERSION_1)
697     newpdu = convert_v1pdu_to_v2(pdu);
698   if(!newpdu || newpdu->version != SNMP_VERSION_2c) goto cleanup;
699
700   for(var = newpdu->variables; var != NULL; var = var->next_variable) {
701     if(netsnmp_oid_equals(var->name, var->name_length,
702                           snmptrap_oid, snmptrap_oid_len) == 0)
703       break;
704   }
705
706   if (!var || var->type != ASN_OBJECT_ID) {
707     noitL(nlerr, "unsupport trap (not a trap?) received\n");
708     goto cleanup;
709   }
710
711   /* var is the oid on which we are trapping.
712    * It should be in the reconnoiter check prefix.
713    */
714   if(noit_snmp_oid_to_checkid(var->val.objid, var->val_len/sizeof(oid),
715                               checkid, uuid_str)) {
716     goto cleanup;
717   }
718   noitL(nldeb, "recieved trap for %s\n", uuid_str);
719   check = noit_poller_lookup(checkid);
720   if(!check) {
721     noitL(nlerr, "trap received for non-existent check '%s'\n", uuid_str);
722     goto cleanup;
723   }
724   if(!noit_hash_retr_str(check->config, "community", strlen("community"),
725                          &community) &&
726      !noit_hash_retr_str(conf->options, "community", strlen("community"),
727                          &community)) {
728     noitL(nlerr, "No community defined for check, dropping trap\n");
729     goto cleanup;
730   }
731
732   if(strlen(community) != newpdu->community_len ||
733      memcmp(community, newpdu->community, newpdu->community_len)) {
734     noitL(nlerr, "trap attempt with wrong community string\n");
735     goto cleanup;
736   }
737
738   /* We have a check. The trap is authorized. Now, extract everything. */
739   memset(&current, 0, sizeof(current));
740   gettimeofday(&current.whence, NULL);
741   current.available = NP_AVAILABLE;
742
743   for(; var != NULL; var = var->next_variable)
744     if(noit_snmp_trapvars_to_stats(&current, var) == 0) success++;
745   if(success) {
746     char buff[24];
747     snprintf(buff, sizeof(buff), "%d datum", success);
748     current.state = NP_GOOD;
749     current.status = strdup(buff);
750   }
751   else {
752     current.state = NP_BAD;
753     current.status = strdup("no data");
754   }
755   noit_check_set_stats(ts->self, check, &current);
756
757  cleanup:
758   if(newpdu != pdu)
759     snmp_free_pdu(newpdu);
760   return 0;
761 }
762 static int noit_snmp_asynch_response(int operation, struct snmp_session *sp,
763                                      int reqid, struct snmp_pdu *pdu,
764                                      void *magic) {
765   struct check_info *info;
766   struct target_session *ts = magic;
767
768   /* We don't deal with refcnt hitting zero here.  We could only be hit from
769    * the snmp read/timeout stuff.  Handle it there.
770    */
771   ts->refcnt--;
772
773   info = get_check(reqid);
774   if(!info) return 1;
775   remove_check(info);
776   if(info->timeoutevent) {
777     eventer_remove(info->timeoutevent);
778     eventer_free(info->timeoutevent);
779     info->timeoutevent = NULL;
780   }
781
782   /* Log our findings */
783   noit_snmp_log_results(info->self, info->check, pdu);
784   info->check->flags &= ~NP_RUNNING;
785   return 1;
786 }
787
788 static void noit_snmp_sess_open(struct target_session *ts,
789                                 noit_check_t *check) {
790   const char *community;
791   struct snmp_session sess;
792   snmp_sess_init(&sess);
793   sess.version = SNMP_VERSION_2c;
794   sess.peername = ts->target;
795   if(!noit_hash_retr_str(check->config, "community", strlen("community"),
796                          &community)) {
797     community = "public";
798   }
799   sess.community = (unsigned char *)community;
800   sess.community_len = strlen(community);
801   sess.callback = noit_snmp_asynch_response;
802   sess.callback_magic = ts;
803   ts->sess_handle = snmp_sess_open(&sess);
804 }
805
806 static int noit_snmp_fill_req(struct snmp_pdu *req, noit_check_t *check) {
807   int i, klen;
808   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
809   const char *name, *value;
810   struct check_info *info = check->closure;
811   noit_hash_table check_attrs_hash = NOIT_HASH_EMPTY;
812
813   /* Toss the old set and bail if we have zero */
814   if(info->oids) {
815     for(i=0; i<info->noids;i++) {
816       if(info->oids[i].confname) free(info->oids[i].confname);
817       if(info->oids[i].oidname) free(info->oids[i].oidname);
818     }
819     free(info->oids);
820   }
821   info->noids = 0;
822   info->oids = NULL;
823
824   /* Figure our how many. */
825   while(noit_hash_next_str(check->config, &iter, &name, &klen, &value)) {
826     if(!strncasecmp(name, "oid_", 4)) {
827       info->noids++;
828     }
829   }
830
831   if(info->noids == 0) return 0;
832
833   /* Create a hash of important check attributes */
834   noit_check_make_attrs(check, &check_attrs_hash);
835
836   /* Fill out the new set of required oids */
837   info->oids = calloc(info->noids, sizeof(*info->oids));
838   memset(&iter, 0, sizeof(iter));
839   i = 0;
840   while(noit_hash_next_str(check->config, &iter, &name, &klen, &value)) {
841     if(!strncasecmp(name, "oid_", 4)) {
842       char oidbuff[128];
843       name += 4;
844       info->oids[i].confname = strdup(name);
845       noit_check_interpolate(oidbuff, sizeof(oidbuff), value,
846                              &check_attrs_hash, check->config);
847       info->oids[i].oidname = strdup(oidbuff);
848       info->oids[i].oidlen = MAX_OID_LEN;
849       get_node(oidbuff, info->oids[i].oid, &info->oids[i].oidlen);
850       snmp_add_null_var(req, info->oids[i].oid, info->oids[i].oidlen);
851       i++;
852     }
853   }
854   assert(info->noids == i);
855   noit_hash_destroy(&check_attrs_hash, NULL, NULL);
856   return info->noids;
857 }
858 static int noit_snmp_send(noit_module_t *self, noit_check_t *check) {
859   struct snmp_pdu *req;
860   struct target_session *ts;
861   struct check_info *info = check->closure;
862   int port = 161;
863   const char *portstr;
864   char target_port[64];
865
866   info->self = self;
867   info->check = check;
868   info->timedout = 0;
869
870   check->flags |= NP_RUNNING;
871
872   if(noit_hash_retr_str(check->config, "port", strlen("port"),
873                         &portstr)) {
874     port = atoi(portstr);
875   }
876   snprintf(target_port, sizeof(target_port), "%s:%d", check->target, port);
877   ts = _get_target_session(self, target_port);
878   gettimeofday(&check->last_fire_time, NULL);
879   if(!ts->refcnt) {
880     eventer_t newe;
881     int fds, block;
882     struct timeval timeout;
883     fd_set fdset;
884     noit_snmp_sess_open(ts, check);
885     block = 0;
886     fds = 0;
887     FD_ZERO(&fdset);
888     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
889     assert(fds > 0);
890     ts->fd = fds-1;
891     newe = eventer_alloc();
892     newe->fd = ts->fd;
893     newe->callback = noit_snmp_handler;
894     newe->closure = ts;
895     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
896     eventer_add(newe);
897   }
898   if(!ts->sess_handle) {
899     /* Error */
900     /* No need to do anything, this will be handled in the else below */
901   }
902   ts->refcnt++; /* Increment here, decrement when this check completes */
903
904   req = snmp_pdu_create(SNMP_MSG_GET);
905   if(req) noit_snmp_fill_req(req, check);
906   /* Setup out snmp requests */
907   if(ts->sess_handle && req &&
908      (info->reqid = snmp_sess_send(ts->sess_handle, req)) != 0) {
909     struct timeval when, to;
910     info->timeoutevent = eventer_alloc();
911     info->timeoutevent->callback = noit_snmp_check_timeout;
912     info->timeoutevent->closure = info;
913     info->timeoutevent->mask = EVENTER_TIMER;
914
915     gettimeofday(&when, NULL);
916     to.tv_sec = check->timeout / 1000;
917     to.tv_usec = (check->timeout % 1000) * 1000;
918     add_timeval(when, to, &info->timeoutevent->whence);
919     eventer_add(info->timeoutevent);
920     add_check(info);
921   }
922   else {
923     ts->refcnt--;
924     noit_snmp_session_cleanse(ts);
925     /* Error */
926     if(req) snmp_free_pdu(req);
927     /* Log our findings */
928     noit_snmp_log_results(self, check, NULL);
929     check->flags &= ~NP_RUNNING;
930   }
931   return 0;
932 }
933
934 static int noit_snmp_initiate_check(noit_module_t *self, noit_check_t *check,
935                                     int once, noit_check_t *cause) {
936   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
937   INITIATE_CHECK(noit_snmp_send, self, check);
938   return 0;
939 }
940
941 static int noit_snmptrap_initiate_check(noit_module_t *self,
942                                         noit_check_t *check,
943                                         int once, noit_check_t *cause) {
944   /* We don't do anything for snmptrap checks.  Not intuitive... but they
945    * never "run."  We accept input out-of-band via snmp traps.
946    */
947   return 0;
948 }
949
950 static int noit_snmp_config(noit_module_t *self, noit_hash_table *options) {
951   snmp_mod_config_t *conf;
952   conf = noit_module_get_userdata(self);
953   if(conf) {
954     if(conf->options) {
955       noit_hash_destroy(conf->options, free, free);
956       free(conf->options);
957     }
958   }
959   else
960     conf = calloc(1, sizeof(*conf));
961   conf->options = options;
962   noit_module_set_userdata(self, conf);
963   return 1;
964 }
965 static int noit_snmp_onload(noit_image_t *self) {
966   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
967   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
968   if(!nlerr) nlerr = noit_stderr;
969   if(!nldeb) nldeb = noit_debug;
970   eventer_name_callback("noit_snmp/check_timeout", noit_snmp_check_timeout);
971   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
972   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
973   return 0;
974 }
975
976 static int noit_snmptrap_onload(noit_image_t *self) {
977   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
978   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
979   if(!nlerr) nlerr = noit_stderr;
980   if(!nldeb) nldeb = noit_debug;
981   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
982   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
983   return 0;
984 }
985
986 static int noit_snmp_init(noit_module_t *self) {
987   const char *opt;
988   snmp_mod_config_t *conf;
989
990   conf = noit_module_get_userdata(self);
991
992   if(!__snmp_initialize_once) {
993     register_mib_handlers();
994     read_premib_configs();
995     read_configs();
996     init_snmp("noitd");
997     __snmp_initialize_once = 1;
998   }
999
1000   if(strcmp(self->hdr.name, "snmptrap") == 0) {
1001     eventer_t newe;
1002     int i, block = 0, fds = 0;
1003     fd_set fdset;
1004     struct timeval timeout = { 0, 0 };
1005     struct target_session *ts;
1006     netsnmp_transport *transport;
1007     netsnmp_session sess, *session = &sess;
1008
1009     if(!noit_hash_retrieve(conf->options,
1010                            "snmptrapd_port", strlen("snmptrapd_port"),
1011                            (void **)&opt))
1012       opt = "162";
1013
1014     transport = netsnmp_transport_open_server("snmptrap", opt);
1015     if(!transport) {
1016       noitL(nlerr, "cannot open netsnmp transport for trap daemon\n");
1017       return -1;
1018     }
1019     ts = _get_target_session(self, "snmptrapd");
1020     snmp_sess_init(session);
1021     session->peername = SNMP_DEFAULT_PEERNAME;
1022     session->version = SNMP_DEFAULT_VERSION;
1023     session->community_len = SNMP_DEFAULT_COMMUNITY_LEN;
1024     session->retries = SNMP_DEFAULT_RETRIES;
1025     session->timeout = SNMP_DEFAULT_TIMEOUT;
1026     session->callback = noit_snmp_trapd_response;
1027     session->callback_magic = (void *) ts;
1028     session->authenticator = NULL;
1029     session->isAuthoritative = SNMP_SESS_UNKNOWNAUTH;
1030     ts->sess_handle = snmp_sess_add(session, transport, NULL, NULL);
1031
1032     FD_ZERO(&fdset);
1033     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
1034     assert(fds > 0);
1035     for(i=0; i<fds; i++) {
1036       if(FD_ISSET(i, &fdset)) {
1037         ts->refcnt++;
1038         ts->fd = i;
1039         newe = eventer_alloc();
1040         newe->fd = ts->fd;
1041         newe->callback = noit_snmp_handler;
1042         newe->closure = ts;
1043         newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
1044         eventer_add(newe);
1045       }
1046     }
1047   }
1048   return 0;
1049 }
1050
1051 #include "snmp.xmlh"
1052 noit_module_t snmp = {
1053   {
1054     NOIT_MODULE_MAGIC,
1055     NOIT_MODULE_ABI_VERSION,
1056     "snmp",
1057     "SNMP collection",
1058     snmp_xml_description,
1059     noit_snmp_onload
1060   },
1061   noit_snmp_config,
1062   noit_snmp_init,
1063   noit_snmp_initiate_check,
1064   NULL /* noit_snmp_cleanup */
1065 };
1066
1067 #include "snmptrap.xmlh"
1068 noit_module_t snmptrap = {
1069   {
1070     NOIT_MODULE_MAGIC,
1071     NOIT_MODULE_ABI_VERSION,
1072     "snmptrap",
1073     "SNMP trap collection",
1074     snmptrap_xml_description,
1075     noit_snmptrap_onload
1076   },
1077   noit_snmp_config,
1078   noit_snmp_init,
1079   noit_snmptrap_initiate_check,
1080   NULL /* noit_snmp_cleanup */
1081 };
Note: See TracBrowser for help on using the browser.