Ten Wifi Dongles At Once

I used to try to do one dumb/weird hardware thing every year. One year, I used a Palm Pilot as a serial terminal for a Sun SPARCStation-10. Multiple serial cable adapters and gender changers involved.

My dumb hardware stunt for October 2024: 10 USB WiFi adapters on the same laptop.

All 10 WiFi adapters

Top row, left to right: Airlink, TP-Link TL-WN725N, TP-Link Archer T3U TP-Link TL-WN722N, TP-Link TL-WN821N

Second row down: TP-Link AC 1200

Third row down: TP-Link Archer T2U Plus

Bottom row, left to right: D-Link DWA-171A, Netgear A6210, Wi-Pi™

Model Chip set USB ID MAC Address Kernel module
Airlink Realtek RTL8188CUS 0bda:8176 00:21:2f:3a:75:50 rtl8xxxu
D-Link DWA-171A1 Realtek RTL8811AU 2001:3314 c4:12:f5:de:67:36 8821au
Netgear A6210 Mediatek MT7612U 0846:9053 6c:cd:d6:c0:dc:7d mt76x2u
TP-Link AC 1200 RTL88x2bu 0bda:b812 00:13:ef:f2:4d:bd rtw88_8822bu
TP-Link Archer T2U Plus RTL8821AU 2357:0120 98:48:27:3f:c0:95 8821au
TP-Link T3U RTL8812BU 1d6b:0003 d0:37:45:50:60:0a rtw88_8822bu
TP-Link TL-WN722N Atheros AR9271 802.11n 0cf3:9271 e8:de:27:a1:77:ea ath9k_htc
TP-Link TL-WN725N RTL8188EUS 0bda:8179 b4:b0:24:e0:89:88 rt2800usb
TP-Link TL-WN821N Atheros AR7010+AR9287 0cf3:7015 f8:d1:11:14:79:21 ath9k_htc
Wi-Pi™ Ralink RT5370 148f:5370 40:a5:ef:07:1c:8d rt2800usb

Device Drivers

We’re constantly told that Linux device drivers, in particular WiFi drivers, is depressingly poor. I did not find tat to be the case.

cartoon mocking the state of Linux Wireless

Six of the adapters Just Worked™, but I had to fiddle a bit for the other four. I’m doing this on a Dell E7470 Latitude laptop running Arch Linux, with kernel 6.11.1-zen1-1-zen.

Wi-Pi and TP-Link TL-WN725N: sudo modprobe -v rt2800usb The kernel driver was included in Linux 6.11.1, but it didn’t get loaded automatically. I’m a little mystified by how automatic module loading works in general

DWA-171A and T2U Plus: had to compile a kernel driver from source.

Here’s what lsusb says about these two WiFi adapters:

Bus 001 Device 021: ID 2001:3314 D-Link Corp. DWA-171 AC600 DB Wireless Adapter(rev.A1) [Realtek RTL8811AU]
Bus 001 Device 024: ID 2357:0120 TP-Link Archer T2U PLUS [RTL8821AU]

I tried three from-source drivers, rtl88xxau-aircrack-dkms-git from the Arch AUR, and two Github repositories, morrownr/8821cu-20210916 and brektrou/rtl8821CU. The AUR driver caused kernel “oops” that prevented me from killing processes, the other two couldn’t find either adapter.

I ended up using morrownr/8821au-20210708. It strangely works for both DWA-171 and T2U PLUS, despite the DWA-171 identifying as having the RTL8811AU chip set.

Each adapter could associate with my WiFi access points, and dhcpcd could acquire an IPv4 address, and a route.

13 Socket externally-powered USB Hub

You can’t plug in more USB devices than your computer has physical USB sockets. I purchased an externally powered USB hub a few years ago by mistake. I was trying to make a Raspberry Pi Kubernetes cluster, and I needed to power a bunch of Raspberry Pi boards. This was not the solution to that problem.

externally powered 13-port USB hub

It has no brand name or other markings on it. After you plug it in, lsusb has this to say about it:

Bus 001 Device 017: ID 14cd:8601 Super Top 4-Port hub
Bus 001 Device 018: ID 14cd:8601 Super Top 4-Port hub
Bus 001 Device 019: ID 14cd:8601 Super Top 4-Port hub
Bus 001 Device 020: ID 14cd:8601 Super Top 4-Port hub

The adapters closest to the power socket got hot over the 30 minutes or so it took me to plug everything in, copy a wpa_supplicant config file, and turn the device up. This seems like a flaw.

It Worked!

USB hub with 10 wifi adapters plugged in

There’s only 9 adapters visible in the view above. The DWA-171 is obscured by the tall antenna of the T2U PLUS or the AC1200. It’s in slot 8 of the USB hub.

USB hub with 10 wifi adapters plugged in

I plugged in each USB WiFi adapter one a time, creating a wpa_supplicant configuration file then using ip link set dev $SOMETHING up to activate.

The steps go something like this:

  1. Plug in adapter
  2. Watch journalctl -f output to see the kernel recognize a new USB device
  3. Run ip -br link to find the weird interface name assigned
  4. cp /etc/wpa_supplicant/wpa-supplicant-wlp1s0.conf /etc/wpa_supplicant/wpa-supplicant-$NEWDEVICE.conf
  5. systemctcl start wpa-supplicant@$NEWDEVICE
  6. ip link set dev $NEWDEVICE up
  7. Watch journalctl -f output to see dhcpcd acquire a new IPv4 address

Addresses and weird interface names`

Here’s how the IPv4 addresses initially got assigned.

1142 % ip -br address
lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp0s31f6        DOWN           
wlp1s0           UP             10.0.20.35/24 fe80::3dde:54b1:a20f:b816/64 
wlp0s20f0u1u1u4  UP             10.0.20.105/24 fe80::cc9:a944:a2b9:ad10/64 
wlp0s20f0u1u1u3  UP             10.0.20.106/24 fe80::26f7:9acb:59eb:9fa4/64 
wlp0s20f0u1u2u4  UP             10.0.20.107/24 fe80::f38b:6cc3:4cc9:c176/64 
wlp0s20f0u1u1u2  UP             10.0.20.108/24 fe80::a4df:5208:9156:27cb/64 
wlp0s20f0u1u2u1  UP             10.0.0.109/24 fe80::46b:a293:5e35:13a2/64 
wlp0s20f0u1u2u3  UP             10.0.20.109/24 fe80::603e:92db:fd1:37ca/64 
wlp0s20f0u1u3u3  UP             10.0.0.110/24 fe80::acb4:a169:9184:9ef/64 
wlp0s20f0u1u3u2  UP             10.0.0.111/24 fe80::bd64:56b9:c6c7:eb6d/64 
wlp0s20f0u1u3u1  UP             10.0.0.112/24 fe80::ba50:d06d:2a37:e935/64 
wlp0s20f0u1u4    UP             10.0.20.110/24 fe80::ee1e:4996:8dea:1c60/64 

I have three Linksys Velop wireless access points placed strategically around my house. Each one has a CAT-5 cable running back to my Dell R530 server. They’re in “bridge mode” so that my server can control IP addresses and routing, consolidate NTP and DNS. My desk is roughly equidistant from the access point that has address 10.0.0.1 and the access point that has address 10.0.20.1. kea-dhcp4 gives out 10.0.0.0/24 or 10.0.20.0/24 addresses from pools appropriately.

enp0s31f6 is the name of the CAT-5 ethernet cable port. wlp1s0 is my laptop’s built-in WiFi device.

Interface name MAC Address WiFi adapter
wlp0s20f0u1u1u2 d0:37:45:50:60:0a TP-Link T3U
wlp0s20f0u1u3u1 98:48:27:3f:c0:95 TP-Link Archer T2U Plus
wlp0s20f0u1u4 40:a5:ef:07:1c:8d Wi-Pi™
wlp0s20f0u1u2u1 e8:de:27:a1:77:ea TP-Link TL-WN722N
wlp0s20f0u1u3u2 c4:12:f5:de:67:3c D-Link DWA-171A1
wlp0s20f0u1u1u3 b4:b0:24:e0:89:88 TP-Link TL-WN725N
wlp0s20f0u1u2u3 6c:cd:d6:c0:dc:7d Netgear A6210
wlp0s20f0u1u3u3 00:13:ef:f2:4d:bd TP-Link AC 1200
wlp0s20f0u1u1u4 f8:d1:11:14:79:21 TP-Link TL-WN821N
wlp0s20f0u1u2u4 00:21:2f:3a:75:50 Airlink

I’m in the dark about how interface names are assigned. I suspect that the names have something to do with how many USB hubs are chained, but I’m not certain.

Routing Table

Just to see how weird this got, here’s ip -br route output:

default via 10.0.20.1 dev wlp1s0 proto dhcp src 10.0.20.35 metric 3004 
default via 10.0.20.1 dev wlp0s20f0u1u1u4 proto dhcp src 10.0.20.105 metric 3018 
default via 10.0.20.1 dev wlp0s20f0u1u1u3 proto dhcp src 10.0.20.106 metric 3019 
default via 10.0.20.1 dev wlp0s20f0u1u2u4 proto dhcp src 10.0.20.107 metric 3020 
default via 10.0.20.1 dev wlp0s20f0u1u1u2 proto dhcp src 10.0.20.108 metric 3021 
default via 10.0.0.1 dev wlp0s20f0u1u2u1 proto dhcp src 10.0.0.109 metric 3022 
default via 10.0.20.1 dev wlp0s20f0u1u2u3 proto dhcp src 10.0.20.109 metric 3023 
default via 10.0.0.1 dev wlp0s20f0u1u3u3 proto dhcp src 10.0.0.110 metric 3024 
default via 10.0.0.1 dev wlp0s20f0u1u3u2 proto dhcp src 10.0.0.111 metric 3025 
default via 10.0.0.1 dev wlp0s20f0u1u3u1 proto dhcp src 10.0.0.112 metric 3026 
default via 10.0.20.1 dev wlp0s20f0u1u4 proto dhcp src 10.0.20.110 metric 3027 
10.0.0.0/24 dev wlp0s20f0u1u2u1 proto dhcp scope link src 10.0.0.109 metric 3022 
10.0.0.0/24 dev wlp0s20f0u1u3u3 proto dhcp scope link src 10.0.0.110 metric 3024 
10.0.0.0/24 dev wlp0s20f0u1u3u2 proto dhcp scope link src 10.0.0.111 metric 3025 
10.0.0.0/24 dev wlp0s20f0u1u3u1 proto dhcp scope link src 10.0.0.112 metric 3026 
10.0.20.0/24 dev wlp1s0 proto dhcp scope link src 10.0.20.35 metric 3004 
10.0.20.0/24 dev wlp0s20f0u1u1u4 proto dhcp scope link src 10.0.20.105 metric 3018 
10.0.20.0/24 dev wlp0s20f0u1u1u3 proto dhcp scope link src 10.0.20.106 metric 3019 
10.0.20.0/24 dev wlp0s20f0u1u2u4 proto dhcp scope link src 10.0.20.107 metric 3020 
10.0.20.0/24 dev wlp0s20f0u1u1u2 proto dhcp scope link src 10.0.20.108 metric 3021 
10.0.20.0/24 dev wlp0s20f0u1u2u3 proto dhcp scope link src 10.0.20.109 metric 3023 
10.0.20.0/24 dev wlp0s20f0u1u4 proto dhcp scope link src 10.0.20.110 metric 3027 

ssh to every interface

I used ssh to log in from one interface to another. I’m not convinced the TCP packets went out one interface, and back into another, however. They might all have been routed internally.

1001 % who
bediger  tty1         Oct  5 22:19
bediger  pts/0        Oct  5 22:19 (:0)
bediger  pts/1        Oct  5 22:19 (:0)
bediger  pts/2        Oct  5 22:19 (:0)
bediger  pts/3        Oct  5 22:19 (:0)
bediger  pts/4        Oct  8 14:11 (:0)
bediger  pts/5        Oct  6 20:44 (:0)
bediger  pts/6        Oct  8 15:03 (10.0.20.35)
bediger  pts/7        Oct  8 15:03 (10.0.20.105)
bediger  pts/8        Oct  8 15:03 (10.0.20.106)
bediger  pts/9        Oct  8 15:03 (10.0.20.107)
bediger  pts/10       Oct  8 15:04 (10.0.20.108)
bediger  pts/11       Oct  8 15:04 (10.0.20.109)
bediger  pts/12       Oct  8 15:05 (10.0.0.110)
bediger  pts/13       Oct  8 15:05 (10.0.0.111)
bediger  pts/14       Oct  8 15:05 (10.0.0.112)
bediger  pts/15       Oct  8 15:05 (10.0.20.110)

I probably should have ssh'ed to each interface from my R530, just to insure that all interfaces work simultaneously.

The view from my router

I logged in to my router, a Dell R530 rack mount with far too many ethernet ports that is set to use monarch as it’s host name. Here’s what I could see.

bediger@monarch
1013 % ip n
10.0.0.43 dev eno1 lladdr 00:e0:4c:68:22:34 REACHABLE 
10.0.20.77 dev eno4 lladdr c4:41:1e:4e:38:03 REACHABLE 
10.0.0.42 dev eno1 FAILED 
10.0.10.105 dev eno2 FAILED 
10.0.10.104 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.30.77 dev ens4 FAILED 
10.0.20.124 dev eno4 lladdr 8c:49:62:46:f4:f4 REACHABLE 
10.0.10.106 dev eno2 FAILED 
10.0.10.109 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.108 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.0.35 dev eno1 FAILED 
10.0.20.123 dev eno4 lladdr f8:ff:c2:0e:54:57 STALE 
10.0.0.105 dev eno1 lladdr f8:ff:c2:0e:54:57 STALE 
10.0.0.104 dev eno1 FAILED 
10.0.0.107 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.0.106 dev eno1 FAILED 
10.0.0.109 dev eno1 lladdr e8:de:27:a1:77:ea REACHABLE 
10.0.10.101 dev eno2 FAILED 
10.0.0.108 dev eno1 lladdr 40:a5:ef:07:1c:8d STALE 
10.0.10.100 dev eno2 FAILED 
10.0.10.103 dev eno2 FAILED 
10.0.10.102 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.20.100 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.20.102 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.40.70 dev ens5 lladdr 20:7c:14:f3:83:05 STALE 
10.0.0.101 dev eno1 lladdr 50:14:79:9d:fd:3e STALE 
10.0.0.100 dev eno1 lladdr c4:41:1e:4e:38:3f STALE 
10.0.0.103 dev eno1 FAILED 
10.0.0.102 dev eno1 FAILED 
10.0.10.77 dev eno2 lladdr c4:41:1e:4e:37:b5 REACHABLE 
10.0.20.101 dev eno4 lladdr 98:48:27:3f:c0:95 STALE 
10.0.20.100 dev eno4 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.20.103 dev eno4 FAILED 
10.0.20.102 dev eno4 FAILED 
10.0.0.104 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.0.106 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.0.42 dev eno4 lladdr a8:8f:d9:30:4d:54 STALE 
10.0.10.101 dev eno4 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.106 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.109 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.108 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.102 dev eno4 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.20.42 dev eno4 lladdr a8:8f:d9:30:4d:54 STALE 
10.0.0.69 dev eno1 lladdr 00:17:c8:c0:de:d1 STALE 
10.0.0.103 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.101 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.0.102 dev eno2 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.100 dev eno1 lladdr 50:14:79:9d:fd:3e STALE 
10.0.20.35 dev eno4 lladdr e4:a4:71:30:21:86 REACHABLE 
10.0.10.103 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.102 dev eno1 lladdr 74:74:46:cb:d9:d9 STALE 
10.0.10.35 dev eno2 FAILED 
fe80::3dde:54b1:a20f:b816 dev eno4 lladdr e4:a4:71:30:21:86 STALE 
fe80::c641:1eff:fe4e:3803 dev eno4 lladdr c4:41:1e:4e:38:03 router STALE 
fe80::acb4:a169:9184:9ef dev eno1 lladdr 00:13:ef:f2:4d:bd STALE 
fe80::c641:1eff:fe4e:383f dev eno1 lladdr c4:41:1e:4e:38:3f router STALE 
fe80::c641:1eff:fe4e:37b5 dev eno2 lladdr c4:41:1e:4e:37:b5 router STALE

IPv6 pings

IPv6 is weird. It has all these broadcast addresses, like ff02::1, the link-local broadcast address for all nodes. I tried this address on my Dell R530. I had to specify which interface on the R530 to send out the ping, because IPv6 routing is so dang complicated.

“eno1” is the R530’s 10.0.0.1 interface, and “eno4” is the 10.0.20.1 interface. Each WiFi access point is plugged in to a different ethernet socket on the R530.

PING ff02::1%eno1 (ff02::1%eno1) 56 data bytes
64 bytes from fe80::46a8:42ff:fe2d:c255%eno1: icmp_seq=1 ttl=64 time=0.022 ms
64 bytes from fe80::217:c8ff:fec0:ded1%eno1: icmp_seq=1 ttl=255 time=0.426 ms
64 bytes from fe80::c641:1eff:fe4e:383f%eno1: icmp_seq=1 ttl=64 time=0.441 ms
64 bytes from fe80::f38b:6cc3:4cc9:c176%eno1: icmp_seq=1 ttl=64 time=59.0 ms
64 bytes from fe80::cc9:a944:a2b9:ad10%eno1: icmp_seq=1 ttl=64 time=59.8 ms
64 bytes from fe80::acb4:a169:9184:9ef%eno1: icmp_seq=1 ttl=64 time=74.3 ms
64 bytes from fe80::a4df:5208:9156:27cb%eno1: icmp_seq=1 ttl=64 time=74.5 ms
64 bytes from fe80::ba50:d06d:2a37:e935%eno1: icmp_seq=1 ttl=64 time=75.3 ms
64 bytes from fe80::603e:92db:fd1:37ca%eno1: icmp_seq=1 ttl=64 time=79.7 ms
64 bytes from fe80::549b:6750:dafc:1684%eno1: icmp_seq=1 ttl=64 time=140 ms

If you look closely, you can see the IPv6 addresses of TP-Link T3U, TP-Link Archer T2U Plus, Netgear A6210, TP-Link AC 1200, TP-Link TL-WN821N, Airlink.

On the other interface, which should be 10.0.20.0/24 addresses:

PING ff02::1%eno4 (ff02::1%eno4) 56 data bytes
64 bytes from fe80::46a8:42ff:fe2d:c258%eno4: icmp_seq=1 ttl=64 time=0.025 ms
64 bytes from fe80::c641:1eff:fe4e:3803%eno4: icmp_seq=1 ttl=64 time=0.712 ms
64 bytes from fe80::8e49:62ff:fe46:f4f4%eno4: icmp_seq=1 ttl=64 time=11.4 ms
64 bytes from fe80::3dde:54b1:a20f:b816%eno4: icmp_seq=1 ttl=64 time=77.3 ms
64 bytes from fe80::bd64:56b9:c6c7:eb6d%eno4: icmp_seq=1 ttl=64 time=83.7 ms
64 bytes from fe80::46b:a293:5e35:13a2%eno4: icmp_seq=1 ttl=64 time=106 ms

I only find the IPv6 address of D-Link DWA-171A1. I think I futzed around too long between pings. dhcpcd must have switched some interface from 10.0.20.0/24 addresses to 10.0.0.0/24 addresses, so I missed them in the second ping.

Addresses flip as time progresses

Remember that I wrote that my laptop and all 10 dongles were about equidistant from each of 2 access points? Over time, DHCP renewals switched access points:

ip -br a
lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp0s31f6        DOWN           
wlp1s0           UP             10.0.20.35/24 fe80::3dde:54b1:a20f:b816/64 
wlp0s20f0u1u1u2  UP             10.0.0.113/24 fe80::a4df:5208:9156:27cb/64 
wlp0s20f0u1u3u1  UP             10.0.0.112/24 fe80::ba50:d06d:2a37:e935/64 
wlp0s20f0u1u4    UP             10.0.20.110/24 fe80::ee1e:4996:8dea:1c60/64 
wlp0s20f0u1u2u1  UP             10.0.20.104/24 fe80::46b:a293:5e35:13a2/64 
wlp0s20f0u1u3u2  UP             10.0.20.111/24 fe80::bd64:56b9:c6c7:eb6d/64 
wlp0s20f0u1u1u3  UP             10.0.0.115/24 fe80::26f7:9acb:59eb:9fa4/64 
wlp0s20f0u1u2u3  UP             10.0.0.116/24 fe80::603e:92db:fd1:37ca/64 
wlp0s20f0u1u3u3  UP             10.0.0.110/24 fe80::acb4:a169:9184:9ef/64 
wlp0s20f0u1u1u4  UP             10.0.0.117/24 fe80::cc9:a944:a2b9:ad10/64 
wlp0s20f0u1u2u4  UP             10.0.0.114/24 fe80::f38b:6cc3:4cc9:c176/64 

My kea-dhcp4 must give out very short lease times, or something. dhcpcd is weird about how long to retain a lease as opposed to how long the server says the lease is good for.

The other factor is the WiFi access points are in bridging mode. I don’t believe they can cooperate to hand off a connection.