Skip to content

Commit

Permalink
enh: Support SO_BINDTODEVICE
Browse files Browse the repository at this point in the history
This lets iperf work better with multi-homed machines and
VRF.

Towards #1089.

Based on a patch by Ben Greear <greearb@candelatech.com> via PR #817.
  • Loading branch information
greearb authored and bmah888 committed Dec 21, 2020
1 parent d1260e6 commit 776ee2a
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/iperf.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
# define _GNU_SOURCE
#endif
#include <netinet/tcp.h>
#include <net/if.h> // for IFNAMSIZ

#if defined(HAVE_CPUSET_SETAFFINITY)
#include <sys/param.h>
Expand Down Expand Up @@ -258,6 +259,7 @@ struct iperf_test
char *server_hostname; /* -c option */
char *tmp_template;
char *bind_address; /* first -B option */
char *bind_dev; /* bind to network device */
TAILQ_HEAD(xbind_addrhead, xbind_entry) xbind_addrs; /* all -X opts */
int bind_port; /* --cport option */
int server_port;
Expand Down
4 changes: 4 additions & 0 deletions src/iperf3.1
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ CPUs).
.TP
.BR -B ", " --bind " \fIhost\fR"
bind to the specific interface associated with address \fIhost\fR.
.BR --bind-dev " \fIdev\R"
bind to the specified network interface.
This option uses SO_BINDTODEVICE, and may require root permissions.
(Available on Linux and possibly other systems.)
.TP
.BR -V ", " --verbose " "
give more detailed output
Expand Down
18 changes: 18 additions & 0 deletions src/iperf_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,12 @@ iperf_get_test_bind_address(struct iperf_test *ipt)
return ipt->bind_address;
}

char *
iperf_get_test_bind_dev(struct iperf_test *ipt)
{
return ipt->bind_dev;
}

int
iperf_get_test_udp_counters_64bit(struct iperf_test *ipt)
{
Expand Down Expand Up @@ -661,6 +667,12 @@ iperf_set_test_bind_address(struct iperf_test *ipt, const char *bnd_address)
ipt->bind_address = strdup(bnd_address);
}

void
iperf_set_test_bind_dev(struct iperf_test *ipt, char *bnd_dev)
{
ipt->bind_dev = strdup(bnd_dev);
}

void
iperf_set_test_udp_counters_64bit(struct iperf_test *ipt, int udp_counters_64bit)
{
Expand Down Expand Up @@ -894,6 +906,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
{"bidir", no_argument, NULL, OPT_BIDIRECTIONAL},
{"window", required_argument, NULL, 'w'},
{"bind", required_argument, NULL, 'B'},
{"bind-dev", required_argument, NULL, OPT_BIND_DEV},
{"cport", required_argument, NULL, OPT_CLIENT_PORT},
{"set-mss", required_argument, NULL, 'M'},
{"no-delay", no_argument, NULL, 'N'},
Expand Down Expand Up @@ -1147,6 +1160,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
case 'B':
test->bind_address = strdup(optarg);
break;
case OPT_BIND_DEV:
test->bind_dev = strdup(optarg);
break;
case OPT_CLIENT_PORT:
portno = atoi(optarg);
if (portno < 1 || portno > 65535) {
Expand Down Expand Up @@ -2635,6 +2651,8 @@ iperf_free_test(struct iperf_test *test)
free(test->tmp_template);
if (test->bind_address)
free(test->bind_address);
if (test->bind_dev)
free(test->bind_dev);
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
struct xbind_entry *xbe;

Expand Down
2 changes: 2 additions & 0 deletions src/iperf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ typedef uint64_t iperf_size_t;
#define OPT_SERVER_BITRATE_LIMIT 21
#define OPT_TIMESTAMPS 22
#define OPT_SERVER_SKEW_THRESHOLD 23
#define OPT_BIND_DEV 24

/* states */
#define TEST_START 1
Expand Down Expand Up @@ -408,6 +409,7 @@ enum {
IESETPACING= 140, // Unable to set socket pacing rate
IESETBUF2= 141, // Socket buffer size incorrect (written value != read value)
IEAUTHTEST = 142, // Test authorization failed
IEBINDDEV = 143, // Unable to bind-to-device (check perror, maybe permissions?)
/* Stream errors */
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)
Expand Down
2 changes: 1 addition & 1 deletion src/iperf_client_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ iperf_connect(struct iperf_test *test)
/* Create and connect the control channel */
if (test->ctrl_sck < 0)
// Create the control channel using an ephemeral port
test->ctrl_sck = netdial(test->settings->domain, Ptcp, test->bind_address, 0, test->server_hostname, test->server_port, test->settings->connect_timeout);
test->ctrl_sck = netdial(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, 0, test->server_hostname, test->server_port, test->settings->connect_timeout);
if (test->ctrl_sck < 0) {
i_errno = IECONNECT;
return -1;
Expand Down
4 changes: 4 additions & 0 deletions src/iperf_locale.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
" -A, --affinity n/n,m set CPU affinity\n"
#endif /* HAVE_CPU_AFFINITY */
" -B, --bind <host> bind to the interface associated with the address <host>\n"
" --bind-dev <dev> bind to the network interface with SO_BINDTODEVICE\n"
" -V, --verbose more detailed output\n"
" -J, --json output in JSON format\n"
" --logfile f send output to a log file\n"
Expand Down Expand Up @@ -233,6 +234,9 @@ const char client_port[] =
const char bind_address[] =
"Binding to local address %s\n";

const char bind_dev[] =
"Binding to local network device %s\n";

const char bind_port[] =
"Binding to local port %s\n";

Expand Down
1 change: 1 addition & 0 deletions src/iperf_locale.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extern const char seperator_line[];
extern const char server_port[] ;
extern const char client_port[] ;
extern const char bind_address[] ;
extern const char bind_dev[] ;
extern const char multicast_ttl[] ;
extern const char join_multicast[] ;
extern const char client_datagram_size[] ;
Expand Down
36 changes: 33 additions & 3 deletions src/iperf_sctp.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,21 @@ iperf_sctp_listen(struct iperf_test *test)
}
}

if (test->bind_dev) {
#ifdef SO_BINDTODEVICE
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif
{
saved_errno = errno;
close(s);
freeaddrinfo(res);
i_errno = IEBINDDEV;
errno = saved_errno;
return -1;
}
}

#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
if (res->ai_family == AF_INET6 && (test->settings->domain == AF_UNSPEC ||
test->settings->domain == AF_INET6)) {
Expand Down Expand Up @@ -284,7 +299,7 @@ iperf_sctp_connect(struct iperf_test *test)
#if defined(HAVE_SCTP_H)
int s, opt, saved_errno;
char portstr[6];
struct addrinfo hints, *local_res, *server_res;
struct addrinfo hints, *local_res = NULL, *server_res = NULL;

if (test->bind_address) {
memset(&hints, 0, sizeof(hints));
Expand All @@ -309,8 +324,7 @@ iperf_sctp_connect(struct iperf_test *test)

s = socket(server_res->ai_family, SOCK_STREAM, IPPROTO_SCTP);
if (s < 0) {
if (test->bind_address)
freeaddrinfo(local_res);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
i_errno = IESTREAMCONNECT;
return -1;
Expand All @@ -336,6 +350,22 @@ iperf_sctp_connect(struct iperf_test *test)
}
}

if (test->bind_dev) {
#ifdef SO_BINDTODEVICE
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif
{
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
i_errno = IEBINDDEV;
errno = saved_errno;
return -1;
}
}

/*
* Various ways to bind the local end of the connection.
* 1. --bind (with or without --cport).
Expand Down
4 changes: 2 additions & 2 deletions src/iperf_server_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ int
iperf_server_listen(struct iperf_test *test)
{
retry:
if((test->listener = netannounce(test->settings->domain, Ptcp, test->bind_address, test->server_port)) < 0) {
if((test->listener = netannounce(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
if (errno == EAFNOSUPPORT && (test->settings->domain == AF_INET6 || test->settings->domain == AF_UNSPEC)) {
/* If we get "Address family not supported by protocol", that
** probably means we were compiled with IPv6 but the running
Expand Down Expand Up @@ -609,7 +609,7 @@ iperf_run_server(struct iperf_test *test)
FD_CLR(test->listener, &test->read_set);
close(test->listener);
test->listener = 0;
if ((s = netannounce(test->settings->domain, Ptcp, test->bind_address, test->server_port)) < 0) {
if ((s = netannounce(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
cleanup_server(test);
i_errno = IELISTEN;
return -1;
Expand Down
6 changes: 3 additions & 3 deletions src/iperf_udp.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ iperf_udp_accept(struct iperf_test *test)
/*
* Create a new "listening" socket to replace the one we were using before.
*/
test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->server_port);
test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port);
if (test->prot_listener < 0) {
i_errno = IESTREAMLISTEN;
return -1;
Expand Down Expand Up @@ -472,7 +472,7 @@ iperf_udp_listen(struct iperf_test *test)
{
int s;

if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->server_port)) < 0) {
if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
i_errno = IESTREAMLISTEN;
return -1;
}
Expand All @@ -499,7 +499,7 @@ iperf_udp_connect(struct iperf_test *test)
int rc;

/* Create and bind our local socket. */
if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) {
if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) {
i_errno = IESTREAMCONNECT;
return -1;
}
Expand Down
1 change: 1 addition & 0 deletions src/libiperf.3
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Setting test parameters:
.nf
void iperf_set_test_role( struct iperf_test *pt, char role );
void iperf_set_test_bind_address( struct iperf_test *t, char *bind_address );
void iperf_set_test_bind_dev( struct iperf_test *t, char *bind_dev );
void iperf_set_test_server_hostname( struct iperf_test *t, char *server_host );
void iperf_set_test_server_port( struct iperf_test *t, int server_port );
void iperf_set_test_duration( struct iperf_test *t, int duration );
Expand Down
36 changes: 33 additions & 3 deletions src/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#include <poll.h>
#endif /* HAVE_POLL_H */

#include "iperf.h"
#include "iperf_util.h"
#include "net.h"
#include "timer.h"
Expand Down Expand Up @@ -121,9 +122,9 @@ timeout_connect(int s, const struct sockaddr *name, socklen_t namelen,

/* make connection to server */
int
netdial(int domain, int proto, const char *local, int local_port, const char *server, int port, int timeout)
netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout)
{
struct addrinfo hints, *local_res, *server_res;
struct addrinfo hints, *local_res = NULL, *server_res = NULL;
int s, saved_errno;

if (local) {
Expand All @@ -148,6 +149,21 @@ netdial(int domain, int proto, const char *local, int local_port, const char *se
return -1;
}

if (bind_dev) {
#ifdef SO_BINDTODEVICE
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
bind_dev, IFNAMSIZ) < 0)
#endif
{
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
errno = saved_errno;
return -1;
}
}

/* Bind the local address if given a name (with or without --cport) */
if (local) {
if (local_port) {
Expand Down Expand Up @@ -218,7 +234,7 @@ netdial(int domain, int proto, const char *local, int local_port, const char *se
/***************************************************************/

int
netannounce(int domain, int proto, const char *local, int port)
netannounce(int domain, int proto, const char *local, const char *bind_dev, int port)
{
struct addrinfo hints, *res;
char portstr[6];
Expand Down Expand Up @@ -255,6 +271,20 @@ netannounce(int domain, int proto, const char *local, int port)
return -1;
}

if (bind_dev) {
#ifdef SO_BINDTODEVICE
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
bind_dev, IFNAMSIZ) < 0)
#endif
{
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
}

opt = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(char *) &opt, sizeof(opt)) < 0) {
Expand Down
4 changes: 2 additions & 2 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
#define __NET_H

int timeout_connect(int s, const struct sockaddr *name, socklen_t namelen, int timeout);
int netdial(int domain, int proto, const char *local, int local_port, const char *server, int port, int timeout);
int netannounce(int domain, int proto, const char *local, int port);
int netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout);
int netannounce(int domain, int proto, const char *local, const char *bind_dev, int port);
int Nread(int fd, char *buf, size_t count, int prot);
int Nwrite(int fd, const char *buf, size_t count, int prot) /* __attribute__((hot)) */;
int has_sendfile(void);
Expand Down

0 comments on commit 776ee2a

Please sign in to comment.