diff --git a/src/iperf.h b/src/iperf.h index a614b4d33..c6354617d 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -40,6 +40,7 @@ # define _GNU_SOURCE #endif #include +#include // for IFNAMSIZ #if defined(HAVE_CPUSET_SETAFFINITY) #include @@ -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; diff --git a/src/iperf3.1 b/src/iperf3.1 index 879a2c13f..1c3308059 100644 --- a/src/iperf3.1 +++ b/src/iperf3.1 @@ -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 diff --git a/src/iperf_api.c b/src/iperf_api.c index d6997b69e..893c52c90 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -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) { @@ -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) { @@ -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'}, @@ -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) { @@ -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; diff --git a/src/iperf_api.h b/src/iperf_api.h index 5d71e7983..905568e2f 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -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 @@ -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) diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index 59bebd70a..4f83f7615 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -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; diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 60f1b9cd5..e33fe6670 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -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 bind to the interface associated with the address \n" + " --bind-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" @@ -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"; diff --git a/src/iperf_locale.h b/src/iperf_locale.h index 15bdcdb0b..47cf416ae 100644 --- a/src/iperf_locale.h +++ b/src/iperf_locale.h @@ -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[] ; diff --git a/src/iperf_sctp.c b/src/iperf_sctp.c index e0c1ec111..b13478470 100644 --- a/src/iperf_sctp.c +++ b/src/iperf_sctp.c @@ -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)) { @@ -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)); @@ -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; @@ -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). diff --git a/src/iperf_server_api.c b/src/iperf_server_api.c index da91ee250..34662f4e7 100644 --- a/src/iperf_server_api.c +++ b/src/iperf_server_api.c @@ -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 @@ -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; diff --git a/src/iperf_udp.c b/src/iperf_udp.c index 2fd7bf5de..126cd639f 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -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; @@ -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; } @@ -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; } diff --git a/src/libiperf.3 b/src/libiperf.3 index e88516d86..a91e882c7 100644 --- a/src/libiperf.3 +++ b/src/libiperf.3 @@ -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 ); diff --git a/src/net.c b/src/net.c index 8fde9c38d..26ad7827d 100644 --- a/src/net.c +++ b/src/net.c @@ -61,6 +61,7 @@ #include #endif /* HAVE_POLL_H */ +#include "iperf.h" #include "iperf_util.h" #include "net.h" #include "timer.h" @@ -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) { @@ -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) { @@ -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]; @@ -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) { diff --git a/src/net.h b/src/net.h index 80a2161b6..44c0d7efe 100644 --- a/src/net.h +++ b/src/net.h @@ -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);