ether_sw_offload.c revision 1.9 1 /* $NetBSD: ether_sw_offload.c,v 1.9 2024/09/15 08:33:13 andvar 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.9 2024/09/15 08:33:13 andvar 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 * chunks with payloads of size MSS. For checksum 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 KASSERT(m->m_len >= sizeof(*ep));
100 ep = mtod(m, struct ether_header *);
101 switch (type = ntohs(ep->ether_type)) {
102 case ETHERTYPE_IP:
103 case ETHERTYPE_IPV6:
104 ehlen = ETHER_HDR_LEN;
105 break;
106 case ETHERTYPE_VLAN:
107 ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
108 break;
109 default:
110 if (ratecheck(&eso_err_lasttime, &eso_err_interval))
111 log(LOG_ERR, "%s: %s: dropping invalid frame "
112 "type 0x%04hx csum_flags 0x%08x\n",
113 __func__, ifp->if_xname, type, flags);
114 goto quit;
115 }
116 KASSERT(m->m_pkthdr.len >= ehlen);
117
118 v6 = flags & (M_CSUM_TSOv6 | M_CSUM_TCPv6 | M_CSUM_UDPv6);
119 #ifndef INET6
120 KASSERT(!v6);
121 #endif
122
123 if (flags & (M_CSUM_TSOv4 | M_CSUM_TSOv6)) {
124 /*
125 * tcp[46]_segment() assume that size of payloads is
126 * a multiple of MSS. Further, tcp6_segment() assumes
127 * no extension headers.
128 *
129 * XXX Do we need some KASSERT's?
130 */
131 #ifdef INET6
132 if (v6)
133 return tcp6_segment(m, ehlen);
134 else
135 #endif
136 return tcp4_segment(m, ehlen);
137 }
138
139 #ifdef INET6
140 if (v6)
141 in6_undefer_cksum(m, ehlen, flags);
142 else
143 #endif
144 in_undefer_cksum(m, ehlen, flags);
145 done:
146 m->m_pkthdr.csum_flags = 0;
147 m->m_nextpkt = NULL;
148 return m;
149 quit:
150 m_freem(m);
151 return NULL;
152 }
153
154 /*
155 * Handle RX offload in software.
156 *
157 * XXX Fragmented packets or packets with IPv6 extension headers
158 * are not currently supported.
159 */
160
161 struct mbuf *
162 ether_sw_offload_rx(struct ifnet *ifp, struct mbuf *m)
163 {
164 struct ether_header *eh;
165 struct ip *ip;
166 struct tcphdr *th;
167 struct udphdr *uh;
168 uint16_t sum, osum;
169 uint8_t proto;
170 int flags, enabled, len, ehlen, iphlen, l4offset;
171 bool v6;
172
173 flags = 0;
174
175 enabled = ifp->if_csum_flags_rx;
176 if (!(enabled & (M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4 |
177 M_CSUM_TCPv6 | M_CSUM_UDPv6)))
178 goto done;
179
180 KASSERT(m->m_flags & M_PKTHDR);
181 len = m->m_pkthdr.len;
182
183 KASSERT(len >= sizeof(*eh));
184 if (m->m_len < sizeof(*eh)) {
185 m = m_pullup(m, sizeof(*eh));
186 if (m == NULL)
187 return NULL;
188 }
189 eh = mtod(m, struct ether_header *);
190 switch (htons(eh->ether_type)) {
191 case ETHERTYPE_IP:
192 case ETHERTYPE_IPV6:
193 ehlen = ETHER_HDR_LEN;
194 break;
195 case ETHERTYPE_VLAN:
196 ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
197 break;
198 default:
199 goto done;
200 }
201
202 KASSERT(len >= ehlen);
203 len = m->m_pkthdr.len - ehlen;
204
205 KASSERT(len >= sizeof(*ip));
206 if (m->m_len < ehlen + sizeof(*ip)) {
207 m = m_pullup(m, ehlen + sizeof(*ip));
208 if (m == NULL)
209 return NULL;
210 }
211 ip = (void *)(mtod(m, char *) + ehlen);
212 v6 = (ip->ip_v != IPVERSION);
213
214 if (v6) {
215 #ifdef INET6
216 struct ip6_hdr *ip6;
217
218 KASSERT(len >= sizeof(*ip6));
219 if (m->m_len < ehlen + sizeof(*ip6)) {
220 m = m_pullup(m, ehlen + sizeof(*ip6));
221 if (m == NULL)
222 return NULL;
223 }
224 ip6 = (void *)(mtod(m, char *) + ehlen);
225 KASSERT((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION);
226
227 iphlen = sizeof(*ip6);
228
229 len -= iphlen;
230
231 proto = ip6->ip6_nxt;
232 switch (proto) {
233 case IPPROTO_TCP:
234 if (!(enabled & M_CSUM_TCPv6))
235 goto done;
236 break;
237 case IPPROTO_UDP:
238 if (!(enabled & M_CSUM_UDPv6))
239 goto done;
240 break;
241 default:
242 /* XXX Extension headers are not supported. */
243 goto done;
244 }
245
246 sum = in6_cksum_phdr(&ip6->ip6_src, &ip6->ip6_dst, htonl(len),
247 htonl(proto));
248 #else
249 goto done;
250 #endif
251 } else {
252 if (enabled & M_CSUM_IPv4)
253 flags |= M_CSUM_IPv4;
254
255 iphlen = ip->ip_hl << 2;
256 KASSERT(iphlen >= sizeof(*ip));
257
258 len -= iphlen;
259 KASSERT(len >= 0);
260
261 if (in4_cksum(m, 0, ehlen, iphlen) != 0) {
262 if (enabled & M_CSUM_IPv4)
263 flags |= M_CSUM_IPv4_BAD;
264 /* Broken. Do not check further. */
265 goto done;
266 }
267
268 /* Check if fragmented. */
269 if (ntohs(ip->ip_off) & ~(IP_DF | IP_RF))
270 goto done;
271
272 proto = ip->ip_p;
273 switch (proto) {
274 case IPPROTO_TCP:
275 if (!(enabled & M_CSUM_TCPv4))
276 goto done;
277 break;
278 case IPPROTO_UDP:
279 if (!(enabled & M_CSUM_UDPv4))
280 goto done;
281 break;
282 default:
283 goto done;
284 }
285
286 sum = in_cksum_phdr(ip->ip_src.s_addr, ip->ip_dst.s_addr,
287 htons((uint16_t)len + proto));
288 }
289
290 l4offset = ehlen + iphlen;
291 switch (proto) {
292 case IPPROTO_TCP:
293 KASSERT(len >= sizeof(*th));
294 if (m->m_len < l4offset + sizeof(*th)) {
295 m = m_pullup(m, l4offset + sizeof(*th));
296 if (m == NULL)
297 return NULL;
298 }
299 th = (void *)(mtod(m, char *) + l4offset);
300 osum = th->th_sum;
301 th->th_sum = sum;
302 #ifdef INET6
303 if (v6) {
304 flags |= M_CSUM_TCPv6;
305 sum = in6_cksum(m, 0, l4offset, len);
306 } else
307 #endif
308 {
309 flags |= M_CSUM_TCPv4;
310 sum = in4_cksum(m, 0, l4offset, len);
311 }
312 if (sum != osum)
313 flags |= M_CSUM_TCP_UDP_BAD;
314 th->th_sum = osum;
315 break;
316 case IPPROTO_UDP:
317 KASSERT(len >= sizeof(*uh));
318 if (m->m_len < l4offset + sizeof(*uh)) {
319 m = m_pullup(m, l4offset + sizeof(*uh));
320 if (m == NULL)
321 return NULL;
322 }
323 uh = (void *)(mtod(m, char *) + l4offset);
324 osum = uh->uh_sum;
325 if (osum == 0)
326 break;
327 uh->uh_sum = sum;
328 #ifdef INET6
329 if (v6) {
330 flags |= M_CSUM_UDPv6;
331 sum = in6_cksum(m, 0, l4offset, len);
332 } else
333 #endif
334 {
335 flags |= M_CSUM_UDPv4;
336 sum = in4_cksum(m, 0, l4offset, len);
337 }
338 if (sum == 0)
339 sum = 0xffff;
340 if (sum != osum)
341 flags |= M_CSUM_TCP_UDP_BAD;
342 uh->uh_sum = osum;
343 break;
344 default:
345 panic("%s: impossible", __func__);
346 }
347
348 done:
349 m->m_pkthdr.csum_flags = flags;
350 return m;
351 }
352