HardenedBSD/sys/netipsec/ipsec_input.c
Konstantin Belousov ef2a572bf6 ipsec_offload: kernel infrastructure
Inline IPSEC offload moves almost whole IPSEC processing from the
CPU/MCU and possibly crypto accelerator, to the network card.

The transmitted packet content is not touched by CPU during TX
operations, kernel only does the required policy and security
association lookups to find out that given flow is offloaded, and then
packet is transmitted as plain text to the card. For driver convenience,
a metadata is attached to the packet identifying SA which must process
the packet. Card does encryption of the payload, padding, calculates
authentication, and does the reformat according to the policy.

Similarly, on receive, card does the decapsulation, decryption, and
authentification.  Kernel receives the identifier of SA that was
used to process the packet, together with the plain-text packet.

Overall, payload octets are only read or written by card DMA engine,
removing a lot of memory subsystem overhead, and saving CPU time because
IPSEC algos calculations are avoided.

If driver declares support for inline IPSEC offload (with the
IFCAP2_IPSEC_OFFLOAD capability set and registering method table struct
if_ipsec_accel_methods), kernel offers the SPD and SAD to driver.
Driver decides which policies and SAs can be offloaded based on
hardware capacity, and acks/nacks each SA for given interface to
kernel.  Kernel needs to keep this information to make a decision to
skip software processing on TX, and to assume processing already done
on RX.  This shadow SPD/SAD database of offloads is rooted from
policies (struct secpolicy accel_ifps, struct ifp_handle_sp) and SAs
(struct secasvar accel_ipfs, struct ifp_handle_sav).

Some extensions to the PF_KEY socket allow to limit interfaces for
which given SP/SA could be offloaded (proposed for offload).  Also,
additional statistics extensions allow to observe allocation/octet/use
counters for specific SA.

Since SPs and SAs are typically instantiated in non-sleepable context,
while offloading them into card is expected to require costly async
manipulations of the card state, calls to the driver for offload and
termination are executed in the threaded taskqueue.  It also solves
the issue of allocating resources needed for the offload database.
Neither ipf_handle_sp nor ipf_handle_sav do not add reference to the
owning SP/SA, the offload must be terminated before last reference is
dropped.  ipsec_accel only adds transient references to ensure safe
pointer ownership by taskqueue.

Maintaining the SA counters for hardware-accelerated packets is the
duty of the driver.  The helper ipsec_accel_drv_sa_lifetime_update()
is provided to hide accel infrastructure from drivers which would use
expected callout to query hardware periodically for updates.

Reviewed by:	rscheff	(transport, stack integration), np
Sponsored by:	NVIDIA networking
Differential revision:	https://reviews.freebsd.org/D44219
2024-07-12 07:27:58 +03:00

796 lines
20 KiB
C

/* $OpenBSD: ipsec_input.c,v 1.63 2003/02/20 18:35:43 deraadt Exp $ */
/*-
* The authors of this code are John Ioannidis (ji@tla.org),
* Angelos D. Keromytis (kermit@csd.uch.gr) and
* Niels Provos (provos@physnet.uni-hamburg.de).
*
* This code was written by John Ioannidis for BSD/OS in Athens, Greece,
* in November 1995.
*
* Ported to OpenBSD and NetBSD, with additional transforms, in December 1996,
* by Angelos D. Keromytis.
*
* Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis
* and Niels Provos.
*
* Additional features in 1999 by Angelos D. Keromytis.
*
* Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis,
* Angelos D. Keromytis and Niels Provos.
* Copyright (c) 2001, Angelos D. Keromytis.
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
*
* Permission to use, copy, and modify this software with or without fee
* is hereby granted, provided that this entire notice is included in
* all copies of any software which is or includes a copy or
* modification of this software.
* You may use this code under the GNU public license if you so wish. Please
* contribute changes back to the authors under this freer than GPL license
* so that we may further the use of strong encryption without limitations to
* all.
*
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE
* MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR
* PURPOSE.
*/
/*
* IPsec input processing.
*/
#include <sys/cdefs.h>
#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_ipsec.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/domain.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/hhook.h>
#include <sys/syslog.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_enc.h>
#include <net/if_private.h>
#include <net/netisr.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_var.h>
#include <netinet/tcp_var.h>
#include <netinet/ip6.h>
#ifdef INET6
#include <netinet6/ip6_var.h>
#endif
#include <netinet/in_pcb.h>
#ifdef INET6
#include <netinet/icmp6.h>
#endif
#include <netipsec/ipsec.h>
#ifdef INET6
#include <netipsec/ipsec6.h>
#endif
#include <netipsec/ipsec_support.h>
#include <netipsec/ah_var.h>
#include <netipsec/esp.h>
#include <netipsec/esp_var.h>
#include <netipsec/ipcomp_var.h>
#include <netipsec/ipsec_offload.h>
#include <netipsec/key.h>
#include <netipsec/keydb.h>
#include <netipsec/key_debug.h>
#include <netipsec/xform.h>
#include <machine/in_cksum.h>
#include <machine/stdarg.h>
#define IPSEC_ISTAT(proto, name) do { \
if ((proto) == IPPROTO_ESP) \
ESPSTAT_INC(esps_##name); \
else if ((proto) == IPPROTO_AH) \
AHSTAT_INC(ahs_##name); \
else \
IPCOMPSTAT_INC(ipcomps_##name); \
} while (0)
/*
* ipsec_common_input gets called when an IPsec-protected packet
* is received by IPv4 or IPv6. Its job is to find the right SA
* and call the appropriate transform. The transform callback
* takes care of further processing (like ingress filtering).
*/
static int
ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto)
{
IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
union sockaddr_union dst_address;
struct secasvar *sav;
uint32_t spi;
int error;
IPSEC_ISTAT(sproto, input);
IPSEC_ASSERT(m != NULL, ("null packet"));
IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH ||
sproto == IPPROTO_IPCOMP,
("unexpected security protocol %u", sproto));
if ((sproto == IPPROTO_ESP && !V_esp_enable) ||
(sproto == IPPROTO_AH && !V_ah_enable) ||
(sproto == IPPROTO_IPCOMP && !V_ipcomp_enable)) {
m_freem(m);
IPSEC_ISTAT(sproto, pdrops);
return EOPNOTSUPP;
}
if (m->m_pkthdr.len - skip < 2 * sizeof (u_int32_t)) {
m_freem(m);
IPSEC_ISTAT(sproto, hdrops);
DPRINTF(("%s: packet too small\n", __func__));
return EINVAL;
}
/* Retrieve the SPI from the relevant IPsec header */
if (sproto == IPPROTO_ESP)
m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi);
else if (sproto == IPPROTO_AH)
m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t),
(caddr_t) &spi);
else if (sproto == IPPROTO_IPCOMP) {
u_int16_t cpi;
m_copydata(m, skip + sizeof(u_int16_t), sizeof(u_int16_t),
(caddr_t) &cpi);
spi = ntohl(htons(cpi));
}
/*
* Find the SA and (indirectly) call the appropriate
* kernel crypto routine. The resulting mbuf chain is a valid
* IP packet ready to go through input processing.
*/
bzero(&dst_address, sizeof (dst_address));
dst_address.sa.sa_family = af;
switch (af) {
#ifdef INET
case AF_INET:
dst_address.sin.sin_len = sizeof(struct sockaddr_in);
m_copydata(m, offsetof(struct ip, ip_dst),
sizeof(struct in_addr),
(caddr_t) &dst_address.sin.sin_addr);
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
dst_address.sin6.sin6_len = sizeof(struct sockaddr_in6);
m_copydata(m, offsetof(struct ip6_hdr, ip6_dst),
sizeof(struct in6_addr),
(caddr_t) &dst_address.sin6.sin6_addr);
/* We keep addresses in SADB without embedded scope id */
if (IN6_IS_SCOPE_LINKLOCAL(&dst_address.sin6.sin6_addr)) {
/* XXX: sa6_recoverscope() */
dst_address.sin6.sin6_scope_id =
ntohs(dst_address.sin6.sin6_addr.s6_addr16[1]);
dst_address.sin6.sin6_addr.s6_addr16[1] = 0;
}
break;
#endif /* INET6 */
default:
DPRINTF(("%s: unsupported protocol family %u\n", __func__, af));
m_freem(m);
IPSEC_ISTAT(sproto, nopf);
return EPFNOSUPPORT;
}
/* NB: only pass dst since key_allocsa follows RFC2401 */
sav = key_allocsa(&dst_address, sproto, spi);
if (sav == NULL) {
DPRINTF(("%s: no key association found for SA %s/%08lx/%u\n",
__func__, ipsec_address(&dst_address, buf, sizeof(buf)),
(u_long) ntohl(spi), sproto));
IPSEC_ISTAT(sproto, notdb);
m_freem(m);
return ENOENT;
}
if (sav->tdb_xform == NULL) {
DPRINTF(("%s: attempted to use uninitialized SA %s/%08lx/%u\n",
__func__, ipsec_address(&dst_address, buf, sizeof(buf)),
(u_long) ntohl(spi), sproto));
IPSEC_ISTAT(sproto, noxform);
key_freesav(&sav);
m_freem(m);
return ENXIO;
}
/*
* Call appropriate transform and return -- callback takes care of
* everything else.
*/
error = (*sav->tdb_xform->xf_input)(m, sav, skip, protoff);
return (error);
}
#ifdef INET
/*
* IPSEC_INPUT() method implementation for IPv4.
* 0 - Permitted by inbound security policy for further processing.
* EACCES - Forbidden by inbound security policy.
* EINPROGRESS - consumed by IPsec.
*/
int
ipsec4_input(struct mbuf *m, int offset, int proto)
{
int error;
error = ipsec_accel_input(m, offset, proto);
if (error != ENXIO)
return (error);
switch (proto) {
case IPPROTO_AH:
case IPPROTO_ESP:
case IPPROTO_IPCOMP:
/* Do inbound IPsec processing for AH/ESP/IPCOMP */
ipsec_common_input(m, offset,
offsetof(struct ip, ip_p), AF_INET, proto);
return (EINPROGRESS); /* mbuf consumed by IPsec */
default:
/*
* Protocols with further headers get their IPsec treatment
* within the protocol specific processing.
*/
switch (proto) {
case IPPROTO_ICMP:
case IPPROTO_IGMP:
case IPPROTO_IPV4:
case IPPROTO_IPV6:
case IPPROTO_RSVP:
case IPPROTO_GRE:
case IPPROTO_MOBILE:
case IPPROTO_ETHERIP:
case IPPROTO_PIM:
case IPPROTO_SCTP:
break;
default:
return (0);
}
};
/*
* Enforce IPsec policy checking if we are seeing last header.
*/
if (ipsec4_in_reject(m, NULL) != 0) {
/* Forbidden by inbound security policy */
m_freem(m);
return (EACCES);
}
return (0);
}
int
ipsec4_ctlinput(ipsec_ctlinput_param_t param)
{
struct icmp *icp = param.icmp;
struct ip *ip = &icp->icmp_ip;
struct sockaddr_in icmpsrc = {
.sin_len = sizeof(struct sockaddr_in),
.sin_family = AF_INET,
.sin_addr = ip->ip_dst,
};
struct in_conninfo inc;
struct secasvar *sav;
uint32_t pmtu, spi;
uint32_t max_pmtu;
uint8_t proto;
pmtu = ntohs(icp->icmp_nextmtu);
if (pmtu < V_ip4_ipsec_min_pmtu)
return (EINVAL);
proto = ip->ip_p;
if (proto != IPPROTO_ESP && proto != IPPROTO_AH &&
proto != IPPROTO_IPCOMP)
return (EINVAL);
memcpy(&spi, (caddr_t)ip + (ip->ip_hl << 2), sizeof(spi));
sav = key_allocsa((union sockaddr_union *)&icmpsrc, proto, spi);
if (sav == NULL)
return (ENOENT);
key_freesav(&sav);
memset(&inc, 0, sizeof(inc));
inc.inc_faddr = ip->ip_dst;
/* Update pmtu only if its smaller than the current one. */
max_pmtu = tcp_hc_getmtu(&inc);
if (max_pmtu == 0)
max_pmtu = tcp_maxmtu(&inc, NULL);
if (pmtu < max_pmtu)
tcp_hc_updatemtu(&inc, pmtu);
return (0);
}
/*
* IPsec input callback for INET protocols.
* This routine is called as the transform callback.
* Takes care of filtering and other sanity checks on
* the processed packet.
*/
int
ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
int protoff)
{
IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
struct epoch_tracker et;
struct ipsec_ctx_data ctx;
struct xform_history *xh;
struct secasindex *saidx;
struct m_tag *mtag;
struct ip *ip;
int error, prot, af, sproto, isr_prot;
IPSEC_ASSERT(sav != NULL, ("null SA"));
IPSEC_ASSERT(sav->sah != NULL, ("null SAH"));
saidx = &sav->sah->saidx;
af = saidx->dst.sa.sa_family;
IPSEC_ASSERT(af == AF_INET, ("unexpected af %u", af));
sproto = saidx->proto;
IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH ||
sproto == IPPROTO_IPCOMP,
("unexpected security protocol %u", sproto));
if (skip != 0) {
/*
* Fix IPv4 header
*/
if (m->m_len < skip && (m = m_pullup(m, skip)) == NULL) {
DPRINTF(("%s: processing failed for SA %s/%08lx\n",
__func__, ipsec_address(&sav->sah->saidx.dst,
buf, sizeof(buf)), (u_long) ntohl(sav->spi)));
IPSEC_ISTAT(sproto, hdrops);
error = ENOBUFS;
goto bad_noepoch;
}
ip = mtod(m, struct ip *);
ip->ip_len = htons(m->m_pkthdr.len);
ip->ip_sum = 0;
ip->ip_sum = in_cksum(m, ip->ip_hl << 2);
} else {
ip = mtod(m, struct ip *);
}
prot = ip->ip_p;
/*
* Check that we have NAT-T enabled and apply transport mode
* decapsulation NAT procedure (RFC3948).
* Do this before invoking into the PFIL.
*/
if (sav->natt != NULL &&
(prot == IPPROTO_UDP || prot == IPPROTO_TCP))
udp_ipsec_adjust_cksum(m, sav, prot, skip);
/*
* Needed for ipsec_run_hooks and netisr_queue_src
*/
NET_EPOCH_ENTER(et);
IPSEC_INIT_CTX(&ctx, &m, NULL, sav, AF_INET, IPSEC_ENC_BEFORE);
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0)
goto bad;
ip = mtod(m, struct ip *); /* update pointer */
/* IP-in-IP encapsulation */
if (prot == IPPROTO_IPIP &&
saidx->mode != IPSEC_MODE_TRANSPORT) {
if (m->m_pkthdr.len - skip < sizeof(struct ip)) {
IPSEC_ISTAT(sproto, hdrops);
error = EINVAL;
goto bad;
}
/* enc0: strip outer IPv4 header */
m_striphdr(m, 0, ip->ip_hl << 2);
}
#ifdef INET6
/* IPv6-in-IP encapsulation. */
else if (prot == IPPROTO_IPV6 &&
saidx->mode != IPSEC_MODE_TRANSPORT) {
if (m->m_pkthdr.len - skip < sizeof(struct ip6_hdr)) {
IPSEC_ISTAT(sproto, hdrops);
error = EINVAL;
goto bad;
}
/* enc0: strip IPv4 header, keep IPv6 header only */
m_striphdr(m, 0, ip->ip_hl << 2);
}
#endif /* INET6 */
else if (prot != IPPROTO_IPV6 && saidx->mode == IPSEC_MODE_ANY) {
/*
* When mode is wildcard, inner protocol is IPv6 and
* we have no INET6 support - drop this packet a bit later.
* In other cases we assume transport mode. Set prot to
* correctly choose netisr.
*/
prot = IPPROTO_IPIP;
}
/*
* Record what we've done to the packet (under what SA it was
* processed).
*/
if (sproto != IPPROTO_IPCOMP) {
mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE,
sizeof(struct xform_history), M_NOWAIT);
if (mtag == NULL) {
DPRINTF(("%s: failed to get tag\n", __func__));
IPSEC_ISTAT(sproto, hdrops);
error = ENOMEM;
goto bad;
}
xh = (struct xform_history *)(mtag + 1);
bcopy(&saidx->dst, &xh->dst, saidx->dst.sa.sa_len);
xh->spi = sav->spi;
xh->proto = sproto;
xh->mode = saidx->mode;
m_tag_prepend(m, mtag);
}
key_sa_recordxfer(sav, m); /* record data transfer */
/*
* In transport mode requeue decrypted mbuf back to IPv4 protocol
* handler. This is necessary to correctly expose rcvif.
*/
if (saidx->mode == IPSEC_MODE_TRANSPORT)
prot = IPPROTO_IPIP;
/*
* Re-dispatch via software interrupt.
*/
switch (prot) {
case IPPROTO_IPIP:
isr_prot = NETISR_IP;
af = AF_INET;
break;
#ifdef INET6
case IPPROTO_IPV6:
isr_prot = NETISR_IPV6;
af = AF_INET6;
break;
#endif
default:
DPRINTF(("%s: cannot handle inner ip proto %d\n",
__func__, prot));
IPSEC_ISTAT(sproto, nopf);
error = EPFNOSUPPORT;
goto bad;
}
IPSEC_INIT_CTX(&ctx, &m, NULL, sav, af, IPSEC_ENC_AFTER);
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0)
goto bad;
/* Handle virtual tunneling interfaces */
if (saidx->mode == IPSEC_MODE_TUNNEL)
error = ipsec_if_input(m, sav, af);
if (error == 0) {
error = netisr_queue_src(isr_prot, (uintptr_t)sav->spi, m);
if (error) {
IPSEC_ISTAT(sproto, qfull);
DPRINTF(("%s: queue full; proto %u packet dropped\n",
__func__, sproto));
}
}
NET_EPOCH_EXIT(et);
key_freesav(&sav);
return (error);
bad:
NET_EPOCH_EXIT(et);
bad_noepoch:
key_freesav(&sav);
if (m != NULL)
m_freem(m);
return (error);
}
#endif /* INET */
#ifdef INET6
static bool
ipsec6_lasthdr(int proto)
{
switch (proto) {
case IPPROTO_IPV4:
case IPPROTO_IPV6:
case IPPROTO_GRE:
case IPPROTO_ICMPV6:
case IPPROTO_ETHERIP:
case IPPROTO_PIM:
case IPPROTO_SCTP:
return (true);
default:
return (false);
};
}
/*
* IPSEC_INPUT() method implementation for IPv6.
* 0 - Permitted by inbound security policy for further processing.
* EACCES - Forbidden by inbound security policy.
* EINPROGRESS - consumed by IPsec.
*/
int
ipsec6_input(struct mbuf *m, int offset, int proto)
{
int error;
error = ipsec_accel_input(m, offset, proto);
if (error != ENXIO)
return (error);
switch (proto) {
case IPPROTO_AH:
case IPPROTO_ESP:
case IPPROTO_IPCOMP:
/* Do inbound IPsec processing for AH/ESP/IPCOMP */
ipsec_common_input(m, offset,
offsetof(struct ip6_hdr, ip6_nxt), AF_INET6, proto);
return (EINPROGRESS); /* mbuf consumed by IPsec */
default:
/*
* Protocols with further headers get their IPsec treatment
* within the protocol specific processing.
*/
if (!ipsec6_lasthdr(proto))
return (0);
/* FALLTHROUGH */
};
/*
* Enforce IPsec policy checking if we are seeing last header.
*/
if (ipsec6_in_reject(m, NULL) != 0) {
/* Forbidden by inbound security policy */
m_freem(m);
return (EACCES);
}
return (0);
}
int
ipsec6_ctlinput(ipsec_ctlinput_param_t param)
{
return (0);
}
extern ipproto_input_t *ip6_protox[];
/*
* IPsec input callback, called by the transform callback. Takes care of
* filtering and other sanity checks on the processed packet.
*/
int
ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
int protoff)
{
IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
struct epoch_tracker et;
struct ipsec_ctx_data ctx;
struct xform_history *xh;
struct secasindex *saidx;
struct ip6_hdr *ip6;
struct m_tag *mtag;
int prot, af, sproto;
int nxt, isr_prot;
int error, nest;
uint8_t nxt8;
IPSEC_ASSERT(sav != NULL, ("null SA"));
IPSEC_ASSERT(sav->sah != NULL, ("null SAH"));
saidx = &sav->sah->saidx;
af = saidx->dst.sa.sa_family;
IPSEC_ASSERT(af == AF_INET6, ("unexpected af %u", af));
sproto = saidx->proto;
IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH ||
sproto == IPPROTO_IPCOMP,
("unexpected security protocol %u", sproto));
NET_EPOCH_ENTER(et);
/* Fix IPv6 header */
if (m->m_len < sizeof(struct ip6_hdr) &&
(m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
DPRINTF(("%s: processing failed for SA %s/%08lx\n",
__func__, ipsec_address(&sav->sah->saidx.dst, buf,
sizeof(buf)), (u_long) ntohl(sav->spi)));
IPSEC_ISTAT(sproto, hdrops);
error = EACCES;
goto bad;
}
IPSEC_INIT_CTX(&ctx, &m, NULL, sav, af, IPSEC_ENC_BEFORE);
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0)
goto bad;
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr));
/* Save protocol */
m_copydata(m, protoff, 1, &nxt8);
prot = nxt8;
/*
* Check that we have NAT-T enabled and apply transport mode
* decapsulation NAT procedure (RFC3948).
* Do this before invoking into the PFIL.
*/
if (sav->natt != NULL &&
(prot == IPPROTO_UDP || prot == IPPROTO_TCP))
udp_ipsec_adjust_cksum(m, sav, prot, skip);
/* IPv6-in-IP encapsulation */
if (prot == IPPROTO_IPV6 &&
saidx->mode != IPSEC_MODE_TRANSPORT) {
if (m->m_pkthdr.len - skip < sizeof(struct ip6_hdr)) {
IPSEC_ISTAT(sproto, hdrops);
error = EINVAL;
goto bad;
}
/* ip6n will now contain the inner IPv6 header. */
m_striphdr(m, 0, skip);
skip = 0;
}
#ifdef INET
/* IP-in-IP encapsulation */
else if (prot == IPPROTO_IPIP &&
saidx->mode != IPSEC_MODE_TRANSPORT) {
if (m->m_pkthdr.len - skip < sizeof(struct ip)) {
IPSEC_ISTAT(sproto, hdrops);
error = EINVAL;
goto bad;
}
/* ipn will now contain the inner IPv4 header */
m_striphdr(m, 0, skip);
skip = 0;
}
#endif /* INET */
else {
prot = IPPROTO_IPV6; /* for correct BPF processing */
}
/*
* Record what we've done to the packet (under what SA it was
* processed).
*/
if (sproto != IPPROTO_IPCOMP) {
mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE,
sizeof(struct xform_history), M_NOWAIT);
if (mtag == NULL) {
DPRINTF(("%s: failed to get tag\n", __func__));
IPSEC_ISTAT(sproto, hdrops);
error = ENOMEM;
goto bad;
}
xh = (struct xform_history *)(mtag + 1);
bcopy(&saidx->dst, &xh->dst, saidx->dst.sa.sa_len);
xh->spi = sav->spi;
xh->proto = sproto;
xh->mode = saidx->mode;
m_tag_prepend(m, mtag);
}
key_sa_recordxfer(sav, m);
#ifdef INET
if (prot == IPPROTO_IPIP)
af = AF_INET;
else
#endif
af = AF_INET6;
IPSEC_INIT_CTX(&ctx, &m, NULL, sav, af, IPSEC_ENC_AFTER);
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0)
goto bad;
if (skip == 0) {
/*
* We stripped outer IPv6 header.
* Now we should requeue decrypted packet via netisr.
*/
switch (prot) {
#ifdef INET
case IPPROTO_IPIP:
isr_prot = NETISR_IP;
break;
#endif
case IPPROTO_IPV6:
isr_prot = NETISR_IPV6;
break;
default:
DPRINTF(("%s: cannot handle inner ip proto %d\n",
__func__, prot));
IPSEC_ISTAT(sproto, nopf);
error = EPFNOSUPPORT;
goto bad;
}
/* Handle virtual tunneling interfaces */
if (saidx->mode == IPSEC_MODE_TUNNEL)
error = ipsec_if_input(m, sav, af);
if (error == 0) {
error = netisr_queue_src(isr_prot,
(uintptr_t)sav->spi, m);
if (error) {
IPSEC_ISTAT(sproto, qfull);
DPRINTF(("%s: queue full; proto %u packet"
" dropped\n", __func__, sproto));
}
}
NET_EPOCH_EXIT(et);
key_freesav(&sav);
return (error);
}
/*
* See the end of ip6_input for this logic.
* IPPROTO_IPV[46] case will be processed just like other ones
*/
nest = 0;
nxt = nxt8;
while (nxt != IPPROTO_DONE) {
if (V_ip6_hdrnestlimit && (++nest > V_ip6_hdrnestlimit)) {
IP6STAT_INC(ip6s_toomanyhdr);
error = EINVAL;
goto bad;
}
/*
* Protection against faulty packet - there should be
* more sanity checks in header chain processing.
*/
if (m->m_pkthdr.len < skip) {
IP6STAT_INC(ip6s_tooshort);
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_truncated);
error = EINVAL;
goto bad;
}
/*
* Enforce IPsec policy checking if we are seeing last header.
* note that we do not visit this with protocols with pcb layer
* code - like udp/tcp/raw ip.
*/
if (ipsec6_lasthdr(nxt) && ipsec6_in_reject(m, NULL)) {
error = EINVAL;
goto bad;
}
nxt = ip6_protox[nxt](&m, &skip, nxt);
}
NET_EPOCH_EXIT(et);
key_freesav(&sav);
return (0);
bad:
NET_EPOCH_EXIT(et);
key_freesav(&sav);
if (m)
m_freem(m);
return (error);
}
#endif /* INET6 */