diff --git a/tests/framework/utils_vsock.py b/tests/framework/utils_vsock.py index eb3756607dd..511ef3a8a4e 100644 --- a/tests/framework/utils_vsock.py +++ b/tests/framework/utils_vsock.py @@ -94,18 +94,29 @@ def make_blob(dst_dir, size=BLOB_SIZE): return blob_path, blob_hash.hexdigest() -def check_host_connections(vm, uds_path, blob_path, blob_hash): +def start_guest_echo_server(vm): + """Start a vsock echo server in the microVM. + + Returns a UDS path to connect to the server. + """ + cmd = f"nohup socat VSOCK-LISTEN:{ECHO_SERVER_PORT},backlog=128,reuseaddr,fork EXEC:'/bin/cat' > /dev/null 2>&1 &" + ecode, _, stderr = vm.ssh.run(cmd) + assert ecode == 0, stderr + + # Give the server time to initialise + time.sleep(1) + + return os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH) + + +def check_host_connections(uds_path, blob_path, blob_hash): """Test host-initiated connections. - This will start a daemonized echo server on the guest VM, and then spawn - `TEST_CONNECTION_COUNT` `HostEchoWorker` threads. + This will spawn `TEST_CONNECTION_COUNT` `HostEchoWorker` threads. After the workers are done transferring the data read from `blob_path`, the hashes they computed for the data echoed back by the server are checked against `blob_hash`. """ - cmd = "/tmp/vsock_helper echosrv -d {}".format(ECHO_SERVER_PORT) - ecode, _, _ = vm.ssh.run(cmd) - assert ecode == 0 workers = [] for _ in range(TEST_CONNECTION_COUNT): @@ -226,5 +237,5 @@ def check_vsock_device(vm, bin_vsock_path, test_fc_session_root_path, ssh_connec check_guest_connections(vm, path, vm_blob_path, blob_hash) # Test vsock host-initiated connections. - path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH) - check_host_connections(vm, path, blob_path, blob_hash) + path = start_guest_echo_server(vm) + check_host_connections(path, blob_path, blob_hash) diff --git a/tests/host_tools/vsock_helper.c b/tests/host_tools/vsock_helper.c index 0fa1c998bab..00424dd6b6e 100644 --- a/tests/host_tools/vsock_helper.c +++ b/tests/host_tools/vsock_helper.c @@ -2,11 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // This is a vsock helper tool, used by the Firecracker integration tests, -// to - well - test the virtio vsock device. It can be used to: -// 1. Run a forking echo server, that echoes back any data received from -// a client; and -// 2. Run a vsock echo client, that reads data from STDIN, sends it to an -// echo server, then forwards the server's reply to STDOUT. +// to - well - test the virtio vsock device. It can be used to +// run a vsock echo client, that reads data from STDIN, sends it to an +// echo server, then forwards the server's reply to STDOUT. #include #include @@ -27,12 +25,7 @@ int print_usage() { - fprintf(stderr, "Usage: ./vsock-helper {echosrv [-d] | echo }\n"); - fprintf(stderr, "\n"); - fprintf(stderr, " echosrv start a vsock echo server. The server will accept\n"); - fprintf(stderr, " any incoming connection, and echo back any data\n"); - fprintf(stderr, " received on it.\n"); - fprintf(stderr, " -d can be used to daemonize the server.\n"); + fprintf(stderr, "Usage: ./vsock-helper echo \n"); fprintf(stderr, "\n"); fprintf(stderr, " echo connect to an echo server, listening on CID:port.\n"); fprintf(stderr, " STDIN will be piped through to the echo server, and\n"); @@ -60,66 +53,6 @@ int xfer(int src_fd, int dst_fd) { } -int run_echosrv(uint32_t port) { - - struct sockaddr_vm vsock_addr = { - .svm_family = AF_VSOCK, - .svm_port = port, - .svm_cid = VMADDR_CID_ANY - }; - - int srv_sock = socket(AF_VSOCK, SOCK_STREAM, 0); - if (srv_sock < 0) { - perror("socket()"); - return -1; - } - - int res = bind(srv_sock, (struct sockaddr*)&vsock_addr, sizeof(struct sockaddr_vm)); - if (res) { - perror("bind()"); - return -1; - } - - res = listen(srv_sock, SERVER_ACCEPT_BACKLOG); - if (res) { - perror("listen()"); - return -1; - } - - for (;;) { - struct sockaddr cl_addr; - socklen_t sockaddr_len = sizeof(cl_addr); - int cl_sock = accept(srv_sock, &cl_addr, &sockaddr_len); - if (cl_sock < 0) { - perror("accept()"); - continue; - } - - int pid = fork(); - if (pid < 0) { - perror("fork()"); - close(cl_sock); - continue; - } - - if (!pid) { - int res; - do { - res = xfer(cl_sock, cl_sock); - } while (res > 0); - return res >= 0 ? 0 : -1; - } - - close(cl_sock); - int cstatus; - waitpid(-1, &cstatus, WNOHANG); - printf("New client forked...\n"); - } - - return 0; -} - - int run_echo(uint32_t cid, uint32_t port) { int sock = socket(AF_VSOCK, SOCK_STREAM, 0); @@ -161,36 +94,6 @@ int main(int argc, char **argv) { return print_usage(); } - if (strcmp(argv[1], "echosrv") == 0) { - uint32_t port; - if (strcmp(argv[2], "-d") == 0) { - if (argc < 4) { - return print_usage(); - } - port = atoi(argv[3]); - if (!port) { - return print_usage(); - } - int pid = fork(); - if (pid < 0) return -1; - if (pid) { - printf("Forked vsock echo daemon listening on port %d\n", port); - return 0; - } - setsid(); - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } - else { - port = atoi(argv[2]); - if (!port) { - return print_usage(); - } - } - return run_echosrv(port); - } - if (strcmp(argv[1], "echo") == 0) { if (argc != 4) { return print_usage(); diff --git a/tests/integration_tests/functional/test_snapshot_basic.py b/tests/integration_tests/functional/test_snapshot_basic.py index de43f0b83a1..7975b98d824 100644 --- a/tests/integration_tests/functional/test_snapshot_basic.py +++ b/tests/integration_tests/functional/test_snapshot_basic.py @@ -21,6 +21,7 @@ check_host_connections, make_blob, make_host_port_path, + start_guest_echo_server, ) @@ -93,8 +94,8 @@ def test_5_snapshots( ) check_guest_connections(microvm, path, vm_blob_path, blob_hash) # Test vsock host-initiated connections. - path = os.path.join(microvm.jailer.chroot_path(), VSOCK_UDS_PATH) - check_host_connections(microvm, path, blob_path, blob_hash) + path = start_guest_echo_server(microvm) + check_host_connections(path, blob_path, blob_hash) # Check that the root device is not corrupted. check_filesystem(microvm.ssh, "squashfs", "/dev/vda") diff --git a/tests/integration_tests/functional/test_vsock.py b/tests/integration_tests/functional/test_vsock.py index f8940ec7f98..b3f695ad298 100644 --- a/tests/integration_tests/functional/test_vsock.py +++ b/tests/integration_tests/functional/test_vsock.py @@ -4,8 +4,7 @@ In order to test the vsock device connection state machine, these tests will: - Generate a 20MiB random data blob; -- Use `host_tools/vsock_helper.c` to start a listening echo server inside the - guest VM; +- Use `socat` to start a listening echo server inside the guest VM; - Run 50, concurrent, host-initiated connections, each transfering the random blob to and from the guest echo server; - For every connection, check that the data received back from the echo server @@ -27,6 +26,7 @@ check_vsock_device, make_blob, make_host_port_path, + start_guest_echo_server, ) NEGATIVE_TEST_CONNECTION_COUNT = 100 @@ -51,7 +51,7 @@ def test_vsock(test_microvm_with_api, bin_vsock_path, test_fc_session_root_path) check_vsock_device(vm, bin_vsock_path, test_fc_session_root_path, vm.ssh) -def negative_test_host_connections(vm, uds_path, blob_path, blob_hash): +def negative_test_host_connections(vm, blob_path, blob_hash): """Negative test for host-initiated connections. This will start a daemonized echo server on the guest VM, and then spawn @@ -59,9 +59,7 @@ def negative_test_host_connections(vm, uds_path, blob_path, blob_hash): Closes the UDS sockets while data is in flight. """ - cmd = "/tmp/vsock_helper echosrv -d {}".format(ECHO_SERVER_PORT) - ecode, _, _ = vm.ssh.run(cmd) - assert ecode == 0 + uds_path = start_guest_echo_server(vm) workers = [] for _ in range(NEGATIVE_TEST_CONNECTION_COUNT): @@ -78,9 +76,22 @@ def negative_test_host_connections(vm, uds_path, blob_path, blob_hash): # Should fail if Firecracker exited from SIGPIPE handler. assert ecode == 0 + metrics = vm.flush_metrics() + # Validate that at least 1 `SIGPIPE` signal was received. + # Since we are reusing the existing echo server which triggers + # reads/writes on the UDS backend connections, these might be closed + # before a read() or a write() is about to be performed by the emulation. + # The test uses 100 connections it is enough to close at least one + # before write(). + # + # If this ever fails due to 100 closes before read() we must + # add extra tooling that will trigger only writes(). + assert metrics["signals"]["sigpipe"] > 0 + # Validate vsock emulation still accepts connections and works - # as expected. - check_host_connections(vm, uds_path, blob_path, blob_hash) + # as expected. Use the default blob size to speed up the test. + blob_path, blob_hash = make_blob(os.path.dirname(blob_path)) + check_host_connections(uds_path, blob_path, blob_hash) def test_vsock_epipe(test_microvm_with_api, bin_vsock_path, test_fc_session_root_path): @@ -102,22 +113,9 @@ def test_vsock_epipe(test_microvm_with_api, bin_vsock_path, test_fc_session_root # Guest-initiated connections (echo workers) will use this blob. _copy_vsock_data_to_guest(vm.ssh, blob_path, vm_blob_path, bin_vsock_path) - path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH) # Negative test for host-initiated connections that # are closed with in flight data. - negative_test_host_connections(vm, path, blob_path, blob_hash) - - metrics = vm.flush_metrics() - # Validate that at least 1 `SIGPIPE` signal was received. - # Since we are reusing the existing echo server which triggers - # reads/writes on the UDS backend connections, these might be closed - # before a read() or a write() is about to be performed by the emulation. - # The test uses 100 connections it is enough to close at least one - # before write(). - # - # If this ever fails due to 100 closes before read() we must - # add extra tooling that will trigger only writes(). - assert metrics["signals"]["sigpipe"] > 0 + negative_test_host_connections(vm, blob_path, blob_hash) def test_vsock_transport_reset( @@ -152,10 +150,7 @@ def test_vsock_transport_reset( _copy_vsock_data_to_guest(test_vm.ssh, blob_path, vm_blob_path, bin_vsock_path) # Start guest echo server. - path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH) - cmd = f"/tmp/vsock_helper echosrv -d {ECHO_SERVER_PORT}" - ecode, _, _ = test_vm.ssh.run(cmd) - assert ecode == 0 + path = start_guest_echo_server(test_vm) # Start host workers that connect to the guest server. workers = [] @@ -176,8 +171,8 @@ def test_vsock_transport_reset( # Whatever we send to the server, it should return the same # value. buf = bytearray("TEST\n".encode("utf-8")) - worker.sock.send(buf) try: + worker.sock.send(buf) # Arbitrary timeout, we set this so the socket won't block as # it shouldn't receive anything. worker.sock.settimeout(0.25) @@ -187,7 +182,7 @@ def test_vsock_transport_reset( assert False, "Connection not closed: response recieved '{}'".format( response.decode("utf-8") ) - except SocketTimeout: + except (SocketTimeout, ConnectionResetError, BrokenPipeError): assert True # Terminate VM. @@ -206,4 +201,4 @@ def test_vsock_transport_reset( # Test host-initiated connections. path = os.path.join(vm2.jailer.chroot_path(), VSOCK_UDS_PATH) - check_host_connections(vm2, path, blob_path, blob_hash) + check_host_connections(path, blob_path, blob_hash)