selftest: Add linux namespace support (USE_NAMESPACES=1)
authorTim Beale <timbeale@catalyst.net.nz>
Thu, 23 May 2019 05:44:37 +0000 (17:44 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Fri, 31 May 2019 05:18:20 +0000 (05:18 +0000)
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 <timbeale@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
selftest/selftest.pl
selftest/target/Samba.pm
selftest/target/Samba4.pm
selftest/wscript

index 1bbe2f81bcfd07056ac6a1b21fc65705dc666dbd..9e3d81801a6f9dcd4fdfaf98cf52f066552ad289 100755 (executable)
@@ -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";
 
index 6fd8d01da06358da70dea5f4049ad0daccc33a6a..ca3099c9d0571105810cf8303c1fd60097b8c19f 100644 (file)
@@ -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;
index 609ff837af26060a9ff4c8edd189113873659c30..b1c6aa459c19502021702ff607aa91ed14062c68 100755 (executable)
@@ -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) = @_;
index 5c864ebed96c5ce1d72970f5a1789bb84ad87901..f204f34201ba2fc49ec0b11ad43fd68bef033f7e 100644 (file)
@@ -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"