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:
committed by
Andrei Vagin
parent
0cf79a3608
commit
fc7705a13f
@@ -214,6 +214,7 @@ TST_NOFILE := \
|
||||
uffd-events \
|
||||
thread_different_uid_gid \
|
||||
pipe03 \
|
||||
netns_lock \
|
||||
netns_sub \
|
||||
netns_sub_veth \
|
||||
netns_sub_sysctl \
|
||||
|
71
test/zdtm/static/netns_lock.c
Normal file
71
test/zdtm/static/netns_lock.c
Normal 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;
|
||||
}
|
6
test/zdtm/static/netns_lock.desc
Normal file
6
test/zdtm/static/netns_lock.desc
Normal 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
144
test/zdtm/static/netns_lock.hook
Executable 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()
|
Reference in New Issue
Block a user