diff options
Diffstat (limited to 'usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c')
-rw-r--r-- | usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c | 1690 |
1 files changed, 1690 insertions, 0 deletions
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c new file mode 100644 index 0000000..d9d136a --- /dev/null +++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c @@ -0,0 +1,1690 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + * Netgraph interface for SNMPd. + */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/socket.h> +#include <sys/syslog.h> +#include <sys/queue.h> +#include <sys/sysctl.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <netgraph.h> +#include <bsnmp/snmpmod.h> +#include "snmp_netgraph.h" +#include "netgraph_tree.h" +#include "netgraph_oid.h" + +/* maximum message size */ +#define RESBUFSIZ 20000 + +/* default node name */ +#define NODENAME "NgSnmpd" + +/* my node Id */ +ng_ID_t snmp_node; +u_char *snmp_nodename; + +/* the Object Resource registration index */ +static u_int reg_index; +static const struct asn_oid oid_begemotNg = OIDX_begemotNg; + +/* configuration */ +/* this must be smaller than int32_t because the functions in libnetgraph + * falsely return an int */ +static size_t resbufsiz = RESBUFSIZ; +static u_int timeout = 1000; +static u_int debug_level; + +/* number of microseconds per clock tick */ +static struct clockinfo clockinfo; + +/* Csock buffers. Communication on the csock is asynchronuous. This means + * if we wait for a specific response, we may get other messages. Put these + * into a queue and execute them when we are idle. */ +struct csock_buf { + STAILQ_ENTRY(csock_buf) link; + struct ng_mesg *mesg; + char path[NG_PATHSIZ]; +}; +static STAILQ_HEAD(, csock_buf) csock_bufs = + STAILQ_HEAD_INITIALIZER(csock_bufs); + +/* + * We dispatch unsolicieted messages by node cookies and ids. + * So we must keep a list of hook names and dispatch functions. + */ +struct msgreg { + u_int32_t cookie; + ng_ID_t id; + ng_cookie_f *func; + void *arg; + const struct lmodule *mod; + SLIST_ENTRY(msgreg) link; +}; +static SLIST_HEAD(, msgreg) msgreg_list = + SLIST_HEAD_INITIALIZER(msgreg_list); + +/* + * Data messages are dispatched by hook names. + */ +struct datareg { + char hook[NG_HOOKSIZ]; + ng_hook_f *func; + void *arg; + const struct lmodule *mod; + SLIST_ENTRY(datareg) link; +}; +static SLIST_HEAD(, datareg) datareg_list = + SLIST_HEAD_INITIALIZER(datareg_list); + +/* the netgraph sockets */ +static int csock, dsock; +static void *csock_fd, *dsock_fd; + +/* our module handle */ +static struct lmodule *module; + +/* statistics */ +static u_int32_t stats[LEAF_begemotNgTooLargeDatas+1]; + +/* netgraph type list */ +struct ngtype { + char name[NG_TYPESIZ]; + struct asn_oid index; + TAILQ_ENTRY(ngtype) link; +}; +TAILQ_HEAD(ngtype_list, ngtype); + +static struct ngtype_list ngtype_list; +static uint64_t ngtype_tick; + + +/* + * Register a function to receive unsolicited messages + */ +void * +ng_register_cookie(const struct lmodule *mod, u_int32_t cookie, ng_ID_t id, + ng_cookie_f *func, void *arg) +{ + struct msgreg *d; + + if ((d = malloc(sizeof(*d))) == NULL) + return (NULL); + + d->cookie = cookie; + d->id = id; + d->func = func; + d->arg = arg; + d->mod = mod; + + SLIST_INSERT_HEAD(&msgreg_list, d, link); + + return (d); +} + +/* + * Remove a registration. + */ +void +ng_unregister_cookie(void *dd) +{ + struct msgreg *d = dd; + + SLIST_REMOVE(&msgreg_list, d, msgreg, link); + free(d); +} + +/* + * Register a function for hook data. + */ +void * +ng_register_hook(const struct lmodule *mod, const char *hook, + ng_hook_f *func, void *arg) +{ + struct datareg *d; + + if ((d = malloc(sizeof(*d))) == NULL) + return (NULL); + + strcpy(d->hook, hook); + d->func = func; + d->arg = arg; + d->mod = mod; + + SLIST_INSERT_HEAD(&datareg_list, d, link); + + return (d); +} + +/* + * Unregister a hook function + */ +void +ng_unregister_hook(void *dd) +{ + struct datareg *d = dd; + + SLIST_REMOVE(&datareg_list, d, datareg, link); + free(d); +} + +/* + * Unregister all hooks and cookies for that module. Note: doesn't disconnect + * any hooks! + */ +void +ng_unregister_module(const struct lmodule *mod) +{ + struct msgreg *m, *m1; + struct datareg *d, *d1; + + m = SLIST_FIRST(&msgreg_list); + while (m != NULL) { + m1 = SLIST_NEXT(m, link); + if (m->mod == mod) { + SLIST_REMOVE(&msgreg_list, m, msgreg, link); + free(m); + } + m = m1; + } + + d = SLIST_FIRST(&datareg_list); + while (d != NULL) { + d1 = SLIST_NEXT(d, link); + if (d->mod == mod) { + SLIST_REMOVE(&datareg_list, d, datareg, link); + free(d); + } + d = d1; + } +} + +/* + * Dispatch a message to the correct module and delete it. More than one + * module can get a message. + */ +static void +csock_handle(struct ng_mesg *mesg, const char *path) +{ + struct msgreg *d, *d1; + u_int id; + int len; + + if (sscanf(path, "[%x]:%n", &id, &len) != 1 || + (u_int)len != strlen(path)) { + syslog(LOG_ERR, "cannot parse message path '%s'", path); + id = 0; + } + + d = SLIST_FIRST(&msgreg_list); + while (d != NULL) { + d1 = SLIST_NEXT(d, link); + if (d->cookie == mesg->header.typecookie && + (d->id == 0 || d->id == id || id == 0)) + (*d->func)(mesg, path, id, d->arg); + d = d1; + } + free(mesg); +} + +/* + * Input from the control socket. + */ +static struct ng_mesg * +csock_read(char *path) +{ + struct ng_mesg *mesg; + int ret, err; + + if ((mesg = malloc(resbufsiz + 1)) == NULL) { + stats[LEAF_begemotNgNoMems]++; + syslog(LOG_CRIT, "out of memory"); + errno = ENOMEM; + return (NULL); + } + if ((ret = NgRecvMsg(csock, mesg, resbufsiz + 1, path)) < 0) { + err = errno; + free(mesg); + if (errno == EWOULDBLOCK) { + errno = err; + return (NULL); + } + stats[LEAF_begemotNgMsgReadErrs]++; + syslog(LOG_WARNING, "read from csock: %m"); + errno = err; + return (NULL); + } + if (ret == 0) { + syslog(LOG_DEBUG, "node closed -- exiting"); + exit(0); + } + if ((size_t)ret > resbufsiz) { + stats[LEAF_begemotNgTooLargeMsgs]++; + syslog(LOG_WARNING, "ng message too large"); + free(mesg); + errno = EFBIG; + return (NULL); + } + return (mesg); +} + +static void +csock_input(int fd __unused, void *udata __unused) +{ + struct ng_mesg *mesg; + char path[NG_PATHSIZ]; + + if ((mesg = csock_read(path)) == NULL) + return; + + csock_handle(mesg, path); +} + +/* + * Write a message to a node. + */ +int +ng_output(const char *path, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + return (NgSendMsg(csock, path, (int)cookie, (int)opcode, arg, arglen)); +} +int +ng_output_node(const char *node, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + char path[NG_PATHSIZ]; + + sprintf(path, "%s:", node); + return (ng_output(path, cookie, opcode, arg, arglen)); +} +int +ng_output_id(ng_ID_t node, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + char path[NG_PATHSIZ]; + + sprintf(path, "[%x]:", node); + return (ng_output(path, cookie, opcode, arg, arglen)); +} + + + +/* + * Execute a synchronuous dialog with the csock. All message we receive, that + * do not match our request, are queue until the next call to the IDLE function. + */ +struct ng_mesg * +ng_dialog(const char *path, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + int token, err; + struct ng_mesg *mesg; + char rpath[NG_PATHSIZ]; + struct csock_buf *b; + struct timeval end, tv; + + if ((token = ng_output(path, cookie, opcode, arg, arglen)) < 0) + return (NULL); + + if (csock_fd) + fd_suspend(csock_fd); + + gettimeofday(&end, NULL); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + timeradd(&end, &tv, &end); + for (;;) { + mesg = NULL; + gettimeofday(&tv, NULL); + if (timercmp(&tv, &end, >=)) { + block: + syslog(LOG_WARNING, "no response for request %u/%u", + cookie, opcode); + errno = EWOULDBLOCK; + break; + } + timersub(&end, &tv, &tv); + if (tv.tv_sec == 0 && tv.tv_usec < clockinfo.tick) + goto block; + + if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) + syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO): %m"); + if ((mesg = csock_read(rpath)) == NULL) { + if (errno == EWOULDBLOCK) + continue; + break; + } + if (mesg->header.token == (u_int)token) + break; + if ((b = malloc(sizeof(*b))) == NULL) { + stats[LEAF_begemotNgNoMems]++; + syslog(LOG_ERR, "out of memory"); + free(mesg); + continue; + } + b->mesg = mesg; + strcpy(b->path, rpath); + STAILQ_INSERT_TAIL(&csock_bufs, b, link); + } + + tv.tv_sec = 0; + tv.tv_usec = 0; + if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) + syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO,0): %m"); + + if (csock_fd) { + err = errno; + fd_resume(csock_fd); + errno = err; + } + + return (mesg); +} +struct ng_mesg * +ng_dialog_node(const char *node, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + char path[NG_PATHSIZ]; + + sprintf(path, "%s:", node); + return (ng_dialog(path, cookie, opcode, arg, arglen)); +} +struct ng_mesg * +ng_dialog_id(ng_ID_t id, u_int cookie, u_int opcode, + const void *arg, size_t arglen) +{ + char path[NG_PATHSIZ]; + + sprintf(path, "[%x]:", id); + return (ng_dialog(path, cookie, opcode, arg, arglen)); +} + + +/* + * Send a data message to a given hook. + */ +int +ng_send_data(const char *hook, const void *sndbuf, size_t sndlen) +{ + return (NgSendData(dsock, hook, sndbuf, sndlen)); +} + +/* + * Input from a data socket. Dispatch to the function for that hook. + */ +static void +dsock_input(int fd __unused, void *udata __unused) +{ + u_char *resbuf, embuf[100]; + ssize_t len; + char hook[NG_HOOKSIZ]; + struct datareg *d, *d1; + + if ((resbuf = malloc(resbufsiz + 1)) == NULL) { + stats[LEAF_begemotNgNoMems]++; + syslog(LOG_CRIT, "out of memory"); + (void)NgRecvData(fd, embuf, sizeof(embuf), hook); + errno = ENOMEM; + return; + } + if ((len = NgRecvData(fd, resbuf, resbufsiz + 1, hook)) == -1) { + stats[LEAF_begemotNgDataReadErrs]++; + syslog(LOG_ERR, "reading message: %m"); + free(resbuf); + return; + } + if (len == 0) { + free(resbuf); + return; + } + if ((size_t)len == resbufsiz + 1) { + stats[LEAF_begemotNgTooLargeDatas]++; + syslog(LOG_WARNING, "message too long"); + free(resbuf); + return; + } + + /* + * Dispatch message. Maybe dispatched to more than one function. + */ + d = SLIST_FIRST(&datareg_list); + while (d != NULL) { + d1 = SLIST_NEXT(d, link); + if (strcmp(hook, d->hook) == 0) + (*d->func)(hook, resbuf, len, d->arg); + d = d1; + } + + free(resbuf); +} + +/* + * The SNMP daemon is about to wait for an event. Look whether we have + * netgraph messages waiting. If yes, drain the queue. + */ +static void +ng_idle(void) +{ + struct csock_buf *b; + + /* execute waiting csock_bufs */ + while ((b = STAILQ_FIRST(&csock_bufs)) != NULL) { + STAILQ_REMOVE_HEAD(&csock_bufs, link); + csock_handle(b->mesg, b->path); + free(b); + } +} + +/* + * Called when the module is loaded. Returning a non-zero value means, + * rejecting the initialisation. + * + * We make the netgraph socket. + */ +static int +ng_init(struct lmodule *mod, int argc, char *argv[]) +{ + int name[2]; + size_t len; + + module = mod; + + if (argc == 0) { + if ((snmp_nodename = malloc(strlen(NODENAME) + 1)) == NULL) + return (ENOMEM); + strcpy(snmp_nodename, NODENAME); + } else { + if ((snmp_nodename = malloc(NG_NODESIZ)) == NULL) + return (ENOMEM); + strlcpy(snmp_nodename, argv[0], NG_NODESIZ); + } + + /* fetch clockinfo (for the number of microseconds per tick) */ + name[0] = CTL_KERN; + name[1] = KERN_CLOCKRATE; + len = sizeof(clockinfo); + if (sysctl(name, 2, &clockinfo, &len, NULL, 0) == -1) + return (errno); + + TAILQ_INIT(&ngtype_list); + + return (0); +} + +/* + * Get the node Id/name/type of a node. + */ +ng_ID_t +ng_node_id(const char *path) +{ + struct ng_mesg *resp; + ng_ID_t id; + + if ((resp = ng_dialog(path, NGM_GENERIC_COOKIE, NGM_NODEINFO, + NULL, 0)) == NULL) + return (0); + id = ((struct nodeinfo *)(void *)resp->data)->id; + free(resp); + return (id); +} +ng_ID_t +ng_node_id_node(const char *node) +{ + struct ng_mesg *resp; + ng_ID_t id; + + if ((resp = ng_dialog_node(node, NGM_GENERIC_COOKIE, NGM_NODEINFO, + NULL, 0)) == NULL) + return (0); + id = ((struct nodeinfo *)(void *)resp->data)->id; + free(resp); + return (id); +} +ng_ID_t +ng_node_name(ng_ID_t id, char *name) +{ + struct ng_mesg *resp; + + if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO, + NULL, 0)) == NULL) + return (0); + strcpy(name, ((struct nodeinfo *)(void *)resp->data)->name); + free(resp); + return (id); + +} +ng_ID_t +ng_node_type(ng_ID_t id, char *type) +{ + struct ng_mesg *resp; + + if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO, + NULL, 0)) == NULL) + return (0); + strcpy(type, ((struct nodeinfo *)(void *)resp->data)->type); + free(resp); + return (id); +} + +/* + * Connect our node to some other node + */ +int +ng_connect_node(const char *node, const char *ourhook, const char *peerhook) +{ + struct ngm_connect conn; + + snprintf(conn.path, NG_PATHSIZ, "%s:", node); + strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ); + strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ); + return (NgSendMsg(csock, ".:", + NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn))); +} +int +ng_connect_id(ng_ID_t id, const char *ourhook, const char *peerhook) +{ + struct ngm_connect conn; + + snprintf(conn.path, NG_PATHSIZ, "[%x]:", id); + strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ); + strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ); + return (NgSendMsg(csock, ".:", + NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn))); +} + +int +ng_connect2_id(ng_ID_t id, ng_ID_t peer, const char *ourhook, + const char *peerhook) +{ + struct ngm_connect conn; + char path[NG_PATHSIZ]; + + snprintf(path, NG_PATHSIZ, "[%x]:", id); + + snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer); + strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ); + strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ); + return (NgSendMsg(csock, path, + NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn))); +} + +int +ng_connect2_tee_id(ng_ID_t id, ng_ID_t peer, const char *ourhook, + const char *peerhook) +{ + struct ngm_connect conn; + char path[NG_PATHSIZ]; + ng_ID_t tee; + + if ((tee = ng_mkpeer_id(id, NULL, "tee", ourhook, "left")) == 0) + return (-1); + + snprintf(path, NG_PATHSIZ, "[%x]:", tee); + + snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer); + strlcpy(conn.ourhook, "right", NG_HOOKSIZ); + strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ); + return (NgSendMsg(csock, path, + NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn))); +} + +/* + * Ensure that a node of type 'type' is connected to 'hook' of 'node' + * and return its node id. tee nodes between node and the target node + * are skipped. If the type is wrong, or the hook is a dead-end return 0. + * If type is NULL, it is not checked. + */ +static ng_ID_t +ng_next_node_id_internal(ng_ID_t node, const char *type, const char *hook, + int skip_tee) +{ + struct ng_mesg *resp; + struct hooklist *hooklist; + u_int i; + + if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, + NULL, 0)) == NULL) { + syslog(LOG_ERR, "get hook list: %m"); + exit(1); + } + hooklist = (struct hooklist *)(void *)resp->data; + + for (i = 0; i < hooklist->nodeinfo.hooks; i++) + if (strcmp(hooklist->link[i].ourhook, hook) == 0) + break; + + if (i == hooklist->nodeinfo.hooks) { + free(resp); + return (0); + } + + node = hooklist->link[i].nodeinfo.id; + + if (skip_tee && strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) { + if (strcmp(hooklist->link[i].peerhook, "left") == 0) + node = ng_next_node_id(node, type, "right"); + else if (strcmp(hooklist->link[i].peerhook, "right") == 0) + node = ng_next_node_id(node, type, "left"); + else if (type != NULL && + strcmp(hooklist->link[i].nodeinfo.type, type) != 0) + node = 0; + + } else if (type != NULL && + strcmp(hooklist->link[i].nodeinfo.type, type) != 0) + node = 0; + + free(resp); + + return (node); +} + +/* + * Ensure that a node of type 'type' is connected to 'hook' of 'node' + * and return its node id. tee nodes between node and the target node + * are skipped. If the type is wrong, or the hook is a dead-end return 0. + * If type is NULL, it is not checked. + */ +ng_ID_t +ng_next_node_id(ng_ID_t node, const char *type, const char *hook) +{ + return (ng_next_node_id_internal(node, type, hook, 1)); +} + +ng_ID_t +ng_mkpeer_id(ng_ID_t id, const char *nodename, const char *type, + const char *hook, const char *peerhook) +{ + char path[NG_PATHSIZ]; + struct ngm_mkpeer mkpeer; + struct ngm_name name; + + strlcpy(mkpeer.type, type, NG_TYPESIZ); + strlcpy(mkpeer.ourhook, hook, NG_HOOKSIZ); + strlcpy(mkpeer.peerhook, peerhook, NG_HOOKSIZ); + + sprintf(path, "[%x]:", id); + if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, + &mkpeer, sizeof(mkpeer)) == -1) + return (0); + + if ((id = ng_next_node_id_internal(id, NULL, hook, 0)) == 0) + return (0); + + if (nodename != NULL) { + strcpy(name.name, nodename); + sprintf(path, "[%x]:", id); + if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_NAME, + &name, sizeof(name)) == -1) + return (0); + } + return (id); +} + +/* + * SHutdown node + */ +int +ng_shutdown_id(ng_ID_t id) +{ + char path[NG_PATHSIZ]; + + snprintf(path, NG_PATHSIZ, "[%x]:", id); + return (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, + NGM_SHUTDOWN, NULL, 0)); +} + +/* + * Disconnect one of our hooks + */ +int +ng_rmhook(const char *ourhook) +{ + struct ngm_rmhook rmhook; + + strlcpy(rmhook.ourhook, ourhook, NG_HOOKSIZ); + return (NgSendMsg(csock, ".:", + NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook))); +} + +/* + * Disconnect a hook of a node + */ +int +ng_rmhook_id(ng_ID_t id, const char *hook) +{ + struct ngm_rmhook rmhook; + char path[NG_PATHSIZ]; + + strlcpy(rmhook.ourhook, hook, NG_HOOKSIZ); + snprintf(path, NG_PATHSIZ, "[%x]:", id); + return (NgSendMsg(csock, path, + NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook))); +} + +/* + * Disconnect a hook and shutdown all tee nodes that were connected to that + * hook. + */ +int +ng_rmhook_tee_id(ng_ID_t node, const char *hook) +{ + struct ng_mesg *resp; + struct hooklist *hooklist; + u_int i; + int first = 1; + ng_ID_t next_node; + const char *next_hook; + + again: + /* if we have just shutdown a tee node, which had no other hooks + * connected, the node id may already be wrong here. */ + if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, + NULL, 0)) == NULL) + return (0); + + hooklist = (struct hooklist *)(void *)resp->data; + + for (i = 0; i < hooklist->nodeinfo.hooks; i++) + if (strcmp(hooklist->link[i].ourhook, hook) == 0) + break; + + if (i == hooklist->nodeinfo.hooks) { + free(resp); + return (0); + } + + next_node = 0; + next_hook = NULL; + if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) { + if (strcmp(hooklist->link[i].peerhook, "left") == 0) { + next_node = hooklist->link[i].nodeinfo.id; + next_hook = "right"; + } else if (strcmp(hooklist->link[i].peerhook, "right") == 0) { + next_node = hooklist->link[i].nodeinfo.id; + next_hook = "left"; + } + } + free(resp); + + if (first) { + ng_rmhook_id(node, hook); + first = 0; + } else { + ng_shutdown_id(node); + } + if ((node = next_node) == 0) + return (0); + hook = next_hook; + + goto again; +} + +/* + * Get the peer hook of a hook on a given node. Skip any tee nodes in between + */ +int +ng_peer_hook_id(ng_ID_t node, const char *hook, char *peerhook) +{ + struct ng_mesg *resp; + struct hooklist *hooklist; + u_int i; + int ret; + + if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, + NULL, 0)) == NULL) { + syslog(LOG_ERR, "get hook list: %m"); + exit(1); + } + hooklist = (struct hooklist *)(void *)resp->data; + + for (i = 0; i < hooklist->nodeinfo.hooks; i++) + if (strcmp(hooklist->link[i].ourhook, hook) == 0) + break; + + if (i == hooklist->nodeinfo.hooks) { + free(resp); + return (-1); + } + + node = hooklist->link[i].nodeinfo.id; + + ret = 0; + if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) { + if (strcmp(hooklist->link[i].peerhook, "left") == 0) + ret = ng_peer_hook_id(node, "right", peerhook); + else if (strcmp(hooklist->link[i].peerhook, "right") == 0) + ret = ng_peer_hook_id(node, "left", peerhook); + else + strcpy(peerhook, hooklist->link[i].peerhook); + + } else + strcpy(peerhook, hooklist->link[i].peerhook); + + free(resp); + + return (ret); +} + + +/* + * Now the module is started. Select on the sockets, so that we can get + * unsolicited input. + */ +static void +ng_start(void) +{ + if (snmp_node == 0) { + if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) { + syslog(LOG_ERR, "NgMkSockNode: %m"); + exit(1); + } + snmp_node = ng_node_id(".:"); + } + + if ((csock_fd = fd_select(csock, csock_input, NULL, module)) == NULL) { + syslog(LOG_ERR, "fd_select failed on csock: %m"); + return; + } + if ((dsock_fd = fd_select(dsock, dsock_input, NULL, module)) == NULL) { + syslog(LOG_ERR, "fd_select failed on dsock: %m"); + return; + } + + reg_index = or_register(&oid_begemotNg, + "The MIB for the NetGraph access module for SNMP.", module); +} + +/* + * Called, when the module is to be unloaded after it was successfully loaded + */ +static int +ng_fini(void) +{ + struct ngtype *t; + + while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) { + TAILQ_REMOVE(&ngtype_list, t, link); + free(t); + } + + if (csock_fd != NULL) + fd_deselect(csock_fd); + (void)close(csock); + + if (dsock_fd != NULL) + fd_deselect(dsock_fd); + (void)close(dsock); + + free(snmp_nodename); + + or_unregister(reg_index); + + return (0); +} + +const struct snmp_module config = { + "This module implements access to the netgraph sub-system", + ng_init, + ng_fini, + ng_idle, + NULL, + NULL, + ng_start, + NULL, + netgraph_ctree, + netgraph_CTREE_SIZE, + NULL +}; + +int +op_ng_config(struct snmp_context *ctx, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + int ret; + + switch (op) { + + case SNMP_OP_GETNEXT: + abort(); + + case SNMP_OP_GET: + /* + * Come here for GET, GETNEXT and COMMIT + */ + switch (which) { + + case LEAF_begemotNgControlNodeName: + return (string_get(value, snmp_nodename, -1)); + + case LEAF_begemotNgResBufSiz: + value->v.integer = resbufsiz; + break; + + case LEAF_begemotNgTimeout: + value->v.integer = timeout; + break; + + case LEAF_begemotNgDebugLevel: + value->v.uint32 = debug_level; + break; + + default: + abort(); + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_SET: + switch (which) { + + case LEAF_begemotNgControlNodeName: + /* only at initialisation */ + if (community != COMM_INITIALIZE) + return (SNMP_ERR_NOT_WRITEABLE); + + if (snmp_node != 0) + return (SNMP_ERR_NOT_WRITEABLE); + + if ((ret = string_save(value, ctx, -1, &snmp_nodename)) + != SNMP_ERR_NOERROR) + return (ret); + + if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) { + syslog(LOG_ERR, "NgMkSockNode: %m"); + string_rollback(ctx, &snmp_nodename); + return (SNMP_ERR_GENERR); + } + snmp_node = ng_node_id(".:"); + + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgResBufSiz: + ctx->scratch->int1 = resbufsiz; + if (value->v.integer < 1024 || + value->v.integer > 0x10000) + return (SNMP_ERR_WRONG_VALUE); + resbufsiz = value->v.integer; + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgTimeout: + ctx->scratch->int1 = timeout; + if (value->v.integer < 10 || + value->v.integer > 10000) + return (SNMP_ERR_WRONG_VALUE); + timeout = value->v.integer; + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgDebugLevel: + ctx->scratch->int1 = debug_level; + debug_level = value->v.uint32; + NgSetDebug(debug_level); + return (SNMP_ERR_NOERROR); + } + abort(); + + case SNMP_OP_ROLLBACK: + switch (which) { + + case LEAF_begemotNgControlNodeName: + string_rollback(ctx, &snmp_nodename); + close(csock); + close(dsock); + snmp_node = 0; + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgResBufSiz: + resbufsiz = ctx->scratch->int1; + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgTimeout: + timeout = ctx->scratch->int1; + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgDebugLevel: + debug_level = ctx->scratch->int1; + NgSetDebug(debug_level); + return (SNMP_ERR_NOERROR); + } + abort(); + + case SNMP_OP_COMMIT: + switch (which) { + + case LEAF_begemotNgControlNodeName: + string_commit(ctx); + return (SNMP_ERR_NOERROR); + + case LEAF_begemotNgResBufSiz: + case LEAF_begemotNgTimeout: + case LEAF_begemotNgDebugLevel: + return (SNMP_ERR_NOERROR); + } + abort(); + } + abort(); +} + +int +op_ng_stats(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op op) +{ + switch (op) { + + case SNMP_OP_GETNEXT: + abort(); + + case SNMP_OP_GET: + value->v.uint32 = stats[value->var.subs[sub - 1] - 1]; + return (SNMP_ERR_NOERROR); + + case SNMP_OP_SET: + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + abort(); + } + abort(); +} + +/* + * Netgraph type table + */ +static int +fetch_types(void) +{ + struct ngtype *t; + struct typelist *typelist; + struct ng_mesg *resp; + u_int u, i; + + if (this_tick <= ngtype_tick) + return (0); + + while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) { + TAILQ_REMOVE(&ngtype_list, t, link); + free(t); + } + + if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, + NGM_LISTTYPES, NULL, 0)) == NULL) + return (SNMP_ERR_GENERR); + typelist = (struct typelist *)(void *)resp->data; + + for (u = 0; u < typelist->numtypes; u++) { + if ((t = malloc(sizeof(*t))) == NULL) { + free(resp); + return (SNMP_ERR_GENERR); + } + strcpy(t->name, typelist->typeinfo[u].type_name); + t->index.subs[0] = strlen(t->name); + t->index.len = t->index.subs[0] + 1; + for (i = 0; i < t->index.subs[0]; i++) + t->index.subs[i + 1] = t->name[i]; + + INSERT_OBJECT_OID(t, &ngtype_list); + } + + ngtype_tick = this_tick; + + free(resp); + return (0); +} + +/* + * Try to load the netgraph type with the given name. We assume, that + * type 'type' is implemented in the kernel module 'ng_type'. + */ +static int +ngtype_load(const u_char *name, size_t namelen) +{ + char *mod; + int ret; + + if ((mod = malloc(namelen + 4)) == NULL) + return (-1); + strcpy(mod, "ng_"); + strncpy(mod + 3, name, namelen); + mod[namelen + 3] = '\0'; + + ret = kldload(mod); + free(mod); + return (ret); +} + +/* + * Unload a netgraph type. + */ +static int +ngtype_unload(const u_char *name, size_t namelen) +{ + char *mod; + int id; + + if ((mod = malloc(namelen + 4)) == NULL) + return (-1); + strcpy(mod, "ng_"); + strncpy(mod + 3, name, namelen); + mod[namelen + 3] = '\0'; + + if ((id = kldfind(mod)) == -1) { + free(mod); + return (-1); + } + free(mod); + return (kldunload(id)); +} + +int +op_ng_type(struct snmp_context *ctx, struct snmp_value *value, + u_int sub, u_int iidx, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + struct ngtype *t; + u_char *name; + size_t namelen; + int status = 1; + int ret; + + switch (op) { + + case SNMP_OP_GETNEXT: + if ((ret = fetch_types()) != 0) + return (ret); + if ((t = NEXT_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + index_append(&value->var, sub, &t->index); + break; + + case SNMP_OP_GET: + if ((ret = fetch_types()) != 0) + return (ret); + if ((t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_SET: + if (index_decode(&value->var, sub, iidx, &name, &namelen)) + return (SNMP_ERR_NO_CREATION); + if (namelen == 0 || namelen >= NG_TYPESIZ) { + free(name); + return (SNMP_ERR_NO_CREATION); + } + if ((ret = fetch_types()) != 0) { + free(name); + return (ret); + } + t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub); + + if (which != LEAF_begemotNgTypeStatus) { + free(name); + if (t != NULL) + return (SNMP_ERR_NOT_WRITEABLE); + return (SNMP_ERR_NO_CREATION); + } + if (!TRUTH_OK(value->v.integer)) { + free(name); + return (SNMP_ERR_WRONG_VALUE); + } + ctx->scratch->int1 = TRUTH_GET(value->v.integer); + ctx->scratch->int1 |= (t != NULL) << 1; + ctx->scratch->ptr2 = name; + ctx->scratch->int2 = namelen; + + if (t == NULL) { + /* type not loaded */ + if (ctx->scratch->int1 & 1) { + /* request to load */ + if (ngtype_load(name, namelen) == -1) { + free(name); + if (errno == ENOENT) + return (SNMP_ERR_INCONS_NAME); + else + return (SNMP_ERR_GENERR); + } + } + } else { + /* is type loaded */ + if (!(ctx->scratch->int1 & 1)) { + /* request to unload */ + if (ngtype_unload(name, namelen) == -1) { + free(name); + return (SNMP_ERR_GENERR); + } + } + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + ret = SNMP_ERR_NOERROR; + if (!(ctx->scratch->int1 & 2)) { + /* did not exist */ + if (ctx->scratch->int1 & 1) { + /* request to load - unload */ + if (ngtype_unload(ctx->scratch->ptr2, + ctx->scratch->int2) == -1) + ret = SNMP_ERR_UNDO_FAILED; + } + } else { + /* did exist */ + if (!(ctx->scratch->int1 & 1)) { + /* request to unload - reload */ + if (ngtype_load(ctx->scratch->ptr2, + ctx->scratch->int2) == -1) + ret = SNMP_ERR_UNDO_FAILED; + } + } + free(ctx->scratch->ptr2); + return (ret); + + case SNMP_OP_COMMIT: + free(ctx->scratch->ptr2); + return (SNMP_ERR_NOERROR); + + default: + abort(); + } + + /* + * Come here for GET and COMMIT + */ + switch (which) { + + case LEAF_begemotNgTypeStatus: + value->v.integer = status; + break; + + default: + abort(); + } + return (SNMP_ERR_NOERROR); +} + +/* + * Implement the node table + */ +static int +find_node(const struct asn_oid *oid, u_int sub, struct nodeinfo *info) +{ + ng_ID_t id = oid->subs[sub]; + struct ng_mesg *resp; + + if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO, + NULL, 0)) == NULL) + return (-1); + + *info = *(struct nodeinfo *)(void *)resp->data; + free(resp); + return (0); +} + +static int +ncmp(const void *p1, const void *p2) +{ + const struct nodeinfo *i1 = p1; + const struct nodeinfo *i2 = p2; + + if (i1->id < i2->id) + return (-1); + if (i1->id > i2->id) + return (+1); + return (0); +} + +static int +find_node_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *info) +{ + u_int idxlen = oid->len - sub; + struct ng_mesg *resp; + struct namelist *list; + ng_ID_t id; + u_int i; + + if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES, + NULL, 0)) == NULL) + return (-1); + list = (struct namelist *)(void *)resp->data; + + qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp); + + if (idxlen == 0) { + if (list->numnames == 0) { + free(resp); + return (-1); + } + *info = list->nodeinfo[0]; + free(resp); + return (0); + } + id = oid->subs[sub]; + + for (i = 0; i < list->numnames; i++) + if (list->nodeinfo[i].id > id) { + *info = list->nodeinfo[i]; + free(resp); + return (0); + } + + free(resp); + return (-1); +} + +int +op_ng_node(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + u_int idxlen = value->var.len - sub; + struct nodeinfo nodeinfo; + + switch (op) { + + case SNMP_OP_GETNEXT: + if (find_node_next(&value->var, sub, &nodeinfo) == -1) + return (SNMP_ERR_NOSUCHNAME); + value->var.len = sub + 1; + value->var.subs[sub] = nodeinfo.id; + break; + + case SNMP_OP_GET: + if (idxlen != 1) + return (SNMP_ERR_NOSUCHNAME); + if (find_node(&value->var, sub, &nodeinfo) == -1) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_SET: + if (idxlen != 1) + return (SNMP_ERR_NO_CREATION); + if (find_node(&value->var, sub, &nodeinfo) == -1) + return (SNMP_ERR_NO_CREATION); + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + default: + abort(); + } + + /* + * Come here for GET and COMMIT + */ + switch (which) { + + case LEAF_begemotNgNodeStatus: + value->v.integer = 1; + break; + case LEAF_begemotNgNodeName: + return (string_get(value, nodeinfo.name, -1)); + case LEAF_begemotNgNodeType: + return (string_get(value, nodeinfo.type, -1)); + case LEAF_begemotNgNodeHooks: + value->v.uint32 = nodeinfo.hooks; + break; + + default: + abort(); + } + return (SNMP_ERR_NOERROR); +} + +/* + * Implement the hook table + */ +static int +find_hook(int32_t id, const u_char *hook, size_t hooklen, struct linkinfo *info) +{ + struct ng_mesg *resp; + struct hooklist *list; + u_int i; + + if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, + NGM_LISTHOOKS, NULL, 0)) == NULL) + return (-1); + + list = (struct hooklist *)(void *)resp->data; + + for (i = 0; i < list->nodeinfo.hooks; i++) { + if (strlen(list->link[i].ourhook) == hooklen && + strncmp(list->link[i].ourhook, hook, hooklen) == 0) { + *info = list->link[i]; + free(resp); + return (0); + } + } + free(resp); + return (-1); +} + +static int +hook_cmp(const void *p1, const void *p2) +{ + const struct linkinfo *i1 = p1; + const struct linkinfo *i2 = p2; + + if (strlen(i1->ourhook) < strlen(i2->ourhook)) + return (-1); + if (strlen(i1->ourhook) > strlen(i2->ourhook)) + return (+1); + return (strcmp(i1->ourhook, i2->ourhook)); +} + +static int +find_hook_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *nodeinfo, + struct linkinfo *linkinfo) +{ + u_int idxlen = oid->len - sub; + struct namelist *list; + struct ng_mesg *resp; + struct hooklist *hooks; + struct ng_mesg *resp1; + u_int node_index; + struct asn_oid idx; + u_int i, j; + + /* + * Get and sort Node list + */ + if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES, + NULL, 0)) == NULL) + return (-1); + list = (struct namelist *)(void *)resp->data; + + qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp); + + /* + * If we have no index, take the first node and return the + * first hook. + */ + if (idxlen == 0) { + node_index = 0; + goto return_first_hook; + } + + /* + * Locate node + */ + for (node_index = 0; node_index < list->numnames; node_index++) + if (list->nodeinfo[node_index].id >= oid->subs[sub]) + break; + + /* + * If we have only the node part of the index take, or + * there is no node with that Id, take the first hook of that node. + */ + if (idxlen == 1 || node_index >= list->numnames || + list->nodeinfo[node_index].id > oid->subs[sub]) + goto return_first_hook; + + /* + * We had an exact match on the node id and have (at last part) + * of the hook name index. Loop through the hooks of the node + * and find the next one. + */ + if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id, + NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) { + free(resp); + return (-1); + } + hooks = (struct hooklist *)(void *)resp1->data; + if (hooks->nodeinfo.hooks > 0) { + qsort(hooks->link, hooks->nodeinfo.hooks, + sizeof(hooks->link[0]), hook_cmp); + for (i = 0; i < hooks->nodeinfo.hooks; i++) { + idx.len = strlen(hooks->link[i].ourhook) + 1; + idx.subs[0] = idx.len - 1; + for (j = 0; j < idx.len; j++) + idx.subs[j + 1] = hooks->link[i].ourhook[j]; + if (index_compare(oid, sub + 1, &idx) < 0) + break; + } + if (i < hooks->nodeinfo.hooks) { + *nodeinfo = hooks->nodeinfo; + *linkinfo = hooks->link[i]; + + free(resp); + free(resp1); + return (0); + } + } + + /* no hook found larger than the index on the index node - take + * first hook of next node */ + free(resp1); + node_index++; + + return_first_hook: + while (node_index < list->numnames) { + if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id, + NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) + break; + hooks = (struct hooklist *)(void *)resp1->data; + if (hooks->nodeinfo.hooks > 0) { + qsort(hooks->link, hooks->nodeinfo.hooks, + sizeof(hooks->link[0]), hook_cmp); + + *nodeinfo = hooks->nodeinfo; + *linkinfo = hooks->link[0]; + + free(resp); + free(resp1); + return (0); + } + + /* if we don't have hooks, try next node */ + free(resp1); + node_index++; + } + + free(resp); + return (-1); +} + +int +op_ng_hook(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + struct linkinfo linkinfo; + struct nodeinfo nodeinfo; + u_int32_t lid; + u_char *hook; + size_t hooklen; + u_int i; + + switch (op) { + + case SNMP_OP_GETNEXT: + if (find_hook_next(&value->var, sub, &nodeinfo, &linkinfo) == -1) + return (SNMP_ERR_NOSUCHNAME); + + value->var.len = sub + 1 + 1 + strlen(linkinfo.ourhook); + value->var.subs[sub] = nodeinfo.id; + value->var.subs[sub + 1] = strlen(linkinfo.ourhook); + for (i = 0; i < strlen(linkinfo.ourhook); i++) + value->var.subs[sub + i + 2] = + linkinfo.ourhook[i]; + break; + + case SNMP_OP_GET: + if (index_decode(&value->var, sub, iidx, &lid, + &hook, &hooklen)) + return (SNMP_ERR_NOSUCHNAME); + if (find_hook(lid, hook, hooklen, &linkinfo) == -1) { + free(hook); + return (SNMP_ERR_NOSUCHNAME); + } + free(hook); + break; + + case SNMP_OP_SET: + if (index_decode(&value->var, sub, iidx, &lid, + &hook, &hooklen)) + return (SNMP_ERR_NO_CREATION); + if (find_hook(lid, hook, hooklen, &linkinfo) == -1) { + free(hook); + return (SNMP_ERR_NO_CREATION); + } + free(hook); + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + default: + abort(); + + } + + switch (which) { + + case LEAF_begemotNgHookStatus: + value->v.integer = 1; + break; + case LEAF_begemotNgHookPeerNodeId: + value->v.uint32 = linkinfo.nodeinfo.id; + break; + case LEAF_begemotNgHookPeerHook: + return (string_get(value, linkinfo.peerhook, -1)); + case LEAF_begemotNgHookPeerType: + return (string_get(value, linkinfo.nodeinfo.type, -1)); + default: + abort(); + } + return (SNMP_ERR_NOERROR); +} |