Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/netipsec/udpencap.c
39475 views
1
/*-
2
* Copyright (c) 2016 Andrey V. Elsukov <[email protected]>
3
* All rights reserved.
4
*
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
7
* are met:
8
*
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
*/
26
27
#include <sys/cdefs.h>
28
#include "opt_inet.h"
29
#include "opt_inet6.h"
30
#include "opt_ipsec.h"
31
32
#include <sys/param.h>
33
#include <sys/systm.h>
34
#include <sys/kernel.h>
35
#include <sys/lock.h>
36
#include <sys/mbuf.h>
37
#include <sys/protosw.h>
38
#include <sys/socket.h>
39
#include <sys/sockopt.h>
40
#include <sys/sysctl.h>
41
42
#include <netinet/in.h>
43
#include <netinet/in_pcb.h>
44
#include <netinet/in_systm.h>
45
#include <netinet/ip.h>
46
#include <netinet/ip6.h>
47
#include <netinet/ip_var.h>
48
#include <netinet/tcp.h>
49
#include <netinet/udp.h>
50
#include <netinet/udp_var.h>
51
52
#include <netinet6/ip6_var.h>
53
54
#include <net/vnet.h>
55
56
#include <netipsec/ipsec.h>
57
#include <netipsec/esp.h>
58
#include <netipsec/esp_var.h>
59
#include <netipsec/xform.h>
60
61
#include <netipsec/key.h>
62
#include <netipsec/key_debug.h>
63
#include <netipsec/ipsec_support.h>
64
#include <machine/in_cksum.h>
65
66
/*
67
* Handle UDP_ENCAP socket option. Always return with released INP_WLOCK.
68
*/
69
int
70
udp_ipsec_pcbctl(struct inpcb *inp, struct sockopt *sopt)
71
{
72
struct udpcb *up;
73
int error, optval;
74
75
INP_WLOCK_ASSERT(inp);
76
if (sopt->sopt_name != UDP_ENCAP) {
77
INP_WUNLOCK(inp);
78
return (ENOPROTOOPT);
79
}
80
81
up = intoudpcb(inp);
82
if (sopt->sopt_dir == SOPT_GET) {
83
if (up->u_flags & UF_ESPINUDP)
84
optval = UDP_ENCAP_ESPINUDP;
85
else
86
optval = 0;
87
INP_WUNLOCK(inp);
88
return (sooptcopyout(sopt, &optval, sizeof(optval)));
89
}
90
INP_WUNLOCK(inp);
91
92
error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval));
93
if (error != 0)
94
return (error);
95
96
INP_WLOCK(inp);
97
switch (optval) {
98
case 0:
99
up->u_flags &= ~UF_ESPINUDP;
100
break;
101
case UDP_ENCAP_ESPINUDP:
102
up->u_flags |= UF_ESPINUDP;
103
break;
104
default:
105
error = EINVAL;
106
}
107
INP_WUNLOCK(inp);
108
return (error);
109
}
110
111
/*
112
* Potentially decap ESP in UDP frame. Check for an ESP header.
113
* If present, strip the UDP header and push the result through IPSec.
114
*
115
* Returns error if mbuf consumed and/or processed, otherwise 0.
116
*/
117
int
118
udp_ipsec_input(struct mbuf *m, int off, int af)
119
{
120
union sockaddr_union dst;
121
struct secasvar *sav;
122
struct udphdr *udp;
123
uint32_t spi;
124
int hlen;
125
126
/*
127
* Just return if packet doesn't have enough data.
128
* We need at least [IP header + UDP header + ESP header].
129
* NAT-Keepalive packet has only one byte of payload, so it
130
* by default will not be processed.
131
*/
132
if (m->m_pkthdr.len < off + sizeof(struct esp))
133
return (0);
134
135
m_copydata(m, off, sizeof(uint32_t), (caddr_t)&spi);
136
if (spi == 0) /* Non-ESP marker. */
137
return (0);
138
139
/*
140
* Find SA and check that it is configured for UDP
141
* encapsulation.
142
*/
143
bzero(&dst, sizeof(dst));
144
dst.sa.sa_family = af;
145
switch (af) {
146
#ifdef INET
147
case AF_INET: {
148
struct ip *ip;
149
150
dst.sin.sin_len = sizeof(struct sockaddr_in);
151
ip = mtod(m, struct ip *);
152
ip->ip_p = IPPROTO_ESP;
153
off = offsetof(struct ip, ip_p);
154
hlen = ip->ip_hl << 2;
155
dst.sin.sin_addr = ip->ip_dst;
156
break;
157
}
158
#endif
159
#ifdef INET6
160
case AF_INET6: {
161
struct ip6_hdr *ip6;
162
163
dst.sin6.sin6_len = sizeof(struct sockaddr_in6);
164
ip6 = mtod(m, struct ip6_hdr *);
165
ip6->ip6_nxt = IPPROTO_ESP;
166
off = offsetof(struct ip6_hdr, ip6_nxt);
167
hlen = sizeof(struct ip6_hdr);
168
dst.sin6.sin6_addr = ip6->ip6_dst;
169
break;
170
}
171
#endif
172
default:
173
ESPSTAT_INC(esps_nopf);
174
m_freem(m);
175
return (EPFNOSUPPORT);
176
}
177
178
sav = key_allocsa(&dst, IPPROTO_ESP, spi);
179
if (sav == NULL) {
180
ESPSTAT_INC(esps_notdb);
181
m_freem(m);
182
return (ENOENT);
183
}
184
udp = mtodo(m, hlen);
185
if (sav->natt == NULL ||
186
sav->natt->sport != udp->uh_sport ||
187
sav->natt->dport != udp->uh_dport) {
188
/* XXXAE: should we check source address? */
189
ESPSTAT_INC(esps_notdb);
190
key_freesav(&sav);
191
m_freem(m);
192
return (ENOENT);
193
}
194
/*
195
* Remove the UDP header
196
* Before:
197
* <--- off --->
198
* +----+------+-----+
199
* | IP | UDP | ESP |
200
* +----+------+-----+
201
* <-skip->
202
* After:
203
* +----+-----+
204
* | IP | ESP |
205
* +----+-----+
206
* <-skip->
207
*/
208
m_striphdr(m, hlen, sizeof(*udp));
209
210
/*
211
* We cannot yet update the cksums so clear any h/w cksum flags
212
* as they are no longer valid.
213
*/
214
switch (af) {
215
#ifdef INET
216
case AF_INET:
217
if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID)
218
m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID | CSUM_PSEUDO_HDR);
219
break;
220
#endif /* INET */
221
#ifdef INET6
222
case AF_INET6:
223
if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID_IPV6)
224
m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID_IPV6 | CSUM_PSEUDO_HDR);
225
break;
226
#endif /* INET6 */
227
default:
228
ESPSTAT_INC(esps_nopf);
229
m_freem(m);
230
return (EPFNOSUPPORT);
231
}
232
233
/*
234
* We can update ip_len and ip_sum here, but ipsec4_input_cb()
235
* will do this anyway, so don't touch them here.
236
*/
237
ESPSTAT_INC(esps_input);
238
(*sav->tdb_xform->xf_input)(m, sav, hlen, off);
239
return (EINPROGRESS); /* Consumed by IPsec. */
240
}
241
242
int
243
udp_ipsec_output(struct mbuf *m, struct secasvar *sav)
244
{
245
struct udphdr *udp;
246
struct mbuf *n;
247
int hlen, off;
248
249
IPSEC_ASSERT(sav->natt != NULL, ("UDP encapsulation isn't required."));
250
251
switch (sav->sah->saidx.dst.sa.sa_family) {
252
#ifdef INET
253
case AF_INET: {
254
struct ip *ip;
255
ip = mtod(m, struct ip *);
256
hlen = ip->ip_hl << 2;
257
break;
258
}
259
#endif
260
#ifdef INET6
261
case AF_INET6:
262
hlen = sizeof(struct ip6_hdr);
263
break;
264
#endif
265
default:
266
ESPSTAT_INC(esps_nopf);
267
return (EAFNOSUPPORT);
268
}
269
270
n = m_makespace(m, hlen, sizeof(*udp), &off);
271
if (n == NULL) {
272
DPRINTF(("%s: m_makespace for udphdr failed\n", __func__));
273
return (ENOBUFS);
274
}
275
276
udp = mtodo(n, off);
277
udp->uh_dport = sav->natt->dport;
278
udp->uh_sport = sav->natt->sport;
279
udp->uh_sum = 0;
280
udp->uh_ulen = htons(m->m_pkthdr.len - hlen);
281
282
switch (sav->sah->saidx.dst.sa.sa_family) {
283
#ifdef INET
284
case AF_INET: {
285
struct ip *ip;
286
287
ip = mtod(m, struct ip *);
288
ip->ip_len = htons(m->m_pkthdr.len);
289
ip->ip_p = IPPROTO_UDP;
290
break;
291
}
292
#endif
293
#ifdef INET6
294
case AF_INET6: {
295
struct ip6_hdr *ip6;
296
297
ip6 = mtod(m, struct ip6_hdr *);
298
KASSERT(ip6->ip6_nxt == IPPROTO_ESP,
299
("unexpected next header type %d", ip6->ip6_nxt));
300
ip6->ip6_plen = htons(m->m_pkthdr.len);
301
ip6->ip6_nxt = IPPROTO_UDP;
302
udp->uh_sum = in6_cksum_pseudo(ip6,
303
m->m_pkthdr.len - hlen, ip6->ip6_nxt, 0);
304
m->m_pkthdr.csum_flags = CSUM_UDP_IPV6;
305
m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum);
306
break;
307
}
308
#endif
309
default:
310
ESPSTAT_INC(esps_nopf);
311
return (EAFNOSUPPORT);
312
}
313
314
return (0);
315
}
316
317
void
318
udp_ipsec_adjust_cksum(struct mbuf *m, struct secasvar *sav, int proto,
319
int skip)
320
{
321
uint16_t cksum, off;
322
323
IPSEC_ASSERT(sav->natt != NULL, ("NAT-T isn't required"));
324
IPSEC_ASSERT(proto == IPPROTO_UDP || proto == IPPROTO_TCP,
325
("unexpected protocol %u", proto));
326
327
if (proto == IPPROTO_UDP)
328
off = offsetof(struct udphdr, uh_sum);
329
else
330
off = offsetof(struct tcphdr, th_sum);
331
332
if (V_natt_cksum_policy == 0) { /* auto */
333
if (sav->natt->cksum != 0) {
334
/* Incrementally recompute. */
335
m_copydata(m, skip + off, sizeof(cksum),
336
(caddr_t)&cksum);
337
/* Do not adjust UDP checksum if it is zero. */
338
if (proto == IPPROTO_UDP && cksum == 0)
339
return;
340
cksum = in_addword(cksum, sav->natt->cksum);
341
} else {
342
/* No OA from IKEd. */
343
if (proto == IPPROTO_TCP) {
344
/* Ignore for TCP. */
345
m->m_pkthdr.csum_data = 0xffff;
346
switch (sav->sah->saidx.dst.sa.sa_family) {
347
#ifdef INET
348
case AF_INET:
349
m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID |
350
CSUM_PSEUDO_HDR);
351
break;
352
#endif
353
#ifdef INET6
354
case AF_INET6:
355
m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID_IPV6 |
356
CSUM_PSEUDO_HDR);
357
break;
358
#endif
359
default:
360
break;
361
}
362
return;
363
}
364
cksum = 0; /* Reset for UDP. */
365
}
366
m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum);
367
} else { /* Fully recompute */
368
switch (sav->sah->saidx.dst.sa.sa_family) {
369
#ifdef INET
370
case AF_INET: {
371
struct ip *ip;
372
373
ip = mtod(m, struct ip *);
374
cksum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
375
htons(m->m_pkthdr.len - skip + proto));
376
m_copyback(m, skip + off, sizeof(cksum),
377
(caddr_t)&cksum);
378
m->m_pkthdr.csum_flags =
379
(proto == IPPROTO_UDP) ? CSUM_UDP : CSUM_TCP;
380
m->m_pkthdr.csum_data = off;
381
in_delayed_cksum(m);
382
m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
383
break;
384
}
385
#endif
386
#ifdef INET6
387
case AF_INET6: {
388
struct ip6_hdr *ip6;
389
390
ip6 = mtod(m, struct ip6_hdr *);
391
cksum = in6_cksum_pseudo(ip6, m->m_pkthdr.len - skip,
392
proto, 0);
393
m_copyback(m, skip + off, sizeof(cksum),
394
(caddr_t)&cksum);
395
m->m_pkthdr.csum_flags =
396
(proto == IPPROTO_UDP) ? CSUM_UDP_IPV6 : CSUM_TCP_IPV6;
397
m->m_pkthdr.csum_data = off;
398
in6_delayed_cksum(m,
399
m->m_pkthdr.len - sizeof(struct ip6_hdr),
400
sizeof(struct ip6_hdr));
401
m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA_IPV6;
402
break;
403
}
404
#endif
405
default:
406
break;
407
}
408
}
409
}
410
411