From: dlg Date: Mon, 7 Aug 2023 01:57:33 +0000 (+0000) Subject: add sec(4) to support route based ipsec vpns. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=433cd47b3998c7b6449aab67696f889070f0a238;p=openbsd add sec(4) to support route based ipsec vpns. 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@ --- diff --git a/sys/net/if_sec.c b/sys/net/if_sec.c new file mode 100644 index 00000000000..d41f2c3d3f5 --- /dev/null +++ b/sys/net/if_sec.c @@ -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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#include +#endif + +#ifdef MPLS +#include +#endif /* MPLS */ + +#if NBPFILTER > 0 +#include +#endif + +#if NPF > 0 +#include +#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 = <db->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 index 00000000000..f4f01ed01e0 --- /dev/null +++ b/sys/net/if_sec.h @@ -0,0 +1,44 @@ +/* $OpenBSD: if_sec.h,v 1.1 2023/08/07 01:57:33 dlg Exp $ */ + +/* + * Copyright (c) 2023 David Gwynne + * + * 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 */