2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-31 14:25:49 +00:00

zdtm: add network namespace locking test

When criu dumps a process in a network namespace it locks
the network so that no packets from peer enters the stack,
otherwise RST will be sent by a kernel causing the connection
to fail.

In netns_lock.c 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 test 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.

This test would be useful when testing that the network is
locked using different ways (using iptables currently and
other methods later).

v2:
	- check that packets with SOCCR_MARK are allowed to
	  pass when the netns is locked.

v3:
	- fix pre-restore hook skipping non SOCCR_MARK
	  connection test due to early exit in SOCCR_MARK
	  variant.

Signed-off-by: Zeyad Yasser <zeyady98@gmail.com>
This commit is contained in:
Zeyad Yasser
2021-04-29 21:18:58 +02:00
committed by Andrei Vagin
parent 0cf79a3608
commit fc7705a13f
4 changed files with 222 additions and 0 deletions

View File

@@ -214,6 +214,7 @@ TST_NOFILE := \
uffd-events \
thread_different_uid_gid \
pipe03 \
netns_lock \
netns_sub \
netns_sub_veth \
netns_sub_sysctl \

View File

@@ -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 <zeyady98@gmail.com>";
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#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;
}

View File

@@ -0,0 +1,6 @@
{
'flavor': 'h',
'flags': 'suid',
'opts': '--tcp-established',
'ropts': '--join-ns net:/var/run/netns/criu-net-lock-test'
}

144
test/zdtm/static/netns_lock.hook Executable file
View File

@@ -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()