Skip to content

Commit c822613

Browse files
authored
Merge pull request #1945 from scottlamb/mac-bind-to-device
support `--bind-dev` on macOS
2 parents 333fa40 + 08d4298 commit c822613

11 files changed

Lines changed: 126 additions & 34 deletions

File tree

configure

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17228,6 +17228,49 @@ printf "%s\n" "#define HAVE_SO_BINDTODEVICE 1" >>confdefs.h
1722817228

1722917229
fi
1723017230

17231+
# Check for IP_BOUND_IF sockopt (believed to be macOS only)
17232+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking IP_BOUND_IF socket option" >&5
17233+
printf %s "checking IP_BOUND_IF socket option... " >&6; }
17234+
if test ${iperf3_cv_header_ip_bound_if+y}
17235+
then :
17236+
printf %s "(cached) " >&6
17237+
else case e in #(
17238+
e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
17239+
/* end confdefs.h. */
17240+
#include <netinet/in.h>
17241+
int
17242+
main (void)
17243+
{
17244+
int foo = IP_BOUND_IF;
17245+
;
17246+
return 0;
17247+
}
17248+
_ACEOF
17249+
if ac_fn_c_try_compile "$LINENO"
17250+
then :
17251+
iperf3_cv_header_ip_bound_if=yes
17252+
else case e in #(
17253+
e) iperf3_cv_header_ip_bound_if=no ;;
17254+
esac
17255+
fi
17256+
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
17257+
esac
17258+
fi
17259+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_ip_bound_if" >&5
17260+
printf "%s\n" "$iperf3_cv_header_ip_bound_if" >&6; }
17261+
if test "x$iperf3_cv_header_ip_bound_if" = "xyes"; then
17262+
17263+
printf "%s\n" "#define HAVE_IP_BOUND_IF 1" >>confdefs.h
17264+
17265+
fi
17266+
17267+
if test "x$iperf3_cv_header_so_bindtodevice" = "xyes" -o "x$iperf3_cv_header_ip_bound_if" = "xyes"; then
17268+
17269+
printf "%s\n" "#define CAN_BIND_TO_DEVICE 1" >>confdefs.h
17270+
17271+
fi
17272+
17273+
1723117274
# Check for IP_MTU_DISCOVER (mostly on Linux)
1723217275
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking IP_MTU_DISCOVER socket option" >&5
1723317276
printf %s "checking IP_MTU_DISCOVER socket option... " >&6; }

configure.ac

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,23 @@ if test "x$iperf3_cv_header_so_bindtodevice" = "xyes"; then
289289
AC_DEFINE([HAVE_SO_BINDTODEVICE], [1], [Have SO_BINDTODEVICE sockopt.])
290290
fi
291291

292+
# Check for IP_BOUND_IF sockopt (believed to be macOS only)
293+
AC_CACHE_CHECK([IP_BOUND_IF socket option],
294+
[iperf3_cv_header_ip_bound_if],
295+
AC_COMPILE_IFELSE(
296+
[AC_LANG_PROGRAM([[#include <netinet/in.h>]],
297+
[[int foo = IP_BOUND_IF;]])],
298+
iperf3_cv_header_ip_bound_if=yes,
299+
iperf3_cv_header_ip_bound_if=no))
300+
if test "x$iperf3_cv_header_ip_bound_if" = "xyes"; then
301+
AC_DEFINE([HAVE_IP_BOUND_IF], [1], [Have IP_BOUND_IF sockopt.])
302+
fi
303+
304+
if test "x$iperf3_cv_header_so_bindtodevice" = "xyes" -o "x$iperf3_cv_header_ip_bound_if" = "xyes"; then
305+
AC_DEFINE([CAN_BIND_TO_DEVICE], [1], [Can bind to device via SO_BINDTODEVICE or IP_BOUND_IF.])
306+
fi
307+
308+
292309
# Check for IP_MTU_DISCOVER (mostly on Linux)
293310
AC_CACHE_CHECK([IP_MTU_DISCOVER socket option],
294311
[iperf3_cv_header_ip_mtu_discover],

docs/invoking.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ the executable.
167167
order to set the link-local scope.
168168
169169
--bind-dev dev
170-
Bind to the specified network interface. This option uses
171-
SO_BINDTODEVICE, and may require root permissions. (Available
172-
on Linux and possibly other systems.)
170+
Bind to the specified network interface. This option uses
171+
SO_BINDTODEVICE or IP_BOUND_IF, and may require root permissions.
172+
(Available on Linux and possibly other systems.)
173173
174174
-V, --verbose
175175
Produce more detailed output.

src/iperf3.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ scope.
156156
.TP
157157
.BR --bind-dev " \fIdev\fR"
158158
Bind to the specified network interface.
159-
This option uses SO_BINDTODEVICE, and may require root permissions.
159+
This option uses SO_BINDTODEVICE or IP_BOUND_IF, and may require root permissions.
160160
(Available on Linux and possibly other systems.)
161161
.TP
162162
.BR -V ", " --verbose " "

src/iperf_api.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
#include <sys/cpuset.h>
6161
#endif /* HAVE_CPUSET_SETAFFINITY */
6262

63+
#if defined(HAVE_IP_BOUND_IF)
64+
#include <netinet/in.h>
65+
#endif
66+
6367
#if defined(__CYGWIN__) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
6468
#define CPU_SETSIZE __CPU_SETSIZE
6569
#endif /* __CYGWIN__, _WIN32, _WIN64, __WINDOWS__ */
@@ -1125,9 +1129,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
11251129
{"bidir", no_argument, NULL, OPT_BIDIRECTIONAL},
11261130
{"window", required_argument, NULL, 'w'},
11271131
{"bind", required_argument, NULL, 'B'},
1128-
#if defined(HAVE_SO_BINDTODEVICE)
1132+
#if defined(CAN_BIND_TO_DEVICE)
11291133
{"bind-dev", required_argument, NULL, OPT_BIND_DEV},
1130-
#endif /* HAVE_SO_BINDTODEVICE */
1134+
#endif /* CAN_BIND_TO_DEVICE */
11311135
{"cport", required_argument, NULL, OPT_CLIENT_PORT},
11321136
{"set-mss", required_argument, NULL, 'M'},
11331137
{"no-delay", no_argument, NULL, 'N'},
@@ -1301,16 +1305,16 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
13011305

13021306
char *arg = strdup(optarg);
13031307
if (iperf_parse_hostname(test, arg, &p, &p1)) {
1304-
#if defined(HAVE_SO_BINDTODEVICE)
1308+
#if defined(CAN_BIND_TO_DEVICE)
13051309
/* Get rid of the hostname we saved earlier. */
13061310
free(iperf_get_test_server_hostname(test));
13071311
iperf_set_test_server_hostname(test, p);
13081312
iperf_set_test_bind_dev(test, p1);
1309-
#else /* HAVE_SO_BINDTODEVICE */
1313+
#else /* CAN_BIND_TO_DEVICE */
13101314
free(arg);
13111315
i_errno = IEBINDDEVNOSUPPORT;
13121316
return -1;
1313-
#endif /* HAVE_SO_BINDTODEVICE */
1317+
#endif /* CAN_BIND_TO_DEVICE */
13141318
}
13151319
free(arg);
13161320
break;
@@ -1452,25 +1456,25 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
14521456

14531457
char *arg = strdup(optarg);
14541458
if (iperf_parse_hostname(test, arg, &p, &p1)) {
1455-
#if defined(HAVE_SO_BINDTODEVICE)
1459+
#if defined(CAN_BIND_TO_DEVICE)
14561460
/* Get rid of the hostname we saved earlier. */
14571461
free(iperf_get_test_bind_address(test));
14581462
iperf_set_test_bind_address(test, p);
14591463
iperf_set_test_bind_dev(test, p1);
1460-
#else /* HAVE_SO_BINDTODEVICE */
1464+
#else /* CAN_BIND_TO_DEVICE */
14611465
free(arg);
14621466
i_errno = IEBINDDEVNOSUPPORT;
14631467
return -1;
1464-
#endif /* HAVE_SO_BINDTODEVICE */
1468+
#endif /* CAN_BIND_TO_DEVICE */
14651469
}
14661470
free(arg);
14671471
break;
14681472
}
1469-
#if defined (HAVE_SO_BINDTODEVICE)
1473+
#if defined(CAN_BIND_TO_DEVICE)
14701474
case OPT_BIND_DEV:
14711475
iperf_set_test_bind_dev(test, optarg);
14721476
break;
1473-
#endif /* HAVE_SO_BINDTODEVICE */
1477+
#endif /* CAN_BIND_TO_DEVICE */
14741478
case OPT_CLIENT_PORT:
14751479
portno = atoi(optarg);
14761480
if (portno < 1 || portno > 65535) {

src/iperf_config.h.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/* src/iperf_config.h.in. Generated from configure.ac by autoheader. */
22

3+
/* Can bind to device via SO_BINDTODEVICE or IP_BOUND_IF. */
4+
#undef CAN_BIND_TO_DEVICE
5+
36
/* Define to 1 if you have the 'clock_gettime' function. */
47
#undef HAVE_CLOCK_GETTIME
58

@@ -36,6 +39,9 @@
3639
/* Have MPTCP protocol. */
3740
#undef HAVE_IPPROTO_MPTCP
3841

42+
/* Have IP_BOUND_IF sockopt. */
43+
#undef HAVE_IP_BOUND_IF
44+
3945
/* Have IP_DONTFRAG sockopt. */
4046
#undef HAVE_IP_DONTFRAG
4147

src/iperf_locale.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
108108
" -A, --affinity n[,m] set CPU affinity core number to n (the core the process will use)\n"
109109
" (optional Client only m - the Server's core number for this test)\n"
110110
#endif /* HAVE_CPU_AFFINITY */
111-
#if defined(HAVE_SO_BINDTODEVICE)
111+
#if defined(CAN_BIND_TO_DEVICE)
112112
" -B, --bind <host>[%%<dev>] bind to the interface associated with the address <host>\n"
113113
" (optional <dev> equivalent to `--bind-dev <dev>`)\n"
114114
" --bind-dev <dev> bind to the network interface with SO_BINDTODEVICE\n"
115-
#else /* HAVE_SO_BINDTODEVICE */
115+
#else /* CAN_BIND_TO_DEVICE */
116116
" -B, --bind <host> bind to the interface associated with the address <host>\n"
117-
#endif /* HAVE_SO_BINDTODEVICE */
117+
#endif /* CAN_BIND_TO_DEVICE */
118118
" -V, --verbose more detailed output\n"
119119
" -J, --json output in JSON format\n"
120120
" --json-stream output in line-delimited JSON format\n"

src/iperf_sctp.c

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,7 @@ iperf_sctp_listen(struct iperf_test *test)
209209
}
210210

211211
if (test->bind_dev) {
212-
#if defined(SO_BINDTODEVICE)
213-
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
214-
test->bind_dev, IFNAMSIZ) < 0)
215-
#endif // SO_BINDTODEVICE
216-
{
212+
if (bind_to_device(s, res->ai_family, test->bind_dev) < 0) {
217213
saved_errno = errno;
218214
close(s);
219215
freeaddrinfo(res);
@@ -348,11 +344,7 @@ iperf_sctp_connect(struct iperf_test *test)
348344
}
349345

350346
if (test->bind_dev) {
351-
#if defined(SO_BINDTODEVICE)
352-
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
353-
test->bind_dev, IFNAMSIZ) < 0)
354-
#endif // SO_BINDTODEVICE
355-
{
347+
if (bind_to_device(s, server_res->ai_family, test->bind_dev) < 0) {
356348
saved_errno = errno;
357349
close(s);
358350
freeaddrinfo(local_res);

src/iperf_util.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,15 @@ get_optional_features(void)
317317
numfeatures++;
318318
#endif /* HAVE_SSL */
319319

320-
#if defined(HAVE_SO_BINDTODEVICE)
320+
#if defined(CAN_BIND_TO_DEVICE)
321321
if (numfeatures > 0) {
322322
strncat(features, ", ",
323323
sizeof(features) - strlen(features) - 1);
324324
}
325325
strncat(features, "bind to device",
326326
sizeof(features) - strlen(features) - 1);
327327
numfeatures++;
328-
#endif /* HAVE_SO_BINDTODEVICE */
328+
#endif /* CAN_BIND_TO_DEVICE */
329329

330330
#if defined(HAVE_DONT_FRAGMENT)
331331
if (numfeatures > 0) {

src/net.c

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
#endif
6060
#endif /* HAVE_SENDFILE */
6161

62+
#ifdef HAVE_IP_BOUND_IF
63+
#include <netinet/in.h>
64+
#endif /* HAVE_IP_BOUND_IF */
65+
6266
#ifdef HAVE_POLL_H
6367
#include <poll.h>
6468
#endif /* HAVE_POLL_H */
@@ -125,6 +129,35 @@ timeout_connect(int s, const struct sockaddr *name, socklen_t namelen,
125129
* Copyright: http://swtch.com/libtask/COPYRIGHT
126130
*/
127131

132+
int
133+
bind_to_device(int s, int domain, const char *bind_dev)
134+
{
135+
#if defined(HAVE_SO_BINDTODEVICE)
136+
return setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, bind_dev, IFNAMSIZ);
137+
#elif defined(HAVE_IP_BOUND_IF)
138+
int opt;
139+
switch (domain) {
140+
case IPPROTO_IP:
141+
opt = IP_BOUND_IF;
142+
break;
143+
case IPPROTO_IPV6:
144+
opt = IPV6_BOUND_IF;
145+
break;
146+
default:
147+
errno = ENOTSUP;
148+
return -1;
149+
}
150+
int index = if_nametoindex(bind_dev);
151+
if (index == 0) {
152+
return -1;
153+
}
154+
return setsockopt(s, domain, opt, &index, sizeof(index));
155+
#else
156+
errno = ENOTSUP;
157+
return -1;
158+
#endif
159+
}
160+
128161
/* create a socket */
129162
int
130163
create_socket(int domain, int type, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, struct addrinfo **server_res_out)
@@ -160,11 +193,7 @@ create_socket(int domain, int type, int proto, const char *local, const char *bi
160193
}
161194

162195
if (bind_dev) {
163-
#if defined(HAVE_SO_BINDTODEVICE)
164-
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
165-
bind_dev, IFNAMSIZ) < 0)
166-
#endif // HAVE_SO_BINDTODEVICE
167-
{
196+
if (bind_to_device(s, domain, bind_dev) < 0) {
168197
saved_errno = errno;
169198
close(s);
170199
freeaddrinfo(local_res);

0 commit comments

Comments
 (0)