diff options
Diffstat (limited to 'sys/netipsec/ipsec_output.c')
-rw-r--r-- | sys/netipsec/ipsec_output.c | 1132 |
1 files changed, 684 insertions, 448 deletions
diff --git a/sys/netipsec/ipsec_output.c b/sys/netipsec/ipsec_output.c index 24d8f4e571f3e..392008e919788 100644 --- a/sys/netipsec/ipsec_output.c +++ b/sys/netipsec/ipsec_output.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +33,7 @@ #include "opt_inet.h" #include "opt_inet6.h" #include "opt_ipsec.h" +#include "opt_sctp.h" #include <sys/param.h> #include <sys/systm.h> @@ -67,7 +69,13 @@ #ifdef INET6 #include <netinet/icmp6.h> #endif +#ifdef SCTP +#include <netinet/sctp_crc32.h> +#endif +#include <netinet/udp.h> +#include <netipsec/ah.h> +#include <netipsec/esp.h> #include <netipsec/ipsec.h> #ifdef INET6 #include <netipsec/ipsec6.h> @@ -84,25 +92,616 @@ #include <machine/in_cksum.h> -#ifdef IPSEC_NAT_T -#include <netinet/udp.h> +#define IPSEC_OSTAT_INC(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) + +static int ipsec_encap(struct mbuf **mp, struct secasindex *saidx); + +#ifdef INET +static struct secasvar * +ipsec4_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) +{ + struct secasindex *saidx, tmpsaidx; + struct ipsecrequest *isr; + struct sockaddr_in *sin; + struct secasvar *sav; + struct ip *ip; + + /* + * Check system global policy controls. + */ +next: + isr = sp->req[*pidx]; + if ((isr->saidx.proto == IPPROTO_ESP && !V_esp_enable) || + (isr->saidx.proto == IPPROTO_AH && !V_ah_enable) || + (isr->saidx.proto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { + DPRINTF(("%s: IPsec outbound packet dropped due" + " to policy (check your sysctls)\n", __func__)); + IPSEC_OSTAT_INC(isr->saidx.proto, pdrops); + *error = EHOSTUNREACH; + return (NULL); + } + /* + * Craft SA index to search for proper SA. Note that + * we only initialize unspecified SA peers for transport + * mode; for tunnel mode they must already be filled in. + */ + if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { + saidx = &tmpsaidx; + *saidx = isr->saidx; + ip = mtod(m, struct ip *); + if (saidx->src.sa.sa_len == 0) { + sin = &saidx->src.sin; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + sin->sin_addr = ip->ip_src; + } + if (saidx->dst.sa.sa_len == 0) { + sin = &saidx->dst.sin; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + sin->sin_addr = ip->ip_dst; + } + } else + saidx = &sp->req[*pidx]->saidx; + /* + * Lookup SA and validate it. + */ + sav = key_allocsa_policy(sp, saidx, error); + if (sav == NULL) { + IPSECSTAT_INC(ips_out_nosa); + if (*error != 0) + return (NULL); + if (ipsec_get_reqlevel(sp, *pidx) != IPSEC_LEVEL_REQUIRE) { + /* + * We have no SA and policy that doesn't require + * this IPsec transform, thus we can continue w/o + * IPsec processing, i.e. return EJUSTRETURN. + * But first check if there is some bundled transform. + */ + if (sp->tcount > ++(*pidx)) + goto next; + *error = EJUSTRETURN; + } + return (NULL); + } + IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); + return (sav); +} + +/* + * IPsec output logic for IPv4. + */ +static int +ipsec4_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) +{ + char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + union sockaddr_union *dst; + struct secasvar *sav; + struct ip *ip; + 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. + */ + IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; + + ip = mtod(m, struct ip *); + dst = &sav->sah->saidx.dst; + /* Do the appropriate encapsulation, if necessary */ + 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 && /* Proxy */ + dst->sin.sin_addr.s_addr != INADDR_ANY && + dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { + /* Fix IPv4 header checksum and length */ + ip->ip_len = htons(m->m_pkthdr.len); + ip->ip_sum = 0; + ip->ip_sum = in_cksum(m, ip->ip_hl << 2); + error = ipsec_encap(&m, &sav->sah->saidx); + if (error != 0) { + DPRINTF(("%s: encapsulation for SA %s->%s " + "SPI 0x%08x failed with error %d\n", __func__, + 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); */ + goto bad; + } + } + + IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; + + /* + * Dispatch to the appropriate IPsec transform logic. The + * packet will be returned for transmission after crypto + * processing, etc. are completed. + * + * NB: m & sav are ``passed to caller'' who's responsible for + * reclaiming their resources. + */ + switch(dst->sa.sa_family) { + case AF_INET: + ip = mtod(m, struct ip *); + i = ip->ip_hl << 2; + off = offsetof(struct ip, ip_p); + break; +#ifdef INET6 + case AF_INET6: + i = sizeof(struct ip6_hdr); + off = offsetof(struct ip6_hdr, ip6_nxt); + break; +#endif /* INET6 */ + default: + DPRINTF(("%s: unsupported protocol family %u\n", + __func__, dst->sa.sa_family)); + error = EPFNOSUPPORT; + IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); + goto bad; + } + error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); + if (error != 0) { + key_freesav(&sav); + key_freesp(&sp); + } + return (error); +bad: + IPSECSTAT_INC(ips_out_inval); + if (m != NULL) + m_freem(m); + if (sav != NULL) + key_freesav(&sav); + key_freesp(&sp); + return (error); +} + +int +ipsec4_process_packet(struct mbuf *m, struct secpolicy *sp, + struct inpcb *inp) +{ + + return (ipsec4_perform_request(m, sp, 0)); +} + +static int +ipsec4_common_output(struct mbuf *m, struct inpcb *inp, int forwarding) +{ + struct secpolicy *sp; + int error; + + /* Lookup for the corresponding outbound security policy */ + sp = ipsec4_checkpolicy(m, inp, &error); + if (sp == NULL) { + if (error == -EINVAL) { + /* Discarded by policy. */ + m_freem(m); + return (EACCES); + } + return (0); /* No IPsec required. */ + } + + /* + * Usually we have to have tunnel mode IPsec security policy + * when we are forwarding a packet. Otherwise we could not handle + * encrypted replies, because they are not destined for us. But + * some users are doing source address translation for forwarded + * packets, and thus, even if they are forwarded, the replies will + * return back to us. + */ + if (!forwarding) { + /* + * Do delayed checksums now because we send before + * this is done in the normal processing path. + */ + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { + in_delayed_cksum(m); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + } +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP) { + struct ip *ip = mtod(m, struct ip *); + + sctp_delayed_cksum(m, (uint32_t)(ip->ip_hl << 2)); + m->m_pkthdr.csum_flags &= ~CSUM_SCTP; + } #endif + } + /* NB: callee frees mbuf and releases reference to SP */ + error = ipsec4_process_packet(m, sp, inp); + if (error == EJUSTRETURN) { + /* + * We had a SP with a level of 'use' and no SA. We + * will just continue to process the packet without + * IPsec processing and return without error. + */ + return (0); + } + if (error == 0) + return (EINPROGRESS); /* consumed by IPsec */ + return (error); +} +/* + * IPSEC_OUTPUT() method implementation for IPv4. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ int -ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) +ipsec4_output(struct mbuf *m, struct inpcb *inp) { - struct tdb_ident *tdbi; - struct m_tag *mtag; + + /* + * If the packet is resubmitted to ip_output (e.g. after + * AH, ESP, etc. processing), there will be a tag to bypass + * the lookup and related policy checking. + */ + if (m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL) != NULL) + return (0); + + return (ipsec4_common_output(m, inp, 0)); +} + +/* + * IPSEC_FORWARD() method implementation for IPv4. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec4_forward(struct mbuf *m) +{ + + /* + * Check if this packet has an active inbound SP and needs to be + * dropped instead of forwarded. + */ + if (ipsec4_in_reject(m, NULL) != 0) { + m_freem(m); + return (EACCES); + } + return (ipsec4_common_output(m, NULL, 1)); +} +#endif + +#ifdef INET6 +static int +in6_sa_equal_addrwithscope(const struct sockaddr_in6 *sa, + const struct in6_addr *ia) +{ + struct in6_addr ia2; + + if (IN6_IS_SCOPE_LINKLOCAL(&sa->sin6_addr)) { + memcpy(&ia2, &sa->sin6_addr, sizeof(ia2)); + ia2.s6_addr16[1] = htons(sa->sin6_scope_id); + return (IN6_ARE_ADDR_EQUAL(ia, &ia2)); + } + return (IN6_ARE_ADDR_EQUAL(&sa->sin6_addr, ia)); +} + +static struct secasvar * +ipsec6_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) +{ + struct secasindex *saidx, tmpsaidx; + struct ipsecrequest *isr; + struct sockaddr_in6 *sin6; struct secasvar *sav; - struct secasindex *saidx; + struct ip6_hdr *ip6; + + /* + * Check system global policy controls. + */ +next: + isr = sp->req[*pidx]; + if ((isr->saidx.proto == IPPROTO_ESP && !V_esp_enable) || + (isr->saidx.proto == IPPROTO_AH && !V_ah_enable) || + (isr->saidx.proto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { + DPRINTF(("%s: IPsec outbound packet dropped due" + " to policy (check your sysctls)\n", __func__)); + IPSEC_OSTAT_INC(isr->saidx.proto, pdrops); + *error = EHOSTUNREACH; + return (NULL); + } + /* + * Craft SA index to search for proper SA. Note that + * we only fillin unspecified SA peers for transport + * mode; for tunnel mode they must already be filled in. + */ + if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { + saidx = &tmpsaidx; + *saidx = isr->saidx; + ip6 = mtod(m, struct ip6_hdr *); + if (saidx->src.sin6.sin6_len == 0) { + sin6 = (struct sockaddr_in6 *)&saidx->src; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + sin6->sin6_addr = ip6->ip6_src; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = + ntohs(ip6->ip6_src.s6_addr16[1]); + } + } + if (saidx->dst.sin6.sin6_len == 0) { + sin6 = (struct sockaddr_in6 *)&saidx->dst; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + sin6->sin6_addr = ip6->ip6_dst; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = + ntohs(ip6->ip6_dst.s6_addr16[1]); + } + } + } else + saidx = &sp->req[*pidx]->saidx; + /* + * Lookup SA and validate it. + */ + sav = key_allocsa_policy(sp, saidx, error); + if (sav == NULL) { + IPSEC6STAT_INC(ips_out_nosa); + if (*error != 0) + return (NULL); + if (ipsec_get_reqlevel(sp, *pidx) != IPSEC_LEVEL_REQUIRE) { + /* + * We have no SA and policy that doesn't require + * this IPsec transform, thus we can continue w/o + * IPsec processing, i.e. return EJUSTRETURN. + * But first check if there is some bundled transform. + */ + if (sp->tcount > ++(*pidx)) + goto next; + *error = EJUSTRETURN; + } + return (NULL); + } + IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); + return (sav); +} + +/* + * IPsec output logic for IPv6. + */ +static int +ipsec6_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) +{ + char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + union sockaddr_union *dst; + struct secasvar *sav; + struct ip6_hdr *ip6; + 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. */ + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6)); + + IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET6, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; + + ip6 = mtod(m, struct ip6_hdr *); /* pfil can change mbuf */ + dst = &sav->sah->saidx.dst; + + /* Do the appropriate encapsulation, if necessary */ + 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) && + (!IN6_IS_ADDR_UNSPECIFIED(&dst->sin6.sin6_addr)) && + (!in6_sa_equal_addrwithscope(&dst->sin6, &ip6->ip6_dst)))) { + if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) { + /* No jumbogram support. */ + error = ENXIO; /*XXX*/ + goto bad; + } + error = ipsec_encap(&m, &sav->sah->saidx); + if (error != 0) { + DPRINTF(("%s: encapsulation for SA %s->%s " + "SPI 0x%08x failed with error %d\n", __func__, + 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); */ + goto bad; + } + } + + IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; + + switch(dst->sa.sa_family) { +#ifdef INET + case AF_INET: + { + struct ip *ip; + ip = mtod(m, struct ip *); + i = ip->ip_hl << 2; + off = offsetof(struct ip, ip_p); + } + break; +#endif /* AF_INET */ + case AF_INET6: + i = sizeof(struct ip6_hdr); + off = offsetof(struct ip6_hdr, ip6_nxt); + break; + default: + DPRINTF(("%s: unsupported protocol family %u\n", + __func__, dst->sa.sa_family)); + error = EPFNOSUPPORT; + IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); + goto bad; + } + error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); + if (error != 0) { + key_freesav(&sav); + key_freesp(&sp); + } + return (error); +bad: + IPSEC6STAT_INC(ips_out_inval); + if (m != NULL) + m_freem(m); + if (sav != NULL) + key_freesav(&sav); + key_freesp(&sp); + return (error); +} + +int +ipsec6_process_packet(struct mbuf *m, struct secpolicy *sp, + struct inpcb *inp) +{ + + return (ipsec6_perform_request(m, sp, 0)); +} + +static int +ipsec6_common_output(struct mbuf *m, struct inpcb *inp, int forwarding) +{ + struct secpolicy *sp; int error; - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(isr != NULL, ("null ISR")); - IPSEC_ASSERT(isr->sp != NULL, ("NULL isr->sp")); - sav = isr->sav; - IPSEC_ASSERT(sav != NULL, ("null SA")); - IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); + /* Lookup for the corresponding outbound security policy */ + sp = ipsec6_checkpolicy(m, inp, &error); + if (sp == NULL) { + if (error == -EINVAL) { + /* Discarded by policy. */ + m_freem(m); + return (EACCES); + } + return (0); /* No IPsec required. */ + } + + if (!forwarding) { + /* + * Do delayed checksums now because we send before + * this is done in the normal processing path. + */ + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA_IPV6) { + in6_delayed_cksum(m, m->m_pkthdr.len - + sizeof(struct ip6_hdr), sizeof(struct ip6_hdr)); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA_IPV6; + } +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP_IPV6) { + sctp_delayed_cksum(m, sizeof(struct ip6_hdr)); + m->m_pkthdr.csum_flags &= ~CSUM_SCTP_IPV6; + } +#endif + } + /* NB: callee frees mbuf and releases reference to SP */ + error = ipsec6_process_packet(m, sp, inp); + if (error == EJUSTRETURN) { + /* + * We had a SP with a level of 'use' and no SA. We + * will just continue to process the packet without + * IPsec processing and return without error. + */ + return (0); + } + if (error == 0) + return (EINPROGRESS); /* consumed by IPsec */ + return (error); +} + +/* + * IPSEC_OUTPUT() method implementation for IPv6. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec6_output(struct mbuf *m, struct inpcb *inp) +{ + + /* + * If the packet is resubmitted to ip_output (e.g. after + * AH, ESP, etc. processing), there will be a tag to bypass + * the lookup and related policy checking. + */ + if (m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL) != NULL) + return (0); + + return (ipsec6_common_output(m, inp, 0)); +} + +/* + * IPSEC_FORWARD() method implementation for IPv6. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec6_forward(struct mbuf *m) +{ + + /* + * Check if this packet has an active inbound SP and needs to be + * dropped instead of forwarded. + */ + if (ipsec6_in_reject(m, NULL) != 0) { + m_freem(m); + return (EACCES); + } + return (ipsec6_common_output(m, NULL, 1)); +} +#endif /* INET6 */ + +int +ipsec_process_done(struct mbuf *m, struct secpolicy *sp, struct secasvar *sav, + u_int idx) +{ + struct xform_history *xh; + struct secasindex *saidx; + struct m_tag *mtag; + int error; saidx = &sav->sah->saidx; switch (saidx->dst.sa.sa_family) { @@ -136,21 +735,20 @@ ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) } /* - * Add a record of what we've done or what needs to be done to the - * packet. + * Add a record of what we've done to the packet. */ - mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, - sizeof(struct tdb_ident), M_NOWAIT); + mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, sizeof(*xh), M_NOWAIT); if (mtag == NULL) { DPRINTF(("%s: could not get packet tag\n", __func__)); error = ENOMEM; goto bad; } - tdbi = (struct tdb_ident *)(mtag + 1); - tdbi->dst = saidx->dst; - tdbi->proto = saidx->proto; - tdbi->spi = sav->spi; + xh = (struct xform_history *)(mtag + 1); + xh->dst = saidx->dst; + xh->proto = saidx->proto; + xh->mode = saidx->mode; + xh->spi = sav->spi; m_tag_prepend(m, mtag); key_sa_recordxfer(sav, m); /* record data transfer */ @@ -162,32 +760,41 @@ ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) * to set the packet on so we can unwind the stack before * doing further processing. */ - if (isr->next) { - /* XXX-BZ currently only support same AF bundles. */ + if (++idx < sp->tcount) { switch (saidx->dst.sa.sa_family) { #ifdef INET case AF_INET: + key_freesav(&sav); IPSECSTAT_INC(ips_out_bundlesa); - return (ipsec4_process_packet(m, isr->next)); + return (ipsec4_perform_request(m, sp, idx)); /* NOTREACHED */ #endif -#ifdef notyet #ifdef INET6 case AF_INET6: - /* XXX */ + key_freesav(&sav); IPSEC6STAT_INC(ips_out_bundlesa); - return (ipsec6_process_packet(m, isr->next)); + return (ipsec6_perform_request(m, sp, idx)); /* NOTREACHED */ #endif /* INET6 */ -#endif default: DPRINTF(("%s: unknown protocol family %u\n", __func__, saidx->dst.sa.sa_family)); - error = ENXIO; + error = EPFNOSUPPORT; goto bad; } } + key_freesp(&sp), sp = NULL; /* Release reference to SP */ +#ifdef INET + /* + * Do UDP encapsulation if SA requires it. + */ + if (sav->natt != NULL) { + error = udp_ipsec_output(m, sav); + if (error != 0) + goto bad; + } +#endif /* INET */ /* * We're done with IPsec processing, transmit the packet using the * appropriate network protocol (IP or IPv6). @@ -195,225 +802,68 @@ ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) switch (saidx->dst.sa.sa_family) { #ifdef INET case AF_INET: -#ifdef IPSEC_NAT_T - /* - * If NAT-T is enabled, now that all IPsec processing is done - * insert UDP encapsulation header after IP header. - */ - if (sav->natt_type) { - struct ip *ip = mtod(m, struct ip *); - const int hlen = (ip->ip_hl << 2); - int size, off; - struct mbuf *mi; - struct udphdr *udp; - - size = sizeof(struct udphdr); - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) { - /* - * draft-ietf-ipsec-nat-t-ike-0[01].txt and - * draft-ietf-ipsec-udp-encaps-(00/)01.txt, - * ignoring possible AH mode - * non-IKE marker + non-ESP marker - * from draft-ietf-ipsec-udp-encaps-00.txt. - */ - size += sizeof(u_int64_t); - } - mi = m_makespace(m, hlen, size, &off); - if (mi == NULL) { - DPRINTF(("%s: m_makespace for udphdr failed\n", - __func__)); - error = ENOBUFS; - goto bad; - } - - udp = (struct udphdr *)(mtod(mi, caddr_t) + off); - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) - udp->uh_sport = htons(UDP_ENCAP_ESPINUDP_PORT); - else - udp->uh_sport = - KEY_PORTFROMSADDR(&sav->sah->saidx.src); - udp->uh_dport = KEY_PORTFROMSADDR(&sav->sah->saidx.dst); - udp->uh_sum = 0; - udp->uh_ulen = htons(m->m_pkthdr.len - hlen); - ip->ip_len = htons(m->m_pkthdr.len); - ip->ip_p = IPPROTO_UDP; - - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) - *(u_int64_t *)(udp + 1) = 0; - } -#endif /* IPSEC_NAT_T */ - + key_freesav(&sav); return ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL); #endif /* INET */ #ifdef INET6 case AF_INET6: - /* - * We don't need massage, IPv6 header fields are always in - * net endian. - */ + key_freesav(&sav); return ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); #endif /* INET6 */ } panic("ipsec_process_done"); bad: m_freem(m); + key_freesav(&sav); + if (sp != NULL) + key_freesp(&sp); return (error); } -static struct ipsecrequest * -ipsec_nextisr( - struct mbuf *m, - struct ipsecrequest *isr, - int af, - struct secasindex *saidx, - int *error -) +/* + * ipsec_prepend() is optimized version of M_PREPEND(). + * ipsec_encap() is called by IPsec output routine for tunnel mode SA. + * It is expected that after IP encapsulation some IPsec transform will + * be performed. Each IPsec transform inserts its variable length header + * just after outer IP header using m_makespace(). If given mbuf has not + * enough free space at the beginning, we allocate new mbuf and reserve + * some space at the beginning and at the end. + * This helps avoid allocating of new mbuf and data copying in m_makespace(), + * we place outer header in the middle of mbuf's data with reserved leading + * and trailing space: + * [ LEADINGSPACE ][ Outer IP header ][ TRAILINGSPACE ] + * LEADINGSPACE will be used to add ethernet header, TRAILINGSPACE will + * be used to inject AH/ESP/IPCOMP header. + */ +#define IPSEC_TRAILINGSPACE (sizeof(struct udphdr) +/* NAT-T */ \ + max(sizeof(struct newesp) + EALG_MAX_BLOCK_LEN, /* ESP + IV */ \ + sizeof(struct newah) + HASH_MAX_LEN /* AH + ICV */)) +static struct mbuf * +ipsec_prepend(struct mbuf *m, int len, int how) { -#define IPSEC_OSTAT(name) do { \ - if (isr->saidx.proto == IPPROTO_ESP) \ - ESPSTAT_INC(esps_##name); \ - else if (isr->saidx.proto == IPPROTO_AH)\ - AHSTAT_INC(ahs_##name); \ - else \ - IPCOMPSTAT_INC(ipcomps_##name); \ -} while (0) - struct secasvar *sav; - - IPSECREQUEST_LOCK_ASSERT(isr); - - IPSEC_ASSERT(af == AF_INET || af == AF_INET6, - ("invalid address family %u", af)); -again: - /* - * Craft SA index to search for proper SA. Note that - * we only fillin unspecified SA peers for transport - * mode; for tunnel mode they must already be filled in. - */ - *saidx = isr->saidx; - if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { - /* Fillin unspecified SA peers only for transport mode */ - if (af == AF_INET) { - struct sockaddr_in *sin; - struct ip *ip = mtod(m, struct ip *); - - if (saidx->src.sa.sa_len == 0) { - sin = &saidx->src.sin; - sin->sin_len = sizeof(*sin); - sin->sin_family = AF_INET; - sin->sin_port = IPSEC_PORT_ANY; - sin->sin_addr = ip->ip_src; - } - if (saidx->dst.sa.sa_len == 0) { - sin = &saidx->dst.sin; - sin->sin_len = sizeof(*sin); - sin->sin_family = AF_INET; - sin->sin_port = IPSEC_PORT_ANY; - sin->sin_addr = ip->ip_dst; - } - } else { - struct sockaddr_in6 *sin6; - struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); - - if (saidx->src.sin6.sin6_len == 0) { - sin6 = (struct sockaddr_in6 *)&saidx->src; - sin6->sin6_len = sizeof(*sin6); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = IPSEC_PORT_ANY; - sin6->sin6_addr = ip6->ip6_src; - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { - /* fix scope id for comparing SPD */ - sin6->sin6_addr.s6_addr16[1] = 0; - sin6->sin6_scope_id = - ntohs(ip6->ip6_src.s6_addr16[1]); - } - } - if (saidx->dst.sin6.sin6_len == 0) { - sin6 = (struct sockaddr_in6 *)&saidx->dst; - sin6->sin6_len = sizeof(*sin6); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = IPSEC_PORT_ANY; - sin6->sin6_addr = ip6->ip6_dst; - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { - /* fix scope id for comparing SPD */ - sin6->sin6_addr.s6_addr16[1] = 0; - sin6->sin6_scope_id = - ntohs(ip6->ip6_dst.s6_addr16[1]); - } - } - } + struct mbuf *n; + + M_ASSERTPKTHDR(m); + IPSEC_ASSERT(len < MHLEN, ("wrong length")); + if (M_LEADINGSPACE(m) >= len) { + /* No need to allocate new mbuf. */ + m->m_data -= len; + m->m_len += len; + m->m_pkthdr.len += len; + return (m); } - - /* - * Lookup SA and validate it. - */ - *error = key_checkrequest(isr, saidx); - if (*error != 0) { - /* - * IPsec processing is required, but no SA found. - * I assume that key_acquire() had been called - * to get/establish the SA. Here I discard - * this packet because it is responsibility for - * upper layer to retransmit the packet. - */ - switch(af) { - case AF_INET: - IPSECSTAT_INC(ips_out_nosa); - break; -#ifdef INET6 - case AF_INET6: - IPSEC6STAT_INC(ips_out_nosa); - break; -#endif - } - goto bad; - } - sav = isr->sav; - if (sav == NULL) { - IPSEC_ASSERT(ipsec_get_reqlevel(isr) == IPSEC_LEVEL_USE, - ("no SA found, but required; level %u", - ipsec_get_reqlevel(isr))); - IPSECREQUEST_UNLOCK(isr); - isr = isr->next; - /* - * If isr is NULL, we found a 'use' policy w/o SA. - * Return w/o error and w/o isr so we can drop out - * and continue w/o IPsec processing. - */ - if (isr == NULL) - return isr; - IPSECREQUEST_LOCK(isr); - goto again; - } - - /* - * Check system global policy controls. - */ - if ((isr->saidx.proto == IPPROTO_ESP && !V_esp_enable) || - (isr->saidx.proto == IPPROTO_AH && !V_ah_enable) || - (isr->saidx.proto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { - DPRINTF(("%s: IPsec outbound packet dropped due" - " to policy (check your sysctls)\n", __func__)); - IPSEC_OSTAT(pdrops); - *error = EHOSTUNREACH; - goto bad; - } - - /* - * Sanity check the SA contents for the caller - * before they invoke the xform output method. - */ - if (sav->tdb_xform == NULL) { - DPRINTF(("%s: no transform for SA\n", __func__)); - IPSEC_OSTAT(noxform); - *error = EHOSTUNREACH; - goto bad; + n = m_gethdr(how, m->m_type); + if (n == NULL) { + m_freem(m); + return (NULL); } - return isr; -bad: - IPSEC_ASSERT(*error != 0, ("error return w/ no error code")); - IPSECREQUEST_UNLOCK(isr); - return NULL; -#undef IPSEC_OSTAT + m_move_pkthdr(n, m); + n->m_next = m; + if (len + IPSEC_TRAILINGSPACE < M_SIZE(n)) + m_align(n, len + IPSEC_TRAILINGSPACE); + n->m_len = len; + n->m_pkthdr.len += len; + return (n); } static int @@ -467,7 +917,7 @@ ipsec_encap(struct mbuf **mp, struct secasindex *saidx) saidx->src.sin.sin_addr.s_addr == INADDR_ANY || saidx->dst.sin.sin_addr.s_addr == INADDR_ANY) return (EINVAL); - M_PREPEND(*mp, sizeof(struct ip), M_NOWAIT); + *mp = ipsec_prepend(*mp, sizeof(struct ip), M_NOWAIT); if (*mp == NULL) return (ENOBUFS); ip = mtod(*mp, struct ip *); @@ -490,7 +940,7 @@ ipsec_encap(struct mbuf **mp, struct secasindex *saidx) IN6_IS_ADDR_UNSPECIFIED(&saidx->src.sin6.sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&saidx->dst.sin6.sin6_addr)) return (EINVAL); - M_PREPEND(*mp, sizeof(struct ip6_hdr), M_NOWAIT); + *mp = ipsec_prepend(*mp, sizeof(struct ip6_hdr), M_NOWAIT); if (*mp == NULL) return (ENOBUFS); ip6 = mtod(*mp, struct ip6_hdr *); @@ -515,221 +965,7 @@ ipsec_encap(struct mbuf **mp, struct secasindex *saidx) default: return (EAFNOSUPPORT); } + (*mp)->m_flags &= ~(M_BCAST | M_MCAST); return (0); } -#ifdef INET -/* - * IPsec output logic for IPv4. - */ -int -ipsec4_process_packet(struct mbuf *m, struct ipsecrequest *isr) -{ - char sbuf[INET6_ADDRSTRLEN], dbuf[INET6_ADDRSTRLEN]; - struct ipsec_ctx_data ctx; - union sockaddr_union *dst; - struct secasindex saidx; - struct secasvar *sav; - struct ip *ip; - int error, i, off; - - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(isr != NULL, ("null isr")); - - IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ - - isr = ipsec_nextisr(m, isr, AF_INET, &saidx, &error); - if (isr == NULL) { - if (error != 0) - goto bad; - return EJUSTRETURN; - } - - sav = isr->sav; - if (m->m_len < sizeof(struct ip) && - (m = m_pullup(m, sizeof (struct ip))) == NULL) { - error = ENOBUFS; - goto bad; - } - - IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); - if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) - goto bad; - - ip = mtod(m, struct ip *); - dst = &sav->sah->saidx.dst; - /* Do the appropriate encapsulation, if necessary */ - if (isr->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ - dst->sa.sa_family != AF_INET || /* PF mismatch */ - (dst->sa.sa_family == AF_INET && /* Proxy */ - dst->sin.sin_addr.s_addr != INADDR_ANY && - dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { - /* Fix IPv4 header checksum and length */ - ip->ip_len = htons(m->m_pkthdr.len); - ip->ip_sum = 0; - ip->ip_sum = in_cksum(m, ip->ip_hl << 2); - error = ipsec_encap(&m, &sav->sah->saidx); - if (error != 0) { - DPRINTF(("%s: encapsulation for SA %s->%s " - "SPI 0x%08x failed with error %d\n", __func__, - ipsec_address(&sav->sah->saidx.src, sbuf, - sizeof(sbuf)), - ipsec_address(&sav->sah->saidx.dst, dbuf, - sizeof(dbuf)), ntohl(sav->spi), error)); - goto bad; - } - } - - IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); - if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) - goto bad; - - /* - * Dispatch to the appropriate IPsec transform logic. The - * packet will be returned for transmission after crypto - * processing, etc. are completed. - * - * NB: m & sav are ``passed to caller'' who's responsible for - * for reclaiming their resources. - */ - switch(dst->sa.sa_family) { - case AF_INET: - ip = mtod(m, struct ip *); - i = ip->ip_hl << 2; - off = offsetof(struct ip, ip_p); - break; -#ifdef INET6 - case AF_INET6: - i = sizeof(struct ip6_hdr); - off = offsetof(struct ip6_hdr, ip6_nxt); - break; -#endif /* INET6 */ - default: - DPRINTF(("%s: unsupported protocol family %u\n", - __func__, dst->sa.sa_family)); - error = EPFNOSUPPORT; - IPSECSTAT_INC(ips_out_inval); - goto bad; - } - error = (*sav->tdb_xform->xf_output)(m, isr, NULL, i, off); - IPSECREQUEST_UNLOCK(isr); - return (error); -bad: - if (isr) - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - return error; -} -#endif - - -#ifdef INET6 -static int -in6_sa_equal_addrwithscope(const struct sockaddr_in6 *sa, const struct in6_addr *ia) -{ - struct in6_addr ia2; - - memcpy(&ia2, &sa->sin6_addr, sizeof(ia2)); - if (IN6_IS_SCOPE_LINKLOCAL(&sa->sin6_addr)) - ia2.s6_addr16[1] = htons(sa->sin6_scope_id); - - return IN6_ARE_ADDR_EQUAL(ia, &ia2); -} - -/* - * IPsec output logic for IPv6. - */ -int -ipsec6_process_packet(struct mbuf *m, struct ipsecrequest *isr) -{ - char sbuf[INET6_ADDRSTRLEN], dbuf[INET6_ADDRSTRLEN]; - struct ipsec_ctx_data ctx; - struct secasindex saidx; - struct secasvar *sav; - struct ip6_hdr *ip6; - int error, i, off; - union sockaddr_union *dst; - - IPSEC_ASSERT(m != NULL, ("ipsec6_process_packet: null mbuf")); - IPSEC_ASSERT(isr != NULL, ("ipsec6_process_packet: null isr")); - - IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ - - isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); - if (isr == NULL) { - if (error != 0) - goto bad; - return EJUSTRETURN; - } - sav = isr->sav; - dst = &sav->sah->saidx.dst; - - IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET6, IPSEC_ENC_BEFORE); - if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) - goto bad; - - ip6 = mtod(m, struct ip6_hdr *); - ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6)); - - /* Do the appropriate encapsulation, if necessary */ - if (isr->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ - dst->sa.sa_family != AF_INET6 || /* PF mismatch */ - ((dst->sa.sa_family == AF_INET6) && - (!IN6_IS_ADDR_UNSPECIFIED(&dst->sin6.sin6_addr)) && - (!in6_sa_equal_addrwithscope(&dst->sin6, - &ip6->ip6_dst)))) { - if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) { - /* No jumbogram support. */ - error = ENXIO; /*XXX*/ - goto bad; - } - error = ipsec_encap(&m, &sav->sah->saidx); - if (error != 0) { - DPRINTF(("%s: encapsulation for SA %s->%s " - "SPI 0x%08x failed with error %d\n", __func__, - ipsec_address(&sav->sah->saidx.src, sbuf, - sizeof(sbuf)), - ipsec_address(&sav->sah->saidx.dst, dbuf, - sizeof(dbuf)), ntohl(sav->spi), error)); - goto bad; - } - } - - IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); - if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) - goto bad; - - switch(dst->sa.sa_family) { -#ifdef INET - case AF_INET: - { - struct ip *ip; - ip = mtod(m, struct ip *); - i = ip->ip_hl << 2; - off = offsetof(struct ip, ip_p); - } - break; -#endif /* AF_INET */ - case AF_INET6: - i = sizeof(struct ip6_hdr); - off = offsetof(struct ip6_hdr, ip6_nxt); - break; - default: - DPRINTF(("%s: unsupported protocol family %u\n", - __func__, dst->sa.sa_family)); - error = EPFNOSUPPORT; - goto bad; - } - error = (*sav->tdb_xform->xf_output)(m, isr, NULL, i, off); - IPSECREQUEST_UNLOCK(isr); - return error; -bad: - IPSEC6STAT_INC(ips_out_inval); - if (isr) - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - return error; -} -#endif /*INET6*/ |