Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bind to non-primary interface seems to not work #1099

Closed
geerlingguy opened this issue Jan 4, 2021 · 9 comments · Fixed by #1153
Closed

Bind to non-primary interface seems to not work #1099

geerlingguy opened this issue Jan 4, 2021 · 9 comments · Fixed by #1153
Assignees
Labels

Comments

@geerlingguy
Copy link

geerlingguy commented Jan 4, 2021

Context

  • Version of iperf3: master (2021-01-04 commit 21581a7).

  • Hardware: Raspberry Pi Compute Module 4 + IO Board + PCIe Intel AX200 interface

  • Operating system (and distribution, if any): Raspberry Pi OS 64-bit beta (Debian 10.3 base)

  • Other relevant information (for example, non-default compilers,
    libraries, cross-compiling, etc.):

Compiled from source.

Bug Report

Background: My Pi has the following interfaces connected to the same network / subnet:

$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.100.120/24 brd 10.0.100.255 scope global dynamic noprefixroute eth0
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    inet 10.0.100.169/24 brd 10.0.100.255 scope global dynamic noprefixroute wlan0

Basically, eth0 at .120, and wlan0 at .169.

eth0 is connected via wire to my 10 Gbps network (though it's a 1 Gbps device), and normally gets ~930 Mbps at the default packet size (1500 MTU).

wlan0 is connected via WiFi to my WiFi 6 network, and normally gets 600-800 Mbps at the default packet size (1500 MTU). I can disable my onboard Ethernet port (either via sudo ip link set eth0 down, or unplugging it) and get this benchmark result.

My Mac is connected to my 10 Gbps network directly, and is not a limiting factor.

  • Expected Behavior

Using a command like:

sudo ./iperf3 --bind 10.0.100.169 --bind-dev wlan0 -c 10.0.100.144

I had expected this to bind the network traffic for the test to the wlan0 interface/device directly, so the wired network connection would not affect my WiFi benchmarking.

  • Actual Behavior

Running the above command results in the same performance as when testing via the wired connection:

[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.09 GBytes   934 Mbits/sec    0             sender
[  5]   0.00-10.00  sec  1.08 GBytes   932 Mbits/sec                  receiver
  • Steps to Reproduce

See Background above

  • Possible Solution

arp_filter=1 may be used to disable some of the Linux kernel's packet routing that seems to optimize for the fastest (or primary?) interface.

Ideally it would be nice to make iperf3 able to bind to a particular interface directly (e.g. --bind-dev wlan0 and it would take care of everything for me.

@geerlingguy
Copy link
Author

I was referred to this new functionality from #865 (comment)

@bmah888
Copy link
Contributor

bmah888 commented Jan 4, 2021

Thanks for the bug report and hint. That behavior is...not what I would have expected.

@geerlingguy
Copy link
Author

@bmah888 - Ha! Me neither, though I have to admit that I've already learned a lot about kernel networking in the past few weeks and I'm pretty far out of my element—though I promise I can try any patches or PRs and give as good of debugging information as possible :)

@bmah888
Copy link
Contributor

bmah888 commented Jan 25, 2021

I forgot to follow-up on this. I did some testing going from a Raspberry Pi 3 over its Ethernet and WiFi interfaces (on the same subnet) to a Mac Pro running Big Sur. I realize the problem is more generic than this, but the combination of a Raspberry Pi and a Mac is something that I happened to have available and could easily set up.

The basic methodology was to set up the server on the Mac and run the client on the Pi, while doing an instance of tcpdump on the Pi's Ethernet interface. I varied the command-line arguments on the client, with different combinations of --bind and --bind-dev roughly as follows:

(none): See packets on eth0 (Ethernet), with local address 192.168.32.7 (Ethernet IPv4 address)
--bind 192.168.32.182: See packets on eth0, with local address 192.168.32.182 (WiFi IPv4 address)
--bind-dev wlan0: See packets on eth0, with local address 192.168.32.7 (Ethernet IPv4 address)
--bind 192.168.32.182 --bind-dev wlan0: See outbound packets on eth0, local address 192.168.32.182 (WiFi IPv4 address)

Then I set sysctl net.ipv4.conf.eth0.arp_filter=1 and sysctl net.ipv4.conf.wlan0.arp_filter=1 and repeated the tests:

(none): See packets on eth0 (Ethernet), with local address 192.168.32.7 (Ethernet IPv4 address)
--bind 192.168.32.182: See outbound packets on eth0, with local address 192.168.32.182 (WiFi IPv4 address)
--bind-dev wlan0: See inbound packets on eth0, with local address 192.168.32.7 (Ethernet IPv4 address)
--bind 192.168.32.182 --bind-dev wlan0: See outbound packets on eth0, local address 192.168.32.182 (WiFi IPv4 address)

So far I haven't found a combination for this scenario where I can get all of the traffic to go over the WiFi interface (both directions).

@bunder2015
Copy link

bunder2015 commented May 12, 2021

I can't seem to get this to work either unfortunately...

mcbin ~ # ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.2  netmask 255.255.0.0  broadcast 10.0.0.255
        inet6 fe80::251:82ff:fe11:2200  prefixlen 64  scopeid 0x20<link>
        ether 00:51:82:11:22:00  txqueuelen 2048  (Ethernet)
        RX packets 66  bytes 3960 (3.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 88  bytes 6496 (6.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

mcbin ~ # iperf3 -s -B 10.0.0.2 --bind-dev eth0 -p 5200
-----------------------------------------------------------
Server listening on 5200 (test #1)
-----------------------------------------------------------
((nothing))
mcbin ~ # ifconfig eth1
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.1  netmask 255.255.0.0  broadcast 10.0.0.255
        inet6 fe80::251:82ff:fe11:2201  prefixlen 64  scopeid 0x20<link>
        ether 00:51:82:11:22:01  txqueuelen 2048  (Ethernet)
        RX packets 12  bytes 720 (720.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 142  bytes 8768 (8.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

mcbin ~ # iperf3 -c 10.0.0.2 --bind-dev eth1 -p 5200
iperf3: error - unable to connect to server: No route to host

Don't have any firewalls set up yet, and eth0 is directly connected to eth1 (no switches, etc). Tried it in both directions.

I tried arp_filter=1 as well as adding routes that point to the other interface's IP, but no luck. I do see the interface lights blink when it tries to connect to the server, but it just never connects.

@chenshuo
Copy link
Contributor

Looks like iperf_tcp_connect() in iperf_tcp.c doesn't call SO_BINDTODEVICE.

I found following patch worked on my Raspberry Pi 3:

diff --git a/src/iperf_tcp.c b/src/iperf_tcp.c
index c78f4f5..98550f9 100644
--- a/src/iperf_tcp.c
+++ b/src/iperf_tcp.c
@@ -465,6 +465,21 @@ iperf_tcp_connect(struct iperf_test *test)
             return -1;
         }
     }
+    if (test->bind_dev) {
+#if defined(HAVE_SO_BINDTODEVICE)
+        if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
+                       test->bind_dev, IFNAMSIZ) < 0)
+#endif // HAVE_SO_BINDTODEVICE
+        {
+            saved_errno = errno;
+            close(s);
+            // freeaddrinfo(local_res);
+            freeaddrinfo(server_res);
+            errno = saved_errno;
+            i_errno = IESTREAMCONNECT;  // XXX which error code should I use?
+            return -1;
+        }
+    }
 
     /* Set socket options */
     if (test->no_delay) {

Do do mind test it yourself?

Here's my test results, --bind-dev wlan0 works with this patch:

10.0.0.110 is IP for wlan0, 10.0.0.130 is IP for eth0. 10.0.0.42 is a Linux host running iperf3 -s.

Bind to wlan0's IP, but not binding to wlan0 dev

$ ./iperf3 -c 10.0.0.42 --bind 10.0.0.110  
Connecting to host 10.0.0.42, port 5201
[  5] local 10.0.0.110 port 33989 connected to 10.0.0.42 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  11.2 MBytes  93.6 Mbits/sec    0    124 KBytes       
[  5]   1.00-2.00   sec  11.3 MBytes  94.9 Mbits/sec    0    156 KBytes       
[  5]   2.00-3.00   sec  11.4 MBytes  95.4 Mbits/sec    0    163 KBytes       
[  5]   3.00-4.00   sec  11.2 MBytes  93.8 Mbits/sec    0    171 KBytes       
[  5]   4.00-5.00   sec  11.2 MBytes  94.4 Mbits/sec    0    178 KBytes       
[  5]   5.00-6.00   sec  11.2 MBytes  94.4 Mbits/sec    0    178 KBytes       
[  5]   6.00-7.00   sec  11.2 MBytes  93.8 Mbits/sec    0    178 KBytes       
[  5]   7.00-8.00   sec  11.2 MBytes  94.4 Mbits/sec    0    178 KBytes       
[  5]   8.00-9.00   sec  11.2 MBytes  93.8 Mbits/sec    0    300 KBytes       
[  5]   9.00-10.00  sec  11.2 MBytes  93.8 Mbits/sec    0    300 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   112 MBytes  94.2 Mbits/sec    0             sender
[  5]   0.00-10.02  sec   112 MBytes  93.6 Mbits/sec                  receiver

iperf Done.

Also bind to wlan0 dev:

pi@rpi3:~/iperf/src $ ./iperf3 -c 10.0.0.42 --bind 10.0.0.110  --bind-dev wlan0 
Connecting to host 10.0.0.42, port 5201
[  5] local 10.0.0.110 port 55877 connected to 10.0.0.42 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  5.23 MBytes  43.9 Mbits/sec    0    206 KBytes       
[  5]   1.00-2.00   sec  4.47 MBytes  37.5 Mbits/sec    0    206 KBytes       
[  5]   2.00-3.00   sec  3.73 MBytes  31.3 Mbits/sec    0    219 KBytes       
[  5]   3.00-4.00   sec  3.48 MBytes  29.2 Mbits/sec    0    219 KBytes       
[  5]   4.00-5.00   sec  3.98 MBytes  33.4 Mbits/sec    0    230 KBytes       
[  5]   5.00-6.00   sec  4.10 MBytes  34.4 Mbits/sec    0    230 KBytes       
[  5]   6.00-7.00   sec  3.79 MBytes  31.8 Mbits/sec    0    230 KBytes       
[  5]   7.00-8.00   sec  3.54 MBytes  29.7 Mbits/sec    0    230 KBytes       
[  5]   8.00-9.00   sec  3.54 MBytes  29.7 Mbits/sec    0    230 KBytes       
[  5]   9.00-10.00  sec  3.98 MBytes  33.4 Mbits/sec    0    230 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  39.8 MBytes  33.4 Mbits/sec    0             sender
[  5]   0.00-10.04  sec  39.3 MBytes  32.9 Mbits/sec                  receiver

iperf Done.

For the record, I also set net.ipv4.conf.all.arp_announce=2 and net.ipv4.conf.all.arp_ignore=1, so that the incoming packets go to wlan0 instead of eth0.

$ ./iperf3 -c 10.0.0.42 --bind 10.0.0.110 -R
Connecting to host 10.0.0.42, port 5201
Reverse mode, remote host 10.0.0.42 is sending
[  5] local 10.0.0.110 port 60643 connected to 10.0.0.42 port 5201
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec  6.82 MBytes  57.2 Mbits/sec                  
[  5]   1.00-2.00   sec  6.75 MBytes  56.7 Mbits/sec                  
[  5]   2.00-3.00   sec  6.99 MBytes  58.6 Mbits/sec                  
[  5]   3.00-4.00   sec  6.96 MBytes  58.4 Mbits/sec                  
[  5]   4.00-5.00   sec  6.80 MBytes  57.1 Mbits/sec                  
[  5]   5.00-6.00   sec  6.92 MBytes  58.0 Mbits/sec                  
[  5]   6.00-7.00   sec  6.92 MBytes  58.1 Mbits/sec                  
[  5]   7.00-8.00   sec  6.81 MBytes  57.1 Mbits/sec                  
[  5]   8.00-9.00   sec  6.68 MBytes  56.0 Mbits/sec                  
[  5]   9.00-10.00  sec  6.61 MBytes  55.5 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.01  sec  71.8 MBytes  60.1 Mbits/sec    0             sender
[  5]   0.00-10.00  sec  68.3 MBytes  57.3 Mbits/sec                  receiver

iperf Done.

Bind to eth0's IP:

$ ./iperf3 -c 10.0.0.42 --bind 10.0.0.130 -R
Connecting to host 10.0.0.42, port 5201
Reverse mode, remote host 10.0.0.42 is sending
[  5] local 10.0.0.130 port 36959 connected to 10.0.0.42 port 5201
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec  11.3 MBytes  94.5 Mbits/sec                  
[  5]   1.00-2.00   sec  11.2 MBytes  94.2 Mbits/sec                  
[  5]   2.00-3.00   sec  11.2 MBytes  94.1 Mbits/sec                  
[  5]   3.00-4.00   sec  11.2 MBytes  94.2 Mbits/sec                  
[  5]   4.00-5.00   sec  11.2 MBytes  94.1 Mbits/sec                  
[  5]   5.00-6.00   sec  11.2 MBytes  93.7 Mbits/sec                  
[  5]   6.00-7.00   sec  11.3 MBytes  94.6 Mbits/sec                  
[  5]   7.00-8.00   sec  11.2 MBytes  94.2 Mbits/sec                  
[  5]   8.00-9.00   sec  11.2 MBytes  94.2 Mbits/sec                  
[  5]   9.00-10.00  sec  11.2 MBytes  94.1 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.01  sec   113 MBytes  94.5 Mbits/sec   51             sender
[  5]   0.00-10.00  sec   112 MBytes  94.2 Mbits/sec                  receiver

iperf Done.

@bunder2015
Copy link

Thanks for the patch, I was able to get it to compile, but haven't had time to test it yet. Hopefully I should have some time to test on Friday.

chenshuo added a commit to chenshuo/iperf that referenced this issue May 27, 2021
netdial() honors --bind-dev option but iperf_tcp_connect() doesn't,
as a result, only the control socket is bound to the device, but not
the data socket.

Instead of duplicaing code from netdial to iperf_tcp_connect(), this
fix extracts a common util function create_socket() from netdial() and
let iperf_tcp_connect() call create_socket() to create a socket with
optional bindings.

Tested on Raspberry Pi 3 with eth0 and wlan0.
@chenshuo
Copy link
Contributor

I send a better patch in #1153, which doesn't duplicate code.

dsahern added a commit to dsahern/iperf that referenced this issue Aug 2, 2021
esnet#1153

From: Shuo Chen <chenshuo@chenshuo.com>
Date: Thu, 27 May 2021 14:24:07 -0700
Subject: [PATCH] Fix --bind-dev options for TCP streams. (esnet#1099)

netdial() honors --bind-dev option but iperf_tcp_connect() doesn't,
as a result, only the control socket is bound to the device, but not
the data socket.

Instead of duplicaing code from netdial to iperf_tcp_connect(), this
fix extracts a common util function create_socket() from netdial() and
let iperf_tcp_connect() call create_socket() to create a socket with
optional bindings.

Tested on Raspberry Pi 3 with eth0 and wlan0.

Signed-off-by: David Ahern <dsahern@gmail.com>
@Bankst
Copy link

Bankst commented Oct 26, 2021

I built and tested this myself on a Jetson Nano with an Intel 8265NGW, and can confirm that binding works properly now

dsahern added a commit to dsahern/iperf that referenced this issue Oct 31, 2021
esnet#1153

From: Shuo Chen <chenshuo@chenshuo.com>
Date: Thu, 27 May 2021 14:24:07 -0700
Subject: [PATCH] Fix --bind-dev options for TCP streams. (esnet#1099)

netdial() honors --bind-dev option but iperf_tcp_connect() doesn't,
as a result, only the control socket is bound to the device, but not
the data socket.

Instead of duplicaing code from netdial to iperf_tcp_connect(), this
fix extracts a common util function create_socket() from netdial() and
let iperf_tcp_connect() call create_socket() to create a socket with
optional bindings.

Tested on Raspberry Pi 3 with eth0 and wlan0.

Signed-off-by: David Ahern <dsahern@gmail.com>
@bmah888 bmah888 linked a pull request Nov 12, 2021 that will close this issue
bmah888 added a commit that referenced this issue Nov 12, 2021
Fix --bind-dev options for TCP streams. (#1099)
@bmah888 bmah888 self-assigned this Nov 12, 2021
@bmah888 bmah888 added the bug label Nov 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants