xref: /freebsd/sys/netgraph/ng_checksum.c (revision 3416500aef140042c64bc149cb1ec6620483bc44)
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 
79 #define ERROUT(x) { error = (x); goto done; }
80 
81 static const struct ng_parse_struct_field ng_checksum_config_type_fields[]
82 	= NG_CHECKSUM_CONFIG_TYPE;
83 static const struct ng_parse_type ng_checksum_config_type = {
84 	&ng_parse_struct_type,
85 	&ng_checksum_config_type_fields
86 };
87 
88 static const struct ng_parse_struct_field ng_checksum_stats_fields[]
89 	= NG_CHECKSUM_STATS_TYPE;
90 static const struct ng_parse_type ng_checksum_stats_type = {
91 	&ng_parse_struct_type,
92 	&ng_checksum_stats_fields
93 };
94 
95 static 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 
148 static 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 
160 NETGRAPH_INIT(checksum, &typestruct);
161 
162 static int
163 ng_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 
175 static int
176 ng_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 
190 static int
191 ng_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 
287 done:
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
307 static int
308 checksum_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
416 static int
417 checksum_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 
476 loopend:
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 
541 static int
542 ng_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 				}
607 loopend:
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 
667 bypass:
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 
685 done:
686 drop:
687 	NG_FREE_ITEM(item);
688 	NG_FREE_M(m);
689 
690 	priv->stats.dropped++;
691 
692 	return (error);
693 }
694 
695 static int
696 ng_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 
711 static int
712 ng_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