xref: /netbsd-src/sys/net/ether_sw_offload.c (revision f3cfa6f6ce31685c6c4a758bc430e69eb99f50a4)
1 /*	$NetBSD: ether_sw_offload.c,v 1.6 2018/12/15 07:38:58 rin Exp $	*/
2 
3 /*
4  * Copyright (c) 2018 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Rin Okuyama.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #ifdef _KERNEL_OPT
33 #include "opt_inet.h"
34 #endif
35 
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: ether_sw_offload.c,v 1.6 2018/12/15 07:38:58 rin Exp $");
38 
39 #include <sys/param.h>
40 #include <sys/types.h>
41 #include <sys/mbuf.h>
42 #include <sys/syslog.h>
43 #include <sys/time.h>
44 
45 #include <net/if.h>
46 #include <net/if_ether.h>
47 #include <net/ether_sw_offload.h>
48 
49 #include <netinet/in.h>
50 #include <netinet/in_offload.h>
51 #include <netinet/ip.h>
52 #include <netinet/tcp.h>
53 #include <netinet/udp.h>
54 
55 #ifdef INET6
56 #include <netinet/ip6.h>
57 #include <netinet6/in6.h>
58 #include <netinet6/in6_offload.h>
59 #endif
60 
61 /*
62  * Limit error messages at most once per 10 seconds.
63  */
64 static const struct timeval eso_err_interval = {
65 	.tv_sec = 10,
66 	.tv_usec = 0,
67 };
68 static struct timeval eso_err_lasttime;
69 
70 /*
71  * Handle TX offload in software. For TSO, split the packet into
72  * chanks with payloads of size MSS. For chekcsum offload, update
73  * required checksum fields. The results are more than one packet
74  * in general. Return a mbuf queue consists of them.
75  */
76 
77 struct mbuf *
78 ether_sw_offload_tx(struct ifnet *ifp, struct mbuf *m)
79 {
80 	struct ether_header *ep;
81 	int flags, ehlen;
82 	uint16_t type;
83 #ifdef INET6
84 	bool v6;
85 #else
86 	bool v6 __diagused;
87 #endif
88 
89 	KASSERT(m->m_flags & M_PKTHDR);
90 	flags = m->m_pkthdr.csum_flags;
91 	if (flags == 0)
92 		goto done;
93 
94 	/* Sanity check */
95 	if (!TX_OFFLOAD_SUPPORTED(ifp->if_csum_flags_tx, flags))
96 		goto quit;
97 
98 	KASSERT(m->m_pkthdr.len >= sizeof(*ep));
99 	if (m->m_len < sizeof(*ep)) {
100 		m = m_pullup(m, sizeof(*ep));
101 		if (m == NULL)
102 			return NULL;
103 	}
104 	ep = mtod(m, struct ether_header *);
105 	switch (type = ntohs(ep->ether_type)) {
106 	case ETHERTYPE_IP:
107 	case ETHERTYPE_IPV6:
108 		ehlen = ETHER_HDR_LEN;
109 		break;
110 	case ETHERTYPE_VLAN:
111 		ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
112 		break;
113 	default:
114 		if (ratecheck(&eso_err_lasttime, &eso_err_interval))
115 			log(LOG_ERR, "%s: %s: dropping invalid frame "
116 			    "type 0x%04hx csum_flags 0x%08x\n",
117 			    __func__, ifp->if_xname, type, flags);
118 		goto quit;
119 	}
120 	KASSERT(m->m_pkthdr.len >= ehlen);
121 
122 	v6 = flags & (M_CSUM_TSOv6 | M_CSUM_TCPv6 | M_CSUM_UDPv6);
123 #ifndef INET6
124 	KASSERT(!v6);
125 #endif
126 
127 	if (flags & (M_CSUM_TSOv4 | M_CSUM_TSOv6)) {
128 		/*
129 		 * tcp[46]_segment() assume that size of payloads is
130 		 * a multiple of MSS. Further, tcp6_segment() assumes
131 		 * no extention headers.
132 		 *
133 		 * XXX Do we need some KASSERT's?
134 		 */
135 #ifdef INET6
136 		if (v6)
137 			return tcp6_segment(m, ehlen);
138 		else
139 #endif
140 			return tcp4_segment(m, ehlen);
141 	}
142 
143 #ifdef INET6
144 	if (v6)
145 		in6_undefer_cksum(m, ehlen, flags);
146 	else
147 #endif
148 		in_undefer_cksum(m, ehlen, flags);
149 done:
150 	m->m_pkthdr.csum_flags = 0;
151 	m->m_nextpkt = NULL;
152 	return m;
153 quit:
154 	m_freem(m);
155 	return NULL;
156 }
157 
158 /*
159  * Handle RX offload in software.
160  *
161  * XXX Fragmented packets or packets with IPv6 extension headers
162  * are not currently supported.
163  */
164 
165 struct mbuf *
166 ether_sw_offload_rx(struct ifnet *ifp, struct mbuf *m)
167 {
168 	struct ether_header *eh;
169 	struct ip *ip;
170 	struct tcphdr *th;
171 	struct udphdr *uh;
172 	uint16_t sum, osum;
173 	uint8_t proto;
174 	int flags, enabled, len, ehlen, iphlen, l4offset;
175 	bool v6;
176 
177 	flags = 0;
178 
179 	enabled = ifp->if_csum_flags_rx;
180 	if (!(enabled & (M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4 |
181 	    M_CSUM_TCPv6 | M_CSUM_UDPv6)))
182 		goto done;
183 
184 	KASSERT(m->m_flags & M_PKTHDR);
185 	len = m->m_pkthdr.len;
186 
187 	KASSERT(len >= sizeof(*eh));
188 	if (m->m_len < sizeof(*eh)) {
189 		m = m_pullup(m, sizeof(*eh));
190 		if (m == NULL)
191 			return NULL;
192 	}
193 	eh = mtod(m, struct ether_header *);
194 	switch (htons(eh->ether_type)) {
195 	case ETHERTYPE_IP:
196 	case ETHERTYPE_IPV6:
197 		ehlen = ETHER_HDR_LEN;
198 		break;
199 	case ETHERTYPE_VLAN:
200 		ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
201 		break;
202 	default:
203 		goto done;
204 	}
205 
206 	KASSERT(len >= ehlen);
207 	len = m->m_pkthdr.len - ehlen;
208 
209 	KASSERT(len >= sizeof(*ip));
210 	if (m->m_len < ehlen + sizeof(*ip)) {
211 		m = m_pullup(m, ehlen + sizeof(*ip));
212 		if (m == NULL)
213 			return NULL;
214 	}
215 	ip = (void *)(mtod(m, char *) + ehlen);
216 	v6 = (ip->ip_v != IPVERSION);
217 
218 	if (v6) {
219 #ifdef INET6
220 		struct ip6_hdr *ip6;
221 
222 		KASSERT(len >= sizeof(*ip6));
223 		if (m->m_len < ehlen + sizeof(*ip6)) {
224 			m = m_pullup(m, ehlen + sizeof(*ip6));
225 			if (m == NULL)
226 				return NULL;
227 		}
228 		ip6 = (void *)(mtod(m, char *) + ehlen);
229 		KASSERT((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION);
230 
231 		iphlen = sizeof(*ip6);
232 
233 		len -= iphlen;
234 
235 		proto = ip6->ip6_nxt;
236 		switch (proto) {
237 		case IPPROTO_TCP:
238 			if (!(enabled & M_CSUM_TCPv6))
239 				goto done;
240 			break;
241 		case IPPROTO_UDP:
242 			if (!(enabled & M_CSUM_UDPv6))
243 				goto done;
244 			break;
245 		default:
246 			/* XXX Extension headers are not supported. */
247 			goto done;
248 		}
249 
250 		sum = in6_cksum_phdr(&ip6->ip6_src, &ip6->ip6_dst, htonl(len),
251 		    htonl(proto));
252 #else
253 		goto done;
254 #endif
255 	} else {
256 		if (enabled & M_CSUM_IPv4)
257 			flags |= M_CSUM_IPv4;
258 
259 		iphlen = ip->ip_hl << 2;
260 		KASSERT(iphlen >= sizeof(*ip));
261 
262 		len -= iphlen;
263 		KASSERT(len >= 0);
264 
265 		if (in4_cksum(m, 0, ehlen, iphlen) != 0) {
266 			if (enabled & M_CSUM_IPv4)
267 				flags |= M_CSUM_IPv4_BAD;
268 			/* Broken. Do not check further. */
269 			goto done;
270 		}
271 
272 		/* Check if fragmented. */
273 		if (ntohs(ip->ip_off) & ~(IP_DF | IP_RF))
274 			goto done;
275 
276 		proto = ip->ip_p;
277 		switch (proto) {
278 		case IPPROTO_TCP:
279 			if (!(enabled & M_CSUM_TCPv4))
280 				goto done;
281 			break;
282 		case IPPROTO_UDP:
283 			if (!(enabled & M_CSUM_UDPv4))
284 				goto done;
285 			break;
286 		default:
287 			goto done;
288 		}
289 
290 		sum = in_cksum_phdr(ip->ip_src.s_addr, ip->ip_dst.s_addr,
291 		    htons((uint16_t)len + proto));
292 	}
293 
294 	l4offset = ehlen + iphlen;
295 	switch (proto) {
296 	case IPPROTO_TCP:
297 		KASSERT(len >= sizeof(*th));
298 		if (m->m_len < l4offset + sizeof(*th)) {
299 			m = m_pullup(m, l4offset + sizeof(*th));
300 			if (m == NULL)
301 				return NULL;
302 		}
303 		th = (void *)(mtod(m, char *) + l4offset);
304 		osum = th->th_sum;
305 		th->th_sum = sum;
306 #ifdef INET6
307 		if (v6) {
308 			flags |= M_CSUM_TCPv6;
309 			sum = in6_cksum(m, 0, l4offset, len);
310 		} else
311 #endif
312 		{
313 			flags |= M_CSUM_TCPv4;
314 			sum = in4_cksum(m, 0, l4offset, len);
315 		}
316 		if (sum != osum)
317 			flags |= M_CSUM_TCP_UDP_BAD;
318 		th->th_sum = osum;
319 		break;
320 	case IPPROTO_UDP:
321 		KASSERT(len >= sizeof(*uh));
322 		if (m->m_len < l4offset + sizeof(*uh)) {
323 			m = m_pullup(m, l4offset + sizeof(*uh));
324 			if (m == NULL)
325 				return NULL;
326 		}
327 		uh = (void *)(mtod(m, char *) + l4offset);
328 		osum = uh->uh_sum;
329 		if (osum == 0)
330 			break;
331 		uh->uh_sum = sum;
332 #ifdef INET6
333 		if (v6) {
334 			flags |= M_CSUM_UDPv6;
335 			sum = in6_cksum(m, 0, l4offset, len);
336 		} else
337 #endif
338 		{
339 			flags |= M_CSUM_UDPv4;
340 			sum = in4_cksum(m, 0, l4offset, len);
341 		}
342 		if (sum == 0)
343 			sum = 0xffff;
344 		if (sum != osum)
345 			flags |= M_CSUM_TCP_UDP_BAD;
346 		uh->uh_sum = osum;
347 		break;
348 	default:
349 		panic("%s: impossible", __func__);
350 	}
351 
352 done:
353 	m->m_pkthdr.csum_flags = flags;
354 	return m;
355 }
356