Contents
Problem
Your laptop is connected via Ethernet (e.g. through a dock) and Wi-Fi at the same time, and both connections are in the same network. IPv4 transfers are much slower than the wired link should allow, or the speed varies a lot.
Typical signs:
-
ip route get <remote-ip>shows the wired interface, but the interface counters show that most of the downloaded data arrives on the Wi-Fi interface. - Turning Wi-Fi off makes the transfer fast immediately.
- Right after turning Wi-Fi off, new connections may hang for a short time.
- IPv6 traffic is usually not affected.
To check which interface actually receives the data, compare the receive counters before and after
a download:
cat /sys/class/net/<wired-if>/statistics/rx_bytes /sys/class/net/<wifi-if>/statistics/rx_bytes
You can see the cause directly by capturing ARP traffic for the IP address of your wired interface:
sudo tcpdump -eni any 'arp and host <wired-ip>'
If the problem is present, both interfaces answer the router's request, and the Wi-Fi interface
answers with its own MAC address:
<wired-if> Out ... Reply <wired-ip> is-at <wired-mac>
<wifi-if> Out ... Reply <wired-ip> is-at <wifi-mac>
Cause
By default, Linux answers ARP requests for any of its local IP addresses on any interface (arp_ignore=0). When both interfaces are in the same network, they both receive the router's ARP request for the wired IP address, and both reply. The router stores the reply it receives last. That is usually the one sent over Wi-Fi, because Wi-Fi is slower to deliver it. As a result, outgoing traffic leaves through the cable, but incoming traffic comes back over Wi-Fi.
Really?!!
Yes, it's called the weak end system model and it's allowed in RFC 1122 (from 1989).
Solution
Configure the kernel so that each interface answers ARP requests only for its own IP address, and uses only its own IP address as the sender in ARP requests:
# Save the current values
sysctl net.ipv4.conf.all.arp_ignore net.ipv4.conf.all.arp_announce | tee ~/arp-sysctl-before.txt
# Apply immediately
sudo sysctl -w net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2
# Keep the setting after a reboot
printf 'net.ipv4.conf.all.arp_ignore = 1\nnet.ipv4.conf.all.arp_announce = 2\n' \
| sudo tee /etc/sysctl.d/60-arp-flux.conf
For both settings, the kernel uses the higher of the value for all and the value for the interface. Setting all is therefore enough, and it also covers interfaces that appear later, e.g. when you plug in a dock.
The router may still have the Wi-Fi MAC address stored for a short while. It is replaced the next time the router refreshes its ARP entry, usually within seconds to a few minutes.
Revert
sudo rm /etc/sysctl.d/60-arp-flux.conf
sudo sysctl -w net.ipv4.conf.all.arp_ignore=0 net.ipv4.conf.all.arp_announce=0
Use the values from ~/arp-sysctl-before.txt if they were not 0.