summaryrefslogtreecommitdiff
path: root/sys/net/if_tpmr.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/net/if_tpmr.c')
-rw-r--r--sys/net/if_tpmr.c717
1 files changed, 717 insertions, 0 deletions
diff --git a/sys/net/if_tpmr.c b/sys/net/if_tpmr.c
new file mode 100644
index 00000000000..ed4832868de
--- /dev/null
+++ b/sys/net/if_tpmr.c
@@ -0,0 +1,717 @@
+/* $OpenBSD: if_tpmr.c,v 1.1 2019/08/01 03:05:46 dlg Exp $ */
+
+/*
+ * Copyright (c) 2019 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).
+ */
+
+#include "bpfilter.h"
+#include "vlan.h"
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/systm.h>
+#include <sys/syslog.h>
+#include <sys/rwlock.h>
+#include <sys/percpu.h>
+#include <sys/smr.h>
+#include <sys/task.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <net/if_media.h> /* if_trunk.h uses ifmedia bits */
+#include <crypto/siphash.h> /* if_trunk.h uses siphash bits */
+#include <net/if_trunk.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#if NVLAN > 0
+#include <net/if_vlan_var.h>
+#endif
+
+/*
+ * tpmr interface
+ */
+
+#define TPMR_NUM_PORTS 2
+#define TPMR_TRUNK_PROTO TRUNK_PROTO_NONE
+
+struct tpmr_softc;
+
+struct tpmr_port {
+ struct ifnet *p_ifp0;
+
+ int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
+ int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
+ struct rtentry *);
+
+ void *p_lcookie;
+ void *p_dcookie;
+
+ struct tpmr_softc *p_tpmr;
+ unsigned int p_slot;
+};
+
+struct tpmr_softc {
+ struct ifnet sc_if;
+ unsigned int sc_dead;
+
+ struct tpmr_port *sc_ports[TPMR_NUM_PORTS];
+ unsigned int sc_nports;
+};
+
+#define DPRINTF(_sc, fmt...) do { \
+ if (ISSET((_sc)->sc_if.if_flags, IFF_DEBUG)) \
+ printf(fmt); \
+} while (0)
+
+static int tpmr_clone_create(struct if_clone *, int);
+static int tpmr_clone_destroy(struct ifnet *);
+
+static int tpmr_ioctl(struct ifnet *, u_long, caddr_t);
+static int tpmr_enqueue(struct ifnet *, struct mbuf *);
+static int tpmr_output(struct ifnet *, struct mbuf *, struct sockaddr *,
+ struct rtentry *);
+static void tpmr_start(struct ifqueue *);
+
+static int tpmr_up(struct tpmr_softc *);
+static int tpmr_down(struct tpmr_softc *);
+static int tpmr_iff(struct tpmr_softc *);
+
+static void tpmr_p_linkch(void *);
+static void tpmr_p_detach(void *);
+static int tpmr_p_ioctl(struct ifnet *, u_long, caddr_t);
+static int tpmr_p_output(struct ifnet *, struct mbuf *,
+ struct sockaddr *, struct rtentry *);
+
+static int tpmr_get_trunk(struct tpmr_softc *, struct trunk_reqall *);
+static void tpmr_p_dtor(struct tpmr_softc *, struct tpmr_port *,
+ const char *);
+static int tpmr_add_port(struct tpmr_softc *,
+ const struct trunk_reqport *);
+static int tpmr_get_port(struct tpmr_softc *, struct trunk_reqport *);
+static int tpmr_del_port(struct tpmr_softc *,
+ const struct trunk_reqport *);
+
+static struct if_clone tpmr_cloner =
+ IF_CLONE_INITIALIZER("tpmr", tpmr_clone_create, tpmr_clone_destroy);
+
+void
+tpmrattach(int count)
+{
+ if_clone_attach(&tpmr_cloner);
+}
+
+static int
+tpmr_clone_create(struct if_clone *ifc, int unit)
+{
+ struct tpmr_softc *sc;
+ struct ifnet *ifp;
+
+ sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
+ if (sc == NULL)
+ return (ENOMEM);
+
+ ifp = &sc->sc_if;
+
+ snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
+ ifc->ifc_name, unit);
+
+ ifp->if_softc = sc;
+ ifp->if_type = IFT_BRIDGE;
+ ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
+ ifp->if_mtu = 0;
+ ifp->if_addrlen = ETHER_ADDR_LEN;
+ ifp->if_hdrlen = ETHER_HDR_LEN;
+ ifp->if_ioctl = tpmr_ioctl;
+ ifp->if_output = tpmr_output;
+ ifp->if_enqueue = tpmr_enqueue;
+ ifp->if_qstart = tpmr_start;
+ ifp->if_flags = IFF_POINTOPOINT;
+ ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
+ ifp->if_link_state = LINK_STATE_DOWN;
+ IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
+
+ if_counters_alloc(ifp);
+ if_attach(ifp);
+ if_alloc_sadl(ifp);
+
+#if NBPFILTER > 0
+ bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, ETHER_HDR_LEN);
+#endif
+
+ ifp->if_llprio = IFQ_MAXPRIO;
+
+ return (0);
+}
+
+static int
+tpmr_clone_destroy(struct ifnet *ifp)
+{
+ struct tpmr_softc *sc = ifp->if_softc;
+ unsigned int i;
+
+ NET_LOCK();
+ sc->sc_dead = 1;
+
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ tpmr_down(sc);
+ NET_UNLOCK();
+
+ if_detach(ifp);
+
+ for (i = 0; i < nitems(sc->sc_ports); i++) {
+ struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
+ if (p == NULL)
+ continue;
+ tpmr_p_dtor(sc, p, "destroy");
+ }
+
+ free(sc, M_DEVBUF, sizeof(*sc));
+
+ return (0);
+}
+
+static int
+tpmr_input(struct ifnet *ifp0, struct mbuf *m, void *cookie)
+{
+ struct tpmr_port *p = cookie;
+ struct tpmr_softc *sc = p->p_tpmr;
+ struct ifnet *ifp = &sc->sc_if;
+ struct tpmr_port *pn;
+ int len;
+#if NBPFILTER > 0
+ caddr_t if_bpf;
+#endif
+
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ goto drop;
+
+#if NVLAN > 0
+ /*
+ * If the underlying interface removed the VLAN header itself,
+ * add it back.
+ */
+ if (ISSET(m->m_flags, M_VLANTAG)) {
+ m = vlan_inject(m, ETHERTYPE_VLAN, m->m_pkthdr.ether_vtag);
+ if (m == NULL) {
+ counters_inc(ifp->if_counters, ifc_ierrors);
+ goto drop;
+ }
+ }
+#endif
+
+ len = m->m_pkthdr.len;
+ counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes, len);
+
+#if NBPFILTER > 0
+ if_bpf = ifp->if_bpf;
+ if (if_bpf) {
+ if (bpf_mtap(if_bpf, m, 0))
+ goto drop;
+ }
+#endif
+
+ smr_read_enter();
+ pn = SMR_PTR_GET(&sc->sc_ports[!p->p_slot]);
+ if (pn == NULL)
+ m_freem(m);
+ else {
+ struct ifnet *ifpn = pn->p_ifp0;
+ if ((*ifpn->if_enqueue)(ifpn, m))
+ counters_inc(ifp->if_counters, ifc_oerrors);
+ else {
+ counters_pkt(ifp->if_counters,
+ ifc_opackets, ifc_obytes, len);
+ }
+ }
+ smr_read_leave();
+
+ return (1);
+
+drop:
+ m_freem(m);
+ return (1);
+}
+
+static int
+tpmr_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
+ struct rtentry *rt)
+{
+ m_freem(m);
+ return (ENODEV);
+}
+
+static int
+tpmr_enqueue(struct ifnet *ifp, struct mbuf *m)
+{
+ m_freem(m);
+ return (ENODEV);
+}
+
+static void
+tpmr_start(struct ifqueue *ifq)
+{
+ ifq_purge(ifq);
+}
+
+static int
+tpmr_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct tpmr_softc *sc = ifp->if_softc;
+ int error = 0;
+
+ if (sc->sc_dead)
+ return (ENXIO);
+
+ switch (cmd) {
+ case SIOCSIFADDR:
+ error = EAFNOSUPPORT;
+ break;
+
+ case SIOCSIFFLAGS:
+ if (ISSET(ifp->if_flags, IFF_UP)) {
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ error = tpmr_up(sc);
+ } else {
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ error = tpmr_down(sc);
+ }
+ break;
+
+ case SIOCSTRUNK:
+ error = suser(curproc);
+ if (error != 0)
+ break;
+
+ if (((struct trunk_reqall *)data)->ra_proto !=
+ TRUNK_PROTO_LACP) {
+ error = EPROTONOSUPPORT;
+ break;
+ }
+
+ /* nop */
+ break;
+ case SIOCGTRUNK:
+ error = tpmr_get_trunk(sc, (struct trunk_reqall *)data);
+ break;
+
+ case SIOCSTRUNKOPTS:
+ error = suser(curproc);
+ if (error != 0)
+ break;
+
+ error = EPROTONOSUPPORT;
+ break;
+
+ case SIOCGTRUNKOPTS:
+ break;
+
+ case SIOCGTRUNKPORT:
+ error = tpmr_get_port(sc, (struct trunk_reqport *)data);
+ break;
+ case SIOCSTRUNKPORT:
+ error = suser(curproc);
+ if (error != 0)
+ break;
+
+ error = tpmr_add_port(sc, (struct trunk_reqport *)data);
+ break;
+ case SIOCSTRUNKDELPORT:
+ error = suser(curproc);
+ if (error != 0)
+ break;
+
+ error = tpmr_del_port(sc, (struct trunk_reqport *)data);
+ break;
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ if (error == ENETRESET)
+ error = tpmr_iff(sc);
+
+ return (error);
+}
+
+static int
+tpmr_get_trunk(struct tpmr_softc *sc, struct trunk_reqall *ra)
+{
+ struct ifnet *ifp = &sc->sc_if;
+ size_t size = ra->ra_size;
+ caddr_t ubuf = (caddr_t)ra->ra_port;
+ int error = 0;
+ int i;
+
+ ra->ra_proto = TPMR_TRUNK_PROTO;
+ memset(&ra->ra_psc, 0, sizeof(ra->ra_psc));
+
+ ra->ra_ports = sc->sc_nports;
+ for (i = 0; i < nitems(sc->sc_ports); i++) {
+ struct trunk_reqport rp;
+ struct ifnet *ifp0;
+ struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
+ if (p == NULL)
+ continue;
+
+ if (size < sizeof(rp))
+ break;
+
+ ifp0 = p->p_ifp0;
+
+ CTASSERT(sizeof(rp.rp_ifname) == sizeof(ifp->if_xname));
+ CTASSERT(sizeof(rp.rp_portname) == sizeof(ifp0->if_xname));
+
+ memset(&rp, 0, sizeof(rp));
+ memcpy(rp.rp_ifname, ifp->if_xname, sizeof(rp.rp_ifname));
+ memcpy(rp.rp_portname, ifp0->if_xname, sizeof(rp.rp_portname));
+
+ if (!ISSET(ifp0->if_flags, IFF_RUNNING))
+ SET(rp.rp_flags, TRUNK_PORT_DISABLED);
+ else {
+ SET(rp.rp_flags, TRUNK_PORT_ACTIVE);
+ if (LINK_STATE_IS_UP(ifp0->if_link_state)) {
+ SET(rp.rp_flags, TRUNK_PORT_COLLECTING |
+ TRUNK_PORT_DISTRIBUTING);
+ }
+ }
+
+ error = copyout(&rp, ubuf, sizeof(rp));
+ if (error != 0)
+ break;
+
+ ubuf += sizeof(rp);
+ size -= sizeof(rp);
+ }
+
+ return (error);
+}
+
+static int
+tpmr_add_port(struct tpmr_softc *sc, const struct trunk_reqport *rp)
+{
+ struct ifnet *ifp = &sc->sc_if;
+ struct ifnet *ifp0;
+ struct arpcom *ac0;
+ struct tpmr_port **pp;
+ struct tpmr_port *p;
+ int i;
+ int error;
+
+ NET_ASSERT_LOCKED();
+ if (sc->sc_nports >= nitems(sc->sc_ports))
+ return (ENOSPC);
+
+ ifp0 = ifunit(rp->rp_portname);
+ if (ifp0 == NULL)
+ return (EINVAL);
+
+ if (ifp0->if_type != IFT_ETHER)
+ return (EPROTONOSUPPORT);
+
+ ac0 = (struct arpcom *)ifp0;
+ if (ac0->ac_trunkport != NULL)
+ return (EBUSY);
+
+ /* let's try */
+
+ ifp0 = if_get(ifp0->if_index); /* get an actual reference */
+ if (ifp0 == NULL) {
+ /* XXX this should never happen */
+ return (EINVAL);
+ }
+
+ p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
+ if (p == NULL) {
+ error = ENOMEM;
+ goto put;
+ }
+
+ p->p_ifp0 = ifp0;
+ p->p_tpmr = sc;
+
+ p->p_ioctl = ifp0->if_ioctl;
+ p->p_output = ifp0->if_output;
+
+ error = ifpromisc(ifp0, 1);
+ if (error != 0)
+ goto free;
+
+ p->p_lcookie = hook_establish(ifp0->if_linkstatehooks, 1,
+ tpmr_p_linkch, p);
+ p->p_dcookie = hook_establish(ifp0->if_detachhooks, 0,
+ tpmr_p_detach, p);
+
+ /* commit */
+ DPRINTF(sc, "%s %s trunkport: creating port\n",
+ ifp->if_xname, ifp0->if_xname);
+
+ for (i = 0; i < nitems(sc->sc_ports); i++) {
+ pp = &sc->sc_ports[i];
+ if (SMR_PTR_GET_LOCKED(pp) == NULL)
+ break;
+ }
+ sc->sc_nports++;
+
+ p->p_slot = i;
+
+ ac0->ac_trunkport = p;
+ /* make sure p is visible before handlers can run */
+ membar_producer();
+ ifp0->if_ioctl = tpmr_p_ioctl;
+ ifp0->if_output = tpmr_p_output;
+ if_ih_insert(ifp0, tpmr_input, p);
+
+ SMR_PTR_SET_LOCKED(pp, p);
+
+ tpmr_p_linkch(p);
+
+ return (0);
+
+free:
+ free(p, M_DEVBUF, sizeof(*p));
+put:
+ if_put(ifp0);
+ return (error);
+}
+
+static struct tpmr_port *
+tpmr_trunkport(struct tpmr_softc *sc, const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(sc->sc_ports); i++) {
+ struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
+ if (p == NULL)
+ continue;
+
+ if (strcmp(p->p_ifp0->if_xname, name) == 0)
+ return (p);
+ }
+
+ return (NULL);
+}
+
+static int
+tpmr_get_port(struct tpmr_softc *sc, struct trunk_reqport *rp)
+{
+ struct tpmr_port *p;
+
+ NET_ASSERT_LOCKED();
+ p = tpmr_trunkport(sc, rp->rp_portname);
+ if (p == NULL)
+ return (EINVAL);
+
+ /* XXX */
+
+ return (0);
+}
+
+static int
+tpmr_del_port(struct tpmr_softc *sc, const struct trunk_reqport *rp)
+{
+ struct tpmr_port *p;
+
+ NET_ASSERT_LOCKED();
+ p = tpmr_trunkport(sc, rp->rp_portname);
+ if (p == NULL)
+ return (EINVAL);
+
+ tpmr_p_dtor(sc, p, "del");
+
+ return (0);
+}
+
+static int
+tpmr_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
+{
+ struct arpcom *ac0 = (struct arpcom *)ifp0;
+ struct tpmr_port *p = ac0->ac_trunkport;
+ int error = 0;
+
+ switch (cmd) {
+ case SIOCSIFADDR:
+ error = EBUSY;
+ break;
+
+ case SIOCGTRUNKPORT: {
+ struct trunk_reqport *rp = (struct trunk_reqport *)data;
+ struct tpmr_softc *sc = p->p_tpmr;
+ struct ifnet *ifp = &sc->sc_if;
+
+ if (strncmp(rp->rp_ifname, rp->rp_portname,
+ sizeof(rp->rp_ifname)) != 0)
+ return (EINVAL);
+
+ CTASSERT(sizeof(rp->rp_ifname) == sizeof(ifp->if_xname));
+ memcpy(rp->rp_ifname, ifp->if_xname, sizeof(rp->rp_ifname));
+ break;
+ }
+
+ default:
+ error = (*p->p_ioctl)(ifp0, cmd, data);
+ break;
+ }
+
+ return (error);
+}
+
+static int
+tpmr_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
+ struct rtentry *rt)
+{
+ struct arpcom *ac0 = (struct arpcom *)ifp0;
+ struct tpmr_port *p = ac0->ac_trunkport;
+
+ /* restrict transmission to bpf only */
+ if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
+ m_freem(m);
+ return (EBUSY);
+ }
+
+ return ((*p->p_output)(ifp0, m, dst, rt));
+}
+
+static void
+tpmr_p_dtor(struct tpmr_softc *sc, struct tpmr_port *p, const char *op)
+{
+ struct ifnet *ifp = &sc->sc_if;
+ struct ifnet *ifp0 = p->p_ifp0;
+ struct arpcom *ac0 = (struct arpcom *)ifp0;
+
+ DPRINTF(sc, "%s %s: destroying port\n",
+ ifp->if_xname, ifp0->if_xname);
+
+ if_ih_remove(ifp0, tpmr_input, p);
+
+ ifp0->if_ioctl = p->p_ioctl;
+ ifp0->if_output = p->p_output;
+ membar_producer();
+
+ ac0->ac_trunkport = NULL;
+
+ sc->sc_nports--;
+ SMR_PTR_SET_LOCKED(&sc->sc_ports[p->p_slot], NULL);
+
+ if (ifpromisc(ifp0, 0) != 0) {
+ log(LOG_WARNING, "%s %s: unable to disable promisc",
+ ifp->if_xname, ifp0->if_xname);
+ }
+
+ hook_disestablish(ifp0->if_detachhooks, p->p_dcookie);
+ hook_disestablish(ifp0->if_linkstatehooks, p->p_lcookie);
+
+ smr_barrier();
+
+ if_put(ifp0);
+ free(p, M_DEVBUF, sizeof(*p));
+
+ if (ifp->if_link_state != LINK_STATE_DOWN) {
+ ifp->if_link_state = LINK_STATE_DOWN;
+ if_link_state_change(ifp);
+ }
+}
+
+static void
+tpmr_p_detach(void *arg)
+{
+ struct tpmr_port *p = arg;
+ struct tpmr_softc *sc = p->p_tpmr;
+
+ tpmr_p_dtor(sc, p, "detach");
+
+ NET_ASSERT_LOCKED();
+}
+
+static int
+tpmr_p_active(struct tpmr_port *p)
+{
+ struct ifnet *ifp0 = p->p_ifp0;
+
+ return (ISSET(ifp0->if_flags, IFF_RUNNING) &&
+ LINK_STATE_IS_UP(ifp0->if_link_state));
+}
+
+static void
+tpmr_p_linkch(void *arg)
+{
+ struct tpmr_port *p = arg;
+ struct tpmr_softc *sc = p->p_tpmr;
+ struct ifnet *ifp = &sc->sc_if;
+ struct tpmr_port *np;
+ u_char link_state = LINK_STATE_FULL_DUPLEX;
+
+ NET_ASSERT_LOCKED();
+
+ if (!tpmr_p_active(p))
+ link_state = LINK_STATE_DOWN;
+
+ np = SMR_PTR_GET_LOCKED(&sc->sc_ports[!p->p_slot]);
+ if (np == NULL || !tpmr_p_active(np))
+ link_state = LINK_STATE_DOWN;
+
+ if (ifp->if_link_state != link_state) {
+ ifp->if_link_state = link_state;
+ if_link_state_change(ifp);
+ }
+}
+
+static int
+tpmr_up(struct tpmr_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_if;
+
+ NET_ASSERT_LOCKED();
+ SET(ifp->if_flags, IFF_RUNNING);
+
+ return (0);
+}
+
+static int
+tpmr_iff(struct tpmr_softc *sc)
+{
+ return (0);
+}
+
+static int
+tpmr_down(struct tpmr_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_if;
+
+ NET_ASSERT_LOCKED();
+ CLR(ifp->if_flags, IFF_RUNNING);
+
+ return (0);
+}