in6_offload.c revision 1.11 1 /* $NetBSD: in6_offload.c,v 1.11 2018/09/19 07:54:11 rin Exp $ */
2
3 /*
4 * Copyright (c)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: in6_offload.c,v 1.11 2018/09/19 07:54:11 rin 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/ip6.h>
40 #include <netinet/tcp.h>
41 #include <netinet6/in6_var.h>
42 #include <netinet6/ip6_var.h>
43 #include <netinet6/nd6.h>
44 #include <netinet6/in6_offload.h>
45
46 /*
47 * Handle M_CSUM_TSOv6 in software. Split the TCP payload in chunks of
48 * size MSS, and send them.
49 */
50 static int
51 tcp6_segment(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
52 const struct sockaddr_in6 *dst, struct rtentry *rt)
53 {
54 int mss;
55 int iphlen;
56 int thlen;
57 int hlen;
58 int len;
59 struct ip6_hdr *iph;
60 struct tcphdr *th;
61 uint32_t tcpseq;
62 struct mbuf *hdr = NULL;
63 struct mbuf *t;
64 int error = 0;
65
66 KASSERT((m->m_flags & M_PKTHDR) != 0);
67 KASSERT((m->m_pkthdr.csum_flags & M_CSUM_TSOv6) != 0);
68
69 m->m_pkthdr.csum_flags = 0;
70
71 len = m->m_pkthdr.len;
72 KASSERT(len >= sizeof(*iph) + sizeof(*th));
73
74 if (m->m_len < sizeof(*iph)) {
75 m = m_pullup(m, sizeof(*iph));
76 if (m == NULL) {
77 error = ENOMEM;
78 goto quit;
79 }
80 }
81 iph = mtod(m, struct ip6_hdr *);
82 iphlen = sizeof(*iph);
83 KASSERT((iph->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION);
84 KASSERT(iph->ip6_nxt == IPPROTO_TCP);
85
86 hlen = iphlen + sizeof(*th);
87 if (m->m_len < hlen) {
88 m = m_pullup(m, hlen);
89 if (m == NULL) {
90 error = ENOMEM;
91 goto quit;
92 }
93 }
94 th = (void *)(mtod(m, char *) + iphlen);
95 tcpseq = ntohl(th->th_seq);
96 thlen = th->th_off * 4;
97 hlen = iphlen + thlen;
98
99 mss = m->m_pkthdr.segsz;
100 KASSERT(mss != 0);
101 KASSERT(len > hlen);
102
103 t = m_split(m, hlen, M_NOWAIT);
104 if (t == NULL) {
105 error = ENOMEM;
106 goto quit;
107 }
108 hdr = m;
109 m = t;
110 len -= hlen;
111 KASSERT(len % mss == 0);
112 while (len > 0) {
113 struct mbuf *n;
114
115 n = m_dup(hdr, 0, hlen, M_NOWAIT);
116 if (n == NULL) {
117 error = ENOMEM;
118 goto quit;
119 }
120 KASSERT(n->m_len == hlen); /* XXX */
121
122 t = m_split(m, mss, M_NOWAIT);
123 if (t == NULL) {
124 m_freem(n);
125 error = ENOMEM;
126 goto quit;
127 }
128 m_cat(n, m);
129 m = t;
130
131 KASSERT(n->m_len >= hlen); /* XXX */
132
133 n->m_pkthdr.len = hlen + mss;
134 iph = mtod(n, struct ip6_hdr *);
135 KASSERT((iph->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION);
136 iph->ip6_plen = htons(thlen + mss);
137 th = (void *)(mtod(n, char *) + iphlen);
138 th->th_seq = htonl(tcpseq);
139 th->th_sum = 0;
140 th->th_sum = in6_cksum(n, IPPROTO_TCP, iphlen, thlen + mss);
141
142 error = ip6_if_output(ifp, origifp, n, dst, rt);
143 if (error) {
144 goto quit;
145 }
146
147 tcpseq += mss;
148 len -= mss;
149 }
150
151 quit:
152 if (hdr != NULL) {
153 m_freem(hdr);
154 }
155 if (m != NULL) {
156 m_freem(m);
157 }
158
159 return error;
160 }
161
162 int
163 ip6_tso_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
164 const struct sockaddr_in6 *dst, struct rtentry *rt)
165 {
166 return tcp6_segment(ifp, origifp, m, dst, rt);
167 }
168
169 /*
170 * Compute now in software the IP and TCP/UDP checksums. Cancel the
171 * hardware offloading.
172 */
173 void
174 in6_undefer_cksum(struct mbuf *m, size_t hdrlen, int csum_flags)
175 {
176 const size_t ip6_plen_offset =
177 hdrlen + offsetof(struct ip6_hdr, ip6_plen);
178 size_t l4hdroff;
179 size_t l4offset;
180 uint16_t plen;
181 uint16_t csum;
182
183 KASSERT(m->m_flags & M_PKTHDR);
184 KASSERT((m->m_pkthdr.csum_flags & csum_flags) == csum_flags);
185 KASSERT(csum_flags == M_CSUM_UDPv6 || csum_flags == M_CSUM_TCPv6);
186
187 if (__predict_true(hdrlen + sizeof(struct ip6_hdr) <= m->m_len)) {
188 plen = *(uint16_t *)(mtod(m, char *) + ip6_plen_offset);
189 } else {
190 m_copydata(m, ip6_plen_offset, sizeof(plen), &plen);
191 }
192 plen = ntohs(plen);
193
194 l4hdroff = M_CSUM_DATA_IPv6_IPHL(m->m_pkthdr.csum_data);
195 l4offset = hdrlen + l4hdroff;
196 csum = in6_cksum(m, 0, l4offset,
197 plen - (l4hdroff - sizeof(struct ip6_hdr)));
198
199 if (csum == 0 && (csum_flags & M_CSUM_UDPv6) != 0)
200 csum = 0xffff;
201
202 l4offset += M_CSUM_DATA_IPv6_OFFSET(m->m_pkthdr.csum_data);
203
204 if (__predict_true((l4offset + sizeof(uint16_t)) <= m->m_len)) {
205 *(uint16_t *)(mtod(m, char *) + l4offset) = csum;
206 } else {
207 m_copyback(m, l4offset, sizeof(csum), (void *) &csum);
208 }
209
210 m->m_pkthdr.csum_flags ^= csum_flags;
211 }
212
213 /*
214 * Compute now in software the TCP/UDP checksum. Cancel the hardware
215 * offloading.
216 */
217 void
218 in6_undefer_cksum_tcpudp(struct mbuf *m)
219 {
220 uint16_t csum, offset;
221
222 KASSERT((m->m_pkthdr.csum_flags & (M_CSUM_UDPv6|M_CSUM_TCPv6)) != 0);
223 KASSERT((~m->m_pkthdr.csum_flags & (M_CSUM_UDPv6|M_CSUM_TCPv6)) != 0);
224 KASSERT((m->m_pkthdr.csum_flags
225 & (M_CSUM_UDPv4|M_CSUM_TCPv4|M_CSUM_TSOv4)) == 0);
226
227 offset = M_CSUM_DATA_IPv6_IPHL(m->m_pkthdr.csum_data);
228 csum = in6_cksum(m, 0, offset, m->m_pkthdr.len - offset);
229 if (csum == 0 && (m->m_pkthdr.csum_flags & M_CSUM_UDPv6) != 0) {
230 csum = 0xffff;
231 }
232
233 offset += M_CSUM_DATA_IPv6_OFFSET(m->m_pkthdr.csum_data);
234 if ((offset + sizeof(csum)) > m->m_len) {
235 m_copyback(m, offset, sizeof(csum), &csum);
236 } else {
237 *(uint16_t *)(mtod(m, char *) + offset) = csum;
238 }
239 }
240