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