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