diff --git a/test/zdtm/static/Makefile b/test/zdtm/static/Makefile index 1c11b36c3..d998dcc83 100644 --- a/test/zdtm/static/Makefile +++ b/test/zdtm/static/Makefile @@ -214,6 +214,7 @@ TST_NOFILE := \ uffd-events \ thread_different_uid_gid \ pipe03 \ + netns_lock \ netns_sub \ netns_sub_veth \ netns_sub_sysctl \ diff --git a/test/zdtm/static/netns_lock.c b/test/zdtm/static/netns_lock.c new file mode 100644 index 000000000..300349c03 --- /dev/null +++ b/test/zdtm/static/netns_lock.c @@ -0,0 +1,71 @@ +#include "zdtmtst.h" + +const char *test_doc = "Check network namespace is locked between dump and restore\n"; +const char *test_author = "Zeyad Yasser "; + +#include +#include +#include + +#define NS_PATH "/var/run/netns/criu-net-lock-test" +#define MAX_RETRY 3 + +int main(int argc, char **argv) +{ + int i, ns_fd; + + test_init(argc, argv); + + /* + * We try to enter the netns created by post-start hook so that + * criu locks the network namespace between dump and restore. + * + * A TCP server is started in post-start hook inside the netns + * and runs in the background detached from its parent so that + * it stays alive for the duration of the test. + * + * Other hooks (pre-dump, pre-restore, post-restore) try to + * connect to the server. + * + * Pre-dump and post-restore hooks should be able to connect + * successfully. + * + * Pre-restore hook client with SOCCR_MARK should also connect + * successfully. + * + * Pre-restore hook client without SOCCR_MARK should not be able + * to connect but also should not get connection refused as all + * packets are dropped in the namespace so the kernel shouldn't + * send an RST packet as a result. Instead we check that the + * connect operation causes a timeout. + */ + + for (i = 0; i < MAX_RETRY; i++) { + ns_fd = open(NS_PATH, O_RDONLY); + if (ns_fd < 0) { + /* Netns not created yet by post-start hook */ + sleep(1); + continue; + } + break; + } + + if (ns_fd < 0) { + pr_perror("can't open network ns"); + return 1; + } + + if (setns(ns_fd, CLONE_NEWNET)) { + pr_perror("setns %d", ns_fd); + return 1; + } + + close(ns_fd); + + test_daemon(); + test_waitsig(); + + pass(); + + return 0; +} diff --git a/test/zdtm/static/netns_lock.desc b/test/zdtm/static/netns_lock.desc new file mode 100644 index 000000000..79362790e --- /dev/null +++ b/test/zdtm/static/netns_lock.desc @@ -0,0 +1,6 @@ +{ + 'flavor': 'h', + 'flags': 'suid', + 'opts': '--tcp-established', + 'ropts': '--join-ns net:/var/run/netns/criu-net-lock-test' +} diff --git a/test/zdtm/static/netns_lock.hook b/test/zdtm/static/netns_lock.hook new file mode 100755 index 000000000..bd5967c94 --- /dev/null +++ b/test/zdtm/static/netns_lock.hook @@ -0,0 +1,144 @@ +#!/usr/bin/env python + +import subprocess +import socket +import sys +import os + +from ctypes import CDLL + +libc = CDLL('libc.so.6') + +SO_MARK = 36 # Define socket option for python2 compatibility +SOCCR_MARK = 0xC114 +CLONE_NEWNET = 0x40000000 +PORT = 8880 +NETNS = "criu-net-lock-test" +TIMEOUT = 0.1 + +def nsenter(): + with open("/var/run/netns/{}".format(NETNS)) as f: + libc.setns(f.fileno(), CLONE_NEWNET) + +if sys.argv[1] == "--post-start": + # Add test netns + subprocess.Popen(["ip", "netns", "add", NETNS]).wait() + nsenter() # Enter test netns + subprocess.Popen(["ip", "link", "set", "up", "dev", "lo"]).wait() + + # TCP server daemon + pid = os.fork() + if(pid == 0): + os.setsid() # Detach from parent + # Run server + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.bind(("127.0.0.1", PORT)) + srv.listen(1) + + # We should accept connections normally from + # pre-dump client, post-restore client and + # the pre-restore client using SOCCR_MARK + # The pre-restore client without SOCCR_MARK + # should fail with a timeout + + # Accept pre-dump client + cln, addr = srv.accept() + cln.sendall(str.encode("--pre-dump")) + cln.close() + + # Accept pre-restore client with SOCCR_MARK + srv.setsockopt(socket.SOL_SOCKET, SO_MARK, SOCCR_MARK) + cln, addr = srv.accept() + cln.sendall(str.encode("--pre-restore")) + cln.close() + srv.setsockopt(socket.SOL_SOCKET, SO_MARK, 0) + + # Accept post-restore client + cln, addr = srv.accept() + cln.sendall(str.encode("--post-restore")) + cln.close() + + srv.close() + +if sys.argv[1] == "--pre-dump": + # Network is not locked yet + # Client should be able to connect normally + + nsenter() # Enter test netns + + socket.setdefaulttimeout(TIMEOUT) + cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + cln.connect(("127.0.0.1", PORT)) + resp = cln.recv(100).decode() + if resp != sys.argv[1]: + print("Expected '{}', got '{}'".format(sys.argv[1], resp)) + sys.exit(1) + except socket.timeout: + print("Timeout when trying to connect to server") + sys.exit(1) + else: + sys.exit(0) + finally: + cln.close() + +if sys.argv[1] == "--pre-restore": + # Network should be locked now + # Only packets with SOCCR_MARK should be allowed + # Client should timeout when connecting without SOCCR_MARK + + nsenter() + + socket.setdefaulttimeout(TIMEOUT) + # SOCCR_MARK + try: + cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + cln.setsockopt(socket.SOL_SOCKET, SO_MARK, SOCCR_MARK) + cln.connect(("localhost", PORT)) + resp = cln.recv(100).decode() + if resp != sys.argv[1]: + print("Expected '{}', got '{}'".format(sys.argv[1], resp)) + sys.exit(1) + except socket.timeout: + print("Timeout when trying to connect to server") + sys.exit(1) + finally: + cln.close() + + # No SOCCR_MARK + try: + cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + cln.connect(("localhost", PORT)) + except socket.timeout: + sys.exit(0) + else: + print("Network should be locked, but client connected normally") + sys.exit(1) + finally: + cln.close() + +if sys.argv[1] == "--post-restore": + # Network should be unlocked now + # Client should be able to connect normally + + nsenter() + + socket.setdefaulttimeout(TIMEOUT) + cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + cln.connect(("localhost", PORT)) + resp = cln.recv(100).decode() + if resp != sys.argv[1]: + print("Expected '{}', got '{}'".format(sys.argv[1], resp)) + sys.exit(1) + except socket.timeout: + print("Timeout when trying to connect to server") + sys.exit(1) + else: + sys.exit(0) + finally: + cln.close() + +if sys.argv[1] == "--clean": + # Delete test netns + subprocess.Popen(["ip", "netns", "delete", NETNS]).wait()