1/*-
2 * Copyright (c) 2015 Dmitry Vagin <daemon.hammer@ya.ru>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28#include <sys/cdefs.h>
29#include "opt_inet.h"
30#include "opt_inet6.h"
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/kernel.h>
35#include <sys/endian.h>
36#include <sys/malloc.h>
37#include <sys/mbuf.h>
38#include <sys/socket.h>
39
40#include <net/bpf.h>
41#include <net/ethernet.h>
42#include <net/if.h>
43#include <net/if_vlan_var.h>
44
45#include <netinet/in.h>
46#include <netinet/ip.h>
47#include <netinet/ip6.h>
48#include <netinet/tcp.h>
49#include <netinet/udp.h>
50#include <machine/in_cksum.h>
51
52#include <netgraph/ng_message.h>
53#include <netgraph/ng_parse.h>
54#include <netgraph/netgraph.h>
55
56#include <netgraph/ng_checksum.h>
57
58/* private data */
59struct ng_checksum_priv {
60	hook_p in;
61	hook_p out;
62	uint8_t dlt;	/* DLT_XXX from bpf.h */
63	struct ng_checksum_config *conf;
64	struct ng_checksum_stats stats;
65};
66
67typedef struct ng_checksum_priv *priv_p;
68
69/* Netgraph methods */
70static ng_constructor_t	ng_checksum_constructor;
71static ng_rcvmsg_t	ng_checksum_rcvmsg;
72static ng_shutdown_t	ng_checksum_shutdown;
73static ng_newhook_t	ng_checksum_newhook;
74static ng_rcvdata_t	ng_checksum_rcvdata;
75static ng_disconnect_t	ng_checksum_disconnect;
76#define ERROUT(x) { error = (x); goto done; }
77
78static const struct ng_parse_struct_field ng_checksum_config_type_fields[]
79	= NG_CHECKSUM_CONFIG_TYPE;
80static const struct ng_parse_type ng_checksum_config_type = {
81	&ng_parse_struct_type,
82	&ng_checksum_config_type_fields
83};
84
85static const struct ng_parse_struct_field ng_checksum_stats_fields[]
86	= NG_CHECKSUM_STATS_TYPE;
87static const struct ng_parse_type ng_checksum_stats_type = {
88	&ng_parse_struct_type,
89	&ng_checksum_stats_fields
90};
91
92static const struct ng_cmdlist ng_checksum_cmdlist[] = {
93	{
94		NGM_CHECKSUM_COOKIE,
95		NGM_CHECKSUM_GETDLT,
96		"getdlt",
97		NULL,
98		&ng_parse_uint8_type
99	},
100	{
101		NGM_CHECKSUM_COOKIE,
102		NGM_CHECKSUM_SETDLT,
103		"setdlt",
104		&ng_parse_uint8_type,
105		NULL
106	},
107	{
108		NGM_CHECKSUM_COOKIE,
109		NGM_CHECKSUM_GETCONFIG,
110		"getconfig",
111		NULL,
112		&ng_checksum_config_type
113	},
114	{
115		NGM_CHECKSUM_COOKIE,
116		NGM_CHECKSUM_SETCONFIG,
117		"setconfig",
118		&ng_checksum_config_type,
119		NULL
120	},
121	{
122		NGM_CHECKSUM_COOKIE,
123		NGM_CHECKSUM_GET_STATS,
124		"getstats",
125		NULL,
126		&ng_checksum_stats_type
127	},
128	{
129		NGM_CHECKSUM_COOKIE,
130		NGM_CHECKSUM_CLR_STATS,
131		"clrstats",
132		NULL,
133		NULL
134	},
135	{
136		NGM_CHECKSUM_COOKIE,
137		NGM_CHECKSUM_GETCLR_STATS,
138		"getclrstats",
139		NULL,
140		&ng_checksum_stats_type
141	},
142	{ 0 }
143};
144
145static struct ng_type typestruct = {
146	.version =	NG_ABI_VERSION,
147	.name =		NG_CHECKSUM_NODE_TYPE,
148	.constructor =	ng_checksum_constructor,
149	.rcvmsg =	ng_checksum_rcvmsg,
150	.shutdown =	ng_checksum_shutdown,
151	.newhook =	ng_checksum_newhook,
152	.rcvdata =	ng_checksum_rcvdata,
153	.disconnect =	ng_checksum_disconnect,
154	.cmdlist =	ng_checksum_cmdlist,
155};
156
157NETGRAPH_INIT(checksum, &typestruct);
158
159static int
160ng_checksum_constructor(node_p node)
161{
162	priv_p priv;
163
164	priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK|M_ZERO);
165	priv->dlt = DLT_RAW;
166
167	NG_NODE_SET_PRIVATE(node, priv);
168
169	return (0);
170}
171
172static int
173ng_checksum_newhook(node_p node, hook_p hook, const char *name)
174{
175	const priv_p priv = NG_NODE_PRIVATE(node);
176
177	if (strncmp(name, NG_CHECKSUM_HOOK_IN, strlen(NG_CHECKSUM_HOOK_IN)) == 0) {
178		priv->in = hook;
179	} else if (strncmp(name, NG_CHECKSUM_HOOK_OUT, strlen(NG_CHECKSUM_HOOK_OUT)) == 0) {
180		priv->out = hook;
181	} else
182		return (EINVAL);
183
184	return (0);
185}
186
187static int
188ng_checksum_rcvmsg(node_p node, item_p item, hook_p lasthook)
189{
190	const priv_p priv = NG_NODE_PRIVATE(node);
191	struct ng_checksum_config *conf, *newconf;
192	struct ng_mesg *msg;
193	struct ng_mesg *resp = NULL;
194	int error = 0;
195
196	NGI_GET_MSG(item, msg);
197
198	if  (msg->header.typecookie != NGM_CHECKSUM_COOKIE)
199		ERROUT(EINVAL);
200
201	switch (msg->header.cmd)
202	{
203		case NGM_CHECKSUM_GETDLT:
204			NG_MKRESPONSE(resp, msg, sizeof(uint8_t), M_WAITOK);
205
206			if (resp == NULL)
207				ERROUT(ENOMEM);
208
209			*((uint8_t *) resp->data) = priv->dlt;
210
211			break;
212
213		case NGM_CHECKSUM_SETDLT:
214			if (msg->header.arglen != sizeof(uint8_t))
215				ERROUT(EINVAL);
216
217			switch (*(uint8_t *) msg->data)
218			{
219				case DLT_EN10MB:
220				case DLT_RAW:
221					priv->dlt = *(uint8_t *) msg->data;
222					break;
223
224				default:
225					ERROUT(EINVAL);
226			}
227
228			break;
229
230		case NGM_CHECKSUM_GETCONFIG:
231			if (priv->conf == NULL)
232				ERROUT(0);
233
234			NG_MKRESPONSE(resp, msg, sizeof(struct ng_checksum_config), M_WAITOK);
235
236			if (resp == NULL)
237				ERROUT(ENOMEM);
238
239			bcopy(priv->conf, resp->data, sizeof(struct ng_checksum_config));
240
241			break;
242
243		case NGM_CHECKSUM_SETCONFIG:
244			conf = (struct ng_checksum_config *) msg->data;
245
246			if (msg->header.arglen != sizeof(struct ng_checksum_config))
247				ERROUT(EINVAL);
248
249			conf->csum_flags &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
250			conf->csum_offload &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
251
252			newconf = malloc(sizeof(struct ng_checksum_config), M_NETGRAPH, M_WAITOK|M_ZERO);
253
254			bcopy(conf, newconf, sizeof(struct ng_checksum_config));
255
256			if (priv->conf)
257				free(priv->conf, M_NETGRAPH);
258
259			priv->conf = newconf;
260
261			break;
262
263		case NGM_CHECKSUM_GET_STATS:
264		case NGM_CHECKSUM_CLR_STATS:
265		case NGM_CHECKSUM_GETCLR_STATS:
266			if (msg->header.cmd != NGM_CHECKSUM_CLR_STATS) {
267				NG_MKRESPONSE(resp, msg, sizeof(struct ng_checksum_stats), M_WAITOK);
268
269				if (resp == NULL)
270					ERROUT(ENOMEM);
271
272				bcopy(&(priv->stats), resp->data, sizeof(struct ng_checksum_stats));
273			}
274
275			if (msg->header.cmd != NGM_CHECKSUM_GET_STATS)
276				bzero(&(priv->stats), sizeof(struct ng_checksum_stats));
277
278			break;
279
280		default:
281			ERROUT(EINVAL);
282	}
283
284done:
285	NG_RESPOND_MSG(error, node, item, resp);
286	NG_FREE_MSG(msg);
287
288	return (error);
289}
290
291#define	PULLUP_CHECK(mbuf, length) do {					\
292	pullup_len += length;						\
293	if (((mbuf)->m_pkthdr.len < pullup_len) ||			\
294	    (pullup_len > MHLEN)) {					\
295		return (EINVAL);					\
296	}								\
297	if ((mbuf)->m_len < pullup_len &&				\
298	    (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) {	\
299		return (ENOBUFS);					\
300	}								\
301} while (0)
302
303#ifdef INET
304static int
305checksum_ipv4(priv_p priv, struct mbuf *m, int l3_offset)
306{
307	struct ip *ip4;
308	int pullup_len;
309	int hlen, plen;
310	int processed = 0;
311
312	pullup_len = l3_offset;
313
314	PULLUP_CHECK(m, sizeof(struct ip));
315	ip4 = (struct ip *) mtodo(m, l3_offset);
316
317	if (ip4->ip_v != IPVERSION)
318		return (EOPNOTSUPP);
319
320	hlen = ip4->ip_hl << 2;
321	plen = ntohs(ip4->ip_len);
322
323	if (hlen < sizeof(struct ip) || m->m_pkthdr.len < l3_offset + plen)
324		return (EINVAL);
325
326	if (m->m_pkthdr.csum_flags & CSUM_IP) {
327		ip4->ip_sum = 0;
328
329		if ((priv->conf->csum_offload & CSUM_IP) == 0) {
330			if (hlen == sizeof(struct ip))
331				ip4->ip_sum = in_cksum_hdr(ip4);
332			else
333				ip4->ip_sum = in_cksum_skip(m, l3_offset + hlen, l3_offset);
334
335			m->m_pkthdr.csum_flags &= ~CSUM_IP;
336		}
337
338		processed = 1;
339	}
340
341	pullup_len = l3_offset + hlen;
342
343	/* We can not calculate a checksum fragmented packets */
344	if (ip4->ip_off & htons(IP_MF|IP_OFFMASK)) {
345		m->m_pkthdr.csum_flags &= ~(CSUM_TCP|CSUM_UDP);
346		return (0);
347	}
348
349	switch (ip4->ip_p)
350	{
351		case IPPROTO_TCP:
352			if (m->m_pkthdr.csum_flags & CSUM_TCP) {
353				struct tcphdr *th;
354
355				PULLUP_CHECK(m, sizeof(struct tcphdr));
356				th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
357
358				th->th_sum = in_pseudo(ip4->ip_src.s_addr,
359				    ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
360
361				if ((priv->conf->csum_offload & CSUM_TCP) == 0) {
362					th->th_sum = in_cksum_skip(m, l3_offset + plen, l3_offset + hlen);
363					m->m_pkthdr.csum_flags &= ~CSUM_TCP;
364				}
365
366				processed = 1;
367			}
368
369			m->m_pkthdr.csum_flags &= ~CSUM_UDP;
370			break;
371
372		case IPPROTO_UDP:
373			if (m->m_pkthdr.csum_flags & CSUM_UDP) {
374				struct udphdr *uh;
375
376				PULLUP_CHECK(m, sizeof(struct udphdr));
377				uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
378
379				uh->uh_sum = in_pseudo(ip4->ip_src.s_addr,
380				    ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
381
382				if ((priv->conf->csum_offload & CSUM_UDP) == 0) {
383					uh->uh_sum = in_cksum_skip(m,
384					    l3_offset + plen, l3_offset + hlen);
385
386					if (uh->uh_sum == 0)
387						uh->uh_sum = 0xffff;
388
389					m->m_pkthdr.csum_flags &= ~CSUM_UDP;
390				}
391
392				processed = 1;
393			}
394
395			m->m_pkthdr.csum_flags &= ~CSUM_TCP;
396			break;
397
398		default:
399			m->m_pkthdr.csum_flags &= ~(CSUM_TCP|CSUM_UDP);
400			break;
401	}
402
403	m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV6;
404
405	if (processed)
406		priv->stats.processed++;
407
408	return (0);
409}
410#endif /* INET */
411
412#ifdef INET6
413static int
414checksum_ipv6(priv_p priv, struct mbuf *m, int l3_offset)
415{
416	struct ip6_hdr *ip6;
417	struct ip6_ext *ip6e = NULL;
418	int pullup_len;
419	int hlen, plen;
420	int nxt;
421	int processed = 0;
422
423	pullup_len = l3_offset;
424
425	PULLUP_CHECK(m, sizeof(struct ip6_hdr));
426	ip6 = (struct ip6_hdr *) mtodo(m, l3_offset);
427
428	if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
429		return (EOPNOTSUPP);
430
431	hlen = sizeof(struct ip6_hdr);
432	plen = ntohs(ip6->ip6_plen) + hlen;
433
434	if (m->m_pkthdr.len < l3_offset + plen)
435		return (EINVAL);
436
437	nxt = ip6->ip6_nxt;
438
439	for (;;) {
440		switch (nxt)
441		{
442			case IPPROTO_DSTOPTS:
443			case IPPROTO_HOPOPTS:
444			case IPPROTO_ROUTING:
445				PULLUP_CHECK(m, sizeof(struct ip6_ext));
446				ip6e = (struct ip6_ext *) mtodo(m, l3_offset + hlen);
447				nxt = ip6e->ip6e_nxt;
448				hlen += (ip6e->ip6e_len + 1) << 3;
449				pullup_len = l3_offset + hlen;
450				break;
451
452			case IPPROTO_AH:
453				PULLUP_CHECK(m, sizeof(struct ip6_ext));
454				ip6e = (struct ip6_ext *) mtodo(m, l3_offset + hlen);
455				nxt = ip6e->ip6e_nxt;
456				hlen += (ip6e->ip6e_len + 2) << 2;
457				pullup_len = l3_offset + hlen;
458				break;
459
460			case IPPROTO_FRAGMENT:
461				/* We can not calculate a checksum fragmented packets */
462				m->m_pkthdr.csum_flags &= ~(CSUM_TCP_IPV6|CSUM_UDP_IPV6);
463				return (0);
464
465			default:
466				goto loopend;
467		}
468
469		if (nxt == 0)
470			return (EINVAL);
471	}
472
473loopend:
474
475	switch (nxt)
476	{
477		case IPPROTO_TCP:
478			if (m->m_pkthdr.csum_flags & CSUM_TCP_IPV6) {
479				struct tcphdr *th;
480
481				PULLUP_CHECK(m, sizeof(struct tcphdr));
482				th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
483
484				th->th_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
485
486				if ((priv->conf->csum_offload & CSUM_TCP_IPV6) == 0) {
487					th->th_sum = in_cksum_skip(m, l3_offset + plen, l3_offset + hlen);
488					m->m_pkthdr.csum_flags &= ~CSUM_TCP_IPV6;
489				}
490
491				processed = 1;
492			}
493
494			m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
495			break;
496
497		case IPPROTO_UDP:
498			if (m->m_pkthdr.csum_flags & CSUM_UDP_IPV6) {
499				struct udphdr *uh;
500
501				PULLUP_CHECK(m, sizeof(struct udphdr));
502				uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
503
504				uh->uh_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
505
506				if ((priv->conf->csum_offload & CSUM_UDP_IPV6) == 0) {
507					uh->uh_sum = in_cksum_skip(m,
508					    l3_offset + plen, l3_offset + hlen);
509
510					if (uh->uh_sum == 0)
511						uh->uh_sum = 0xffff;
512
513					m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
514				}
515
516				processed = 1;
517			}
518
519			m->m_pkthdr.csum_flags &= ~CSUM_TCP_IPV6;
520			break;
521
522		default:
523			m->m_pkthdr.csum_flags &= ~(CSUM_TCP_IPV6|CSUM_UDP_IPV6);
524			break;
525	}
526
527	m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV4;
528
529	if (processed)
530		priv->stats.processed++;
531
532	return (0);
533}
534#endif /* INET6 */
535
536#undef	PULLUP_CHECK
537
538static int
539ng_checksum_rcvdata(hook_p hook, item_p item)
540{
541	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
542	struct mbuf *m;
543	hook_p out;
544	int error = 0;
545
546	priv->stats.received++;
547
548	NGI_GET_M(item, m);
549
550#define	PULLUP_CHECK(mbuf, length) do {					\
551	pullup_len += length;						\
552	if (((mbuf)->m_pkthdr.len < pullup_len) ||			\
553	    (pullup_len > MHLEN)) {					\
554		error = EINVAL;						\
555		goto bypass;						\
556	}								\
557	if ((mbuf)->m_len < pullup_len &&				\
558	    (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) {	\
559		error = ENOBUFS;					\
560		goto drop;						\
561	}								\
562} while (0)
563
564	if (!(priv->conf && hook == priv->in && m && (m->m_flags & M_PKTHDR)))
565		goto bypass;
566
567	m->m_pkthdr.csum_flags |= priv->conf->csum_flags;
568
569	if (m->m_pkthdr.csum_flags & (NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6))
570	{
571		struct ether_header *eh;
572		struct ng_checksum_vlan_header *vh;
573		int pullup_len = 0;
574		uint16_t etype;
575
576		m = m_unshare(m, M_NOWAIT);
577
578		if (m == NULL)
579			ERROUT(ENOMEM);
580
581		switch (priv->dlt)
582		{
583			case DLT_EN10MB:
584				PULLUP_CHECK(m, sizeof(struct ether_header));
585				eh = mtod(m, struct ether_header *);
586				etype = ntohs(eh->ether_type);
587
588				for (;;) {	/* QinQ support */
589					switch (etype)
590					{
591						case 0x8100:
592						case 0x88A8:
593						case 0x9100:
594							PULLUP_CHECK(m, sizeof(struct ng_checksum_vlan_header));
595							vh = (struct ng_checksum_vlan_header *) mtodo(m,
596							    pullup_len - sizeof(struct ng_checksum_vlan_header));
597							etype = ntohs(vh->etype);
598							break;
599
600						default:
601							goto loopend;
602					}
603				}
604loopend:
605#ifdef INET
606				if (etype == ETHERTYPE_IP &&
607				    (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV4)) {
608					error = checksum_ipv4(priv, m, pullup_len);
609					if (error == ENOBUFS)
610						goto drop;
611				} else
612#endif
613#ifdef INET6
614				if (etype == ETHERTYPE_IPV6 &&
615				    (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV6)) {
616					error = checksum_ipv6(priv, m, pullup_len);
617					if (error == ENOBUFS)
618						goto drop;
619				} else
620#endif
621				{
622					m->m_pkthdr.csum_flags &=
623					    ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
624				}
625
626				break;
627
628			case DLT_RAW:
629#ifdef INET
630				if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV4)
631				{
632					error = checksum_ipv4(priv, m, pullup_len);
633
634					if (error == 0)
635						goto bypass;
636					else if (error == ENOBUFS)
637						goto drop;
638				}
639#endif
640#ifdef INET6
641				if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV6)
642				{
643					error = checksum_ipv6(priv, m, pullup_len);
644
645					if (error == 0)
646						goto bypass;
647					else if (error == ENOBUFS)
648						goto drop;
649				}
650#endif
651				if (error)
652					m->m_pkthdr.csum_flags &=
653					    ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
654
655				break;
656
657			default:
658				ERROUT(EINVAL);
659		}
660	}
661
662#undef	PULLUP_CHECK
663
664bypass:
665	out = NULL;
666
667	if (hook == priv->in) {
668		/* return frames on 'in' hook if 'out' not connected */
669		out = priv->out ? priv->out : priv->in;
670	} else if (hook == priv->out && priv->in) {
671		/* pass frames on 'out' hook if 'in' connected */
672		out = priv->in;
673	}
674
675	if (out == NULL)
676		ERROUT(0);
677
678	NG_FWD_NEW_DATA(error, item, out, m);
679
680	return (error);
681
682done:
683	NG_FREE_M(m);
684drop:
685	NG_FREE_ITEM(item);
686
687	priv->stats.dropped++;
688
689	return (error);
690}
691
692static int
693ng_checksum_shutdown(node_p node)
694{
695	const priv_p priv = NG_NODE_PRIVATE(node);
696
697	NG_NODE_SET_PRIVATE(node, NULL);
698	NG_NODE_UNREF(node);
699
700	if (priv->conf)
701		free(priv->conf, M_NETGRAPH);
702
703	free(priv, M_NETGRAPH);
704
705	return (0);
706}
707
708static int
709ng_checksum_disconnect(hook_p hook)
710{
711	priv_p priv;
712
713	priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
714
715	if (hook == priv->in)
716		priv->in = NULL;
717
718	if (hook == priv->out)
719		priv->out = NULL;
720
721	if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 &&
722	    NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) /* already shutting down? */
723		ng_rmnode_self(NG_HOOK_NODE(hook));
724
725	return (0);
726}
727