Home | History | Annotate | Line # | Download | only in net
      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