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;
78 #define ERROUT(x) { error = (x); goto done; }
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
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
94 static const struct ng_cmdlist ng_checksum_cmdlist[] = {
106 &ng_parse_uint8_type,
111 NGM_CHECKSUM_GETCONFIG,
114 &ng_checksum_config_type
118 NGM_CHECKSUM_SETCONFIG,
120 &ng_checksum_config_type,
125 NGM_CHECKSUM_GET_STATS,
128 &ng_checksum_stats_type
132 NGM_CHECKSUM_CLR_STATS,
139 NGM_CHECKSUM_GETCLR_STATS,
142 &ng_checksum_stats_type
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,
159 NETGRAPH_INIT(checksum, &typestruct);
162 ng_checksum_constructor(node_p node)
166 priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK|M_ZERO);
169 NG_NODE_SET_PRIVATE(node, priv);
175 ng_checksum_newhook(node_p node, hook_p hook, const char *name)
177 const priv_p priv = NG_NODE_PRIVATE(node);
179 if (strncmp(name, NG_CHECKSUM_HOOK_IN, strlen(NG_CHECKSUM_HOOK_IN)) == 0) {
181 } else if (strncmp(name, NG_CHECKSUM_HOOK_OUT, strlen(NG_CHECKSUM_HOOK_OUT)) == 0) {
190 ng_checksum_rcvmsg(node_p node, item_p item, hook_p lasthook)
192 const priv_p priv = NG_NODE_PRIVATE(node);
193 struct ng_checksum_config *conf, *newconf;
195 struct ng_mesg *resp = NULL;
198 NGI_GET_MSG(item, msg);
200 if (msg->header.typecookie != NGM_CHECKSUM_COOKIE)
203 switch (msg->header.cmd)
205 case NGM_CHECKSUM_GETDLT:
206 NG_MKRESPONSE(resp, msg, sizeof(uint8_t), M_WAITOK);
211 *((uint8_t *) resp->data) = priv->dlt;
215 case NGM_CHECKSUM_SETDLT:
216 if (msg->header.arglen != sizeof(uint8_t))
219 switch (*(uint8_t *) msg->data)
223 priv->dlt = *(uint8_t *) msg->data;
232 case NGM_CHECKSUM_GETCONFIG:
233 if (priv->conf == NULL)
236 NG_MKRESPONSE(resp, msg, sizeof(struct ng_checksum_config), M_WAITOK);
241 bcopy(priv->conf, resp->data, sizeof(struct ng_checksum_config));
245 case NGM_CHECKSUM_SETCONFIG:
246 conf = (struct ng_checksum_config *) msg->data;
248 if (msg->header.arglen != sizeof(struct ng_checksum_config))
251 conf->csum_flags &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
252 conf->csum_offload &= NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6;
254 newconf = malloc(sizeof(struct ng_checksum_config), M_NETGRAPH, M_WAITOK|M_ZERO);
256 bcopy(conf, newconf, sizeof(struct ng_checksum_config));
259 free(priv->conf, M_NETGRAPH);
261 priv->conf = newconf;
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);
274 bcopy(&(priv->stats), resp->data, sizeof(struct ng_checksum_stats));
277 if (msg->header.cmd != NGM_CHECKSUM_GET_STATS)
278 bzero(&(priv->stats), sizeof(struct ng_checksum_stats));
287 NG_RESPOND_MSG(error, node, item, resp);
293 #define PULLUP_CHECK(mbuf, length) do { \
294 pullup_len += length; \
295 if (((mbuf)->m_pkthdr.len < pullup_len) || \
296 (pullup_len > MHLEN)) { \
299 if ((mbuf)->m_len < pullup_len && \
300 (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) { \
307 checksum_ipv4(priv_p priv, struct mbuf *m, int l3_offset)
314 pullup_len = l3_offset;
316 PULLUP_CHECK(m, sizeof(struct ip));
317 ip4 = (struct ip *) mtodo(m, l3_offset);
319 if (ip4->ip_v != IPVERSION)
322 hlen = ip4->ip_hl << 2;
323 plen = ntohs(ip4->ip_len);
325 if (hlen < sizeof(struct ip) || m->m_pkthdr.len < l3_offset + plen)
328 if (m->m_pkthdr.csum_flags & CSUM_IP) {
331 if ((priv->conf->csum_offload & CSUM_IP) == 0) {
332 if (hlen == sizeof(struct ip))
333 ip4->ip_sum = in_cksum_hdr(ip4);
335 ip4->ip_sum = in_cksum_skip(m, l3_offset + hlen, l3_offset);
337 m->m_pkthdr.csum_flags &= ~CSUM_IP;
343 pullup_len = l3_offset + hlen;
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);
354 if (m->m_pkthdr.csum_flags & CSUM_TCP) {
357 PULLUP_CHECK(m, sizeof(struct tcphdr));
358 th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
360 th->th_sum = in_pseudo(ip4->ip_src.s_addr,
361 ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
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;
371 m->m_pkthdr.csum_flags &= ~CSUM_UDP;
375 if (m->m_pkthdr.csum_flags & CSUM_UDP) {
378 PULLUP_CHECK(m, sizeof(struct udphdr));
379 uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
381 uh->uh_sum = in_pseudo(ip4->ip_src.s_addr,
382 ip4->ip_dst.s_addr, htons(ip4->ip_p + plen - hlen));
384 if ((priv->conf->csum_offload & CSUM_UDP) == 0) {
385 uh->uh_sum = in_cksum_skip(m,
386 l3_offset + plen, l3_offset + hlen);
391 m->m_pkthdr.csum_flags &= ~CSUM_UDP;
397 m->m_pkthdr.csum_flags &= ~CSUM_TCP;
401 m->m_pkthdr.csum_flags &= ~(CSUM_TCP|CSUM_UDP);
405 m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV6;
408 priv->stats.processed++;
416 checksum_ipv6(priv_p priv, struct mbuf *m, int l3_offset)
419 struct ip6_ext *ip6e = NULL;
425 pullup_len = l3_offset;
427 PULLUP_CHECK(m, sizeof(struct ip6_hdr));
428 ip6 = (struct ip6_hdr *) mtodo(m, l3_offset);
430 if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
433 hlen = sizeof(struct ip6_hdr);
434 plen = ntohs(ip6->ip6_plen) + hlen;
436 if (m->m_pkthdr.len < l3_offset + plen)
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;
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;
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);
480 if (m->m_pkthdr.csum_flags & CSUM_TCP_IPV6) {
483 PULLUP_CHECK(m, sizeof(struct tcphdr));
484 th = (struct tcphdr *) mtodo(m, l3_offset + hlen);
486 th->th_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
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;
496 m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
500 if (m->m_pkthdr.csum_flags & CSUM_UDP_IPV6) {
503 PULLUP_CHECK(m, sizeof(struct udphdr));
504 uh = (struct udphdr *) mtodo(m, l3_offset + hlen);
506 uh->uh_sum = in6_cksum_pseudo(ip6, plen - hlen, nxt, 0);
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);
515 m->m_pkthdr.csum_flags &= ~CSUM_UDP_IPV6;
521 m->m_pkthdr.csum_flags &= ~CSUM_TCP_IPV6;
525 m->m_pkthdr.csum_flags &= ~(CSUM_TCP_IPV6|CSUM_UDP_IPV6);
529 m->m_pkthdr.csum_flags &= ~NG_CHECKSUM_CSUM_IPV4;
532 priv->stats.processed++;
541 ng_checksum_rcvdata(hook_p hook, item_p item)
543 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
548 priv->stats.received++;
552 #define PULLUP_CHECK(mbuf, length) do { \
553 pullup_len += length; \
554 if (((mbuf)->m_pkthdr.len < pullup_len) || \
555 (pullup_len > MHLEN)) { \
559 if ((mbuf)->m_len < pullup_len && \
560 (((mbuf) = m_pullup((mbuf), pullup_len)) == NULL)) { \
566 if (!(priv->conf && hook == priv->in && m && (m->m_flags & M_PKTHDR)))
569 m->m_pkthdr.csum_flags |= priv->conf->csum_flags;
571 if (m->m_pkthdr.csum_flags & (NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6))
573 struct ether_header *eh;
574 struct ng_checksum_vlan_header *vh;
578 m = m_unshare(m, M_NOWAIT);
586 PULLUP_CHECK(m, sizeof(struct ether_header));
587 eh = mtod(m, struct ether_header *);
588 etype = ntohs(eh->ether_type);
590 for (;;) { /* QinQ support */
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);
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)
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)
624 m->m_pkthdr.csum_flags &=
625 ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
632 if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV4)
634 error = checksum_ipv4(priv, m, pullup_len);
638 else if (error == ENOBUFS)
643 if (m->m_pkthdr.csum_flags & NG_CHECKSUM_CSUM_IPV6)
645 error = checksum_ipv6(priv, m, pullup_len);
649 else if (error == ENOBUFS)
654 m->m_pkthdr.csum_flags &=
655 ~(NG_CHECKSUM_CSUM_IPV4|NG_CHECKSUM_CSUM_IPV6);
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 */
680 NG_FWD_NEW_DATA(error, item, out, m);
689 priv->stats.dropped++;
695 ng_checksum_shutdown(node_p node)
697 const priv_p priv = NG_NODE_PRIVATE(node);
699 NG_NODE_SET_PRIVATE(node, NULL);
703 free(priv->conf, M_NETGRAPH);
705 free(priv, M_NETGRAPH);
711 ng_checksum_disconnect(hook_p hook)
715 priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
717 if (hook == priv->in)
720 if (hook == priv->out)
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));