xref: /freebsd/sys/netgraph/ng_checksum.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
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 */
59 struct 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 
67 typedef struct ng_checksum_priv *priv_p;
68 
69 /* Netgraph methods */
70 static ng_constructor_t	ng_checksum_constructor;
71 static ng_rcvmsg_t	ng_checksum_rcvmsg;
72 static ng_shutdown_t	ng_checksum_shutdown;
73 static ng_newhook_t	ng_checksum_newhook;
74 static ng_rcvdata_t	ng_checksum_rcvdata;
75 static ng_disconnect_t	ng_checksum_disconnect;
76 #define ERROUT(x) { error = (x); goto done; }
77 
78 static const struct ng_parse_struct_field ng_checksum_config_type_fields[]
79 	= NG_CHECKSUM_CONFIG_TYPE;
80 static const struct ng_parse_type ng_checksum_config_type = {
81 	&ng_parse_struct_type,
82 	&ng_checksum_config_type_fields
83 };
84 
85 static const struct ng_parse_struct_field ng_checksum_stats_fields[]
86 	= NG_CHECKSUM_STATS_TYPE;
87 static const struct ng_parse_type ng_checksum_stats_type = {
88 	&ng_parse_struct_type,
89 	&ng_checksum_stats_fields
90 };
91 
92 static 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 
145 static 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 
157 NETGRAPH_INIT(checksum, &typestruct);
158 
159 static int
160 ng_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 
172 static int
173 ng_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 
187 static int
188 ng_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 
284 done:
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
304 static int
305 checksum_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
413 static int
414 checksum_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 
473 loopend:
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 
538 static int
539 ng_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 				}
604 loopend:
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 
664 bypass:
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 
682 done:
683 	NG_FREE_M(m);
684 drop:
685 	NG_FREE_ITEM(item);
686 
687 	priv->stats.dropped++;
688 
689 	return (error);
690 }
691 
692 static int
693 ng_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 
708 static int
709 ng_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