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)