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