root/src/modules/snmp.c

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

refs #340

This is a rather major change. Targets can now be hostnames in addition
to IP addresses so long as //checks/@resolve_targets is not false.

If a target is entered that does not look like an IP (inet_pton fails)
then the check is marked as needing resolution NP_RESOLVE.

A passive look-aside dns cache has been implemented in noit_check_resolver.c
that is used to power the whole system and some reasonably simply console
command have been provided:

show dns_cache [fqdn1 [fqdn2]] -- shows the state
dns_cache <fqdn> [fqdn2 [fqdn3]] -- submits for lookup
no dns_cache <fqdn> [fqdn2 [fqdn3]] -- purges from cache

The big change is that modules that relied on check->target to be an IP
address are going to explode when names are provided. Instead, modules
should now use target for the provided target (possibly a FQDN) and use
target_ip (check->target_ip or check.target_ip) for a resolved IP address
and also check for the case of empty string: (check->target_ip[0] == '\0')
for the case that resolution has failed. In lua, the target_ip will be
presented as nil in the case of failed name resolution.

I believe I've updated all necessary components of the system for this to
"just work" but people that have implemented their own check should update
them before they elect to use non-IP addresses as targets.

The dns subsystem supports both IPv4 and IPv6, but currently prefers IPv4
addresses if any are present.

  • 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   struct snmp_pdu *req;
880   struct target_session *ts;
881   struct check_info *info = check->closure;
882   int port = 161;
883   const char *portstr;
884   char target_port[64];
885
886   info->self = self;
887   info->check = check;
888   info->timedout = 0;
889
890   check->flags |= NP_RUNNING;
891
892   if(noit_hash_retr_str(check->config, "port", strlen("port"),
893                         &portstr)) {
894     port = atoi(portstr);
895   }
896   snprintf(target_port, sizeof(target_port), "%s:%d", check->target_ip, port);
897   ts = _get_target_session(self, target_port);
898   gettimeofday(&check->last_fire_time, NULL);
899   if(!ts->refcnt) {
900     eventer_t newe;
901     int fds, block;
902     struct timeval timeout;
903     fd_set fdset;
904     noit_snmp_sess_open(ts, check);
905     block = 0;
906     fds = 0;
907     FD_ZERO(&fdset);
908     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
909     assert(fds > 0);
910     ts->fd = fds-1;
911     newe = eventer_alloc();
912     newe->fd = ts->fd;
913     newe->callback = noit_snmp_handler;
914     newe->closure = ts;
915     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
916     eventer_add(newe);
917   }
918   if(!ts->sess_handle) {
919     /* Error */
920     /* No need to do anything, this will be handled in the else below */
921   }
922   ts->refcnt++; /* Increment here, decrement when this check completes */
923
924   req = snmp_pdu_create(SNMP_MSG_GET);
925   if(req) noit_snmp_fill_req(req, check);
926   /* Setup out snmp requests */
927   if(ts->sess_handle && req &&
928      (info->reqid = snmp_sess_send(ts->sess_handle, req)) != 0) {
929     struct timeval when, to;
930     info->ts = ts;
931     info->timeoutevent = eventer_alloc();
932     info->timeoutevent->callback = noit_snmp_check_timeout;
933     info->timeoutevent->closure = info;
934     info->timeoutevent->mask = EVENTER_TIMER;
935
936     noitL(nldeb, "Sending snmp get\n");
937     gettimeofday(&when, NULL);
938     to.tv_sec = check->timeout / 1000;
939     to.tv_usec = (check->timeout % 1000) * 1000;
940     add_timeval(when, to, &info->timeoutevent->whence);
941     eventer_add(info->timeoutevent);
942     add_check(info);
943   }
944   else {
945     ts->refcnt--;
946     noitL(nlerr, "Error sending snmp get request.\n");
947     noit_snmp_session_cleanse(ts);
948     /* Error */
949     if(req) snmp_free_pdu(req);
950     /* Log our findings */
951     noit_snmp_log_results(self, check, NULL);
952     check->flags &= ~NP_RUNNING;
953   }
954   return 0;
955 }
956
957 static int noit_snmp_initiate_check(noit_module_t *self, noit_check_t *check,
958                                     int once, noit_check_t *cause) {
959   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
960   INITIATE_CHECK(noit_snmp_send, self, check);
961   return 0;
962 }
963
964 static int noit_snmptrap_initiate_check(noit_module_t *self,
965                                         noit_check_t *check,
966                                         int once, noit_check_t *cause) {
967   /* We don't do anything for snmptrap checks.  Not intuitive... but they
968    * never "run."  We accept input out-of-band via snmp traps.
969    */
970   return 0;
971 }
972
973 static int noit_snmp_config(noit_module_t *self, noit_hash_table *options) {
974   snmp_mod_config_t *conf;
975   conf = noit_module_get_userdata(self);
976   if(conf) {
977     if(conf->options) {
978       noit_hash_destroy(conf->options, free, free);
979       free(conf->options);
980     }
981   }
982   else
983     conf = calloc(1, sizeof(*conf));
984   conf->options = options;
985   noit_module_set_userdata(self, conf);
986   return 1;
987 }
988 static int noit_snmp_onload(noit_image_t *self) {
989   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
990   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
991   if(!nlerr) nlerr = noit_stderr;
992   if(!nldeb) nldeb = noit_debug;
993   eventer_name_callback("noit_snmp/check_timeout", noit_snmp_check_timeout);
994   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
995   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
996   return 0;
997 }
998
999 static int noit_snmptrap_onload(noit_image_t *self) {
1000   if(!nlerr) nlerr = noit_log_stream_find("error/snmp");
1001   if(!nldeb) nldeb = noit_log_stream_find("debug/snmp");
1002   if(!nlerr) nlerr = noit_stderr;
1003   if(!nldeb) nldeb = noit_debug;
1004   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
1005   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
1006   return 0;
1007 }
1008
1009 static void
1010 nc_printf_snmpts_brief(noit_console_closure_t ncct,
1011                        struct target_session *ts) {
1012   char fd[32];
1013   struct timeval now, diff;
1014   gettimeofday(&now, NULL);
1015   sub_timeval(now, ts->last_open, &diff);
1016   if(ts->fd < 0)
1017     snprintf(fd, sizeof(fd), "%s", "(closed)");
1018   else
1019     snprintf(fd, sizeof(fd), "%d", ts->fd);
1020   nc_printf(ncct, "[%s]\n\topened: %0.3fs ago\n\tFD: %s\n\trefcnt: %d\n",
1021             ts->target, diff.tv_sec + (float)diff.tv_usec/1000000,
1022             fd, ts->refcnt);
1023 }
1024
1025 static int
1026 noit_console_show_snmp(noit_console_closure_t ncct,
1027                        int argc, char **argv,
1028                        noit_console_state_t *dstate,
1029                        void *closure) {
1030   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
1031   uuid_t key_id;
1032   int klen;
1033   void *vts;
1034   snmp_mod_config_t *conf = closure;
1035
1036   while(noit_hash_next(&conf->target_sessions, &iter,
1037                        (const char **)key_id, &klen,
1038                        &vts)) {
1039     struct target_session *ts = vts;
1040     nc_printf_snmpts_brief(ncct, ts);
1041   }
1042   return 0;
1043 }
1044
1045 static void
1046 register_console_snmp_commands(snmp_mod_config_t *conf) {
1047   noit_console_state_t *tl;
1048   cmd_info_t *showcmd;
1049
1050   tl = noit_console_state_initial();
1051   showcmd = noit_console_state_get_cmd(tl, "show");
1052   assert(showcmd && showcmd->dstate);
1053   noit_console_state_add_cmd(showcmd->dstate,
1054     NCSCMD("snmp", noit_console_show_snmp, NULL, NULL, conf));
1055 }
1056
1057 static int noit_snmp_init(noit_module_t *self) {
1058   const char *opt;
1059   snmp_mod_config_t *conf;
1060
1061   conf = noit_module_get_userdata(self);
1062
1063   if(!__snmp_initialize_once) {
1064     register_mib_handlers();
1065     read_premib_configs();
1066     read_configs();
1067     init_snmp("noitd");
1068     __snmp_initialize_once = 1;
1069   }
1070   if(strcmp(self->hdr.name, "snmp") == 0) {
1071     register_console_snmp_commands(conf);
1072   }
1073
1074   if(strcmp(self->hdr.name, "snmptrap") == 0) {
1075     eventer_t newe;
1076     int i, block = 0, fds = 0;
1077     fd_set fdset;
1078     struct timeval timeout = { 0, 0 };
1079     struct target_session *ts;
1080     netsnmp_transport *transport;
1081     netsnmp_session sess, *session = &sess;
1082
1083     if(!noit_hash_retrieve(conf->options,
1084                            "snmptrapd_port", strlen("snmptrapd_port"),
1085                            (void **)&opt))
1086       opt = "162";
1087
1088     transport = netsnmp_transport_open_server("snmptrap", opt);
1089     if(!transport) {
1090       noitL(nlerr, "cannot open netsnmp transport for trap daemon\n");
1091       return -1;
1092     }
1093     ts = _get_target_session(self, "snmptrapd");
1094     snmp_sess_init(session);
1095     session->peername = SNMP_DEFAULT_PEERNAME;
1096     session->version = SNMP_DEFAULT_VERSION;
1097     session->community_len = SNMP_DEFAULT_COMMUNITY_LEN;
1098     session->retries = SNMP_DEFAULT_RETRIES;
1099     session->timeout = SNMP_DEFAULT_TIMEOUT;
1100     session->callback = noit_snmp_trapd_response;
1101     session->callback_magic = (void *) ts;
1102     session->authenticator = NULL;
1103     session->isAuthoritative = SNMP_SESS_UNKNOWNAUTH;
1104     ts->sess_handle = snmp_sess_add(session, transport, NULL, NULL);
1105
1106     FD_ZERO(&fdset);
1107     snmp_sess_select_info(ts->sess_handle, &fds, &fdset, &timeout, &block);
1108     assert(fds > 0);
1109     for(i=0; i<fds; i++) {
1110       if(FD_ISSET(i, &fdset)) {
1111         ts->refcnt++;
1112         ts->fd = i;
1113         newe = eventer_alloc();
1114         newe->fd = ts->fd;
1115         newe->callback = noit_snmp_handler;
1116         newe->closure = ts;
1117         newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
1118         eventer_add(newe);
1119       }
1120     }
1121   }
1122   return 0;
1123 }
1124
1125 #include "snmp.xmlh"
1126 noit_module_t snmp = {
1127   {
1128     NOIT_MODULE_MAGIC,
1129     NOIT_MODULE_ABI_VERSION,
1130     "snmp",
1131     "SNMP collection",
1132     snmp_xml_description,
1133     noit_snmp_onload
1134   },
1135   noit_snmp_config,
1136   noit_snmp_init,
1137   noit_snmp_initiate_check,
1138   NULL /* noit_snmp_cleanup */
1139 };
1140
1141 #include "snmptrap.xmlh"
1142 noit_module_t snmptrap = {
1143   {
1144     NOIT_MODULE_MAGIC,
1145     NOIT_MODULE_ABI_VERSION,
1146     "snmptrap",
1147     "SNMP trap collection",
1148     snmptrap_xml_description,
1149     noit_snmptrap_onload
1150   },
1151   noit_snmp_config,
1152   noit_snmp_init,
1153   noit_snmptrap_initiate_check,
1154   NULL /* noit_snmp_cleanup */
1155 };
Note: See TracBrowser for help on using the browser.