diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -4430,7 +4430,7 @@ ipsec_support inet | ipsec_support inet6 netipsec/subr_ipsec.c optional ipsec inet | ipsec inet6 | \ ipsec_support inet | ipsec_support inet6 -netipsec/udpencap.c optional ipsec inet +netipsec/udpencap.c optional ipsec inet | ipsec inet6 netipsec/xform_ah.c optional ipsec inet | ipsec inet6 netipsec/xform_esp.c optional ipsec inet | ipsec inet6 netipsec/xform_ipcomp.c optional ipsec inet | ipsec inet6 diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -269,7 +269,7 @@ } if (up->u_flags & UF_ESPINUDP) {/* IPSec UDP encaps. */ if (IPSEC_ENABLED(ipv4) && - UDPENCAP_INPUT(n, off, AF_INET) != 0) + UDPENCAP_INPUT(ipv4, n, off, AF_INET) != 0) return (0); /* Consumed. */ } #endif /* IPSEC */ @@ -895,19 +895,32 @@ case SOPT_SET: switch (sopt->sopt_name) { #if defined(IPSEC) || defined(IPSEC_SUPPORT) -#ifdef INET +#if defined(INET) || defined(INET6) case UDP_ENCAP: - if (!INP_CHECK_SOCKAF(so, AF_INET)) { - INP_WUNLOCK(inp); - return (EINVAL); - } - if (!IPSEC_ENABLED(ipv4)) { - INP_WUNLOCK(inp); - return (ENOPROTOOPT); +#ifdef INET + if (INP_SOCKAF(so) == AF_INET) { + if (!IPSEC_ENABLED(ipv4)) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + error = UDPENCAP_PCBCTL(ipv4, inp, sopt); + break; } - error = UDPENCAP_PCBCTL(inp, sopt); - break; #endif /* INET */ +#ifdef INET6 + if (INP_SOCKAF(so) == AF_INET6) { + if (!IPSEC_ENABLED(ipv6)) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + error = UDPENCAP_PCBCTL(ipv6, inp, sopt); + break; + } +#endif /* INET6 */ + INP_WUNLOCK(inp); + return (EINVAL); +#endif /* INET || INET6 */ + #endif /* IPSEC */ case UDPLITE_SEND_CSCOV: case UDPLITE_RECV_CSCOV: @@ -946,19 +959,32 @@ case SOPT_GET: switch (sopt->sopt_name) { #if defined(IPSEC) || defined(IPSEC_SUPPORT) -#ifdef INET +#if defined(INET) || defined(INET6) case UDP_ENCAP: - if (!INP_CHECK_SOCKAF(so, AF_INET)) { - INP_WUNLOCK(inp); - return (EINVAL); - } - if (!IPSEC_ENABLED(ipv4)) { - INP_WUNLOCK(inp); - return (ENOPROTOOPT); +#ifdef INET + if (INP_SOCKAF(so) == AF_INET) { + if (!IPSEC_ENABLED(ipv4)) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + error = UDPENCAP_PCBCTL(ipv4, inp, sopt); + break; } - error = UDPENCAP_PCBCTL(inp, sopt); - break; #endif /* INET */ +#ifdef INET6 + if (INP_SOCKAF(so) == AF_INET6) { + if (!IPSEC_ENABLED(ipv6)) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + error = UDPENCAP_PCBCTL(ipv6, inp, sopt); + break; + } +#endif /* INET6 */ + INP_WUNLOCK(inp); + return (EINVAL); +#endif /* INET || INET6 */ + #endif /* IPSEC */ case UDPLITE_SEND_CSCOV: case UDPLITE_RECV_CSCOV: diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c --- a/sys/netinet6/udp6_usrreq.c +++ b/sys/netinet6/udp6_usrreq.c @@ -77,6 +77,7 @@ #include "opt_rss.h" #include +#include #include #include #include @@ -160,6 +161,9 @@ if (filtered) return (in_pcbrele_rlocked(inp)); } + + off += sizeof(struct udphdr); + #if defined(IPSEC) || defined(IPSEC_SUPPORT) /* Check AH/ESP integrity. */ if (IPSEC_ENABLED(ipv6)) { @@ -167,6 +171,12 @@ m_freem(n); return (0); } + + /* IPSec UDP encaps. */ + if ((up->u_flags & UF_ESPINUDP) != 0 && + UDPENCAP_INPUT(ipv6, n, off, AF_INET6) != 0) { + return (0); /* Consumed. */ + } } #endif /* IPSEC */ #ifdef MAC @@ -191,7 +201,7 @@ opts = tmp_opts; } } - m_adj(n, off + sizeof(struct udphdr)); + m_adj(n, off); so = inp->inp_socket; SOCKBUF_LOCK(&so->so_rcv); diff --git a/sys/netipsec/ipsec_input.c b/sys/netipsec/ipsec_input.c --- a/sys/netipsec/ipsec_input.c +++ b/sys/netipsec/ipsec_input.c @@ -628,6 +628,15 @@ 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) { diff --git a/sys/netipsec/ipsec_mod.c b/sys/netipsec/ipsec_mod.c --- a/sys/netipsec/ipsec_mod.c +++ b/sys/netipsec/ipsec_mod.c @@ -85,6 +85,8 @@ .check_policy = ipsec6_in_reject, .ctlinput = ipsec6_ctlinput, .hdrsize = ipsec_hdrsiz_inpcb, + .udp_input = udp_ipsec_input, + .udp_pcbctl = udp_ipsec_pcbctl, }; #ifndef KLD_MODULE static const struct ipsec_support ipv6_ipsec = { diff --git a/sys/netipsec/ipsec_output.c b/sys/netipsec/ipsec_output.c --- a/sys/netipsec/ipsec_output.c +++ b/sys/netipsec/ipsec_output.c @@ -935,7 +935,7 @@ } key_freesp(&sp), sp = NULL; /* Release reference to SP */ -#ifdef INET +#if defined(INET) || defined(INET6) /* * Do UDP encapsulation if SA requires it. */ @@ -944,7 +944,7 @@ if (error != 0) goto bad; } -#endif /* INET */ +#endif /* INET || INET6 */ /* * We're done with IPsec processing, transmit the packet using the * appropriate network protocol (IP or IPv6). diff --git a/sys/netipsec/ipsec_support.h b/sys/netipsec/ipsec_support.h --- a/sys/netipsec/ipsec_support.h +++ b/sys/netipsec/ipsec_support.h @@ -164,10 +164,10 @@ #define IPSEC_CTLINPUT(proto, param) \ (*(proto ## _ipsec_support)->methods->ctlinput)(param) -#define UDPENCAP_INPUT(m, ...) \ - (*ipv4_ipsec_support->methods->udp_input)(m, __VA_ARGS__) -#define UDPENCAP_PCBCTL(inp, sopt) \ - (*ipv4_ipsec_support->methods->udp_pcbctl)(inp, sopt) +#define UDPENCAP_INPUT(proto, m, ...) \ + (*(proto ## _ipsec_support)->methods->udp_input)(m, __VA_ARGS__) +#define UDPENCAP_PCBCTL(proto, inp, sopt) \ + (*(proto ## _ipsec_support)->methods->udp_pcbctl)(inp, sopt) #elif defined(IPSEC_SUPPORT) struct ipsec_support { @@ -196,10 +196,10 @@ int ipsec_kmod_udp_pcbctl(struct ipsec_support * const, struct inpcb *, struct sockopt *); -#define UDPENCAP_INPUT(m, ...) \ - ipsec_kmod_udp_input(ipv4_ipsec_support, m, __VA_ARGS__) -#define UDPENCAP_PCBCTL(inp, sopt) \ - ipsec_kmod_udp_pcbctl(ipv4_ipsec_support, inp, sopt) +#define UDPENCAP_INPUT(proto, m, ...) \ + ipsec_kmod_udp_input(proto ## _ipsec_support, m, __VA_ARGS__) +#define UDPENCAP_PCBCTL(proto, inp, sopt) \ + ipsec_kmod_udp_pcbctl(proto ## _ipsec_support, inp, sopt) #define IPSEC_INPUT(proto, ...) \ ipsec_kmod_input(proto ## _ipsec_support, __VA_ARGS__) diff --git a/sys/netipsec/key.c b/sys/netipsec/key.c --- a/sys/netipsec/key.c +++ b/sys/netipsec/key.c @@ -5752,6 +5752,7 @@ struct sockaddr *sa; uint32_t addr; uint16_t cksum; + int i; IPSEC_ASSERT(sav->natt == NULL, ("natt is already initialized")); /* @@ -5838,52 +5839,117 @@ if (sav->sah->saidx.mode != IPSEC_MODE_TUNNEL) { cksum = 0; if (oai != NULL) { - /* Currently we support only AF_INET */ sa = (struct sockaddr *)(oai + 1); - if (sa->sa_family != AF_INET || - sa->sa_len != sizeof(struct sockaddr_in)) { + switch (sa->sa_family) { +#ifdef AF_INET + case AF_INET: + if (sa->sa_len != sizeof(struct sockaddr_in)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAi header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (((struct sockaddr_in *)sa)->sin_addr.s_addr != + sav->sah->saidx.src.sin.sin_addr.s_addr) { + bcopy(sa, &sav->natt->oai.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAI; + /* Calculate checksum delta */ + addr = sav->sah->saidx.src.sin.sin_addr.s_addr; + cksum = in_addword(cksum, ~addr >> 16); + cksum = in_addword(cksum, ~addr & 0xffff); + addr = sav->natt->oai.sin.sin_addr.s_addr; + cksum = in_addword(cksum, addr >> 16); + cksum = in_addword(cksum, addr & 0xffff); + } + break; +#endif +#ifdef AF_INET6 + case AF_INET6: + if (sa->sa_len != sizeof(struct sockaddr_in6)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAi header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (memcmp(&((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr, + &sav->sah->saidx.src.sin6.sin6_addr.s6_addr, + sizeof(struct in6_addr)) != 0) { + bcopy(sa, &sav->natt->oai.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAI; + /* Calculate checksum delta */ + for (i = 0; i < 8; i++) { + cksum = in_addword(cksum, + ~sav->sah->saidx.src.sin6.sin6_addr.s6_addr16[i]); + cksum = in_addword(cksum, + sav->natt->oai.sin6.sin6_addr.s6_addr16[i]); + } + } + break; +#endif + default: ipseclog((LOG_DEBUG, "%s: wrong NAT-OAi header.\n", __func__)); return (EINVAL); } - /* Ignore address if it the same */ - if (((struct sockaddr_in *)sa)->sin_addr.s_addr != - sav->sah->saidx.src.sin.sin_addr.s_addr) { - bcopy(sa, &sav->natt->oai.sa, sa->sa_len); - sav->natt->flags |= IPSEC_NATT_F_OAI; - /* Calculate checksum delta */ - addr = sav->sah->saidx.src.sin.sin_addr.s_addr; - cksum = in_addword(cksum, ~addr >> 16); - cksum = in_addword(cksum, ~addr & 0xffff); - addr = sav->natt->oai.sin.sin_addr.s_addr; - cksum = in_addword(cksum, addr >> 16); - cksum = in_addword(cksum, addr & 0xffff); - } } if (oar != NULL) { - /* Currently we support only AF_INET */ sa = (struct sockaddr *)(oar + 1); - if (sa->sa_family != AF_INET || - sa->sa_len != sizeof(struct sockaddr_in)) { + switch (sa->sa_family) { +#ifdef AF_INET + case AF_INET: + if (sa->sa_len != sizeof(struct sockaddr_in)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAr header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (((struct sockaddr_in *)sa)->sin_addr.s_addr != + sav->sah->saidx.dst.sin.sin_addr.s_addr) { + bcopy(sa, &sav->natt->oar.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAR; + /* Calculate checksum delta */ + addr = sav->sah->saidx.dst.sin.sin_addr.s_addr; + cksum = in_addword(cksum, ~addr >> 16); + cksum = in_addword(cksum, ~addr & 0xffff); + addr = sav->natt->oar.sin.sin_addr.s_addr; + cksum = in_addword(cksum, addr >> 16); + cksum = in_addword(cksum, addr & 0xffff); + } + break; +#endif +#ifdef AF_INET6 + case AF_INET6: + if (sa->sa_len != sizeof(struct sockaddr_in6)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAr header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (memcmp(&((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr, + &sav->sah->saidx.dst.sin6.sin6_addr.s6_addr, 16) != 0) { + bcopy(sa, &sav->natt->oar.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAR; + /* Calculate checksum delta */ + for (i = 0; i < 8; i++) { + cksum = in_addword(cksum, + ~sav->sah->saidx.dst.sin6.sin6_addr.s6_addr16[i]); + cksum = in_addword(cksum, + sav->natt->oar.sin6.sin6_addr.s6_addr16[i]); + } + } + break; +#endif + default: ipseclog((LOG_DEBUG, "%s: wrong NAT-OAr header.\n", __func__)); return (EINVAL); } - /* Ignore address if it the same */ - if (((struct sockaddr_in *)sa)->sin_addr.s_addr != - sav->sah->saidx.dst.sin.sin_addr.s_addr) { - bcopy(sa, &sav->natt->oar.sa, sa->sa_len); - sav->natt->flags |= IPSEC_NATT_F_OAR; - /* Calculate checksum delta */ - addr = sav->sah->saidx.dst.sin.sin_addr.s_addr; - cksum = in_addword(cksum, ~addr >> 16); - cksum = in_addword(cksum, ~addr & 0xffff); - addr = sav->natt->oar.sin.sin_addr.s_addr; - cksum = in_addword(cksum, addr >> 16); - cksum = in_addword(cksum, addr & 0xffff); - } } sav->natt->cksum = cksum; } diff --git a/sys/netipsec/udpencap.c b/sys/netipsec/udpencap.c --- a/sys/netipsec/udpencap.c +++ b/sys/netipsec/udpencap.c @@ -26,6 +26,7 @@ #include #include "opt_inet.h" +#include "opt_inet6.h" #include "opt_ipsec.h" #include @@ -42,11 +43,14 @@ #include #include #include +#include #include #include #include #include +#include + #include #include @@ -116,7 +120,6 @@ union sockaddr_union dst; struct secasvar *sav; struct udphdr *udp; - struct ip *ip; uint32_t spi; int hlen; @@ -141,7 +144,9 @@ dst.sa.sa_family = af; switch (af) { #ifdef INET - case AF_INET: + case AF_INET: { + struct ip *ip; + dst.sin.sin_len = sizeof(struct sockaddr_in); ip = mtod(m, struct ip *); ip->ip_p = IPPROTO_ESP; @@ -149,11 +154,20 @@ hlen = ip->ip_hl << 2; dst.sin.sin_addr = ip->ip_dst; break; + } #endif #ifdef INET6 - case AF_INET6: - /* Not yet */ - /* FALLTHROUGH */ + case AF_INET6: { + struct ip6_hdr *ip6; + + dst.sin6.sin6_len = sizeof(struct sockaddr_in6); + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_nxt = IPPROTO_ESP; + off = offsetof(struct ip6_hdr, ip6_nxt); + hlen = sizeof(struct ip6_hdr); + dst.sin6.sin6_addr = ip6->ip6_dst; + break; + } #endif default: ESPSTAT_INC(esps_nopf); @@ -192,12 +206,30 @@ * <-skip-> */ m_striphdr(m, hlen, sizeof(*udp)); + /* * We cannot yet update the cksums so clear any h/w cksum flags * as they are no longer valid. */ - if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) - m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID | CSUM_PSEUDO_HDR); + switch (af) { +#ifdef INET + case AF_INET: + if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) + m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID | CSUM_PSEUDO_HDR); + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID_IPV6) + m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID_IPV6 | CSUM_PSEUDO_HDR); + break; +#endif /* INET6 */ + default: + ESPSTAT_INC(esps_nopf); + m_freem(m); + return (EPFNOSUPPORT); + } + /* * We can update ip_len and ip_sum here, but ipsec4_input_cb() * will do this anyway, so don't touch them here. @@ -212,16 +244,31 @@ { struct udphdr *udp; struct mbuf *n; - struct ip *ip; int hlen, off; IPSEC_ASSERT(sav->natt != NULL, ("UDP encapsulation isn't required.")); - if (sav->sah->saidx.dst.sa.sa_family == AF_INET6) + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: { + struct ip *ip; + ip = mtod(m, struct ip *); + hlen = ip->ip_hl << 2; + break; + } +#endif +#ifdef INET6 + case AF_INET6: + KASSERT(ip6->ip6_nxt == IPPROTO_ESP, + ("unexpected next header type %d", ip->ip6_nxt)); + hlen = sizeof(struct ip6_hdr); + break; +#endif + default: + ESPSTAT_INC(esps_nopf); return (EAFNOSUPPORT); + } - ip = mtod(m, struct ip *); - hlen = ip->ip_hl << 2; n = m_makespace(m, hlen, sizeof(*udp), &off); if (n == NULL) { DPRINTF(("%s: m_makespace for udphdr failed\n", __func__)); @@ -234,9 +281,36 @@ udp->uh_sum = 0; udp->uh_ulen = htons(m->m_pkthdr.len - hlen); - ip = mtod(m, struct ip *); - ip->ip_len = htons(m->m_pkthdr.len); - ip->ip_p = IPPROTO_UDP; + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: { + struct ip *ip; + + ip = mtod(m, struct ip *); + ip->ip_len = htons(m->m_pkthdr.len); + ip->ip_p = IPPROTO_UDP; + break; + } +#endif +#ifdef INET6 + case AF_INET6: { + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(m->m_pkthdr.len); + ip6->ip6_nxt = IPPROTO_UDP; + udp->uh_sum = in6_cksum_pseudo(ip6, + m->m_pkthdr.len - hlen, ip6->ip6_nxt, 0); + m->m_pkthdr.csum_flags = CSUM_UDP_IPV6; + m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); + break; + } +#endif + default: + ESPSTAT_INC(esps_nopf); + return (EAFNOSUPPORT); + } + return (0); } @@ -244,7 +318,6 @@ udp_ipsec_adjust_cksum(struct mbuf *m, struct secasvar *sav, int proto, int skip) { - struct ip *ip; uint16_t cksum, off; IPSEC_ASSERT(sav->natt != NULL, ("NAT-T isn't required")); @@ -270,22 +343,67 @@ if (proto == IPPROTO_TCP) { /* Ignore for TCP. */ m->m_pkthdr.csum_data = 0xffff; - m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | - CSUM_PSEUDO_HDR); + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | + CSUM_PSEUDO_HDR); + break; +#endif +#ifdef INET6 + case AF_INET6: + m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID_IPV6 | + CSUM_PSEUDO_HDR); + break; +#endif + default: + break; + } return; } cksum = 0; /* Reset for UDP. */ } m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); } else { /* Fully recompute */ - ip = mtod(m, struct ip *); - cksum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, - htons(m->m_pkthdr.len - skip + proto)); - m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); - m->m_pkthdr.csum_flags = - (proto == IPPROTO_UDP) ? CSUM_UDP: CSUM_TCP; - m->m_pkthdr.csum_data = off; - in_delayed_cksum(m); - m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: { + struct ip *ip; + + ip = mtod(m, struct ip *); + cksum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, + htons(m->m_pkthdr.len - skip + proto)); + m_copyback(m, skip + off, sizeof(cksum), + (caddr_t)&cksum); + m->m_pkthdr.csum_flags = + (proto == IPPROTO_UDP) ? CSUM_UDP : CSUM_TCP; + m->m_pkthdr.csum_data = off; + in_delayed_cksum(m); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + break; + } +#endif +#ifdef INET6 + case AF_INET6: { + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + cksum = in6_cksum_pseudo(ip6, m->m_pkthdr.len - skip, + proto, 0); + m_copyback(m, skip + off, sizeof(cksum), + (caddr_t)&cksum); + m->m_pkthdr.csum_flags = + (proto == IPPROTO_UDP) ? CSUM_UDP_IPV6 : CSUM_TCP_IPV6; + m->m_pkthdr.csum_data = off; + 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; + break; + } +#endif + default: + break; + } } }