I didn't know it at the time, but my results testing the EDUP WiFi 6 card (which uses the Intel AX200 chipset) on the Raspberry Pi in December weren't accurate.
It doesn't get 1.34 gigabits of bandwidth with the Raspberry Pi Compute Module 4 like I stated in my December video, WiFi 6 on the Raspberry Pi CM4 makes it Fly!.
I'm very thorough in my benchmarking, and if there's ever a weird anomaly, I try everything I can to prove or disprove the result before sharing it with anyone.
In this case, since I was chomping at the bit to move on to testing a Rosewill 2.5 gigabit Ethernet card, I didn't spend as much time as I should have re-verifying my results.
In this post I'll describe how testing this $20 M.2 WiFi adapter card suggested by Javier Choclin led to me learning a lot about Linux's wireless networking stack.
Video for this post
There is a video that goes along with this blog post, for the visually-inclined:
It's just not possible!
Full transparency, what really got me started thinking about my performance results was this comment on YouTube, which reads:
Intel ax200 is a 2x2 chip. Max 11ax Phy datarate it can support is MCS11 (1024-QAM) which is 1201Mbps. It is impossible to get 1340 Mbps iperf throughput using a 2x2 chipset. Can you share the iperf log showing 1340Mbps like you showed for ethernet :)
That 1.2 gigabit limit is definitely lower than the 1.34 gigabit result I got in my testing.
So what gives?
Well, let's start with the facts:
- A couple months ago, when I tested five Ethernet interfaces at the same time on the Intel I340-T4 card, I put each interface on its own network, connecting just that interface to an individual Raspberry Pi on the same network. I got 4.15 Gbps with the four interfaces on that card and the internal network interface using Jumbo Frames.
- When I tested the EDUP WiFi 6 card, I got 930 Mbps of one-way throughput.
- When I tested an ASUS 10 Gbps card (more on it coming soon!), I got 3.26 Gbps of one-way throughput.
- When I tested an Intel AX200 Desktop Kit in the MZHOU adapter, I got 930 Mbps of one-way throughput.
- Then I tested it again, though I had the Pi disconnected from it's wired network connection, and I only got 600-800 Mbps of one-way throughput.
In hindsight, the problem is more obvious—getting 930 Mbps for all the wireless tests when Ethernet was connected should've clued me in earlier. But getting to the point where I could prove what I thought might've happened actually happened took some time.
Testing the MZHOU WiFi/Bluetooth M.2 NGFF adapter
To quickly review the card that I tested for this post—if you want to adapt an M.2 WiFi and bluetooth module you have laying around, maybe from an old broken down laptop or desktop computer, this little MZHOU adapter I tested works a treat!
With that out of the way... let's get back to the substance of this post.
Strange behaviors in the Linux networking stack
Anyways, getting back to the problem I uncovered, I found it odd that both of my WiFi tests got about 930 Mbps when I tested them the first time.
I was using
--bind option, which according to the documentation:
binds to the interface associated with the address
So if I have two interfaces, let's say
eth0 on 192.168.0.5, and
wlan0 on 192.168.0.6, and I wanted to test the
wlan0 interface, I could just use
--bind 192.168.0.6, right?
Well, no, actually.
Linux's kernel networking stack is optimized for getting packets routed in the most efficient manner. Usually, that's a great thing!
But the problem was that the Linux kernel saw that both my wired network and wifi connections were operating on the same network, so it optimized the packet flow for me by routing data through the wired ethernet connection, even though I told iperf3 to bind to the wifi interface.
Long story short, I am not the first person to run into this issue, and I found out about a couple things that I could try to combat the problem, like adjusting the
arp_filter setting to disable the kernel's intelligent routing, or segregating all my network interfaces on their own subnets.
But the easiest thing, in the end, was to disable the network interface I wasn't testing. So before I re-ran my benchmarks, I ran
sudo ip link set eth0 down to disable the onboard gigabit ethernet.
And after doing that, and running more benchmarks, I have to agree with marvell marvell: it is, in fact, impossible to get more than 1.2 gigabits of throughput with the Intel AX200.
My benchmarking showed that throughput reached around 800 Mbps from the Pi to the router, and 1.1 Gbps from the router to the Pi.
Those results are still excellent, and beat any older WiFi device I have in my house right now—including my $3000 MacBook Pro—but my conclusion from the last video that "WiFi is faster than Ethernet" on this Pi isn't entirely true.
Before I move on from that topic, I should mention the maintainers of
iperf3 have been trying to make benchmarking work better with multiple devices on the same network, and there's a bleeding edge option I tested called
--bind-dev, but unfortunately it didn't make a difference on the Raspberry Pi.
In the middle of all this benchmarking, I decided to also dig in and learn more about
wpa_supplicant that we use to control WiFi on our Raspberry Pis, or in Debian in general.
I noticed in my testing that if I had both the internal WiFi interface (
wlan0) and the PCIe interface (
wpa_supplicant would always choose the external interface. And usually that was what I wanted, so I didn't question it. But why did it always choose the external interface?
I realize the number of people with multiple WiFi interfaces on their computers is probably very tiny, but still, the
wpa_supplicant documentation shows nothing about how to specify an interface for a configuration during startup—only how to specify an interface when invoking
Well, after wondering this a long time, inspiration finally struck me after I saw this answer from Hannes on Stack Exchange. His post reminded me of the obvious fact: I'm using Linux. I can figure this out myself!
First I tried finding the wpa_supplicant codebase and documentation, and found the docs through this website... but a lot of the links were either broken or somewhat unhelpful. I couldn't even find a clone of the source on GitHub (though I didn't look too hard). So I switched gears and had more luck searching for the code behind
dhcpcd, which took me to Roy Marple's website (https://roy.marples.name). His site has links to the official code repository, as well as a GitHub mirror that's easier to browse.
Looking around in the code, I noticed the files that load wpa_supplicant on the Raspberry Pi are in the
hooks folder. Specifically, the source for the
wpa_supplicant hook is in this file.
Right at the top, it looks like there's my answer: there's a bash
for loop that looks for a given set of
wpa_supplicant files, starting with a file named after the interface:
for x in \ /etc/wpa_supplicant/wpa_supplicant-"$interface".conf \ /etc/wpa_supplicant/wpa_supplicant.conf \ /etc/wpa_supplicant-"$interface".conf \ /etc/wpa_supplicant.conf \ ; do
If it doesn't find a file with the interface name in it, it goes to the plain config file. If it doesn't find either, it goes up a directory, into et-see, and searches for the same files.
There's still more digging I could do—but my curiosity was satisfied for the time being, and I was itching to get back to some testing with 10 Gbps networking gear.
More testing with the MZHOU and Coral.ai
But it's just a happy accident that I learned a ton about WiFi and Linux networking in the process of testing this boring little adapter!