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