root/src/modules/snmp.c

Revision f91ddca09660d8415b1bcdd88b20946e82b0ef62, 38.3 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 years ago)

propagate the cause for causal checks into all of the calls

  • 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   struct timeval last_open;
132 };
133
134 struct snmp_check_closure {
135   noit_module_t *self;
136   noit_check_t *check;
137 };
138
139 struct check_info {
140   int reqid;
141   int timedout;
142   struct {
143      char *confname;
144      char *oidname;
145      oid oid[MAX_OID_LEN];
146      size_t oidlen;
147   } *oids;
148   int noids;
149   eventer_t timeoutevent;
150   noit_module_t *self;
151   noit_check_t *check;
152   struct target_session *ts;
153 };
154
155 /* We hold struct check_info's in there key's by their reqid.
156  *   If they timeout, we remove them.
157  *
158  *   When SNMP queries complete, we look them up, if we find them
159  *   then we know we can remove the timeout and  complete the check.
160  *   If we don't find them, the timeout fired and removed the check.
161  */
162 noit_hash_table active_checks = NOIT_HASH_EMPTY;
163 static void add_check(struct check_info *c) {
164   noit_hash_store(&active_checks, (char *)&c->reqid, sizeof(c->reqid), c);
165 }
166 static struct check_info *get_check(int reqid) {
167   void *vc;
168   if(noit_hash_retrieve(&active_checks, (char *)&reqid, sizeof(reqid), &vc))
169     return (struct check_info *)vc;
170   return NULL;
171 }
172 static void remove_check(struct check_info *c) {
173   noit_hash_delete(&active_checks, (char *)&c->reqid, sizeof(c->reqid),
174                    NULL, NULL);
175 }
176
177 struct target_session *
178 _get_target_session(noit_module_t *self, char *target) {
179   void *vts;
180   struct target_session *ts;
181   snmp_mod_config_t *conf;
182   conf = noit_module_get_userdata(self);
183   if(!noit_hash_retrieve(&conf->target_sessions,
184                          target, strlen(target), &vts)) {
185     ts = calloc(1, sizeof(*ts));
186     ts->self = self;
187     ts->fd = -1;
188     ts->refcnt = 0;
189     ts->target = strdup(target);
190     ts->in_table = 1;
191     noit_hash_store(&conf->target_sessions,
192                     ts->target, strlen(ts->target), ts);
193     vts = ts;
194   }
195   return (struct target_session *)vts;
196 }
197
198 /* Handling of results */
199 static void noit_snmp_log_results(noit_module_t *self, noit_check_t *check,
200                                   struct snmp_pdu *pdu) {
201   struct check_info *info = check->closure;
202   struct variable_list *vars;
203   struct timeval duration;
204   char buff[128];
205   stats_t current;
206   int nresults = 0;
207
208   noit_check_stats_clear(&current);
209
210   if(pdu)
211     for(vars = pdu->variables; vars; vars = vars->next_variable)
212       nresults++;
213
214   gettimeofday(&current.whence, NULL);
215   sub_timeval(current.whence, check->last_fire_time, &duration);
216   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
217   current.available = pdu ? NP_AVAILABLE : NP_UNAVAILABLE;
218   current.state = (nresults == info->noids) ? NP_GOOD : NP_BAD;
219   snprintf(buff, sizeof(buff), "%d/%d gets", nresults, info->noids);
220   current.status = buff;
221
222   /* We have no results over which to iterate. */
223   if(!pdu) {
224     noit_check_set_stats(self, check, &current);
225     return;
226   }
227
228   /* manipulate the information ourselves */
229   nresults = 0;
230   for(vars = pdu->variables; vars; vars = vars->next_variable) {
231     char *sp;
232     int oid_idx;
233     double float_conv;
234     u_int64_t u64;
235     int64_t i64;
236     char *endptr;
237     char varbuff[256];
238
239     /* find the oid to which this is the response */
240     oid_idx = nresults; /* our current idx is the most likely */
241     if(info->oids[oid_idx].oidlen != vars->name_length ||
242        memcmp(info->oids[oid_idx].oid, vars->name,
243               vars->name_length * sizeof(oid))) {
244       /* Not the most obvious guess */
245       for(oid_idx = info->noids - 1; oid_idx >= 0; oid_idx--) {
246         if(info->oids[oid_idx].oidlen == vars->name_length &&
247            memcmp(info->oids[oid_idx].oid, vars->name,
248                   vars->name_length * sizeof(oid))) break;
249       }
250     }
251     if(oid_idx < 0) {
252       snprint_variable(varbuff, sizeof(varbuff),
253                        vars->name, vars->name_length, vars);
254       noitL(nlerr, "Unexpected oid results to %s`%s`%s: %s\n",
255             check->target, check->module, check->name, varbuff);
256       nresults++;
257       continue;
258     }
259    
260 #define SETM(a,b) noit_stats_set_metric(&current, \
261                                         info->oids[oid_idx].confname, a, b)
262     switch(vars->type) {
263       case ASN_OCTET_STR:
264         sp = malloc(1 + vars->val_len);
265         memcpy(sp, vars->val.string, vars->val_len);
266         sp[vars->val_len] = '\0';
267         SETM(METRIC_STRING, sp);
268         free(sp);
269         break;
270       case ASN_INTEGER:
271       case ASN_GAUGE:
272         SETM(METRIC_INT32, vars->val.integer);
273         break;
274       case ASN_TIMETICKS:
275       case ASN_COUNTER:
276         SETM(METRIC_UINT32, vars->val.integer);
277         break;
278       case ASN_INTEGER64:
279         printI64(varbuff, vars->val.counter64);
280         i64 = strtoll(varbuff, &endptr, 10);
281         SETM(METRIC_INT64, (varbuff == endptr) ? NULL : &i64);
282         break;
283       case ASN_COUNTER64:
284         printU64(varbuff, vars->val.counter64);
285         u64 = strtoull(varbuff, &endptr, 10);
286         SETM(METRIC_UINT64, (varbuff == endptr) ? NULL : &u64);
287         break;
288       case ASN_FLOAT:
289         if(vars->val.floatVal) float_conv = *(vars->val.floatVal);
290         SETM(METRIC_DOUBLE, vars->val.floatVal ? &float_conv : NULL);
291         break;
292       case ASN_DOUBLE:
293         SETM(METRIC_DOUBLE, vars->val.doubleVal);
294         break;
295       case SNMP_NOSUCHOBJECT:
296       case SNMP_NOSUCHINSTANCE:
297         SETM(METRIC_STRING, NULL);
298         break;
299       default:
300         snprint_variable(varbuff, sizeof(varbuff), vars->name, vars->name_length, vars);
301         /* Advance passed the first space and use that unless there
302          * is no space or we have no more string left.
303          */
304         sp = strchr(varbuff, ' ');
305         if(sp) sp++;
306         SETM(METRIC_STRING, (sp && *sp) ? sp : NULL);
307     }
308     nresults++;
309   }
310   noit_check_set_stats(self, check, &current);
311 }
312
313 static int noit_snmp_session_cleanse(struct target_session *ts) {
314   if(ts->refcnt == 0 && ts->sess_handle) {
315     eventer_remove_fd(ts->fd);
316     ts->fd = -1;
317     if(ts->timeoutevent) {
318       eventer_remove(ts->timeoutevent);
319       ts->timeoutevent = NULL;
320     }
321     snmp_sess_close(ts->sess_handle);
322     ts->sess_handle = NULL;
323     if(!ts->in_table) {
324       free(ts);
325     }
326     return 1;
327   }
328   return 0;
329 }
330
331 static int noit_snmp_session_timeout(eventer_t e, int mask, void *closure,
332                                      struct timeval *now) {
333   struct target_session *ts = closure;
334   snmp_sess_timeout(ts->sess_handle);
335   noit_snmp_session_cleanse(ts);
336   if(ts->timeoutevent == e)
337     ts->timeoutevent = NULL; /* this will be freed on return */
338   return 0;
339 }
340
341 static int noit_snmp_check_timeout(eventer_t e, int mask, void *closure,
342                                    struct timeval *now) {
343   struct check_info *info = closure;
344   info->timedout = 1;
345   if(info->ts) {
346     info->ts->refcnt--;
347     noit_snmp_session_cleanse(info->ts);
348     info->ts = NULL;
349   }
350   remove_check(info);
351   /* Log our findings */
352   noit_snmp_log_results(info->self, info->check, NULL);
353   info->check->flags &= ~NP_RUNNING;
354   return 0;
355 }
356
357 static void _set_ts_timeout(struct target_session *ts, struct timeval *t) {
358   struct timeval now;
359   eventer_t e = NULL;
360   if(ts->timeoutevent) {
361     e = eventer_remove(ts->timeoutevent);
362     ts->timeoutevent = NULL;
363   }
364   if(!t) return;
365
366   gettimeofday(&now, NULL);
367   if(!e) e = eventer_alloc();
368   e->callback = noit_snmp_session_timeout;
369   e->closure = ts;
370   e->mask = EVENTER_TIMER;
371   add_timeval(now, *t, &e->whence);
372   ts->timeoutevent = e;
373   eventer_add(e);
374 }
375
376 static int noit_snmp_handler(eventer_t e, int mask, void *closure,
377                              struct timeval *now) {
378   fd_set fdset;
379   int fds, block = 0;
380   struct timeval timeout = { 0, 0 };
381   struct target_session *ts = closure;
382   FD_ZERO(&fdset);
383   FD_SET(e->fd, &fdset);
384   fds = e->fd + 1;
385   snmp_sess_read(ts->sess_handle, &fdset);
386   if(noit_snmp_session_cleanse(ts))
387     return 0;
388   snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
389   _set_ts_timeout(ts, block ? &timeout : NULL);
390   return EVENTER_READ | EVENTER_EXCEPTION;
391 }
392
393 /* This 'convert_v1pdu_to_v2' was cribbed directly from netsnmp */
394 static netsnmp_pdu *
395 convert_v1pdu_to_v2( netsnmp_pdu* template_v1pdu ) {
396   netsnmp_pdu *template_v2pdu;
397   netsnmp_variable_list *var;
398   oid enterprise[MAX_OID_LEN];
399   size_t enterprise_len;
400
401   /*
402    * Make a copy of the v1 Trap PDU
403    *   before starting to convert this
404    *   into a v2 Trap PDU.
405    */
406   template_v2pdu = snmp_clone_pdu( template_v1pdu);
407   if(!template_v2pdu) {
408     snmp_log(LOG_WARNING,
409              "send_trap: failed to copy v2 template PDU\n");
410     return NULL;
411   }
412   template_v2pdu->command = SNMP_MSG_TRAP2;
413
414   /*
415    * Insert an snmpTrapOID varbind before the original v1 varbind list
416    *   either using one of the standard defined trap OIDs,
417    *   or constructing this from the PDU enterprise & specific trap fields
418    */
419   if(template_v1pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
420     memcpy(enterprise, template_v1pdu->enterprise,
421            template_v1pdu->enterprise_length*sizeof(oid));
422     enterprise_len = template_v1pdu->enterprise_length;
423     enterprise[enterprise_len++] = 0;
424     enterprise[enterprise_len++] = template_v1pdu->specific_type;
425   } else {
426     memcpy(enterprise, cold_start_oid, sizeof(cold_start_oid));
427     enterprise[9]  = template_v1pdu->trap_type+1;
428     enterprise_len = sizeof(cold_start_oid)/sizeof(oid);
429   }
430
431   var = NULL;
432   if(!snmp_varlist_add_variable(&var,
433                                 snmptrap_oid, snmptrap_oid_len,
434                                 ASN_OBJECT_ID,
435                                 (u_char*)enterprise,
436                                 enterprise_len*sizeof(oid))) {
437     noitL(nlerr, "send_trap: failed to insert copied snmpTrapOID varbind\n");
438     snmp_free_pdu(template_v2pdu);
439     return NULL;
440   }
441   var->next_variable        = template_v2pdu->variables;
442   template_v2pdu->variables = var;
443
444   /*
445    * Insert a sysUptime varbind at the head of the v2 varbind list
446    */
447   var = NULL;
448   if(!snmp_varlist_add_variable(&var,
449                                 sysuptime_oid, sysuptime_oid_len,
450                                 ASN_TIMETICKS,
451                                 (u_char*)&(template_v1pdu->time),
452                                 sizeof(template_v1pdu->time))) {
453     noitL(nlerr, "send_trap: failed to insert copied sysUptime varbind\n");
454     snmp_free_pdu(template_v2pdu);
455     return NULL;
456   }
457   var->next_variable = template_v2pdu->variables;
458   template_v2pdu->variables = var;
459
460   /*
461    * Append the other three conversion varbinds,
462    *  (snmpTrapAgentAddr, snmpTrapCommunity & snmpTrapEnterprise)
463    *  if they're not already present.
464    *  But don't bomb out completely if there are problems.
465    */
466   var = find_varbind_in_list(template_v2pdu->variables,
467                              agentaddr_oid, agentaddr_oid_len);
468   if(!var && (template_v1pdu->agent_addr[0]
469               || template_v1pdu->agent_addr[1]
470               || template_v1pdu->agent_addr[2]
471               || template_v1pdu->agent_addr[3])) {
472     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
473                                   agentaddr_oid, agentaddr_oid_len,
474                                   ASN_IPADDRESS,
475                                   (u_char*)&(template_v1pdu->agent_addr),
476                                   sizeof(template_v1pdu->agent_addr)))
477       noitL(nlerr, "send_trap: failed to append snmpTrapAddr varbind\n");
478   }
479   var = find_varbind_in_list(template_v2pdu->variables,
480                              community_oid, community_oid_len);
481   if(!var && template_v1pdu->community) {
482     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
483                                   community_oid, community_oid_len,
484                                   ASN_OCTET_STR,
485                                   template_v1pdu->community,
486                                   template_v1pdu->community_len))
487       noitL(nlerr, "send_trap: failed to append snmpTrapCommunity varbind\n");
488   }
489   var = find_varbind_in_list(template_v2pdu->variables,
490                              snmptrapenterprise_oid,
491                              snmptrapenterprise_oid_len);
492   if(!var &&
493      template_v1pdu->trap_type != SNMP_TRAP_ENTERPRISESPECIFIC) {
494     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
495                                   snmptrapenterprise_oid,
496                                   snmptrapenterprise_oid_len,
497                                   ASN_OBJECT_ID,
498                                   (u_char*)template_v1pdu->enterprise,
499                                   template_v1pdu->enterprise_length*sizeof(oid)))
500       noitL(nlerr, "send_trap: failed to append snmpEnterprise varbind\n");
501   }
502   return template_v2pdu;
503 }
504
505 static int noit_snmp_oid_to_checkid(oid *o, int l, uuid_t checkid, char *out) {
506   int i;
507   char _uuid_str[UUID_STR_LEN+1], *cp, *uuid_str;
508
509   uuid_str = out ? out : _uuid_str;
510   if(l != reconnoiter_check_oid_len) {
511     noitL(nlerr, "unsupported (length) trap recieved\n");
512     return -1;
513   }
514   if(netsnmp_oid_equals(o,
515                         reconnoiter_check_prefix_oid_len,
516                         reconnoiter_check_prefix_oid,
517                         reconnoiter_check_prefix_oid_len) != 0) {
518     noitL(nlerr, "unsupported (wrong namespace) trap recieved\n");
519     return -1;
520   }
521   /* encode this as a uuid */
522   cp = uuid_str;
523   for(i=0;
524       i < reconnoiter_check_oid_len - reconnoiter_check_prefix_oid_len;
525       i++) {
526     oid v = o[i + reconnoiter_check_prefix_oid_len];
527     if(v < 0 || v > 0xffff) {
528       noitL(nlerr, "trap target oid [%ld] out of range\n", v);
529       return -1;
530     }
531     snprintf(cp, 5, "%04x", (unsigned short)(v & 0xffff));
532     cp += 4;
533     /* hyphens after index 1,2,3,4 */
534     if(i > 0 && i < 5) *cp++ = '-';
535   }
536   if(uuid_parse(uuid_str, checkid) != 0) {
537     noitL(nlerr, "unexpected error decoding trap uuid '%s'\n", uuid_str);
538     return -1;
539   }
540   return 0;
541 }
542
543 #define isoid(a,b,c,d) (netsnmp_oid_equals(a,b,c,d) == 0)
544 #define isoidprefix(a,b,c,d) (netsnmp_oid_equals(a,MIN(b,d),c,d) == 0)
545 #define setstatus(st,soid,sv) \
546   if(isoid(o,l,soid,reconnoiter_check_state_val_len)) current->st = sv
547
548 static int
549 noit_snmp_trapvars_to_stats(stats_t *current, netsnmp_variable_list *var) {
550   if(isoidprefix(var->name, var->name_length, reconnoiter_check_status_oid,
551                  reconnoiter_check_status_oid_len)) {
552     if(var->type == ASN_OBJECT_ID) {
553       if(isoid(var->name, var->name_length,
554                reconnoiter_check_state_oid, reconnoiter_check_state_oid_len)) {
555         oid *o = var->val.objid;
556         size_t l = var->val_len / sizeof(*o);
557         setstatus(state, reconnoiter_check_state_unknown_oid, NP_UNKNOWN);
558         else setstatus(state, reconnoiter_check_state_good_oid, NP_GOOD);
559         else setstatus(state, reconnoiter_check_state_bad_oid, NP_BAD);
560         else return -1;
561       }
562       else if(isoid(var->name, var->name_length,
563                     reconnoiter_check_available_oid,
564                     reconnoiter_check_available_oid_len)) {
565         oid *o = var->val.objid;
566         size_t l = var->val_len / sizeof(*o);
567         setstatus(available, reconnoiter_check_available_unknown_oid, NP_UNKNOWN);
568         else setstatus(available, reconnoiter_check_available_yes_oid, NP_AVAILABLE);
569         else setstatus(available, reconnoiter_check_available_no_oid, NP_UNAVAILABLE);
570         else return -1;
571       }
572       else {
573         /* We don't unerstand any other OBJECT_ID types */
574         return -1;
575       }
576     }
577     else if(var->type == ASN_UNSIGNED) {
578       /* This is only for the duration (in ms) */
579       if(isoid(var->name, var->name_length,
580                reconnoiter_check_duration_oid,
581                reconnoiter_check_duration_oid_len)) {
582         current->duration = *(var->val.integer);
583       }
584       else
585         return -1;
586     }
587     else if(var->type == ASN_OCTET_STR) {
588       /* This is only for the status message */
589       if(isoid(var->name, var->name_length,
590                reconnoiter_check_status_msg_oid,
591                reconnoiter_check_status_msg_oid_len)) {
592         current->status = malloc(var->val_len + 1);
593         memcpy(current->status, var->val.string, var->val_len);
594         current->status[var->val_len] = '\0';
595       }
596       else
597         return -1;
598     }
599     else {
600       /* I don't understand any other type of status message */
601       return -1;
602     }
603   }
604   else if(isoidprefix(var->name, var->name_length,
605                       reconnoiter_metric_prefix_oid,
606                       reconnoiter_metric_prefix_oid_len)) {
607     /* decode the metric and store the value */
608     int i, len;
609     u_int64_t u64;
610     double doubleVal;
611     char metric_name[128], buff[128], *cp;
612     if(var->name_length <= reconnoiter_metric_prefix_oid_len) return -1;
613     len = var->name[reconnoiter_metric_prefix_oid_len];
614     if(var->name_length != (reconnoiter_metric_prefix_oid_len + 1 + len) ||
615        len > sizeof(metric_name) - 1) {
616       noitL(nlerr, "snmp trap, malformed metric name\n");
617       return -1;
618     }
619     for(i=0;i<len;i++) {
620       ((unsigned char *)metric_name)[i] =
621         (unsigned char)var->name[reconnoiter_metric_prefix_oid_len + 1 + i];
622       if(!isprint(metric_name[i])) {
623         noitL(nlerr, "metric_name contains unprintable characters\n");
624         return -1;
625       }
626     }
627     metric_name[i] = '\0';
628     switch(var->type) {
629       case ASN_INTEGER:
630       case ASN_UINTEGER:
631       case ASN_TIMETICKS:
632       case ASN_INTEGER64:
633         noit_stats_set_metric(current, metric_name,
634                               METRIC_INT64, var->val.integer);
635         break;
636       case ASN_COUNTER64:
637         u64 = ((u_int64_t)var->val.counter64->high) << 32;
638         u64 |= var->val.counter64->low;
639         noit_stats_set_metric(current, metric_name,
640                               METRIC_UINT64, &u64);
641         break;
642       case ASN_OPAQUE_FLOAT:
643         doubleVal = (double)*var->val.floatVal;
644         noit_stats_set_metric(current, metric_name,
645                               METRIC_DOUBLE, &doubleVal);
646         break;
647       case ASN_OPAQUE_DOUBLE:
648         noit_stats_set_metric(current, metric_name,
649                               METRIC_DOUBLE, var->val.doubleVal);
650         break;
651       case ASN_OCTET_STR:
652         snprint_value(buff, sizeof(buff), var->name, var->name_length, var);
653         /* Advance passed the first space and use that unless there
654          * is no space or we have no more string left.
655          */
656         cp = strchr(buff, ' ');
657         if(cp) {
658           char *ecp;
659           cp++;
660           if(*cp == '"') {
661             ecp = cp + strlen(cp) - 1;
662             if(*ecp == '"') {
663               cp++; *ecp = '\0';
664             }
665           }
666         }
667         noit_stats_set_metric(current, metric_name,
668                               METRIC_STRING, (cp && *cp) ? cp : NULL);
669         break;
670       default:
671         noitL(nlerr, "snmp trap unsupport data type %d\n", var->type);
672     }
673     noitL(nldeb, "metric_name -> '%s'\n", metric_name);
674   }
675   else {
676     /* No idea what this is */
677     return -1;
678   }
679   return 0;
680 }
681 static int noit_snmp_trapd_response(int operation, struct snmp_session *sp,
682                                     int reqid, struct snmp_pdu *pdu,
683                                     void *magic) {
684   /* the noit pieces */
685   noit_check_t *check;
686   struct target_session *ts = magic;
687   snmp_mod_config_t *conf;
688   const char *community = NULL;
689   stats_t current;
690   int success = 0;
691
692   /* parsing destination */
693   char uuid_str[UUID_STR_LEN + 1];
694   uuid_t checkid;
695
696   /* snmp oid parsing helper vars */
697   netsnmp_pdu *newpdu = pdu;
698   netsnmp_variable_list *var;
699
700   conf = noit_module_get_userdata(ts->self);
701
702   if(pdu->version == SNMP_VERSION_1)
703     newpdu = convert_v1pdu_to_v2(pdu);
704   if(!newpdu || newpdu->version != SNMP_VERSION_2c) goto cleanup;
705
706   for(var = newpdu->variables; var != NULL; var = var->next_variable) {
707     if(netsnmp_oid_equals(var->name, var->name_length,
708                           snmptrap_oid, snmptrap_oid_len) == 0)
709       break;
710   }
711
712   if (!var || var->type != ASN_OBJECT_ID) {
713     noitL(nlerr, "unsupport trap (not a trap?) received\n");
714     goto cleanup;
715   }
716
717   /* var is the oid on which we are trapping.
718    * It should be in the reconnoiter check prefix.
719    */
720   if(noit_snmp_oid_to_checkid(var->val.objid, var->val_len/sizeof(oid),
721                               checkid, uuid_str)) {
722     goto cleanup;
723   }
724   noitL(nldeb, "recieved trap for %s\n", uuid_str);
725   check = noit_poller_lookup(checkid);
726   if(!check) {
727     noitL(nlerr, "trap received for non-existent check '%s'\n", uuid_str);
728     goto cleanup;
729   }
730   if(!noit_hash_retr_str(check->config, "community", strlen("community"),
731                          &community) &&
732      !noit_hash_retr_str(conf->options, "community", strlen("community"),
733                          &community)) {
734     noitL(nlerr, "No community defined for check, dropping trap\n");
735     goto cleanup;
736   }
737
738   if(strlen(community) != newpdu->community_len ||
739      memcmp(community, newpdu->community, newpdu->community_len)) {
740     noitL(nlerr, "trap attempt with wrong community string\n");
741     goto cleanup;
742   }
743
744   /* We have a check. The trap is authorized. Now, extract everything. */
745   memset(&current, 0, sizeof(current));
746   gettimeofday(&current.whence, NULL);
747   current.available = NP_AVAILABLE;
748
749   /* Rate limit */
750   if(((current.whence.tv_sec * 1000 +
751        current.whence.tv_usec / 1000) -
752       (check->last_fire_time.tv_sec * 1000 +
753        check->last_fire_time.tv_usec / 1000)) < check->period) goto cleanup;
754
755   /* update the last fire time... */
756   gettimeofday(&check->last_fire_time, NULL);
757
758   for(; var != NULL; var = var->next_variable)
759     if(noit_snmp_trapvars_to_stats(&current, var) == 0) success++;
760   if(success) {
761     char buff[24];
762     snprintf(buff, sizeof(buff), "%d datum", success);
763     current.state = NP_GOOD;
764     current.status = strdup(buff);
765   }
766   else {
767     current.state = NP_BAD;
768     current.status = strdup("no data");
769   }
770   noit_check_set_stats(ts->self, check, &current);
771
772  cleanup:
773   if(newpdu != pdu)
774     snmp_free_pdu(newpdu);
775   return 0;
776 }
777 static int noit_snmp_asynch_response(int operation, struct snmp_session *sp,
778                                      int reqid, struct snmp_pdu *pdu,
779                                      void *magic) {
780   struct check_info *info;
781   /* We don't deal with refcnt hitting zero here.  We could only be hit from
782    * the snmp read/timeout stuff.  Handle it there.
783    */
784
785   info = get_check(reqid);
786   if(!info) return 1;
787   if(info->ts) {
788     info->ts->refcnt--;
789     info->ts = NULL;
790   }
791   remove_check(info);
792   if(info->timeoutevent) {
793     eventer_remove(info->timeoutevent);
794     eventer_free(info->timeoutevent);
795     info->timeoutevent = NULL;
796   }
797
798   /* Log our findings */
799   noit_snmp_log_results(info->self, info->check, pdu);
800   info->check->flags &= ~NP_RUNNING;
801   return 1;
802 }
803
804 static void noit_snmp_sess_open(struct target_session *ts,
805                                 noit_check_t *check) {
806   const char *community;
807   struct snmp_session sess;
808   snmp_sess_init(&sess);
809   sess.version = SNMP_VERSION_2c;
810   sess.peername = ts->target;
811   if(!noit_hash_retr_str(check->config, "community", strlen("community"),
812                          &community)) {
813     community = "public";
814   }
815   sess.community = (unsigned char *)community;
816   sess.community_len = strlen(community);
817   sess.callback = noit_snmp_asynch_response;
818   sess.callback_magic = ts;
819   ts->sess_handle = snmp_sess_open(&sess);
820   gettimeofday(&ts->last_open, NULL);
821 }
822
823 static int noit_snmp_fill_req(struct snmp_pdu *req, noit_check_t *check) {
824   int i, klen;
825   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
826   const char *name, *value;
827   struct check_info *info = check->closure;
828   noit_hash_table check_attrs_hash = NOIT_HASH_EMPTY;
829
830   /* Toss the old set and bail if we have zero */
831   if(info->oids) {
832     for(i=0; i<info->noids;i++) {
833       if(info->oids[i].confname) free(info->oids[i].confname);
834       if(info->oids[i].oidname) free(info->oids[i].oidname);
835     }
836     free(info->oids);
837   }
838   info->noids = 0;
839   info->oids = NULL;
840
841   /* Figure our how many. */
842   while(noit_hash_next_str(check->config, &iter, &name, &klen, &value)) {
843     if(!strncasecmp(name, "oid_", 4)) {
844       info->noids++;
845     }
846   }
847
848   if(info->noids == 0) return 0;
849
850   /* Create a hash of important check attributes */
851   noit_check_make_attrs(check, &check_attrs_hash);
852
853   /* Fill out the new set of required oids */
854   info->oids = calloc(info->noids, sizeof(*info->oids));
855   memset(&iter, 0, sizeof(iter));
856   i = 0;
857   while(noit_hash_next_str(check->config, &iter, &name, &klen, &value)) {
858     if(!strncasecmp(name, "oid_", 4)) {
859       char oidbuff[128];
860       name += 4;
861       info->oids[i].confname = strdup(name);
862       noit_check_interpolate(oidbuff, sizeof(oidbuff), value,
863                              &check_attrs_hash, check->config);
864       info->oids[i].oidname = strdup(oidbuff);
865       info->oids[i].oidlen = MAX_OID_LEN;
866       if(oidbuff[0] == '.')
867         read_objid(oidbuff, info->oids[i].oid, &info->oids[i].oidlen);
868       else
869         get_node(oidbuff, info->oids[i].oid, &info->oids[i].oidlen);
870       snmp_add_null_var(req, info->oids[i].oid, info->oids[i].oidlen);
871       i++;
872     }
873   }
874   assert(info->noids == i);
875   noit_hash_destroy(&check_attrs_hash, NULL, NULL);
876   return info->noids;
877 }
878 static int noit_snmp_send(noit_module_t *self, noit_check_t *check,
879                           noit_check_t *cause) {
880   struct snmp_pdu *req;
881   struct target_session *ts;
882   struct check_info *info = check->closure;
883   int port = 161;
884   const char *portstr;
885   char target_port[64];
886
887   info->self = self;
888   info->check = check;
889   info->timedout = 0;
890
891   check->flags |= NP_RUNNING;
892
893   if(noit_hash_retr_str(check->config, "port", strlen("port"),
894                         &portstr)) {
895     port = atoi(portstr);
896   }
897   snprintf(target_port, sizeof(target_port), "%s:%d", check->target_ip, port);
898   ts = _get_target_session(self, target_port);
899   gettimeofday(&check->last_fire_time, NULL);
900   if(!ts->refcnt) {
901     eventer_t newe;
902     int fds, block;
903     struct timeval timeout;
904     fd_set fdset;
905     noit_snmp_sess_open(ts, check);
906     block = 0;
907     fds = 0;
908     FD_ZERO(&fdset);
909     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
910     assert(fds > 0);
911     ts->fd = fds-1;
912     newe = eventer_alloc();
913     newe->fd = ts->fd;
914     newe->callback = noit_snmp_handler;
915     newe->closure = ts;
916     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
917     eventer_add(newe);
918   }
919   if(!ts->sess_handle) {
920     /* Error */
921     /* No need to do anything, this will be handled in the else below */
922   }
923   ts->refcnt++; /* Increment here, decrement when this check completes */
924
925   req = snmp_pdu_create(SNMP_MSG_GET);
926   if(req) noit_snmp_fill_req(req, check);
927   /* Setup out snmp requests */
928   if(ts->sess_handle && req &&
929      (info->reqid = snmp_sess_send(ts->sess_handle, req)) != 0) {
930     struct timeval when, to;
931     info->ts = ts;
932     info->timeoutevent = eventer_alloc();
933     info->timeoutevent->callback = noit_snmp_check_timeout;
934     info->timeoutevent->closure = info;
935     info->timeoutevent->mask = EVENTER_TIMER;
936
937     noitL(nldeb, "Sending snmp get\n");
938     gettimeofday(&when, NULL);
939     to.tv_sec = check->timeout / 1000;
940     to.tv_usec = (check->timeout % 1000) * 1000;
941     add_timeval(when, to, &info->timeoutevent->whence);
942     eventer_add(info->timeoutevent);
943     add_check(info);
944   }
945   else {
946     ts->refcnt--;
947     noitL(nlerr, "Error sending snmp get request.\n");
948     noit_snmp_session_cleanse(ts);
949     /* Error */
950     if(req) snmp_free_pdu(req);
951     /* Log our findings */
952     noit_snmp_log_results(self, check, NULL);
953     check->flags &= ~NP_RUNNING;
954   }
955   return 0;
956 }
957
958 static int noit_snmp_initiate_check(noit_module_t *self, noit_check_t *check,
959                                     int once, noit_check_t *cause) {
960   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
961   INITIATE_CHECK(noit_snmp_send, self, check, cause);
962   return 0;
963 }
964
965 static int noit_snmptrap_initiate_check(noit_module_t *self,
966                                         noit_check_t *check,
967                                         int once, noit_check_t *cause) {
968   /* We don't do anything for snmptrap checks.  Not intuitive... but they
969    * never "run."  We accept input out-of-band via snmp traps.
970    */
971   return 0;
972 }
973
974 static int noit_snmp_config(noit_module_t *self, noit_hash_table *options) {
975   snmp_mod_config_t *conf;
976   conf = noit_module_get_userdata(self);
977   if(conf) {
978     if(conf->options) {
979       noit_hash_destroy(conf->options, free, free);
980       free(conf->options);
981     }
982   }
983   else
984     conf = calloc(1, sizeof(*conf));
985   conf->options = options;
986   noit_module_set_userdata(self, conf);
987   return 1;
988 }
989 static int noit_snmp_onload(noit_image_t *self) {
990   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
991   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
992   if(!nlerr) nlerr = noit_stderr;
993   if(!nldeb) nldeb = noit_debug;
994   eventer_name_callback("noit_snmp/check_timeout", noit_snmp_check_timeout);
995   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
996   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
997   return 0;
998 }
999
1000 static int noit_snmptrap_onload(noit_image_t *self) {
1001   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
1002   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
1003   if(!nlerr) nlerr = noit_stderr;
1004   if(!nldeb) nldeb = noit_debug;
1005   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
1006   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
1007   return 0;
1008 }
1009
1010 static void
1011 nc_printf_snmpts_brief(noit_console_closure_t ncct,
1012                        struct target_session *ts) {
1013   char fd[32];
1014   struct timeval now, diff;
1015   gettimeofday(&now, NULL);
1016   sub_timeval(now, ts->last_open, &diff);
1017   if(ts->fd < 0)
1018     snprintf(fd, sizeof(fd), "%s", "(closed)");
1019   else
1020     snprintf(fd, sizeof(fd), "%d", ts->fd);
1021   nc_printf(ncct, "[%s]\n\topened: %0.3fs ago\n\tFD: %s\n\trefcnt: %d\n",
1022             ts->target, diff.tv_sec + (float)diff.tv_usec/1000000,
1023             fd, ts->refcnt);
1024 }
1025
1026 static int
1027 noit_console_show_snmp(noit_console_closure_t ncct,
1028                        int argc, char **argv,
1029                        noit_console_state_t *dstate,
1030                        void *closure) {
1031   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
1032   uuid_t key_id;
1033   int klen;
1034   void *vts;
1035   snmp_mod_config_t *conf = closure;
1036
1037   while(noit_hash_next(&conf->target_sessions, &iter,
1038                        (const char **)key_id, &klen,
1039                        &vts)) {
1040     struct target_session *ts = vts;
1041     nc_printf_snmpts_brief(ncct, ts);
1042   }
1043   return 0;
1044 }
1045
1046 static void
1047 register_console_snmp_commands(snmp_mod_config_t *conf) {
1048   noit_console_state_t *tl;
1049   cmd_info_t *showcmd;
1050
1051   tl = noit_console_state_initial();
1052   showcmd = noit_console_state_get_cmd(tl, "show");
1053   assert(showcmd && showcmd->dstate);
1054   noit_console_state_add_cmd(showcmd->dstate,
1055     NCSCMD("snmp", noit_console_show_snmp, NULL, NULL, conf));
1056 }
1057
1058 static int noit_snmp_init(noit_module_t *self) {
1059   const char *opt;
1060   snmp_mod_config_t *conf;
1061
1062   conf = noit_module_get_userdata(self);
1063
1064   if(!__snmp_initialize_once) {
1065     register_mib_handlers();
1066     read_premib_configs();
1067     read_configs();
1068     init_snmp("noitd");
1069     __snmp_initialize_once = 1;
1070   }
1071   if(strcmp(self->hdr.name, "snmp") == 0) {
1072     register_console_snmp_commands(conf);
1073   }
1074
1075   if(strcmp(self->hdr.name, "snmptrap") == 0) {
1076     eventer_t newe;
1077     int i, block = 0, fds = 0;
1078     fd_set fdset;
1079     struct timeval timeout = { 0, 0 };
1080     struct target_session *ts;
1081     netsnmp_transport *transport;
1082     netsnmp_session sess, *session = &sess;
1083
1084     if(!noit_hash_retrieve(conf->options,
1085                            "snmptrapd_port", strlen("snmptrapd_port"),
1086                            (void **)&opt))
1087       opt = "162";
1088
1089     transport = netsnmp_transport_open_server("snmptrap", opt);
1090     if(!transport) {
1091       noitL(nlerr, "cannot open netsnmp transport for trap daemon\n");
1092       return -1;
1093     }
1094     ts = _get_target_session(self, "snmptrapd");
1095     snmp_sess_init(session);
1096     session->peername = SNMP_DEFAULT_PEERNAME;
1097     session->version = SNMP_DEFAULT_VERSION;
1098     session->community_len = SNMP_DEFAULT_COMMUNITY_LEN;
1099     session->retries = SNMP_DEFAULT_RETRIES;
1100     session->timeout = SNMP_DEFAULT_TIMEOUT;
1101     session->callback = noit_snmp_trapd_response;
1102     session->callback_magic = (void *) ts;
1103     session->authenticator = NULL;
1104     session->isAuthoritative = SNMP_SESS_UNKNOWNAUTH;
1105     ts->sess_handle = snmp_sess_add(session, transport, NULL, NULL);
1106
1107     FD_ZERO(&fdset);
1108     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
1109     assert(fds > 0);
1110     for(i=0; i<fds; i++) {
1111       if(FD_ISSET(i, &fdset)) {
1112         ts->refcnt++;
1113         ts->fd = i;
1114         newe = eventer_alloc();
1115         newe->fd = ts->fd;
1116         newe->callback = noit_snmp_handler;
1117         newe->closure = ts;
1118         newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
1119         eventer_add(newe);
1120       }
1121     }
1122   }
1123   return 0;
1124 }
1125
1126 #include "snmp.xmlh"
1127 noit_module_t snmp = {
1128   {
1129     NOIT_MODULE_MAGIC,
1130     NOIT_MODULE_ABI_VERSION,
1131     "snmp",
1132     "SNMP collection",
1133     snmp_xml_description,
1134     noit_snmp_onload
1135   },
1136   noit_snmp_config,
1137   noit_snmp_init,
1138   noit_snmp_initiate_check,
1139   NULL /* noit_snmp_cleanup */
1140 };
1141
1142 #include "snmptrap.xmlh"
1143 noit_module_t snmptrap = {
1144   {
1145     NOIT_MODULE_MAGIC,
1146     NOIT_MODULE_ABI_VERSION,
1147     "snmptrap",
1148     "SNMP trap collection",
1149     snmptrap_xml_description,
1150     noit_snmptrap_onload
1151   },
1152   noit_snmp_config,
1153   noit_snmp_init,
1154   noit_snmptrap_initiate_check,
1155   NULL /* noit_snmp_cleanup */
1156 };
Note: See TracBrowser for help on using the browser.