diff --git a/Makefile.am b/Makefile.am index 0b8e2e8..a8247f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,7 @@ AM_CFLAGS = @GLIB_CFLAGS@ @SLIRP_CFLAGS@ @LIBCAP_CFLAGS@ @LIBSECCOMP_CFLAGS@ noinst_LIBRARIES = libparson.a AM_TESTS_ENVIRONMENT = PATH="$(abs_top_builddir):$(PATH)" -TESTS = tests/test-slirp4netns.sh tests/test-slirp4netns-configure.sh tests/test-slirp4netns-exit-fd.sh tests/test-slirp4netns-ready-fd.sh tests/test-slirp4netns-api-socket.sh tests/test-slirp4netns-disable-host-loopback.sh tests/test-slirp4netns-cidr.sh +TESTS = tests/test-slirp4netns.sh tests/test-slirp4netns-configure.sh tests/test-slirp4netns-exit-fd.sh tests/test-slirp4netns-ready-fd.sh tests/test-slirp4netns-api-socket.sh tests/test-slirp4netns-disable-host-loopback.sh tests/test-slirp4netns-cidr.sh tests/test-slirp4netns-outbound-addr.sh tests/test-slirp4netns-disable-dns.sh EXTRA_DIST = \ slirp4netns.1.md \ diff --git a/main.c b/main.c index 2272368..331cda1 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ #include #include #include "slirp4netns.h" +#include #define DEFAULT_MTU (1500) #define DEFAULT_CIDR ("10.0.2.0/24") @@ -271,6 +272,19 @@ static int parent(int sock, int ready_fd, int exit_fd, const char *api_socket, if (api_socket != NULL) { printf("* API Socket: %s\n", api_socket); } +#if SLIRP_CONFIG_VERSION_MAX >= 2 + if (cfg->enable_outbound_addr) { + printf("* Outbound IPv4: %s\n", + inet_ntoa(cfg->outbound_addr.sin_addr)); + } + if (cfg->enable_outbound_addr6) { + char str[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &cfg->outbound_addr6.sin6_addr, str, + INET6_ADDRSTRLEN) != NULL) { + printf("* Outbound IPv6: %s\n", str); + } + } +#endif if (!cfg->disable_host_loopback) { printf( "WARNING: 127.0.0.1:* on the host is accessible as %s (set " @@ -332,6 +346,17 @@ static void usage(const char *argv0) "caps except CAP_NET_BIND_SERVICE if running as the root)\n"); printf("--enable-seccomp enable seccomp to limit syscalls " "(experimental)\n"); + /* v1.1.0 */ +#if SLIRP_CONFIG_VERSION_MAX >= 2 + printf("--outbound-addr=IPv4 sets outbound ipv4 address to bound to " + "(experimental)\n"); + printf("--outbound-addr6=IPv6 sets outbound ipv6 address to bound to " + "(experimental)\n"); +#endif +#if SLIRP_CONFIG_VERSION_MAX >= 3 + printf("--disable-dns disables 10.0.2.3 (or configured internal " + "ip) to host dns redirect (experimental)\n"); +#endif /* others */ printf("-h, --help show this help and exit\n"); printf("-v, --version show version and exit\n"); @@ -345,24 +370,28 @@ static void version() printf("commit: %s\n", COMMIT); #endif printf("libslirp: %s\n", slirp_version_string()); + printf("SLIRP_CONFIG_VERSION_MAX: %d\n", SLIRP_CONFIG_VERSION_MAX); } struct options { - pid_t target_pid; // argv[1] char *tapname; // argv[2] - bool do_config_network; // -c - int exit_fd; // -e - int ready_fd; // -r - unsigned int mtu; // -m - bool disable_host_loopback; // --disable-host-loopback char *cidr; // --cidr - bool enable_ipv6; // -6 char *api_socket; // -a char *netns_type; // argv[1] char *netns_path; // --netns-path char *userns_path; // --userns-path + char *outbound_addr; // --outbound-addr + char *outbound_addr6; // --outbound-addr6 + pid_t target_pid; // argv[1] + int exit_fd; // -e + int ready_fd; // -r + unsigned int mtu; // -m + bool do_config_network; // -c + bool disable_host_loopback; // --disable-host-loopback + bool enable_ipv6; // -6 bool enable_sandbox; // --enable-sandbox bool enable_seccomp; // --enable-seccomp + bool disable_dns; // --disable-dns }; static void options_init(struct options *options) @@ -398,6 +427,14 @@ static void options_destroy(struct options *options) free(options->userns_path); options->userns_path = NULL; } + if (options->outbound_addr != NULL) { + free(options->outbound_addr); + options->outbound_addr = NULL; + } + if (options->outbound_addr6 != NULL) { + free(options->outbound_addr6); + options->outbound_addr6 = NULL; + } } // * caller does not need to call options_init() @@ -411,12 +448,17 @@ static void parse_args(int argc, char *const argv[], struct options *options) char *optarg_netns_type = NULL; char *optarg_userns_path = NULL; char *optarg_api_socket = NULL; + char *optarg_outbound_addr = NULL; + char *optarg_outbound_addr6 = NULL; #define CIDR -42 #define DISABLE_HOST_LOOPBACK -43 #define NETNS_TYPE -44 #define USERNS_PATH -45 #define ENABLE_SANDBOX -46 #define ENABLE_SECCOMP -47 +#define OUTBOUND_ADDR -48 +#define OUTBOUND_ADDR6 -49 +#define DISABLE_DNS -50 #define _DEPRECATED_NO_HOST_LOOPBACK \ -10043 // deprecated in favor of disable-host-loopback #define _DEPRECATED_CREATE_SANDBOX \ @@ -438,6 +480,9 @@ static void parse_args(int argc, char *const argv[], struct options *options) { "enable-seccomp", no_argument, NULL, ENABLE_SECCOMP }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, + { "outbound-addr", required_argument, NULL, OUTBOUND_ADDR }, + { "outbound-addr6", required_argument, NULL, OUTBOUND_ADDR6 }, + { "disable-dns", no_argument, NULL, DISABLE_DNS }, { 0, 0, 0, 0 }, }; options_init(options); @@ -526,6 +571,17 @@ static void parse_args(int argc, char *const argv[], struct options *options) version(); exit(EXIT_SUCCESS); break; + case OUTBOUND_ADDR: + printf("WARNING: Support for --outbount-addr is experimental\n"); + optarg_outbound_addr = optarg; + break; + case OUTBOUND_ADDR6: + printf("WARNING: Support for --outbount-addr6 is experimental\n"); + optarg_outbound_addr6 = optarg; + break; + case DISABLE_DNS: + options->disable_dns = true; + break; default: goto error; break; @@ -543,6 +599,12 @@ static void parse_args(int argc, char *const argv[], struct options *options) if (optarg_api_socket != NULL) { options->api_socket = strdup(optarg_api_socket); } + if (optarg_outbound_addr != NULL) { + options->outbound_addr = strdup(optarg_outbound_addr); + } + if (optarg_outbound_addr6 != NULL) { + options->outbound_addr6 = strdup(optarg_outbound_addr6); + } #undef CIDR #undef DISABLE_HOST_LOOPBACK #undef NETNS_TYPE @@ -550,6 +612,9 @@ static void parse_args(int argc, char *const argv[], struct options *options) #undef _DEPRECATED_NO_HOST_LOOPBACK #undef ENABLE_SANDBOX #undef ENABLE_SECCOMP +#undef OUTBOUND_ADDR +#undef OUTBOUND_ADDR6 +#undef DISABLE_DNS if (argc - optind < 2) { goto error; } @@ -670,6 +735,36 @@ static int slirp4netns_config_from_cidr(struct slirp4netns_config *cfg, return rc; } +static int get_interface_addr(const char *interface, int af, void *addr) +{ + struct ifaddrs *ifaddr, *ifa; + if (interface == NULL) + return -1; + + if (getifaddrs(&ifaddr) == -1) { + fprintf(stderr, "getifaddrs failed to obtain interface addresses"); + return -1; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL) + continue; + if (ifa->ifa_addr->sa_family == af) { + if (strcmp(ifa->ifa_name, interface) == 0) { + if (af == AF_INET) { + *(struct in_addr *)addr = + ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; + } else { + *(struct in6_addr *)addr = + ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + } + return 0; + } + } + } + return -1; +} + static int slirp4netns_config_from_options(struct slirp4netns_config *cfg, struct options *opt) { @@ -684,6 +779,71 @@ static int slirp4netns_config_from_options(struct slirp4netns_config *cfg, cfg->disable_host_loopback = opt->disable_host_loopback; cfg->enable_sandbox = opt->enable_sandbox; cfg->enable_seccomp = opt->enable_seccomp; + +#if SLIRP_CONFIG_VERSION_MAX >= 2 + cfg->enable_outbound_addr = false; + cfg->enable_outbound_addr6 = false; +#endif + + if (opt->outbound_addr != NULL) { +#if SLIRP_CONFIG_VERSION_MAX >= 2 + cfg->outbound_addr.sin_family = AF_INET; + cfg->outbound_addr.sin_port = 0; // Any local port will do + if (inet_pton(AF_INET, opt->outbound_addr, + &cfg->outbound_addr.sin_addr) == 1) { + cfg->enable_outbound_addr = true; + } else { + if (get_interface_addr(opt->outbound_addr, AF_INET, + &cfg->outbound_addr.sin_addr) != 0) { + fprintf(stderr, "outbound-addr has to be valid ipv4 address or " + "iterface name."); + rc = -1; + goto finish; + } + cfg->enable_outbound_addr = true; + } +#else + fprintf(stderr, "slirp4netns has to be compiled against libslrip 4.2.0 " + "or newer for --outbound-addr support."); + rc = -1; + goto finish; +#endif + } + if (opt->outbound_addr6 != NULL) { +#if SLIRP_CONFIG_VERSION_MAX >= 2 + cfg->outbound_addr6.sin6_family = AF_INET6; + cfg->outbound_addr6.sin6_port = 0; // Any local port will do + if (inet_pton(AF_INET6, opt->outbound_addr6, + &cfg->outbound_addr6.sin6_addr) == 1) { + cfg->enable_outbound_addr6 = true; + } else { + if (get_interface_addr(opt->outbound_addr, AF_INET6, + &cfg->outbound_addr6.sin6_addr) != 0) { + fprintf(stderr, "outbound-addr has to be valid ipv4 address or " + "iterface name."); + rc = -1; + goto finish; + } + cfg->enable_outbound_addr6 = true; + } +#else + fprintf(stderr, "slirp4netns has to be compiled against libslirp 4.2.0 " + "or newer for --outbound-addr6 support."); + rc = -1; + goto finish; +#endif + } + +#if SLIRP_CONFIG_VERSION_MAX >= 3 + cfg->disable_dns = opt->disable_dns; +#else + if (opt->disable_dns) { + fprintf(stderr, "slirp4netns has to be compiled against libslirp 4.3.0 " + "or newer for --disable-dns support."); + rc = -1; + goto finish; + } +#endif finish: return rc; } diff --git a/slirp4netns.1.md b/slirp4netns.1.md index 445aebb..d1e7daf 100644 --- a/slirp4netns.1.md +++ b/slirp4netns.1.md @@ -75,12 +75,28 @@ the capabilities except `CAP_NET_BIND_SERVICE` are dropped. enable **seccomp(2)** to limit syscalls. Typically used in conjunction with **--enable-sandbox**. +**--outbound-addr=IPv4** (since v1.1.0, EXPERIMENTAL) +specify outbound ipv4 address slirp should bind to + +**--outbound-addr=INTERFACE** (since v1.1.0, EXPERIMENTAL) +specify outbound interface slirp should bind to (ipv4 traffic only) + +**--outbound-addr=IPv6** (since v1.1.0, EXPERIMENTAL) +specify outbound ipv6 address slirp should bind to + +**--outbound-addr6=INTERFACE** (since v1.1.0, EXPERIMENTAL) +specify outbound interface slirp should bind to (ipv6 traffic only) + +**--disable-dns** (since v1.1.0) +disable built-in DNS (10.0.2.3 by default) + **-h**, **--help** (since v0.2.0) show help and exit **-v**, **--version** (since v0.2.0) show version and exit + # EXAMPLE Terminal 1: Create user/network/mount namespaces @@ -215,6 +231,19 @@ Additionally, a **--userns-path=PATH** argument can be included to override any $ slirp4netns --netns-type=path --userns-path=/path/to/userns /path/to/netns tap0 ``` +# OUTBOUND ADDRESSES +A user can defined preferred outbound ipv4 and ipv6 address in multi IP scenarios. + +```console +$ slirp4netns --outbound-addr=10.2.2.10 --outbound-addr6=fe80::10 ... +``` + +Optionally you can use interface names instead of ip addresses. + +```console +$ slirp4netns --outbound-addr=eth0 --outbound-addr6=eth0 ... +``` + # BUGS Kernel 4.20 bumped up the default value of **/proc/sys/net/ipv4/tcp_rmem** from 87380 to 131072. diff --git a/slirp4netns.c b/slirp4netns.c index 4b16af7..8748808 100644 --- a/slirp4netns.c +++ b/slirp4netns.c @@ -275,6 +275,24 @@ Slirp *create_slirp(void *opaque, struct slirp4netns_config *s4nn) cfg.if_mtu = s4nn->mtu; cfg.if_mru = s4nn->mtu; cfg.disable_host_loopback = s4nn->disable_host_loopback; +#if SLIRP_CONFIG_VERSION_MAX >= 2 + cfg.outbound_addr = NULL; + cfg.outbound_addr6 = NULL; + if (s4nn->enable_outbound_addr) { + cfg.version = 2; + cfg.outbound_addr = &s4nn->outbound_addr; + } + if (s4nn->enable_outbound_addr6) { + cfg.version = 2; + cfg.outbound_addr6 = &s4nn->outbound_addr6; + } +#endif +#if SLIRP_CONFIG_VERSION_MAX >= 3 + if (s4nn->disable_dns) { + cfg.version = 3; + cfg.disable_dns = true; + } +#endif slirp = slirp_new(&cfg, &libslirp_cb, opaque); if (slirp == NULL) { fprintf(stderr, "slirp_new failed\n"); diff --git a/slirp4netns.h b/slirp4netns.h index a5df49d..afe6065 100644 --- a/slirp4netns.h +++ b/slirp4netns.h @@ -1,21 +1,32 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SLIRP4NETNS_H -# define SLIRP4NETNS_H +#define SLIRP4NETNS_H #include struct slirp4netns_config { - unsigned int mtu; - struct in_addr vnetwork; // 10.0.2.0 - struct in_addr vnetmask; // 255.255.255.0 - struct in_addr vhost; // 10.0.2.2 - struct in_addr vdhcp_start; // 10.0.2.15 - struct in_addr vnameserver; // 10.0.2.3 - struct in_addr recommended_vguest; // 10.0.2.100 (slirp itself is unaware of vguest) - bool enable_ipv6; - bool disable_host_loopback; - bool enable_sandbox; - bool enable_seccomp; + unsigned int mtu; + struct in_addr vnetwork; // 10.0.2.0 + struct in_addr vnetmask; // 255.255.255.0 + struct in_addr vhost; // 10.0.2.2 + struct in_addr vdhcp_start; // 10.0.2.15 + struct in_addr vnameserver; // 10.0.2.3 + struct in_addr + recommended_vguest; // 10.0.2.100 (slirp itself is unaware of vguest) + bool enable_ipv6; + bool disable_host_loopback; + bool enable_sandbox; + bool enable_seccomp; +#if SLIRP_CONFIG_VERSION_MAX >= 2 + bool enable_outbound_addr; + struct sockaddr_in outbound_addr; + bool enable_outbound_addr6; + struct sockaddr_in6 outbound_addr6; +#endif +#if SLIRP_CONFIG_VERSION_MAX >= 3 + bool disable_dns; +#endif }; -int do_slirp(int tapfd, int readyfd, int exitfd, const char *api_socket, struct slirp4netns_config *cfg); +int do_slirp(int tapfd, int readyfd, int exitfd, const char *api_socket, + struct slirp4netns_config *cfg); #endif diff --git a/tests/common.sh b/tests/common.sh index 5f94e89..c7f9aa7 100755 --- a/tests/common.sh +++ b/tests/common.sh @@ -50,6 +50,18 @@ function wait_for_ping_connectivity { done } +function wait_for_connectivity { + COUNTER=0 + while [ $COUNTER -lt 40 ]; do + if echo "wait_for_connectivity" | nsenter --preserve-credentials -U -n --target=$1 ncat -v $2 $3; then + break + else + sleep 0.5 + fi + let COUNTER=COUNTER+1 + done +} + function wait_for_file_content { # Wait for a file to get the specified content. COUNTER=0 diff --git a/tests/test-slirp4netns-disable-dns.sh b/tests/test-slirp4netns-disable-dns.sh new file mode 100755 index 0000000..c148f3d --- /dev/null +++ b/tests/test-slirp4netns-disable-dns.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -xeuo pipefail + +SLIRP_CONFIG_VERSION_MAX=$(slirp4netns -v | grep "SLIRP_CONFIG_VERSION_MAX: " | sed 's#SLIRP_CONFIG_VERSION_MAX: \(\)##') + +if [ "${SLIRP_CONFIG_VERSION_MAX:-0}" -lt 3 ]; then + printf "'--disable-dns' requires SLIRP_CONFIG_VERSION_MAX 3 or newer. Test skipped..." + exit 0 +fi + +. $(dirname $0)/common.sh + +port=53 +unshare -r -n sleep infinity & +child=$! + +wait_for_network_namespace $child + +mtu=${MTU:=1500} +slirp4netns -c --mtu $mtu --disable-dns $child tun11 & +slirp_pid=$! + +wait_for_network_device $child tun11 +# ping to 10.0.2.2 +wait_for_ping_connectivity $child 10.0.2.2 + +function cleanup() { + kill -9 $child $slirp_pid +} +trap cleanup EXIT + +set +e +err=$(echo "should fail" | nsenter --preserve-credentials -U -n --target=$child ncat -v 10.0.2.3 $port 2>&1) +set -e +echo $err | grep "Connection timed out" diff --git a/tests/test-slirp4netns-outbound-addr.sh b/tests/test-slirp4netns-outbound-addr.sh new file mode 100755 index 0000000..4ad1947 --- /dev/null +++ b/tests/test-slirp4netns-outbound-addr.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -xeuo pipefail + +SLIRP_CONFIG_VERSION_MAX=$(slirp4netns -v | grep "SLIRP_CONFIG_VERSION_MAX: " | sed 's#SLIRP_CONFIG_VERSION_MAX: \(\)##') + +if [ "${SLIRP_CONFIG_VERSION_MAX:-0}" -lt 2 ]; then + printf "'--disable-dns' requires SLIRP_CONFIG_VERSION_MAX 2 or newer. Test skipped..." + exit 0 +fi + +. $(dirname $0)/common.sh + +IPv4_1="127.0.0.1" +IPv4_2=$(ip a | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p' | head -n 1) + +# For future ipv6 tests +#IPv6_1="::1" +#IPv6_2=$(ip a | sed -En 's/::1\/128//;s/.*inet6 (addr:)?([^ ]*)\/.*$/\2/p' | head -n 1) + +function cleanup() { + rm -rf ncat.log + kill -9 $child $slirp_pid || exit 0 +} +trap cleanup EXIT + +port=12122 +mtu=${MTU:=1500} + +IPs=("$IPv4_1" "$IPv4_2") +for ip in "${IPs[@]}"; do + ncat -l $port -v >ncat.log 2>&1 & + ncat1=$! + + unshare -r -n sleep infinity & + child=$! + + wait_for_network_namespace $child + + slirp4netns -c --mtu $mtu --outbound-addr="$ip" $child tun11 & + slirp_pid=$! + + wait_for_network_device $child tun11 + + wait_for_connectivity $child 10.0.2.2 $port + + wait_process_exits $ncat1 + if ! grep "$ip" ncat.log; then + printf "%s not found in ncat.log" "$ip" + exit 1 + fi + cleanup + let port=port+1 +done