Commit Diff


commit - 75efd81ba8d7f49e388fad87971503e05e91ef9c
commit + 28bd28ce02f4f0d97454036901f09cbedc246eeb
blob - 3bb6fb8c73870b4e441a078429963f09c34e44a1
blob + 56e6e4720899aa40ac70e6cd370e774565a6ba5a
--- agentx.c
+++ agentx.c
@@ -1,4 +1,4 @@
-/*      $OpenBSD: agentx.c,v 1.7 2014/11/14 16:18:43 reyk Exp $    */
+/*      $OpenBSD: agentx.c,v 1.8 2014/11/19 10:24:40 blambert Exp $    */
 /*
  * Copyright (c) 2013,2014 Bret Stephen Lambert <blambert@openbsd.org>
  *
@@ -42,6 +42,7 @@ int	snmp_agentx_do_read_raw(struct agentx_pdu *, void 
 void	snmp_agentx_update_ids(struct agentx_handle *, struct agentx_pdu *);
 struct agentx_pdu *
 	agentx_find_inflight(struct agentx_handle *, uint32_t, uint32_t);
+int	snmp_agentx_do_read_oid(struct agentx_pdu *, struct snmp_oid *, int *);
 
 #ifdef DEBUG
 static void	snmp_agentx_dump_hdr(struct agentx_hdr *);
@@ -254,17 +255,6 @@ snmp_agentx_pdu_free(struct agentx_pdu *pdu)
 	free(pdu);
 }
 
-/*
- * Set the callback function to be called when a complete PDU is received.
- */
-void
-snmp_agentx_set_callback(struct agentx_handle *h,
-    void (*cb)(struct agentx_handle *, struct agentx_pdu *, void *), void *arg)
-{
-	h->cb = cb;
-	h->cb_arg = arg;
-}
-
 int
 snmp_agentx_buffer_consume(struct agentx_pdu *b, u_int len)
 {
@@ -365,6 +355,11 @@ snmp_agentx_recv(struct agentx_handle *h)
 			return (NULL);
 		}
 
+		if (pdu->hdr->version != AGENTX_VERSION) {
+			h->error = AGENTX_ERR_PARSE_ERROR;
+			return (NULL);
+		}
+
 		if (snmp_agentx_buffercheck(pdu, pdu->hdr->length) == -1)
 			return (NULL);
 	}
@@ -385,6 +380,11 @@ snmp_agentx_recv(struct agentx_handle *h)
 #ifdef DEBUG
 	snmp_agentx_dump_hdr(pdu->hdr);
 #endif
+	if (pdu->hdr->version != AGENTX_VERSION) {
+		h->error = AGENTX_ERR_PARSE_ERROR;
+		goto fail;
+	}
+
 	/* If this is an open on a new connection, fix it up */
 	if (pdu->hdr->type == AGENTX_OPEN && h->sessionid == 0) {
 		pdu->hdr->sessionid = 0;		/* ignored, per RFC */
@@ -392,11 +392,6 @@ snmp_agentx_recv(struct agentx_handle *h)
 		h->packetid = pdu->hdr->packetid;
 	}
 
-	if (pdu->hdr->version != AGENTX_VERSION) {
-		h->error = AGENTX_ERR_PARSE_ERROR;
-		goto fail;
-	}
-
 	if (pdu->hdr->type == AGENTX_RESPONSE) {
 
 		match = agentx_find_inflight(h, pdu->hdr->transactid,
@@ -410,9 +405,6 @@ snmp_agentx_recv(struct agentx_handle *h)
 		pdu->request = match;
 		h->r = NULL;
 
-		if (h->cb)
-			h->cb(h, pdu, h->cb_arg);
-
 	} else {
 		if (pdu->hdr->sessionid != h->sessionid) {
 			h->error = AGENTX_ERR_NOT_OPEN;
@@ -638,6 +630,61 @@ snmp_agentx_register_pdu(struct snmp_oid *oid, int tim
 	return (pdu);
 }
 
+struct agentx_pdu *
+snmp_agentx_unregister_pdu(struct snmp_oid *oid, int range_index,
+    int range_bound)
+{
+	struct agentx_unregister_hdr	 uhdr;
+	struct agentx_pdu		*pdu;
+
+	if ((pdu = snmp_agentx_pdu_alloc()) == NULL)
+		return (NULL);
+
+	pdu->hdr->version = AGENTX_VERSION;
+	pdu->hdr->type = AGENTX_UNREGISTER;
+
+	uhdr.reserved1 = 0;
+	uhdr.priority = AGENTX_REGISTER_PRIO_DEFAULT;
+	uhdr.subrange = range_index;
+	uhdr.reserved2 = 0;
+
+	if (snmp_agentx_raw(pdu, &uhdr, sizeof(uhdr)) == -1 ||
+	    snmp_agentx_oid(pdu, oid) == -1 ||
+	    snmp_agentx_oid(pdu, oid) == -1 ||
+	    (range_index && snmp_agentx_int(pdu, &range_bound) == -1)) {
+		snmp_agentx_pdu_free(pdu);
+		return (NULL);
+	}
+
+	return (pdu);
+}
+
+struct agentx_pdu *
+snmp_agentx_get_pdu(struct snmp_oid oid[], int noid)
+{
+	struct snmp_oid		 nulloid;
+	struct agentx_pdu	*pdu;
+	int			 i;
+
+	if ((pdu = snmp_agentx_pdu_alloc()) == NULL)
+		return (NULL);
+
+	pdu->hdr->version = AGENTX_VERSION;
+	pdu->hdr->type = AGENTX_GET;
+
+	bzero(&nulloid, sizeof(nulloid));
+
+	for (i = 0; i < noid; i++) {
+		if (snmp_agentx_oid(pdu, &oid[i]) == -1 ||
+		    snmp_agentx_oid(pdu, &nulloid) == -1) {
+			snmp_agentx_pdu_free(pdu);
+			return (NULL);
+		}
+	}
+
+	return (pdu);
+}
+
 /*
  * AgentX PDU write routines.
  */
@@ -831,6 +878,15 @@ snmp_agentx_read_int64(struct agentx_pdu *pdu, uint64_
 int
 snmp_agentx_read_oid(struct agentx_pdu *pdu, struct snmp_oid *oid)
 {
+	int dummy;
+
+	return (snmp_agentx_do_read_oid(pdu, oid, &dummy));
+}
+
+int
+snmp_agentx_do_read_oid(struct agentx_pdu *pdu, struct snmp_oid *oid,
+    int *include)
+{
 	struct agentx_oid_hdr ohdr;
 	int i = 0;
 
@@ -853,10 +909,21 @@ snmp_agentx_read_oid(struct agentx_pdu *pdu, struct sn
 			return (-1);
 
 	oid->o_n = i;
+	*include = ohdr.include;
 
 	return (0);
 }
 
+int
+snmp_agentx_read_searchrange(struct agentx_pdu *pdu, struct agentx_search_range *sr)
+{
+	if (snmp_agentx_do_read_oid(pdu, &sr->start, &sr->include) == -1 ||
+	    snmp_agentx_read_oid(pdu, &sr->end) == -1)
+		return (-1);
+
+	return (0);
+}
+
 char *
 snmp_agentx_read_octetstr(struct agentx_pdu *pdu, int *len)
 {
@@ -915,6 +982,63 @@ snmp_agentx_update_ids(struct agentx_handle *h, struct
 }
 
 char *
+snmp_oid2string(struct snmp_oid *o, char *buf, size_t len)
+{
+	char		 str[256];
+	size_t		 i;
+
+	bzero(buf, len);
+
+	for (i = 0; i < o->o_n; i++) {
+		snprintf(str, sizeof(str), "%u", o->o_id[i]);
+		strlcat(buf, str, len);
+		if (i < (o->o_n - 1))
+			strlcat(buf, ".", len);
+	}
+
+	return (buf);
+}
+
+int
+snmp_oid_cmp(struct snmp_oid *a, struct snmp_oid *b)
+{
+	size_t		i;
+
+	for (i = 0; i < SNMP_MAX_OID_LEN; i++) {
+		if (a->o_id[i] != 0) {
+			if (a->o_id[i] == b->o_id[i])
+				continue;
+			else if (a->o_id[i] < b->o_id[i]) {
+				/* b is a successor of a */
+				return (1);
+			} else {
+				/* b is a predecessor of a */
+				return (-1);
+			}
+		} else if (b->o_id[i] != 0) {
+			/* b is larger, but a child of a */
+			return (2);
+		} else
+			break;
+	}
+
+	/* b and a are identical */
+	return (0);
+}
+
+void
+snmp_oid_increment(struct snmp_oid *o)
+{
+	u_int		i;
+
+	for (i = o->o_n; i > 0; i--) {
+		o->o_id[i - 1]++;
+		if (o->o_id[i - 1] != 0)
+			break;
+	}
+}
+
+char *
 snmp_agentx_type2name(int type)
 {
 	static char *names[] = {
blob - 9c10e6e54ed1871c3df862513d837887b7d4bce1
blob + 0cdec86bab6fab392a6e6d31fb56f7a8f6b8681d
--- config.c
+++ config.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: config.c,v 1.18 2014/07/11 16:59:38 reyk Exp $	*/
+/*	$OpenBSD: config.c,v 1.19 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2011 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -407,6 +407,7 @@ config_gethost(struct relayd *env, struct imsg *imsg)
 
 	SLIST_INIT(&host->children);
 	TAILQ_INSERT_TAIL(&tb->hosts, host, entry);
+	TAILQ_INSERT_TAIL(&env->sc_hosts, host, globalentry);
 
 	DPRINTF("%s: %s %d received host %s for table %s", __func__,
 	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
blob - 83d7d94b3738340bdbda298784a741a3cc5aab4f
blob + a541a631db59c21b519c1814f8a9e066a04d1504
--- hce.c
+++ hce.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: hce.c,v 1.65 2014/10/25 03:23:49 lteo Exp $	*/
+/*	$OpenBSD: hce.c,v 1.66 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -48,6 +48,7 @@ void	 hce_disable_events(void);
 
 int	 hce_dispatch_parent(int, struct privsep_proc *, struct imsg *);
 int	 hce_dispatch_pfe(int, struct privsep_proc *, struct imsg *);
+int	 hce_dispatch_relay(int, struct privsep_proc *, struct imsg *);
 
 static struct relayd *env = NULL;
 int			 running = 0;
@@ -55,6 +56,7 @@ int			 running = 0;
 static struct privsep_proc procs[] = {
 	{ "parent",	PROC_PARENT,	hce_dispatch_parent },
 	{ "pfe",	PROC_PFE,	hce_dispatch_pfe },
+	{ "relay",	PROC_RELAY,	hce_dispatch_relay },
 };
 
 pid_t
@@ -78,8 +80,6 @@ hce_init(struct privsep *ps, struct privsep_proc *p, v
 
 	/* Allow maximum available sockets for TCP checks */
 	socket_rlimit(-1);
-
-	snmp_init(env, PROC_PARENT);
 }
 
 void
@@ -274,9 +274,6 @@ hce_notify_done(struct host *host, enum host_error he)
 		    print_availability(host->check_cnt, host->up_cnt));
 	}
 
-	if (host->last_up != host->up)
-		snmp_hosttrap(env, table, host);
-
 	host->last_up = host->up;
 
 	if (SLIST_EMPTY(&host->children))
@@ -361,9 +358,6 @@ hce_dispatch_parent(int fd, struct privsep_proc *p, st
 	case IMSG_CFG_HOST:
 		config_gethost(env, imsg);
 		break;
-	case IMSG_SNMPSOCK:
-		snmp_getsock(env, imsg);
-		break;
 	case IMSG_CFG_DONE:
 		config_getcfg(env, imsg);
 		break;
@@ -379,3 +373,14 @@ hce_dispatch_parent(int fd, struct privsep_proc *p, st
 
 	return (0);
 }
+
+int
+hce_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+	switch (imsg->hdr.type) {
+	default:
+		break;
+	}
+
+	return (-1);
+}
blob - fcb91f9d1d6a2a8b71452b0bd7f961c0b479bcde
blob + d3e8641a970ea9aa5c17e09c1abc5427677b2c6e
--- parse.y
+++ parse.y
@@ -1,4 +1,4 @@
-/*	$OpenBSD: parse.y,v 1.193 2014/11/07 13:48:06 jsing Exp $	*/
+/*	$OpenBSD: parse.y,v 1.194 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -3018,6 +3018,7 @@ table_inherit(struct table *tb)
 		h->tablename = tb->conf.name;
 		SLIST_INIT(&h->children);
 		TAILQ_INSERT_TAIL(&tb->hosts, h, entry);
+		TAILQ_INSERT_TAIL(&conf->sc_hosts, h, globalentry);
 	}
 
 	conf->sc_tablecount++;
blob - 03865fc23b0ee6e5edfeddde4e179e3ef8d4aacd
blob + 150642a9a78e79a91b05f401e04e54a118ad0108
--- pfe.c
+++ pfe.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: pfe.c,v 1.75 2014/07/09 16:42:05 reyk Exp $	*/
+/*	$OpenBSD: pfe.c,v 1.76 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -68,6 +68,8 @@ pfe_init(struct privsep *ps, struct privsep_proc *p, v
 	if (config_init(ps->ps_env) == -1)
 		fatal("failed to initialize configuration");
 
+	snmp_init(env, PROC_PARENT);
+
 	p->p_shutdown = pfe_shutdown;
 }
 
@@ -141,6 +143,8 @@ pfe_dispatch_hce(int fd, struct privsep_proc *p, struc
 		log_debug("%s: state %d for host %u %s", __func__,
 		    st.up, host->conf.id, host->conf.name);
 
+		snmp_hosttrap(env, table, host);
+
 		/*
 		 * Do not change the table state when the host
 		 * state switches between UNKNOWN and DOWN.
@@ -212,6 +216,9 @@ pfe_dispatch_parent(int fd, struct privsep_proc *p, st
 	case IMSG_CTL_RESET:
 		config_getreset(env, imsg);
 		break;
+	case IMSG_SNMPSOCK:
+		snmp_getsock(env, imsg);
+		break;
 	default:
 		return (-1);
 	}
@@ -226,8 +233,9 @@ pfe_dispatch_relay(int fd, struct privsep_proc *p, str
 	struct ctl_stats	 crs;
 	struct relay		*rlay;
 	struct ctl_conn		*c;
-	struct rsession		 con;
+	struct rsession		 con, *s, *t;
 	int			 cid;
+	objid_t			 sid;
 
 	switch (imsg->hdr.type) {
 	case IMSG_NATLOOK:
@@ -282,6 +290,34 @@ pfe_dispatch_relay(int fd, struct privsep_proc *p, str
 			    0, 0, -1, NULL, 0);
 		}
 		break;
+	case IMSG_SESS_PUBLISH:
+		IMSG_SIZE_CHECK(imsg, s);
+		if ((s = calloc(1, sizeof(*s))) == NULL)
+			return (0);		/* XXX */
+		memcpy(s, imsg->data, sizeof(*s));
+		TAILQ_FOREACH(t, &env->sc_sessions, se_entry) {
+			if (t->se_id == s->se_id)	/* duplicate registration */
+				return (0);
+			if (t->se_id > s->se_id)
+				break;
+		}
+		if (t)
+			TAILQ_INSERT_BEFORE(t, s, se_entry);
+		else
+			TAILQ_INSERT_TAIL(&env->sc_sessions, s, se_entry);
+		break;
+	case IMSG_SESS_UNPUBLISH:
+		IMSG_SIZE_CHECK(imsg, &sid);
+		memcpy(&sid, imsg->data, sizeof(sid));
+		TAILQ_FOREACH(s, &env->sc_sessions, se_entry)
+			if (s->se_id == sid)
+				break;
+		if (s) {
+			TAILQ_REMOVE(&env->sc_sessions, s, se_entry);
+			free(s);
+		} else
+			log_warnx("removal of unpublished session %i", sid);
+		break;
 	default:
 		return (-1);
 	}
blob - ee6d99aba1a5634ee5874d4dace0882ee03ee420
blob + b405d717b020d4a845c0e9d6384737ebbfa5a8a4
--- relay.c
+++ relay.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: relay.c,v 1.180 2014/11/07 13:48:06 jsing Exp $	*/
+/*	$OpenBSD: relay.c,v 1.181 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -54,6 +54,8 @@ int		 relay_dispatch_pfe(int, struct privsep_proc *,
 		    struct imsg *);
 int		 relay_dispatch_ca(int, struct privsep_proc *,
 		    struct imsg *);
+int		 relay_dispatch_hce(int, struct privsep_proc *,
+		    struct imsg *);
 void		 relay_shutdown(void);
 
 void		 relay_protodebug(struct relay *);
@@ -99,7 +101,8 @@ int				 proc_id;
 static struct privsep_proc procs[] = {
 	{ "parent",	PROC_PARENT,	relay_dispatch_parent },
 	{ "pfe",	PROC_PFE,	relay_dispatch_pfe },
-	{ "ca",		PROC_CA,	relay_dispatch_ca }
+	{ "ca",		PROC_CA,	relay_dispatch_ca },
+	{ "hce",	PROC_HCE,	relay_dispatch_hce },
 };
 
 pid_t
@@ -337,6 +340,20 @@ relay_init(struct privsep *ps, struct privsep_proc *p,
 }
 
 void
+relay_session_publish(struct rsession *s)
+{
+	proc_compose_imsg(env->sc_ps, PROC_PFE, -1, IMSG_SESS_PUBLISH, -1,
+	    s, sizeof(*s));
+}
+
+void
+relay_session_unpublish(struct rsession *s)
+{
+	proc_compose_imsg(env->sc_ps, PROC_PFE, -1, IMSG_SESS_UNPUBLISH, -1,
+	    &s->se_id, sizeof(s->se_id));
+}
+
+void
 relay_statistics(int fd, short events, void *arg)
 {
 	struct relay		*rlay;
@@ -1076,6 +1093,7 @@ relay_accept(int fd, short event, void *arg)
 
 	relay_sessions++;
 	SPLAY_INSERT(session_tree, &rlay->rl_sessions, con);
+	relay_session_publish(con);
 
 	/* Increment the per-relay session counter */
 	rlay->rl_stats[proc_id].last++;
@@ -1575,6 +1593,7 @@ relay_close(struct rsession *con, const char *msg)
 	struct protocol	*proto = rlay->rl_proto;
 
 	SPLAY_REMOVE(session_tree, &rlay->rl_sessions, con);
+	relay_session_unpublish(con);
 
 	event_del(&con->se_ev);
 	if (con->se_in.bev != NULL)
@@ -1953,6 +1972,17 @@ relay_ssl_callback_dh(SSL *ssl, int export, int keylen
 	return (dh);
 }
 
+int
+relay_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+	switch (imsg->hdr.type) {
+	default:
+		break;
+	}
+
+	return (-1);
+}
+
 SSL_CTX *
 relay_ssl_ctx_create(struct relay *rlay)
 {
blob - fab7b966495fb408d4cb9635a18c1986ad24cb7b
blob + 2f1269660dbf157c3d449f407b70a678f61983d0
--- relay_udp.c
+++ relay_udp.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: relay_udp.c,v 1.33 2014/10/25 03:23:49 lteo Exp $	*/
+/*	$OpenBSD: relay_udp.c,v 1.34 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2007 - 2013 Reyk Floeter <reyk@openbsd.org>
@@ -269,6 +269,7 @@ relay_udp_server(int fd, short sig, void *arg)
 
 	relay_sessions++;
 	SPLAY_INSERT(session_tree, &rlay->rl_sessions, con);
+	relay_session_publish(con);
 
 	/* Increment the per-relay session counter */
 	rlay->rl_stats[proc_id].last++;
blob - 8fd87a9931972793ab3de5f3b1030dd70fbfb787
blob + b0ffa7bcf5ca49a2fbdbae1148c1f3cb284a1183
--- relayd.c
+++ relayd.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: relayd.c,v 1.131 2014/08/18 12:59:00 reyk Exp $	*/
+/*	$OpenBSD: relayd.c,v 1.132 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -200,6 +200,8 @@ main(int argc, char *argv[])
 	TAILQ_INIT(&ps->ps_rcsocks);
 	env->sc_conffile = conffile;
 	env->sc_opts = opts;
+	TAILQ_INIT(&env->sc_hosts);
+	TAILQ_INIT(&env->sc_sessions);
 
 	if (parse_config(env->sc_conffile, env) == -1)
 		exit(1);
@@ -453,6 +455,9 @@ parent_dispatch_pfe(int fd, struct privsep_proc *p, st
 	case IMSG_CFG_DONE:
 		parent_configure_done(env);
 		break;
+	case IMSG_SNMPSOCK:
+		(void)snmp_setsock(env, p->p_id);
+		break;
 	default:
 		return (-1);
 	}
@@ -475,9 +480,6 @@ parent_dispatch_hce(int fd, struct privsep_proc *p, st
 		proc_compose_imsg(ps, PROC_HCE, -1, IMSG_SCRIPT,
 		    -1, &scr, sizeof(scr));
 		break;
-	case IMSG_SNMPSOCK:
-		(void)snmp_setsock(env, p->p_id);
-		break;
 	case IMSG_CFG_DONE:
 		parent_configure_done(env);
 		break;
blob - 6ed2c4bcdecb48a6c686015eb8806a672a2b1915
blob + 39eecf483c87f008dfda3b6db7074984cfd47ae5
--- relayd.h
+++ relayd.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: relayd.h,v 1.196 2014/11/07 13:48:06 jsing Exp $	*/
+/*	$OpenBSD: relayd.h,v 1.197 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -384,6 +384,7 @@ struct host_config {
 
 struct host {
 	TAILQ_ENTRY(host)	 entry;
+	TAILQ_ENTRY(host)	 globalentry;
 	SLIST_ENTRY(host)	 child;
 	SLIST_HEAD(,host)	 children;
 	struct host_config	 conf;
@@ -537,8 +538,10 @@ struct rsession {
 	int				 se_cid;
 	pid_t				 se_pid;
 	SPLAY_ENTRY(rsession)		 se_nodes;
+	TAILQ_ENTRY(rsession)		 se_entry;
 };
 SPLAY_HEAD(session_tree, rsession);
+TAILQ_HEAD(sessionlist, rsession);
 
 enum prototype {
 	RELAY_PROTO_TCP		= 0,
@@ -935,7 +938,9 @@ enum imsg_type {
 	IMSG_CFG_RELAY_TABLE,
 	IMSG_CFG_DONE,
 	IMSG_CA_PRIVENC,
-	IMSG_CA_PRIVDEC
+	IMSG_CA_PRIVDEC,
+	IMSG_SESS_PUBLISH,	/* from relay to hce */
+	IMSG_SESS_UNPUBLISH
 };
 
 enum privsep_procid {
@@ -1017,12 +1022,14 @@ struct relayd {
 	struct protocol		 sc_proto_default;
 	struct event		 sc_ev;
 	struct tablelist	*sc_tables;
+	struct hostlist		 sc_hosts;
 	struct rdrlist		*sc_rdrs;
 	struct protolist	*sc_protos;
 	struct relaylist	*sc_relays;
 	struct routerlist	*sc_rts;
 	struct netroutelist	*sc_routes;
 	struct ca_pkeylist	*sc_pkeys;
+	struct sessionlist	 sc_sessions;
 	u_int16_t		 sc_prefork_relay;
 	char			 sc_demote_group[IFNAMSIZ];
 	u_int16_t		 sc_id;
@@ -1152,6 +1159,10 @@ int	 relay_test(struct protocol *, struct ctl_relay_ev
 void	 relay_calc_skip_steps(struct relay_rules *);
 void	 relay_match(struct kvlist *, struct kv *, struct kv *,
 	    struct kvtree *);
+void	 relay_session_insert(struct rsession *);
+void	 relay_session_remove(struct rsession *);
+void	 relay_session_publish(struct rsession *);
+void	 relay_session_unpublish(struct rsession *);
 
 SPLAY_PROTOTYPE(session_tree, rsession, se_nodes, relay_session_cmp);
 
blob - 1dd663b1e74014497c83cab662b26d527859979c
blob + 117ecc182eb4144efddae71916d137d39e53806a
--- snmp.c
+++ snmp.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: snmp.c,v 1.17 2014/06/25 11:05:15 reyk Exp $	*/
+/*	$OpenBSD: snmp.c,v 1.18 2014/11/19 10:24:40 blambert Exp $	*/
 
 /*
  * Copyright (c) 2008 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -39,6 +39,16 @@
 #include "relayd.h"
 #include "snmp.h"
 
+#define	HOST_MAX_SUBIDX		10
+#define	TABLE_MAX_SUBIDX	3
+#define	ROUTER_MAX_SUBIDX	6
+#define	NETROUTE_MAX_SUBIDX	5
+#define	RELAY_MAX_SUBIDX	10
+#define	SESSION_MAX_SUBIDX	12
+#define	RDR_MAX_SUBIDX		10
+
+#define	OIDIDX_relaydInfo	9
+
 #define RELAYD_MIB	"1.3.6.1.4.1.30155.3"
 #define SNMP_ELEMENT(x...)	do {				\
 	if (snmp_element(RELAYD_MIB x) == -1)			\
@@ -50,6 +60,11 @@ static struct snmp_oid	hosttrapoid = {
 	10
 };
 
+static struct snmp_oid	relaydinfooid = {
+	{ 1, 3, 6, 1, 4, 1, 30155, 3, 2 },
+	9
+};
+
 static struct agentx_handle	*snmp_agentx = NULL;
 enum privsep_procid		 snmp_procid;
 
@@ -57,10 +72,48 @@ void	 snmp_sock(int, short, void *);
 int	 snmp_element(const char *, enum snmp_type, void *, int64_t,
 	    struct agentx_pdu *);
 int	 snmp_string2oid(const char *, struct snmp_oid *);
+char	*snmp_oid2string(struct snmp_oid *, char *, size_t);
 void	 snmp_event_add(struct relayd *, int);
 void	 snmp_agentx_process(struct agentx_handle *, struct agentx_pdu *,
 	    void *);
+int	 snmp_register(struct relayd *);
+int	 snmp_unregister(struct relayd *);
+void	 snmp_event_add(struct relayd *, int);
+void	 snmp_agentx_process(struct agentx_handle *, struct agentx_pdu *, void *);
 
+void	*sstodata(struct sockaddr_storage *);
+size_t	 sstolen(struct sockaddr_storage *);
+
+struct host *
+	 snmp_host_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct table *
+	 snmp_table_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct router *
+	 snmp_router_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct relay *
+	 snmp_relay_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct rsession *
+	 snmp_session_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct rdr *
+	 snmp_rdr_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+struct netroute *
+	 snmp_netroute_byidx(struct relayd *, u_int *, u_int *, u_int, u_int);
+
+int	 snmp_redirect(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_relay(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_router(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_netroute(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_host(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_session(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+int	 snmp_table(struct relayd *, struct snmp_oid *, struct agentx_pdu *,
+	    int, uint32_t, uint32_t, u_int);
+
 void
 snmp_init(struct relayd *env, enum privsep_procid id)
 {
@@ -70,6 +123,7 @@ snmp_init(struct relayd *env, enum privsep_procid id)
 		event_del(&env->sc_snmpto);
 	if (env->sc_snmp != -1) {
 		if (snmp_agentx) {
+			snmp_unregister(env);
 			snmp_agentx_close(snmp_agentx, AGENTX_CLOSE_OTHER);
 			snmp_agentx = NULL;
 		}
@@ -130,7 +184,6 @@ snmp_getsock(struct relayd *env, struct imsg *imsg)
 		fatal("snmp_getsock: agentx pdu");
 	(void)snmp_agentx_send(snmp_agentx, pdu);
 
-	snmp_agentx_set_callback(snmp_agentx, snmp_agentx_process, env);
 	snmp_event_add(env, EV_WRITE);
 
 	return (0);
@@ -151,8 +204,9 @@ snmp_event_add(struct relayd *env, int wflag)
 void
 snmp_sock(int fd, short event, void *arg)
 {
-	struct relayd	*env = arg;
-	int		 evflags = 0;
+	struct relayd		*env = arg;
+	struct agentx_pdu	*pdu;
+	int			 evflags = 0;
 
 	if (event & EV_TIMEOUT) {
 		goto reopen;
@@ -167,7 +221,7 @@ snmp_sock(int fd, short event, void *arg)
 		}
 	}
 	if (event & EV_READ) {
-		if (snmp_agentx_recv(snmp_agentx) == NULL) {
+		if ((pdu = snmp_agentx_recv(snmp_agentx)) == NULL) {
 			if (snmp_agentx->error) {
 				log_warnx("agentx protocol error '%i'",
 				    snmp_agentx->error);
@@ -181,7 +235,7 @@ snmp_sock(int fd, short event, void *arg)
 			/* short read */
 		}
 
-		/* PDU handled in the registered callback */
+		snmp_agentx_process(snmp_agentx, pdu, env);
 	}
 
 	snmp_event_add(env, evflags);
@@ -201,21 +255,19 @@ snmp_sock(int fd, short event, void *arg)
 void
 snmp_agentx_process(struct agentx_handle *h, struct agentx_pdu *pdu, void *arg)
 {
+	struct agentx_getbulk_repeaters		 repeaters;
+	struct agentx_search_range		 sr;
+	struct snmp_oid				 oid;
 	struct agentx_close_request_data	 close_hdr;
 	struct relayd				*env = arg;
+	struct agentx_pdu			*resp;
+	u_int					 erridx = 0;
+	int					 getnext = 0, repetitions;
+	int					 nonrepeaters, maxrepetitions;
 
-	switch (pdu->request->hdr->type) {
+	nonrepeaters = maxrepetitions = -1;
 
-	case AGENTX_NOTIFY:
-		if (snmp_agentx_response(h, pdu) == -1)
-			break;
-		break;
-
-	case AGENTX_OPEN:
-		if (snmp_agentx_open_response(h, pdu) == -1)
-			break;
-		break;
-
+	switch (pdu->hdr->type) {
 	case AGENTX_CLOSE:
 		snmp_agentx_read_raw(pdu, &close_hdr, sizeof(close_hdr));
 		log_info("snmp: agentx master has closed connection (%i)",
@@ -228,7 +280,288 @@ snmp_agentx_process(struct agentx_handle *h, struct ag
 		    IMSG_SNMPSOCK, -1, NULL, 0);
 		break;
 
+	case AGENTX_GET_BULK:
+		if (snmp_agentx_read_raw(pdu,
+		    &repeaters, sizeof(repeaters)) == -1)
+			break;
+
+		nonrepeaters = repeaters.nonrepeaters;
+		maxrepetitions = repeaters.maxrepetitions;
+
+		/* FALLTHROUGH */
+	case AGENTX_GET:
+	case AGENTX_GET_NEXT:
+		if ((resp = snmp_agentx_response_pdu(0, AGENTX_ERR_NONE, 0)) == NULL) {
+			log_warn("%s unable to allocate response pdu", __func__);
+			break;
+		}
+		repetitions = 0;
+ repeat:
+		while (pdu->datalen > sizeof(struct agentx_hdr)) {
+			uint32_t infoendidx, infoentryendidx, infoentryidxendidx;
+
+			erridx++;
+
+			/* AgentX GETs are the OID followed by the null OID */
+			if (snmp_agentx_read_searchrange(pdu, &sr) == -1) {
+				snmp_agentx_pdu_free(resp);
+				resp = NULL;
+				break;
+			}
+
+			if (sr.end.o_n >= OIDIDX_relaydInfo + 1)
+				infoendidx = sr.end.o_id[OIDIDX_relaydInfo];
+			else
+				infoendidx = UINT32_MAX;
+			if (sr.end.o_n >= OIDIDX_relaydInfo + 1 + 1)
+				infoentryendidx = sr.end.o_id[OIDIDX_relaydInfo + 1];
+			else
+				infoentryendidx = UINT32_MAX;
+			if (sr.end.o_n >= OIDIDX_relaydInfo + 2 + 1)
+				infoentryidxendidx = sr.end.o_id[OIDIDX_relaydInfo + 2];
+			else
+				infoentryidxendidx = UINT32_MAX;
+
+			bcopy(&sr.start, &oid, sizeof(oid));
+
+			/*
+			 * If the requested OID is not part of the registered MIB,
+			 * return "no such object", per RFC
+			 */
+			if (snmp_oid_cmp(&relaydinfooid, &oid) == -1) {
+				if (snmp_agentx_varbind(resp, &sr.start,
+				    AGENTX_NO_SUCH_OBJECT, NULL, 0) == -1) {
+					log_warn("%s: unable to generate response",
+					    __func__);
+					snmp_agentx_pdu_free(resp);
+					resp = NULL;
+				}
+				goto reply;
+			}
+
+			if (oid.o_n != OIDIDX_relaydInfo + 2 + 1) {
+				/* GET requests require the exact OID to exist */
+				if (pdu->hdr->type == AGENTX_GET)
+					goto nosuchinstance;
+
+				if (oid.o_n == OIDIDX_relaydInfo + 1) {
+					oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+					oid.o_n = OIDIDX_relaydInfo + 1 + 1;
+				}
+				if (oid.o_n == OIDIDX_relaydInfo + 1 + 1) {
+					oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+					oid.o_n = OIDIDX_relaydInfo + 2 + 1;
+				}
+				if (oid.o_n > OIDIDX_relaydInfo + 2 + 1)
+					oid.o_n = OIDIDX_relaydInfo + 2 + 1;
+			}
+
+			/*
+			 * If not including the starting OID, increment
+			 * here to go to the 'next' OID to allow the lookups
+			 * to work correctly, as they do 'include' matching
+			 */
+			if (pdu->hdr->type == AGENTX_GET_NEXT)
+				getnext = 1;
+
+			switch (oid.o_id[OIDIDX_relaydInfo]) {
+			case 1:
+				log_warnx("%s: redirects", __func__);
+				if (infoendidx < 1)
+					break;
+				if (snmp_redirect(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 2;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 2:
+				log_warnx("%s: relays", __func__);
+				if (infoendidx < 2)
+					break;
+				if (snmp_relay(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 3;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 3:
+				log_warnx("%s: routers", __func__);
+				if (infoendidx < 3)
+					break;
+				if (snmp_router(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 4;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 4:
+				log_warnx("%s: relaydNetRoutes", __func__);
+				if (infoendidx < 4)
+					break;
+				if (snmp_netroute(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 5;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 5:
+				log_warnx("%s: hosts", __func__);
+				if (infoendidx < 5)
+					break;
+				if (snmp_host(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 6;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 6:
+				log_warnx("%s: sessions", __func__);
+				if (infoendidx < 6)
+					break;
+				if (snmp_session(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				oid.o_id[OIDIDX_relaydInfo] = 7;
+				oid.o_id[OIDIDX_relaydInfo + 1] = 0;
+				oid.o_id[OIDIDX_relaydInfo + 2] = 0;
+				/* FALLTHROUGH */
+			case 7:
+				log_warnx("%s: tables", __func__);
+				if (infoendidx < 7)
+					break;
+				if (snmp_table(env, &oid, resp, getnext,
+				    infoentryendidx, infoentryidxendidx, sr.include) == 0)
+					break;
+				if (!getnext)
+					goto nosuchinstance;
+
+				if (snmp_agentx_varbind(resp, &oid,
+				    AGENTX_END_OF_MIB_VIEW, NULL, 0) == -1) {
+					log_warn("%s: unable to generate response",
+					    __func__);
+					snmp_agentx_pdu_free(resp);
+					resp = NULL;
+				}
+				goto reply;
+			default:
+ nosuchinstance:
+				log_warnx("unknown index %i", oid.o_id[OIDIDX_relaydInfo]);
+				if (snmp_agentx_varbind(resp, &sr.start,
+				    AGENTX_NO_SUCH_INSTANCE, NULL, 0) == -1) {
+					log_warn("%s: unable to generate response",
+					    __func__);
+					snmp_agentx_pdu_free(resp);
+					resp = NULL;
+				}
+				goto reply;
+			}
+		}
+
+		if (pdu->hdr->type == AGENTX_GET_BULK) {
+			if (nonrepeaters >= 0)
+				nonrepeaters--;
+			else if (repetitions < maxrepetitions) {
+				repetitions++;
+				goto repeat;
+			}
+		}
+ reply:
+		if (resp) {
+			snmp_agentx_send(snmp_agentx, resp);
+			snmp_event_add(env, EV_WRITE);
+		}
+
+		break;
+
+	case AGENTX_TEST_SET:
+	case AGENTX_COMMIT_SET:
+	case AGENTX_UNDO_SET:
+	case AGENTX_CLEANUP_SET:
+		log_warnx("unimplemented request type '%s'",
+		    snmp_agentx_type2name(pdu->hdr->type));
+		break;
+
+	case AGENTX_RESPONSE:
+		switch (pdu->request->hdr->type) {
+		case AGENTX_NOTIFY:
+			if (snmp_agentx_response(h, pdu) == -1)
+				break;
+		break;
+
+		case AGENTX_OPEN:
+			if (snmp_agentx_open_response(h, pdu) == -1)
+				break;
+			/* Open AgentX socket; register MIB if not trap-only */
+			if (!(env->sc_snmp_flags & FSNMP_TRAPONLY))
+				if (snmp_register(env) == -1) {
+					log_warn("failed to register MIB");
+					break;
+				}
+			break;
+
+		case AGENTX_CLOSE:
+			if (snmp_agentx_response(h, pdu) == -1)
+				break;
+			break;
+
+		case AGENTX_REGISTER:
+			if (snmp_agentx_response(h, pdu) == -1)
+				break;
+			break;
+		break;
+
+		case AGENTX_UNREGISTER:
+			if (snmp_agentx_response(h, pdu) == -1)
+				break;
+			break;
+
+		default:
+			if (snmp_agentx_response(h, pdu) == -1)
+				break;
+			break;
+		}
+		break;
+
+	/* these are nonsensical for subagents to receive */
+	case AGENTX_OPEN:
+	case AGENTX_REGISTER:
+	case AGENTX_UNREGISTER:
+	case AGENTX_NOTIFY:
+	case AGENTX_PING:
+	case AGENTX_INDEX_ALLOCATE:
+	case AGENTX_INDEX_DEALLOCATE:
+	case AGENTX_ADD_AGENT_CAPS:
+	case AGENTX_REMOVE_AGENT_CAPS:
+		log_warnx("ignoring request type '%s'",
+		    snmp_agentx_type2name(pdu->hdr->type));
+		break;
+
 	default:
+		log_warnx("unknown request type '%i'", pdu->hdr->type);
 		if (snmp_agentx_response(h, pdu) == -1)
 			break;
 		break;
@@ -238,8 +571,37 @@ snmp_agentx_process(struct agentx_handle *h, struct ag
 	return;
 }
 
+int
+snmp_register(struct relayd *env)
+{
+	struct agentx_pdu	*pdu;
 
+	if ((pdu = snmp_agentx_register_pdu(&relaydinfooid, 3, 0, 0)) == NULL)
+		return (-1);
+
+	if (snmp_agentx_send(snmp_agentx, pdu) == -1)
+		return (-1);
+
+	snmp_event_add(env, EV_WRITE);
+	return (0);
+}
+
 int
+snmp_unregister(struct relayd *env)
+{
+	struct agentx_pdu	*pdu;
+
+	if ((pdu = snmp_agentx_unregister_pdu(&relaydinfooid, 0, 0)) == NULL)
+		return (-1);
+
+	if (snmp_agentx_send(snmp_agentx, pdu) == -1)
+		return (-1);
+
+	snmp_event_add(env, EV_WRITE);
+	return (0);
+}
+
+int
 snmp_element(const char *oidstr, enum snmp_type type, void *buf, int64_t val,
     struct agentx_pdu *pdu)
 {
@@ -373,8 +735,1000 @@ snmp_string2oid(const char *oidstr, struct snmp_oid *o
 			*p++ = '\0';
 		o->o_id[o->o_n++] = strtonum(sp, 0, UINT_MAX, &errstr);
 		if (errstr || o->o_n > SNMP_MAX_OID_LEN)
+			return (-1);
+	}
+
+	return (0);
+}
+
+void *
+sstodata(struct sockaddr_storage *ss)
+{
+	if (ss->ss_family == AF_INET)
+		return (&((struct sockaddr_in *)ss)->sin_addr);
+	if (ss->ss_family == AF_INET6)
+		return (&((struct sockaddr_in6 *)ss)->sin6_addr);
+	return (NULL);
+}
+
+size_t
+sstolen(struct sockaddr_storage *ss)
+{
+	if (ss->ss_family == AF_INET)
+		return (((struct sockaddr_in *)ss)->sin_len);
+	if (ss->ss_family == AF_INET6)
+		return (((struct sockaddr_in6 *)ss)->sin6_len);
+	return (0);
+}
+
+struct rdr *
+snmp_rdr_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct rdr	*rdr;
+
+	if (*objectidx > RDR_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
+		if (rdr->conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (rdr->conf.id == *instanceidx && !include)
+					rdr = TAILQ_NEXT(rdr, entry);
+				if (rdr) {
+					*instanceidx = rdr->conf.id;
+					return (rdr);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < RDR_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (rdr->conf.id == *instanceidx)
+				return (rdr);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct relay *
+snmp_relay_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct relay	*rly;
+
+	if (*objectidx > RELAY_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(rly, env->sc_relays, rl_entry) {
+		if (rly->rl_conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (rly->rl_conf.id == *instanceidx && !include)
+					rly = TAILQ_NEXT(rly, rl_entry);
+				if (rly) {
+					*instanceidx = rly->rl_conf.id;
+					return (rly);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < RELAY_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (rly);
+			}
+
+			if (rly->rl_conf.id == *instanceidx)
+				return (rly);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct router *
+snmp_router_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct router	*router;
+
+	if (*objectidx > ROUTER_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(router, env->sc_rts, rt_entry) {
+		if (router->rt_conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (router->rt_conf.id == *instanceidx && !include)
+					router = TAILQ_NEXT(router, rt_entry);
+				if (router) {
+					*instanceidx = router->rt_conf.id;
+					return (router);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < ROUTER_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (router->rt_conf.id == *instanceidx)
+				return (router);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct netroute *
+snmp_netroute_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct netroute		*nr;
+
+	if (*objectidx > NETROUTE_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(nr, env->sc_routes, nr_route) {
+		if (nr->nr_conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (nr->nr_conf.id == *instanceidx && !include)
+					nr = TAILQ_NEXT(nr, nr_route);
+				if (nr) {
+					*instanceidx = nr->nr_conf.id;
+					return (nr);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < NETROUTE_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (nr->nr_conf.id == *instanceidx)
+				return (nr);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct host *
+snmp_host_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct host	*host;
+
+	if (*objectidx > HOST_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(host, &env->sc_hosts, globalentry) {
+		if (host->conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (host->conf.id == *instanceidx && !include)
+					host = TAILQ_NEXT(host, globalentry);
+				if (host) {
+					*instanceidx = host->conf.id;
+					return (host);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < HOST_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (host->conf.id == *instanceidx)
+				return (host);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct rsession *
+snmp_session_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct rsession		*session;
+
+	if (*objectidx > SESSION_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(session, &env->sc_sessions, se_entry) {
+		if (session->se_id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (session->se_id == *instanceidx && !include)
+					session = TAILQ_NEXT(session, se_entry);
+				if (session) {
+					*instanceidx = session->se_id;
+					return (session);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < SESSION_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (session->se_id == *instanceidx)
+				return (session);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+struct table *
+snmp_table_byidx(struct relayd *env, u_int *instanceidx, u_int *objectidx,
+    u_int getnext, u_int include)
+{
+	struct table		*table;
+
+	if (*objectidx > TABLE_MAX_SUBIDX)
+		return (NULL);
+	if (*objectidx == 0) {
+		if (!getnext)
+			return (NULL);
+		*objectidx = 1;
+	}
+
+ restart:
+	TAILQ_FOREACH(table, env->sc_tables, entry) {
+		if (table->conf.id >= *instanceidx) {
+			if (getnext) {
+				/*  Lexographical ordering */
+
+				/* 1) try the next instance index */
+				if (table->conf.id == *instanceidx && !include)
+					table = TAILQ_NEXT(table, entry);
+				if (table) {
+					*instanceidx = table->conf.id;
+					return (table);
+				}
+
+				/* 2) try the next object index */
+				if (*objectidx < TABLE_MAX_SUBIDX) {
+					*objectidx += 1;
+					*instanceidx = 1;
+					include = 1;
+					goto restart;
+				}
+
+				/* 3) no further OIDs under this prefix */
+				return (NULL);
+			}
+
+			if (table->conf.id == *instanceidx)
+				return (table);
+
+			return (NULL);
+		}
+	}
+
+	return (NULL);
+}
+
+int
+snmp_redirect(struct relayd *env, struct snmp_oid *oid,
+    struct agentx_pdu *resp, int getnext, uint32_t einstanceidx,
+    uint32_t eobjectidx, u_int include)
+{
+	struct rdr	*rdr;
+	u_int		 instanceidx, objectidx;
+	u_int32_t	 status;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	rdr = snmp_rdr_byidx(env, &instanceidx, &objectidx, getnext, include);
+	if (rdr == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->conf.id,
+		    sizeof(rdr->conf.id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* status */
+		if (rdr->conf.flags & F_DISABLE)
+			status = 1;
+		else if (rdr->conf.flags & F_DOWN)
+			status = 2;
+		else if (rdr->conf.flags & F_BACKUP)
+			status = 3;
+		else
+			status = 0;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	case 3:		/* name */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, rdr->conf.name,
+		    strlen(rdr->conf.name)) == -1)
+			return (-1);
+		break;
+	case 4:		/* count */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_COUNTER64, &rdr->stats.cnt,
+		    sizeof(rdr->stats.cnt)) == -1)
+			return (-1);
+		break;
+	case 5:		/* average */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.avg,
+		    sizeof(rdr->stats.avg)) == -1)
+			return (-1);
+		break;
+	case 6:		/* last */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.last,
+		    sizeof(rdr->stats.last)) == -1)
+			return (-1);
+		break;
+	case 7:		/* average hour */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.avg_hour,
+		    sizeof(rdr->stats.avg_hour)) == -1)
+			return (-1);
+		break;
+	case 8:		/* last hour */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.last_hour,
+		    sizeof(rdr->stats.last_hour)) == -1)
+			return (-1);
+		break;
+	case 9:		/* average day */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.avg_day,
+		    sizeof(rdr->stats.avg_day)) == -1)
+			return (-1);
+		break;
+	case 10:	/* last day */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rdr->stats.last_day,
+		    sizeof(rdr->stats.last_day)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled host element id");
+	}
+
+	return (0);
+}
+
+int
+snmp_relay(struct relayd *env, struct snmp_oid *oid, struct agentx_pdu *resp,
+    int getnext, uint32_t einstanceidx, uint32_t eobjectidx, u_int include)
+{
+	struct relay	*rly;
+	u_int		 instanceidx, objectidx;
+	u_int32_t	 status, value = 0;
+	u_int64_t	 value64 = 0;
+	int		 i;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	rly = snmp_relay_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (rly == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &rly->rl_conf.id,
+		    sizeof(rly->rl_conf.id)) == -1)
 			return (-1);
+		break;
+	case 2:		/* status */
+		if (rly->rl_up == HOST_UP)
+			status = 0;		/* active */
+		else
+			status = 1;		/* disabled */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	case 3:		/* name */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, &rly->rl_conf.name,
+		    strlen(rly->rl_conf.name)) == -1)
+			return (-1);
+		break;
+	case 4:		/* count */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value64 += rly->rl_stats[i].cnt;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_COUNTER64, &value64,
+		    sizeof(value64)) == -1)
+			return (-1);
+		break;
+	case 5:		/* average */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].avg;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	case 6:		/* last */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].last;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	case 7:		/* average hour */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].avg_hour;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	case 8:		/* last hour */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].last_hour;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	case 9:		/* average day */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].avg_day;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	case 10:	/* last day */
+		for (i = 0; i < env->sc_prefork_relay; i++)
+			value += rly->rl_stats[i].last_day;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &value,
+		    sizeof(value)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled host element id");
 	}
 
 	return (0);
 }
+
+int
+snmp_router(struct relayd *env, struct snmp_oid *oid, struct agentx_pdu *resp,
+    int getnext, uint32_t einstanceidx, uint32_t eobjectidx, u_int include)
+{
+	struct router	*router;
+	u_int		 instanceidx, objectidx;
+	u_int32_t	 status;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	router = snmp_router_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (router == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &router->rt_conf.id,
+		    sizeof(router->rt_conf.id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* table index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &router->rt_conf.gwtable,
+		    sizeof(router->rt_conf.gwtable)) == -1)
+			return (-1);
+		break;
+	case 3:		/* status */
+		if (router->rt_conf.flags & F_DISABLE)
+			status = 1;		/* disabled */
+		else
+			status = 0;		/* active */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	case 4:		/* name */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, router->rt_conf.name,
+		    strlen(router->rt_conf.name)) == -1)
+			return (-1);
+		break;
+	case 5:		/* pf label */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, router->rt_conf.label,
+		    strlen(router->rt_conf.label)) == -1)
+			return (-1);
+		break;
+	case 6:		/* rtable */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &router->rt_conf.rtable,
+		    sizeof(router->rt_conf.rtable)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled host element id");
+	}
+
+	return (0);
+}
+
+int
+snmp_netroute(struct relayd *env, struct snmp_oid *oid,
+    struct agentx_pdu *resp, int getnext, uint32_t einstanceidx,
+    uint32_t eobjectidx, u_int include)
+{
+	struct netroute			*nr;
+	u_int32_t			 addrtype;
+	u_int		 		 instanceidx, objectidx;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	nr = snmp_netroute_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (nr == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &nr->nr_conf.id,
+		    sizeof(nr->nr_conf.id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* address */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, sstodata(&nr->nr_conf.ss),
+		    sstolen(&nr->nr_conf.ss)) == -1)
+			return (-1);
+		break;
+	case 3:		/* address type */
+		if (nr->nr_conf.ss.ss_family == AF_INET)
+			addrtype = 1;
+		else if (nr->nr_conf.ss.ss_family == AF_INET6)
+			addrtype = 2;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &addrtype,
+		    sizeof(addrtype)) == -1)
+			return (-1);
+		break;
+	case 4:		/* prefix length */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &nr->nr_conf.prefixlen,
+		    sizeof(nr->nr_conf.prefixlen)) == -1)
+			return (-1);
+		break;
+	case 5:		/* router index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &nr->nr_conf.routerid,
+		    sizeof(nr->nr_conf.routerid)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled host element id");
+	}
+
+	return (0);
+}
+
+int
+snmp_host(struct relayd *env, struct snmp_oid *oid, struct agentx_pdu *resp,
+    int getnext, uint32_t einstanceidx, uint32_t eobjectidx, u_int include)
+{
+	struct host			*host;
+	u_int32_t			 addrtype, count, error, status;
+	u_int				 instanceidx, objectidx;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	host = snmp_host_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (host == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &host->conf.id,
+		    sizeof(host->conf.id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* parent index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &host->conf.parentid,
+		    sizeof(host->conf.parentid)) == -1)
+			return (-1);
+		break;
+	case 3:		/* table index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &host->conf.tableid,
+		    sizeof(host->conf.tableid)) == -1)
+			return (-1);
+		break;
+	case 4:		/* name */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, host->conf.name,
+		    strlen(host->conf.name)) == -1)
+			return (-1);
+		break;
+	case 5:		/* address */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, sstodata(&host->conf.ss),
+		    sstolen(&host->conf.ss)) == -1)
+			return (-1);
+		break;
+	case 6:		/* address type */
+		if (host->conf.ss.ss_family == AF_INET)
+			addrtype = 1;
+		else if (host->conf.ss.ss_family == AF_INET6)
+			addrtype = 2;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &addrtype,
+		    sizeof(addrtype)) == -1)
+			return (-1);
+		break;
+	case 7:		/* status */
+		if (host->flags & F_DISABLE)
+			status = 1;
+		else if (host->up == HOST_UP)
+			status = 0;
+		else if (host->up == HOST_DOWN)
+			status = 2;
+		else
+			status = 3;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	case 8:		/* check count */
+		count = host->check_cnt;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &count,
+		    sizeof(count)) == -1)
+			return (-1);
+		break;
+	case 9:		/* up count */
+		count = host->up_cnt;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &count,
+		    sizeof(count)) == -1)
+			return (-1);
+		break;
+	case 10:	/* errno */
+		error = host->he;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &error,
+		    sizeof(errno)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled host element id");
+	}
+
+	return (0);
+}
+
+int
+snmp_session(struct relayd *env, struct snmp_oid *oid, struct agentx_pdu *resp,
+    int getnext, uint32_t einstanceidx, uint32_t eobjectidx, u_int include)
+{
+	struct timeval		 tv, now;
+	time_t			 ticks;
+	struct rsession		*session;
+	u_int	 		 instanceidx, objectidx;
+	u_int32_t		 status, pid, port, addrtype;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	session = snmp_session_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (session == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &session->se_id,
+		    sizeof(session->se_id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* relay index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &session->se_relayid,
+		    sizeof(session->se_relayid)) == -1)
+			return (-1);
+		break;
+	case 3:		/* in address */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, sstodata(&session->se_in.ss),
+		    sstolen(&session->se_in.ss)) == -1)
+			return (-1);
+		break;
+	case 4:		/* in address type */
+		if (session->se_in.ss.ss_family == AF_INET)
+			addrtype = 1;
+		else if (session->se_in.ss.ss_family == AF_INET6)
+			addrtype = 2;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &addrtype,
+		    sizeof(addrtype)) == -1)
+			return (-1);
+		break;
+	case 5:		/* out address */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, sstodata(&session->se_out.ss),
+		    sstolen(&session->se_out.ss)) == -1)
+			return (-1);
+		break;
+	case 6:		/* out address type */
+		if (session->se_out.ss.ss_family == AF_INET)
+			addrtype = 1;
+		else if (session->se_out.ss.ss_family == AF_INET6)
+			addrtype = 2;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &addrtype,
+		    sizeof(addrtype)) == -1)
+			return (-1);
+		break;
+	case 7:		/* port out */
+		port = session->se_out.port;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &port,
+		    sizeof(port)) == -1)
+			return (-1);
+		break;
+	case 8:		/* port in */
+		port = session->se_in.port;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &port,
+		    sizeof(port)) == -1)
+			return (-1);
+		break;
+	case 9:		/* age */
+		getmonotime(&now);
+		timerclear(&tv);
+		timersub(&now, &session->se_tv_start, &tv);
+		ticks = tv.tv_sec * 100 + tv.tv_usec / 10;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &ticks,
+		    sizeof(ticks)) == -1)
+			return (-1);
+		break;
+	case 10:	/* idle time */
+		getmonotime(&now);
+		timerclear(&tv);
+		timersub(&now, &session->se_tv_last, &tv);
+		ticks = tv.tv_sec * 100 + tv.tv_usec / 10;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &ticks,
+		    sizeof(ticks)) == -1)
+			return (-1);
+		break;
+	case 11:	/* status */
+		if (session->se_done)
+			status = 1;
+		else
+			status = 0;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	case 12:	/* session pid */
+		pid = (u_int32_t)session->se_pid;
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &pid,
+		    sizeof(pid)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled table element id");
+	}
+
+	return (0);
+}
+
+int
+snmp_table(struct relayd *env, struct snmp_oid *oid, struct agentx_pdu *resp,
+    int getnext, uint32_t einstanceidx, uint32_t eobjectidx, u_int include)
+{
+	struct table		*table;
+	u_int	 		 instanceidx, objectidx;
+	u_int32_t		 status;
+
+	instanceidx = oid->o_id[OIDIDX_relaydInfo + 2];
+	objectidx = oid->o_id[OIDIDX_relaydInfo + 1];
+	table = snmp_table_byidx(env, &instanceidx, &objectidx, getnext,
+	    include);
+	if (table == NULL)
+		return (-1);
+
+	if (instanceidx >= einstanceidx || objectidx >= eobjectidx)
+		return (0);
+
+	oid->o_id[OIDIDX_relaydInfo + 1] = objectidx;
+	oid->o_id[OIDIDX_relaydInfo + 2] = instanceidx;
+
+	switch (objectidx) {
+	case 1:		/* index */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &table->conf.id,
+		    sizeof(table->conf.id)) == -1)
+			return (-1);
+		break;
+	case 2:		/* name */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_OCTET_STRING, &table->conf.name,
+		    strlen(table->conf.name)) == -1)
+			return (-1);
+		break;
+	case 3:		/* status */
+		if (TAILQ_EMPTY(&table->hosts))
+			status = 1;		/* empty */
+		else if (table->conf.flags & F_DISABLE)
+			status = 2;		/* disabled */
+		else
+			status = 0;		/* active */
+		if (snmp_agentx_varbind(resp, oid,
+		    AGENTX_INTEGER, &status,
+		    sizeof(status)) == -1)
+			return (-1);
+		break;
+	default:
+		fatalx("unhandled table element id");
+	}
+
+	return (0);
+}