539 lines
13 KiB
C
539 lines
13 KiB
C
/* $OpenBSD: ip_ipcomp.c,v 1.92 2022/05/03 09:18:11 claudio Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2001 Jean-Jacques Bernard-Gundol (jj@wabbitt.org)
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/* IP payload compression protocol (IPComp), see RFC 2393 */
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/bpf.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip_var.h>
|
|
|
|
#ifdef INET6
|
|
#include <netinet/ip6.h>
|
|
#endif /* INET6 */
|
|
|
|
#include <netinet/ip_ipsp.h>
|
|
#include <netinet/ip_ipcomp.h>
|
|
#include <net/pfkeyv2.h>
|
|
#include <net/if_enc.h>
|
|
|
|
#include <crypto/cryptodev.h>
|
|
#include <crypto/xform.h>
|
|
|
|
#include "bpfilter.h"
|
|
|
|
#ifdef ENCDEBUG
|
|
#define DPRINTF(fmt, args...) \
|
|
do { \
|
|
if (encdebug) \
|
|
printf("%s: " fmt "\n", __func__, ## args); \
|
|
} while (0)
|
|
#else
|
|
#define DPRINTF(fmt, args...) \
|
|
do { } while (0)
|
|
#endif
|
|
|
|
/*
|
|
* ipcomp_attach() is called from the transformation code
|
|
*/
|
|
int
|
|
ipcomp_attach(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ipcomp_init() is called when an CPI is being set up.
|
|
*/
|
|
int
|
|
ipcomp_init(struct tdb *tdbp, const struct xformsw *xsp, struct ipsecinit *ii)
|
|
{
|
|
const struct comp_algo *tcomp = NULL;
|
|
struct cryptoini cric;
|
|
int error;
|
|
|
|
switch (ii->ii_compalg) {
|
|
case SADB_X_CALG_DEFLATE:
|
|
tcomp = &comp_algo_deflate;
|
|
break;
|
|
default:
|
|
DPRINTF("unsupported compression algorithm %d specified",
|
|
ii->ii_compalg);
|
|
return EINVAL;
|
|
}
|
|
|
|
tdbp->tdb_compalgxform = tcomp;
|
|
|
|
DPRINTF("initialized TDB with ipcomp algorithm %s", tcomp->name);
|
|
|
|
tdbp->tdb_xform = xsp;
|
|
|
|
/* Initialize crypto session */
|
|
memset(&cric, 0, sizeof(cric));
|
|
cric.cri_alg = tdbp->tdb_compalgxform->type;
|
|
|
|
KERNEL_LOCK();
|
|
error = crypto_newsession(&tdbp->tdb_cryptoid, &cric, 0);
|
|
KERNEL_UNLOCK();
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* ipcomp_zeroize() used when IPCA is deleted
|
|
*/
|
|
int
|
|
ipcomp_zeroize(struct tdb *tdbp)
|
|
{
|
|
int error;
|
|
|
|
KERNEL_LOCK();
|
|
error = crypto_freesession(tdbp->tdb_cryptoid);
|
|
KERNEL_UNLOCK();
|
|
tdbp->tdb_cryptoid = 0;
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* ipcomp_input() gets called to uncompress an input packet
|
|
*/
|
|
int
|
|
ipcomp_input(struct mbuf **mp, struct tdb *tdb, int skip, int protoff)
|
|
{
|
|
const struct comp_algo *ipcompx = tdb->tdb_compalgxform;
|
|
struct mbuf *m = *mp;
|
|
struct cryptodesc *crdc = NULL;
|
|
struct cryptop *crp;
|
|
int hlen, error, clen, roff;
|
|
u_int8_t nproto;
|
|
u_int64_t ibytes;
|
|
struct mbuf *m1, *mo;
|
|
struct ipcomp *ipcomp;
|
|
caddr_t addr;
|
|
#ifdef ENCDEBUG
|
|
char buf[INET6_ADDRSTRLEN];
|
|
#endif
|
|
|
|
hlen = IPCOMP_HLENGTH;
|
|
|
|
/* Get crypto descriptors */
|
|
crp = crypto_getreq(1);
|
|
if (crp == NULL) {
|
|
DPRINTF("failed to acquire crypto descriptors");
|
|
ipcompstat_inc(ipcomps_crypto);
|
|
goto drop;
|
|
}
|
|
crdc = &crp->crp_desc[0];
|
|
|
|
crdc->crd_skip = skip + hlen;
|
|
crdc->crd_len = m->m_pkthdr.len - (skip + hlen);
|
|
crdc->crd_inject = skip;
|
|
|
|
/* Decompression operation */
|
|
crdc->crd_alg = ipcompx->type;
|
|
|
|
/* Crypto operation descriptor */
|
|
crp->crp_ilen = m->m_pkthdr.len - (skip + hlen);
|
|
crp->crp_flags = CRYPTO_F_IMBUF;
|
|
crp->crp_buf = (caddr_t)m;
|
|
crp->crp_sid = tdb->tdb_cryptoid;
|
|
|
|
while ((error = crypto_invoke(crp)) == EAGAIN) {
|
|
/* Reset the session ID */
|
|
if (tdb->tdb_cryptoid != 0)
|
|
tdb->tdb_cryptoid = crp->crp_sid;
|
|
}
|
|
if (error) {
|
|
DPRINTF("crypto error %d", error);
|
|
ipsecstat_inc(ipsec_noxform);
|
|
goto drop;
|
|
}
|
|
|
|
clen = crp->crp_olen;
|
|
|
|
/* Release the crypto descriptors */
|
|
crypto_freereq(crp);
|
|
crp = NULL;
|
|
|
|
/* update the counters */
|
|
ibytes = m->m_pkthdr.len - (skip + hlen);
|
|
tdb->tdb_cur_bytes += ibytes;
|
|
tdbstat_add(tdb, tdb_ibytes, ibytes);
|
|
ipcompstat_add(ipcomps_ibytes, ibytes);
|
|
|
|
/* Hard expiration */
|
|
if ((tdb->tdb_flags & TDBF_BYTES) &&
|
|
(tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) {
|
|
ipsecstat_inc(ipsec_exctdb);
|
|
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD);
|
|
tdb_delete(tdb);
|
|
goto drop;
|
|
}
|
|
/* Notify on soft expiration */
|
|
mtx_enter(&tdb->tdb_mtx);
|
|
if ((tdb->tdb_flags & TDBF_SOFT_BYTES) &&
|
|
(tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) {
|
|
tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */
|
|
mtx_leave(&tdb->tdb_mtx);
|
|
/* may sleep in solock() for the pfkey socket */
|
|
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
|
|
} else
|
|
mtx_leave(&tdb->tdb_mtx);
|
|
|
|
/* In case it's not done already, adjust the size of the mbuf chain */
|
|
m->m_pkthdr.len = clen + hlen + skip;
|
|
|
|
if (m->m_len < skip + hlen &&
|
|
(m = *mp = m_pullup(m, skip + hlen)) == NULL) {
|
|
ipcompstat_inc(ipcomps_hdrops);
|
|
goto drop;
|
|
}
|
|
|
|
/* Find the beginning of the IPCOMP header */
|
|
m1 = m_getptr(m, skip, &roff);
|
|
if (m1 == NULL) {
|
|
DPRINTF("bad mbuf chain, IPCA %s/%08x",
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_hdrops);
|
|
goto drop;
|
|
}
|
|
/* Keep the next protocol field */
|
|
addr = (caddr_t) mtod(m, struct ip *) + skip;
|
|
ipcomp = (struct ipcomp *) addr;
|
|
nproto = ipcomp->ipcomp_nh;
|
|
|
|
/* Remove the IPCOMP header from the mbuf */
|
|
if (roff == 0) {
|
|
/* The IPCOMP header is at the beginning of m1 */
|
|
m_adj(m1, hlen);
|
|
/*
|
|
* If m1 is the first mbuf, it has set M_PKTHDR and m_adj()
|
|
* has already adjusted the packet header length for us.
|
|
*/
|
|
if (m1 != m)
|
|
m->m_pkthdr.len -= hlen;
|
|
} else if (roff + hlen >= m1->m_len) {
|
|
int adjlen;
|
|
|
|
if (roff + hlen > m1->m_len) {
|
|
adjlen = roff + hlen - m1->m_len;
|
|
|
|
/* Adjust the next mbuf by the remainder */
|
|
m_adj(m1->m_next, adjlen);
|
|
|
|
/*
|
|
* The second mbuf is guaranteed not to have a
|
|
* pkthdr...
|
|
*/
|
|
m->m_pkthdr.len -= adjlen;
|
|
}
|
|
/* Now, let's unlink the mbuf chain for a second... */
|
|
mo = m1->m_next;
|
|
m1->m_next = NULL;
|
|
|
|
/* ...and trim the end of the first part of the chain...sick */
|
|
adjlen = m1->m_len - roff;
|
|
m_adj(m1, -adjlen);
|
|
/*
|
|
* If m1 is the first mbuf, it has set M_PKTHDR and m_adj()
|
|
* has already adjusted the packet header length for us.
|
|
*/
|
|
if (m1 != m)
|
|
m->m_pkthdr.len -= adjlen;
|
|
|
|
/* Finally, let's relink */
|
|
m1->m_next = mo;
|
|
} else {
|
|
memmove(mtod(m1, u_char *) + roff,
|
|
mtod(m1, u_char *) + roff + hlen,
|
|
m1->m_len - (roff + hlen));
|
|
m1->m_len -= hlen;
|
|
m->m_pkthdr.len -= hlen;
|
|
}
|
|
|
|
/* Restore the Next Protocol field */
|
|
m_copyback(m, protoff, sizeof(u_int8_t), &nproto, M_NOWAIT);
|
|
|
|
/* Back to generic IPsec input processing */
|
|
return ipsec_common_input_cb(mp, tdb, skip, protoff);
|
|
|
|
drop:
|
|
m_freemp(mp);
|
|
crypto_freereq(crp);
|
|
return IPPROTO_DONE;
|
|
}
|
|
|
|
/*
|
|
* IPComp output routine, called by ipsp_process_packet()
|
|
*/
|
|
int
|
|
ipcomp_output(struct mbuf *m, struct tdb *tdb, int skip, int protoff)
|
|
{
|
|
const struct comp_algo *ipcompx = tdb->tdb_compalgxform;
|
|
int error, hlen, ilen, olen, rlen, roff;
|
|
struct cryptodesc *crdc = NULL;
|
|
struct cryptop *crp = NULL;
|
|
struct mbuf *mi, *mo;
|
|
struct ip *ip;
|
|
u_int16_t cpi;
|
|
#ifdef INET6
|
|
struct ip6_hdr *ip6;
|
|
#endif
|
|
#ifdef ENCDEBUG
|
|
char buf[INET6_ADDRSTRLEN];
|
|
#endif
|
|
#if NBPFILTER > 0
|
|
struct ifnet *encif;
|
|
struct ipcomp *ipcomp;
|
|
|
|
if ((encif = enc_getif(0, tdb->tdb_tap)) != NULL) {
|
|
encif->if_opackets++;
|
|
encif->if_obytes += m->m_pkthdr.len;
|
|
|
|
if (encif->if_bpf) {
|
|
struct enchdr hdr;
|
|
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
|
|
hdr.af = tdb->tdb_dst.sa.sa_family;
|
|
hdr.spi = tdb->tdb_spi;
|
|
|
|
bpf_mtap_hdr(encif->if_bpf, (char *)&hdr,
|
|
ENC_HDRLEN, m, BPF_DIRECTION_OUT);
|
|
}
|
|
}
|
|
#endif
|
|
hlen = IPCOMP_HLENGTH;
|
|
|
|
ipcompstat_inc(ipcomps_output);
|
|
|
|
switch (tdb->tdb_dst.sa.sa_family) {
|
|
case AF_INET:
|
|
/* Check for IPv4 maximum packet size violations */
|
|
/*
|
|
* Since compression is going to reduce the size, no need to
|
|
* worry
|
|
*/
|
|
if (m->m_pkthdr.len + hlen > IP_MAXPACKET) {
|
|
DPRINTF("packet in IPCA %s/%08x got too big",
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_toobig);
|
|
error = EMSGSIZE;
|
|
goto drop;
|
|
}
|
|
break;
|
|
|
|
#ifdef INET6
|
|
case AF_INET6:
|
|
/* Check for IPv6 maximum packet size violations */
|
|
if (m->m_pkthdr.len + hlen > IPV6_MAXPACKET) {
|
|
DPRINTF("packet in IPCA %s/%08x got too big",
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_toobig);
|
|
error = EMSGSIZE;
|
|
goto drop;
|
|
}
|
|
break;
|
|
#endif /* INET6 */
|
|
|
|
default:
|
|
DPRINTF("unknown/unsupported protocol family %d, IPCA %s/%08x",
|
|
tdb->tdb_dst.sa.sa_family,
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_nopf);
|
|
error = EPFNOSUPPORT;
|
|
goto drop;
|
|
}
|
|
|
|
/* Update the counters */
|
|
tdb->tdb_cur_bytes += m->m_pkthdr.len - skip;
|
|
ipcompstat_add(ipcomps_obytes, m->m_pkthdr.len - skip);
|
|
|
|
/* Hard byte expiration */
|
|
if ((tdb->tdb_flags & TDBF_BYTES) &&
|
|
(tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) {
|
|
ipsecstat_inc(ipsec_exctdb);
|
|
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD);
|
|
tdb_delete(tdb);
|
|
error = EINVAL;
|
|
goto drop;
|
|
}
|
|
|
|
/* Soft byte expiration */
|
|
mtx_enter(&tdb->tdb_mtx);
|
|
if ((tdb->tdb_flags & TDBF_SOFT_BYTES) &&
|
|
(tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) {
|
|
tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */
|
|
mtx_leave(&tdb->tdb_mtx);
|
|
/* may sleep in solock() for the pfkey socket */
|
|
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
|
|
} else
|
|
mtx_leave(&tdb->tdb_mtx);
|
|
|
|
/*
|
|
* Loop through mbuf chain; if we find a readonly mbuf,
|
|
* copy the packet.
|
|
*/
|
|
mi = m;
|
|
while (mi != NULL && !M_READONLY(mi))
|
|
mi = mi->m_next;
|
|
|
|
if (mi != NULL) {
|
|
struct mbuf *n = m_dup_pkt(m, 0, M_DONTWAIT);
|
|
|
|
if (n == NULL) {
|
|
DPRINTF("bad mbuf chain, IPCA %s/%08x",
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_hdrops);
|
|
error = ENOBUFS;
|
|
goto drop;
|
|
}
|
|
|
|
m_freem(m);
|
|
m = n;
|
|
}
|
|
/* Ok now, we can pass to the crypto processing */
|
|
|
|
/* Get crypto descriptors */
|
|
crp = crypto_getreq(1);
|
|
if (crp == NULL) {
|
|
DPRINTF("failed to acquire crypto descriptors");
|
|
ipcompstat_inc(ipcomps_crypto);
|
|
error = ENOBUFS;
|
|
goto drop;
|
|
}
|
|
crdc = &crp->crp_desc[0];
|
|
|
|
/* Compression descriptor */
|
|
crdc->crd_skip = skip;
|
|
crdc->crd_len = m->m_pkthdr.len - skip;
|
|
crdc->crd_flags = CRD_F_COMP;
|
|
crdc->crd_inject = skip;
|
|
|
|
/* Compression operation */
|
|
crdc->crd_alg = ipcompx->type;
|
|
|
|
/* Crypto operation descriptor */
|
|
crp->crp_ilen = m->m_pkthdr.len; /* Total input length */
|
|
crp->crp_flags = CRYPTO_F_IMBUF;
|
|
crp->crp_buf = (caddr_t)m;
|
|
crp->crp_sid = tdb->tdb_cryptoid;
|
|
|
|
while ((error = crypto_invoke(crp)) == EAGAIN) {
|
|
/* Reset the session ID */
|
|
if (tdb->tdb_cryptoid != 0)
|
|
tdb->tdb_cryptoid = crp->crp_sid;
|
|
}
|
|
if (error) {
|
|
DPRINTF("crypto error %d", error);
|
|
ipsecstat_inc(ipsec_noxform);
|
|
goto drop;
|
|
}
|
|
|
|
ilen = crp->crp_ilen;
|
|
olen = crp->crp_olen;
|
|
|
|
/* Release the crypto descriptors */
|
|
crypto_freereq(crp);
|
|
crp = NULL;
|
|
|
|
rlen = ilen - skip;
|
|
|
|
/* Check sizes. */
|
|
if (rlen <= olen + IPCOMP_HLENGTH) {
|
|
/* Compression was useless, we have lost time. */
|
|
ipcompstat_inc(ipcomps_minlen); /* misnomer, but like to count */
|
|
goto skiphdr;
|
|
}
|
|
|
|
/* Inject IPCOMP header */
|
|
mo = m_makespace(m, skip, IPCOMP_HLENGTH, &roff);
|
|
if (mo == NULL) {
|
|
DPRINTF("ailed to inject IPCOMP header for IPCA %s/%08x",
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_wrap);
|
|
error = ENOBUFS;
|
|
goto drop;
|
|
}
|
|
|
|
/* Initialize the IPCOMP header */
|
|
ipcomp = (struct ipcomp *)(mtod(mo, caddr_t) + roff);
|
|
memset(ipcomp, 0, sizeof(struct ipcomp));
|
|
cpi = (u_int16_t) ntohl(tdb->tdb_spi);
|
|
ipcomp->ipcomp_cpi = htons(cpi);
|
|
|
|
/* m_pullup before ? */
|
|
switch (tdb->tdb_dst.sa.sa_family) {
|
|
case AF_INET:
|
|
ip = mtod(m, struct ip *);
|
|
ipcomp->ipcomp_nh = ip->ip_p;
|
|
ip->ip_p = IPPROTO_IPCOMP;
|
|
break;
|
|
#ifdef INET6
|
|
case AF_INET6:
|
|
ip6 = mtod(m, struct ip6_hdr *);
|
|
ipcomp->ipcomp_nh = ip6->ip6_nxt;
|
|
ip6->ip6_nxt = IPPROTO_IPCOMP;
|
|
break;
|
|
#endif
|
|
default:
|
|
DPRINTF("unsupported protocol family %d, IPCA %s/%08x",
|
|
tdb->tdb_dst.sa.sa_family,
|
|
ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
|
|
ntohl(tdb->tdb_spi));
|
|
ipcompstat_inc(ipcomps_nopf);
|
|
error = EPFNOSUPPORT;
|
|
goto drop;
|
|
}
|
|
|
|
skiphdr:
|
|
error = ipsp_process_done(m, tdb);
|
|
if (error)
|
|
ipcompstat_inc(ipcomps_outfail);
|
|
return error;
|
|
|
|
drop:
|
|
m_freem(m);
|
|
crypto_freereq(crp);
|
|
return error;
|
|
}
|