2 * Copyright (c) 2015 Dmitry Vagin <daemon.hammer@ya.ru>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
32 #include "opt_inet6.h"
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>
40 #include <sys/socket.h>
43 #include <net/ethernet.h>
45 #include <net/if_vlan_var.h>
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>
54 #include <netgraph/ng_message.h>
55 #include <netgraph/ng_parse.h>
56 #include <netgraph/netgraph.h>
58 #include <netgraph/ng_checksum.h>
61 struct ng_checksum_priv {
64 uint8_t dlt; /* DLT_XXX from bpf.h */
65 struct ng_checksum_config *conf;
66 struct ng_checksum_stats stats;
69 typedef struct ng_checksum_priv *priv_p;
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;
79 #define ERROUT(x) { error = (x); goto done; }
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
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
95 static const struct ng_cmdlist ng_checksum_cmdlist[] = {
107 &ng_parse_uint8_type,
112 NGM_CHECKSUM_GETCONFIG,
115 &ng_checksum_config_type
119 NGM_CHECKSUM_SETCONFIG,
121 &ng_checksum_config_type,
126 NGM_CHECKSUM_GET_STATS,
129 &ng_checksum_stats_type
133 NGM_CHECKSUM_CLR_STATS,
140 NGM_CHECKSUM_GETCLR_STATS,
143 &ng_checksum_stats_type
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,
160 NETGRAPH_INIT(checksum, &typestruct);
163 ng_checksum_constructor(node_p node)
167 priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK|M_ZERO);
170 NG_NODE_SET_PRIVATE(node, priv);
176 ng_checksum_newhook(node_p node, hook_p hook, const char *name)
178 const priv_p priv = NG_NODE_PRIVATE(node);
180 if (strncmp(name, NG_CHECKSUM_HOOK_IN, strlen(NG_CHECKSUM_HOOK_IN)) == 0) {
182 } else if (strncmp(name, NG_CHECKSUM_HOOK_OUT, strlen(NG_CHECKSUM_HOOK_OUT)) == 0) {
191 ng_checksum_rcvmsg(node_p node, item_p item, hook_p lasthook)
193 const priv_p priv = NG_NODE_PRIVATE(node);
194 struct ng_checksum_config *conf, *newconf;
196 struct ng_mesg *resp = NULL;
199 NGI_GET_MSG(item, msg);
201 if (msg->header.typecookie != NGM_CHECKSUM_COOKIE)
204 switch (msg->header.cmd)
206 case NGM_CHECKSUM_GETDLT:
207 NG_MKRESPONSE(resp, msg, sizeof(uint8_t), M_WAITOK);
212 *((uint8_t *) resp->data) = priv->dlt;
216 case NGM_CHECKSUM_SETDLT:
217 if (msg->header.arglen != sizeof(uint8_t))
220 switch (*(uint8_t *) msg->data)
224 priv->dlt = *(uint8_t *) msg->data;
233 case NGM_CHECKSUM_GETCONFIG:
234 if (priv->conf == NULL)
237 NG_MKRESPONSE(resp, msg, sizeof(struct ng_checksum_config), M_WAITOK);
242 bcopy(priv->conf, resp->data, sizeof(struct ng_checksum_config));
246 case NGM_CHECKSUM_SETCONFIG:
247 conf = (struct ng_checksum_config *) msg->data;
249 if (msg->header.arglen != sizeof(struct ng_checksum_config))
252 conf->csum_flags &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
253 conf->csum_offload &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
255 newconf = malloc(sizeof(struct ng_checksum_config), M_NETGRAPH, M_WAITOK|M_ZERO);
257 bcopy(conf, newconf, sizeof(struct ng_checksum_config));
260 free(priv->conf, M_NETGRAPH);
262 priv->conf = newconf;
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);
275 bcopy(&(priv->stats), resp->data, sizeof(struct ng_checksum_stats));
278 if (msg->header.cmd != NGM_CHECKSUM_GET_STATS)
279 bzero(&(priv->stats), sizeof(struct ng_checksum_stats));
288 NG_RESPOND_MSG(error, node, item, resp);
294 #define PULLUP_CHECK(mbuf, length) do { \
295 pullup_len += length; \
296 if (((mbuf)->m_pkthdr.len < pullup_len) || \
297 (pullup_len > MHLEN)) { \
300 if ((mbuf)->m_len < pullup_len && \
301 (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) { \
308 checksum_ipv4(priv_p priv, struct mbuf *m, int l3_offset)
315 pullup_len = l3_offset;
317 PULLUP_CHECK(m, sizeof(struct ip));
318 ip4 = (struct ip *) mtodo(m, l3_offset);
320 if (ip4->ip_v != IPVERSION)
323 hlen = ip4->ip_hl << 2;
324 plen = ntohs(ip4->ip_len);
326 if (hlen < sizeof(struct ip) || m->m_pkthdr.len < l3_offset + plen)
329 if (m->m_pkthdr.csum_flags & CSUM_IP) {
332 if ((priv->conf->csum_offload & CSUM_IP) == 0) {
333 if (hlen == sizeof(struct ip))
334 ip4->ip_sum = in_cksum_hdr(ip4);
336 ip4->ip_sum = in_cksum_skip(m, l3_offset + hlen, l3_offset);
338 m->m_pkthdr.csum_flags &= ~CSUM_IP;
344 pullup_len = l3_offset + hlen;
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);
355 if (m->m_pkthdr.csum_flags & CSUM_TCP) {
358 PULLUP_CHECK(m, sizeof(struct tcphdr));
359 th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
361 th->th_sum = in_pseudo(ip4->ip_src.s_addr,
362 ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
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;
372 m->m_pkthdr.csum_flags &= ~CSUM_UDP;
376 if (m->m_pkthdr.csum_flags & CSUM_UDP) {
379 PULLUP_CHECK(m, sizeof(struct udphdr));
380 uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
382 uh->uh_sum = in_pseudo(ip4->ip_src.s_addr,
383 ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
385 if ((priv->conf->csum_offload & CSUM_UDP) == 0) {
386 uh->uh_sum = in_cksum_skip(m,
387 l3_offset + plen, l3_offset + hlen);
392 m->m_pkthdr.csum_flags &= ~CSUM_UDP;
398 m->m_pkthdr.csum_flags &= ~CSUM_TCP;
402 m->m_pkthdr.csum_flags &= ~(CSUM_TCP|CSUM_UDP);
406 m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV6;
409 priv->stats.processed++;
417 checksum_ipv6(priv_p priv, struct mbuf *m, int l3_offset)
420 struct ip6_ext *ip6e = NULL;
426 pullup_len = l3_offset;
428 PULLUP_CHECK(m, sizeof(struct ip6_hdr));
429 ip6 = (struct ip6_hdr *) mtodo(m, l3_offset);
431 if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
434 hlen = sizeof(struct ip6_hdr);
435 plen = ntohs(ip6->ip6_plen) + hlen;
437 if (m->m_pkthdr.len < l3_offset + plen)
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;
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;
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);
481 if (m->m_pkthdr.csum_flags & CSUM_TCP_IPV6) {
484 PULLUP_CHECK(m, sizeof(struct tcphdr));
485 th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
487 th->th_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
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;
497 m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
501 if (m->m_pkthdr.csum_flags & CSUM_UDP_IPV6) {
504 PULLUP_CHECK(m, sizeof(struct udphdr));
505 uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
507 uh->uh_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
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);
516 m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
522 m->m_pkthdr.csum_flags &= ~CSUM_TCP_IPV6;
526 m->m_pkthdr.csum_flags &= ~(CSUM_TCP_IPV6|CSUM_UDP_IPV6);
530 m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV4;
533 priv->stats.processed++;
542 ng_checksum_rcvdata(hook_p hook, item_p item)
544 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
549 priv->stats.received++;
553 #define PULLUP_CHECK(mbuf, length) do { \
554 pullup_len += length; \
555 if (((mbuf)->m_pkthdr.len < pullup_len) || \
556 (pullup_len > MHLEN)) { \
560 if ((mbuf)->m_len < pullup_len && \
561 (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) { \
567 if (!(priv->conf && hook == priv->in && m && (m->m_flags & M_PKTHDR)))
570 m->m_pkthdr.csum_flags |= priv->conf->csum_flags;
572 if (m->m_pkthdr.csum_flags & (NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6))
574 struct ether_header *eh;
575 struct ng_checksum_vlan_header *vh;
579 m = m_unshare(m, M_NOWAIT);
587 PULLUP_CHECK(m, sizeof(struct ether_header));
588 eh = mtod(m, struct ether_header *);
589 etype = ntohs(eh->ether_type);
591 for (;;) { /* QinQ support */
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);
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)
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)
625 m->m_pkthdr.csum_flags &=
626 ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
633 if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV4)
635 error = checksum_ipv4(priv, m, pullup_len);
639 else if (error == ENOBUFS)
644 if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV6)
646 error = checksum_ipv6(priv, m, pullup_len);
650 else if (error == ENOBUFS)
655 m->m_pkthdr.csum_flags &=
656 ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
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 */
681 NG_FWD_NEW_DATA(error, item, out, m);
690 priv->stats.dropped++;
696 ng_checksum_shutdown(node_p node)
698 const priv_p priv = NG_NODE_PRIVATE(node);
700 NG_NODE_SET_PRIVATE(node, NULL);
704 free(priv->conf, M_NETGRAPH);
706 free(priv, M_NETGRAPH);
712 ng_checksum_disconnect(hook_p hook)
716 priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
718 if (hook == priv->in)
721 if (hook == priv->out)
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));