a Two-Port MAC Relay is basically a cut down bridge(4). it only supports
two ports, and unconditionally relays packets between those ports
instead of doing learning or anything like that.

i've been trying to get a redundant pair of bridges set up between two
datacenters here to help me while i migrate between them. so far all my
efforts to make it redundant have mostly worked, until they introduced
loops in the layer 2 topology, which generates a broadcast storm, which
basically takes the net down for a few minutes at a time. it's feels
very betraying.

my frustration is that switches plugged together have mechanisms to
prevent loops like that, more specifically they use spanning tree or
lacp to make appropriate use of redundant links. i got to a point where
i just wanted the switches to talk to each other and do their own thing
to negotiate use of the redundant links.

unfortunately the only way to get ethernet packets off a physical
wire and onto a tunnel over an ip network is bridge(4), and bridge(4)
tries to be a compliant switch from a standards point of view. this
means it intercepts packets that are meant to be processed by bridges,
because it is a bridge. these types of packets include spanning tree and
lacp, which means i couldnt get the physical switches at each site to
talk to each other. sadface.

so to solve my problem i hacked up a small driver that did less than
bridge(4). however, it turns out that what i hacked up is an actual
thing that already exists as something done in the real world. IEEE
802.1Q describes TPMR, which is defined as intercepting far less
than a real bridge does. one of the appendices specifically describes
lacp going through one, which is exactly what i wanted. cisco does
something like this with their layer 2 cross-connects (search for cisco
xconnect for examples), juniper has l2circuits, and so on.

the way i'm using this is like below. i have a pair of bridges in each
datacenter, so 4 boxes in total. they peer directly with the ip network
that sits between the datacenter. each box has a 4 physical network
ports. 2 of those ports are configured with aggr(4) and talk IP into the
core network. the other two ports are connected to the switches at
each site for use with tpmr. there's 2 etherip interfaces configured on
each physical box, each of which is connected to the tpmr.

all that together looks a bit like the following:

 +-+ +--------------------------+      +---------------------------+ +-+
 |d|-|ix2 <-> tpmr0 <-> etherip0|------|etherip0 <-> tpmr0 <-> ixl0|-|d|
 |c| |                          |      |                           | |c|
 |0|-|ix3 <-> tpmr1 <-> etherip1|-    -|etherip1 <-> tpmr1 <-> ixl1|-|1|
 ||| +--------------------------+ \  / +---------------------------+ |||
 |s|         dc0-bridge0           \/          dc1-bridge0           |s|
 |w|                               /\                                |w|
 |i| +--------------------------+ /  \ +---------------------------+ |i|
 |t|-|ix2 <-> tpmr0 <-> etherip0|-    -|etherip0 <-> tpmr0 <-> ixl0|-|t|
 |c| |                          |      |                           | |c|
 |h|-|ix3 <-> tpmr1 <-> etherip1|------|etherip1 <-> tpmr1 <-> ixl1|-|h|
 +-+ +--------------------------+      +---------------------------+ +-+
             dc0-bridge1                       dc1-bridge1

each switch has a 4 port port-channel (lacp aggregation) set up. because
each physical interface on the bridges are tied to a single tunnel, the
packets effectively traverse a point-to-point link, ie, a really
complicated wire. because lacp makes it from each point to the other
point, the switches make sure only active lacp ports are used, which
avoids layer 2 loops. lacp also means i get to use all the links when
theyre available.

with the topology above i can lose a bridge at each site and should
still have a working link to the other side, so i get my redundancy. the
use of the extra links with lacp is a bonus. at this point i would have
been happy for spanning tree to shut links down.

anyway, here's the code.

it was originally called xcon(4) since it provides a software
cross-connect, but i changed my mind after looking at 802.1Q. it might
be unfair to refer to 802.1Q because tpmr(4) does none of the filtering
that the spec says it should. i just needed it to work though.

the guts of it is tpmr_input(). it basically gets the rxed packet from
one port and enqueues it for tranmission immediately on the other port.
it does run bpf though, and supports filtering on bpf, which has been
handy for us when we needed to test taking bpdus off the wire for a bit.

because it does such a small amount of work, it is relatively fast.
hrvoje popovski has given it a quick spin and seen the following
results on a fast box with a pair of ix(4) interfaces:

plain ip forwarding: 1.5Mpps
bridge(4) under load from 14Mpps: 500Kpps
bridge(4) under load from 1Mpps: 800Kpps
tpmr(4): 1.75Mpps

1.75Mpps was lower than I was expecting, but it turns out he was hitting
limits in other parts of the system. with some tuning we got it up to
2.25Mpps. the softnet taskq was only at about 66% cpu time, but we
couldnt see any other obvious places that we were dropping load.

on a slower box that can do IP forwarding at 1Mpps, tpmr(4) can do
1.6Mpps. it's worth noting that the boxes were extremely responsive (ie,
ssh feels fine) when tpmr is under load, which is not the case when ip
forwarding or bridge are being hammered.

my point is that it might be useful having tpmr(4) just to be able to
test network driver performance improvements independently of the stack.
im probably going to be using it to monitor links as a "bump in the
wire" too.

lastly regarding the code. i made this use the trunk(4) ioctls instead
of the bridge ones, mostly because i had to fake less stuff to make
ifconfig output look ok.

ifconfig output looks like this:

xdlg@dc3-bridge1:~$ ifconfig tpmr
     
tpmr0: flags=51<UP,POINTOPOINT,RUNNING>
        description: xconnect
        index 15 priority 0 llprio 7
        trunk: trunkproto none
                ix2 port active,collecting,distributing
                etherip10 port active,collecting,distributing
        groups: tpmr
        status: active

anyway. thoughts? ok?

Index: net/if_tpmr.c
===================================================================
RCS file: net/if_tpmr.c
diff -N net/if_tpmr.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ net/if_tpmr.c       29 Jul 2019 09:44:26 -0000
@@ -0,0 +1,717 @@
+/*     $OpenBSD$ */
+
+/*
+ * 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 <d...@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);
+}
Index: conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/conf/GENERIC,v
retrieving revision 1.263
diff -u -p -r1.263 GENERIC
--- conf/GENERIC        8 Jul 2019 01:16:02 -0000       1.263
+++ conf/GENERIC        29 Jul 2019 09:44:26 -0000
@@ -102,6 +102,7 @@ pseudo-device       pppx            # PPP multiplexer
 pseudo-device  sppp    1       # Sync PPP/HDLC
 pseudo-device  trunk           # Trunking support
 pseudo-device  aggr            # 802.1AX Link Aggregation
+pseudo-device  tpmr            # 802.1Q Two-Port MAC Relay (TPMR)
 pseudo-device  tun             # network tunneling over tty (tun & tap)
 pseudo-device  vether          # Virtual ethernet
 pseudo-device  vxlan           # Virtual extensible LAN
Index: conf/files
===================================================================
RCS file: /cvs/src/sys/conf/files,v
retrieving revision 1.672
diff -u -p -r1.672 files
--- conf/files  5 Jul 2019 01:37:13 -0000       1.672
+++ conf/files  29 Jul 2019 09:44:26 -0000
@@ -563,6 +563,7 @@ pseudo-device mobileip: ifnet
 pseudo-device crypto: ifnet
 pseudo-device trunk: ifnet, ether, ifmedia
 pseudo-device aggr: ifnet, ether, ifmedia
+pseudo-device tpmr: ifnet, ether, ifmedia
 pseudo-device mpe: ifnet, mpls
 pseudo-device mpw: ifnet, mpls, ether
 pseudo-device mpip: ifnet, mpls
@@ -818,6 +819,7 @@ file net/if_mobileip.c                      mobileip        
        needs
 file net/if_trunk.c                    trunk                   needs-count
 file net/trunklacp.c                   trunk
 file net/if_aggr.c                     aggr
+file net/if_tpmr.c                     tpmr
 file net/if_mpe.c                      mpe                     needs-count
 file net/if_mpw.c                      mpw                     needs-count
 file net/if_mpip.c                     mpip


Reply via email to