diff options
Diffstat (limited to 'sys/netinet/ipsec_input.c')
-rw-r--r-- | sys/netinet/ipsec_input.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/sys/netinet/ipsec_input.c b/sys/netinet/ipsec_input.c new file mode 100644 index 00000000000..b9921bda314 --- /dev/null +++ b/sys/netinet/ipsec_input.c @@ -0,0 +1,602 @@ +/* $OpenBSD: ipsec_input.c,v 1.1 1999/12/09 10:15:23 angelos 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. + * + * Permission to use, copy, and modify this software 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. + */ + +/* + * Authentication Header Processing + * Per RFC1826 (Atkinson, 1995) + * + * Encapsulation Security Payload Processing + * Per RFC1827 (Atkinson, 1995) + */ + +#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/sysctl.h> +#include <sys/errno.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> + +#ifdef INET6 +#include <netinet6/in6.h> +#include <netinet6/ip6.h> +#endif/ * INET6 */ + +#include <netinet/ip_ipsp.h> +#include <netinet/ip_esp.h> +#include <netinet/ip_ah.h> + +#include <net/if_enc.h> + +#include "bpfilter.h" + +extern struct enc_softc encif[]; + +#ifdef ENCDEBUG +#define DPRINTF(x) if (encdebug) printf x +#else +#define DPRINTF(x) +#endif + +#ifndef offsetof +#define offsetof(s, e) ((int)&((s *)0)->e) +#endif + +int esp_enable = 0; +int ah_enable = 0; + +/* + * ipsec_common_input() gets called when we receive an IPsec-protected packet + * in IPv4 or IPv6. + */ + +static void +ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) +{ +#define IPSEC_ISTAT(y,z) (sproto == IPPROTO_ESP ? (y)++ : (z)++) +#define IPSEC_NAME (sproto == IPPROTO_ESP ? "esp_input()" : "ah_input()") + + union sockaddr_union sunion; + struct ifqueue *ifq = NULL; + struct tdb *tdbp; + u_int32_t spi; + u_int8_t prot; + int s; + +#ifdef INET + struct ip *ip, ipn; +#endif /* INET */ + +#ifdef INET6 + struct ip6_hdr *ip6, ip6n; +#endif /* INET6 */ + + IPSEC_ISTAT(espstat.esps_input, ahstat.ahs_input); + + if ((sproto == IPPROTO_ESP && !esp_enable) || + (sproto == IPPROTO_AH && !ah_enable)) + { + m_freem(m); + IPSEC_ISTAT(espstat.esps_pdrops, ahstat.ahs_pdrops); + return; + } + + /* Retrieve the SPI from the relevant IPsec header */ + if (sproto == IPPROTO_ESP) + m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi); + else + m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t), + (caddr_t) &spi); + + /* + * Find tunnel control block and (indirectly) call the appropriate + * kernel crypto routine. The resulting mbuf chain is a valid + * IP packet ready to go through input processing. + */ + + bzero(&sunion, sizeof(sunion)); + sunion.sin.sin_family = af; + +#ifdef INET + if (af == AF_INET) + { + sunion.sin.sin_len = sizeof(struct sockaddr_in); + m_copydata(m, offsetof(struct ip, ip_dst), sizeof(struct in_addr), + (unsigned char *) &(sunion.sin.sin_addr)); + } +#endif /* INET */ + +#ifdef INET6 + if (af == AF_INET6) + { + sunion.sin6.sin6_len = sizeof(struct sockaddr_in6); + m_copydata(m, offsetof(struct ip6_hdr, ip6_dst), + sizeof(struct in6_addr), + (unsigned char *) &(sunion.sin6.sin6_addr)); + } +#endif /* INET6 */ + + s = spltdb(); + tdbp = gettdb(spi, &sunion, sproto); + if (tdbp == NULL) + { + DPRINTF(("%s: could not find SA for packet to %s, spi %08x\n", + IPSEC_NAME, ipsp_address(sunion), ntohl(spi))); + m_freem(m); + IPSEC_ISTAT(espstat.esps_notdb, ahstat.ahs_notdb); + return; + } + + if (tdbp->tdb_flags & TDBF_INVALID) + { + DPRINTF(("%s: attempted to use invalid SA %s/%08x\n", + IPSEC_NAME, ipsp_address(sunion), ntohl(spi))); + m_freem(m); + IPSEC_ISTAT(espstat.esps_invalid, ahstat.ahs_invalid); + return; + } + + if (tdbp->tdb_xform == NULL) + { + DPRINTF(("%s: attempted to use uninitialized SA %s/%08x\n", + IPSEC_NAME, ipsp_address(sunion), ntohl(spi))); + m_freem(m); + IPSEC_ISTAT(espstat.esps_noxform, ahstat.ahs_noxform); + return; + } + + if (tdbp->tdb_interface) + m->m_pkthdr.rcvif = (struct ifnet *) tdbp->tdb_interface; + else + m->m_pkthdr.rcvif = &encif[0].sc_if; + + /* Register first use, setup expiration timer */ + if (tdbp->tdb_first_use == 0) + { + tdbp->tdb_first_use = time.tv_sec; + tdb_expiration(tdbp, TDBEXP_TIMEOUT); + } + + m = (*(tdbp->tdb_xform->xf_input))(m, tdbp, skip, protoff); + if (m == NULL) + { + /* The called routine will print a message if necessary */ + IPSEC_ISTAT(espstat.esps_badkcr, ahstat.ahs_badkcr); + return; + } + +#ifdef INET + /* Fix IPv4 header */ + if (af == AF_INET) + { + if ((m = m_pullup(m, skip)) == 0) + { + DPRINTF(("%s: processing failed for SA %s/%08x\n", + IPSEC_NAME, ipsp_address(tdbp->tdb_dst), ntohl(spi))); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + + 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); + prot = ip->ip_p; + + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) + { + /* ipn will now contain the inner IPv4 header */ + m_copydata(m, ip->ip_hl << 2, sizeof(struct ip), (caddr_t) &ipn); + + /* + * Check that the inner source address is the same as + * the proxy address, if available. + */ + if (((tdbp->tdb_proxy.sa.sa_family == AF_INET) && + (tdbp->tdb_proxy.sin.sin_addr.s_addr != INADDR_ANY) && + (ipn.ip_src.s_addr != tdbp->tdb_proxy.sin.sin_addr.s_addr)) || + ((tdbp->tdb_proxy.sa.sa_family != AF_INET6) && + (tdbp->tdb_proxy.sa.sa_family != 0))) + { + DPRINTF(("%s: inner source address %s doesn't correspond to expected proxy source %s, SA %s/%08x\n", IPSEC_NAME, inet_ntoa4(ipn.ip_src), ipsp_address(tdbp->tdb_proxy), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } + +#if INET6 + /* IPv6-in-IP encapsulation */ + if (prot == IPPROTO_IPV6) + { + /* ip6n will now contain the inner IPv6 header */ + m_copydata(m, ip->ip_hl << 2, sizeof(struct ip6_hdr), + (caddr_t) &ip6n); + + /* + * Check that the inner source address is the same as + * the proxy address, if available. + */ + if (((tdbp->tdb_proxy.sa.sa_family == AF_INET6) && + !IN6_IS_ADDR_UNSPECIFIED(&tdbp->tdb_proxy.sin6.sin6_addr) && + !IN6_ARE_ADDR_EQUAL(&ip6n.ip6_src, + &tdbp->tdb_proxy.sin6.sin6_addr)) || + ((tdbp->tdb_proxy.sa.sa_family != AF_INET6) && + (tdbp->tdb_proxy.sa.sa_family != 0))) + { + DPRINTF(("%s: inner source address %s doesn't correspond to expected proxy source %s, SA %s/%08x\n", IPSEC_NAME, inet6_ntoa4(ip6n.ip6_src), ipsp_address(tdbp->tdb_proxy), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } +#endif /* INET6 */ + + /* + * Check that the source address is an expected one, if we know what + * it's supposed to be. This avoids source address spoofing. + */ + if (((tdbp->tdb_src.sa.sa_family == AF_INET) && + (tdbp->tdb_src.sin.sin_addr.s_addr != INADDR_ANY) && + (ip->ip_src.s_addr != tdbp->tdb_src.sin.sin_addr.s_addr)) || + ((tdbp->tdb_src.sa.sa_family != AF_INET) && + (tdbp->tdb_src.sa.sa_family != 0))) + { + DPRINTF(("%s: source address %s doesn't correspond to expected source %s, SA %s/%08x\n", IPSEC_NAME, inet_ntoa4(ip->ip_src), ipsp_address(tdbp->tdb_src), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } +#endif /* INET */ + +#ifdef INET6 + /* Fix IPv6 header */ + if (af == INET6) + { + if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == 0) + { + DPRINTF(("%s: processing failed for SA %s/%08x\n", + IPSEC_NAME, ipsp_address(tdbp->tdb_dst), ntohl(spi))); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(m->m_pkthdr.len); + + /* Save protocol */ + m_copydata(m, protoff, 1, (unsigned char *) &prot); + +#ifdef INET + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) + { + /* ipn will now contain the inner IPv4 header */ + m_copydata(m, skip, sizeof(struct ip), (caddr_t) &ipn); + + /* + * Check that the inner source address is the same as + * the proxy address, if available. + */ + if (((tdbp->tdb_proxy.sa.sa_family == AF_INET) && + (tdbp->tdb_proxy.sin.sin_addr.s_addr != INADDR_ANY) && + (ipn.ip_src.s_addr != tdbp->tdb_proxy.sin.sin_addr.s_addr)) || + ((tdbp->tdb_proxy.sa.sa_family != AF_INET) && + (tdbp->tdb_proxy.sa.sa_family != 0))) + { + DPRINTF(("%s: inner source address %s doesn't correspond to expected proxy source %s, SA %s/%08x\n", IPSEC_NAME, inet_ntoa4(ipn.ip_src), ipsp_address(tdbp->tdb_proxy), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } +#endif /* INET */ + + /* IPv6-in-IP encapsulation */ + if (prot == IPPROTO_IPV6) + { + /* ip6n will now contain the inner IPv6 header */ + m_copydata(m, skip, sizeof(struct ip6_hdr), (caddr_t) &ip6n); + + /* + * Check that the inner source address is the same as + * the proxy address, if available. + */ + if (((tdbp->tdb_proxy.sa.sa_family == AF_INET6) && + !IN6_IS_ADDR_UNSPECIFIED(&tdbp->tdb_proxy.sin6.sin6_addr) && + !IN6_ARE_ADDR_EQUAL(&ip6n.ip6_src, + &tdbp->tdb_proxy.sin6.sin6_addr)) || + ((tdbp->tdb_proxy.sa.sa_family != AF_INET6) && + (tdbp->tdb_proxy.sa.sa_family != 0))) + { + DPRINTF(("%s: inner source address %s doesn't correspond to expected proxy source %s, SA %s/%08x\n", IPSEC_NAME, inet6_ntoa4(ip6n.ip6_src), ipsp_address(tdbp->tdb_proxy), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } + + /* + * Check that the source address is an expected one, if we know what + * it's supposed to be. This avoids source address spoofing. + */ + if (((tdbp->tdb_src.sa.sa_family == AF_INET6) && + !IN6_IS_ADDR_UNSPECIFIED(&tdbp->tdb_src.sin6.sin6_addr) && + !IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, + &tdbp->tdb_src.sin6.sin6_addr)) || + ((tdbp->tdb_src.sa.sa_family != AF_INET6) && + (tdbp->tdb_src.sa.sa_family != 0))) + { + DPRINTF(("%s: source address %s doesn't correspond to expected source %s, SA %s/%08x\n", IPSEC_NAME, inet6_ntoa4(ip6->ip6_src), ipsp_address(tdbp->tdb_src), ipsp_address(tdbp->tdb_dst), ntohl(spi))); + m_free(m); + IPSEC_ISTAT(espstat.esps_hdrops, ahstat.ahs_hdrops); + return; + } + } +#endif /* INET6 */ + + if (prot == IPPROTO_TCP || prot == IPPROTO_UDP) + { + struct tdb_ident *tdbi = NULL; + + if (tdbp->tdb_bind_out) + { + tdbi = m->m_pkthdr.tdbi; + if (!(m->m_flags & M_PKTHDR)) + DPRINTF(("%s: mbuf is not a packet header!\n", IPSEC_NAME)); + + MALLOC(tdbi, struct tdb_ident *, sizeof(struct tdb_ident), + M_TEMP, M_NOWAIT); + + if (tdbi == NULL) + m->m_pkthdr.tdbi = NULL; + else + { + tdbi->spi = tdbp->tdb_bind_out->tdb_spi; + tdbi->dst = tdbp->tdb_bind_out->tdb_dst; + tdbi->proto = tdbp->tdb_bind_out->tdb_sproto; + } + } + } + else + m->m_pkthdr.tdbi = NULL; + + if (sproto == IPPROTO_ESP) + { + /* Packet is confidental */ + m->m_flags |= M_CONF; + /* XXX How about M_AUTH if the SA has authentication on ? */ + } + else + m->m_flags |= M_AUTH; + +#if NBPFILTER > 0 + if (m->m_pkthdr.rcvif->if_bpf) + { + /* + * We need to prepend the address family as + * a four byte field. Cons up a dummy header + * to pacify bpf. This is safe because bpf + * will only read from the mbuf (i.e., it won't + * try to free it or keep a pointer a to it). + */ + struct mbuf m0; + struct enchdr hdr; + + hdr.af = af; + hdr.spi = tdbp->tdb_spi; + hdr.flags = m->m_flags & (M_AUTH|M_CONF); + + m0.m_next = m; + m0.m_len = ENC_HDRLEN; + m0.m_data = (char *) &hdr; + + bpf_mtap(m->m_pkthdr.rcvif->if_bpf, &m0); + } +#endif + splx(s); + + /* + * Interface pointer is already in first mbuf; chop off the + * `outer' header and reschedule. + */ + +#ifdef INET + if (af == AF_INET) + ifq = &ipintrq; +#endif /* INET */ + +#ifdef INET6 + if (af == AF_INET6) + ifq = &ip6intrq; +#endif /* INET6 */ + + s = splimp(); /* isn't it already? */ + if (IF_QFULL(ifq)) + { + IF_DROP(ifq); + if (m->m_pkthdr.tdbi) + free(m->m_pkthdr.tdbi, M_TEMP); + m_freem(m); + IPSEC_ISTAT(espstat.esps_qfull, ahstat.ahs_qfull); + splx(s); + DPRINTF(("%s: dropped packet because of full IP queue\n", IPSEC_NAME)); + return; + } + + IF_ENQUEUE(ifq, m); + +#ifdef INET + if (af == AF_INET) + schednetisr(NETISR_IP); +#endif /* INET */ + +#ifdef INET6 + if (af == AF_INET6) + schednetisr(NETISR_IPV6); +#endif /* INET6 */ + + splx(s); + return; +#undef IPSEC_NAME +#undef IPSEC_ISTAT +} + +int +esp_sysctl(name, namelen, oldp, oldlenp, newp, newlen) + int *name; + u_int namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +{ + /* All sysctl names at this level are terminal. */ + if (namelen != 1) + return (ENOTDIR); + + switch (name[0]) { + case ESPCTL_ENABLE: + return (sysctl_int(oldp, oldlenp, newp, newlen, &esp_enable)); + default: + return (ENOPROTOOPT); + } + /* NOTREACHED */ +} + +int +ah_sysctl(name, namelen, oldp, oldlenp, newp, newlen) + int *name; + u_int namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +{ + /* All sysctl names at this level are terminal. */ + if (namelen != 1) + return (ENOTDIR); + + switch (name[0]) { + case AHCTL_ENABLE: + return (sysctl_int(oldp, oldlenp, newp, newlen, &ah_enable)); + default: + return (ENOPROTOOPT); + } + /* NOTREACHED */ +} + +#ifdef INET +/* IPv4 AH wrapper */ +void +ah_input(struct mbuf *m, ...) +{ + int skip; + + va_list ap; + va_start(ap, m); + skip = va_arg(ap, int); + va_end(ap); + + ipsec_common_input(m, skip, offsetof(struct ip, ip_p), AF_INET, + IPPROTO_AH); +} + +/* IPv4 ESP wrapper */ +void +esp_input(struct mbuf *m, ...) +{ + int skip; + + va_list ap; + va_start(ap, m); + skip = va_arg(ap, int); + va_end(ap); + + ipsec_common_input(m, skip, offsetof(struct ip, ip_p), AF_INET, + IPPROTO_ESP); +} +#endif /* INET */ + +#ifdef INET6 +/* IPv6 AH wrapper */ +void +ah6_input(struct mbuf *m, ...) +{ + int skip, protoff; + + va_list ap; + + va_start(ap, m); + skip = va_arg(ap, int); + protoff = va_arg(ap, int); + va_end(ap); + + ipsec_common_input(m, skip, protoff, AF_INET6, IPPROTO_AH); +} + +/* IPv6 ESP wrapper */ +void +esp6_input(struct mbuf *m, ...) +{ + int skip, protoff; + + va_list ap; + + va_start(ap, m); + skip = va_arg(ap, int); + protoff = va_arg(ap, int); + va_end(ap); + + ipsec_common_input(m, skip, protoff, AF_INET6, IPPROTO_ESP); +} +#endif /* INET6 */ |