--- /dev/null
+/* $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 = <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);
+}