Index | Thread | Search

From:
Alexander Bluhm <bluhm@openbsd.org>
Subject:
Re: move aggr/trunk input processing into ether_input
To:
David Gwynne <david@gwynne.id.au>
Cc:
tech@openbsd.org
Date:
Tue, 25 Nov 2025 21:05:17 +0100

Download raw body.

Thread
On Sun, Nov 23, 2025 at 03:55:05PM +1000, David Gwynne wrote:
> currently aggr and trunk (and my switch chip drivers) replace the
> if_input handler (which is ether_input) on interfaces they take over.
> 
> this changes it so these drivers operate like the bridge drivers where
> an "ether_port" struct is given to arpcom that ether_input will look at
> and allow input packets to be filtered with.
> 
> i want this for two reasons. firstly, the old "swap if_input" semantic
> assumes that if_input is run while holding NET_LOCK in some way, and i
> want the freedom to call it without NET_LOCK in the future. secondly, it
> makes it hard to make an ethernet driver that has some extra encap you
> have to remove before calling ether_input. such a driver will mess up if
> you try to use aggr or trunk with them and their own if_input handling
> has been swapped with aggr_input or trunk_input.
> 
> ive tested aggr and veb with this change. anyone want to test the trunk
> changes before i put this in?

I have automated trunk tests.  They pass.
It is trunk only, no veb, vlan or carp involved.

bluhm

> Index: net/if_aggr.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_aggr.c,v
> diff -u -p -r1.50 if_aggr.c
> --- net/if_aggr.c	7 Jul 2025 02:28:50 -0000	1.50
> +++ net/if_aggr.c	22 Nov 2025 09:28:42 -0000
> @@ -346,12 +346,13 @@ struct aggr_port {
>  	struct ifnet		*p_ifp0;
>  	struct kstat		*p_kstat;
>  	struct mutex		 p_mtx;
> +	struct ether_port	 p_ether_port;
> +	struct refcnt		 p_refs;
>  
>  	uint8_t			 p_lladdr[ETHER_ADDR_LEN];
>  	uint32_t		 p_mtu;
>  
>  	int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
> -	void (*p_input)(struct ifnet *, struct mbuf *, struct netstack *);
>  	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
>  	    struct rtentry *);
>  
> @@ -743,75 +744,93 @@ aggr_start(struct ifqueue *ifq)
>  	smr_read_leave();
>  }
>  
> +static void
> +aggr_port_take(void *port)
> +{
> +	struct aggr_port *p = port;
> +	refcnt_take(&p->p_refs);
> +}
> + 
> +static void
> +aggr_port_rele(void *port)
> +{
> +	struct aggr_port *p = port;
> +	refcnt_rele_wake(&p->p_refs);
> +}
> +
> +static inline int
> +aggr_is_lldp(struct mbuf *m, uint64_t dst)
> +{
> +	struct ether_header *eh = mtod(m, struct ether_header *);
> +
> +	if (eh->ether_type == htons(ETHERTYPE_LLDP) &&
> +	    ETH64_IS_8021_RSVD(dst)) {
> +		/* look at the last nibble of the 802.1 reserved address */
> +		switch (dst & 0xf) {
> +		case 0x0: /* Nearest Customer Bridge */
> +		case 0x3: /* Non-TPMR Bridge */
> +		case 0xe: /* Nearest Bridge */
> +			return (1);
> +		default:
> +			break;
> +		}
> +	}
> +
> +	return (0);
> +}
> +
>  static inline struct mbuf *
> -aggr_input_control(struct aggr_port *p, struct mbuf *m, struct netstack *ns)
> +aggr_input_control(struct aggr_port *p, struct mbuf *m, uint64_t dst)
>  {
>  	struct ether_header *eh;
>  	int hlen = sizeof(*eh);
> -	uint16_t etype;
> -	uint64_t dst;
> +	unsigned int rx_proto = AGGR_PROTO_RX_LACP;
> +	struct ether_slowproto_hdr *sph;
> +	int drop = 0;
>  
>  	if (ISSET(m->m_flags, M_VLANTAG))
>  		return (m);
>  
>  	eh = mtod(m, struct ether_header *);
> -	etype = eh->ether_type;
> -	dst = ether_addr_to_e64((struct ether_addr *)eh->ether_dhost);
> -
> -	if (__predict_false(etype == htons(ETHERTYPE_SLOW) &&
> -	    dst == LACP_ADDR_SLOW_E64)) {
> -		unsigned int rx_proto = AGGR_PROTO_RX_LACP;
> -		struct ether_slowproto_hdr *sph;
> -		int drop = 0;
> -
> -		hlen += sizeof(*sph);
> -		if (m->m_len < hlen) {
> -			m = m_pullup(m, hlen);
> -			if (m == NULL) {
> -				/* short++ */
> -				return (NULL);
> -			}
> -			eh = mtod(m, struct ether_header *);
> -		}
> -
> -		sph = (struct ether_slowproto_hdr *)(eh + 1);
> -		switch (sph->sph_subtype) {
> -		case SLOWPROTOCOLS_SUBTYPE_LACP_MARKER:
> -			rx_proto = AGGR_PROTO_RX_MARKER;
> -			/* FALLTHROUGH */
> -		case SLOWPROTOCOLS_SUBTYPE_LACP:
> -			mtx_enter(&p->p_mtx);
> -			p->p_proto_counts[rx_proto].c_pkts++;
> -			p->p_proto_counts[rx_proto].c_bytes += m->m_pkthdr.len;
> -
> -			if (ml_len(&p->p_rxm_ml) < AGGR_MAX_SLOW_PKTS)
> -				ml_enqueue(&p->p_rxm_ml, m);
> -			else {
> -				p->p_rx_drops++;
> -				drop = 1;
> -			}
> -			mtx_leave(&p->p_mtx);
> +	if (__predict_true(eh->ether_type != htons(ETHERTYPE_SLOW) ||
> +	    dst != LACP_ADDR_SLOW_E64))
> +		return (m);
>  
> -			if (drop)
> -				goto drop;
> -			else
> -				task_add(systq, &p->p_rxm_task);
> +	hlen += sizeof(*sph);
> +	if (m->m_len < hlen) {
> +		m = m_pullup(m, hlen);
> +		if (m == NULL) {
> +			/* short++ */
>  			return (NULL);
> -		default:
> -			break;
>  		}
> -	} else if (__predict_false(etype == htons(ETHERTYPE_LLDP) &&
> -	    ETH64_IS_8021_RSVD(dst))) {
> -		/* look at the last nibble of the 802.1 reserved address */
> -		switch (dst & 0xf) {
> -		case 0x0: /* Nearest Customer Bridge */
> -		case 0x3: /* Non-TPMR Bridge */
> -		case 0xe: /* Nearest Bridge */
> -			p->p_input(p->p_ifp0, m, ns);
> -			return (NULL);
> -		default:
> -			break;
> +		eh = mtod(m, struct ether_header *);
> +	}
> +
> +	sph = (struct ether_slowproto_hdr *)(eh + 1);
> +	switch (sph->sph_subtype) {
> +	case SLOWPROTOCOLS_SUBTYPE_LACP_MARKER:
> +		rx_proto = AGGR_PROTO_RX_MARKER;
> +		/* FALLTHROUGH */
> +	case SLOWPROTOCOLS_SUBTYPE_LACP:
> +		mtx_enter(&p->p_mtx);
> +		p->p_proto_counts[rx_proto].c_pkts++;
> +		p->p_proto_counts[rx_proto].c_bytes += m->m_pkthdr.len;
> +
> +		if (ml_len(&p->p_rxm_ml) < AGGR_MAX_SLOW_PKTS)
> +			ml_enqueue(&p->p_rxm_ml, m);
> +		else {
> +			p->p_rx_drops++;
> +			drop = 1;
>  		}
> +		mtx_leave(&p->p_mtx);
> +
> +		if (drop)
> +			goto drop;
> +		else
> +			task_add(systq, &p->p_rxm_task);
> +		return (NULL);
> +	default:
> +		break;
>  	}
>  
>  	return (m);
> @@ -821,20 +840,23 @@ drop:
>  	return (NULL);
>  }
>  
> -static void
> -aggr_input(struct ifnet *ifp0, struct mbuf *m, struct netstack *ns)
> +static struct mbuf *
> +aggr_port_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *port,
> +    struct netstack *ns)
>  {
> -	struct arpcom *ac0 = (struct arpcom *)ifp0;
> -	struct aggr_port *p = ac0->ac_trunkport;
> +	struct aggr_port *p = port;
>  	struct aggr_softc *sc = p->p_aggr;
>  	struct ifnet *ifp = &sc->sc_if;
>  
> +	if (__predict_false(aggr_is_lldp(m, dst)))
> +		return (m);
> +
>  	if (!ISSET(ifp->if_flags, IFF_RUNNING))
>  		goto drop;
>  
> -	m = aggr_input_control(p, m, ns);
> +	m = aggr_input_control(p, m, dst);
>  	if (m == NULL)
> -		return;
> +		return (NULL);
>  
>  	if (__predict_false(!p->p_collecting))
>  		goto drop;
> @@ -844,10 +866,11 @@ aggr_input(struct ifnet *ifp0, struct mb
>  
>  	if_vinput(ifp, m, ns);
>  
> -	return;
> +	return (NULL);
>  
>  drop:
>  	m_freem(m);
> +	return (NULL);
>  }
>  
>  static int
> @@ -1168,7 +1191,7 @@ aggr_add_port(struct aggr_softc *sc, con
>  	}
>  
>  	ac0 = (struct arpcom *)ifp0;
> -	if (ac0->ac_trunkport != NULL) {
> +	if (SMR_PTR_GET_LOCKED(&ac0->ac_trport) != NULL) {
>  		error = EBUSY;
>  		goto put;
>  	}
> @@ -1188,11 +1211,16 @@ aggr_add_port(struct aggr_softc *sc, con
>  	p->p_aggr = sc;
>  	p->p_mtu = ifp0->if_mtu;
>  	mtx_init(&p->p_mtx, IPL_SOFTNET);
> +	refcnt_init(&p->p_refs);
> +
> +	p->p_ether_port.ep_input = aggr_port_input;
> +	p->p_ether_port.ep_port_take = aggr_port_take;
> +	p->p_ether_port.ep_port_rele = aggr_port_rele;
> +	p->p_ether_port.ep_port = p;
>  
>  	CTASSERT(sizeof(p->p_lladdr) == sizeof(ac0->ac_enaddr));
>  	memcpy(p->p_lladdr, ac0->ac_enaddr, sizeof(p->p_lladdr));
>  	p->p_ioctl = ifp0->if_ioctl;
> -	p->p_input = ifp0->if_input;
>  	p->p_output = ifp0->if_output;
>  
>  	error = aggr_group(sc, p, SIOCADDMULTI);
> @@ -1256,16 +1284,16 @@ aggr_add_port(struct aggr_softc *sc, con
>  	aggr_update_capabilities(sc);
>  
>  	/*
> -	 * use (and modification) of ifp->if_input and ac->ac_trunkport
> -	 * is protected by NET_LOCK.
> +	 * modification of ac->ac_trport is protected by NET_LOCK.
>  	 */
>  
> -	ac0->ac_trunkport = p;
> +	KASSERT(SMR_PTR_GET_LOCKED(&ac0->ac_trport) == NULL);
> +	aggr_port_take(p); /* for the SMR ptr */
> +	SMR_PTR_SET_LOCKED(&ac0->ac_trport, &p->p_ether_port);
>  
>  	/* make sure p is visible before handlers can run */
>  	membar_producer();
>  	ifp0->if_ioctl = aggr_p_ioctl;
> -	ifp0->if_input = aggr_input;
>  	ifp0->if_output = aggr_p_output;
>  
>  	aggr_mux(sc, p, LACP_MUX_E_BEGIN);
> @@ -1399,10 +1427,20 @@ static int
>  aggr_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
>  {
>  	struct arpcom *ac0 = (struct arpcom *)ifp0;
> -	struct aggr_port *p = ac0->ac_trunkport;
> +	const struct ether_port *ep = SMR_PTR_GET_LOCKED(&ac0->ac_trport);
> +	struct aggr_port *p;
>  	struct ifreq *ifr = (struct ifreq *)data;
>  	int error = 0;
>  
> +	KASSERTMSG(ep != NULL,
> +	    "%s: %s called without an ether_port set",
> +	    ifp0->if_xname, __func__);
> +	KASSERTMSG(ep->ep_input == aggr_port_input,
> +	    "%s called %s, but ep_input (%p) seems wrong",
> +	    ifp0->if_xname, __func__, ep->ep_input);
> +
> +	p = ep->ep_port;
> +
>  	switch (cmd) {
>  	case SIOCGTRUNKPORT: {
>  		struct trunk_reqport *rp = (struct trunk_reqport *)data;
> @@ -1449,8 +1487,10 @@ static int
>  aggr_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
>      struct rtentry *rt)
>  {
> +	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
> +	    struct rtentry *) = NULL;
>  	struct arpcom *ac0 = (struct arpcom *)ifp0;
> -	struct aggr_port *p = ac0->ac_trunkport;
> +	const struct ether_port *ep;
>  
>  	/* restrict transmission to bpf only */
>  	if (m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL) {
> @@ -1458,7 +1498,20 @@ aggr_p_output(struct ifnet *ifp0, struct
>  		return (EBUSY);
>  	}
>  
> -	return ((*p->p_output)(ifp0, m, dst, rt));
> +	smr_read_enter();
> +	ep = SMR_PTR_GET(&ac0->ac_trport);
> +	if (ep != NULL && ep->ep_input == aggr_port_input) {
> +		struct aggr_port *p = ep->ep_port;
> +		p_output = p->p_output; /* code doesn't go away */
> +	}
> +	smr_read_leave();
> +
> +	if (p_output == NULL) {
> +		m_freem(m);
> +		return (ENXIO);
> +	}
> +
> +	return ((*p_output)(ifp0, m, dst, rt));
>  }
>  
>  static void
> @@ -1469,6 +1522,7 @@ aggr_p_dtor(struct aggr_softc *sc, struc
>  	struct arpcom *ac0 = (struct arpcom *)ifp0;
>  	struct aggr_multiaddr *ma;
>  	enum aggr_port_selected selected;
> +	struct smr_entry smrdtor;
>  	int error;
>  
>  	DPRINTF(sc, "%s %s %s: destroying port\n",
> @@ -1486,14 +1540,19 @@ aggr_p_dtor(struct aggr_softc *sc, struc
>  	timeout_del(&p->p_wait_while_timer);
>  
>  	/*
> -	 * use (and modification) of ifp->if_input and ac->ac_trunkport
> -	 * is protected by NET_LOCK.
> +	 * modification of ac->ac_trport is protected by NET_LOCK.
>  	 */
>  
> -	ac0->ac_trunkport = NULL;
> -	ifp0->if_input = p->p_input;
> +	NET_ASSERT_LOCKED();
> +	KASSERT(SMR_PTR_GET_LOCKED(&ac0->ac_trport) == &p->p_ether_port);
> +	smr_init(&smrdtor);
> +
>  	ifp0->if_ioctl = p->p_ioctl;
>  	ifp0->if_output = p->p_output;
> +	SMR_PTR_SET_LOCKED(&ac0->ac_trport, NULL);
> +	smr_call(&smrdtor, aggr_port_rele, p);
> +
> +	refcnt_finalize(&p->p_refs, "aggrdtor");
>  
>  #if NKSTAT > 0
>  	aggr_port_kstat_detach(p);
> Index: net/if_bridge.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_bridge.c,v
> diff -u -p -r1.380 if_bridge.c
> --- net/if_bridge.c	3 Nov 2025 23:50:57 -0000	1.380
> +++ net/if_bridge.c	22 Nov 2025 09:28:42 -0000
> @@ -149,7 +149,7 @@ struct niqueue bridgeintrq = NIQUEUE_INI
>  struct if_clone bridge_cloner =
>      IF_CLONE_INITIALIZER("bridge", bridge_clone_create, bridge_clone_destroy);
>  
> -const struct ether_brport bridge_brport = {
> +const struct ether_port bridge_brport = {
>  	bridge_input,
>  	bridge_take,
>  	bridge_rele,
> Index: net/if_ethersubr.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_ethersubr.c,v
> diff -u -p -r1.304 if_ethersubr.c
> --- net/if_ethersubr.c	3 Nov 2025 23:50:57 -0000	1.304
> +++ net/if_ethersubr.c	22 Nov 2025 09:28:42 -0000
> @@ -375,20 +375,31 @@ ether_output(struct ifnet *ifp, struct m
>  	return (if_enqueue(ifp, m));
>  }
>  
> +static struct mbuf *
> +ether_port_input(struct ifnet *ifp, struct mbuf *m, uint64_t dst,
> +    const struct ether_port **epp, struct netstack *ns)
> +{
> +	const struct ether_port *ep;
> +
> +	smr_read_enter();
> +	ep = SMR_PTR_GET(epp);
> +	if (ep != NULL)
> +		ep->ep_port_take(ep->ep_port);
> +	smr_read_leave();
> +	if (ep != NULL) {
> +		m = (*ep->ep_input)(ifp, m, dst, ep->ep_port, ns);
> +		ep->ep_port_rele(ep->ep_port);
> +	}
> +
> +	return (m);
> +}
> +
>  /*
>   * Process a received Ethernet packet.
>   *
>   * Ethernet input has several "phases" of filtering packets to
>   * support virtual/pseudo interfaces before actual layer 3 protocol
>   * handling.
> - *
> - * First phase:
> - *
> - * The first phase supports drivers that aggregate multiple Ethernet
> - * ports into a single logical interface, ie, aggr(4) and trunk(4).
> - * These drivers intercept packets by swapping out the if_input handler
> - * on the "port" interfaces to steal the packets before they get here
> - * to ether_input().
>   */
>  void
>  ether_input(struct ifnet *ifp, struct mbuf *m, struct netstack *ns)
> @@ -396,8 +407,7 @@ ether_input(struct ifnet *ifp, struct mb
>  	struct ether_header *eh;
>  	void (*input)(struct ifnet *, struct mbuf *, struct netstack *);
>  	u_int16_t etype;
> -	struct arpcom *ac;
> -	const struct ether_brport *eb;
> +	struct arpcom *ac = (struct arpcom *)ifp;
>  	unsigned int sdelim = 0;
>  	uint64_t dst, self;
>  
> @@ -405,6 +415,20 @@ ether_input(struct ifnet *ifp, struct mb
>  	if (m->m_len < ETHER_HDR_LEN)
>  		goto dropanyway;
>  
> +	eh = mtod(m, struct ether_header *);
> +	dst = ether_addr_to_e64((struct ether_addr *)eh->ether_dhost);
> +
> +	/*
> +	 * First phase:
> +	 *
> +	 * The first phase supports drivers that aggregate multiple
> +	 * Ethernet ports into a single logical interface, ie, aggr(4)
> +	 * and trunk(4).
> +	 */
> +	m = ether_port_input(ifp, m, dst, &ac->ac_trport, ns);
> +	if (m == NULL)
> +		return;
> +
>  	/*
>  	 * Second phase: service delimited packet filtering.
>  	 *
> @@ -414,8 +438,6 @@ ether_input(struct ifnet *ifp, struct mb
>  	 * bridge can have a go at forwarding them.
>  	 */
>  
> -	eh = mtod(m, struct ether_header *);
> -	dst = ether_addr_to_e64((struct ether_addr *)eh->ether_dhost);
>  	etype = ntohs(eh->ether_type);
>  
>  	if (ISSET(m->m_flags, M_VLANTAG) ||
> @@ -438,21 +460,9 @@ ether_input(struct ifnet *ifp, struct mb
>  	 * may return it here to ether_input() to support local
>  	 * delivery to this port.
>  	 */
> -
> -	ac = (struct arpcom *)ifp;
> -
> -	smr_read_enter();
> -	eb = SMR_PTR_GET(&ac->ac_brport);
> -	if (eb != NULL)
> -		eb->eb_port_take(eb->eb_port);
> -	smr_read_leave();
> -	if (eb != NULL) {
> -		m = (*eb->eb_input)(ifp, m, dst, eb->eb_port, ns);
> -		eb->eb_port_rele(eb->eb_port);
> -		if (m == NULL) {
> -			return;
> -		}
> -	}
> +	m = ether_port_input(ifp, m, dst, &ac->ac_brport, ns);
> +	if (m == NULL)
> +		return;
>  
>  	/*
>  	 * Fourth phase: drop service delimited packets.
> @@ -606,7 +616,7 @@ ether_brport_isset(struct ifnet *ifp)
>  }
>  
>  void
> -ether_brport_set(struct ifnet *ifp, const struct ether_brport *eb)
> +ether_brport_set(struct ifnet *ifp, const struct ether_port *ep)
>  {
>  	struct arpcom *ac = (struct arpcom *)ifp;
>  
> @@ -614,7 +624,7 @@ ether_brport_set(struct ifnet *ifp, cons
>  	KASSERTMSG(SMR_PTR_GET_LOCKED(&ac->ac_brport) == NULL,
>  	    "%s setting an already set brport", ifp->if_xname);
>  
> -	SMR_PTR_SET_LOCKED(&ac->ac_brport, eb);
> +	SMR_PTR_SET_LOCKED(&ac->ac_brport, ep);
>  }
>  
>  void
> @@ -629,7 +639,7 @@ ether_brport_clr(struct ifnet *ifp)
>  	SMR_PTR_SET_LOCKED(&ac->ac_brport, NULL);
>  }
>  
> -const struct ether_brport *
> +const struct ether_port *
>  ether_brport_get(struct ifnet *ifp)
>  {
>  	struct arpcom *ac = (struct arpcom *)ifp;
> @@ -637,7 +647,7 @@ ether_brport_get(struct ifnet *ifp)
>  	return (SMR_PTR_GET(&ac->ac_brport));
>  }
>  
> -const struct ether_brport *
> +const struct ether_port *
>  ether_brport_get_locked(struct ifnet *ifp)
>  {
>  	struct arpcom *ac = (struct arpcom *)ifp;
> Index: net/if_tpmr.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_tpmr.c,v
> diff -u -p -r1.38 if_tpmr.c
> --- net/if_tpmr.c	4 Nov 2025 12:12:00 -0000	1.38
> +++ net/if_tpmr.c	22 Nov 2025 09:28:42 -0000
> @@ -81,7 +81,7 @@ struct tpmr_port {
>  
>  	int		 	 p_refcnt;
>  
> -	struct ether_brport	 p_brport;
> +	struct ether_port	 p_brport;
>  };
>  
>  struct tpmr_softc {
> @@ -545,10 +545,10 @@ tpmr_add_port(struct tpmr_softc *sc, con
>  	task_set(&p->p_dtask, tpmr_p_detach, p);
>  	if_detachhook_add(ifp0, &p->p_dtask);
>  
> -	p->p_brport.eb_input = tpmr_input;
> -	p->p_brport.eb_port_take = tpmr_p_take;
> -	p->p_brport.eb_port_rele = tpmr_p_rele;
> -	p->p_brport.eb_port = p;
> +	p->p_brport.ep_input = tpmr_input;
> +	p->p_brport.ep_port_take = tpmr_p_take;
> +	p->p_brport.ep_port_rele = tpmr_p_rele;
> +	p->p_brport.ep_port = p;
>  
>  	/* commit */
>  	DPRINTF(sc, "%s %s trunkport: creating port\n",
> @@ -661,18 +661,18 @@ done:
>  static int
>  tpmr_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
>  {
> -	const struct ether_brport *eb = ether_brport_get_locked(ifp0);
> +	const struct ether_port *ep = ether_brport_get_locked(ifp0);
>  	struct tpmr_port *p;
>  	int error = 0;
>  
> -	KASSERTMSG(eb != NULL,
> +	KASSERTMSG(ep != NULL,
>  	    "%s: %s called without an ether_brport set",
>  	    ifp0->if_xname, __func__);
> -	KASSERTMSG(eb->eb_input == tpmr_input,
> -	    "%s: %s called, but eb_input seems wrong (%p != tpmr_input())",
> -	    ifp0->if_xname, __func__, eb->eb_input);
> +	KASSERTMSG(ep->ep_input == tpmr_input,
> +	    "%s: %s called, but ep_input seems wrong (%p != tpmr_input())",
> +	    ifp0->if_xname, __func__, ep->ep_input);
>  
> -	p = eb->eb_port;
> +	p = ep->ep_port;
>  
>  	switch (cmd) {
>  	case SIOCSIFADDR:
> @@ -693,7 +693,7 @@ tpmr_p_output(struct ifnet *ifp0, struct
>  {
>  	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
>  	    struct rtentry *) = NULL;
> -	const struct ether_brport *eb;
> +	const struct ether_port *ep;
>  
>  	/* restrict transmission to bpf only */
>  	if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
> @@ -702,9 +702,9 @@ tpmr_p_output(struct ifnet *ifp0, struct
>  	}
>  
>  	smr_read_enter();
> -	eb = ether_brport_get(ifp0);
> -	if (eb != NULL && eb->eb_input == tpmr_input) {
> -		struct tpmr_port *p = eb->eb_port;
> +	ep = ether_brport_get(ifp0);
> +	if (ep != NULL && ep->ep_input == tpmr_input) {
> +		struct tpmr_port *p = ep->ep_port;
>  		p_output = p->p_output; /* code doesn't go away */
>  	}
>  	smr_read_leave();
> Index: net/if_trunk.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_trunk.c,v
> diff -u -p -r1.157 if_trunk.c
> --- net/if_trunk.c	7 Jul 2025 02:28:50 -0000	1.157
> +++ net/if_trunk.c	22 Nov 2025 09:28:42 -0000
> @@ -24,6 +24,7 @@
>  #include <sys/sockio.h>
>  #include <sys/systm.h>
>  #include <sys/task.h>
> +#include <sys/smr.h>
>  
>  #include <crypto/siphash.h>
>  
> @@ -72,7 +73,11 @@ int	 trunk_ether_delmulti(struct trunk_s
>  void	 trunk_ether_purgemulti(struct trunk_softc *);
>  int	 trunk_ether_cmdmulti(struct trunk_port *, u_long);
>  int	 trunk_ioctl_allports(struct trunk_softc *, u_long, caddr_t);
> -void	 trunk_input(struct ifnet *, struct mbuf *, struct netstack *);
> +void	 trunk_port_take(void *);
> +void	 trunk_port_rele(void *);
> +struct mbuf *
> +	 trunk_input(struct ifnet *, struct mbuf *, uint64_t, void *,
> +	     struct netstack *);
>  void	 trunk_start(struct ifnet *);
>  void	 trunk_init(struct ifnet *);
>  void	 trunk_stop(struct ifnet *);
> @@ -296,7 +301,7 @@ trunk_port_create(struct trunk_softc *tr
>  		return (EPROTONOSUPPORT);
>  
>  	ac0 = (struct arpcom *)ifp;
> -	if (ac0->ac_trunkport != NULL)
> +	if (SMR_PTR_GET_LOCKED(&ac0->ac_trport) != NULL)
>  		return (EBUSY);
>  
>  	/* Take MTU from the first member port */
> @@ -318,6 +323,12 @@ trunk_port_create(struct trunk_softc *tr
>  	if ((tp = malloc(sizeof *tp, M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL)
>  		return (ENOMEM);
>  
> +	refcnt_init(&tp->tp_refs);
> +	tp->tp_ether_port.ep_input = trunk_input;
> +	tp->tp_ether_port.ep_port_take = trunk_port_take;
> +	tp->tp_ether_port.ep_port_rele = trunk_port_rele;
> +	tp->tp_ether_port.ep_port = tp;
> +
>  	/* Check if port is a stacked trunk */
>  	SLIST_FOREACH(tr_ptr, &trunk_list, tr_entries) {
>  		if (ifp == &tr_ptr->tr_ac.ac_if) {
> @@ -377,11 +388,9 @@ trunk_port_create(struct trunk_softc *tr
>  	if (tr->tr_port_create != NULL)
>  		error = (*tr->tr_port_create)(tp);
>  
> -	/* Change input handler of the physical interface. */
> -	tp->tp_input = ifp->if_input;
> +	/* Assing input handler of the physical interface. */
>  	NET_ASSERT_LOCKED();
> -	ac0->ac_trunkport = tp;
> -	ifp->if_input = trunk_input;
> +	SMR_PTR_SET_LOCKED(&ac0->ac_trport, &tp->tp_ether_port);
>  
>  	return (error);
>  }
> @@ -413,8 +422,10 @@ trunk_port_destroy(struct trunk_port *tp
>  
>  	/* Restore previous input handler. */
>  	NET_ASSERT_LOCKED();
> -	ifp->if_input = tp->tp_input;
> -	ac0->ac_trunkport = NULL;
> +	KASSERT(SMR_PTR_GET_LOCKED(&ac0->ac_trport) == &tp->tp_ether_port);
> +	SMR_PTR_SET_LOCKED(&ac0->ac_trport, NULL);
> +	smr_barrier();
> +	refcnt_finalize(&tp->tp_refs, "trdtor");
>  
>  	/* Remove multicast addresses from this port */
>  	trunk_ether_cmdmulti(tp, SIOCDELMULTI);
> @@ -664,9 +675,12 @@ trunk_ioctl(struct ifnet *ifp, u_long cm
>  		 */
>  		NET_ASSERT_LOCKED();
>  		SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
> -			/* if_ih_remove(tp->tp_if, trunk_input, tp); */
> -			tp->tp_if->if_input = tp->tp_input;
> +			struct arpcom *ac = (struct arpcom *)tp->tp_if;
> +			SMR_PTR_SET_LOCKED(&ac->ac_trport, NULL);
>  		}
> +		SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
> +			refcnt_finalize(&tp->tp_refs, "trunkset");
> +		smr_barrier();
>  		if (tr->tr_proto != TRUNK_PROTO_NONE)
>  			error = tr->tr_detach(tr);
>  		if (error != 0)
> @@ -681,9 +695,11 @@ trunk_ioctl(struct ifnet *ifp, u_long cm
>  				if (tr->tr_proto != TRUNK_PROTO_NONE)
>  					error = trunk_protos[i].ti_attach(tr);
>  				SLIST_FOREACH(tp, &tr->tr_ports, tp_entries) {
> -					/* if_ih_insert(tp->tp_if,
> -					    trunk_input, tp); */
> -					tp->tp_if->if_input = trunk_input;
> +					struct arpcom *ac =
> +					    (struct arpcom *)tp->tp_if;
> +					refcnt_init(&tp->tp_refs);
> +					SMR_PTR_SET_LOCKED(&ac->ac_trport,
> +					    &tp->tp_ether_port);
>  				}
>  				/* Update trunk capabilities */
>  				tr->tr_capabilities = trunk_capabilities(tr);
> @@ -1144,10 +1160,24 @@ trunk_stop(struct ifnet *ifp)
>  }
>  
>  void
> -trunk_input(struct ifnet *ifp, struct mbuf *m, struct netstack *ns)
> +trunk_port_take(void *port)
>  {
> -	struct arpcom *ac0 = (struct arpcom *)ifp;
> -	struct trunk_port *tp;
> +	struct trunk_port *tp = port;
> +	refcnt_take(&tp->tp_refs);
> +}
> +
> +void
> +trunk_port_rele(void *port)
> +{
> +	struct trunk_port *tp = port;
> +	refcnt_rele_wake(&tp->tp_refs);
> +}
> +
> +struct mbuf *
> +trunk_input(struct ifnet *ifp, struct mbuf *m, uint64_t dst, void *port,
> +    struct netstack *ns)
> +{
> +	struct trunk_port *tp = port;
>  	struct trunk_softc *tr;
>  	struct ifnet *trifp = NULL;
>  	struct ether_header *eh;
> @@ -1163,7 +1193,6 @@ trunk_input(struct ifnet *ifp, struct mb
>  	if (ifp->if_type != IFT_IEEE8023ADLAG)
>  		goto bad;
>  
> -	tp = (struct trunk_port *)ac0->ac_trunkport;
>  	if ((tr = (struct trunk_softc *)tp->tp_trunk) == NULL)
>  		goto bad;
>  
> @@ -1176,7 +1205,7 @@ trunk_input(struct ifnet *ifp, struct mb
>  		 * We stop here if the packet has been consumed
>  		 * by the protocol routine.
>  		 */
> -		return;
> +		return (NULL);
>  	}
>  
>  	if ((trifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
> @@ -1190,20 +1219,19 @@ trunk_input(struct ifnet *ifp, struct mb
>  	    (ifp->if_flags & IFF_PROMISC) &&
>  	    (trifp->if_flags & IFF_PROMISC) == 0) {
>  		if (bcmp(&tr->tr_ac.ac_enaddr, eh->ether_dhost,
> -		    ETHER_ADDR_LEN)) {
> -			m_freem(m);
> -			return;
> -		}
> +		    ETHER_ADDR_LEN))
> +			goto drop;
>  	}
>  
> -
>  	if_vinput(trifp, m, ns);
> -	return;
> +	return (NULL);
>  
>   bad:
>  	if (trifp != NULL)
>  		trifp->if_ierrors++;
> +drop:
>  	m_freem(m);
> +	return (NULL);
>  }
>  
>  int
> Index: net/if_trunk.h
> ===================================================================
> RCS file: /cvs/src/sys/net/if_trunk.h,v
> diff -u -p -r1.32 if_trunk.h
> --- net/if_trunk.h	2 Mar 2025 21:28:32 -0000	1.32
> +++ net/if_trunk.h	22 Nov 2025 09:28:42 -0000
> @@ -159,6 +159,8 @@ struct trunk_softc;
>  struct trunk_port {
>  	struct ifnet			*tp_if;		/* physical interface */
>  	struct trunk_softc		*tp_trunk;	/* parent trunk */
> +	struct refcnt			tp_refs;
> +	struct ether_port		tp_ether_port;
>  	u_int8_t			tp_lladdr[ETHER_ADDR_LEN];
>  	caddr_t				tp_psc;		/* protocol data */
>  
> @@ -172,7 +174,6 @@ struct trunk_port {
>  	int	(*tp_ioctl)(struct ifnet *, u_long, caddr_t);
>  	int	(*tp_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
>  		    struct rtentry *);
> -	void	(*tp_input)(struct ifnet *, struct mbuf *, struct netstack *);
>  
>  	SLIST_ENTRY(trunk_port)		tp_entries;
>  };
> Index: net/if_veb.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_veb.c,v
> diff -u -p -r1.54 if_veb.c
> --- net/if_veb.c	22 Nov 2025 06:07:36 -0000	1.54
> +++ net/if_veb.c	22 Nov 2025 09:28:42 -0000
> @@ -117,7 +117,7 @@ struct veb_port {
>  
>  	struct veb_softc		*p_veb;
>  
> -	struct ether_brport		 p_brport;
> +	struct ether_port		 p_brport;
>  
>  	unsigned int			 p_link_state;
>  	unsigned int			 p_bif_flags;
> @@ -289,8 +289,8 @@ static void	 veb_eb_port_rele(void *, vo
>  static size_t	 veb_eb_port_ifname(void *, char *, size_t, void *);
>  static void	 veb_eb_port_sa(void *, struct sockaddr_storage *, void *);
>  
> -static void	 veb_eb_brport_take(void *);
> -static void	 veb_eb_brport_rele(void *);
> +static void	 veb_ep_brport_take(void *);
> +static void	 veb_ep_brport_rele(void *);
>  
>  static const struct etherbridge_ops veb_etherbridge_ops = {
>  	veb_eb_port_cmp,
> @@ -1660,6 +1660,10 @@ veb_add_port(struct veb_softc *sc, const
>  	p->p_ioctl = ifp0->if_ioctl;
>  	p->p_output = ifp0->if_output;
>  
> +	p->p_brport.ep_port = p;
> +	p->p_brport.ep_port_take = veb_ep_brport_take;
> +	p->p_brport.ep_port_rele = veb_ep_brport_rele;
> +
>  	if (span) {
>  		ports_ptr = &sc->sc_spans;
>  
> @@ -1668,7 +1672,7 @@ veb_add_port(struct veb_softc *sc, const
>  			goto free;
>  		}
>  
> -		p->p_brport.eb_input = veb_span_input;
> +		p->p_brport.ep_input = veb_span_input;
>  		p->p_bif_flags = IFBIF_SPAN;
>  	} else {
>  		ports_ptr = &sc->sc_ports;
> @@ -1678,13 +1682,10 @@ veb_add_port(struct veb_softc *sc, const
>  			goto free;
>  
>  		p->p_bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
> -		p->p_brport.eb_input = isvport ?
> +		p->p_brport.ep_input = isvport ?
>  		    veb_vport_input : veb_port_input;
>  	}
>  
> -	p->p_brport.eb_port_take = veb_eb_brport_take;
> -	p->p_brport.eb_port_rele = veb_eb_brport_rele;
> -
>  	om = SMR_PTR_GET_LOCKED(ports_ptr);
>  	nm = veb_ports_insert(om, p);
>  
> @@ -1699,8 +1700,6 @@ veb_add_port(struct veb_softc *sc, const
>  	task_set(&p->p_dtask, veb_p_detach, p);
>  	if_detachhook_add(ifp0, &p->p_dtask);
>  
> -	p->p_brport.eb_port = p;
> -
>  	/* commit */
>  	SMR_PTR_SET_LOCKED(ports_ptr, nm);
>  
> @@ -2970,19 +2969,19 @@ veb_del_vid_addr(struct veb_softc *sc, c
>  static int
>  veb_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
>  {
> -	const struct ether_brport *eb = ether_brport_get_locked(ifp0);
> +	const struct ether_port *ep = ether_brport_get_locked(ifp0);
>  	struct veb_port *p;
>  	int error = 0;
>  
> -	KASSERTMSG(eb != NULL,
> +	KASSERTMSG(ep != NULL,
>  	    "%s: %s called without an ether_brport set",
>  	    ifp0->if_xname, __func__);
> -	KASSERTMSG((eb->eb_input == veb_port_input) ||
> -	    (eb->eb_input == veb_span_input),
> -	    "%s called %s, but eb_input (%p) seems wrong",
> -	    ifp0->if_xname, __func__, eb->eb_input);
> +	KASSERTMSG((ep->ep_input == veb_port_input) ||
> +	    (ep->ep_input == veb_span_input),
> +	    "%s called %s, but ep_input (%p) seems wrong",
> +	    ifp0->if_xname, __func__, ep->ep_input);
>  
> -	p = eb->eb_port;
> +	p = ep->ep_port;
>  
>  	switch (cmd) {
>  	case SIOCSIFADDR:
> @@ -3003,7 +3002,7 @@ veb_p_output(struct ifnet *ifp0, struct 
>  {
>  	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
>  	    struct rtentry *) = NULL;
> -	const struct ether_brport *eb;
> +	const struct ether_port *ep;
>  
>  	/* restrict transmission to bpf only */
>  	if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
> @@ -3012,9 +3011,9 @@ veb_p_output(struct ifnet *ifp0, struct 
>  	}
>  
>  	smr_read_enter();
> -	eb = ether_brport_get(ifp0);
> -	if (eb != NULL && eb->eb_input == veb_port_input) {
> -		struct veb_port *p = eb->eb_port;
> +	ep = ether_brport_get(ifp0);
> +	if (ep != NULL && ep->ep_input == veb_port_input) {
> +		struct veb_port *p = ep->ep_port;
>  		p_output = p->p_output; /* code doesn't go away */
>  	}
>  	smr_read_leave();
> @@ -3189,13 +3188,13 @@ veb_eb_port_rele(void *arg, void *port)
>  }
>  
>  static void
> -veb_eb_brport_take(void *port)
> +veb_ep_brport_take(void *port)
>  {
>  	veb_eb_port_take(NULL, port);
>  }
>  
>  static void
> -veb_eb_brport_rele(void *port)
> +veb_ep_brport_rele(void *port)
>  {
>  	veb_eb_port_rele(NULL, port);
>  }
> @@ -3377,7 +3376,7 @@ static int
>  vport_enqueue(struct ifnet *ifp, struct mbuf *m)
>  {
>  	struct arpcom *ac;
> -	const struct ether_brport *eb;
> +	const struct ether_port *ep;
>  	int error = ENETDOWN;
>  #if NBPFILTER > 0
>  	caddr_t if_bpf;
> @@ -3399,13 +3398,13 @@ vport_enqueue(struct ifnet *ifp, struct 
>  	ac = (struct arpcom *)ifp;
>  
>  	smr_read_enter();
> -	eb = SMR_PTR_GET(&ac->ac_brport);
> -	if (eb != NULL)
> -		eb->eb_port_take(eb->eb_port);
> +	ep = SMR_PTR_GET(&ac->ac_brport);
> +	if (ep != NULL)
> +		ep->ep_port_take(ep->ep_port);
>  	smr_read_leave();
> -	if (eb != NULL) {
> +	if (ep != NULL) {
>  		struct mbuf *(*input)(struct ifnet *, struct mbuf *,
> -		    uint64_t, void *, struct netstack *) = eb->eb_input;
> +		    uint64_t, void *, struct netstack *) = ep->ep_input;
>  		struct ether_header *eh;
>  		uint64_t dst;
>  
> @@ -3423,11 +3422,11 @@ vport_enqueue(struct ifnet *ifp, struct 
>  
>  		if (input == veb_vport_input)
>  			input = veb_port_input;
> -		m = (*input)(ifp, m, dst, eb->eb_port, NULL);
> +		m = (*input)(ifp, m, dst, ep->ep_port, NULL);
>  
>  		error = 0;
>  
> -		eb->eb_port_rele(eb->eb_port);
> +		ep->ep_port_rele(ep->ep_port);
>  	}
>  
>  	m_freem(m);
> Index: netinet/if_ether.h
> ===================================================================
> RCS file: /cvs/src/sys/netinet/if_ether.h,v
> diff -u -p -r1.97 if_ether.h
> --- netinet/if_ether.h	3 Nov 2025 23:50:57 -0000	1.97
> +++ netinet/if_ether.h	22 Nov 2025 09:28:42 -0000
> @@ -131,6 +131,10 @@ struct  ether_vlan_header {
>  #define ETH64_IS_8021_RSVD(_e64)	\
>      (((_e64) & ETH64_8021_RSVD_MASK) == ETH64_8021_RSVD_PREFIX)
>  
> +#define ETH64_8021_NEAREST_CUSTOMER_BR	0x0180c2000000ULL
> +#define ETH64_8021_NON_TPMR_BR		0x0180c2000003ULL
> +#define ETH64_8021_NEAREST_BR		0x0180c200000eULL
> +
>  /*
>   * Ethernet MTU constants.
>   */
> @@ -220,12 +224,12 @@ do {									\
>  
>  #include <net/if_var.h>	/* for "struct ifnet" */
>  
> -struct ether_brport {
> -	struct mbuf	*(*eb_input)(struct ifnet *, struct mbuf *,
> +struct ether_port {
> +	struct mbuf	*(*ep_input)(struct ifnet *, struct mbuf *,
>  			   uint64_t, void *, struct netstack *);
> -	void		(*eb_port_take)(void *);
> -	void		(*eb_port_rele)(void *);
> -	void		  *eb_port;
> +	void		(*ep_port_take)(void *);
> +	void		(*ep_port_rele)(void *);
> +	void		  *ep_port;
>  };
>  
>  /*
> @@ -241,8 +245,8 @@ struct	arpcom {
>  	int	 ac_multicnt;			/* length of ac_multiaddrs */
>  	int	 ac_multirangecnt;		/* number of mcast ranges */
>  
> -	void	*ac_trunkport;
> -	const struct ether_brport *ac_brport;
> +	const struct ether_port *ac_trport;
> +	const struct ether_port *ac_brport;
>  };
>  
>  extern int arpt_keep;				/* arp resolved cache expire */
> @@ -289,11 +293,11 @@ void	ether_rtrequest(struct ifnet *, int
>  char	*ether_sprintf(u_char *);
>  
>  int	ether_brport_isset(struct ifnet *);
> -void	ether_brport_set(struct ifnet *, const struct ether_brport *);
> +void	ether_brport_set(struct ifnet *, const struct ether_port *);
>  void	ether_brport_clr(struct ifnet *);
> -const struct ether_brport *
> +const struct ether_port *
>  	ether_brport_get(struct ifnet *);
> -const struct ether_brport *
> +const struct ether_port *
>  	ether_brport_get_locked(struct ifnet *);
>  
>  uint64_t	ether_addr_to_e64(const struct ether_addr *);