ether_sw_offload.c revision 1.3 1 /* $NetBSD: ether_sw_offload.c,v 1.3 2018/12/13 20:44:33 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.3 2018/12/13 20:44:33 rin Exp $");
38
39 #include <sys/param.h>
40 #include <sys/types.h>
41 #include <sys/mbuf.h>
42
43 #include <net/if.h>
44 #include <net/if_ether.h>
45 #include <net/ether_sw_offload.h>
46
47 #include <netinet/in.h>
48 #include <netinet/in_offload.h>
49 #include <netinet/ip.h>
50 #include <netinet/tcp.h>
51 #include <netinet/udp.h>
52
53 #ifdef INET6
54 #include <netinet/ip6.h>
55 #include <netinet6/in6.h>
56 #include <netinet6/in6_offload.h>
57 #endif
58
59 /*
60 * Handle TX offload in software. For TSO, split the packet into
61 * chanks with payloads of size MSS. For chekcsum offload, update
62 * required checksum fields. The results are more than one packet
63 * in general. Return a mbuf chain consists of them.
64 */
65
66 struct mbuf *
67 ether_sw_offload_tx(struct ifnet *ifp, struct mbuf *m)
68 {
69 struct ether_header *ep;
70 int flags, ehlen;
71 #ifdef INET6
72 bool v6;
73 #else
74 bool v6 __diagused;
75 #endif
76
77 KASSERT(m->m_flags & M_PKTHDR);
78 flags = m->m_pkthdr.csum_flags;
79 if (flags == 0)
80 goto done;
81
82 /* Sanity check */
83 if (!TX_OFFLOAD_SUPPORTED(ifp->if_csum_flags_tx, flags))
84 goto quit;
85
86 KASSERT(m->m_pkthdr.len >= sizeof(*ep));
87 if (m->m_len < sizeof(*ep)) {
88 m = m_pullup(m, sizeof(*ep));
89 if (m == NULL)
90 return NULL;
91 }
92 ep = mtod(m, struct ether_header *);
93 switch (ntohs(ep->ether_type)) {
94 case ETHERTYPE_IP:
95 case ETHERTYPE_IPV6:
96 ehlen = ETHER_HDR_LEN;
97 break;
98 case ETHERTYPE_VLAN:
99 ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
100 break;
101 default:
102 goto quit;
103 }
104 KASSERT(m->m_pkthdr.len >= ehlen);
105
106 v6 = flags & (M_CSUM_TSOv6 | M_CSUM_TCPv6 | M_CSUM_UDPv6);
107 #ifndef INET6
108 KASSERT(!v6);
109 #endif
110
111 if (flags & (M_CSUM_TSOv4 | M_CSUM_TSOv6)) {
112 /*
113 * tcp[46]_segment() assume that size of payloads is
114 * a multiple of MSS. Further, tcp6_segment() assumes
115 * no extention headers.
116 *
117 * XXX Do we need some KASSERT's?
118 */
119 #ifdef INET6
120 if (v6)
121 return tcp6_segment(m, ehlen);
122 else
123 #endif
124 return tcp4_segment(m, ehlen);
125 }
126
127 #ifdef INET6
128 if (v6)
129 in6_undefer_cksum(m, ehlen, flags);
130 else
131 #endif
132 in_undefer_cksum(m, ehlen, flags);
133 done:
134 m->m_pkthdr.csum_flags = 0;
135 m->m_nextpkt = NULL;
136 return m;
137 quit:
138 m_freem(m);
139 return NULL;
140 }
141
142 /*
143 * Handle RX offload in software.
144 *
145 * XXX Fragmented packets or packets with IPv6 extension headers
146 * are not currently supported.
147 */
148
149 struct mbuf *
150 ether_sw_offload_rx(struct ifnet *ifp, struct mbuf *m)
151 {
152 struct ether_header *eh;
153 struct ip *ip;
154 struct tcphdr *th;
155 struct udphdr *uh;
156 uint16_t sum, osum;
157 uint8_t proto;
158 int flags, enabled, len, ehlen, iphlen, l4offset;
159 bool v6;
160
161 flags = 0;
162
163 enabled = ifp->if_csum_flags_rx;
164 if (!(enabled & (M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4 |
165 M_CSUM_TCPv6 | M_CSUM_UDPv6)))
166 goto done;
167
168 KASSERT(m->m_flags & M_PKTHDR);
169 len = m->m_pkthdr.len;
170
171 KASSERT(len >= sizeof(*eh));
172 if (m->m_len < sizeof(*eh)) {
173 m = m_pullup(m, sizeof(*eh));
174 if (m == NULL)
175 return NULL;
176 }
177 eh = mtod(m, struct ether_header *);
178 switch (htons(eh->ether_type)) {
179 case ETHERTYPE_IP:
180 case ETHERTYPE_IPV6:
181 ehlen = ETHER_HDR_LEN;
182 break;
183 case ETHERTYPE_VLAN:
184 ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
185 break;
186 default:
187 goto done;
188 }
189
190 KASSERT(len >= ehlen);
191 len = m->m_pkthdr.len - ehlen;
192
193 KASSERT(len >= sizeof(*ip));
194 if (m->m_len < ehlen + sizeof(*ip)) {
195 m = m_pullup(m, ehlen + sizeof(*ip));
196 if (m == NULL)
197 return NULL;
198 }
199 ip = (void *)(mtod(m, char *) + ehlen);
200 v6 = (ip->ip_v != IPVERSION);
201
202 if (v6) {
203 #ifdef INET6
204 struct ip6_hdr *ip6;
205
206 KASSERT(len >= sizeof(*ip6));
207 if (m->m_len < ehlen + sizeof(*ip6)) {
208 m = m_pullup(m, ehlen + sizeof(*ip6));
209 if (m == NULL)
210 return NULL;
211 }
212 ip6 = (void *)(mtod(m, char *) + ehlen);
213 KASSERT((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION);
214
215 iphlen = sizeof(*ip6);
216
217 len -= iphlen;
218
219 proto = ip6->ip6_nxt;
220 switch (proto) {
221 case IPPROTO_TCP:
222 if (!(enabled & M_CSUM_TCPv6))
223 goto done;
224 break;
225 case IPPROTO_UDP:
226 if (!(enabled & M_CSUM_UDPv6))
227 goto done;
228 break;
229 default:
230 /* XXX Extension headers are not supported. */
231 goto done;
232 }
233
234 sum = in6_cksum_phdr(&ip6->ip6_src, &ip6->ip6_dst, htonl(len),
235 htonl(proto));
236 #else
237 goto done;
238 #endif
239 } else {
240 if (enabled & M_CSUM_IPv4)
241 flags |= M_CSUM_IPv4;
242
243 iphlen = ip->ip_hl << 2;
244 KASSERT(iphlen >= sizeof(*ip));
245
246 len -= iphlen;
247 KASSERT(len >= 0);
248
249 if (in4_cksum(m, 0, ehlen, iphlen) != 0) {
250 if (enabled & M_CSUM_IPv4)
251 flags |= M_CSUM_IPv4_BAD;
252 /* Broken. Do not check further. */
253 goto done;
254 }
255
256 /* Check if fragmented. */
257 if (ntohs(ip->ip_off) & ~(IP_DF | IP_RF))
258 goto done;
259
260 proto = ip->ip_p;
261 switch (proto) {
262 case IPPROTO_TCP:
263 if (!(enabled & M_CSUM_TCPv4))
264 goto done;
265 break;
266 case IPPROTO_UDP:
267 if (!(enabled & M_CSUM_UDPv4))
268 goto done;
269 break;
270 default:
271 goto done;
272 }
273
274 sum = in_cksum_phdr(ip->ip_src.s_addr, ip->ip_dst.s_addr,
275 htons((uint16_t)len + proto));
276 }
277
278 l4offset = ehlen + iphlen;
279 switch (proto) {
280 case IPPROTO_TCP:
281 KASSERT(len >= sizeof(*th));
282 if (m->m_len < l4offset + sizeof(*th)) {
283 m = m_pullup(m, l4offset + sizeof(*th));
284 if (m == NULL)
285 return NULL;
286 }
287 th = (void *)(mtod(m, char *) + l4offset);
288 osum = th->th_sum;
289 th->th_sum = sum;
290 #ifdef INET6
291 if (v6) {
292 flags |= M_CSUM_TCPv6;
293 sum = in6_cksum(m, 0, l4offset, len);
294 } else
295 #endif
296 {
297 flags |= M_CSUM_TCPv4;
298 sum = in4_cksum(m, 0, l4offset, len);
299 }
300 if (sum != osum)
301 flags |= M_CSUM_TCP_UDP_BAD;
302 th->th_sum = osum;
303 break;
304 case IPPROTO_UDP:
305 KASSERT(len >= sizeof(*uh));
306 if (m->m_len < l4offset + sizeof(*uh)) {
307 m = m_pullup(m, l4offset + sizeof(*uh));
308 if (m == NULL)
309 return NULL;
310 }
311 uh = (void *)(mtod(m, char *) + l4offset);
312 osum = uh->uh_sum;
313 if (osum == 0)
314 break;
315 uh->uh_sum = sum;
316 #ifdef INET6
317 if (v6) {
318 flags |= M_CSUM_UDPv6;
319 sum = in6_cksum(m, 0, l4offset, len);
320 } else
321 #endif
322 {
323 flags |= M_CSUM_UDPv4;
324 sum = in4_cksum(m, 0, l4offset, len);
325 }
326 if (sum == 0)
327 sum = 0xffff;
328 if (sum != osum)
329 flags |= M_CSUM_TCP_UDP_BAD;
330 uh->uh_sum = osum;
331 break;
332 default:
333 panic("%s: impossible", __func__);
334 break;
335 }
336
337 done:
338 m->m_pkthdr.csum_flags = flags;
339 return m;
340 }
341