|
17 | 17 | import os.path
|
18 | 18 |
|
19 | 19 | from framework.utils_vsock import make_blob, \
|
20 |
| - check_host_connections, check_guest_connections |
| 20 | + check_host_connections, check_guest_connections, \ |
| 21 | + HostEchoWorker |
21 | 22 | from host_tools.network import SSHConnection
|
| 23 | +import host_tools.logging as log_tools |
22 | 24 |
|
23 | 25 | VSOCK_UDS_PATH = "v.sock"
|
24 | 26 | ECHO_SERVER_PORT = 5252
|
25 | 27 | BLOB_SIZE = 20 * 1024 * 1024
|
| 28 | +NEGATIVE_TEST_CONNECTION_COUNT = 100 |
26 | 29 |
|
27 | 30 |
|
28 | 31 | def test_vsock(
|
@@ -79,3 +82,100 @@ def test_vsock(
|
79 | 82 | def _make_host_port_path(uds_path, port):
|
80 | 83 | """Build the path for a Unix socket, mapped to host vsock port `port`."""
|
81 | 84 | return "{}_{}".format(uds_path, port)
|
| 85 | + |
| 86 | + |
| 87 | +def negative_test_host_connections(vm, uds_path, blob_path, blob_hash): |
| 88 | + """Negative test for host-initiated connections. |
| 89 | +
|
| 90 | + This will start a daemonized echo server on the guest VM, and then spawn |
| 91 | + `NEGATIVE_TEST_CONNECTION_COUNT` `HostEchoWorker` threads. |
| 92 | + Closes the UDS sockets while data is in flight. |
| 93 | + """ |
| 94 | + conn = SSHConnection(vm.ssh_config) |
| 95 | + cmd = "vsock_helper echosrv -d {}". format(ECHO_SERVER_PORT) |
| 96 | + ecode, _, _ = conn.execute_command(cmd) |
| 97 | + assert ecode == 0 |
| 98 | + |
| 99 | + workers = [] |
| 100 | + for _ in range(NEGATIVE_TEST_CONNECTION_COUNT): |
| 101 | + worker = HostEchoWorker(uds_path, blob_path) |
| 102 | + workers.append(worker) |
| 103 | + worker.start() |
| 104 | + |
| 105 | + for wrk in workers: |
| 106 | + wrk.close_uds() |
| 107 | + wrk.join() |
| 108 | + |
| 109 | + # Validate that Firecracker is still up and running. |
| 110 | + ecode, _, _ = conn.execute_command("sync") |
| 111 | + # Should fail if Firecracker exited from SIGPIPE handler. |
| 112 | + assert ecode == 0 |
| 113 | + |
| 114 | + # Validate vsock emulation still accepts connections and works |
| 115 | + # as expected. |
| 116 | + check_host_connections(vm, uds_path, blob_path, blob_hash) |
| 117 | + |
| 118 | + |
| 119 | +def test_vsock_epipe( |
| 120 | + test_microvm_with_ssh, |
| 121 | + network_config, |
| 122 | + bin_vsock_path, |
| 123 | + test_session_root_path |
| 124 | +): |
| 125 | + """Vsock negative test to validate SIGPIPE/EPIPE handling.""" |
| 126 | + vm = test_microvm_with_ssh |
| 127 | + vm.spawn() |
| 128 | + |
| 129 | + vm.basic_config() |
| 130 | + _tap, _, _ = vm.ssh_network_config(network_config, '1') |
| 131 | + vm.vsock.put( |
| 132 | + vsock_id="vsock0", |
| 133 | + guest_cid=3, |
| 134 | + uds_path="/{}".format(VSOCK_UDS_PATH) |
| 135 | + ) |
| 136 | + |
| 137 | + # Configure metrics to assert against `sigpipe` count. |
| 138 | + metrics_fifo_path = os.path.join(vm.path, 'metrics_fifo') |
| 139 | + metrics_fifo = log_tools.Fifo(metrics_fifo_path) |
| 140 | + response = vm.metrics.put( |
| 141 | + metrics_path=vm.create_jailed_resource(metrics_fifo.path) |
| 142 | + ) |
| 143 | + assert vm.api_session.is_status_no_content(response.status_code) |
| 144 | + |
| 145 | + vm.start() |
| 146 | + |
| 147 | + # Generate the random data blob file. |
| 148 | + blob_path, blob_hash = make_blob(test_session_root_path) |
| 149 | + vm_blob_path = "/tmp/vsock/test.blob" |
| 150 | + |
| 151 | + # Set up a tmpfs drive on the guest, so we can copy the blob there. |
| 152 | + # Guest-initiated connections (echo workers) will use this blob. |
| 153 | + conn = SSHConnection(vm.ssh_config) |
| 154 | + cmd = "mkdir -p /tmp/vsock" |
| 155 | + cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format( |
| 156 | + BLOB_SIZE + 1024*1024 |
| 157 | + ) |
| 158 | + ecode, _, _ = conn.execute_command(cmd) |
| 159 | + assert ecode == 0 |
| 160 | + |
| 161 | + # Copy `vsock_helper` and the random blob to the guest. |
| 162 | + vsock_helper = bin_vsock_path |
| 163 | + conn.scp_file(vsock_helper, '/bin/vsock_helper') |
| 164 | + conn.scp_file(blob_path, vm_blob_path) |
| 165 | + |
| 166 | + path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH) |
| 167 | + # Negative test for host-initiated connections that |
| 168 | + # are closed with in flight data. |
| 169 | + negative_test_host_connections(vm, path, blob_path, blob_hash) |
| 170 | + |
| 171 | + metrics = vm.flush_metrics(metrics_fifo) |
| 172 | + # Validate that at least 1 `SIGPIPE` signal was received. |
| 173 | + # Since we are reusing the existing echo server which triggers |
| 174 | + # reads/writes on the UDS backend connections, these might be closed |
| 175 | + # before a read() or a write() is about to be performed by the emulation. |
| 176 | + # The test uses 100 connections it is enough to close at least one |
| 177 | + # before write(). |
| 178 | + # |
| 179 | + # If this ever fails due to 100 closes before read() we must |
| 180 | + # add extra tooling that will trigger only writes(). |
| 181 | + assert metrics['signals']['sigpipe'] > 0 |
0 commit comments