xref: /netbsd-src/sys/arch/xen/xen/xennet_checksum.c (revision 8a3c1b5cf97bf2b14e8b21665019b2190a5ec35d)
1 /*	$NetBSD: xennet_checksum.c,v 1.14 2020/05/04 08:22:45 jdolecek Exp $	*/
2 
3 /*-
4  * Copyright (c)2006 YAMAMOTO Takashi,
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: xennet_checksum.c,v 1.14 2020/05/04 08:22:45 jdolecek Exp $");
31 
32 #ifdef _KERNEL_OPT
33 #include "opt_inet.h"
34 #endif
35 
36 #include <sys/types.h>
37 #include <sys/param.h>
38 
39 #include <net/if.h>
40 #include <net/if_dl.h>
41 #include <net/if_ether.h>
42 #include <net/if_vlanvar.h>
43 
44 #include <netinet/in.h>
45 #include <netinet/in_systm.h>
46 #include <netinet/in_offload.h>
47 #include <netinet/ip.h>
48 #include <netinet/tcp.h>
49 #include <netinet/udp.h>
50 #include <netinet/ip6.h>
51 #include <netinet6/in6_offload.h>
52 
53 #include <xen/xennet_checksum.h>
54 
55 #ifdef XENNET_DEBUG
56 /* ratecheck(9) for checksum validation failures */
57 static const struct timeval xn_cksum_errintvl = { 600, 0 };  /* 10 min, each */
58 #endif
59 
60 static void *
m_extract(struct mbuf * m,int off,int len)61 m_extract(struct mbuf *m, int off, int len)
62 {
63 	if (m->m_len >= off + len)
64 		return mtod(m, char *) + off;
65 	else
66 		return NULL;
67 }
68 
69 /*
70  * xennet_checksum_fill: fill TCP/UDP checksum, or arrange
71  * for hw offload to do it
72  */
73 int
xennet_checksum_fill(struct ifnet * ifp,struct mbuf * m,struct evcnt * cksum_blank,struct evcnt * cksum_undefer)74 xennet_checksum_fill(struct ifnet *ifp, struct mbuf *m,
75     struct evcnt *cksum_blank, struct evcnt *cksum_undefer)
76 {
77 	const struct ether_header *eh;
78 	struct ip *iph = NULL;
79 #ifdef INET6
80 	struct ip6_hdr *ip6h = NULL;
81 #endif
82 	int ehlen;
83 	int iphlen;
84 	int iplen;
85 	uint16_t etype;
86 	uint8_t nxt;
87 	int error = 0;
88 	int sw_csum;
89 
90 	KASSERT(!M_READONLY(m));
91 	KASSERT((m->m_flags & M_PKTHDR) != 0);
92 
93 	eh = m_extract(m, 0, sizeof(*eh));
94 	if (eh == NULL) {
95 		/* Too short, packet will be dropped by upper layer */
96 		return EINVAL;
97 	}
98 	etype = eh->ether_type;
99 	ehlen = ETHER_HDR_LEN;
100 	if (__predict_false(etype == htons(ETHERTYPE_VLAN))) {
101 		struct ether_vlan_header *evl = m_extract(m, 0, sizeof(*evl));
102 		if (evl == NULL) {
103 			/* Too short, packet will be dropped by upper layer */
104 			return EINVAL;
105 		}
106 		ehlen += ETHER_VLAN_ENCAP_LEN;
107 		etype = ntohs(evl->evl_proto);
108 	}
109 
110 	switch (etype) {
111 	case htons(ETHERTYPE_IP):
112 		iph = m_extract(m, ehlen, sizeof(*iph));
113 		if (iph == NULL) {
114 			/* Too short, packet will be dropped by upper layer */
115 			return EINVAL;
116 		}
117 		nxt = iph->ip_p;
118 		iphlen = iph->ip_hl << 2;
119 		iplen = ntohs(iph->ip_len);
120 		break;
121 #ifdef INET6
122 	case htons(ETHERTYPE_IPV6):
123 		ip6h = m_extract(m, ehlen, sizeof(*ip6h));
124 		if (ip6h == NULL) {
125 			/* Too short, packet will be dropped by upper layer */
126 			return EINVAL;
127 		}
128 		if ((ip6h->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) {
129 			/* Bad version */
130 			return EINVAL;
131 		}
132 		nxt = ip6h->ip6_nxt;
133 		iphlen = sizeof(*ip6h);
134 		iplen = ntohs(ip6h->ip6_plen);
135 		break;
136 #endif
137 	default:
138 		/* Not supported ethernet type */
139 		return EOPNOTSUPP;
140 	}
141 
142 	if (ehlen + iplen > m->m_pkthdr.len) {
143 		/* Too short, packet will be dropped by upper layer */
144 		return EINVAL;
145 	}
146 
147 	switch (nxt) {
148 	case IPPROTO_UDP:
149 		if (iph)
150 			m->m_pkthdr.csum_flags = M_CSUM_UDPv4;
151 #ifdef INET6
152 		else
153 			m->m_pkthdr.csum_flags = M_CSUM_UDPv6;
154 #endif
155 		m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum);
156 		m->m_pkthdr.csum_data |= iphlen << 16;
157 		break;
158 	case IPPROTO_TCP:
159 		if (iph)
160 			m->m_pkthdr.csum_flags = M_CSUM_TCPv4;
161 #ifdef INET6
162 		else
163 			m->m_pkthdr.csum_flags = M_CSUM_TCPv6;
164 #endif
165 		m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
166 		m->m_pkthdr.csum_data |= iphlen << 16;
167 		break;
168 	case IPPROTO_ICMP:
169 	case IPPROTO_IGMP:
170 	case IPPROTO_HOPOPTS:
171 	case IPPROTO_ICMPV6:
172 	case IPPROTO_FRAGMENT:
173 		/* nothing to do */
174 		error = 0;
175 		goto out;
176 		/* NOTREACHED */
177 	default:
178 	    {
179 #ifdef XENNET_DEBUG
180 		static struct timeval lasttime;
181 		if (ratecheck(&lasttime, &xn_cksum_errintvl))
182 			printf("%s: unknown proto %d passed no checksum\n",
183 			    ifp->if_xname, nxt);
184 #endif /* XENNET_DEBUG */
185 		error = EOPNOTSUPP;
186 		goto out;
187 	    }
188 	}
189 
190 	/*
191 	 * Only compute the checksum if impossible to defer.
192 	 */
193 	sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_rx;
194 
195 	if (sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4)) {
196 		in_undefer_cksum(m, ehlen,
197 		    sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4));
198 	}
199 
200 #ifdef INET6
201 	if (sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) {
202 		in6_undefer_cksum(m, ehlen,
203 		    sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6));
204 	}
205 #endif
206 
207 	if (m->m_pkthdr.csum_flags != 0) {
208 		if (sw_csum)
209 			cksum_undefer->ev_count++;
210 		cksum_blank->ev_count++;
211 #ifdef M_CSUM_BLANK
212 		m->m_pkthdr.csum_flags |= M_CSUM_BLANK;
213 #endif
214 	} else {
215 		cksum_undefer->ev_count++;
216 	}
217 
218     out:
219 	return error;
220 }
221