summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorDavid Gwynne <dlg@cvs.openbsd.org>2019-08-01 03:05:47 +0000
committerDavid Gwynne <dlg@cvs.openbsd.org>2019-08-01 03:05:47 +0000
commite50d08a46d6b270e3377d9b7ca13f47acb151466 (patch)
treee293424017096f42b85ad47b8c73c74a600bb92c /sys
parentc0236ba652d6f353569f935cf9357035e027876d (diff)
add tpmr(4), a quick and dirty 802.1Q Two-Port MAC Relay implementation
a TPMR is a simplified brigde (as supported by bridge(4)). it only supports two ports, and unconditionally forwards frames between them. this is unlike a real bridge which can support an arbitrary number of ports and implements a learning algorithm. i needed this to tunnel LACP between switches in a couple of data centers separated by an IP network. because bridge(4) implements an actual 802.1Q bridge, it eats packets that are supposed to be sent between bridges, such as spanning tree and LACP. TPMR according to the spec does a lot less of this, and is in fact documented in the spec as being able to support transport of LACP frames. tpmr(4) is actually a lot dumber and current does no filtering (except what you can do with BPF). because the forwarding path in tpmr(4) is so short and simple, it is relatively fast and can be used to isolate and help improve the relative performance of some parts of the system. i also have plans to use this for monitoring traffic without processing it. tpmr(4) implements the trunk(4) ioctls for managing configuration. the ifconfig output for trunk interfaces is a bit shorter and needs a lot less stuff faked to be useful. inside the kernel it appears as an IFT_BRIDGE interface (like bridge(4)). it generally just drops stuff unless it's between the ports it's managing. this has been in production at my work for a few days between some physical nics and etherip(4), and so far it has been really solid. hrvoje popovski has kicked the tyres too, but more from a performance point of view. ok claudio@ deraadt@
Diffstat (limited to 'sys')
-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);
+}