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