From: Tim Beale Date: Thu, 23 May 2019 05:44:37 +0000 (+1200) Subject: selftest: Add linux namespace support (USE_NAMESPACES=1) X-Git-Tag: ldb-2.0.5~524 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=067b4fc03fec20e944eac2645f400471f2bc0b21;p=thirdparty%2Fsamba.git selftest: Add linux namespace support (USE_NAMESPACES=1) This hooks up the selftest/ns/* scripts added earlier with the selftest system, so developers can optionally run a testenv or test using linux namespaces instead of socket-wrapper. The idea is this is experimental functionality that we can extend further in future, in order to make testing Samba more versatile. + The top-level WAF script now does an 'unshare' to create a new top-level 'selftest' namespace in which to create the testenv(s). + selftest.pl creates a common 'selftest0' bridge to connect together the individual DCs. + Update Samba.pm so it can use real IPs instead of loopback addresses. In fork_and_exec(), we add a couple of hooks so that the binary gets started in a different namespace (using unshare/start_in_ns.sh), and the parent process connects the new child namespace up to the common selftest0 bridge (using add_bridge_iface.sh). Signed-off-by: Tim Beale Reviewed-by: Andrew Bartlett --- diff --git a/selftest/selftest.pl b/selftest/selftest.pl index 1bbe2f81bcf..9e3d81801a6 100755 --- a/selftest/selftest.pl +++ b/selftest/selftest.pl @@ -424,6 +424,16 @@ if ($opt_libuid_wrapper_so_path) { } } +if (defined($ENV{USE_NAMESPACES})) { + print "Using linux containerization for selftest testenv(s)...\n"; + + # Create a common bridge to connect up the testenv namespaces. We give + # it the client's IP address, as this is where the tests will run from + my $ipv4_addr = Samba::get_ipv4_addr("client"); + my $ipv6_addr = Samba::get_ipv6_addr("client"); + system "$ENV{SRCDIR_ABS}/selftest/ns/create_bridge.sh selftest0 $ipv4_addr $ipv6_addr"; +} + $ENV{LD_PRELOAD} = $ld_preload; print "LD_PRELOAD=$ENV{LD_PRELOAD}\n"; diff --git a/selftest/target/Samba.pm b/selftest/target/Samba.pm index 6fd8d01da06..ca3099c9d05 100644 --- a/selftest/target/Samba.pm +++ b/selftest/target/Samba.pm @@ -516,7 +516,13 @@ sub get_ipv4_addr $swiface += $iface_num; } - return "127.0.0.$swiface"; + if (use_namespaces()) { + # use real IPs if selftest is running in its own network namespace + return "10.0.0.$swiface"; + } else { + # use loopback IPs with socket-wrapper + return "127.0.0.$swiface"; + } } sub get_ipv6_addr @@ -541,7 +547,12 @@ sub get_interfaces_config } for (my $i = 0; $i < $num_ips; $i++) { my $ipv4_addr = Samba::get_ipv4_addr($hostname, $i); - $interfaces .= "$ipv4_addr/8 "; + if (use_namespaces()) { + # use a /24 subnet with network namespaces + $interfaces .= "$ipv4_addr/24 "; + } else { + $interfaces .= "$ipv4_addr/8 "; + } } my $ipv6_addr = Samba::get_ipv6_addr($hostname); @@ -627,10 +638,13 @@ sub fork_and_exec unlink($daemon_ctx->{LOG_FILE}); print "STARTING $daemon_ctx->{NAME} for $ENV{ENVNAME}..."; + my $parent_pid = $$; my $pid = fork(); # exec the daemon in the child process if ($pid == 0) { + my @preargs = (); + # redirect the daemon's stdout/stderr to a log file if (defined($daemon_ctx->{TEE_STDOUT})) { # in some cases, we want out from samba to go to the log file, @@ -671,12 +685,27 @@ sub fork_and_exec close($env_vars->{STDIN_PIPE}); open STDIN, ">&", $STDIN_READER or die "can't dup STDIN_READER to STDIN: $!"; + # if using kernel namespaces, prepend the command so the process runs in + # its own namespace + if (Samba::use_namespaces()) { + @preargs = ns_exec_preargs($parent_pid, $env_vars); + } + # the command args are stored as an array reference (because...Perl), # so convert the reference back to an array my @full_cmd = @{ $daemon_ctx->{FULL_CMD} }; - exec(@full_cmd) or die("Unable to start $ENV{MAKE_TEST_BINARY}: $!"); + + exec(@preargs, @full_cmd) or die("Unable to start $ENV{MAKE_TEST_BINARY}: $!"); } + print "DONE ($pid)\n"; + + # if using kernel namespaces, we now establish a connection between the + # main selftest namespace (i.e. this process) and the new child namespace + if (use_namespaces()) { + ns_child_forked($pid, $env_vars); + } + return $pid; } @@ -797,4 +826,78 @@ sub export_envvars_to_file close(FILE); } +# Returns true if kernel namespaces are being used instead of socket-wrapper. +# The default is false. +sub use_namespaces +{ + return defined($ENV{USE_NAMESPACES}); +} + +# returns a given testenv's interface-name (only when USE_NAMESPACES=1) +sub ns_interface_name +{ + my ($hostname) = @_; + + # when using namespaces, each testenv has its own vethX interface, + # where X = Samba::get_interface(testenv_name) + my $iface = get_interface($hostname); + return "veth$iface"; +} + +# Called after a new child namespace has been forked +sub ns_child_forked +{ + my ($child_pid, $env_vars) = @_; + + # we only need to do this for the first child forked for this testenv + if (defined($env_vars->{NS_PID})) { + return; + } + + # store the child PID. It's the only way the main (selftest) namespace can + # access the new child (testenv) namespace. + $env_vars->{NS_PID} = $child_pid; + + # Add the new child namespace's interface to the main selftest bridge. + # This connects together the various testenvs so that selftest can talk to + # them all + my $iface = ns_interface_name($env_vars->{NETBIOSNAME}); + system "$ENV{SRCDIR}/selftest/ns/add_bridge_iface.sh $iface-br selftest0"; +} + +# returns args to prepend to a command in order to execute it the correct +# namespace for the testenv (creating a new namespace if needed). +# This should only used when USE_NAMESPACES=1 is set. +sub ns_exec_preargs +{ + my ($parent_pid, $env_vars) = @_; + + # NS_PID stores the pid of the first child daemon run in this namespace + if (defined($env_vars->{NS_PID})) { + + # the namespace has already been created previously. So we use nsenter + # to execute the command in the given testenv's namespace. We need to + # use the NS_PID to identify this particular namespace + return ("nsenter", "-t", "$env_vars->{NS_PID}", "--net"); + } else { + + # We need to create a new namespace for this daemon (i.e. we're + # setting up a new testenv). First, write the environment variables to + # an exports.sh file for this testenv (for convenient access by the + # namespace scripts). + my $exports_file = "$env_vars->{TESTENV_DIR}/exports.sh"; + export_envvars_to_file($exports_file, $env_vars); + + # when using namespaces, each testenv has its own veth interface + my $interface = ns_interface_name($env_vars->{NETBIOSNAME}); + + # we use unshare to create a new network namespace. The start_in_ns.sh + # helper script gets run first to setup the new namespace's interfaces. + # (This all gets prepended around the actual command to run in the new + # namespace) + return ("unshare", "--net", "$ENV{SRCDIR}/selftest/ns/start_in_ns.sh", + $interface, $exports_file, $parent_pid); + } +} + 1; diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm index 609ff837af2..b1c6aa459c1 100755 --- a/selftest/target/Samba4.pm +++ b/selftest/target/Samba4.pm @@ -468,6 +468,9 @@ sub get_cmd_env_vars return $cmd_env; } +# Sets up a forest trust namespace. +# (Note this is different to kernel namespaces, setup by the +# USE_NAMESPACES=1 option) sub setup_namespaces($$:$$) { my ($self, $localenv, $upn_array, $spn_array) = @_; diff --git a/selftest/wscript b/selftest/wscript index 5c864ebed96..f204f34201b 100644 --- a/selftest/wscript +++ b/selftest/wscript @@ -246,9 +246,12 @@ def cmd_testonly(opt): env.OPTIONS += " --nss_wrapper_so_path=" + CONFIG_GET(opt, 'LIBNSS_WRAPPER_SO_PATH') env.OPTIONS += " --resolv_wrapper_so_path=" + CONFIG_GET(opt, 'LIBRESOLV_WRAPPER_SO_PATH') - env.OPTIONS += " --socket_wrapper_so_path=" + CONFIG_GET(opt, 'LIBSOCKET_WRAPPER_SO_PATH') env.OPTIONS += " --uid_wrapper_so_path=" + CONFIG_GET(opt, 'LIBUID_WRAPPER_SO_PATH') + # selftest can optionally use kernel namespaces instead of socket-wrapper + if os.environ.get('USE_NAMESPACES') is None: + env.OPTIONS += " --socket_wrapper_so_path=" + CONFIG_GET(opt, 'LIBSOCKET_WRAPPER_SO_PATH') + #if unversioned_sys_platform in ('freebsd', 'netbsd', 'openbsd', 'sunos'): # env.OPTIONS += " --use-dns-faking" @@ -277,6 +280,13 @@ def cmd_testonly(opt): # We use the full path rather than relative path to avoid problems on some platforms (ie. solaris 8). env.CORE_COMMAND = '${PERL} ${srcdir}/selftest/selftest.pl --target=${SELFTEST_TARGET} --prefix=${SELFTEST_PREFIX} --srcdir=${srcdir} --exclude=${srcdir}/selftest/skip ${TESTLISTS} ${OPTIONS} ${TESTS}' + # If using namespaces (rather than socket-wrapper), run the selftest script + # in its own network namespace (by doing an 'unshare'). (To create a new + # namespace as a non-root user, we have to also unshare the current user + # namespace, and remap ourself as root in the namespace created) + if os.environ.get('USE_NAMESPACES') is not None: + env.CORE_COMMAND = 'unshare --net --user --map-root-user ' + env.CORE_COMMAND + if env.ADDRESS_SANITIZER: # For now we cannot run with leak detection no_leak_check = "ASAN_OPTIONS=detect_leaks=0"