xref: /freebsd/sys/netgraph/ng_checksum.c (revision 8c784bb8cf36911b828652f0bf7e88f443abec50)
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 */
61 struct 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 
69 typedef struct ng_checksum_priv *priv_p;
70 
71 /* Netgraph methods */
72 static ng_constructor_t	ng_checksum_constructor;
73 static ng_rcvmsg_t	ng_checksum_rcvmsg;
74 static ng_shutdown_t	ng_checksum_shutdown;
75 static ng_newhook_t	ng_checksum_newhook;
76 static ng_rcvdata_t	ng_checksum_rcvdata;
77 static ng_disconnect_t	ng_checksum_disconnect;
78 #define ERROUT(x) { error = (x); goto done; }
79 
80 static const struct ng_parse_struct_field ng_checksum_config_type_fields[]
81 	= NG_CHECKSUM_CONFIG_TYPE;
82 static const struct ng_parse_type ng_checksum_config_type = {
83 	&ng_parse_struct_type,
84 	&ng_checksum_config_type_fields
85 };
86 
87 static const struct ng_parse_struct_field ng_checksum_stats_fields[]
88 	= NG_CHECKSUM_STATS_TYPE;
89 static const struct ng_parse_type ng_checksum_stats_type = {
90 	&ng_parse_struct_type,
91 	&ng_checksum_stats_fields
92 };
93 
94 static 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 
147 static 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 
159 NETGRAPH_INIT(checksum, &typestruct);
160 
161 static int
162 ng_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 
174 static int
175 ng_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 
189 static int
190 ng_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 
286 done:
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
306 static int
307 checksum_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
415 static int
416 checksum_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 
475 loopend:
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 
540 static int
541 ng_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 				}
606 loopend:
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 
666 bypass:
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 
684 done:
685 	NG_FREE_M(m);
686 drop:
687 	NG_FREE_ITEM(item);
688 
689 	priv->stats.dropped++;
690 
691 	return (error);
692 }
693 
694 static int
695 ng_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 
710 static int
711 ng_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