Skip to content

Commit

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

Fixes esnet#1089.

Based on a patch by Ben Greear <greearb@candelatech.com> via PR esnet#817.

Co-authored-by: Ben Greear <greearb@candelatech.com>
  • Loading branch information
2 people authored and hanvari committed Jul 3, 2021
1 parent e613604 commit 35eb7e7
Show file tree
Hide file tree
Showing 21 changed files with 182 additions and 34 deletions.
6 changes: 4 additions & 2 deletions Makefile.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Makefile.in generated by automake 1.16.2 from Makefile.am.
# Makefile.in generated by automake 1.16.3 from Makefile.am.
# @configure_input@

# Copyright (C) 1994-2020 Free Software Foundation, Inc.
Expand Down Expand Up @@ -204,6 +204,8 @@ am__relativize = \
DIST_ARCHIVES = $(distdir).tar.gz
GZIP_ENV = --best
DIST_TARGETS = dist-gzip
# Exists only to be overridden by the user if desired.
AM_DISTCHECK_DVI_TARGET = dvi
distuninstallcheck_listfiles = find . -type f -print
am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
| sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
Expand Down Expand Up @@ -629,7 +631,7 @@ distcheck: dist
$(DISTCHECK_CONFIGURE_FLAGS) \
--srcdir=../.. --prefix="$$dc_install_base" \
&& $(MAKE) $(AM_MAKEFLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) dvi \
&& $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \
&& $(MAKE) $(AM_MAKEFLAGS) check \
&& $(MAKE) $(AM_MAKEFLAGS) install \
&& $(MAKE) $(AM_MAKEFLAGS) installcheck \
Expand Down
13 changes: 4 additions & 9 deletions aclocal.m4
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# generated automatically by aclocal 1.16.2 -*- Autoconf -*-
# generated automatically by aclocal 1.16.3 -*- Autoconf -*-

# Copyright (C) 1996-2020 Free Software Foundation, Inc.

Expand Down Expand Up @@ -9059,7 +9059,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.16'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro.
m4_if([$1], [1.16.2], [],
m4_if([$1], [1.16.3], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
])
Expand All @@ -9075,7 +9075,7 @@ m4_define([_AM_AUTOCONF_VERSION], [])
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
[AM_AUTOMAKE_VERSION([1.16.2])dnl
[AM_AUTOMAKE_VERSION([1.16.3])dnl
m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
Expand Down Expand Up @@ -9763,12 +9763,7 @@ AC_DEFUN([AM_MISSING_HAS_RUN],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
AC_REQUIRE_AUX_FILE([missing])dnl
if test x"${MISSING+set}" != xset; then
case $am_aux_dir in
*\ * | *\ *)
MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
*)
MISSING="\${SHELL} $am_aux_dir/missing" ;;
esac
MISSING="\${SHELL} '$am_aux_dir/missing'"
fi
# Use eval to expand $SHELL
if eval "$MISSING --is-lightweight"; then
Expand Down
38 changes: 32 additions & 6 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -2531,12 +2531,7 @@ program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
am_aux_dir=`cd "$ac_aux_dir" && pwd`

if test x"${MISSING+set}" != xset; then
case $am_aux_dir in
*\ * | *\ *)
MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
*)
MISSING="\${SHELL} $am_aux_dir/missing" ;;
esac
MISSING="\${SHELL} '$am_aux_dir/missing'"
fi
# Use eval to expand $SHELL
if eval "$MISSING --is-lightweight"; then
Expand Down Expand Up @@ -13950,6 +13945,37 @@ $as_echo "#define HAVE_SO_MAX_PACING_RATE 1" >>confdefs.h

fi

# Check for SO_BINDTODEVICE sockopt (believed to be Linux only)
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking SO_BINDTODEVICE socket option" >&5
$as_echo_n "checking SO_BINDTODEVICE socket option... " >&6; }
if ${iperf3_cv_header_so_bindtodevice+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <sys/socket.h>
#ifdef SO_BINDTODEVICE
yes
#endif
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "yes" >/dev/null 2>&1; then :
iperf3_cv_header_so_bindtodevice=yes
else
iperf3_cv_header_so_bindtodevice=no
fi
rm -f conftest*

fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_so_bindtodevice" >&5
$as_echo "$iperf3_cv_header_so_bindtodevice" >&6; }
if test "x$iperf3_cv_header_so_bindtodevice" = "xyes"; then

$as_echo "#define HAVE_SO_BINDTODEVICE 1" >>confdefs.h

fi

# Check if we need -lrt for clock_gettime
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
$as_echo_n "checking for library containing clock_gettime... " >&6; }
Expand Down
13 changes: 13 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,19 @@ if test "x$iperf3_cv_header_so_max_pacing_rate" = "xyes"; then
AC_DEFINE([HAVE_SO_MAX_PACING_RATE], [1], [Have SO_MAX_PACING_RATE sockopt.])
fi

# Check for SO_BINDTODEVICE sockopt (believed to be Linux only)
AC_CACHE_CHECK([SO_BINDTODEVICE socket option],
[iperf3_cv_header_so_bindtodevice],
AC_EGREP_CPP(yes,
[#include <sys/socket.h>
#ifdef SO_BINDTODEVICE
yes
#endif
],iperf3_cv_header_so_bindtodevice=yes,iperf3_cv_header_so_bindtodevice=no))
if test "x$iperf3_cv_header_so_bindtodevice" = "xyes"; then
AC_DEFINE([HAVE_SO_BINDTODEVICE], [1], [Have SO_BINDTODEVICE sockopt.])
fi

# Check if we need -lrt for clock_gettime
AC_SEARCH_LIBS(clock_gettime, [rt posix4])
# Check for clock_gettime support
Expand Down
2 changes: 1 addition & 1 deletion examples/Makefile.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Makefile.in generated by automake 1.16.2 from Makefile.am.
# Makefile.in generated by automake 1.16.3 from Makefile.am.
# @configure_input@

# Copyright (C) 1994-2020 Free Software Foundation, Inc.
Expand Down
5 changes: 3 additions & 2 deletions src/Makefile.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Makefile.in generated by automake 1.16.2 from Makefile.am.
# Makefile.in generated by automake 1.16.3 from Makefile.am.
# @configure_input@

# Copyright (C) 1994-2020 Free Software Foundation, Inc.
Expand Down Expand Up @@ -479,6 +479,7 @@ am__set_TESTS_bases = \
bases='$(TEST_LOGS)'; \
bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
bases=`echo $$bases`
AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
RECHECK_LOGS = $(TEST_LOGS)
AM_RECURSIVE_TARGETS = check recheck
TEST_SUITE_LOG = test-suite.log
Expand Down Expand Up @@ -1565,7 +1566,7 @@ $(TEST_SUITE_LOG): $(TEST_LOGS)
test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
fi; \
echo "$${col}$$br$${std}"; \
echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
echo "$${col}$$br$${std}"; \
create_testsuite_report --maybe-color; \
echo "$$col$$br$$std"; \
Expand Down
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
22 changes: 22 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,9 @@ 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'},
#if defined(HAVE_SO_BINDTODEVICE)
{"bind-dev", required_argument, NULL, OPT_BIND_DEV},
#endif /* HAVE_SO_BINDTODEVICE */
{"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 +1162,11 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
case 'B':
test->bind_address = strdup(optarg);
break;
#if defined (HAVE_SO_BINDTODEVICE)
case OPT_BIND_DEV:
test->bind_dev = strdup(optarg);
break;
#endif /* HAVE_SO_BINDTODEVICE */
case OPT_CLIENT_PORT:
portno = atoi(optarg);
if (portno < 1 || portno > 65535) {
Expand Down Expand Up @@ -2635,6 +2655,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 @@ -351,7 +351,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
3 changes: 3 additions & 0 deletions src/iperf_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
/* Define to 1 if you have the `SetProcessAffinityMask' function. */
#undef HAVE_SETPROCESSAFFINITYMASK

/* Have SO_BINDTODEVICE sockopt. */
#undef HAVE_SO_BINDTODEVICE

/* Have SO_MAX_PACING_RATE sockopt. */
#undef HAVE_SO_MAX_PACING_RATE

Expand Down
6 changes: 6 additions & 0 deletions src/iperf_locale.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ 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"
#if defined(HAVE_SO_BINDTODEVICE)
" --bind-dev <dev> bind to the network interface with SO_BINDTODEVICE\n"
#endif /* HAVE_SO_BINDTODEVICE */
" -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 +236,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) {
#if defined(SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif // SO_BINDTODEVICE
{
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) {
#if defined(SO_BINDTODEVICE)
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
test->bind_dev, IFNAMSIZ) < 0)
#endif // SO_BINDTODEVICE
{
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
Loading

0 comments on commit 35eb7e7

Please sign in to comment.