|
| 1 | ++++ |
| 2 | +title = "LACP 802.3ad bonding for Ubuntu hosts in WSL2" |
| 3 | +date = "2025-07-01" |
| 4 | +description = "A guide to getting LACP functional for Ubuntu hosts on Containerlab in Windows" |
| 5 | +tags = [ |
| 6 | + "bonding", |
| 7 | + "containerlab", |
| 8 | + "lacp", |
| 9 | + "wsl2", |
| 10 | + "linux", |
| 11 | + "ubuntu", |
| 12 | +] |
| 13 | +showComments = "true" |
| 14 | +robots = "all" |
| 15 | ++++ |
| 16 | + |
| 17 | +## Introduction |
| 18 | + |
| 19 | +This article will show you how to get 802.3ad LACP bonding working easily on Linux hosts in Containerlab inside of a WSL2 instance, despite search results an your friendly LLM telling you this is not possible (unless you build your own WSL2 kernel). |
| 20 | + |
| 21 | +## Issue |
| 22 | + |
| 23 | +If you have tried to enable bonding in the traditional way, you will see the below error message: |
| 24 | + |
| 25 | +```bash |
| 26 | +admin@host1:~$ sudo modprobe bonding mode=802.3ad miimon=100 lacp_rate=fast |
| 27 | +modprobe: FATAL: Module bonding not found in directory /lib/modules/6.6.87.2-microsoft-standard-WSL2 |
| 28 | +``` |
| 29 | + |
| 30 | +Searching around you will find the below explanation: |
| 31 | + |
| 32 | +> WSL2 does not support loading arbitrary kernel modules, including bonding, using modprobe by default. The WSL2 kernel is a custom kernel with pre-compiled modules and does not include the bonding module or support for loading new modules. To use bonding in WSL2, you would need to build a custom WSL2 kernel with the module included and then configure WSL2 to use that custom kernel. |
| 33 | +
|
| 34 | +## Solution |
| 35 | + |
| 36 | +To fix this, we can simply include the iproute2 package in the Dockerfile builds of your relevant container, i.e. |
| 37 | + |
| 38 | +```Dockerfile |
| 39 | +FROM ubuntu:latest |
| 40 | + |
| 41 | +ENV DEBIAN_FRONTEND=noninteractive |
| 42 | + |
| 43 | +# Install iproute2 |
| 44 | +RUN apt-get update -y && apt-get install -y \ |
| 45 | +iproute2 \ |
| 46 | +&& apt-get autoremove -y \ |
| 47 | +&& apt-get clean -y \ |
| 48 | +&& rm -rf /var/lib/apt/lists/* |
| 49 | +``` |
| 50 | + |
| 51 | +A full example host Dockerfile is available [here](https://github.com/commitconfirmed/npa-showcases/blob/main/containers/lab-host/Dockerfile) |
| 52 | + |
| 53 | +{{< alert >}} |
| 54 | +**Note**: I also build the iperf3 application from source since in this Dockerfile since the ubuntu package manager has an older version. You can remove this and the gcc / make packages if you don't want to use iperf3 |
| 55 | +{{< /alert >}} |
| 56 | + |
| 57 | +From here, you can simply execute the below in a script on the host or via the Containerlab [exec](https://containerlab.dev/cmd/exec/) option in your clab file: |
| 58 | + |
| 59 | +```bash |
| 60 | +ip link add bond0 type bond |
| 61 | +ip link set bond0 type bond mode 802.3ad lacp_active on lacp_rate fast |
| 62 | +ip link set eth1 down |
| 63 | +ip link set eth2 down |
| 64 | +ip link set eth1 master bond0 |
| 65 | +ip link set eth2 master bond0 |
| 66 | +ip addr add 192.168.100.10/24 dev bond0 |
| 67 | +ip link set bond0 up |
| 68 | +``` |
| 69 | + |
| 70 | +A full list of options can be seen by executing `ip link help bond` |
| 71 | + |
| 72 | +<details> |
| 73 | +<summary>Expand / Collapse command output</summary> |
| 74 | + |
| 75 | +```bash |
| 76 | +admin@host1:~$ ip link help bond |
| 77 | +Usage: ... bond [ mode BONDMODE ] [ active_slave SLAVE_DEV ] |
| 78 | + [ clear_active_slave ] [ miimon MIIMON ] |
| 79 | + [ updelay UPDELAY ] [ downdelay DOWNDELAY ] |
| 80 | + [ peer_notify_delay DELAY ] |
| 81 | + [ use_carrier USE_CARRIER ] |
| 82 | + [ arp_interval ARP_INTERVAL ] |
| 83 | + [ arp_validate ARP_VALIDATE ] |
| 84 | + [ arp_all_targets ARP_ALL_TARGETS ] |
| 85 | + [ arp_ip_target [ ARP_IP_TARGET, ... ] ] |
| 86 | + [ ns_ip6_target [ NS_IP6_TARGET, ... ] ] |
| 87 | + [ primary SLAVE_DEV ] |
| 88 | + [ primary_reselect PRIMARY_RESELECT ] |
| 89 | + [ fail_over_mac FAIL_OVER_MAC ] |
| 90 | + [ xmit_hash_policy XMIT_HASH_POLICY ] |
| 91 | + [ resend_igmp RESEND_IGMP ] |
| 92 | + [ num_grat_arp|num_unsol_na NUM_GRAT_ARP|NUM_UNSOL_NA ] |
| 93 | + [ all_slaves_active ALL_SLAVES_ACTIVE ] |
| 94 | + [ min_links MIN_LINKS ] |
| 95 | + [ lp_interval LP_INTERVAL ] |
| 96 | + [ packets_per_slave PACKETS_PER_SLAVE ] |
| 97 | + [ tlb_dynamic_lb TLB_DYNAMIC_LB ] |
| 98 | + [ lacp_rate LACP_RATE ] |
| 99 | + [ lacp_active LACP_ACTIVE] |
| 100 | + [ ad_select AD_SELECT ] |
| 101 | + [ ad_user_port_key PORTKEY ] |
| 102 | + [ ad_actor_sys_prio SYSPRIO ] |
| 103 | + [ ad_actor_system LLADDR ] |
| 104 | + [ arp_missed_max MISSED_MAX ] |
| 105 | + |
| 106 | +BONDMODE := balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb |
| 107 | +ARP_VALIDATE := none|active|backup|all|filter|filter_active|filter_backup |
| 108 | +ARP_ALL_TARGETS := any|all |
| 109 | +PRIMARY_RESELECT := always|better|failure |
| 110 | +FAIL_OVER_MAC := none|active|follow |
| 111 | +XMIT_HASH_POLICY := layer2|layer2+3|layer3+4|encap2+3|encap3+4|vlan+srcmac |
| 112 | +LACP_ACTIVE := off|on |
| 113 | +LACP_RATE := slow|fast |
| 114 | +AD_SELECT := stable|bandwidth|count |
| 115 | +``` |
| 116 | +</details> |
| 117 | + |
| 118 | +## Demonstration |
| 119 | + |
| 120 | +See the below output from a host in my [npa-showcases](/npa-showcases) repo in the [basic-ceos-clos](https://github.com/commitconfirmed/npa-showcases/tree/main/examples/basic-ceos-clos) lab. |
| 121 | + |
| 122 | +```bash |
| 123 | +admin@host1:~$ ip addr list bond0.100 |
| 124 | +9: bond0.100@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 |
| 125 | + link/ether aa:c1:ab:44:7a:12 brd ff:ff:ff:ff:ff:ff |
| 126 | + inet 192.168.100.10/24 scope global bond0.100 |
| 127 | + valid_lft forever preferred_lft forever |
| 128 | + inet6 fe80::a8c1:abff:fe44:7a12/64 scope link |
| 129 | + valid_lft forever preferred_lft forever |
| 130 | + |
| 131 | +admin@host1:~$ cat /proc/net/bonding/bond0 |
| 132 | +Ethernet Channel Bonding Driver: v6.6.87.2-microsoft-standard-WSL2 |
| 133 | + |
| 134 | +Bonding Mode: IEEE 802.3ad Dynamic link aggregation |
| 135 | +Transmit Hash Policy: layer2 (0) |
| 136 | +MII Status: up |
| 137 | +MII Polling Interval (ms): 100 |
| 138 | +Up Delay (ms): 0 |
| 139 | +Down Delay (ms): 0 |
| 140 | +Peer Notification Delay (ms): 0 |
| 141 | + |
| 142 | +802.3ad info |
| 143 | +LACP active: on |
| 144 | +LACP rate: fast |
| 145 | +Min links: 0 |
| 146 | +Aggregator selection policy (ad_select): stable |
| 147 | + |
| 148 | +Slave Interface: eth1 |
| 149 | +MII Status: up |
| 150 | +Speed: 10000 Mbps |
| 151 | +Duplex: full |
| 152 | +Link Failure Count: 0 |
| 153 | +Permanent HW addr: aa:c1:ab:44:7a:12 |
| 154 | +Slave queue ID: 0 |
| 155 | +Aggregator ID: 2 |
| 156 | +Actor Churn State: monitoring |
| 157 | +Partner Churn State: monitoring |
| 158 | +Actor Churned Count: 0 |
| 159 | +Partner Churned Count: 0 |
| 160 | + |
| 161 | +Slave Interface: eth2 |
| 162 | +MII Status: up |
| 163 | +Speed: 10000 Mbps |
| 164 | +Duplex: full |
| 165 | +Link Failure Count: 0 |
| 166 | +Permanent HW addr: aa:c1:ab:9e:04:09 |
| 167 | +Slave queue ID: 0 |
| 168 | +Aggregator ID: 2 |
| 169 | +Actor Churn State: monitoring |
| 170 | +Partner Churn State: monitoring |
| 171 | +Actor Churned Count: 0 |
| 172 | +Partner Churned Count: 0 |
| 173 | + |
| 174 | +admin@host1:~$ ping 192.168.100.1 |
| 175 | +PING 192.168.100.1 (192.168.100.1) 56(84) bytes of data. |
| 176 | +64 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=1.91 ms |
| 177 | +64 bytes from 192.168.100.1: icmp_seq=2 ttl=64 time=1.24 ms |
| 178 | +^C |
| 179 | +--- 192.168.100.1 ping statistics --- |
| 180 | +2 packets transmitted, 2 received, 0% packet loss, time 1001ms |
| 181 | +rtt min/avg/max/mdev = 1.241/1.577/1.913/0.336 ms |
| 182 | +``` |
0 commit comments