jcs's openbsd hax
openbsd
1/* $OpenBSD: ip_ipcomp.c,v 1.96 2025/12/11 05:06:02 dlg Exp $ */
2
3/*
4 * Copyright (c) 2001 Jean-Jacques Bernard-Gundol (jj@wabbitt.org)
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
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 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/* IP payload compression protocol (IPComp), see RFC 2393 */
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/mbuf.h>
35#include <sys/socket.h>
36
37#include <net/if.h>
38#include <net/if_var.h>
39#include <net/bpf.h>
40
41#include <netinet/in.h>
42#include <netinet/ip.h>
43
44#ifdef INET6
45#include <netinet/ip6.h>
46#endif /* INET6 */
47
48#include <netinet/ip_ipsp.h>
49#include <netinet/ip_ipcomp.h>
50#include <net/pfkeyv2.h>
51#include <net/if_enc.h>
52
53#include <crypto/cryptodev.h>
54#include <crypto/xform.h>
55
56#include "bpfilter.h"
57
58#ifdef ENCDEBUG
59#define DPRINTF(fmt, args...) \
60 do { \
61 if (atomic_load_int(&encdebug)) \
62 printf("%s: " fmt "\n", __func__, ## args); \
63 } while (0)
64#else
65#define DPRINTF(fmt, args...) \
66 do { } while (0)
67#endif
68
69/*
70 * ipcomp_attach() is called from the transformation code
71 */
72int
73ipcomp_attach(void)
74{
75 return 0;
76}
77
78/*
79 * ipcomp_init() is called when an CPI is being set up.
80 */
81int
82ipcomp_init(struct tdb *tdbp, const struct xformsw *xsp, struct ipsecinit *ii)
83{
84 const struct comp_algo *tcomp = NULL;
85 struct cryptoini cric;
86 int error;
87
88 switch (ii->ii_compalg) {
89 case SADB_X_CALG_DEFLATE:
90 tcomp = &comp_algo_deflate;
91 break;
92 default:
93 DPRINTF("unsupported compression algorithm %d specified",
94 ii->ii_compalg);
95 return EINVAL;
96 }
97
98 tdbp->tdb_compalgxform = tcomp;
99
100 DPRINTF("initialized TDB with ipcomp algorithm %s", tcomp->name);
101
102 tdbp->tdb_xform = xsp;
103
104 /* Initialize crypto session */
105 memset(&cric, 0, sizeof(cric));
106 cric.cri_alg = tdbp->tdb_compalgxform->type;
107
108 KERNEL_LOCK();
109 error = crypto_newsession(&tdbp->tdb_cryptoid, &cric, 0);
110 KERNEL_UNLOCK();
111 return error;
112}
113
114/*
115 * ipcomp_zeroize() used when IPCA is deleted
116 */
117int
118ipcomp_zeroize(struct tdb *tdbp)
119{
120 int error;
121
122 KERNEL_LOCK();
123 error = crypto_freesession(tdbp->tdb_cryptoid);
124 KERNEL_UNLOCK();
125 tdbp->tdb_cryptoid = 0;
126 return error;
127}
128
129/*
130 * ipcomp_input() gets called to uncompress an input packet
131 */
132int
133ipcomp_input(struct mbuf **mp, struct tdb *tdb, int skip, int protoff,
134 struct netstack *ns)
135{
136 const struct comp_algo *ipcompx = tdb->tdb_compalgxform;
137 struct mbuf *m = *mp;
138 struct cryptodesc *crdc = NULL;
139 struct cryptop *crp;
140 int hlen, error, clen, roff;
141 u_int8_t nproto;
142 u_int64_t ibytes;
143 struct mbuf *m1, *mo;
144 struct ipcomp *ipcomp;
145 caddr_t addr;
146#ifdef ENCDEBUG
147 char buf[INET6_ADDRSTRLEN];
148#endif
149
150 hlen = IPCOMP_HLENGTH;
151
152 /* Get crypto descriptors */
153 crp = crypto_getreq(1);
154 if (crp == NULL) {
155 DPRINTF("failed to acquire crypto descriptors");
156 ipcompstat_inc(ipcomps_crypto);
157 goto drop;
158 }
159 crdc = &crp->crp_desc[0];
160
161 crdc->crd_skip = skip + hlen;
162 crdc->crd_len = m->m_pkthdr.len - (skip + hlen);
163 crdc->crd_inject = skip;
164
165 /* Decompression operation */
166 crdc->crd_alg = ipcompx->type;
167
168 /* Crypto operation descriptor */
169 crp->crp_ilen = m->m_pkthdr.len - (skip + hlen);
170 crp->crp_flags = CRYPTO_F_IMBUF;
171 crp->crp_buf = (caddr_t)m;
172 crp->crp_sid = tdb->tdb_cryptoid;
173
174 while ((error = crypto_invoke(crp)) == EAGAIN) {
175 /* Reset the session ID */
176 if (tdb->tdb_cryptoid != 0)
177 tdb->tdb_cryptoid = crp->crp_sid;
178 }
179 if (error) {
180 DPRINTF("crypto error %d", error);
181 ipsecstat_inc(ipsec_noxform);
182 goto drop;
183 }
184
185 clen = crp->crp_olen;
186
187 /* Release the crypto descriptors */
188 crypto_freereq(crp);
189 crp = NULL;
190
191 /* update the counters */
192 ibytes = m->m_pkthdr.len - (skip + hlen);
193 tdb->tdb_cur_bytes += ibytes;
194 tdbstat_add(tdb, tdb_ibytes, ibytes);
195 ipcompstat_add(ipcomps_ibytes, ibytes);
196
197 /* Hard expiration */
198 if ((tdb->tdb_flags & TDBF_BYTES) &&
199 (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) {
200 ipsecstat_inc(ipsec_exctdb);
201 pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD);
202 tdb_delete(tdb);
203 goto drop;
204 }
205 /* Notify on soft expiration */
206 mtx_enter(&tdb->tdb_mtx);
207 if ((tdb->tdb_flags & TDBF_SOFT_BYTES) &&
208 (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) {
209 tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */
210 mtx_leave(&tdb->tdb_mtx);
211 /* may sleep in solock() for the pfkey socket */
212 pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
213 } else
214 mtx_leave(&tdb->tdb_mtx);
215
216 /* In case it's not done already, adjust the size of the mbuf chain */
217 m->m_pkthdr.len = clen + hlen + skip;
218
219 if (m->m_len < skip + hlen &&
220 (m = *mp = m_pullup(m, skip + hlen)) == NULL) {
221 ipcompstat_inc(ipcomps_hdrops);
222 goto drop;
223 }
224
225 /* Find the beginning of the IPCOMP header */
226 m1 = m_getptr(m, skip, &roff);
227 if (m1 == NULL) {
228 DPRINTF("bad mbuf chain, IPCA %s/%08x",
229 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
230 ntohl(tdb->tdb_spi));
231 ipcompstat_inc(ipcomps_hdrops);
232 goto drop;
233 }
234 /* Keep the next protocol field */
235 addr = (caddr_t) mtod(m, struct ip *) + skip;
236 ipcomp = (struct ipcomp *) addr;
237 nproto = ipcomp->ipcomp_nh;
238
239 /* Remove the IPCOMP header from the mbuf */
240 if (roff == 0) {
241 /* The IPCOMP header is at the beginning of m1 */
242 m_adj(m1, hlen);
243 /*
244 * If m1 is the first mbuf, it has set M_PKTHDR and m_adj()
245 * has already adjusted the packet header length for us.
246 */
247 if (m1 != m)
248 m->m_pkthdr.len -= hlen;
249 } else if (roff + hlen >= m1->m_len) {
250 int adjlen;
251
252 if (roff + hlen > m1->m_len) {
253 adjlen = roff + hlen - m1->m_len;
254
255 /* Adjust the next mbuf by the remainder */
256 m_adj(m1->m_next, adjlen);
257
258 /*
259 * The second mbuf is guaranteed not to have a
260 * pkthdr...
261 */
262 m->m_pkthdr.len -= adjlen;
263 }
264 /* Now, let's unlink the mbuf chain for a second... */
265 mo = m1->m_next;
266 m1->m_next = NULL;
267
268 /* ...and trim the end of the first part of the chain...sick */
269 adjlen = m1->m_len - roff;
270 m_adj(m1, -adjlen);
271 /*
272 * If m1 is the first mbuf, it has set M_PKTHDR and m_adj()
273 * has already adjusted the packet header length for us.
274 */
275 if (m1 != m)
276 m->m_pkthdr.len -= adjlen;
277
278 /* Finally, let's relink */
279 m1->m_next = mo;
280 } else {
281 memmove(mtod(m1, u_char *) + roff,
282 mtod(m1, u_char *) + roff + hlen,
283 m1->m_len - (roff + hlen));
284 m1->m_len -= hlen;
285 m->m_pkthdr.len -= hlen;
286 }
287
288 /* Restore the Next Protocol field */
289 m_copyback(m, protoff, sizeof(u_int8_t), &nproto, M_NOWAIT);
290
291 /* Back to generic IPsec input processing */
292 return ipsec_common_input_cb(mp, tdb, skip, protoff, ns);
293
294 drop:
295 m_freemp(mp);
296 crypto_freereq(crp);
297 return IPPROTO_DONE;
298}
299
300/*
301 * IPComp output routine, called by ipsp_process_packet()
302 */
303int
304ipcomp_output(struct mbuf *m, struct tdb *tdb, int skip, int protoff)
305{
306 const struct comp_algo *ipcompx = tdb->tdb_compalgxform;
307 int error, hlen, ilen, olen, rlen, roff;
308 struct cryptodesc *crdc = NULL;
309 struct cryptop *crp = NULL;
310 struct mbuf *mi, *mo;
311 struct ip *ip;
312 u_int16_t cpi;
313#ifdef INET6
314 struct ip6_hdr *ip6;
315#endif
316#ifdef ENCDEBUG
317 char buf[INET6_ADDRSTRLEN];
318#endif
319#if NBPFILTER > 0
320 struct ifnet *encif;
321 struct ipcomp *ipcomp;
322
323 if ((encif = enc_getif(0, tdb->tdb_tap)) != NULL) {
324 encif->if_opackets++;
325 encif->if_obytes += m->m_pkthdr.len;
326
327 if (encif->if_bpf) {
328 struct enchdr hdr = {
329 .af = htonl(tdb->tdb_dst.sa.sa_family),
330 .spi = tdb->tdb_spi,
331 };
332
333 bpf_mtap_hdr(encif->if_bpf, (char *)&hdr,
334 ENC_HDRLEN, m, BPF_DIRECTION_OUT);
335 }
336 }
337#endif
338 hlen = IPCOMP_HLENGTH;
339
340 ipcompstat_inc(ipcomps_output);
341
342 switch (tdb->tdb_dst.sa.sa_family) {
343 case AF_INET:
344 /* Check for IPv4 maximum packet size violations */
345 /*
346 * Since compression is going to reduce the size, no need to
347 * worry
348 */
349 if (m->m_pkthdr.len + hlen > IP_MAXPACKET) {
350 DPRINTF("packet in IPCA %s/%08x got too big",
351 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
352 ntohl(tdb->tdb_spi));
353 ipcompstat_inc(ipcomps_toobig);
354 error = EMSGSIZE;
355 goto drop;
356 }
357 break;
358
359#ifdef INET6
360 case AF_INET6:
361 /* Check for IPv6 maximum packet size violations */
362 if (m->m_pkthdr.len + hlen > IPV6_MAXPACKET) {
363 DPRINTF("packet in IPCA %s/%08x got too big",
364 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
365 ntohl(tdb->tdb_spi));
366 ipcompstat_inc(ipcomps_toobig);
367 error = EMSGSIZE;
368 goto drop;
369 }
370 break;
371#endif /* INET6 */
372
373 default:
374 DPRINTF("unknown/unsupported protocol family %d, IPCA %s/%08x",
375 tdb->tdb_dst.sa.sa_family,
376 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
377 ntohl(tdb->tdb_spi));
378 ipcompstat_inc(ipcomps_nopf);
379 error = EPFNOSUPPORT;
380 goto drop;
381 }
382
383 /* Update the counters */
384 tdb->tdb_cur_bytes += m->m_pkthdr.len - skip;
385 ipcompstat_add(ipcomps_obytes, m->m_pkthdr.len - skip);
386
387 /* Hard byte expiration */
388 if ((tdb->tdb_flags & TDBF_BYTES) &&
389 (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) {
390 ipsecstat_inc(ipsec_exctdb);
391 pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD);
392 tdb_delete(tdb);
393 error = EINVAL;
394 goto drop;
395 }
396
397 /* Soft byte expiration */
398 mtx_enter(&tdb->tdb_mtx);
399 if ((tdb->tdb_flags & TDBF_SOFT_BYTES) &&
400 (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) {
401 tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */
402 mtx_leave(&tdb->tdb_mtx);
403 /* may sleep in solock() for the pfkey socket */
404 pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
405 } else
406 mtx_leave(&tdb->tdb_mtx);
407
408 /*
409 * Loop through mbuf chain; if we find a readonly mbuf,
410 * copy the packet.
411 */
412 mi = m;
413 while (mi != NULL && !M_READONLY(mi))
414 mi = mi->m_next;
415
416 if (mi != NULL) {
417 struct mbuf *n = m_dup_pkt(m, 0, M_DONTWAIT);
418
419 if (n == NULL) {
420 DPRINTF("bad mbuf chain, IPCA %s/%08x",
421 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
422 ntohl(tdb->tdb_spi));
423 ipcompstat_inc(ipcomps_hdrops);
424 error = ENOBUFS;
425 goto drop;
426 }
427
428 m_freem(m);
429 m = n;
430 }
431 /* Ok now, we can pass to the crypto processing */
432
433 /* Get crypto descriptors */
434 crp = crypto_getreq(1);
435 if (crp == NULL) {
436 DPRINTF("failed to acquire crypto descriptors");
437 ipcompstat_inc(ipcomps_crypto);
438 error = ENOBUFS;
439 goto drop;
440 }
441 crdc = &crp->crp_desc[0];
442
443 /* Compression descriptor */
444 crdc->crd_skip = skip;
445 crdc->crd_len = m->m_pkthdr.len - skip;
446 crdc->crd_flags = CRD_F_COMP;
447 crdc->crd_inject = skip;
448
449 /* Compression operation */
450 crdc->crd_alg = ipcompx->type;
451
452 /* Crypto operation descriptor */
453 crp->crp_ilen = m->m_pkthdr.len; /* Total input length */
454 crp->crp_flags = CRYPTO_F_IMBUF;
455 crp->crp_buf = (caddr_t)m;
456 crp->crp_sid = tdb->tdb_cryptoid;
457
458 while ((error = crypto_invoke(crp)) == EAGAIN) {
459 /* Reset the session ID */
460 if (tdb->tdb_cryptoid != 0)
461 tdb->tdb_cryptoid = crp->crp_sid;
462 }
463 if (error) {
464 DPRINTF("crypto error %d", error);
465 ipsecstat_inc(ipsec_noxform);
466 goto drop;
467 }
468
469 ilen = crp->crp_ilen;
470 olen = crp->crp_olen;
471
472 /* Release the crypto descriptors */
473 crypto_freereq(crp);
474 crp = NULL;
475
476 rlen = ilen - skip;
477
478 /* Check sizes. */
479 if (rlen <= olen + IPCOMP_HLENGTH) {
480 /* Compression was useless, we have lost time. */
481 ipcompstat_inc(ipcomps_minlen); /* misnomer, but like to count */
482 goto skiphdr;
483 }
484
485 /* Inject IPCOMP header */
486 mo = m_makespace(m, skip, IPCOMP_HLENGTH, &roff);
487 if (mo == NULL) {
488 DPRINTF("ailed to inject IPCOMP header for IPCA %s/%08x",
489 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
490 ntohl(tdb->tdb_spi));
491 ipcompstat_inc(ipcomps_wrap);
492 error = ENOBUFS;
493 goto drop;
494 }
495
496 /* Initialize the IPCOMP header */
497 ipcomp = (struct ipcomp *)(mtod(mo, caddr_t) + roff);
498 memset(ipcomp, 0, sizeof(struct ipcomp));
499 cpi = (u_int16_t) ntohl(tdb->tdb_spi);
500 ipcomp->ipcomp_cpi = htons(cpi);
501
502 /* m_pullup before ? */
503 switch (tdb->tdb_dst.sa.sa_family) {
504 case AF_INET:
505 ip = mtod(m, struct ip *);
506 ipcomp->ipcomp_nh = ip->ip_p;
507 ip->ip_p = IPPROTO_IPCOMP;
508 break;
509#ifdef INET6
510 case AF_INET6:
511 ip6 = mtod(m, struct ip6_hdr *);
512 ipcomp->ipcomp_nh = ip6->ip6_nxt;
513 ip6->ip6_nxt = IPPROTO_IPCOMP;
514 break;
515#endif
516 default:
517 DPRINTF("unsupported protocol family %d, IPCA %s/%08x",
518 tdb->tdb_dst.sa.sa_family,
519 ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
520 ntohl(tdb->tdb_spi));
521 ipcompstat_inc(ipcomps_nopf);
522 error = EPFNOSUPPORT;
523 goto drop;
524 }
525
526 skiphdr:
527 error = ipsp_process_done(m, tdb);
528 if (error)
529 ipcompstat_inc(ipcomps_outfail);
530 return error;
531
532 drop:
533 m_freem(m);
534 crypto_freereq(crp);
535 return error;
536}