xref: /freebsd/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /*
2  * Copyright (c) 2001-2003
3  *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
4  *	All rights reserved.
5  *
6  * Author: Harti Brandt <harti@freebsd.org>
7  *
8  * Redistribution of this software and documentation and use in source and
9  * binary forms, with or without modification, are permitted provided that
10  * the following conditions are met:
11  *
12  * 1. Redistributions of source code or documentation must retain the above
13  *    copyright notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
19  * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
22  * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
25  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  * Netgraph interface for SNMPd.
31  */
32 #include <sys/types.h>
33 #include <sys/param.h>
34 #include <sys/linker.h>
35 #include <sys/socket.h>
36 #include <sys/syslog.h>
37 #include <sys/queue.h>
38 #include <sys/sysctl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <errno.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <netgraph.h>
45 #include <bsnmp/snmpmod.h>
46 #include "snmp_netgraph.h"
47 #include "netgraph_tree.h"
48 #include "netgraph_oid.h"
49 
50 /* maximum message size */
51 #define RESBUFSIZ	20000
52 
53 /* default node name */
54 #define NODENAME	"NgSnmpd"
55 
56 /* my node Id */
57 ng_ID_t snmp_node;
58 u_char *snmp_nodename;
59 
60 /* the Object Resource registration index */
61 static u_int reg_index;
62 static const struct asn_oid oid_begemotNg = OIDX_begemotNg;
63 
64 /* configuration */
65 /* this must be smaller than int32_t because the functions in libnetgraph
66  * falsely return an int */
67 static size_t resbufsiz = RESBUFSIZ;
68 static u_int timeout = 1000;
69 static u_int debug_level;
70 
71 /* number of microseconds per clock tick */
72 static struct clockinfo clockinfo;
73 
74 /* Csock buffers. Communication on the csock is asynchronuous. This means
75  * if we wait for a specific response, we may get other messages. Put these
76  * into a queue and execute them when we are idle. */
77 struct csock_buf {
78 	STAILQ_ENTRY(csock_buf) link;
79 	struct ng_mesg *mesg;
80 	char path[NG_PATHSIZ];
81 };
82 static STAILQ_HEAD(, csock_buf) csock_bufs =
83 	STAILQ_HEAD_INITIALIZER(csock_bufs);
84 
85 /*
86  * We dispatch unsolicieted messages by node cookies and ids.
87  * So we must keep a list of hook names and dispatch functions.
88  */
89 struct msgreg {
90 	u_int32_t 	cookie;
91 	ng_ID_t		id;
92 	ng_cookie_f	*func;
93 	void		*arg;
94 	const struct lmodule *mod;
95 	SLIST_ENTRY(msgreg) link;
96 };
97 static SLIST_HEAD(, msgreg) msgreg_list =
98 	SLIST_HEAD_INITIALIZER(msgreg_list);
99 
100 /*
101  * Data messages are dispatched by hook names.
102  */
103 struct datareg {
104 	char		hook[NG_HOOKSIZ];
105 	ng_hook_f	*func;
106 	void		*arg;
107 	const struct lmodule *mod;
108 	SLIST_ENTRY(datareg) link;
109 };
110 static SLIST_HEAD(, datareg) datareg_list =
111 	SLIST_HEAD_INITIALIZER(datareg_list);
112 
113 /* the netgraph sockets */
114 static int csock, dsock;
115 static void *csock_fd, *dsock_fd;
116 
117 /* our module handle */
118 static struct lmodule *module;
119 
120 /* statistics */
121 static u_int32_t stats[LEAF_begemotNgTooLargeDatas+1];
122 
123 /* netgraph type list */
124 struct ngtype {
125 	char		name[NG_TYPESIZ];
126 	struct asn_oid	index;
127 	TAILQ_ENTRY(ngtype) link;
128 };
129 TAILQ_HEAD(ngtype_list, ngtype);
130 
131 static struct ngtype_list ngtype_list;
132 static uint64_t ngtype_tick;
133 
134 
135 /*
136  * Register a function to receive unsolicited messages
137  */
138 void *
139 ng_register_cookie(const struct lmodule *mod, u_int32_t cookie, ng_ID_t id,
140     ng_cookie_f *func, void *arg)
141 {
142 	struct msgreg *d;
143 
144 	if ((d = malloc(sizeof(*d))) == NULL)
145 		return (NULL);
146 
147 	d->cookie = cookie;
148 	d->id = id;
149 	d->func = func;
150 	d->arg = arg;
151 	d->mod = mod;
152 
153 	SLIST_INSERT_HEAD(&msgreg_list, d, link);
154 
155 	return (d);
156 }
157 
158 /*
159  * Remove a registration.
160  */
161 void
162 ng_unregister_cookie(void *dd)
163 {
164 	struct msgreg *d = dd;
165 
166 	SLIST_REMOVE(&msgreg_list, d, msgreg, link);
167 	free(d);
168 }
169 
170 /*
171  * Register a function for hook data.
172  */
173 void *
174 ng_register_hook(const struct lmodule *mod, const char *hook,
175     ng_hook_f *func, void *arg)
176 {
177 	struct datareg *d;
178 
179 	if ((d = malloc(sizeof(*d))) == NULL)
180 		return (NULL);
181 
182 	strcpy(d->hook, hook);
183 	d->func = func;
184 	d->arg = arg;
185 	d->mod = mod;
186 
187 	SLIST_INSERT_HEAD(&datareg_list, d, link);
188 
189 	return (d);
190 }
191 
192 /*
193  * Unregister a hook function
194  */
195 void
196 ng_unregister_hook(void *dd)
197 {
198 	struct datareg *d = dd;
199 
200 	SLIST_REMOVE(&datareg_list, d, datareg, link);
201 	free(d);
202 }
203 
204 /*
205  * Unregister all hooks and cookies for that module. Note: doesn't disconnect
206  * any hooks!
207  */
208 void
209 ng_unregister_module(const struct lmodule *mod)
210 {
211 	struct msgreg *m, *m1;
212 	struct datareg *d, *d1;
213 
214 	m = SLIST_FIRST(&msgreg_list);
215 	while (m != NULL) {
216 		m1 = SLIST_NEXT(m, link);
217 		if (m->mod == mod) {
218 			SLIST_REMOVE(&msgreg_list, m, msgreg, link);
219 			free(m);
220 		}
221 		m = m1;
222 	}
223 
224 	d = SLIST_FIRST(&datareg_list);
225 	while (d != NULL) {
226 		d1 = SLIST_NEXT(d, link);
227 		if (d->mod == mod) {
228 			SLIST_REMOVE(&datareg_list, d, datareg, link);
229 			free(d);
230 		}
231 		d = d1;
232 	}
233 }
234 
235 /*
236  * Dispatch a message to the correct module and delete it. More than one
237  * module can get a message.
238  */
239 static void
240 csock_handle(struct ng_mesg *mesg, const char *path)
241 {
242 	struct msgreg *d, *d1;
243 	u_int id;
244 	int len;
245 
246 	if (sscanf(path, "[%x]:%n", &id, &len) != 1 ||
247 	    (u_int)len != strlen(path)) {
248 		syslog(LOG_ERR, "cannot parse message path '%s'", path);
249 		id = 0;
250 	}
251 
252 	d = SLIST_FIRST(&msgreg_list);
253 	while (d != NULL) {
254 		d1 = SLIST_NEXT(d, link);
255 		if (d->cookie == mesg->header.typecookie &&
256 		    (d->id == 0 || d->id == id || id == 0))
257 			(*d->func)(mesg, path, id, d->arg);
258 		d = d1;
259 	}
260 	free(mesg);
261 }
262 
263 /*
264  * Input from the control socket.
265  */
266 static struct ng_mesg *
267 csock_read(char *path)
268 {
269 	struct ng_mesg *mesg;
270 	int ret, err;
271 
272 	if ((mesg = malloc(resbufsiz + 1)) == NULL) {
273 		stats[LEAF_begemotNgNoMems]++;
274 		syslog(LOG_CRIT, "out of memory");
275 		errno = ENOMEM;
276 		return (NULL);
277 	}
278 	if ((ret = NgRecvMsg(csock, mesg, resbufsiz + 1, path)) < 0) {
279 		err = errno;
280 		free(mesg);
281 		if (errno == EWOULDBLOCK) {
282 			errno = err;
283 			return (NULL);
284 		}
285 		stats[LEAF_begemotNgMsgReadErrs]++;
286 		syslog(LOG_WARNING, "read from csock: %m");
287 		errno = err;
288 		return (NULL);
289 	}
290 	if (ret == 0) {
291 		syslog(LOG_DEBUG, "node closed -- exiting");
292 		exit(0);
293 	}
294 	if ((size_t)ret > resbufsiz) {
295 		stats[LEAF_begemotNgTooLargeMsgs]++;
296 		syslog(LOG_WARNING, "ng message too large");
297 		free(mesg);
298 		errno = EFBIG;
299 		return (NULL);
300 	}
301 	return (mesg);
302 }
303 
304 static void
305 csock_input(int fd __unused, void *udata __unused)
306 {
307 	struct ng_mesg *mesg;
308 	char path[NG_PATHSIZ];
309 
310 	if ((mesg = csock_read(path)) == NULL)
311 		return;
312 
313 	csock_handle(mesg, path);
314 }
315 
316 /*
317  * Write a message to a node.
318  */
319 int
320 ng_output(const char *path, u_int cookie, u_int opcode,
321     const void *arg, size_t arglen)
322 {
323 	return (NgSendMsg(csock, path, (int)cookie, (int)opcode, arg, arglen));
324 }
325 int
326 ng_output_node(const char *node, u_int cookie, u_int opcode,
327     const void *arg, size_t arglen)
328 {
329 	char path[NG_PATHSIZ];
330 
331 	sprintf(path, "%s:", node);
332 	return (ng_output(path, cookie, opcode, arg, arglen));
333 }
334 int
335 ng_output_id(ng_ID_t node, u_int cookie, u_int opcode,
336     const void *arg, size_t arglen)
337 {
338 	char path[NG_PATHSIZ];
339 
340 	sprintf(path, "[%x]:", node);
341 	return (ng_output(path, cookie, opcode, arg, arglen));
342 }
343 
344 
345 
346 /*
347  * Execute a synchronuous dialog with the csock. All message we receive, that
348  * do not match our request, are queue until the next call to the IDLE function.
349  */
350 struct ng_mesg *
351 ng_dialog(const char *path, u_int cookie, u_int opcode,
352     const void *arg, size_t arglen)
353 {
354 	int token, err;
355 	struct ng_mesg *mesg;
356 	char rpath[NG_PATHSIZ];
357 	struct csock_buf *b;
358 	struct timeval end, tv;
359 
360 	if ((token = ng_output(path, cookie, opcode, arg, arglen)) < 0)
361 		return (NULL);
362 
363 	if (csock_fd)
364 		fd_suspend(csock_fd);
365 
366 	gettimeofday(&end, NULL);
367 	tv.tv_sec = timeout / 1000;
368 	tv.tv_usec = (timeout % 1000) * 1000;
369 	timeradd(&end, &tv, &end);
370 	for (;;) {
371 		mesg = NULL;
372 		gettimeofday(&tv, NULL);
373 		if (timercmp(&tv, &end, >=)) {
374   block:
375 			syslog(LOG_WARNING, "no response for request %u/%u",
376 			    cookie, opcode);
377 			errno = EWOULDBLOCK;
378 			break;
379 		}
380 		timersub(&end, &tv, &tv);
381 		if (tv.tv_sec == 0 && tv.tv_usec < clockinfo.tick)
382 			goto block;
383 
384 		if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
385 			syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO): %m");
386 		if ((mesg = csock_read(rpath)) == NULL) {
387 			if (errno == EWOULDBLOCK)
388 				continue;
389 			break;
390 		}
391 		if (mesg->header.token == (u_int)token)
392 			break;
393 		if ((b = malloc(sizeof(*b))) == NULL) {
394 			stats[LEAF_begemotNgNoMems]++;
395 			syslog(LOG_ERR, "out of memory");
396 			free(mesg);
397 			continue;
398 		}
399 		b->mesg = mesg;
400 		strcpy(b->path, rpath);
401 		STAILQ_INSERT_TAIL(&csock_bufs, b, link);
402 	}
403 
404 	tv.tv_sec = 0;
405 	tv.tv_usec = 0;
406 	if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
407 		syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO,0): %m");
408 
409 	if (csock_fd) {
410 		err = errno;
411 		fd_resume(csock_fd);
412 		errno = err;
413 	}
414 
415 	return (mesg);
416 }
417 struct ng_mesg *
418 ng_dialog_node(const char *node, u_int cookie, u_int opcode,
419     const void *arg, size_t arglen)
420 {
421 	char path[NG_PATHSIZ];
422 
423 	sprintf(path, "%s:", node);
424 	return (ng_dialog(path, cookie, opcode, arg, arglen));
425 }
426 struct ng_mesg *
427 ng_dialog_id(ng_ID_t id, u_int cookie, u_int opcode,
428     const void *arg, size_t arglen)
429 {
430 	char path[NG_PATHSIZ];
431 
432 	sprintf(path, "[%x]:", id);
433 	return (ng_dialog(path, cookie, opcode, arg, arglen));
434 }
435 
436 
437 /*
438  * Send a data message to a given hook.
439  */
440 int
441 ng_send_data(const char *hook, const void *sndbuf, size_t sndlen)
442 {
443 	return (NgSendData(dsock, hook, sndbuf, sndlen));
444 }
445 
446 /*
447  * Input from a data socket. Dispatch to the function for that hook.
448  */
449 static void
450 dsock_input(int fd __unused, void *udata __unused)
451 {
452 	u_char *resbuf, embuf[100];
453 	ssize_t len;
454 	char hook[NG_HOOKSIZ];
455 	struct datareg *d, *d1;
456 
457 	if ((resbuf = malloc(resbufsiz + 1)) == NULL) {
458 		stats[LEAF_begemotNgNoMems]++;
459 		syslog(LOG_CRIT, "out of memory");
460 		(void)NgRecvData(fd, embuf, sizeof(embuf), hook);
461 		errno = ENOMEM;
462 		return;
463 	}
464 	if ((len = NgRecvData(fd, resbuf, resbufsiz + 1, hook)) == -1) {
465 		stats[LEAF_begemotNgDataReadErrs]++;
466 		syslog(LOG_ERR, "reading message: %m");
467 		free(resbuf);
468 		return;
469 	}
470 	if (len == 0) {
471 		free(resbuf);
472 		return;
473 	}
474 	if ((size_t)len == resbufsiz + 1) {
475 		stats[LEAF_begemotNgTooLargeDatas]++;
476 		syslog(LOG_WARNING, "message too long");
477 		free(resbuf);
478 		return;
479 	}
480 
481 	/*
482 	 * Dispatch message. Maybe dispatched to more than one function.
483 	 */
484 	d = SLIST_FIRST(&datareg_list);
485 	while (d != NULL) {
486 		d1 = SLIST_NEXT(d, link);
487 		if (strcmp(hook, d->hook) == 0)
488 			(*d->func)(hook, resbuf, len, d->arg);
489 		d = d1;
490 	}
491 
492 	free(resbuf);
493 }
494 
495 /*
496  * The SNMP daemon is about to wait for an event. Look whether we have
497  * netgraph messages waiting. If yes, drain the queue.
498  */
499 static void
500 ng_idle(void)
501 {
502 	struct csock_buf *b;
503 
504 	/* execute waiting csock_bufs */
505 	while ((b = STAILQ_FIRST(&csock_bufs)) != NULL) {
506 		STAILQ_REMOVE_HEAD(&csock_bufs, link);
507 		csock_handle(b->mesg, b->path);
508 		free(b);
509 	}
510 }
511 
512 /*
513  * Called when the module is loaded. Returning a non-zero value means,
514  * rejecting the initialisation.
515  *
516  * We make the netgraph socket.
517  */
518 static int
519 ng_init(struct lmodule *mod, int argc, char *argv[])
520 {
521 	int name[2];
522 	size_t len;
523 
524 	module = mod;
525 
526 	if (argc == 0) {
527 		if ((snmp_nodename = malloc(strlen(NODENAME) + 1)) == NULL)
528 			return (ENOMEM);
529 		strcpy(snmp_nodename, NODENAME);
530 	} else {
531 		if ((snmp_nodename = malloc(NG_NODESIZ)) == NULL)
532 			return (ENOMEM);
533 		strlcpy(snmp_nodename, argv[0], NG_NODESIZ);
534 	}
535 
536 	/* fetch clockinfo (for the number of microseconds per tick) */
537 	name[0] = CTL_KERN;
538 	name[1] = KERN_CLOCKRATE;
539 	len = sizeof(clockinfo);
540 	if (sysctl(name, 2, &clockinfo, &len, NULL, 0) == -1)
541 		return (errno);
542 
543 	TAILQ_INIT(&ngtype_list);
544 
545 	return (0);
546 }
547 
548 /*
549  * Get the node Id/name/type of a node.
550  */
551 ng_ID_t
552 ng_node_id(const char *path)
553 {
554 	struct ng_mesg *resp;
555 	ng_ID_t id;
556 
557 	if ((resp = ng_dialog(path, NGM_GENERIC_COOKIE, NGM_NODEINFO,
558 	    NULL, 0)) == NULL)
559 		return (0);
560 	id = ((struct nodeinfo *)(void *)resp->data)->id;
561 	free(resp);
562 	return (id);
563 }
564 ng_ID_t
565 ng_node_id_node(const char *node)
566 {
567 	struct ng_mesg *resp;
568 	ng_ID_t id;
569 
570 	if ((resp = ng_dialog_node(node, NGM_GENERIC_COOKIE, NGM_NODEINFO,
571 	    NULL, 0)) == NULL)
572 		return (0);
573 	id = ((struct nodeinfo *)(void *)resp->data)->id;
574 	free(resp);
575 	return (id);
576 }
577 ng_ID_t
578 ng_node_name(ng_ID_t id, char *name)
579 {
580 	struct ng_mesg *resp;
581 
582 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
583 	    NULL, 0)) == NULL)
584 		return (0);
585 	strcpy(name, ((struct nodeinfo *)(void *)resp->data)->name);
586 	free(resp);
587 	return (id);
588 
589 }
590 ng_ID_t
591 ng_node_type(ng_ID_t id, char *type)
592 {
593 	struct ng_mesg *resp;
594 
595 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
596 	    NULL, 0)) == NULL)
597 		return (0);
598 	strcpy(type, ((struct nodeinfo *)(void *)resp->data)->type);
599 	free(resp);
600 	return (id);
601 }
602 
603 /*
604  * Connect our node to some other node
605  */
606 int
607 ng_connect_node(const char *node, const char *ourhook, const char *peerhook)
608 {
609 	struct ngm_connect conn;
610 
611 	snprintf(conn.path, NG_PATHSIZ, "%s:", node);
612 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
613 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
614 	return (NgSendMsg(csock, ".:",
615 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
616 }
617 int
618 ng_connect_id(ng_ID_t id, const char *ourhook, const char *peerhook)
619 {
620 	struct ngm_connect conn;
621 
622 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", id);
623 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
624 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
625 	return (NgSendMsg(csock, ".:",
626 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
627 }
628 
629 int
630 ng_connect2_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
631     const char *peerhook)
632 {
633 	struct ngm_connect conn;
634 	char path[NG_PATHSIZ];
635 
636 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
637 
638 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
639 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
640 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
641 	return (NgSendMsg(csock, path,
642 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
643 }
644 
645 int
646 ng_connect2_tee_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
647     const char *peerhook)
648 {
649 	struct ngm_connect conn;
650 	char path[NG_PATHSIZ];
651 	ng_ID_t tee;
652 
653 	if ((tee = ng_mkpeer_id(id, NULL, "tee", ourhook, "left")) == 0)
654 		return (-1);
655 
656 	snprintf(path, NG_PATHSIZ, "[%x]:", tee);
657 
658 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
659 	strlcpy(conn.ourhook, "right", NG_HOOKSIZ);
660 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
661 	return (NgSendMsg(csock, path,
662 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
663 }
664 
665 /*
666  * Ensure that a node of type 'type' is connected to 'hook' of 'node'
667  * and return its node id. tee nodes between node and the target node
668  * are skipped. If the type is wrong, or the hook is a dead-end return 0.
669  * If type is NULL, it is not checked.
670  */
671 static ng_ID_t
672 ng_next_node_id_internal(ng_ID_t node, const char *type, const char *hook,
673     int skip_tee)
674 {
675 	struct ng_mesg *resp;
676 	struct hooklist *hooklist;
677 	u_int i;
678 
679 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
680 	    NULL, 0)) == NULL) {
681 		syslog(LOG_ERR, "get hook list: %m");
682 		exit(1);
683 	}
684 	hooklist = (struct hooklist *)(void *)resp->data;
685 
686 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
687 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
688 			break;
689 
690 	if (i == hooklist->nodeinfo.hooks) {
691 		free(resp);
692 		return (0);
693 	}
694 
695 	node = hooklist->link[i].nodeinfo.id;
696 
697 	if (skip_tee && strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
698 		if (strcmp(hooklist->link[i].peerhook, "left") == 0)
699 			node = ng_next_node_id(node, type, "right");
700 		else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
701 			node = ng_next_node_id(node, type, "left");
702 		else if (type != NULL &&
703 		    strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
704 			node = 0;
705 
706 	} else if (type != NULL &&
707 	    strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
708 		node = 0;
709 
710 	free(resp);
711 
712 	return (node);
713 }
714 
715 /*
716  * Ensure that a node of type 'type' is connected to 'hook' of 'node'
717  * and return its node id. tee nodes between node and the target node
718  * are skipped. If the type is wrong, or the hook is a dead-end return 0.
719  * If type is NULL, it is not checked.
720  */
721 ng_ID_t
722 ng_next_node_id(ng_ID_t node, const char *type, const char *hook)
723 {
724 	return (ng_next_node_id_internal(node, type, hook, 1));
725 }
726 
727 ng_ID_t
728 ng_mkpeer_id(ng_ID_t id, const char *nodename, const char *type,
729     const char *hook, const char *peerhook)
730 {
731 	char path[NG_PATHSIZ];
732 	struct ngm_mkpeer mkpeer;
733 	struct ngm_name name;
734 
735 	strlcpy(mkpeer.type, type, NG_TYPESIZ);
736 	strlcpy(mkpeer.ourhook, hook, NG_HOOKSIZ);
737 	strlcpy(mkpeer.peerhook, peerhook, NG_HOOKSIZ);
738 
739 	sprintf(path, "[%x]:", id);
740 	if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER,
741 	    &mkpeer, sizeof(mkpeer)) == -1)
742 		return (0);
743 
744 	if ((id = ng_next_node_id_internal(id, NULL, hook, 0)) == 0)
745 		return (0);
746 
747 	if (nodename != NULL) {
748 		strcpy(name.name, nodename);
749 		sprintf(path, "[%x]:", id);
750 		if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_NAME,
751 		    &name, sizeof(name)) == -1)
752 			return (0);
753 	}
754 	return (id);
755 }
756 
757 /*
758  * SHutdown node
759  */
760 int
761 ng_shutdown_id(ng_ID_t id)
762 {
763 	char path[NG_PATHSIZ];
764 
765 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
766 	return (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
767 	    NGM_SHUTDOWN, NULL, 0));
768 }
769 
770 /*
771  * Disconnect one of our hooks
772  */
773 int
774 ng_rmhook(const char *ourhook)
775 {
776 	struct ngm_rmhook rmhook;
777 
778 	strlcpy(rmhook.ourhook, ourhook, NG_HOOKSIZ);
779 	return (NgSendMsg(csock, ".:",
780 	    NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
781 }
782 
783 /*
784  * Disconnect a hook of a node
785  */
786 int
787 ng_rmhook_id(ng_ID_t id, const char *hook)
788 {
789 	struct ngm_rmhook rmhook;
790 	char path[NG_PATHSIZ];
791 
792 	strlcpy(rmhook.ourhook, hook, NG_HOOKSIZ);
793 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
794 	return (NgSendMsg(csock, path,
795 	    NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
796 }
797 
798 /*
799  * Disconnect a hook and shutdown all tee nodes that were connected to that
800  * hook.
801  */
802 int
803 ng_rmhook_tee_id(ng_ID_t node, const char *hook)
804 {
805 	struct ng_mesg *resp;
806 	struct hooklist *hooklist;
807 	u_int i;
808 	int first = 1;
809 	ng_ID_t next_node;
810 	const char *next_hook;
811 
812   again:
813 	/* if we have just shutdown a tee node, which had no other hooks
814 	 * connected, the node id may already be wrong here. */
815 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
816 	    NULL, 0)) == NULL)
817 		return (0);
818 
819 	hooklist = (struct hooklist *)(void *)resp->data;
820 
821 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
822 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
823 			break;
824 
825 	if (i == hooklist->nodeinfo.hooks) {
826 		free(resp);
827 		return (0);
828 	}
829 
830 	next_node = 0;
831 	next_hook = NULL;
832 	if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
833 		if (strcmp(hooklist->link[i].peerhook, "left") == 0) {
834 			next_node = hooklist->link[i].nodeinfo.id;
835 			next_hook = "right";
836 		} else if (strcmp(hooklist->link[i].peerhook, "right") == 0) {
837 			next_node = hooklist->link[i].nodeinfo.id;
838 			next_hook = "left";
839 		}
840 	}
841 	free(resp);
842 
843 	if (first) {
844 		ng_rmhook_id(node, hook);
845 		first = 0;
846 	} else {
847 		ng_shutdown_id(node);
848 	}
849 	if ((node = next_node) == 0)
850 		return (0);
851 	hook = next_hook;
852 
853 	goto again;
854 }
855 
856 /*
857  * Get the peer hook of a hook on a given node. Skip any tee nodes in between
858  */
859 int
860 ng_peer_hook_id(ng_ID_t node, const char *hook, char *peerhook)
861 {
862 	struct ng_mesg *resp;
863 	struct hooklist *hooklist;
864 	u_int i;
865 	int ret;
866 
867 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
868 	    NULL, 0)) == NULL) {
869 		syslog(LOG_ERR, "get hook list: %m");
870 		exit(1);
871 	}
872 	hooklist = (struct hooklist *)(void *)resp->data;
873 
874 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
875 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
876 			break;
877 
878 	if (i == hooklist->nodeinfo.hooks) {
879 		free(resp);
880 		return (-1);
881 	}
882 
883 	node = hooklist->link[i].nodeinfo.id;
884 
885 	ret = 0;
886 	if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
887 		if (strcmp(hooklist->link[i].peerhook, "left") == 0)
888 			ret = ng_peer_hook_id(node, "right", peerhook);
889 		else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
890 			ret = ng_peer_hook_id(node, "left", peerhook);
891 		else
892 			strcpy(peerhook, hooklist->link[i].peerhook);
893 
894 	} else
895 		strcpy(peerhook, hooklist->link[i].peerhook);
896 
897 	free(resp);
898 
899 	return (ret);
900 }
901 
902 
903 /*
904  * Now the module is started. Select on the sockets, so that we can get
905  * unsolicited input.
906  */
907 static void
908 ng_start(void)
909 {
910 	if (snmp_node == 0) {
911 		if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
912 			syslog(LOG_ERR, "NgMkSockNode: %m");
913 			exit(1);
914 		}
915 		snmp_node = ng_node_id(".:");
916 	}
917 
918 	if ((csock_fd = fd_select(csock, csock_input, NULL, module)) == NULL) {
919 		syslog(LOG_ERR, "fd_select failed on csock: %m");
920 		return;
921 	}
922 	if ((dsock_fd = fd_select(dsock, dsock_input, NULL, module)) == NULL) {
923 		syslog(LOG_ERR, "fd_select failed on dsock: %m");
924 		return;
925 	}
926 
927 	reg_index = or_register(&oid_begemotNg,
928 	    "The MIB for the NetGraph access module for SNMP.", module);
929 }
930 
931 /*
932  * Called, when the module is to be unloaded after it was successfully loaded
933  */
934 static int
935 ng_fini(void)
936 {
937 	struct ngtype *t;
938 
939 	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
940 		TAILQ_REMOVE(&ngtype_list, t, link);
941 		free(t);
942 	}
943 
944 	if (csock_fd != NULL)
945 		fd_deselect(csock_fd);
946 	(void)close(csock);
947 
948 	if (dsock_fd != NULL)
949 		fd_deselect(dsock_fd);
950 	(void)close(dsock);
951 
952 	free(snmp_nodename);
953 
954 	or_unregister(reg_index);
955 
956 	return (0);
957 }
958 
959 const struct snmp_module config = {
960 	"This module implements access to the netgraph sub-system",
961 	ng_init,
962 	ng_fini,
963 	ng_idle,
964 	NULL,
965 	NULL,
966 	ng_start,
967 	NULL,
968 	netgraph_ctree,
969 	netgraph_CTREE_SIZE,
970 	NULL
971 };
972 
973 int
974 op_ng_config(struct snmp_context *ctx, struct snmp_value *value,
975     u_int sub, u_int iidx __unused, enum snmp_op op)
976 {
977 	asn_subid_t which = value->var.subs[sub - 1];
978 	int ret;
979 
980 	switch (op) {
981 
982 	  case SNMP_OP_GETNEXT:
983 		abort();
984 
985 	  case SNMP_OP_GET:
986 		/*
987 		 * Come here for GET, GETNEXT and COMMIT
988 		 */
989 		switch (which) {
990 
991 		  case LEAF_begemotNgControlNodeName:
992 			return (string_get(value, snmp_nodename, -1));
993 
994 		  case LEAF_begemotNgResBufSiz:
995 			value->v.integer = resbufsiz;
996 			break;
997 
998 		  case LEAF_begemotNgTimeout:
999 			value->v.integer = timeout;
1000 			break;
1001 
1002 		  case LEAF_begemotNgDebugLevel:
1003 			value->v.uint32 = debug_level;
1004 			break;
1005 
1006 		  default:
1007 			abort();
1008 		}
1009 		return (SNMP_ERR_NOERROR);
1010 
1011 	  case SNMP_OP_SET:
1012 		switch (which) {
1013 
1014 		  case LEAF_begemotNgControlNodeName:
1015 			/* only at initialisation */
1016 			if (community != COMM_INITIALIZE)
1017 				return (SNMP_ERR_NOT_WRITEABLE);
1018 
1019 			if (snmp_node != 0)
1020 				return (SNMP_ERR_NOT_WRITEABLE);
1021 
1022 			if ((ret = string_save(value, ctx, -1, &snmp_nodename))
1023 			    != SNMP_ERR_NOERROR)
1024 				return (ret);
1025 
1026 			if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
1027 				syslog(LOG_ERR, "NgMkSockNode: %m");
1028 				string_rollback(ctx, &snmp_nodename);
1029 				return (SNMP_ERR_GENERR);
1030 			}
1031 			snmp_node = ng_node_id(".:");
1032 
1033 			return (SNMP_ERR_NOERROR);
1034 
1035 		  case LEAF_begemotNgResBufSiz:
1036 			ctx->scratch->int1 = resbufsiz;
1037 			if (value->v.integer < 1024 ||
1038 			    value->v.integer > 0x10000)
1039 				return (SNMP_ERR_WRONG_VALUE);
1040 			resbufsiz = value->v.integer;
1041 			return (SNMP_ERR_NOERROR);
1042 
1043 		  case LEAF_begemotNgTimeout:
1044 			ctx->scratch->int1 = timeout;
1045 			if (value->v.integer < 10 ||
1046 			    value->v.integer > 10000)
1047 				return (SNMP_ERR_WRONG_VALUE);
1048 			timeout = value->v.integer;
1049 			return (SNMP_ERR_NOERROR);
1050 
1051 		  case LEAF_begemotNgDebugLevel:
1052 			ctx->scratch->int1 = debug_level;
1053 			debug_level = value->v.uint32;
1054 			NgSetDebug(debug_level);
1055 			return (SNMP_ERR_NOERROR);
1056 		}
1057 		abort();
1058 
1059 	  case SNMP_OP_ROLLBACK:
1060 		switch (which) {
1061 
1062 		  case LEAF_begemotNgControlNodeName:
1063 			string_rollback(ctx, &snmp_nodename);
1064 			close(csock);
1065 			close(dsock);
1066 			snmp_node = 0;
1067 			return (SNMP_ERR_NOERROR);
1068 
1069 		  case LEAF_begemotNgResBufSiz:
1070 			resbufsiz = ctx->scratch->int1;
1071 			return (SNMP_ERR_NOERROR);
1072 
1073 		  case LEAF_begemotNgTimeout:
1074 			timeout = ctx->scratch->int1;
1075 			return (SNMP_ERR_NOERROR);
1076 
1077 		  case LEAF_begemotNgDebugLevel:
1078 			debug_level = ctx->scratch->int1;
1079 			NgSetDebug(debug_level);
1080 			return (SNMP_ERR_NOERROR);
1081 		}
1082 		abort();
1083 
1084 	  case SNMP_OP_COMMIT:
1085 		switch (which) {
1086 
1087 		  case LEAF_begemotNgControlNodeName:
1088 			string_commit(ctx);
1089 			return (SNMP_ERR_NOERROR);
1090 
1091 		  case LEAF_begemotNgResBufSiz:
1092 		  case LEAF_begemotNgTimeout:
1093 		  case LEAF_begemotNgDebugLevel:
1094 			return (SNMP_ERR_NOERROR);
1095 		}
1096 		abort();
1097 	}
1098 	abort();
1099 }
1100 
1101 int
1102 op_ng_stats(struct snmp_context *ctx __unused, struct snmp_value *value,
1103     u_int sub, u_int iidx __unused, enum snmp_op op)
1104 {
1105 	switch (op) {
1106 
1107 	  case SNMP_OP_GETNEXT:
1108 		abort();
1109 
1110 	  case SNMP_OP_GET:
1111 		value->v.uint32 = stats[value->var.subs[sub - 1] - 1];
1112 		return (SNMP_ERR_NOERROR);
1113 
1114 	  case SNMP_OP_SET:
1115 		return (SNMP_ERR_NOT_WRITEABLE);
1116 
1117 	  case SNMP_OP_ROLLBACK:
1118 	  case SNMP_OP_COMMIT:
1119 		abort();
1120 	}
1121 	abort();
1122 }
1123 
1124 /*
1125  * Netgraph type table
1126  */
1127 static int
1128 fetch_types(void)
1129 {
1130 	struct ngtype *t;
1131 	struct typelist *typelist;
1132 	struct ng_mesg *resp;
1133 	u_int u, i;
1134 
1135 	if (this_tick <= ngtype_tick)
1136 		return (0);
1137 
1138 	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
1139 		TAILQ_REMOVE(&ngtype_list, t, link);
1140 		free(t);
1141 	}
1142 
1143 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE,
1144 	    NGM_LISTTYPES, NULL, 0)) == NULL)
1145 		return (SNMP_ERR_GENERR);
1146 	typelist = (struct typelist *)(void *)resp->data;
1147 
1148 	for (u = 0; u < typelist->numtypes; u++) {
1149 		if ((t = malloc(sizeof(*t))) == NULL) {
1150 			free(resp);
1151 			return (SNMP_ERR_GENERR);
1152 		}
1153 		strcpy(t->name, typelist->typeinfo[u].type_name);
1154 		t->index.subs[0] = strlen(t->name);
1155 		t->index.len = t->index.subs[0] + 1;
1156 		for (i = 0; i < t->index.subs[0]; i++)
1157 			t->index.subs[i + 1] = t->name[i];
1158 
1159 		INSERT_OBJECT_OID(t, &ngtype_list);
1160 	}
1161 
1162 	ngtype_tick = this_tick;
1163 
1164 	free(resp);
1165 	return (0);
1166 }
1167 
1168 /*
1169  * Try to load the netgraph type with the given name. We assume, that
1170  * type 'type' is implemented in the kernel module 'ng_type'.
1171  */
1172 static int
1173 ngtype_load(const u_char *name, size_t namelen)
1174 {
1175 	char *mod;
1176 	int ret;
1177 
1178 	if ((mod = malloc(namelen + 4)) == NULL)
1179 		return (-1);
1180 	strcpy(mod, "ng_");
1181 	strncpy(mod + 3, name, namelen);
1182 	mod[namelen + 3] = '\0';
1183 
1184 	ret = kldload(mod);
1185 	free(mod);
1186 	return (ret);
1187 }
1188 
1189 /*
1190  * Unload a netgraph type.
1191  */
1192 static int
1193 ngtype_unload(const u_char *name, size_t namelen)
1194 {
1195 	char *mod;
1196 	int id;
1197 
1198 	if ((mod = malloc(namelen + 4)) == NULL)
1199 		return (-1);
1200 	strcpy(mod, "ng_");
1201 	strncpy(mod + 3, name, namelen);
1202 	mod[namelen + 3] = '\0';
1203 
1204 	if ((id = kldfind(mod)) == -1) {
1205 		free(mod);
1206 		return (-1);
1207 	}
1208 	free(mod);
1209 	return (kldunload(id));
1210 }
1211 
1212 int
1213 op_ng_type(struct snmp_context *ctx, struct snmp_value *value,
1214     u_int sub, u_int iidx, enum snmp_op op)
1215 {
1216 	asn_subid_t which = value->var.subs[sub - 1];
1217 	struct ngtype *t;
1218 	u_char *name;
1219 	size_t namelen;
1220 	int status = 1;
1221 	int ret;
1222 
1223 	switch (op) {
1224 
1225 	  case SNMP_OP_GETNEXT:
1226 		if ((ret = fetch_types()) != 0)
1227 			return (ret);
1228 		if ((t = NEXT_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1229 			return (SNMP_ERR_NOSUCHNAME);
1230 		index_append(&value->var, sub, &t->index);
1231 		break;
1232 
1233 	  case SNMP_OP_GET:
1234 		if ((ret = fetch_types()) != 0)
1235 			return (ret);
1236 		if ((t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1237 			return (SNMP_ERR_NOSUCHNAME);
1238 		break;
1239 
1240 	  case SNMP_OP_SET:
1241 		if (index_decode(&value->var, sub, iidx, &name, &namelen))
1242 			return (SNMP_ERR_NO_CREATION);
1243 		if (namelen == 0 || namelen >= NG_TYPESIZ) {
1244 			free(name);
1245 			return (SNMP_ERR_NO_CREATION);
1246 		}
1247 		if ((ret = fetch_types()) != 0) {
1248 			free(name);
1249 			return (ret);
1250 		}
1251 		t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub);
1252 
1253 		if (which != LEAF_begemotNgTypeStatus) {
1254 			free(name);
1255 			if (t != NULL)
1256 				return (SNMP_ERR_NOT_WRITEABLE);
1257 			return (SNMP_ERR_NO_CREATION);
1258 		}
1259 		if (!TRUTH_OK(value->v.integer)) {
1260 			free(name);
1261 			return (SNMP_ERR_WRONG_VALUE);
1262 		}
1263 		ctx->scratch->int1 = TRUTH_GET(value->v.integer);
1264 		ctx->scratch->int1 |= (t != NULL) << 1;
1265 		ctx->scratch->ptr2 = name;
1266 		ctx->scratch->int2 = namelen;
1267 
1268 		if (t == NULL) {
1269 			/* type not loaded */
1270 			if (ctx->scratch->int1 & 1) {
1271 				/* request to load */
1272 				if (ngtype_load(name, namelen) == -1) {
1273 					free(name);
1274 					if (errno == ENOENT)
1275 						return (SNMP_ERR_INCONS_NAME);
1276 					else
1277 						return (SNMP_ERR_GENERR);
1278 				}
1279 			}
1280 		} else {
1281 			/* is type loaded */
1282 			if (!(ctx->scratch->int1 & 1)) {
1283 				/* request to unload */
1284 				if (ngtype_unload(name, namelen) == -1) {
1285 					free(name);
1286 					return (SNMP_ERR_GENERR);
1287 				}
1288 			}
1289 		}
1290 		return (SNMP_ERR_NOERROR);
1291 
1292 	  case SNMP_OP_ROLLBACK:
1293 		ret = SNMP_ERR_NOERROR;
1294 		if (!(ctx->scratch->int1 & 2)) {
1295 			/* did not exist */
1296 			if (ctx->scratch->int1 & 1) {
1297 				/* request to load - unload */
1298 				if (ngtype_unload(ctx->scratch->ptr2,
1299 				    ctx->scratch->int2) == -1)
1300 					ret = SNMP_ERR_UNDO_FAILED;
1301 			}
1302 		} else {
1303 			/* did exist */
1304 			if (!(ctx->scratch->int1 & 1)) {
1305 				/* request to unload - reload */
1306 				if (ngtype_load(ctx->scratch->ptr2,
1307 				    ctx->scratch->int2) == -1)
1308 					ret = SNMP_ERR_UNDO_FAILED;
1309 			}
1310 		}
1311 		free(ctx->scratch->ptr2);
1312 		return (ret);
1313 
1314 	  case SNMP_OP_COMMIT:
1315 		free(ctx->scratch->ptr2);
1316 		return (SNMP_ERR_NOERROR);
1317 
1318 	  default:
1319 		abort();
1320 	}
1321 
1322 	/*
1323 	 * Come here for GET and COMMIT
1324 	 */
1325 	switch (which) {
1326 
1327 	  case LEAF_begemotNgTypeStatus:
1328 		value->v.integer = status;
1329 		break;
1330 
1331 	  default:
1332 		abort();
1333 	}
1334 	return (SNMP_ERR_NOERROR);
1335 }
1336 
1337 /*
1338  * Implement the node table
1339  */
1340 static int
1341 find_node(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1342 {
1343 	ng_ID_t id = oid->subs[sub];
1344 	struct ng_mesg *resp;
1345 
1346 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
1347 	    NULL, 0)) == NULL)
1348 		return (-1);
1349 
1350 	*info = *(struct nodeinfo *)(void *)resp->data;
1351 	free(resp);
1352 	return (0);
1353 }
1354 
1355 static int
1356 ncmp(const void *p1, const void *p2)
1357 {
1358 	const struct nodeinfo *i1 = p1;
1359 	const struct nodeinfo *i2 = p2;
1360 
1361 	if (i1->id < i2->id)
1362 		return (-1);
1363 	if (i1->id > i2->id)
1364 		return (+1);
1365 	return (0);
1366 }
1367 
1368 static int
1369 find_node_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1370 {
1371 	u_int idxlen = oid->len - sub;
1372 	struct ng_mesg *resp;
1373 	struct namelist *list;
1374 	ng_ID_t id;
1375 	u_int i;
1376 
1377 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1378 	    NULL, 0)) == NULL)
1379 		return (-1);
1380 	list = (struct namelist *)(void *)resp->data;
1381 
1382 	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1383 
1384 	if (idxlen == 0) {
1385 		if (list->numnames == 0) {
1386 			free(resp);
1387 			return (-1);
1388 		}
1389 		*info = list->nodeinfo[0];
1390 		free(resp);
1391 		return (0);
1392 	}
1393 	id = oid->subs[sub];
1394 
1395 	for (i = 0; i < list->numnames; i++)
1396 		if (list->nodeinfo[i].id > id) {
1397 			*info = list->nodeinfo[i];
1398 			free(resp);
1399 			return (0);
1400 		}
1401 
1402 	free(resp);
1403 	return (-1);
1404 }
1405 
1406 int
1407 op_ng_node(struct snmp_context *ctx __unused, struct snmp_value *value,
1408     u_int sub, u_int iidx __unused, enum snmp_op op)
1409 {
1410 	asn_subid_t which = value->var.subs[sub - 1];
1411 	u_int idxlen = value->var.len - sub;
1412 	struct nodeinfo nodeinfo;
1413 
1414 	switch (op) {
1415 
1416 	  case SNMP_OP_GETNEXT:
1417 		if (find_node_next(&value->var, sub, &nodeinfo) == -1)
1418 			return (SNMP_ERR_NOSUCHNAME);
1419 		value->var.len = sub + 1;
1420 		value->var.subs[sub] = nodeinfo.id;
1421 		break;
1422 
1423 	  case SNMP_OP_GET:
1424 		if (idxlen != 1)
1425 			return (SNMP_ERR_NOSUCHNAME);
1426 		if (find_node(&value->var, sub, &nodeinfo) == -1)
1427 			return (SNMP_ERR_NOSUCHNAME);
1428 		break;
1429 
1430 	  case SNMP_OP_SET:
1431 		if (idxlen != 1)
1432 			return (SNMP_ERR_NO_CREATION);
1433 		if (find_node(&value->var, sub, &nodeinfo) == -1)
1434 			return (SNMP_ERR_NO_CREATION);
1435 		return (SNMP_ERR_NOT_WRITEABLE);
1436 
1437 	  case SNMP_OP_ROLLBACK:
1438 	  case SNMP_OP_COMMIT:
1439 	  default:
1440 		abort();
1441 	}
1442 
1443 	/*
1444 	 * Come here for GET and COMMIT
1445 	 */
1446 	switch (which) {
1447 
1448 	  case LEAF_begemotNgNodeStatus:
1449 		value->v.integer = 1;
1450 		break;
1451 	  case LEAF_begemotNgNodeName:
1452 		return (string_get(value, nodeinfo.name, -1));
1453 	  case LEAF_begemotNgNodeType:
1454 		return (string_get(value, nodeinfo.type, -1));
1455 	  case LEAF_begemotNgNodeHooks:
1456 		value->v.uint32 = nodeinfo.hooks;
1457 		break;
1458 
1459 	  default:
1460 		abort();
1461 	}
1462 	return (SNMP_ERR_NOERROR);
1463 }
1464 
1465 /*
1466  * Implement the hook table
1467  */
1468 static int
1469 find_hook(int32_t id, const u_char *hook, size_t hooklen, struct linkinfo *info)
1470 {
1471 	struct ng_mesg *resp;
1472 	struct hooklist *list;
1473 	u_int i;
1474 
1475 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE,
1476 	    NGM_LISTHOOKS, NULL, 0)) == NULL)
1477 		return (-1);
1478 
1479 	list = (struct hooklist *)(void *)resp->data;
1480 
1481 	for (i = 0; i < list->nodeinfo.hooks; i++) {
1482 		if (strlen(list->link[i].ourhook) == hooklen &&
1483 		    strncmp(list->link[i].ourhook, hook, hooklen) == 0) {
1484 			*info = list->link[i];
1485 			free(resp);
1486 			return (0);
1487 		}
1488 	}
1489 	free(resp);
1490 	return (-1);
1491 }
1492 
1493 static int
1494 hook_cmp(const void *p1, const void *p2)
1495 {
1496 	const struct linkinfo *i1 = p1;
1497 	const struct linkinfo *i2 = p2;
1498 
1499 	if (strlen(i1->ourhook) < strlen(i2->ourhook))
1500 		return (-1);
1501 	if (strlen(i1->ourhook) > strlen(i2->ourhook))
1502 		return (+1);
1503 	return (strcmp(i1->ourhook, i2->ourhook));
1504 }
1505 
1506 static int
1507 find_hook_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *nodeinfo,
1508     struct linkinfo *linkinfo)
1509 {
1510 	u_int idxlen = oid->len - sub;
1511 	struct namelist *list;
1512 	struct ng_mesg *resp;
1513 	struct hooklist *hooks;
1514 	struct ng_mesg *resp1;
1515 	u_int node_index;
1516 	struct asn_oid idx;
1517 	u_int i, j;
1518 
1519 	/*
1520 	 * Get and sort Node list
1521 	 */
1522 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1523 	    NULL, 0)) == NULL)
1524 		return (-1);
1525 	list = (struct namelist *)(void *)resp->data;
1526 
1527 	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1528 
1529 	/*
1530 	 * If we have no index, take the first node and return the
1531 	 * first hook.
1532 	 */
1533 	if (idxlen == 0) {
1534 		node_index = 0;
1535 		goto return_first_hook;
1536 	}
1537 
1538 	/*
1539 	 * Locate node
1540 	 */
1541 	for (node_index = 0; node_index < list->numnames; node_index++)
1542 		if (list->nodeinfo[node_index].id >= oid->subs[sub])
1543 			break;
1544 
1545 	/*
1546 	 * If we have only the node part of the index take, or
1547 	 * there is no node with that Id, take the first hook of that node.
1548 	 */
1549 	if (idxlen == 1 || node_index >= list->numnames ||
1550 	    list->nodeinfo[node_index].id > oid->subs[sub])
1551 		goto return_first_hook;
1552 
1553 	/*
1554 	 * We had an exact match on the node id and have (at last part)
1555 	 * of the hook name index. Loop through the hooks of the node
1556 	 * and find the next one.
1557 	 */
1558 	if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1559 	    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) {
1560 		free(resp);
1561 		return (-1);
1562 	}
1563 	hooks = (struct hooklist *)(void *)resp1->data;
1564 	if (hooks->nodeinfo.hooks > 0) {
1565 		qsort(hooks->link, hooks->nodeinfo.hooks,
1566 		    sizeof(hooks->link[0]), hook_cmp);
1567 		for (i = 0; i < hooks->nodeinfo.hooks; i++) {
1568 			idx.len = strlen(hooks->link[i].ourhook) + 1;
1569 			idx.subs[0] = idx.len - 1;
1570 			for (j = 0; j < idx.len; j++)
1571 				idx.subs[j + 1] = hooks->link[i].ourhook[j];
1572 			if (index_compare(oid, sub + 1, &idx) < 0)
1573 				break;
1574 		}
1575 		if (i < hooks->nodeinfo.hooks) {
1576 			*nodeinfo = hooks->nodeinfo;
1577 			*linkinfo = hooks->link[i];
1578 
1579 			free(resp);
1580 			free(resp1);
1581 			return (0);
1582 		}
1583 	}
1584 
1585 	/* no hook found larger than the index on the index node - take
1586 	 * first hook of next node */
1587 	free(resp1);
1588 	node_index++;
1589 
1590   return_first_hook:
1591 	while (node_index < list->numnames) {
1592 		if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1593 		    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL)
1594 			break;
1595 		hooks = (struct hooklist *)(void *)resp1->data;
1596 		if (hooks->nodeinfo.hooks > 0) {
1597 			qsort(hooks->link, hooks->nodeinfo.hooks,
1598 			    sizeof(hooks->link[0]), hook_cmp);
1599 
1600 			*nodeinfo = hooks->nodeinfo;
1601 			*linkinfo = hooks->link[0];
1602 
1603 			free(resp);
1604 			free(resp1);
1605 			return (0);
1606 		}
1607 
1608 		/* if we don't have hooks, try next node */
1609 		free(resp1);
1610 		node_index++;
1611 	}
1612 
1613 	free(resp);
1614 	return (-1);
1615 }
1616 
1617 int
1618 op_ng_hook(struct snmp_context *ctx __unused, struct snmp_value *value,
1619     u_int sub, u_int iidx, enum snmp_op op)
1620 {
1621 	asn_subid_t which = value->var.subs[sub - 1];
1622 	struct linkinfo linkinfo;
1623 	struct nodeinfo nodeinfo;
1624 	u_int32_t lid;
1625 	u_char *hook;
1626 	size_t hooklen;
1627 	u_int i;
1628 
1629 	switch (op) {
1630 
1631 	  case SNMP_OP_GETNEXT:
1632 		if (find_hook_next(&value->var, sub, &nodeinfo, &linkinfo) == -1)
1633 			return (SNMP_ERR_NOSUCHNAME);
1634 
1635 		value->var.len = sub + 1 + 1 + strlen(linkinfo.ourhook);
1636 		value->var.subs[sub] = nodeinfo.id;
1637 		value->var.subs[sub + 1] = strlen(linkinfo.ourhook);
1638 		for (i = 0; i < strlen(linkinfo.ourhook); i++)
1639 			value->var.subs[sub + i + 2] =
1640 			    linkinfo.ourhook[i];
1641 		break;
1642 
1643 	  case SNMP_OP_GET:
1644 		if (index_decode(&value->var, sub, iidx, &lid,
1645 		    &hook, &hooklen))
1646 			return (SNMP_ERR_NOSUCHNAME);
1647 		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1648 			free(hook);
1649 			return (SNMP_ERR_NOSUCHNAME);
1650 		}
1651 		free(hook);
1652 		break;
1653 
1654 	  case SNMP_OP_SET:
1655 		if (index_decode(&value->var, sub, iidx, &lid,
1656 		    &hook, &hooklen))
1657 			return (SNMP_ERR_NO_CREATION);
1658 		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1659 			free(hook);
1660 			return (SNMP_ERR_NO_CREATION);
1661 		}
1662 		free(hook);
1663 		return (SNMP_ERR_NOT_WRITEABLE);
1664 
1665 	  case SNMP_OP_ROLLBACK:
1666 	  case SNMP_OP_COMMIT:
1667 	  default:
1668 		abort();
1669 
1670 	}
1671 
1672 	switch (which) {
1673 
1674 	  case LEAF_begemotNgHookStatus:
1675 		value->v.integer = 1;
1676 		break;
1677 	  case LEAF_begemotNgHookPeerNodeId:
1678 		value->v.uint32 = linkinfo.nodeinfo.id;
1679 		break;
1680 	  case LEAF_begemotNgHookPeerHook:
1681 		return (string_get(value, linkinfo.peerhook, -1));
1682 	  case LEAF_begemotNgHookPeerType:
1683 		return (string_get(value, linkinfo.nodeinfo.type, -1));
1684 	  default:
1685 		abort();
1686 	}
1687 	return (SNMP_ERR_NOERROR);
1688 }
1689