1 1.15 rin /* $NetBSD: in_offload.c,v 1.15 2024/07/05 04:31:54 rin Exp $ */ 2 1.1 yamt 3 1.10 maxv /* 4 1.1 yamt * Copyright (c)2005, 2006 YAMAMOTO Takashi, 5 1.1 yamt * All rights reserved. 6 1.1 yamt * 7 1.1 yamt * Redistribution and use in source and binary forms, with or without 8 1.1 yamt * modification, are permitted provided that the following conditions 9 1.1 yamt * are met: 10 1.1 yamt * 1. Redistributions of source code must retain the above copyright 11 1.1 yamt * notice, this list of conditions and the following disclaimer. 12 1.1 yamt * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 yamt * notice, this list of conditions and the following disclaimer in the 14 1.1 yamt * documentation and/or other materials provided with the distribution. 15 1.1 yamt * 16 1.1 yamt * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 1.1 yamt * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 1.1 yamt * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 1.1 yamt * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 1.1 yamt * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 1.1 yamt * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 1.1 yamt * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 1.1 yamt * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 1.1 yamt * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 1.1 yamt * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 1.1 yamt * SUCH DAMAGE. 27 1.1 yamt */ 28 1.1 yamt 29 1.1 yamt #include <sys/cdefs.h> 30 1.15 rin __KERNEL_RCSID(0, "$NetBSD: in_offload.c,v 1.15 2024/07/05 04:31:54 rin Exp $"); 31 1.1 yamt 32 1.1 yamt #include <sys/param.h> 33 1.1 yamt #include <sys/mbuf.h> 34 1.1 yamt 35 1.1 yamt #include <net/if.h> 36 1.1 yamt 37 1.1 yamt #include <netinet/in.h> 38 1.1 yamt #include <netinet/in_systm.h> 39 1.1 yamt #include <netinet/ip.h> 40 1.6 ozaki #include <netinet/ip_var.h> 41 1.1 yamt #include <netinet/tcp.h> 42 1.1 yamt #include <netinet/in_offload.h> 43 1.1 yamt 44 1.9 maxv /* 45 1.9 maxv * Handle M_CSUM_TSOv4 in software. Split the TCP payload in chunks of 46 1.13 rin * size MSS, and return mbuf chain consists of them. 47 1.9 maxv */ 48 1.13 rin struct mbuf * 49 1.13 rin tcp4_segment(struct mbuf *m, int off) 50 1.1 yamt { 51 1.1 yamt int mss; 52 1.10 maxv int iphlen, thlen; 53 1.10 maxv int hlen, len; 54 1.10 maxv struct ip *ip; 55 1.1 yamt struct tcphdr *th; 56 1.13 rin uint16_t ipid, phsum; 57 1.1 yamt uint32_t tcpseq; 58 1.1 yamt struct mbuf *hdr = NULL; 59 1.13 rin struct mbuf *m0 = NULL; 60 1.13 rin struct mbuf *prev = NULL; 61 1.13 rin struct mbuf *n, *t; 62 1.13 rin int nsegs; 63 1.1 yamt 64 1.1 yamt KASSERT((m->m_flags & M_PKTHDR) != 0); 65 1.1 yamt KASSERT((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) != 0); 66 1.1 yamt 67 1.1 yamt m->m_pkthdr.csum_flags = 0; 68 1.1 yamt 69 1.1 yamt len = m->m_pkthdr.len; 70 1.13 rin KASSERT(len >= off + sizeof(*ip) + sizeof(*th)); 71 1.1 yamt 72 1.13 rin hlen = off + sizeof(*ip); 73 1.13 rin if (m->m_len < hlen) { 74 1.13 rin m = m_pullup(m, hlen); 75 1.13 rin if (m == NULL) 76 1.1 yamt goto quit; 77 1.1 yamt } 78 1.13 rin ip = (void *)(mtod(m, char *) + off); 79 1.10 maxv iphlen = ip->ip_hl * 4; 80 1.10 maxv KASSERT(ip->ip_v == IPVERSION); 81 1.10 maxv KASSERT(iphlen >= sizeof(*ip)); 82 1.10 maxv KASSERT(ip->ip_p == IPPROTO_TCP); 83 1.10 maxv ipid = ntohs(ip->ip_id); 84 1.1 yamt 85 1.13 rin hlen = off + iphlen + sizeof(*th); 86 1.1 yamt if (m->m_len < hlen) { 87 1.1 yamt m = m_pullup(m, hlen); 88 1.13 rin if (m == NULL) 89 1.1 yamt goto quit; 90 1.1 yamt } 91 1.13 rin th = (void *)(mtod(m, char *) + off + iphlen); 92 1.1 yamt tcpseq = ntohl(th->th_seq); 93 1.1 yamt thlen = th->th_off * 4; 94 1.13 rin hlen = off + iphlen + thlen; 95 1.1 yamt 96 1.1 yamt mss = m->m_pkthdr.segsz; 97 1.1 yamt KASSERT(mss != 0); 98 1.1 yamt KASSERT(len > hlen); 99 1.1 yamt 100 1.1 yamt t = m_split(m, hlen, M_NOWAIT); 101 1.13 rin if (t == NULL) 102 1.1 yamt goto quit; 103 1.1 yamt hdr = m; 104 1.1 yamt m = t; 105 1.13 rin 106 1.1 yamt len -= hlen; 107 1.1 yamt KASSERT(len % mss == 0); 108 1.1 yamt 109 1.13 rin ip = (void *)(mtod(hdr, char *) + off); 110 1.13 rin ip->ip_len = htons(iphlen + thlen + mss); 111 1.13 rin phsum = in_cksum_phdr(ip->ip_src.s_addr, ip->ip_dst.s_addr, 112 1.13 rin htons((uint16_t)(thlen + mss) + IPPROTO_TCP)); 113 1.13 rin 114 1.13 rin for (nsegs = len / mss; nsegs > 0; nsegs--) { 115 1.13 rin if (nsegs > 1) { 116 1.13 rin n = m_dup(hdr, 0, hlen, M_NOWAIT); 117 1.13 rin if (n == NULL) 118 1.13 rin goto quit; 119 1.13 rin } else 120 1.13 rin n = hdr; 121 1.1 yamt KASSERT(n->m_len == hlen); /* XXX */ 122 1.1 yamt 123 1.13 rin if (nsegs > 1) { 124 1.13 rin t = m_split(m, mss, M_NOWAIT); 125 1.13 rin if (t == NULL) { 126 1.13 rin m_freem(n); 127 1.13 rin goto quit; 128 1.13 rin } 129 1.13 rin } else 130 1.13 rin t = m; 131 1.1 yamt m_cat(n, m); 132 1.1 yamt m = t; 133 1.1 yamt 134 1.1 yamt KASSERT(n->m_len >= hlen); /* XXX */ 135 1.1 yamt 136 1.13 rin if (m0 == NULL) 137 1.13 rin m0 = n; 138 1.13 rin 139 1.13 rin if (prev != NULL) 140 1.13 rin prev->m_nextpkt = n; 141 1.13 rin 142 1.1 yamt n->m_pkthdr.len = hlen + mss; 143 1.13 rin n->m_nextpkt = NULL; /* XXX */ 144 1.13 rin 145 1.13 rin ip = (void *)(mtod(n, char *) + off); 146 1.10 maxv ip->ip_id = htons(ipid); 147 1.10 maxv ip->ip_sum = 0; 148 1.13 rin ip->ip_sum = in4_cksum(n, 0, off, iphlen); 149 1.1 yamt 150 1.13 rin th = (void *)(mtod(n, char *) + off + iphlen); 151 1.13 rin th->th_seq = htonl(tcpseq); 152 1.13 rin th->th_sum = phsum; 153 1.13 rin th->th_sum = in4_cksum(n, 0, off + iphlen, thlen + mss); 154 1.1 yamt 155 1.1 yamt tcpseq += mss; 156 1.1 yamt ipid++; 157 1.13 rin prev = n; 158 1.1 yamt } 159 1.13 rin return m0; 160 1.1 yamt 161 1.1 yamt quit: 162 1.15 rin m_freem(hdr); 163 1.15 rin m_freem(m); 164 1.13 rin for (m = m0; m != NULL; m = n) { 165 1.13 rin n = m->m_nextpkt; 166 1.1 yamt m_freem(m); 167 1.1 yamt } 168 1.1 yamt 169 1.13 rin return NULL; 170 1.1 yamt } 171 1.3 matt 172 1.9 maxv int 173 1.9 maxv ip_tso_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *sa, 174 1.9 maxv struct rtentry *rt) 175 1.9 maxv { 176 1.13 rin struct mbuf *n; 177 1.13 rin int error = 0; 178 1.13 rin 179 1.13 rin m = tcp4_segment(m, 0); 180 1.13 rin if (m == NULL) 181 1.13 rin return ENOMEM; 182 1.13 rin do { 183 1.13 rin n = m->m_nextpkt; 184 1.13 rin if (error == 0) 185 1.13 rin error = ip_if_output(ifp, m, sa, rt); 186 1.13 rin else 187 1.13 rin m_freem(m); 188 1.13 rin m = n; 189 1.13 rin } while (m != NULL); 190 1.13 rin return error; 191 1.9 maxv } 192 1.9 maxv 193 1.8 maxv /* 194 1.8 maxv * Compute now in software the IP and TCP/UDP checksums. Cancel the 195 1.8 maxv * hardware offloading. 196 1.8 maxv */ 197 1.3 matt void 198 1.14 jdolecek in_undefer_cksum(struct mbuf *mh, size_t hdrlen, int csum_flags) 199 1.3 matt { 200 1.14 jdolecek const size_t iphdrlen = M_CSUM_DATA_IPv4_IPHL(mh->m_pkthdr.csum_data); 201 1.3 matt uint16_t csum; 202 1.3 matt uint16_t ip_len; 203 1.3 matt uint16_t *csump; 204 1.14 jdolecek struct mbuf *m = mh; 205 1.5 yamt 206 1.14 jdolecek KASSERT(mh->m_flags & M_PKTHDR); 207 1.14 jdolecek KASSERT(mh->m_pkthdr.len > hdrlen); 208 1.14 jdolecek KASSERT((mh->m_pkthdr.csum_flags & csum_flags) == csum_flags); 209 1.14 jdolecek 210 1.14 jdolecek /* 211 1.14 jdolecek * Deal with prepended frame header as done by e.g. ether_output(). 212 1.14 jdolecek * If first mbuf in chain has just the header, use second mbuf 213 1.14 jdolecek * for the actual checksum. in4_csum() expects the passed mbuf 214 1.14 jdolecek * to have the whole (struct ip) area contiguous. 215 1.14 jdolecek */ 216 1.14 jdolecek if (m->m_len <= hdrlen) { 217 1.14 jdolecek hdrlen -= m->m_len; 218 1.14 jdolecek m = m->m_next; 219 1.14 jdolecek KASSERT(m != NULL); 220 1.14 jdolecek } 221 1.3 matt 222 1.3 matt if (__predict_true(hdrlen + sizeof(struct ip) <= m->m_len)) { 223 1.3 matt struct ip *ip = (struct ip *)(mtod(m, uint8_t *) + hdrlen); 224 1.5 yamt 225 1.3 matt ip_len = ip->ip_len; 226 1.3 matt csump = &ip->ip_sum; 227 1.3 matt } else { 228 1.5 yamt const size_t ip_len_offset = 229 1.5 yamt hdrlen + offsetof(struct ip, ip_len); 230 1.5 yamt 231 1.3 matt m_copydata(m, ip_len_offset, sizeof(ip_len), &ip_len); 232 1.3 matt csump = NULL; 233 1.3 matt } 234 1.5 yamt ip_len = ntohs(ip_len); 235 1.3 matt 236 1.3 matt if (csum_flags & M_CSUM_IPv4) { 237 1.3 matt csum = in4_cksum(m, 0, hdrlen, iphdrlen); 238 1.3 matt if (csump != NULL) { 239 1.3 matt *csump = csum; 240 1.3 matt } else { 241 1.5 yamt const size_t offset = hdrlen + 242 1.5 yamt offsetof(struct ip, ip_sum); 243 1.5 yamt 244 1.3 matt m_copyback(m, offset, sizeof(uint16_t), &csum); 245 1.3 matt } 246 1.3 matt } 247 1.3 matt 248 1.3 matt if (csum_flags & (M_CSUM_UDPv4|M_CSUM_TCPv4)) { 249 1.5 yamt size_t l4offset = hdrlen + iphdrlen; 250 1.3 matt 251 1.12 rin csum = in4_cksum(m, 0, l4offset, ip_len - iphdrlen); 252 1.3 matt if (csum == 0 && (csum_flags & M_CSUM_UDPv4) != 0) 253 1.3 matt csum = 0xffff; 254 1.3 matt 255 1.3 matt l4offset += M_CSUM_DATA_IPv4_OFFSET(m->m_pkthdr.csum_data); 256 1.3 matt 257 1.3 matt if (__predict_true(l4offset + sizeof(uint16_t) <= m->m_len)) { 258 1.3 matt *(uint16_t *)(mtod(m, char *) + l4offset) = csum; 259 1.3 matt } else { 260 1.10 maxv m_copyback(m, l4offset, sizeof(csum), (void *)&csum); 261 1.3 matt } 262 1.3 matt } 263 1.3 matt 264 1.14 jdolecek mh->m_pkthdr.csum_flags ^= csum_flags; 265 1.3 matt } 266 1.8 maxv 267 1.8 maxv /* 268 1.8 maxv * Compute now in software the TCP/UDP checksum. Cancel the hardware 269 1.8 maxv * offloading. 270 1.8 maxv */ 271 1.8 maxv void 272 1.8 maxv in_undefer_cksum_tcpudp(struct mbuf *m) 273 1.8 maxv { 274 1.8 maxv struct ip *ip; 275 1.10 maxv uint16_t csum, offset; 276 1.8 maxv 277 1.11 maxv KASSERT((m->m_flags & M_PKTHDR) != 0); 278 1.11 maxv KASSERT((m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) != 0); 279 1.11 maxv KASSERT((m->m_pkthdr.csum_flags & (M_CSUM_TCPv6|M_CSUM_UDPv6)) == 0); 280 1.11 maxv 281 1.8 maxv ip = mtod(m, struct ip *); 282 1.8 maxv offset = ip->ip_hl << 2; 283 1.10 maxv 284 1.8 maxv csum = in4_cksum(m, 0, offset, ntohs(ip->ip_len) - offset); 285 1.8 maxv if (csum == 0 && (m->m_pkthdr.csum_flags & M_CSUM_UDPv4) != 0) 286 1.8 maxv csum = 0xffff; 287 1.8 maxv 288 1.8 maxv offset += M_CSUM_DATA_IPv4_OFFSET(m->m_pkthdr.csum_data); 289 1.8 maxv 290 1.10 maxv if ((offset + sizeof(uint16_t)) <= m->m_len) { 291 1.10 maxv *(uint16_t *)(mtod(m, char *) + offset) = csum; 292 1.10 maxv } else { 293 1.8 maxv m_copyback(m, offset, sizeof(csum), (void *)&csum); 294 1.8 maxv } 295 1.8 maxv } 296