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