Changeset View
Changeset View
Standalone View
Standalone View
sys/netipsec/ipsec_output.c
Show First 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | |||||
#include <sys/socket.h> | #include <sys/socket.h> | ||||
#include <sys/errno.h> | #include <sys/errno.h> | ||||
#include <sys/hhook.h> | #include <sys/hhook.h> | ||||
#include <sys/syslog.h> | #include <sys/syslog.h> | ||||
#include <net/if.h> | #include <net/if.h> | ||||
#include <net/if_enc.h> | #include <net/if_enc.h> | ||||
#include <net/if_var.h> | #include <net/if_var.h> | ||||
#include <net/netisr.h> | |||||
#include <net/vnet.h> | #include <net/vnet.h> | ||||
#include <netinet/in.h> | #include <netinet/in.h> | ||||
#include <netinet/in_systm.h> | #include <netinet/in_systm.h> | ||||
#include <netinet/ip.h> | #include <netinet/ip.h> | ||||
#include <netinet/ip_var.h> | #include <netinet/ip_var.h> | ||||
#include <netinet/in_var.h> | #include <netinet/in_var.h> | ||||
#include <netinet/ip_ecn.h> | #include <netinet/ip_ecn.h> | ||||
Show All 28 Lines | |||||
#include <netipsec/xform.h> | #include <netipsec/xform.h> | ||||
#include <netipsec/key.h> | #include <netipsec/key.h> | ||||
#include <netipsec/keydb.h> | #include <netipsec/keydb.h> | ||||
#include <netipsec/key_debug.h> | #include <netipsec/key_debug.h> | ||||
#include <machine/in_cksum.h> | #include <machine/in_cksum.h> | ||||
#define MTAG_IPSEC 1487673374 | |||||
struct ipsec_nh_ctx { | |||||
struct secpolicy *sp; | |||||
struct secasvar *sav; | |||||
u_int idx; | |||||
}; | |||||
#define IPSEC_OSTAT_INC(proto, name) do { \ | #define IPSEC_OSTAT_INC(proto, name) do { \ | ||||
if ((proto) == IPPROTO_ESP) \ | if ((proto) == IPPROTO_ESP) \ | ||||
ESPSTAT_INC(esps_##name); \ | ESPSTAT_INC(esps_##name); \ | ||||
else if ((proto) == IPPROTO_AH)\ | else if ((proto) == IPPROTO_AH)\ | ||||
AHSTAT_INC(ahs_##name); \ | AHSTAT_INC(ahs_##name); \ | ||||
else \ | else \ | ||||
IPCOMPSTAT_INC(ipcomps_##name); \ | IPCOMPSTAT_INC(ipcomps_##name); \ | ||||
} while (0) | } while (0) | ||||
static int ipsec_encap(struct mbuf **mp, struct secasindex *saidx); | static int ipsec_encap(struct mbuf **, struct secasindex *); | ||||
static int ipsec_queue_output(struct mbuf *, struct secpolicy *, | |||||
struct secasvar *, u_int); | |||||
#ifdef INET | #ifdef INET | ||||
static struct secasvar * | static struct secasvar * | ||||
ipsec4_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) | ipsec4_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) | ||||
{ | { | ||||
struct secasindex *saidx, tmpsaidx; | struct secasindex *saidx, tmpsaidx; | ||||
struct ipsecrequest *isr; | struct ipsecrequest *isr; | ||||
struct sockaddr_in *sin; | struct sockaddr_in *sin; | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | next: | ||||
IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); | IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); | ||||
return (sav); | return (sav); | ||||
} | } | ||||
/* | /* | ||||
* IPsec output logic for IPv4. | * IPsec output logic for IPv4. | ||||
*/ | */ | ||||
static int | static int | ||||
ipsec4_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) | ipsec4_xform_output(struct mbuf *m, struct secpolicy *sp, | ||||
struct secasvar *sav, u_int idx) | |||||
{ | { | ||||
char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; | |||||
struct ipsec_ctx_data ctx; | struct ipsec_ctx_data ctx; | ||||
union sockaddr_union *dst; | union sockaddr_union *dst; | ||||
struct secasvar *sav; | |||||
struct ip *ip; | struct ip *ip; | ||||
int error, i, off; | int error, i, off; | ||||
IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); | |||||
/* | /* | ||||
* We hold the reference to SP. Content of SP couldn't be changed. | |||||
* Craft secasindex and do lookup for suitable SA. | |||||
* Then do encapsulation if needed and call xform's output. | |||||
* We need to store SP in the xform callback parameters. | |||||
* In xform callback we will extract SP and it can be used to | |||||
* determine next transform. At the end of transform we can | |||||
* release reference to SP. | |||||
*/ | |||||
sav = ipsec4_allocsa(m, sp, &idx, &error); | |||||
if (sav == NULL) { | |||||
if (error == EJUSTRETURN) { /* No IPsec required */ | |||||
key_freesp(&sp); | |||||
return (error); | |||||
} | |||||
goto bad; | |||||
} | |||||
/* | |||||
* XXXAE: most likely ip_sum at this point is wrong. | * XXXAE: most likely ip_sum at this point is wrong. | ||||
*/ | */ | ||||
IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); | IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); | ||||
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | ||||
goto bad; | goto bad; | ||||
ip = mtod(m, struct ip *); | ip = mtod(m, struct ip *); | ||||
dst = &sav->sah->saidx.dst; | dst = &sav->sah->saidx.dst; | ||||
/* Do the appropriate encapsulation, if necessary */ | /* Do the appropriate encapsulation, if necessary */ | ||||
if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ | if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ | ||||
dst->sa.sa_family != AF_INET || /* PF mismatch */ | dst->sa.sa_family != AF_INET || /* PF mismatch */ | ||||
(dst->sa.sa_family == AF_INET && /* Proxy */ | (dst->sa.sa_family == AF_INET && /* Proxy */ | ||||
dst->sin.sin_addr.s_addr != INADDR_ANY && | dst->sin.sin_addr.s_addr != INADDR_ANY && | ||||
dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { | dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { | ||||
/* Fix IPv4 header checksum and length */ | /* Fix IPv4 header checksum and length */ | ||||
ip->ip_len = htons(m->m_pkthdr.len); | ip->ip_len = htons(m->m_pkthdr.len); | ||||
ip->ip_sum = 0; | ip->ip_sum = 0; | ||||
ip->ip_sum = in_cksum(m, ip->ip_hl << 2); | ip->ip_sum = in_cksum(m, ip->ip_hl << 2); | ||||
error = ipsec_encap(&m, &sav->sah->saidx); | error = ipsec_encap(&m, &sav->sah->saidx); | ||||
if (error != 0) { | if (error != 0) { | ||||
DPRINTF(("%s: encapsulation for SA %s->%s " | DPRINTF(("%s: encapsulation for SPI 0x%08x failed\n", | ||||
"SPI 0x%08x failed with error %d\n", __func__, | __func__, ntohl(sav->spi))); | ||||
ipsec_address(&sav->sah->saidx.src, sbuf, | |||||
sizeof(sbuf)), | |||||
ipsec_address(&sav->sah->saidx.dst, dbuf, | |||||
sizeof(dbuf)), ntohl(sav->spi), error)); | |||||
/* XXXAE: IPSEC_OSTAT_INC(tunnel); */ | /* XXXAE: IPSEC_OSTAT_INC(tunnel); */ | ||||
goto bad; | goto bad; | ||||
} | } | ||||
} | } | ||||
IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); | IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); | ||||
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | ||||
goto bad; | goto bad; | ||||
Show All 21 Lines | #endif /* INET6 */ | ||||
default: | default: | ||||
DPRINTF(("%s: unsupported protocol family %u\n", | DPRINTF(("%s: unsupported protocol family %u\n", | ||||
__func__, dst->sa.sa_family)); | __func__, dst->sa.sa_family)); | ||||
error = EPFNOSUPPORT; | error = EPFNOSUPPORT; | ||||
IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); | IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); | ||||
goto bad; | goto bad; | ||||
} | } | ||||
error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); | error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); | ||||
if (error != 0) { | /* mbuf was consumed by xform_output */ | ||||
key_freesav(&sav); | return (error); | ||||
bad: | |||||
if (m != NULL) | |||||
m_freem(m); | |||||
return (error); | |||||
} | |||||
static int | |||||
ipsec4_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) | |||||
{ | |||||
struct secasvar *sav; | |||||
int error; | |||||
IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); | |||||
/* | |||||
* We hold the reference to SP. Content of SP couldn't be changed. | |||||
* Craft secasindex and do lookup for suitable SA. | |||||
* Then do encapsulation if needed and call xform's output. | |||||
* We need to store SP in the xform callback parameters. | |||||
* In xform callback we will extract SP and it can be used to | |||||
* determine next transform. At the end of transform we can | |||||
* release reference to SP. | |||||
*/ | |||||
sav = ipsec4_allocsa(m, sp, &idx, &error); | |||||
if (sav == NULL) { | |||||
if (error == EJUSTRETURN) { /* No IPsec required */ | |||||
key_freesp(&sp); | key_freesp(&sp); | ||||
return (error); | |||||
} | } | ||||
goto bad; | |||||
} | |||||
if (V_ipsec_use_netisr != 0) | |||||
error = ipsec_queue_output(m, sp, sav, idx); | |||||
else | |||||
error = ipsec4_xform_output(m, sp, sav, idx); | |||||
if (error == 0) | |||||
return (error); | return (error); | ||||
if (error == ENOMEM) | |||||
IPSECSTAT_INC(ips_out_nomem); | |||||
m = NULL; /* mbuf was consumed by netisr/xform_output */ | |||||
bad: | bad: | ||||
IPSECSTAT_INC(ips_out_inval); | IPSECSTAT_INC(ips_out_inval); | ||||
if (m != NULL) | if (m != NULL) | ||||
m_freem(m); | m_freem(m); | ||||
if (sav != NULL) | if (sav != NULL) | ||||
key_freesav(&sav); | key_freesav(&sav); | ||||
key_freesp(&sp); | key_freesp(&sp); | ||||
return (error); | return (error); | ||||
▲ Show 20 Lines • Show All 205 Lines • ▼ Show 20 Lines | next: | ||||
IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); | IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); | ||||
return (sav); | return (sav); | ||||
} | } | ||||
/* | /* | ||||
* IPsec output logic for IPv6. | * IPsec output logic for IPv6. | ||||
*/ | */ | ||||
static int | static int | ||||
ipsec6_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) | ipsec6_xform_output(struct mbuf *m, struct secpolicy *sp, | ||||
struct secasvar *sav, u_int idx) | |||||
{ | { | ||||
char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; | |||||
struct ipsec_ctx_data ctx; | struct ipsec_ctx_data ctx; | ||||
union sockaddr_union *dst; | union sockaddr_union *dst; | ||||
struct secasvar *sav; | |||||
struct ip6_hdr *ip6; | struct ip6_hdr *ip6; | ||||
int error, i, off; | int error, i, off; | ||||
IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); | |||||
sav = ipsec6_allocsa(m, sp, &idx, &error); | |||||
if (sav == NULL) { | |||||
if (error == EJUSTRETURN) { /* No IPsec required */ | |||||
key_freesp(&sp); | |||||
return (error); | |||||
} | |||||
goto bad; | |||||
} | |||||
/* Fix IP length in case if it is not set yet. */ | /* Fix IP length in case if it is not set yet. */ | ||||
ip6 = mtod(m, struct ip6_hdr *); | ip6 = mtod(m, struct ip6_hdr *); | ||||
ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6)); | ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6)); | ||||
IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET6, IPSEC_ENC_BEFORE); | IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET6, IPSEC_ENC_BEFORE); | ||||
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | ||||
goto bad; | goto bad; | ||||
ip6 = mtod(m, struct ip6_hdr *); /* pfil can change mbuf */ | ip6 = mtod(m, struct ip6_hdr *); /* pfil can change mbuf */ | ||||
dst = &sav->sah->saidx.dst; | dst = &sav->sah->saidx.dst; | ||||
/* Do the appropriate encapsulation, if necessary */ | /* Do the appropriate encapsulation, if necessary */ | ||||
if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ | if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ | ||||
dst->sa.sa_family != AF_INET6 || /* PF mismatch */ | dst->sa.sa_family != AF_INET6 || /* PF mismatch */ | ||||
((dst->sa.sa_family == AF_INET6) && | ((dst->sa.sa_family == AF_INET6) && | ||||
(!IN6_IS_ADDR_UNSPECIFIED(&dst->sin6.sin6_addr)) && | (!IN6_IS_ADDR_UNSPECIFIED(&dst->sin6.sin6_addr)) && | ||||
(!in6_sa_equal_addrwithscope(&dst->sin6, &ip6->ip6_dst)))) { | (!in6_sa_equal_addrwithscope(&dst->sin6, &ip6->ip6_dst)))) { | ||||
if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) { | if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) { | ||||
/* No jumbogram support. */ | /* No jumbogram support. */ | ||||
error = ENXIO; /*XXX*/ | error = ENXIO; /*XXX*/ | ||||
goto bad; | goto bad; | ||||
} | } | ||||
error = ipsec_encap(&m, &sav->sah->saidx); | error = ipsec_encap(&m, &sav->sah->saidx); | ||||
if (error != 0) { | if (error != 0) { | ||||
DPRINTF(("%s: encapsulation for SA %s->%s " | DPRINTF(("%s: encapsulation for SPI 0x%08x failed\n", | ||||
"SPI 0x%08x failed with error %d\n", __func__, | __func__, ntohl(sav->spi))); | ||||
ipsec_address(&sav->sah->saidx.src, sbuf, | |||||
sizeof(sbuf)), | |||||
ipsec_address(&sav->sah->saidx.dst, dbuf, | |||||
sizeof(dbuf)), ntohl(sav->spi), error)); | |||||
/* XXXAE: IPSEC_OSTAT_INC(tunnel); */ | /* XXXAE: IPSEC_OSTAT_INC(tunnel); */ | ||||
goto bad; | goto bad; | ||||
} | } | ||||
} | } | ||||
IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); | IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); | ||||
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) | ||||
goto bad; | goto bad; | ||||
Show All 16 Lines | #endif /* AF_INET */ | ||||
default: | default: | ||||
DPRINTF(("%s: unsupported protocol family %u\n", | DPRINTF(("%s: unsupported protocol family %u\n", | ||||
__func__, dst->sa.sa_family)); | __func__, dst->sa.sa_family)); | ||||
error = EPFNOSUPPORT; | error = EPFNOSUPPORT; | ||||
IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); | IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); | ||||
goto bad; | goto bad; | ||||
} | } | ||||
error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); | error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); | ||||
if (error != 0) { | /* mbuf was consumed by xform_output */ | ||||
key_freesav(&sav); | return (error); | ||||
bad: | |||||
if (m != NULL) | |||||
m_freem(m); | |||||
return (error); | |||||
} | |||||
static int | |||||
ipsec6_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) | |||||
{ | |||||
struct secasvar *sav; | |||||
int error; | |||||
IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); | |||||
sav = ipsec6_allocsa(m, sp, &idx, &error); | |||||
if (sav == NULL) { | |||||
if (error == EJUSTRETURN) { /* No IPsec required */ | |||||
key_freesp(&sp); | key_freesp(&sp); | ||||
return (error); | |||||
} | } | ||||
goto bad; | |||||
} | |||||
if (V_ipsec_use_netisr != 0) | |||||
error = ipsec_queue_output(m, sp, sav, idx); | |||||
else | |||||
error = ipsec6_xform_output(m, sp, sav, idx); | |||||
if (error == 0) | |||||
return (error); | return (error); | ||||
if (error == ENOMEM) | |||||
IPSEC6STAT_INC(ips_out_nomem); | |||||
m = NULL; /* mbuf was consumed by netisr/xform_output */ | |||||
bad: | bad: | ||||
IPSEC6STAT_INC(ips_out_inval); | IPSEC6STAT_INC(ips_out_inval); | ||||
if (m != NULL) | if (m != NULL) | ||||
m_freem(m); | m_freem(m); | ||||
if (sav != NULL) | if (sav != NULL) | ||||
key_freesav(&sav); | key_freesav(&sav); | ||||
key_freesp(&sp); | key_freesp(&sp); | ||||
return (error); | return (error); | ||||
▲ Show 20 Lines • Show All 365 Lines • ▼ Show 20 Lines | case AF_INET6: | ||||
ip6->ip6_flow |= htonl((uint32_t)proto << 20); | ip6->ip6_flow |= htonl((uint32_t)proto << 20); | ||||
break; | break; | ||||
#endif /* INET6 */ | #endif /* INET6 */ | ||||
default: | default: | ||||
return (EAFNOSUPPORT); | return (EAFNOSUPPORT); | ||||
} | } | ||||
(*mp)->m_flags &= ~(M_BCAST | M_MCAST); | (*mp)->m_flags &= ~(M_BCAST | M_MCAST); | ||||
return (0); | return (0); | ||||
} | |||||
static int | |||||
ipsec_queue_output(struct mbuf *m, struct secpolicy *sp, | |||||
struct secasvar *sav, u_int idx) | |||||
{ | |||||
struct ipsec_nh_ctx *ctx; | |||||
struct m_tag *mtag; | |||||
mtag = m_tag_alloc(MTAG_IPSEC, 0, sizeof(*ctx), M_NOWAIT); | |||||
if (mtag == NULL) { | |||||
m_freem(m); | |||||
return (ENOMEM); | |||||
} | |||||
m_tag_prepend(m, mtag); | |||||
ctx = (struct ipsec_nh_ctx *)(mtag + 1); | |||||
ctx->sp = sp; | |||||
ctx->sav = sav; | |||||
ctx->idx = idx; | |||||
return (netisr_queue_src(NETISR_IPSEC, (uintptr_t)sav->spi, m)); | |||||
} | |||||
void | |||||
ipsec_netisr_output(struct mbuf *m) | |||||
{ | |||||
struct ipsec_nh_ctx *ctx; | |||||
struct secpolicy *sp; | |||||
struct secasvar *sav; | |||||
struct m_tag *mtag; | |||||
mtag = m_tag_locate(m, MTAG_IPSEC, 0, NULL); | |||||
if (mtag == NULL) { | |||||
m_freem(m); | |||||
return; | |||||
} | |||||
ctx = (struct ipsec_nh_ctx *)(mtag + 1); | |||||
sp = ctx->sp; | |||||
sav = ctx->sav; | |||||
switch (sav->sah->saidx.dst.sa.sa_family) { | |||||
#ifdef INET | |||||
case AF_INET: | |||||
if (ipsec4_xform_output(m, sp, sav, ctx->idx) == 0) | |||||
return; | |||||
IPSECSTAT_INC(ips_out_inval); | |||||
break; | |||||
#endif | |||||
#ifdef INET6 | |||||
case AF_INET6: | |||||
if (ipsec6_xform_output(m, sp, sav, ctx->idx) == 0) | |||||
return; | |||||
IPSEC6STAT_INC(ips_out_inval); | |||||
break; | |||||
#endif | |||||
default: | |||||
m_freem(m); | |||||
} | |||||
key_freesav(&sav); | |||||
key_freesp(&sp); | |||||
} | } | ||||