summaryrefslogtreecommitdiffstats
path: root/sys/netinet/udp_usrreq.c
diff options
context:
space:
mode:
authorvanhu <vanhu@FreeBSD.org>2009-06-12 15:44:35 +0000
committervanhu <vanhu@FreeBSD.org>2009-06-12 15:44:35 +0000
commit16c1346b9a6c737fd054d4d0644bf5104fcb32aa (patch)
treee75e977677e2ddd8c5e3a47752c3693ea92b15e3 /sys/netinet/udp_usrreq.c
parent76ddf574294a7a39ca62f55ea127233303bcf29e (diff)
downloadFreeBSD-src-16c1346b9a6c737fd054d4d0644bf5104fcb32aa.zip
FreeBSD-src-16c1346b9a6c737fd054d4d0644bf5104fcb32aa.tar.gz
Added support for NAT-Traversal (RFC 3948) in IPsec stack.
Thanks to (no special order) Emmanuel Dreyfus (manu@netbsd.org), Larry Baird (lab@gta.com), gnn, bz, and other FreeBSD devs, Julien Vanherzeele (julien.vanherzeele@netasq.com, for years of bug reporting), the PFSense team, and all people who used / tried the NAT-T patch for years and reported bugs, patches, etc... X-MFC: never Reviewed by: bz Approved by: gnn(mentor) Obtained from: NETASQ
Diffstat (limited to 'sys/netinet/udp_usrreq.c')
-rw-r--r--sys/netinet/udp_usrreq.c258
1 files changed, 258 insertions, 0 deletions
diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c
index fdc96f4..51dda23 100644
--- a/sys/netinet/udp_usrreq.c
+++ b/sys/netinet/udp_usrreq.c
@@ -84,6 +84,7 @@ __FBSDID("$FreeBSD$");
#ifdef IPSEC
#include <netipsec/ipsec.h>
+#include <netipsec/esp.h>
#endif
#include <machine/in_cksum.h>
@@ -151,6 +152,14 @@ SYSCTL_V_STRUCT(V_NET, vnet_inet, _net_inet_udp, UDPCTL_STATS, stats,
static void udp_detach(struct socket *so);
static int udp_output(struct inpcb *, struct mbuf *, struct sockaddr *,
struct mbuf *, struct thread *);
+#ifdef IPSEC
+#ifdef IPSEC_NAT_T
+#define UF_ESPINUDP_ALL (UF_ESPINUDP_NON_IKE|UF_ESPINUDP)
+#ifdef INET
+static struct mbuf *udp4_espdecap(struct inpcb *, struct mbuf *, int);
+#endif
+#endif /* IPSEC_NAT_T */
+#endif /* IPSEC */
static void
udp_zone_change(void *tag)
@@ -252,6 +261,13 @@ udp_append(struct inpcb *inp, struct ip *ip, struct mbuf *n, int off,
#ifdef INET6
struct sockaddr_in6 udp_in6;
#endif
+#ifdef IPSEC
+#ifdef IPSEC_NAT_T
+#ifdef INET
+ struct udpcb *up;
+#endif
+#endif
+#endif
INP_RLOCK_ASSERT(inp);
@@ -263,6 +279,17 @@ udp_append(struct inpcb *inp, struct ip *ip, struct mbuf *n, int off,
V_ipsec4stat.in_polvio++;
return;
}
+#ifdef IPSEC_NAT_T
+#ifdef INET
+ up = intoudpcb(inp);
+ KASSERT(up != NULL, ("%s: udpcb NULL", __func__));
+ if (up->u_flags & UF_ESPINUDP_ALL) { /* IPSec UDP encaps. */
+ n = udp4_espdecap(inp, n, off);
+ if (n == NULL) /* Consumed. */
+ return;
+ }
+#endif /* INET */
+#endif /* IPSEC_NAT_T */
#endif /* IPSEC */
#ifdef MAC
if (mac_inpcb_check_deliver(inp, n) != 0) {
@@ -825,6 +852,99 @@ SYSCTL_PROC(_net_inet_udp, OID_AUTO, getcred,
CTLTYPE_OPAQUE|CTLFLAG_RW|CTLFLAG_PRISON, 0, 0,
udp_getcred, "S,xucred", "Get the xucred of a UDP connection");
+int
+udp_ctloutput(struct socket *so, struct sockopt *sopt)
+{
+ int error = 0, optval;
+ struct inpcb *inp;
+#ifdef IPSEC_NAT_T
+ struct udpcb *up;
+#endif
+
+ inp = sotoinpcb(so);
+ KASSERT(inp != NULL, ("%s: inp == NULL", __func__));
+ INP_WLOCK(inp);
+ if (sopt->sopt_level != IPPROTO_UDP) {
+#ifdef INET6
+ if (INP_CHECK_SOCKAF(so, AF_INET6)) {
+ INP_WUNLOCK(inp);
+ error = ip6_ctloutput(so, sopt);
+ } else {
+#endif
+ INP_WUNLOCK(inp);
+ error = ip_ctloutput(so, sopt);
+#ifdef INET6
+ }
+#endif
+ return (error);
+ }
+
+ switch (sopt->sopt_dir) {
+ case SOPT_SET:
+ switch (sopt->sopt_name) {
+ case UDP_ENCAP:
+ INP_WUNLOCK(inp);
+ error = sooptcopyin(sopt, &optval, sizeof optval,
+ sizeof optval);
+ if (error)
+ break;
+ inp = sotoinpcb(so);
+ KASSERT(inp != NULL, ("%s: inp == NULL", __func__));
+ INP_WLOCK(inp);
+#ifdef IPSEC_NAT_T
+ up = intoudpcb(inp);
+ KASSERT(up != NULL, ("%s: up == NULL", __func__));
+#endif
+ switch (optval) {
+ case 0:
+ /* Clear all UDP encap. */
+#ifdef IPSEC_NAT_T
+ up->u_flags &= ~UF_ESPINUDP_ALL;
+#endif
+ break;
+#ifdef IPSEC_NAT_T
+ case UDP_ENCAP_ESPINUDP:
+ case UDP_ENCAP_ESPINUDP_NON_IKE:
+ up->u_flags &= ~UF_ESPINUDP_ALL;
+ if (optval == UDP_ENCAP_ESPINUDP)
+ up->u_flags |= UF_ESPINUDP;
+ else if (optval == UDP_ENCAP_ESPINUDP_NON_IKE)
+ up->u_flags |= UF_ESPINUDP_NON_IKE;
+ break;
+#endif
+ default:
+ error = EINVAL;
+ break;
+ }
+ INP_WUNLOCK(inp);
+ break;
+ default:
+ INP_WUNLOCK(inp);
+ error = ENOPROTOOPT;
+ break;
+ }
+ break;
+ case SOPT_GET:
+ switch (sopt->sopt_name) {
+#ifdef IPSEC_NAT_T
+ case UDP_ENCAP:
+ up = intoudpcb(inp);
+ KASSERT(up != NULL, ("%s: up == NULL", __func__));
+ optval = up->u_flags & UF_ESPINUDP_ALL;
+ INP_WUNLOCK(inp);
+ error = sooptcopyout(sopt, &optval, sizeof optval);
+ break;
+#endif
+ default:
+ INP_WUNLOCK(inp);
+ error = ENOPROTOOPT;
+ break;
+ }
+ break;
+ }
+ return (error);
+}
+
static int
udp_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr,
struct mbuf *control, struct thread *td)
@@ -1136,6 +1256,144 @@ release:
return (error);
}
+
+#if defined(IPSEC) && defined(IPSEC_NAT_T)
+#ifdef INET
+/*
+ * Potentially decap ESP in UDP frame. Check for an ESP header
+ * and optional marker; if present, strip the UDP header and
+ * push the result through IPSec.
+ *
+ * Returns mbuf to be processed (potentially re-allocated) or
+ * NULL if consumed and/or processed.
+ */
+static struct mbuf *
+udp4_espdecap(struct inpcb *inp, struct mbuf *m, int off)
+{
+ INIT_VNET_IPSEC(curvnet);
+ size_t minlen, payload, skip, iphlen;
+ caddr_t data;
+ struct udpcb *up;
+ struct m_tag *tag;
+ struct udphdr *udphdr;
+ struct ip *ip;
+
+ INP_RLOCK_ASSERT(inp);
+
+ /*
+ * Pull up data so the longest case is contiguous:
+ * IP/UDP hdr + non ESP marker + ESP hdr.
+ */
+ minlen = off + sizeof(uint64_t) + sizeof(struct esp);
+ if (minlen > m->m_pkthdr.len)
+ minlen = m->m_pkthdr.len;
+ if ((m = m_pullup(m, minlen)) == NULL) {
+ V_ipsec4stat.in_inval++;
+ return (NULL); /* Bypass caller processing. */
+ }
+ data = mtod(m, caddr_t); /* Points to ip header. */
+ payload = m->m_len - off; /* Size of payload. */
+
+ if (payload == 1 && data[off] == '\xff')
+ return (m); /* NB: keepalive packet, no decap. */
+
+ up = intoudpcb(inp);
+ KASSERT(up != NULL, ("%s: udpcb NULL", __func__));
+ KASSERT((up->u_flags & UF_ESPINUDP_ALL) != 0,
+ ("u_flags 0x%x", up->u_flags));
+
+ /*
+ * Check that the payload is large enough to hold an
+ * ESP header and compute the amount of data to remove.
+ *
+ * NB: the caller has already done a pullup for us.
+ * XXX can we assume alignment and eliminate bcopys?
+ */
+ if (up->u_flags & UF_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.
+ */
+ uint64_t marker;
+
+ if (payload <= sizeof(uint64_t) + sizeof(struct esp))
+ return (m); /* NB: no decap. */
+ bcopy(data + off, &marker, sizeof(uint64_t));
+ if (marker != 0) /* Non-IKE marker. */
+ return (m); /* NB: no decap. */
+ skip = sizeof(uint64_t) + sizeof(struct udphdr);
+ } else {
+ uint32_t spi;
+
+ if (payload <= sizeof(struct esp)) {
+ V_ipsec4stat.in_inval++;
+ m_freem(m);
+ return (NULL); /* Discard. */
+ }
+ bcopy(data + off, &spi, sizeof(uint32_t));
+ if (spi == 0) /* Non-ESP marker. */
+ return (m); /* NB: no decap. */
+ skip = sizeof(struct udphdr);
+ }
+
+ /*
+ * Setup a PACKET_TAG_IPSEC_NAT_T_PORT tag to remember
+ * the UDP ports. This is required if we want to select
+ * the right SPD for multiple hosts behind same NAT.
+ *
+ * NB: ports are maintained in network byte order everywhere
+ * in the NAT-T code.
+ */
+ tag = m_tag_get(PACKET_TAG_IPSEC_NAT_T_PORTS,
+ 2 * sizeof(uint16_t), M_NOWAIT);
+ if (tag == NULL) {
+ V_ipsec4stat.in_nomem++;
+ m_freem(m);
+ return (NULL); /* Discard. */
+ }
+ iphlen = off - sizeof(struct udphdr);
+ udphdr = (struct udphdr *)(data + iphlen);
+ ((uint16_t *)(tag + 1))[0] = udphdr->uh_sport;
+ ((uint16_t *)(tag + 1))[1] = udphdr->uh_dport;
+ m_tag_prepend(m, tag);
+
+ /*
+ * Remove the UDP header (and possibly the non ESP marker)
+ * IP header length is iphlen
+ * Before:
+ * <--- off --->
+ * +----+------+-----+
+ * | IP | UDP | ESP |
+ * +----+------+-----+
+ * <-skip->
+ * After:
+ * +----+-----+
+ * | IP | ESP |
+ * +----+-----+
+ * <-skip->
+ */
+ ovbcopy(data, data + skip, iphlen);
+ m_adj(m, skip);
+
+ ip = mtod(m, struct ip *);
+ ip->ip_len -= skip;
+ ip->ip_p = IPPROTO_ESP;
+
+ /*
+ * 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);
+
+ (void) ipsec4_common_input(m, iphlen, ip->ip_p);
+ return (NULL); /* NB: consumed, bypass processing. */
+}
+#endif /* INET */
+#endif /* defined(IPSEC) && defined(IPSEC_NAT_T) */
+
static void
udp_abort(struct socket *so)
{
OpenPOWER on IntegriCloud