d6d9dd67f4
local tick variable which is used to reduce the number of updates from kernel structures and to ensure intra-PDU consistency also 64 bit.
1691 lines
37 KiB
C
1691 lines
37 KiB
C
/*
|
|
* 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 "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);
|
|
}
|