xref: /dflybsd-src/sys/netgraph7/netflow/ng_netflow.c (revision b06ebda0110aaa466b4f7ba6cacac7a26b29f434)
1*b06ebda0SMatthew Dillon /*-
2*b06ebda0SMatthew Dillon  * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org>
3*b06ebda0SMatthew Dillon  * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net>
4*b06ebda0SMatthew Dillon  * All rights reserved.
5*b06ebda0SMatthew Dillon  *
6*b06ebda0SMatthew Dillon  * Redistribution and use in source and binary forms, with or without
7*b06ebda0SMatthew Dillon  * modification, are permitted provided that the following conditions
8*b06ebda0SMatthew Dillon  * are met:
9*b06ebda0SMatthew Dillon  * 1. Redistributions of source code must retain the above copyright
10*b06ebda0SMatthew Dillon  *    notice, this list of conditions and the following disclaimer.
11*b06ebda0SMatthew Dillon  * 2. Redistributions in binary form must reproduce the above copyright
12*b06ebda0SMatthew Dillon  *    notice, this list of conditions and the following disclaimer in the
13*b06ebda0SMatthew Dillon  *    documentation and/or other materials provided with the distribution.
14*b06ebda0SMatthew Dillon  *
15*b06ebda0SMatthew Dillon  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*b06ebda0SMatthew Dillon  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*b06ebda0SMatthew Dillon  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*b06ebda0SMatthew Dillon  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*b06ebda0SMatthew Dillon  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*b06ebda0SMatthew Dillon  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*b06ebda0SMatthew Dillon  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*b06ebda0SMatthew Dillon  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*b06ebda0SMatthew Dillon  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*b06ebda0SMatthew Dillon  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*b06ebda0SMatthew Dillon  * SUCH DAMAGE.
26*b06ebda0SMatthew Dillon  *
27*b06ebda0SMatthew Dillon  * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $
28*b06ebda0SMatthew Dillon  */
29*b06ebda0SMatthew Dillon 
30*b06ebda0SMatthew Dillon static const char rcs_id[] =
31*b06ebda0SMatthew Dillon     "@(#) $FreeBSD: src/sys/netgraph/netflow/ng_netflow.c,v 1.17 2008/04/16 16:47:14 kris Exp $";
32*b06ebda0SMatthew Dillon 
33*b06ebda0SMatthew Dillon #include <sys/param.h>
34*b06ebda0SMatthew Dillon #include <sys/systm.h>
35*b06ebda0SMatthew Dillon #include <sys/kernel.h>
36*b06ebda0SMatthew Dillon #include <sys/limits.h>
37*b06ebda0SMatthew Dillon #include <sys/mbuf.h>
38*b06ebda0SMatthew Dillon #include <sys/socket.h>
39*b06ebda0SMatthew Dillon #include <sys/syslog.h>
40*b06ebda0SMatthew Dillon #include <sys/ctype.h>
41*b06ebda0SMatthew Dillon 
42*b06ebda0SMatthew Dillon #include <net/if.h>
43*b06ebda0SMatthew Dillon #include <net/ethernet.h>
44*b06ebda0SMatthew Dillon #include <net/if_arp.h>
45*b06ebda0SMatthew Dillon #include <net/if_var.h>
46*b06ebda0SMatthew Dillon #include <net/if_vlan_var.h>
47*b06ebda0SMatthew Dillon #include <net/bpf.h>
48*b06ebda0SMatthew Dillon #include <netinet/in.h>
49*b06ebda0SMatthew Dillon #include <netinet/in_systm.h>
50*b06ebda0SMatthew Dillon #include <netinet/ip.h>
51*b06ebda0SMatthew Dillon #include <netinet/tcp.h>
52*b06ebda0SMatthew Dillon #include <netinet/udp.h>
53*b06ebda0SMatthew Dillon 
54*b06ebda0SMatthew Dillon #include <netgraph/ng_message.h>
55*b06ebda0SMatthew Dillon #include <netgraph/ng_parse.h>
56*b06ebda0SMatthew Dillon #include <netgraph/netgraph.h>
57*b06ebda0SMatthew Dillon #include <netgraph/netflow/netflow.h>
58*b06ebda0SMatthew Dillon #include <netgraph/netflow/ng_netflow.h>
59*b06ebda0SMatthew Dillon 
60*b06ebda0SMatthew Dillon /* Netgraph methods */
61*b06ebda0SMatthew Dillon static ng_constructor_t	ng_netflow_constructor;
62*b06ebda0SMatthew Dillon static ng_rcvmsg_t	ng_netflow_rcvmsg;
63*b06ebda0SMatthew Dillon static ng_close_t	ng_netflow_close;
64*b06ebda0SMatthew Dillon static ng_shutdown_t	ng_netflow_rmnode;
65*b06ebda0SMatthew Dillon static ng_newhook_t	ng_netflow_newhook;
66*b06ebda0SMatthew Dillon static ng_rcvdata_t	ng_netflow_rcvdata;
67*b06ebda0SMatthew Dillon static ng_disconnect_t	ng_netflow_disconnect;
68*b06ebda0SMatthew Dillon 
69*b06ebda0SMatthew Dillon /* Parse type for struct ng_netflow_info */
70*b06ebda0SMatthew Dillon static const struct ng_parse_struct_field ng_netflow_info_type_fields[]
71*b06ebda0SMatthew Dillon 	= NG_NETFLOW_INFO_TYPE;
72*b06ebda0SMatthew Dillon static const struct ng_parse_type ng_netflow_info_type = {
73*b06ebda0SMatthew Dillon 	&ng_parse_struct_type,
74*b06ebda0SMatthew Dillon 	&ng_netflow_info_type_fields
75*b06ebda0SMatthew Dillon };
76*b06ebda0SMatthew Dillon 
77*b06ebda0SMatthew Dillon /*  Parse type for struct ng_netflow_ifinfo */
78*b06ebda0SMatthew Dillon static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[]
79*b06ebda0SMatthew Dillon 	= NG_NETFLOW_IFINFO_TYPE;
80*b06ebda0SMatthew Dillon static const struct ng_parse_type ng_netflow_ifinfo_type = {
81*b06ebda0SMatthew Dillon 	&ng_parse_struct_type,
82*b06ebda0SMatthew Dillon 	&ng_netflow_ifinfo_type_fields
83*b06ebda0SMatthew Dillon };
84*b06ebda0SMatthew Dillon 
85*b06ebda0SMatthew Dillon /* Parse type for struct ng_netflow_setdlt */
86*b06ebda0SMatthew Dillon static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[]
87*b06ebda0SMatthew Dillon 	= NG_NETFLOW_SETDLT_TYPE;
88*b06ebda0SMatthew Dillon static const struct ng_parse_type ng_netflow_setdlt_type = {
89*b06ebda0SMatthew Dillon 	&ng_parse_struct_type,
90*b06ebda0SMatthew Dillon 	&ng_netflow_setdlt_type_fields
91*b06ebda0SMatthew Dillon };
92*b06ebda0SMatthew Dillon 
93*b06ebda0SMatthew Dillon /* Parse type for ng_netflow_setifindex */
94*b06ebda0SMatthew Dillon static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[]
95*b06ebda0SMatthew Dillon 	= NG_NETFLOW_SETIFINDEX_TYPE;
96*b06ebda0SMatthew Dillon static const struct ng_parse_type ng_netflow_setifindex_type = {
97*b06ebda0SMatthew Dillon 	&ng_parse_struct_type,
98*b06ebda0SMatthew Dillon 	&ng_netflow_setifindex_type_fields
99*b06ebda0SMatthew Dillon };
100*b06ebda0SMatthew Dillon 
101*b06ebda0SMatthew Dillon /* Parse type for ng_netflow_settimeouts */
102*b06ebda0SMatthew Dillon static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[]
103*b06ebda0SMatthew Dillon 	= NG_NETFLOW_SETTIMEOUTS_TYPE;
104*b06ebda0SMatthew Dillon static const struct ng_parse_type ng_netflow_settimeouts_type = {
105*b06ebda0SMatthew Dillon 	&ng_parse_struct_type,
106*b06ebda0SMatthew Dillon 	&ng_netflow_settimeouts_type_fields
107*b06ebda0SMatthew Dillon };
108*b06ebda0SMatthew Dillon 
109*b06ebda0SMatthew Dillon /* List of commands and how to convert arguments to/from ASCII */
110*b06ebda0SMatthew Dillon static const struct ng_cmdlist ng_netflow_cmds[] = {
111*b06ebda0SMatthew Dillon        {
112*b06ebda0SMatthew Dillon 	 NGM_NETFLOW_COOKIE,
113*b06ebda0SMatthew Dillon 	 NGM_NETFLOW_INFO,
114*b06ebda0SMatthew Dillon 	 "info",
115*b06ebda0SMatthew Dillon 	 NULL,
116*b06ebda0SMatthew Dillon 	 &ng_netflow_info_type
117*b06ebda0SMatthew Dillon        },
118*b06ebda0SMatthew Dillon        {
119*b06ebda0SMatthew Dillon 	NGM_NETFLOW_COOKIE,
120*b06ebda0SMatthew Dillon 	NGM_NETFLOW_IFINFO,
121*b06ebda0SMatthew Dillon 	"ifinfo",
122*b06ebda0SMatthew Dillon 	&ng_parse_uint16_type,
123*b06ebda0SMatthew Dillon 	&ng_netflow_ifinfo_type
124*b06ebda0SMatthew Dillon        },
125*b06ebda0SMatthew Dillon        {
126*b06ebda0SMatthew Dillon 	NGM_NETFLOW_COOKIE,
127*b06ebda0SMatthew Dillon 	NGM_NETFLOW_SETDLT,
128*b06ebda0SMatthew Dillon 	"setdlt",
129*b06ebda0SMatthew Dillon 	&ng_netflow_setdlt_type,
130*b06ebda0SMatthew Dillon 	NULL
131*b06ebda0SMatthew Dillon        },
132*b06ebda0SMatthew Dillon        {
133*b06ebda0SMatthew Dillon 	NGM_NETFLOW_COOKIE,
134*b06ebda0SMatthew Dillon 	NGM_NETFLOW_SETIFINDEX,
135*b06ebda0SMatthew Dillon 	"setifindex",
136*b06ebda0SMatthew Dillon 	&ng_netflow_setifindex_type,
137*b06ebda0SMatthew Dillon 	NULL
138*b06ebda0SMatthew Dillon        },
139*b06ebda0SMatthew Dillon        {
140*b06ebda0SMatthew Dillon 	NGM_NETFLOW_COOKIE,
141*b06ebda0SMatthew Dillon 	NGM_NETFLOW_SETTIMEOUTS,
142*b06ebda0SMatthew Dillon 	"settimeouts",
143*b06ebda0SMatthew Dillon 	&ng_netflow_settimeouts_type,
144*b06ebda0SMatthew Dillon 	NULL
145*b06ebda0SMatthew Dillon        },
146*b06ebda0SMatthew Dillon        { 0 }
147*b06ebda0SMatthew Dillon };
148*b06ebda0SMatthew Dillon 
149*b06ebda0SMatthew Dillon 
150*b06ebda0SMatthew Dillon /* Netgraph node type descriptor */
151*b06ebda0SMatthew Dillon static struct ng_type ng_netflow_typestruct = {
152*b06ebda0SMatthew Dillon 	.version =	NG_ABI_VERSION,
153*b06ebda0SMatthew Dillon 	.name =		NG_NETFLOW_NODE_TYPE,
154*b06ebda0SMatthew Dillon 	.constructor =	ng_netflow_constructor,
155*b06ebda0SMatthew Dillon 	.rcvmsg =	ng_netflow_rcvmsg,
156*b06ebda0SMatthew Dillon 	.close =	ng_netflow_close,
157*b06ebda0SMatthew Dillon 	.shutdown =	ng_netflow_rmnode,
158*b06ebda0SMatthew Dillon 	.newhook =	ng_netflow_newhook,
159*b06ebda0SMatthew Dillon 	.rcvdata =	ng_netflow_rcvdata,
160*b06ebda0SMatthew Dillon 	.disconnect =	ng_netflow_disconnect,
161*b06ebda0SMatthew Dillon 	.cmdlist =	ng_netflow_cmds,
162*b06ebda0SMatthew Dillon };
163*b06ebda0SMatthew Dillon NETGRAPH_INIT(netflow, &ng_netflow_typestruct);
164*b06ebda0SMatthew Dillon 
165*b06ebda0SMatthew Dillon /* Called at node creation */
166*b06ebda0SMatthew Dillon static int
167*b06ebda0SMatthew Dillon ng_netflow_constructor(node_p node)
168*b06ebda0SMatthew Dillon {
169*b06ebda0SMatthew Dillon 	priv_p priv;
170*b06ebda0SMatthew Dillon 	int error = 0;
171*b06ebda0SMatthew Dillon 
172*b06ebda0SMatthew Dillon 	/* Initialize private data */
173*b06ebda0SMatthew Dillon 	MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT);
174*b06ebda0SMatthew Dillon 	if (priv == NULL)
175*b06ebda0SMatthew Dillon 		return (ENOMEM);
176*b06ebda0SMatthew Dillon 	bzero(priv, sizeof(*priv));
177*b06ebda0SMatthew Dillon 
178*b06ebda0SMatthew Dillon 	/* Make node and its data point at each other */
179*b06ebda0SMatthew Dillon 	NG_NODE_SET_PRIVATE(node, priv);
180*b06ebda0SMatthew Dillon 	priv->node = node;
181*b06ebda0SMatthew Dillon 
182*b06ebda0SMatthew Dillon 	/* Initialize timeouts to default values */
183*b06ebda0SMatthew Dillon 	priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT;
184*b06ebda0SMatthew Dillon 	priv->info.nfinfo_act_t = ACTIVE_TIMEOUT;
185*b06ebda0SMatthew Dillon 
186*b06ebda0SMatthew Dillon 	/* Initialize callout handle */
187*b06ebda0SMatthew Dillon 	callout_init(&priv->exp_callout, CALLOUT_MPSAFE);
188*b06ebda0SMatthew Dillon 
189*b06ebda0SMatthew Dillon 	/* Allocate memory and set up flow cache */
190*b06ebda0SMatthew Dillon 	if ((error = ng_netflow_cache_init(priv)))
191*b06ebda0SMatthew Dillon 		return (error);
192*b06ebda0SMatthew Dillon 
193*b06ebda0SMatthew Dillon 	return (0);
194*b06ebda0SMatthew Dillon }
195*b06ebda0SMatthew Dillon 
196*b06ebda0SMatthew Dillon /*
197*b06ebda0SMatthew Dillon  * ng_netflow supports two hooks: data and export.
198*b06ebda0SMatthew Dillon  * Incoming traffic is expected on data, and expired
199*b06ebda0SMatthew Dillon  * netflow datagrams are sent to export.
200*b06ebda0SMatthew Dillon  */
201*b06ebda0SMatthew Dillon static int
202*b06ebda0SMatthew Dillon ng_netflow_newhook(node_p node, hook_p hook, const char *name)
203*b06ebda0SMatthew Dillon {
204*b06ebda0SMatthew Dillon 	const priv_p priv = NG_NODE_PRIVATE(node);
205*b06ebda0SMatthew Dillon 
206*b06ebda0SMatthew Dillon 	if (strncmp(name, NG_NETFLOW_HOOK_DATA,	/* an iface hook? */
207*b06ebda0SMatthew Dillon 	    strlen(NG_NETFLOW_HOOK_DATA)) == 0) {
208*b06ebda0SMatthew Dillon 		iface_p iface;
209*b06ebda0SMatthew Dillon 		int ifnum = -1;
210*b06ebda0SMatthew Dillon 		const char *cp;
211*b06ebda0SMatthew Dillon 		char *eptr;
212*b06ebda0SMatthew Dillon 
213*b06ebda0SMatthew Dillon 		cp = name + strlen(NG_NETFLOW_HOOK_DATA);
214*b06ebda0SMatthew Dillon 		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
215*b06ebda0SMatthew Dillon 			return (EINVAL);
216*b06ebda0SMatthew Dillon 
217*b06ebda0SMatthew Dillon 		ifnum = (int)strtoul(cp, &eptr, 10);
218*b06ebda0SMatthew Dillon 		if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
219*b06ebda0SMatthew Dillon 			return (EINVAL);
220*b06ebda0SMatthew Dillon 
221*b06ebda0SMatthew Dillon 		/* See if hook is already connected */
222*b06ebda0SMatthew Dillon 		if (priv->ifaces[ifnum].hook != NULL)
223*b06ebda0SMatthew Dillon 			return (EISCONN);
224*b06ebda0SMatthew Dillon 
225*b06ebda0SMatthew Dillon 		iface = &priv->ifaces[ifnum];
226*b06ebda0SMatthew Dillon 
227*b06ebda0SMatthew Dillon 		/* Link private info and hook together */
228*b06ebda0SMatthew Dillon 		NG_HOOK_SET_PRIVATE(hook, iface);
229*b06ebda0SMatthew Dillon 		iface->hook = hook;
230*b06ebda0SMatthew Dillon 
231*b06ebda0SMatthew Dillon 		/*
232*b06ebda0SMatthew Dillon 		 * In most cases traffic accounting is done on an
233*b06ebda0SMatthew Dillon 		 * Ethernet interface, so default data link type
234*b06ebda0SMatthew Dillon 		 * will be DLT_EN10MB.
235*b06ebda0SMatthew Dillon 		 */
236*b06ebda0SMatthew Dillon 		iface->info.ifinfo_dlt = DLT_EN10MB;
237*b06ebda0SMatthew Dillon 
238*b06ebda0SMatthew Dillon 	} else if (strncmp(name, NG_NETFLOW_HOOK_OUT,
239*b06ebda0SMatthew Dillon 	    strlen(NG_NETFLOW_HOOK_OUT)) == 0) {
240*b06ebda0SMatthew Dillon 		iface_p iface;
241*b06ebda0SMatthew Dillon 		int ifnum = -1;
242*b06ebda0SMatthew Dillon 		const char *cp;
243*b06ebda0SMatthew Dillon 		char *eptr;
244*b06ebda0SMatthew Dillon 
245*b06ebda0SMatthew Dillon 		cp = name + strlen(NG_NETFLOW_HOOK_OUT);
246*b06ebda0SMatthew Dillon 		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
247*b06ebda0SMatthew Dillon 			return (EINVAL);
248*b06ebda0SMatthew Dillon 
249*b06ebda0SMatthew Dillon 		ifnum = (int)strtoul(cp, &eptr, 10);
250*b06ebda0SMatthew Dillon 		if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
251*b06ebda0SMatthew Dillon 			return (EINVAL);
252*b06ebda0SMatthew Dillon 
253*b06ebda0SMatthew Dillon 		/* See if hook is already connected */
254*b06ebda0SMatthew Dillon 		if (priv->ifaces[ifnum].out != NULL)
255*b06ebda0SMatthew Dillon 			return (EISCONN);
256*b06ebda0SMatthew Dillon 
257*b06ebda0SMatthew Dillon 		iface = &priv->ifaces[ifnum];
258*b06ebda0SMatthew Dillon 
259*b06ebda0SMatthew Dillon 		/* Link private info and hook together */
260*b06ebda0SMatthew Dillon 		NG_HOOK_SET_PRIVATE(hook, iface);
261*b06ebda0SMatthew Dillon 		iface->out = hook;
262*b06ebda0SMatthew Dillon 
263*b06ebda0SMatthew Dillon 	} else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) {
264*b06ebda0SMatthew Dillon 
265*b06ebda0SMatthew Dillon 		if (priv->export != NULL)
266*b06ebda0SMatthew Dillon 			return (EISCONN);
267*b06ebda0SMatthew Dillon 
268*b06ebda0SMatthew Dillon 		priv->export = hook;
269*b06ebda0SMatthew Dillon 
270*b06ebda0SMatthew Dillon #if 0	/* TODO: profile & test first */
271*b06ebda0SMatthew Dillon 		/*
272*b06ebda0SMatthew Dillon 		 * We send export dgrams in interrupt handlers and in
273*b06ebda0SMatthew Dillon 		 * callout threads. We'd better queue data for later
274*b06ebda0SMatthew Dillon 		 * netgraph ISR processing.
275*b06ebda0SMatthew Dillon 		 */
276*b06ebda0SMatthew Dillon 		NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
277*b06ebda0SMatthew Dillon #endif
278*b06ebda0SMatthew Dillon 
279*b06ebda0SMatthew Dillon 		/* Exporter is ready. Let's schedule expiry. */
280*b06ebda0SMatthew Dillon 		callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
281*b06ebda0SMatthew Dillon 		    (void *)priv);
282*b06ebda0SMatthew Dillon 	} else
283*b06ebda0SMatthew Dillon 		return (EINVAL);
284*b06ebda0SMatthew Dillon 
285*b06ebda0SMatthew Dillon 	return (0);
286*b06ebda0SMatthew Dillon }
287*b06ebda0SMatthew Dillon 
288*b06ebda0SMatthew Dillon /* Get a netgraph control message. */
289*b06ebda0SMatthew Dillon static int
290*b06ebda0SMatthew Dillon ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
291*b06ebda0SMatthew Dillon {
292*b06ebda0SMatthew Dillon 	const priv_p priv = NG_NODE_PRIVATE(node);
293*b06ebda0SMatthew Dillon 	struct ng_mesg *resp = NULL;
294*b06ebda0SMatthew Dillon 	int error = 0;
295*b06ebda0SMatthew Dillon 	struct ng_mesg *msg;
296*b06ebda0SMatthew Dillon 
297*b06ebda0SMatthew Dillon 	NGI_GET_MSG(item, msg);
298*b06ebda0SMatthew Dillon 
299*b06ebda0SMatthew Dillon 	/* Deal with message according to cookie and command */
300*b06ebda0SMatthew Dillon 	switch (msg->header.typecookie) {
301*b06ebda0SMatthew Dillon 	case NGM_NETFLOW_COOKIE:
302*b06ebda0SMatthew Dillon 		switch (msg->header.cmd) {
303*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_INFO:
304*b06ebda0SMatthew Dillon 		{
305*b06ebda0SMatthew Dillon 			struct ng_netflow_info *i;
306*b06ebda0SMatthew Dillon 
307*b06ebda0SMatthew Dillon 			NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info),
308*b06ebda0SMatthew Dillon 			    M_NOWAIT);
309*b06ebda0SMatthew Dillon 			i = (struct ng_netflow_info *)resp->data;
310*b06ebda0SMatthew Dillon 			ng_netflow_copyinfo(priv, i);
311*b06ebda0SMatthew Dillon 
312*b06ebda0SMatthew Dillon 			break;
313*b06ebda0SMatthew Dillon 		}
314*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_IFINFO:
315*b06ebda0SMatthew Dillon 		{
316*b06ebda0SMatthew Dillon 			struct ng_netflow_ifinfo *i;
317*b06ebda0SMatthew Dillon 			const uint16_t *index;
318*b06ebda0SMatthew Dillon 
319*b06ebda0SMatthew Dillon 			if (msg->header.arglen != sizeof(uint16_t))
320*b06ebda0SMatthew Dillon 				 ERROUT(EINVAL);
321*b06ebda0SMatthew Dillon 
322*b06ebda0SMatthew Dillon 			index  = (uint16_t *)msg->data;
323*b06ebda0SMatthew Dillon 			if (*index >= NG_NETFLOW_MAXIFACES)
324*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
325*b06ebda0SMatthew Dillon 
326*b06ebda0SMatthew Dillon 			/* connected iface? */
327*b06ebda0SMatthew Dillon 			if (priv->ifaces[*index].hook == NULL)
328*b06ebda0SMatthew Dillon 				 ERROUT(EINVAL);
329*b06ebda0SMatthew Dillon 
330*b06ebda0SMatthew Dillon 			NG_MKRESPONSE(resp, msg,
331*b06ebda0SMatthew Dillon 			     sizeof(struct ng_netflow_ifinfo), M_NOWAIT);
332*b06ebda0SMatthew Dillon 			i = (struct ng_netflow_ifinfo *)resp->data;
333*b06ebda0SMatthew Dillon 			memcpy((void *)i, (void *)&priv->ifaces[*index].info,
334*b06ebda0SMatthew Dillon 			    sizeof(priv->ifaces[*index].info));
335*b06ebda0SMatthew Dillon 
336*b06ebda0SMatthew Dillon 			break;
337*b06ebda0SMatthew Dillon 		}
338*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_SETDLT:
339*b06ebda0SMatthew Dillon 		{
340*b06ebda0SMatthew Dillon 			struct ng_netflow_setdlt *set;
341*b06ebda0SMatthew Dillon 			struct ng_netflow_iface *iface;
342*b06ebda0SMatthew Dillon 
343*b06ebda0SMatthew Dillon 			if (msg->header.arglen != sizeof(struct ng_netflow_setdlt))
344*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
345*b06ebda0SMatthew Dillon 
346*b06ebda0SMatthew Dillon 			set = (struct ng_netflow_setdlt *)msg->data;
347*b06ebda0SMatthew Dillon 			if (set->iface >= NG_NETFLOW_MAXIFACES)
348*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
349*b06ebda0SMatthew Dillon 			iface = &priv->ifaces[set->iface];
350*b06ebda0SMatthew Dillon 
351*b06ebda0SMatthew Dillon 			/* connected iface? */
352*b06ebda0SMatthew Dillon 			if (iface->hook == NULL)
353*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
354*b06ebda0SMatthew Dillon 
355*b06ebda0SMatthew Dillon 			switch (set->dlt) {
356*b06ebda0SMatthew Dillon 			case	DLT_EN10MB:
357*b06ebda0SMatthew Dillon 				iface->info.ifinfo_dlt = DLT_EN10MB;
358*b06ebda0SMatthew Dillon 				break;
359*b06ebda0SMatthew Dillon 			case	DLT_RAW:
360*b06ebda0SMatthew Dillon 				iface->info.ifinfo_dlt = DLT_RAW;
361*b06ebda0SMatthew Dillon 				break;
362*b06ebda0SMatthew Dillon 			default:
363*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
364*b06ebda0SMatthew Dillon 			}
365*b06ebda0SMatthew Dillon 			break;
366*b06ebda0SMatthew Dillon 		}
367*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_SETIFINDEX:
368*b06ebda0SMatthew Dillon 		{
369*b06ebda0SMatthew Dillon 			struct ng_netflow_setifindex *set;
370*b06ebda0SMatthew Dillon 			struct ng_netflow_iface *iface;
371*b06ebda0SMatthew Dillon 
372*b06ebda0SMatthew Dillon 			if (msg->header.arglen != sizeof(struct ng_netflow_setifindex))
373*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
374*b06ebda0SMatthew Dillon 
375*b06ebda0SMatthew Dillon 			set = (struct ng_netflow_setifindex *)msg->data;
376*b06ebda0SMatthew Dillon 			if (set->iface >= NG_NETFLOW_MAXIFACES)
377*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
378*b06ebda0SMatthew Dillon 			iface = &priv->ifaces[set->iface];
379*b06ebda0SMatthew Dillon 
380*b06ebda0SMatthew Dillon 			/* connected iface? */
381*b06ebda0SMatthew Dillon 			if (iface->hook == NULL)
382*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
383*b06ebda0SMatthew Dillon 
384*b06ebda0SMatthew Dillon 			iface->info.ifinfo_index = set->index;
385*b06ebda0SMatthew Dillon 
386*b06ebda0SMatthew Dillon 			break;
387*b06ebda0SMatthew Dillon 		}
388*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_SETTIMEOUTS:
389*b06ebda0SMatthew Dillon 		{
390*b06ebda0SMatthew Dillon 			struct ng_netflow_settimeouts *set;
391*b06ebda0SMatthew Dillon 
392*b06ebda0SMatthew Dillon 			if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
393*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
394*b06ebda0SMatthew Dillon 
395*b06ebda0SMatthew Dillon 			set = (struct ng_netflow_settimeouts *)msg->data;
396*b06ebda0SMatthew Dillon 
397*b06ebda0SMatthew Dillon 			priv->info.nfinfo_inact_t = set->inactive_timeout;
398*b06ebda0SMatthew Dillon 			priv->info.nfinfo_act_t = set->active_timeout;
399*b06ebda0SMatthew Dillon 
400*b06ebda0SMatthew Dillon 			break;
401*b06ebda0SMatthew Dillon 		}
402*b06ebda0SMatthew Dillon 		case NGM_NETFLOW_SHOW:
403*b06ebda0SMatthew Dillon 		{
404*b06ebda0SMatthew Dillon 			uint32_t *last;
405*b06ebda0SMatthew Dillon 
406*b06ebda0SMatthew Dillon 			if (msg->header.arglen != sizeof(uint32_t))
407*b06ebda0SMatthew Dillon 				ERROUT(EINVAL);
408*b06ebda0SMatthew Dillon 
409*b06ebda0SMatthew Dillon 			last = (uint32_t *)msg->data;
410*b06ebda0SMatthew Dillon 
411*b06ebda0SMatthew Dillon 			NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT);
412*b06ebda0SMatthew Dillon 
413*b06ebda0SMatthew Dillon 			if (!resp)
414*b06ebda0SMatthew Dillon 				ERROUT(ENOMEM);
415*b06ebda0SMatthew Dillon 
416*b06ebda0SMatthew Dillon 			error = ng_netflow_flow_show(priv, *last, resp);
417*b06ebda0SMatthew Dillon 
418*b06ebda0SMatthew Dillon 			break;
419*b06ebda0SMatthew Dillon 		}
420*b06ebda0SMatthew Dillon 		default:
421*b06ebda0SMatthew Dillon 			ERROUT(EINVAL);		/* unknown command */
422*b06ebda0SMatthew Dillon 			break;
423*b06ebda0SMatthew Dillon 		}
424*b06ebda0SMatthew Dillon 		break;
425*b06ebda0SMatthew Dillon 	default:
426*b06ebda0SMatthew Dillon 		ERROUT(EINVAL);		/* incorrect cookie */
427*b06ebda0SMatthew Dillon 		break;
428*b06ebda0SMatthew Dillon 	}
429*b06ebda0SMatthew Dillon 
430*b06ebda0SMatthew Dillon 	/*
431*b06ebda0SMatthew Dillon 	 * Take care of synchronous response, if any.
432*b06ebda0SMatthew Dillon 	 * Free memory and return.
433*b06ebda0SMatthew Dillon 	 */
434*b06ebda0SMatthew Dillon done:
435*b06ebda0SMatthew Dillon 	NG_RESPOND_MSG(error, node, item, resp);
436*b06ebda0SMatthew Dillon 	NG_FREE_MSG(msg);
437*b06ebda0SMatthew Dillon 
438*b06ebda0SMatthew Dillon 	return (error);
439*b06ebda0SMatthew Dillon }
440*b06ebda0SMatthew Dillon 
441*b06ebda0SMatthew Dillon /* Receive data on hook. */
442*b06ebda0SMatthew Dillon static int
443*b06ebda0SMatthew Dillon ng_netflow_rcvdata (hook_p hook, item_p item)
444*b06ebda0SMatthew Dillon {
445*b06ebda0SMatthew Dillon 	const node_p node = NG_HOOK_NODE(hook);
446*b06ebda0SMatthew Dillon 	const priv_p priv = NG_NODE_PRIVATE(node);
447*b06ebda0SMatthew Dillon 	const iface_p iface = NG_HOOK_PRIVATE(hook);
448*b06ebda0SMatthew Dillon 	struct mbuf *m = NULL;
449*b06ebda0SMatthew Dillon 	struct ip *ip;
450*b06ebda0SMatthew Dillon 	int pullup_len = 0;
451*b06ebda0SMatthew Dillon 	int error = 0;
452*b06ebda0SMatthew Dillon 
453*b06ebda0SMatthew Dillon 	if (hook == priv->export) {
454*b06ebda0SMatthew Dillon 		/*
455*b06ebda0SMatthew Dillon 		 * Data arrived on export hook.
456*b06ebda0SMatthew Dillon 		 * This must not happen.
457*b06ebda0SMatthew Dillon 		 */
458*b06ebda0SMatthew Dillon 		log(LOG_ERR, "ng_netflow: incoming data on export hook!\n");
459*b06ebda0SMatthew Dillon 		ERROUT(EINVAL);
460*b06ebda0SMatthew Dillon 	};
461*b06ebda0SMatthew Dillon 
462*b06ebda0SMatthew Dillon 	if (hook == iface->out) {
463*b06ebda0SMatthew Dillon 		/*
464*b06ebda0SMatthew Dillon 		 * Data arrived on out hook. Bypass it.
465*b06ebda0SMatthew Dillon 		 */
466*b06ebda0SMatthew Dillon 		if (iface->hook == NULL)
467*b06ebda0SMatthew Dillon 			ERROUT(ENOTCONN);
468*b06ebda0SMatthew Dillon 
469*b06ebda0SMatthew Dillon 		NG_FWD_ITEM_HOOK(error, item, iface->hook);
470*b06ebda0SMatthew Dillon 		return (error);
471*b06ebda0SMatthew Dillon 	}
472*b06ebda0SMatthew Dillon 
473*b06ebda0SMatthew Dillon 	NGI_GET_M(item, m);
474*b06ebda0SMatthew Dillon 
475*b06ebda0SMatthew Dillon 	/* Increase counters. */
476*b06ebda0SMatthew Dillon 	iface->info.ifinfo_packets++;
477*b06ebda0SMatthew Dillon 
478*b06ebda0SMatthew Dillon 	/*
479*b06ebda0SMatthew Dillon 	 * Depending on interface data link type and packet contents
480*b06ebda0SMatthew Dillon 	 * we pullup enough data, so that ng_netflow_flow_add() does not
481*b06ebda0SMatthew Dillon 	 * need to know about mbuf at all. We keep current length of data
482*b06ebda0SMatthew Dillon 	 * needed to be contiguous in pullup_len. mtod() is done at the
483*b06ebda0SMatthew Dillon 	 * very end one more time, since m can had changed after pulluping.
484*b06ebda0SMatthew Dillon 	 *
485*b06ebda0SMatthew Dillon 	 * In case of unrecognized data we don't return error, but just
486*b06ebda0SMatthew Dillon 	 * pass data to downstream hook, if it is available.
487*b06ebda0SMatthew Dillon 	 */
488*b06ebda0SMatthew Dillon 
489*b06ebda0SMatthew Dillon #define	M_CHECK(length)	do {					\
490*b06ebda0SMatthew Dillon 	pullup_len += length;					\
491*b06ebda0SMatthew Dillon 	if ((m)->m_pkthdr.len < (pullup_len)) {			\
492*b06ebda0SMatthew Dillon 		error = EINVAL;					\
493*b06ebda0SMatthew Dillon 		goto bypass;					\
494*b06ebda0SMatthew Dillon 	} 							\
495*b06ebda0SMatthew Dillon 	if ((m)->m_len < (pullup_len) &&			\
496*b06ebda0SMatthew Dillon 	   (((m) = m_pullup((m),(pullup_len))) == NULL)) {	\
497*b06ebda0SMatthew Dillon 		error = ENOBUFS;				\
498*b06ebda0SMatthew Dillon 		goto done;					\
499*b06ebda0SMatthew Dillon 	}							\
500*b06ebda0SMatthew Dillon } while (0)
501*b06ebda0SMatthew Dillon 
502*b06ebda0SMatthew Dillon 	switch (iface->info.ifinfo_dlt) {
503*b06ebda0SMatthew Dillon 	case DLT_EN10MB:	/* Ethernet */
504*b06ebda0SMatthew Dillon 	    {
505*b06ebda0SMatthew Dillon 		struct ether_header *eh;
506*b06ebda0SMatthew Dillon 		uint16_t etype;
507*b06ebda0SMatthew Dillon 
508*b06ebda0SMatthew Dillon 		M_CHECK(sizeof(struct ether_header));
509*b06ebda0SMatthew Dillon 		eh = mtod(m, struct ether_header *);
510*b06ebda0SMatthew Dillon 
511*b06ebda0SMatthew Dillon 		/* Make sure this is IP frame. */
512*b06ebda0SMatthew Dillon 		etype = ntohs(eh->ether_type);
513*b06ebda0SMatthew Dillon 		switch (etype) {
514*b06ebda0SMatthew Dillon 		case ETHERTYPE_IP:
515*b06ebda0SMatthew Dillon 			M_CHECK(sizeof(struct ip));
516*b06ebda0SMatthew Dillon 			eh = mtod(m, struct ether_header *);
517*b06ebda0SMatthew Dillon 			ip = (struct ip *)(eh + 1);
518*b06ebda0SMatthew Dillon 			break;
519*b06ebda0SMatthew Dillon 		case ETHERTYPE_VLAN:
520*b06ebda0SMatthew Dillon 		    {
521*b06ebda0SMatthew Dillon 			struct ether_vlan_header *evh;
522*b06ebda0SMatthew Dillon 
523*b06ebda0SMatthew Dillon 			M_CHECK(sizeof(struct ether_vlan_header) -
524*b06ebda0SMatthew Dillon 			    sizeof(struct ether_header));
525*b06ebda0SMatthew Dillon 			evh = mtod(m, struct ether_vlan_header *);
526*b06ebda0SMatthew Dillon 			if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
527*b06ebda0SMatthew Dillon 				M_CHECK(sizeof(struct ip));
528*b06ebda0SMatthew Dillon 				ip = (struct ip *)(evh + 1);
529*b06ebda0SMatthew Dillon 				break;
530*b06ebda0SMatthew Dillon 			}
531*b06ebda0SMatthew Dillon 		    }
532*b06ebda0SMatthew Dillon 		default:
533*b06ebda0SMatthew Dillon 			goto bypass;	/* pass this frame */
534*b06ebda0SMatthew Dillon 		}
535*b06ebda0SMatthew Dillon 		break;
536*b06ebda0SMatthew Dillon 	    }
537*b06ebda0SMatthew Dillon 	case DLT_RAW:		/* IP packets */
538*b06ebda0SMatthew Dillon 		M_CHECK(sizeof(struct ip));
539*b06ebda0SMatthew Dillon 		ip = mtod(m, struct ip *);
540*b06ebda0SMatthew Dillon 		break;
541*b06ebda0SMatthew Dillon 	default:
542*b06ebda0SMatthew Dillon 		goto bypass;
543*b06ebda0SMatthew Dillon 		break;
544*b06ebda0SMatthew Dillon 	}
545*b06ebda0SMatthew Dillon 
546*b06ebda0SMatthew Dillon 	if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
547*b06ebda0SMatthew Dillon 		/*
548*b06ebda0SMatthew Dillon 		 * In case of IP header with options, we haven't pulled
549*b06ebda0SMatthew Dillon 		 * up enough, yet.
550*b06ebda0SMatthew Dillon 		 */
551*b06ebda0SMatthew Dillon 		pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
552*b06ebda0SMatthew Dillon 
553*b06ebda0SMatthew Dillon 		switch (ip->ip_p) {
554*b06ebda0SMatthew Dillon 		case IPPROTO_TCP:
555*b06ebda0SMatthew Dillon 			M_CHECK(sizeof(struct tcphdr));
556*b06ebda0SMatthew Dillon 			break;
557*b06ebda0SMatthew Dillon 		case IPPROTO_UDP:
558*b06ebda0SMatthew Dillon 			M_CHECK(sizeof(struct udphdr));
559*b06ebda0SMatthew Dillon 			break;
560*b06ebda0SMatthew Dillon 		}
561*b06ebda0SMatthew Dillon 	}
562*b06ebda0SMatthew Dillon 
563*b06ebda0SMatthew Dillon 	switch (iface->info.ifinfo_dlt) {
564*b06ebda0SMatthew Dillon 	case DLT_EN10MB:
565*b06ebda0SMatthew Dillon 	    {
566*b06ebda0SMatthew Dillon 		struct ether_header *eh;
567*b06ebda0SMatthew Dillon 
568*b06ebda0SMatthew Dillon 		eh = mtod(m, struct ether_header *);
569*b06ebda0SMatthew Dillon 		switch (ntohs(eh->ether_type)) {
570*b06ebda0SMatthew Dillon 		case ETHERTYPE_IP:
571*b06ebda0SMatthew Dillon 			ip = (struct ip *)(eh + 1);
572*b06ebda0SMatthew Dillon 			break;
573*b06ebda0SMatthew Dillon 		case ETHERTYPE_VLAN:
574*b06ebda0SMatthew Dillon 		    {
575*b06ebda0SMatthew Dillon 			struct ether_vlan_header *evh;
576*b06ebda0SMatthew Dillon 
577*b06ebda0SMatthew Dillon 			evh = mtod(m, struct ether_vlan_header *);
578*b06ebda0SMatthew Dillon 			ip = (struct ip *)(evh + 1);
579*b06ebda0SMatthew Dillon 			break;
580*b06ebda0SMatthew Dillon 		     }
581*b06ebda0SMatthew Dillon 		default:
582*b06ebda0SMatthew Dillon 			panic("ng_netflow entered deadcode");
583*b06ebda0SMatthew Dillon 		}
584*b06ebda0SMatthew Dillon 		break;
585*b06ebda0SMatthew Dillon 	     }
586*b06ebda0SMatthew Dillon 	case DLT_RAW:
587*b06ebda0SMatthew Dillon 		ip = mtod(m, struct ip *);
588*b06ebda0SMatthew Dillon 		break;
589*b06ebda0SMatthew Dillon 	default:
590*b06ebda0SMatthew Dillon 		panic("ng_netflow entered deadcode");
591*b06ebda0SMatthew Dillon 	}
592*b06ebda0SMatthew Dillon 
593*b06ebda0SMatthew Dillon #undef	M_CHECK
594*b06ebda0SMatthew Dillon 
595*b06ebda0SMatthew Dillon 	error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif);
596*b06ebda0SMatthew Dillon 
597*b06ebda0SMatthew Dillon bypass:
598*b06ebda0SMatthew Dillon 	if (iface->out != NULL) {
599*b06ebda0SMatthew Dillon 		/* XXX: error gets overwritten here */
600*b06ebda0SMatthew Dillon 		NG_FWD_NEW_DATA(error, item, iface->out, m);
601*b06ebda0SMatthew Dillon 		return (error);
602*b06ebda0SMatthew Dillon 	}
603*b06ebda0SMatthew Dillon done:
604*b06ebda0SMatthew Dillon 	if (item)
605*b06ebda0SMatthew Dillon 		NG_FREE_ITEM(item);
606*b06ebda0SMatthew Dillon 	if (m)
607*b06ebda0SMatthew Dillon 		NG_FREE_M(m);
608*b06ebda0SMatthew Dillon 
609*b06ebda0SMatthew Dillon 	return (error);
610*b06ebda0SMatthew Dillon }
611*b06ebda0SMatthew Dillon 
612*b06ebda0SMatthew Dillon /* We will be shut down in a moment */
613*b06ebda0SMatthew Dillon static int
614*b06ebda0SMatthew Dillon ng_netflow_close(node_p node)
615*b06ebda0SMatthew Dillon {
616*b06ebda0SMatthew Dillon 	const priv_p priv = NG_NODE_PRIVATE(node);
617*b06ebda0SMatthew Dillon 
618*b06ebda0SMatthew Dillon 	callout_drain(&priv->exp_callout);
619*b06ebda0SMatthew Dillon 	ng_netflow_cache_flush(priv);
620*b06ebda0SMatthew Dillon 
621*b06ebda0SMatthew Dillon 	return (0);
622*b06ebda0SMatthew Dillon }
623*b06ebda0SMatthew Dillon 
624*b06ebda0SMatthew Dillon /* Do local shutdown processing. */
625*b06ebda0SMatthew Dillon static int
626*b06ebda0SMatthew Dillon ng_netflow_rmnode(node_p node)
627*b06ebda0SMatthew Dillon {
628*b06ebda0SMatthew Dillon 	const priv_p priv = NG_NODE_PRIVATE(node);
629*b06ebda0SMatthew Dillon 
630*b06ebda0SMatthew Dillon 	NG_NODE_SET_PRIVATE(node, NULL);
631*b06ebda0SMatthew Dillon 	NG_NODE_UNREF(priv->node);
632*b06ebda0SMatthew Dillon 
633*b06ebda0SMatthew Dillon 	FREE(priv, M_NETGRAPH);
634*b06ebda0SMatthew Dillon 
635*b06ebda0SMatthew Dillon 	return (0);
636*b06ebda0SMatthew Dillon }
637*b06ebda0SMatthew Dillon 
638*b06ebda0SMatthew Dillon /* Hook disconnection. */
639*b06ebda0SMatthew Dillon static int
640*b06ebda0SMatthew Dillon ng_netflow_disconnect(hook_p hook)
641*b06ebda0SMatthew Dillon {
642*b06ebda0SMatthew Dillon 	node_p node = NG_HOOK_NODE(hook);
643*b06ebda0SMatthew Dillon 	priv_p priv = NG_NODE_PRIVATE(node);
644*b06ebda0SMatthew Dillon 	iface_p iface = NG_HOOK_PRIVATE(hook);
645*b06ebda0SMatthew Dillon 
646*b06ebda0SMatthew Dillon 	if (iface != NULL) {
647*b06ebda0SMatthew Dillon 		if (iface->hook == hook)
648*b06ebda0SMatthew Dillon 			iface->hook = NULL;
649*b06ebda0SMatthew Dillon 		if (iface->out == hook)
650*b06ebda0SMatthew Dillon 			iface->out = NULL;
651*b06ebda0SMatthew Dillon 	}
652*b06ebda0SMatthew Dillon 
653*b06ebda0SMatthew Dillon 	/* if export hook disconnected stop running expire(). */
654*b06ebda0SMatthew Dillon 	if (hook == priv->export) {
655*b06ebda0SMatthew Dillon 		callout_drain(&priv->exp_callout);
656*b06ebda0SMatthew Dillon 		priv->export = NULL;
657*b06ebda0SMatthew Dillon 	}
658*b06ebda0SMatthew Dillon 
659*b06ebda0SMatthew Dillon 	/* Removal of the last link destroys the node. */
660*b06ebda0SMatthew Dillon 	if (NG_NODE_NUMHOOKS(node) == 0)
661*b06ebda0SMatthew Dillon 		ng_rmnode_self(node);
662*b06ebda0SMatthew Dillon 
663*b06ebda0SMatthew Dillon 	return (0);
664*b06ebda0SMatthew Dillon }
665