add sec(4) to support route based ipsec vpns.
authordlg <dlg@openbsd.org>
Mon, 7 Aug 2023 01:57:33 +0000 (01:57 +0000)
committerdlg <dlg@openbsd.org>
Mon, 7 Aug 2023 01:57:33 +0000 (01:57 +0000)
ipsec security associations (SAs, aka tdbs inside the kernel) can
now specify that they're to be used with an interface (using
TDBF_IFACE) rather than the ipsec security policy database. sec(4)
is the driver providing that interface.

the name is specifically chosen to not be ipsec(4) because that's
already taken by the manpage for the ipsec stack generally. sec(4)
is short, easy to type and pronounce, and kind of sounds like ipsec
anyway. the names for this type of interface in other platforms
seems to be universally terrible and too generic, so i didn't want
to copy any of those either.

sec(4) can be considered equivalent to gif(4) protected by ipsec,
and on the wire it actually looks the same. sec(4) exists to better
support how security associations for route-based ipsec VPNs are
negotiated and to avoid SPD entries for them.

the code is a little green, but i'm putting it in now so it can be
hacked on in the tree.

support from many including markus@ tobhe@ claudio@ sthen@ patrick@
now is a good time deraadt@

sys/net/if_sec.c [new file with mode: 0644]
sys/net/if_sec.h [new file with mode: 0644]

diff --git a/sys/net/if_sec.c b/sys/net/if_sec.c
new file mode 100644 (file)
index 0000000..d41f2c3
--- /dev/null
@@ -0,0 +1,578 @@
+/*     $OpenBSD: if_sec.c,v 1.1 2023/08/07 01:57:33 dlg Exp $ */
+
+/*
+ * Copyright (c) 2022 The University of Queensland
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This code was written by David Gwynne <dlg@uq.edu.au> as part
+ * of the Information Technology Infrastructure Group (ITIG) in the
+ * Faculty of Engineering, Architecture and Information Technology
+ * (EAIT).
+ */
+
+#ifndef IPSEC
+#error sec enabled without IPSEC defined
+#endif
+
+#include "bpfilter.h"
+#include "pf.h"
+
+#include <sys/param.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/kernel.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/timeout.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/pool.h>
+#include <sys/smr.h>
+#include <sys/refcnt.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+#include <net/toeplitz.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/ip_ecn.h>
+#include <netinet/ip_ipsp.h>
+
+#ifdef INET6
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/in6_var.h>
+#endif
+
+#ifdef MPLS
+#include <netmpls/mpls.h>
+#endif /* MPLS */
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#if NPF > 0
+#include <net/pfvar.h>
+#endif
+
+#define SEC_MTU                1280
+#define SEC_MTU_MIN    1280
+#define SEC_MTU_MAX    32768   /* could get closer to 64k... */
+
+struct sec_softc {
+       struct ifnet                    sc_if;
+
+       struct task                     sc_send;
+
+       unsigned int                    sc_unit;
+       SMR_SLIST_ENTRY(sec_softc)      sc_entry;
+       struct refcnt                   sc_refs;
+};
+
+SMR_SLIST_HEAD(sec_bucket, sec_softc);
+
+static int     sec_output(struct ifnet *, struct mbuf *, struct sockaddr *,
+                   struct rtentry *);
+static int     sec_enqueue(struct ifnet *, struct mbuf *);
+static void    sec_send(void *);
+static void    sec_start(struct ifnet *);
+
+static int     sec_ioctl(struct ifnet *, u_long, caddr_t);
+static int     sec_up(struct sec_softc *);
+static int     sec_down(struct sec_softc *);
+
+static int     sec_clone_create(struct if_clone *, int);
+static int     sec_clone_destroy(struct ifnet *);
+
+static struct tdb *
+               sec_tdb_get(unsigned int);
+static void    sec_tdb_gc(void *);
+
+static struct if_clone sec_cloner =
+    IF_CLONE_INITIALIZER("sec", sec_clone_create, sec_clone_destroy);
+
+static struct sec_bucket        sec_map[256] __aligned(CACHELINESIZE);
+static struct tdb              *sec_tdbh[256] __aligned(CACHELINESIZE);
+
+static struct tdb              *sec_tdb_gc_list;
+static struct task              sec_tdb_gc_task =
+                                    TASK_INITIALIZER(sec_tdb_gc, NULL);
+static struct mutex             sec_tdb_gc_mtx =
+                                    MUTEX_INITIALIZER(IPL_MPFLOOR);
+
+void
+secattach(int n)
+{
+       if_clone_attach(&sec_cloner);
+}
+
+static int
+sec_clone_create(struct if_clone *ifc, int unit)
+{
+       struct sec_softc *sc;
+       struct ifnet *ifp;
+
+       sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
+
+       sc->sc_unit = unit;
+
+       task_set(&sc->sc_send, sec_send, sc);
+
+       snprintf(sc->sc_if.if_xname, sizeof sc->sc_if.if_xname, "%s%d",
+           ifc->ifc_name, unit);
+
+       ifp = &sc->sc_if;
+       ifp->if_softc = sc;
+       ifp->if_type = IFT_TUNNEL;
+       ifp->if_mtu = SEC_MTU;
+       ifp->if_flags = IFF_POINTOPOINT|IFF_MULTICAST;
+       ifp->if_xflags = IFXF_CLONED;
+       ifp->if_bpf_mtap = p2p_bpf_mtap;
+       ifp->if_input = p2p_input;
+       ifp->if_output = sec_output;
+       ifp->if_enqueue = sec_enqueue;
+       ifp->if_start = sec_start;
+       ifp->if_ioctl = sec_ioctl;
+       ifp->if_rtrequest = p2p_rtrequest;
+
+       if_counters_alloc(ifp);
+       if_attach(ifp);
+       if_alloc_sadl(ifp);
+
+#if NBPFILTER > 0
+       bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t));
+#endif
+
+       return (0);
+}
+
+static int
+sec_clone_destroy(struct ifnet *ifp)
+{
+       struct sec_softc *sc = ifp->if_softc;
+
+       NET_LOCK();
+       if (ISSET(ifp->if_flags, IFF_RUNNING))
+               sec_down(sc);
+       NET_UNLOCK();
+
+       if_detach(ifp);
+
+       free(sc, M_DEVBUF, sizeof(*sc));
+
+       return (0);
+}
+
+static int
+sec_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+       struct sec_softc *sc = ifp->if_softc;
+       struct ifreq *ifr = (struct ifreq *)data;
+       int error = 0;
+
+       switch (cmd) {
+       case SIOCSIFADDR:
+               break;
+
+       case SIOCSIFFLAGS:
+               if (ISSET(ifp->if_flags, IFF_UP)) {
+                       if (!ISSET(ifp->if_flags, IFF_RUNNING))
+                               error = sec_up(sc);
+                       else
+                               error = 0;
+               } else {
+                       if (ISSET(ifp->if_flags, IFF_RUNNING))
+                               error = sec_down(sc);
+               }
+               break;
+
+       case SIOCADDMULTI:
+       case SIOCDELMULTI:
+               break;
+
+       case SIOCSIFMTU:
+               if (ifr->ifr_mtu < SEC_MTU_MIN ||
+                   ifr->ifr_mtu > SEC_MTU_MAX) {
+                       error = EINVAL;
+                       break;
+               }
+
+               ifp->if_mtu = ifr->ifr_mtu;
+               break;
+
+       default:
+               error = ENOTTY;
+               break;
+       }
+
+       return (error);
+}
+
+static int
+sec_up(struct sec_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_if;
+       unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
+
+       NET_ASSERT_LOCKED();
+
+       SET(ifp->if_flags, IFF_RUNNING);
+       refcnt_init(&sc->sc_refs);
+
+       SMR_SLIST_INSERT_HEAD_LOCKED(&sec_map[idx], sc, sc_entry);
+
+       return (0);
+}
+
+static int
+sec_down(struct sec_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_if;
+       unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
+
+       NET_ASSERT_LOCKED();
+
+       CLR(ifp->if_flags, IFF_RUNNING);
+
+       SMR_SLIST_REMOVE_LOCKED(&sec_map[idx], sc, sec_softc, sc_entry);
+
+       smr_barrier();
+       taskq_del_barrier(systq, &sc->sc_send);
+
+       refcnt_finalize(&sc->sc_refs, "secdown");
+
+       return (0);
+}
+
+static int
+sec_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
+    struct rtentry *rt)
+{
+       struct m_tag *mtag;
+       int error = 0;
+
+       if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
+               error = ENETDOWN;
+               goto drop;
+       }
+
+       switch (dst->sa_family) {
+       case AF_INET:
+#ifdef INET6
+       case AF_INET6:
+#endif
+#ifdef MPLS
+       case AF_MPLS:
+#endif
+               break;
+       default:
+               error = EAFNOSUPPORT;
+               goto drop;
+       }
+
+       mtag = NULL;
+       while ((mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) != NULL) {
+               if (ifp->if_index == *(int *)(mtag + 1)) {
+                       error = EIO;
+                       goto drop;
+               }
+       }
+
+       m->m_pkthdr.ph_family = dst->sa_family;
+
+       error = if_enqueue(ifp, m);
+       if (error != 0)
+               counters_inc(ifp->if_counters, ifc_oerrors);
+
+       return (error);
+
+drop:
+       m_freem(m);
+       return (error);
+}
+
+static int
+sec_enqueue(struct ifnet *ifp, struct mbuf *m)
+{
+       struct sec_softc *sc = ifp->if_softc;
+       struct ifqueue *ifq = &ifp->if_snd;
+       int error;
+
+       error = ifq_enqueue(ifq, m);
+       if (error)
+               return (error);
+
+       task_add(systq, &sc->sc_send);
+
+       return (0);
+}
+
+static void
+sec_send(void *arg)
+{
+       struct sec_softc *sc = arg;
+       struct ifnet *ifp = &sc->sc_if;
+       struct ifqueue *ifq = &ifp->if_snd;
+       struct tdb *tdb;
+       struct mbuf *m;
+       int error;
+
+       if (!ISSET(ifp->if_flags, IFF_RUNNING))
+               return;
+
+       tdb = sec_tdb_get(sc->sc_unit);
+       if (tdb == NULL)
+               goto purge;
+
+       NET_LOCK();
+       while ((m = ifq_dequeue(ifq)) != NULL) {
+               CLR(m->m_flags, M_BCAST|M_MCAST);
+
+#if NPF > 0
+               pf_pkt_addr_changed(m);
+#endif
+
+               error = ipsp_process_packet(m, tdb,
+                   m->m_pkthdr.ph_family, /* already tunnelled? */ 0);
+               if (error != 0)
+                       counters_inc(ifp->if_counters, ifc_oerrors);
+       }
+       NET_UNLOCK();
+
+       tdb_unref(tdb);
+       return;
+
+purge:
+       counters_add(ifp->if_counters, ifc_oerrors, ifq_purge(ifq));
+}
+
+static void
+sec_start(struct ifnet *ifp)
+{
+       counters_add(ifp->if_counters, ifc_oerrors, ifq_purge(&ifp->if_snd));
+}
+
+/*
+ * ipsec_input handling
+ */
+
+struct sec_softc *
+sec_get(unsigned int unit)
+{
+       unsigned int idx = stoeplitz_h32(unit) % nitems(sec_map);
+       struct sec_bucket *sb = &sec_map[idx];
+       struct sec_softc *sc;
+
+       smr_read_enter();
+       SMR_SLIST_FOREACH(sc, sb, sc_entry) {
+               if (sc->sc_unit == unit) {
+                       refcnt_take(&sc->sc_refs);
+                       break;
+               }
+       }
+       smr_read_leave();
+
+       return (sc);
+}
+
+void
+sec_input(struct sec_softc *sc, int af, int proto, struct mbuf *m)
+{
+       struct ip *iph;
+       int hlen;
+
+       switch (af) {
+       case AF_INET:
+               iph = mtod(m, struct ip *);
+               hlen = iph->ip_hl << 2;
+               break;
+#ifdef INET6
+       case AF_INET6:
+               hlen = sizeof(struct ip6_hdr);
+               break;
+#endif
+       default:
+               unhandled_af(af);
+       }
+
+       m_adj(m, hlen);
+
+       switch (proto) {
+       case IPPROTO_IPV4:
+               af = AF_INET;
+               break;
+       case IPPROTO_IPV6:
+               af = AF_INET6;
+               break;
+       case IPPROTO_MPLS:
+               af = AF_MPLS;
+               break;
+       default:
+               af = AF_UNSPEC;
+               break;
+       }
+
+       m->m_pkthdr.ph_family = af;
+
+       if_vinput(&sc->sc_if, m);
+}
+
+void
+sec_put(struct sec_softc *sc)
+{
+       refcnt_rele_wake(&sc->sc_refs);
+}
+
+/*
+ * tdb handling
+ */
+
+static int
+sec_tdb_valid(struct tdb *tdb)
+{
+       KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
+
+       if (!ISSET(tdb->tdb_flags, TDBF_TUNNELING))
+               return (0);
+       if (ISSET(tdb->tdb_flags, TDBF_INVALID))
+               return (0);
+
+       if (tdb->tdb_iface_dir != IPSP_DIRECTION_OUT)
+               return (0);
+
+       return (1);
+}
+
+/*
+ * these are called from netinet/ip_ipsp.c with tdb_sadb_mtx held,
+ * which we rely on to serialise modifications to the sec_tdbh.
+ */
+
+void
+sec_tdb_insert(struct tdb *tdb)
+{
+       unsigned int idx;
+       struct tdb **tdbp;
+       struct tdb *ltdb;
+
+       if (!sec_tdb_valid(tdb))
+               return;
+
+       idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
+       tdbp = &sec_tdbh[idx];
+
+       tdb_ref(tdb); /* take a ref for the SMR pointer */
+
+       /* wire the tdb into the head of the list */
+       ltdb = SMR_PTR_GET_LOCKED(tdbp);
+       SMR_PTR_SET_LOCKED(&tdb->tdb_dnext, ltdb);
+       SMR_PTR_SET_LOCKED(tdbp, tdb);
+}
+
+void
+sec_tdb_remove(struct tdb *tdb)
+{
+       struct tdb **tdbp;
+       struct tdb *ltdb;
+       unsigned int idx;
+
+       if (!sec_tdb_valid(tdb))
+               return;
+
+       idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
+       tdbp = &sec_tdbh[idx];
+
+       while ((ltdb = SMR_PTR_GET_LOCKED(tdbp)) != NULL) {
+               if (ltdb == tdb) {
+                       /* take the tdb out of the list */
+                       ltdb = SMR_PTR_GET_LOCKED(&tdb->tdb_dnext);
+                       SMR_PTR_SET_LOCKED(tdbp, ltdb);
+
+                       /* move the ref to the gc */
+
+                       mtx_enter(&sec_tdb_gc_mtx);
+                       tdb->tdb_dnext = sec_tdb_gc_list;
+                       sec_tdb_gc_list = tdb;
+                       mtx_leave(&sec_tdb_gc_mtx);
+                       task_add(systq, &sec_tdb_gc_task);
+
+                       return;
+               }
+
+               tdbp = &ltdb->tdb_dnext;
+       }
+
+       panic("%s: unable to find tdb %p", __func__, tdb);
+}
+
+static void
+sec_tdb_gc(void *null)
+{
+       struct tdb *tdb, *ntdb;
+
+       mtx_enter(&sec_tdb_gc_mtx);
+       tdb = sec_tdb_gc_list;
+       sec_tdb_gc_list = NULL;
+       mtx_leave(&sec_tdb_gc_mtx);
+
+       if (tdb == NULL)
+               return;
+
+       smr_barrier();
+
+       NET_LOCK();
+       do {
+               ntdb = tdb->tdb_dnext;
+               tdb_unref(tdb);
+               tdb = ntdb;
+       } while (tdb != NULL);
+       NET_UNLOCK();
+}
+
+struct tdb *
+sec_tdb_get(unsigned int unit)
+{
+       unsigned int idx;
+       struct tdb **tdbp;
+       struct tdb *tdb;
+
+       idx = stoeplitz_h32(unit) % nitems(sec_map);
+       tdbp = &sec_tdbh[idx];
+
+       smr_read_enter();
+       while ((tdb = SMR_PTR_GET(tdbp)) != NULL) {
+               KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
+               if (!ISSET(tdb->tdb_flags, TDBF_DELETED) &&
+                   tdb->tdb_iface == unit) {
+                       tdb_ref(tdb);
+                       break;
+               }
+
+               tdbp = &tdb->tdb_dnext;
+       }
+       smr_read_leave();
+
+       return (tdb);
+}
diff --git a/sys/net/if_sec.h b/sys/net/if_sec.h
new file mode 100644 (file)
index 0000000..f4f01ed
--- /dev/null
@@ -0,0 +1,44 @@
+/*     $OpenBSD: if_sec.h,v 1.1 2023/08/07 01:57:33 dlg Exp $ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _NET_IF_SEC_H
+#define _NET_IF_SEC_H
+
+#ifdef _KERNEL
+struct sec_softc;
+struct tdb;
+
+/*
+ * let the IPsec stack hand packets to sec(4) for input
+ */
+
+struct sec_softc       *sec_get(unsigned int);
+void                    sec_input(struct sec_softc * , int, int,
+                            struct mbuf *);
+void                    sec_put(struct sec_softc *);
+
+/*
+ * let the IPsec stack give tdbs to sec(4) for output
+ */
+
+void                    sec_tdb_insert(struct tdb *);
+void                    sec_tdb_remove(struct tdb *);
+
+#endif /* _KERNEL */
+
+#endif /* _NET_IF_SEC_H */