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