#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>
#ifdef INET6
#include <netinet/ip6.h>
#endif
#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 (atomic_load_int(&encdebug)) \
printf("%s: " fmt "\n", __func__, ## args); \
} while (0)
#else
#define DPRINTF(fmt, args...) \
do { } while (0)
#endif
int
ipcomp_attach(void)
{
return 0;
}
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;
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;
}
int
ipcomp_zeroize(struct tdb *tdbp)
{
int error;
KERNEL_LOCK();
error = crypto_freesession(tdbp->tdb_cryptoid);
KERNEL_UNLOCK();
tdbp->tdb_cryptoid = 0;
return error;
}
int
ipcomp_input(struct mbuf **mp, struct tdb *tdb, int skip, int protoff,
struct netstack *ns)
{
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;
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;
crdc->crd_alg = ipcompx->type;
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) {
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;
crypto_freereq(crp);
crp = NULL;
ibytes = m->m_pkthdr.len - (skip + hlen);
tdb->tdb_cur_bytes += ibytes;
tdbstat_add(tdb, tdb_ibytes, ibytes);
ipcompstat_add(ipcomps_ibytes, ibytes);
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;
}
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;
mtx_leave(&tdb->tdb_mtx);
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
} else
mtx_leave(&tdb->tdb_mtx);
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;
}
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;
}
addr = (caddr_t) mtod(m, struct ip *) + skip;
ipcomp = (struct ipcomp *) addr;
nproto = ipcomp->ipcomp_nh;
if (roff == 0) {
m_adj(m1, hlen);
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;
m_adj(m1->m_next, adjlen);
m->m_pkthdr.len -= adjlen;
}
mo = m1->m_next;
m1->m_next = NULL;
adjlen = m1->m_len - roff;
m_adj(m1, -adjlen);
if (m1 != m)
m->m_pkthdr.len -= adjlen;
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;
}
m_copyback(m, protoff, sizeof(u_int8_t), &nproto, M_NOWAIT);
return ipsec_common_input_cb(mp, tdb, skip, protoff, ns);
drop:
m_freemp(mp);
crypto_freereq(crp);
return IPPROTO_DONE;
}
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 = {
.af = htonl(tdb->tdb_dst.sa.sa_family),
.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:
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:
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
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;
}
tdb->tdb_cur_bytes += m->m_pkthdr.len - skip;
ipcompstat_add(ipcomps_obytes, m->m_pkthdr.len - skip);
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;
}
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;
mtx_leave(&tdb->tdb_mtx);
pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT);
} else
mtx_leave(&tdb->tdb_mtx);
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;
}
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];
crdc->crd_skip = skip;
crdc->crd_len = m->m_pkthdr.len - skip;
crdc->crd_flags = CRD_F_COMP;
crdc->crd_inject = skip;
crdc->crd_alg = ipcompx->type;
crp->crp_ilen = m->m_pkthdr.len;
crp->crp_flags = CRYPTO_F_IMBUF;
crp->crp_buf = (caddr_t)m;
crp->crp_sid = tdb->tdb_cryptoid;
while ((error = crypto_invoke(crp)) == EAGAIN) {
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;
crypto_freereq(crp);
crp = NULL;
rlen = ilen - skip;
if (rlen <= olen + IPCOMP_HLENGTH) {
ipcompstat_inc(ipcomps_minlen);
goto skiphdr;
}
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;
}
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);
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;
}