Index | Thread | Search

From:
Chaz Kettleson <chaz@pyr3x.com>
Subject:
Re: trunk(4): deprecate LACP mode
To:
David Gwynne <david@gwynne.id.au>
Cc:
tech@openbsd.org
Date:
Tue, 2 Dec 2025 06:09:09 -0500

Download raw body.

Thread
On Tue, Dec 02, 2025 at 05:14:58PM +1000, David Gwynne wrote:
> aggr(4) is a better option for LACP for several reasons:
> 
> - in my experience aggr is better at following the standard
> 
> it doesn't get into weird states that blackhole traffic at the worst
> possible times.
> 
> - aggr is mpsafe
> 
> the last time i looked at trying to make the lacp code mpsafe in
> trunk(4) i ended up writing aggr(4). this lack of mpsafety is now
> making it hard to unlock more of the network stack.
> 
> - aggr is faster than trunk
> 
> looking at the benchmarking we did when i first introduced aggr,
> it looks like using trunk(4) more than halved the pps you can
> forward through a box, while aggr was more like a 10 to 15% hit.
> switching from trunk to aggr in our environment doubled our firewall
> performance.
> 
> apart from these implementation differences, there's a few operational
> ones too that sthen@ has pointed out:
> 
> - aggr uses a random MAC by default, trunk uses the MAC of the first-
>   added child port.
> 
> - aggr does not bring the interface up automatically, you must use "up".
> 
> anyone still using trunk(4) for LACP links will need to do at least the
> following:
> 
> # mv hostname.trunk0 hostname.aggr0
> # echo up >> hostname.aggr0
> 
> if you want a predictable MAC address on an aggr interface, you'll need
> to specify the lladdr before bringing it up. for example, my configs
> looks like this:
> 
> lladdr fe:e1:ba:d0:e8:43
> trunkport ixl0
> trunkport ixl1
> up
> 
> a bunch of vlan interfaces sit on top of this, which is why it doesnt
> have address config of its own.
> 
> an example with ips:
> 
> lladdr fe:e1:ba:d0:7c:ff
> trunkproto lacp
> trunkport ix0
> trunkport ix1
> inet 192.0.2.216 255.255.255.0
> inet6 2001:db8:230f:bc24:d3d8:7b58:f17c:f96e 64
> up
> 
> Index: share/man/man4/trunk.4
> ===================================================================
> RCS file: /cvs/src/share/man/man4/trunk.4,v
> diff -u -p -r1.31 trunk.4
> --- share/man/man4/trunk.4	24 Aug 2020 07:34:00 -0000	1.31
> +++ share/man/man4/trunk.4	2 Dec 2025 05:05:16 -0000
> @@ -38,7 +38,6 @@ command.
>  The driver currently supports the trunk protocols
>  .Ic broadcast ,
>  .Ic failover ,
> -.Ic lacp ,
>  .Ic loadbalance ,
>  .Ic none ,
>  and
> @@ -58,17 +57,6 @@ If the master port becomes unavailable,
>  the next active port is used.
>  The first interface added is the master port;
>  any interfaces added after that are used as failover devices.
> -.It Ic lacp
> -Uses the IEEE 802.3ad (renamed to 802.1AX in 2014)
> -Link Aggregation Control Protocol (LACP)
> -and the Marker Protocol
> -to increase link speed and provide redundancy.
> -LACP trunk groups are composed of ports of the same speed,
> -set to full-duplex operation.
> -This protocol requires a switch which supports LACP.
> -By default, the LACP implementation uses active-mode LACP,
> -slow timeout, and 0x8000 (medium) priority as system and port
> -priorities.
>  .It Ic loadbalance
>  Distributes outgoing traffic through all active ports
>  and accepts incoming traffic from any active port.
> @@ -90,6 +78,12 @@ The configuration can be done at runtime
>  .Xr hostname.if 5
>  configuration file for
>  .Xr netstart 8 .
> +.Pp
> +.Nm
> +does not implement
> +IEEE 802.1AX (formerly 802.3ad) Link Aggregation,
> +it is supported by
> +.Xr aggr 4 .
>  .Sh EXAMPLES
>  Create a simple round robin trunk with two
>  .Xr bge 4
> @@ -113,6 +107,7 @@ device will be used:
>  	192.168.1.1 netmask 255.255.255.0
>  .Ed
>  .Sh SEE ALSO
> +.Xr aggr 4 ,
>  .Xr inet 4 ,
>  .Xr hostname.if 5 ,
>  .Xr ifconfig 8 ,
> @@ -122,6 +117,10 @@ The
>  .Nm
>  device first appeared in
>  .Ox 3.8 .
> +Support for 802.3ad Link Aggregation was added in
> +.Ox 4.4
> +and removed in
> +.Ox 7.9 .
>  .Sh AUTHORS
>  The
>  .Nm
> Index: sys/conf/files
> ===================================================================
> RCS file: /cvs/src/sys/conf/files,v
> diff -u -p -r1.748 files
> --- sys/conf/files	14 Nov 2025 01:55:07 -0000	1.748
> +++ sys/conf/files	2 Dec 2025 05:05:16 -0000
> @@ -868,7 +872,6 @@ file net/slcompress.c			ppp
>  file net/if_enc.c			enc
>  file net/if_gre.c			gre			needs-count
>  file net/if_trunk.c			trunk
> -file net/trunklacp.c			trunk
>  file net/if_aggr.c			aggr
>  file net/if_tpmr.c			tpmr
>  file net/if_mpe.c			mpe
> Index: sys/net/if_trunk.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_trunk.c,v
> diff -u -p -r1.159 if_trunk.c
> --- sys/net/if_trunk.c	2 Dec 2025 03:24:19 -0000	1.159
> +++ sys/net/if_trunk.c	2 Dec 2025 05:05:16 -0000
> @@ -42,7 +42,6 @@
>  #endif
>  
>  #include <net/if_trunk.h>
> -#include <net/trunklacp.h>
>  
>  #include "bpfilter.h"
>  #if NBPFILTER > 0
> @@ -125,13 +124,6 @@ int	 trunk_bcast_start(struct trunk_soft
>  int	 trunk_bcast_input(struct trunk_softc *, struct trunk_port *,
>  	    struct mbuf *);
>  
> -/* 802.3ad LACP */
> -int	 trunk_lacp_attach(struct trunk_softc *);
> -int	 trunk_lacp_detach(struct trunk_softc *);
> -int	 trunk_lacp_start(struct trunk_softc *, struct mbuf *);
> -int	 trunk_lacp_input(struct trunk_softc *, struct trunk_port *,
> -	    struct mbuf *);
> -
>  /* Trunk protocol table */
>  static const struct {
>  	enum trunk_proto	ti_proto;
> @@ -141,7 +133,6 @@ static const struct {
>  	{ TRUNK_PROTO_FAILOVER,		trunk_fail_attach },
>  	{ TRUNK_PROTO_LOADBALANCE,	trunk_lb_attach },
>  	{ TRUNK_PROTO_BROADCAST,	trunk_bcast_attach },
> -	{ TRUNK_PROTO_LACP,		trunk_lacp_attach },
>  	{ TRUNK_PROTO_NONE,		NULL }
>  };
>  
> @@ -606,11 +597,6 @@ trunk_port2req(struct trunk_port *tp, st
>  		if (TRUNK_PORTACTIVE(tp))
>  			rp->rp_flags |= TRUNK_PORT_ACTIVE;
>  		break;
> -
> -	case TRUNK_PROTO_LACP:
> -		/* LACP has a different definition of active */
> -		rp->rp_flags = lacp_port_status(tp);
> -		break;
>  	default:
>  		break;
>  	}
> @@ -622,11 +608,8 @@ trunk_ioctl(struct ifnet *ifp, u_long cm
>  	struct trunk_softc *tr = (struct trunk_softc *)ifp->if_softc;
>  	struct trunk_reqall *ra = (struct trunk_reqall *)data;
>  	struct trunk_reqport *rp = (struct trunk_reqport *)data, rpbuf;
> -	struct trunk_opts *tro = (struct trunk_opts *)data;
>  	struct ifreq *ifr = (struct ifreq *)data;
> -	struct lacp_softc *lsc;
>  	struct trunk_port *tp;
> -	struct lacp_port *lp;
>  	struct ifnet *tpif;
>  	int i, error = 0;
>  
> @@ -710,16 +693,7 @@ trunk_ioctl(struct ifnet *ifp, u_long cm
>  		break;
>  	case SIOCGTRUNKOPTS:
>  		/* Only LACP trunks have options atm */
> -		if (tro->to_proto != TRUNK_PROTO_LACP) {
> -			error = EPROTONOSUPPORT;
> -			break;
> -		}
> -		lsc = LACP_SOFTC(tr);
> -		tro->to_lacpopts.lacp_mode = lsc->lsc_mode;
> -		tro->to_lacpopts.lacp_timeout = lsc->lsc_timeout;
> -		tro->to_lacpopts.lacp_prio = lsc->lsc_sys_prio;
> -		tro->to_lacpopts.lacp_portprio = lsc->lsc_port_prio;
> -		tro->to_lacpopts.lacp_ifqprio = lsc->lsc_ifq_prio;
> +		error = EPROTONOSUPPORT;
>  		break;
>  	case SIOCSTRUNKOPTS:
>  		if ((error = suser(curproc)) != 0) {
> @@ -727,74 +701,7 @@ trunk_ioctl(struct ifnet *ifp, u_long cm
>  			break;
>  		}
>  		/* Only LACP trunks have options atm */
> -		if (tro->to_proto != TRUNK_PROTO_LACP) {
> -			error = EPROTONOSUPPORT;
> -			break;
> -		}
> -		lsc = LACP_SOFTC(tr);
> -		switch(tro->to_opts) {
> -			case TRUNK_OPT_LACP_MODE:
> -				/*
> -				 * Ensure mode changes occur immediately
> -				 * on all ports
> -				 */
> -				lsc->lsc_mode = tro->to_lacpopts.lacp_mode;
> -				if (lsc->lsc_mode == 0) {
> -					LIST_FOREACH(lp, &lsc->lsc_ports,
> -					    lp_next)
> -						lp->lp_state &=
> -						    ~LACP_STATE_ACTIVITY;
> -				} else {
> -					LIST_FOREACH(lp, &lsc->lsc_ports,
> -					    lp_next)
> -						lp->lp_state |=
> -						    LACP_STATE_ACTIVITY;
> -				}
> -				break;
> -			case TRUNK_OPT_LACP_TIMEOUT:
> -				/*
> -				 * Ensure timeout changes occur immediately
> -				 * on all ports
> -				 */
> -				lsc->lsc_timeout =
> -				    tro->to_lacpopts.lacp_timeout;
> -				if (lsc->lsc_timeout == 0) {
> -					LIST_FOREACH(lp, &lsc->lsc_ports,
> -					    lp_next)
> -						lp->lp_state &=
> -						    ~LACP_STATE_TIMEOUT;
> -				} else {
> -					LIST_FOREACH(lp, &lsc->lsc_ports,
> -					    lp_next)
> -						lp->lp_state |=
> -						    LACP_STATE_TIMEOUT;
> -				}
> -				break;
> -			case TRUNK_OPT_LACP_SYS_PRIO:
> -				if (tro->to_lacpopts.lacp_prio == 0) {
> -					error = EINVAL;	
> -					break;
> -				}
> -				lsc->lsc_sys_prio = tro->to_lacpopts.lacp_prio;
> -				break;
> -			case TRUNK_OPT_LACP_PORT_PRIO:
> -				if (tro->to_lacpopts.lacp_portprio == 0) {
> -					error = EINVAL;	
> -					break;
> -				}
> -				lsc->lsc_port_prio =
> -				    tro->to_lacpopts.lacp_portprio;
> -				break;
> -			case TRUNK_OPT_LACP_IFQ_PRIO:
> -				if (tro->to_lacpopts.lacp_ifqprio >
> -				    IFQ_MAXPRIO) {
> -					error = EINVAL;	
> -					break;
> -				}
> -				lsc->lsc_ifq_prio =
> -				    tro->to_lacpopts.lacp_ifqprio;
> -				break;
> -		}
> +		error = EPROTONOSUPPORT;
>  		break;
>  	case SIOCGTRUNKPORT:
>  		if (rp->rp_portname[0] == '\0' ||
> @@ -1681,70 +1588,4 @@ int
>  trunk_bcast_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
>  {
>  	return (0);
> -}
> -
> -/*
> - * 802.3ad LACP
> - */
> -
> -int
> -trunk_lacp_attach(struct trunk_softc *tr)
> -{
> -	struct trunk_port *tp;
> -	int error;
> -
> -	tr->tr_detach = trunk_lacp_detach;
> -	tr->tr_port_create = lacp_port_create;
> -	tr->tr_port_destroy = lacp_port_destroy;
> -	tr->tr_linkstate = lacp_linkstate;
> -	tr->tr_start = trunk_lacp_start;
> -	tr->tr_input = trunk_lacp_input;
> -	tr->tr_init = lacp_init;
> -	tr->tr_stop = lacp_stop;
> -	tr->tr_req = lacp_req;
> -	tr->tr_portreq = lacp_portreq;
> -
> -	error = lacp_attach(tr);
> -	if (error)
> -		return (error);
> -
> -	SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
> -		lacp_port_create(tp);
> -
> -	return (error);
> -}
> -
> -int
> -trunk_lacp_detach(struct trunk_softc *tr)
> -{
> -	struct trunk_port *tp;
> -	int error;
> -
> -	SLIST_FOREACH(tp, &tr->tr_ports, tp_entries)
> -		lacp_port_destroy(tp);
> -
> -	/* unlocking is safe here */
> -	error = lacp_detach(tr);
> -
> -	return (error);
> -}
> -
> -int
> -trunk_lacp_start(struct trunk_softc *tr, struct mbuf *m)
> -{
> -	struct trunk_port *tp;
> -
> -	tp = lacp_select_tx_port(tr, m);
> -	if (tp == NULL) {
> -		m_freem(m);
> -		return (EBUSY);
> -	}
> -
> -	return (if_enqueue(tp->tp_if, m));
> -}
> -
> -int
> -trunk_lacp_input(struct trunk_softc *tr, struct trunk_port *tp, struct mbuf *m)
> -{
> -	return (lacp_input(tp, m));
>  }
> 

Hello,

Do you know of any cases where aggr is _not_ a complete replacement for
trunk? A few years ago I setup a Dell PowerConnect 5224 in LACP and was
unable to get aggr to work, however, the following does work for me:

cat /etc/hostname.trunk0 
trunkproto lacp
trunkport bnx0
trunkport bnx1
trunkport bge0
up

I can give it a shot again tonight to see if I missed something when I
initially tried aggr.

-- 
Chaz