UDP-encapsulaton has two common use-cases: to make ESP work with NATs and to
circumvent firewalls.  Currently we only support a single UDP-encap port at a
time, that is globally configured via sysctl.

With an iked server, having a single client using a non-standard port to
circumvent a firewall requires changing the global port.  This in turn
requires switching all other clients to use the same port if they are
behind a NAT. Obviously that doesn't scale very well.

The diff below adds a new UDP sockopt called UDP_ENCAP that can be used to
enable receiving of UDP encapsulated IPsec packets on a bound port.  This
allows us to dynamically configure additional udpencap ports directly from
the IKE daemon.

The diff touches pledge to allow the new sockopt from a process with "inet"
promise. The added attack surface seems low to me and "inet" feels like the
right place for this kind of sockopt, but I'd be happy to hear more opinions
on this.

Feedback or ok?

Index: sys/kern/kern_pledge.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_pledge.c,v
retrieving revision 1.275
diff -u -p -r1.275 kern_pledge.c
--- sys/kern/kern_pledge.c      29 Jun 2021 01:46:35 -0000      1.275
+++ sys/kern/kern_pledge.c      25 Oct 2021 17:52:41 -0000
@@ -52,6 +52,7 @@
 #include <netinet6/in6_var.h>
 #include <netinet6/nd6.h>
 #include <netinet/tcp.h>
+#include <netinet/udp.h>
 #include <net/pfvar.h>
 
 #include <sys/conf.h>
@@ -1417,6 +1418,12 @@ pledge_sockopt(struct proc *p, int set, 
                case TCP_SACK_ENABLE:
                case TCP_MAXSEG:
                case TCP_NOPUSH:
+                       return (0);
+               }
+               break;
+       case IPPROTO_UDP:
+               switch (optname) {
+               case UDP_ENCAP:
                        return (0);
                }
                break;
Index: sys/netinet/in_pcb.h
===================================================================
RCS file: /cvs/src/sys/netinet/in_pcb.h,v
retrieving revision 1.121
diff -u -p -r1.121 in_pcb.h
--- sys/netinet/in_pcb.h        25 Jan 2021 03:40:46 -0000      1.121
+++ sys/netinet/in_pcb.h        25 Oct 2021 17:52:41 -0000
@@ -181,6 +181,7 @@ struct inpcbtable {
 #define        INP_RECVDSTPORT 0x200   /* receive IP dst addr before rdr */
 #define        INP_RECVRTABLE  0x400   /* receive routing table */
 #define        INP_IPSECFLOWINFO 0x800 /* receive IPsec flow info */
+#define        INP_UDPENCAP    0x1000  /* check for UDP encapsulated ESP */
 
 #define        INP_CONTROLOPTS (INP_RECVOPTS|INP_RECVRETOPTS|INP_RECVDSTADDR| \
            INP_RXSRCRT|INP_HOPLIMIT|INP_RECVIF|INP_RECVTTL|INP_RECVDSTPORT| \
Index: sys/netinet/in_proto.c
===================================================================
RCS file: /cvs/src/sys/netinet/in_proto.c,v
retrieving revision 1.96
diff -u -p -r1.96 in_proto.c
--- sys/netinet/in_proto.c      24 Oct 2021 22:59:47 -0000      1.96
+++ sys/netinet/in_proto.c      25 Oct 2021 17:52:41 -0000
@@ -188,7 +188,7 @@ const struct protosw inetsw[] = {
   .pr_flags    = PR_ATOMIC|PR_ADDR|PR_SPLICE,
   .pr_input    = udp_input,
   .pr_ctlinput = udp_ctlinput,
-  .pr_ctloutput        = ip_ctloutput,
+  .pr_ctloutput        = udp_ctloutput,
   .pr_usrreq   = udp_usrreq,
   .pr_attach   = udp_attach,
   .pr_detach   = udp_detach,
Index: sys/netinet/udp.h
===================================================================
RCS file: /cvs/src/sys/netinet/udp.h,v
retrieving revision 1.5
diff -u -p -r1.5 udp.h
--- sys/netinet/udp.h   2 Jun 2003 23:28:15 -0000       1.5
+++ sys/netinet/udp.h   25 Oct 2021 17:52:41 -0000
@@ -46,4 +46,14 @@ struct udphdr {
        u_int16_t uh_sum;               /* udp checksum */
 };
 
+/*
+ * User-settable options (used with setsockopt).
+ */
+#define UDP_ENCAP              0x01
+
+/*
+ * UDP Encapsulation of IPsec Packets options.
+ */
+#define UDP_ENCAP_ESPINUDP     0x01
+
 #endif /* _NETINET_UDP_H_ */
Index: sys/netinet/udp_usrreq.c
===================================================================
RCS file: /cvs/src/sys/netinet/udp_usrreq.c,v
retrieving revision 1.263
diff -u -p -r1.263 udp_usrreq.c
--- sys/netinet/udp_usrreq.c    23 Oct 2021 22:19:37 -0000      1.263
+++ sys/netinet/udp_usrreq.c    25 Oct 2021 17:52:41 -0000
@@ -143,6 +143,8 @@ int udp_output(struct inpcb *, struct mb
 void   udp_notify(struct inpcb *, int);
 int    udp_sysctl_udpstat(void *, size_t *, void *);
 
+static inline int udp_encap_input(struct mbuf **, int *, int, int);
+
 #ifndef        UDB_INITIAL_HASH_SIZE
 #define        UDB_INITIAL_HASH_SIZE   128
 #endif
@@ -154,6 +156,48 @@ udp_init(void)
        in_pcbinit(&udbtable, UDB_INITIAL_HASH_SIZE);
 }
 
+static inline int
+udp_encap_input(struct mbuf **mp, int *offp, int proto, int af)
+{
+       struct mbuf *m = *mp;
+       int iphlen = *offp;
+       u_int32_t spi;
+       int skip = iphlen + sizeof(struct udphdr);
+       int protoff;
+
+       if (m->m_pkthdr.len - skip < sizeof(u_int32_t)) {
+               /* packet too short */
+               m_freem(m);
+               return IPPROTO_DONE;
+       }
+       m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi);
+       /*
+        * decapsulate if the SPI is not zero, otherwise pass
+        * to userland
+        */
+       if (spi != 0) {
+               if ((m = *mp = m_pullup(m, skip)) == NULL) {
+                       udpstat_inc(udps_hdrops);
+                       return IPPROTO_DONE;
+               }
+
+               /* remove the UDP header */
+               bcopy(mtod(m, u_char *),
+                   mtod(m, u_char *) + sizeof(struct udphdr), iphlen);
+               m_adj(m, sizeof(struct udphdr));
+               skip -= sizeof(struct udphdr);
+
+               espstat_inc(esps_udpencin);
+               protoff = af == AF_INET ? offsetof(struct ip, ip_p) :
+                   offsetof(struct ip6_hdr, ip6_nxt);
+               ipsec_common_input(mp, skip, protoff,
+                   af, IPPROTO_ESP, 1);
+               return IPPROTO_DONE;
+       }
+
+       return 0;
+}
+
 int
 udp_input(struct mbuf **mp, int *offp, int proto, int af)
 {
@@ -177,7 +221,7 @@ udp_input(struct mbuf **mp, int *offp, i
        struct m_tag *mtag;
        struct tdb_ident *tdbi;
        struct tdb *tdb;
-       int error, protoff;
+       int error;
 #endif /* IPSEC */
        u_int32_t ipsecflowinfo = 0;
 
@@ -276,40 +320,9 @@ udp_input(struct mbuf **mp, int *offp, i
 #if NPF > 0
            !(m->m_pkthdr.pf.flags & PF_TAG_DIVERTED) &&
 #endif
-           uh->uh_dport == htons(udpencap_port)) {
-               u_int32_t spi;
-               int skip = iphlen + sizeof(struct udphdr);
-
-               if (m->m_pkthdr.len - skip < sizeof(u_int32_t)) {
-                       /* packet too short */
-                       m_freem(m);
-                       return IPPROTO_DONE;
-               }
-               m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi);
-               /*
-                * decapsulate if the SPI is not zero, otherwise pass
-                * to userland
-                */
-               if (spi != 0) {
-                       if ((m = *mp = m_pullup(m, skip)) == NULL) {
-                               udpstat_inc(udps_hdrops);
-                               return IPPROTO_DONE;
-                       }
-
-                       /* remove the UDP header */
-                       bcopy(mtod(m, u_char *),
-                           mtod(m, u_char *) + sizeof(struct udphdr), iphlen);
-                       m_adj(m, sizeof(struct udphdr));
-                       skip -= sizeof(struct udphdr);
-
-                       espstat_inc(esps_udpencin);
-                       protoff = af == AF_INET ? offsetof(struct ip, ip_p) :
-                           offsetof(struct ip6_hdr, ip6_nxt);
-                       ipsec_common_input(mp, skip, protoff,
-                           af, IPPROTO_ESP, 1);
+           (udpencap_port && uh->uh_dport == htons(udpencap_port)))
+               if (udp_encap_input(mp, offp, proto, af) == IPPROTO_DONE)
                        return IPPROTO_DONE;
-               }
-       }
 #endif
 
        switch (af) {
@@ -503,6 +516,14 @@ udp_input(struct mbuf **mp, int *offp, i
        }
 
 #ifdef IPSEC
+       if (udpencap_enable && esp_enable &&
+#if NPF > 0
+           !(m->m_pkthdr.pf.flags & PF_TAG_DIVERTED) &&
+#endif
+           inp && inp->inp_flags & INP_UDPENCAP)
+               if (udp_encap_input(mp, offp, proto, af) == IPPROTO_DONE)
+                       return IPPROTO_DONE;
+
        if (ipsec_in_use) {
                mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL);
                if (mtag != NULL) {
@@ -1306,6 +1327,64 @@ udp_sysctl(int *name, u_int namelen, voi
                return (error);
        }
        /* NOTREACHED */
+}
+
+int
+udp_ctloutput(int op, struct socket *so, int level, int optname,
+    struct mbuf *m)
+{
+       struct inpcb *inp;
+       int error = 0;
+
+       inp = sotoinpcb(so);
+       if (inp == NULL)
+               return (ECONNRESET);
+       if (level != IPPROTO_UDP) {
+               switch (so->so_proto->pr_domain->dom_family) {
+#ifdef INET6
+               case PF_INET6:
+                       error = ip6_ctloutput(op, so, level, optname, m);
+                       break;
+#endif /* INET6 */
+               case PF_INET:
+                       error = ip_ctloutput(op, so, level, optname, m);
+                       break;
+               default:
+                       error = EAFNOSUPPORT;   /*?*/
+                       break;
+               }
+               return (error);
+       }
+
+       switch (op) {
+       case PRCO_SETOPT:
+               switch (optname) {
+               case UDP_ENCAP:
+                       if (m == NULL || m->m_len != sizeof(int))
+                               error = EINVAL;
+                       else if (*mtod(m, int *))
+                               inp->inp_flags |= INP_UDPENCAP;
+                       else
+                               inp->inp_flags &= ~INP_UDPENCAP;
+                       break;
+               default:
+                       error = ENOPROTOOPT;
+                       break;
+               }
+               break;
+       case PRCO_GETOPT:
+               switch (optname) {
+               case UDP_ENCAP:
+                       m->m_len = sizeof(int);
+                       *mtod(m, int *) = inp->inp_flags & INP_UDPENCAP;
+                       break;
+               default:
+                       error = ENOPROTOOPT;
+                       break;
+               }
+               break;
+       }
+       return (error);
 }
 
 int
Index: sys/netinet/udp_var.h
===================================================================
RCS file: /cvs/src/sys/netinet/udp_var.h,v
retrieving revision 1.35
diff -u -p -r1.35 udp_var.h
--- sys/netinet/udp_var.h       22 Aug 2020 17:54:57 -0000      1.35
+++ sys/netinet/udp_var.h       25 Oct 2021 17:52:41 -0000
@@ -130,6 +130,7 @@ extern struct       udpstat udpstat;
 void   udp6_ctlinput(int, struct sockaddr *, u_int, void *);
 #endif /* INET6 */
 void    udp_ctlinput(int, struct sockaddr *, u_int, void *);
+int     udp_ctloutput(int, struct socket *, int, int, struct mbuf *);
 void    udp_init(void);
 int     udp_input(struct mbuf **, int *, int, int);
 #ifdef INET6
Index: sbin/iked/config.c
===================================================================
RCS file: /cvs/src/sbin/iked/config.c,v
retrieving revision 1.82
diff -u -p -r1.82 config.c
--- sbin/iked/config.c  12 Oct 2021 09:27:21 -0000      1.82
+++ sbin/iked/config.c  25 Oct 2021 17:52:42 -0000
@@ -21,6 +21,8 @@
 #include <sys/socket.h>
 #include <sys/uio.h>
 
+#include <netinet/in.h>
+#include <netinet/udp.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -578,12 +580,22 @@ config_doreset(struct iked *env, unsigne
  */
 int
 config_setsocket(struct iked *env, struct sockaddr_storage *ss,
-    in_port_t port, enum privsep_procid id)
+    in_port_t port, enum privsep_procid id, int natt)
 {
        int      s;
 
        if ((s = udp_bind((struct sockaddr *)ss, port)) == -1)
                return (-1);
+       if (natt && ss->ss_family != AF_INET6) {
+               int      sopt;
+               sopt = UDP_ENCAP_ESPINUDP;
+               if (setsockopt(s, IPPROTO_UDP, UDP_ENCAP,
+                   &sopt, sizeof(sopt)) < 0) {
+                       log_warn("%s: failed to set UDP encap socket option",
+                           __func__);
+                       return (-1);
+               }
+       }
        proc_compose_imsg(&env->sc_ps, id, -1,
            IMSG_UDP_SOCKET, -1, s, ss, sizeof(*ss));
        return (0);
Index: sbin/iked/iked.c
===================================================================
RCS file: /cvs/src/sbin/iked/iked.c,v
retrieving revision 1.58
diff -u -p -r1.58 iked.c
--- sbin/iked/iked.c    1 Sep 2021 15:30:06 -0000       1.58
+++ sbin/iked/iked.c    25 Oct 2021 17:52:42 -0000
@@ -244,17 +244,17 @@ parent_configure(struct iked *env)
 
        /* see comment on config_setsocket() */
        if (env->sc_nattmode != NATT_FORCE)
-               config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2);
+               config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2, 0);
        if (env->sc_nattmode != NATT_DISABLE)
-               config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2);
+               config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2, 
1);
 
        bzero(&ss, sizeof(ss));
        ss.ss_family = AF_INET6;
 
        if (env->sc_nattmode != NATT_FORCE)
-               config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2);
+               config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2, 0);
        if (env->sc_nattmode != NATT_DISABLE)
-               config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2);
+               config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2, 
1);
 
        /*
         * pledge in the parent process:
Index: sbin/iked/iked.h
===================================================================
RCS file: /cvs/src/sbin/iked/iked.h,v
retrieving revision 1.194
diff -u -p -r1.194 iked.h
--- sbin/iked/iked.h    12 Oct 2021 10:01:59 -0000      1.194
+++ sbin/iked/iked.h    25 Oct 2021 17:52:42 -0000
@@ -862,7 +862,7 @@ int  config_setflow(struct iked *, struc
            enum privsep_procid);
 int     config_getflow(struct iked *, struct imsg *);
 int     config_setsocket(struct iked *, struct sockaddr_storage *, in_port_t,
-           enum privsep_procid);
+           enum privsep_procid, int);
 int     config_getsocket(struct iked *env, struct imsg *,
            void (*cb)(int, short, void *));
 int     config_setpfkey(struct iked *);
Index: sbin/iked/ikev2.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2.c,v
retrieving revision 1.329
diff -u -p -r1.329 ikev2.c
--- sbin/iked/ikev2.c   12 Oct 2021 10:01:59 -0000      1.329
+++ sbin/iked/ikev2.c   25 Oct 2021 17:52:42 -0000
@@ -24,6 +24,7 @@
 
 #include <netinet/in.h>
 #include <netinet/ip_ipsp.h>
+#include <netinet/udp.h>
 #include <arpa/inet.h>
 
 #include <stdlib.h>
@@ -1614,6 +1615,16 @@ ikev2_init_done(struct iked *env, struct
        if (!sa_stateok(sa, IKEV2_STATE_VALID))
                return (0);     /* ignored */
 
+       if (sa->sa_udpencap) {
+               int      sopt;
+               sopt = UDP_ENCAP_ESPINUDP;
+               if (setsockopt(sa->sa_fd, IPPROTO_UDP, UDP_ENCAP,
+                   &sopt, sizeof(sopt)) < 0) {
+                       log_warn("%s: failed to set UDP encap socket option",
+                           __func__);
+                       return (-1);
+               }
+       }
        ret = ikev2_childsa_negotiate(env, sa, &sa->sa_kex, &sa->sa_proposals,
            sa->sa_hdr.sh_initiator, 0);
        if (ret == 0)

Reply via email to