Download raw body.
trunk(4): deprecate LACP mode
On Tue, Dec 02, 2025 at 06:09:09AM -0500, Chaz Kettleson wrote:
> 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
>
Hello,
Good news, everything appears to work.
1. mv /etc/hostname.trunk0 /etc/hostname.aggr0
2. removed trunkproto lacp (no longer needed)
3. updated all hostname.vlan* with parent aggr0
Not sure if something changed since 6.9/7.0 to now, or if I messed
something up when I originally tried -- but everything is working as
expected now!
Thanks for the motivation to change this.
--
Chaz
trunk(4): deprecate LACP mode