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