Skip to content

Commit 8fd0165

Browse files
sandreimalindima
authored andcommitted
Add reggresion/negative vsock test
This covers the scenario when the UDS backend connection is closed by the host application right before the emulation will write() some bytes from the guest application. This will result in a SIGPIPE signal delivery. Test fails if SIGPIPE causes a VMM exit, vsock emulation fails, or if SIGPIPE is never recorded by metrics. Signed-off-by: Andrei Sandu <[email protected]>
1 parent c320ab3 commit 8fd0165

File tree

2 files changed

+110
-7
lines changed

2 files changed

+110
-7
lines changed

tests/framework/utils_vsock.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def __init__(self, uds_path, blob_path):
104104
self.blob_path = blob_path
105105
self.hash = None
106106
self.error = None
107+
self.sock = _vsock_connect_to_guest(self.uds_path, ECHO_SERVER_PORT)
107108

108109
def run(self):
109110
"""Thread code payload.
@@ -118,9 +119,11 @@ def run(self):
118119
except Exception as err:
119120
self.error = err
120121

121-
def _run(self):
122+
def close_uds(self):
123+
"""Close vsock UDS connection."""
124+
self.sock.close()
122125

123-
sock = _vsock_connect_to_guest(self.uds_path, ECHO_SERVER_PORT)
126+
def _run(self):
124127
blob_file = open(self.blob_path, 'rb')
125128
hash_obj = hashlib.md5()
126129

@@ -130,13 +133,13 @@ def _run(self):
130133
if not buf:
131134
break
132135

133-
sent = sock.send(buf)
136+
sent = self.sock.send(buf)
134137
while sent < len(buf):
135-
sent += sock.send(buf[sent:])
138+
sent += self.sock.send(buf[sent:])
136139

137-
buf = sock.recv(sent)
140+
buf = self.sock.recv(sent)
138141
while len(buf) < sent:
139-
buf += sock.recv(sent - len(buf))
142+
buf += self.sock.recv(sent - len(buf))
140143

141144
hash_obj.update(buf)
142145

tests/integration_tests/functional/test_vsock.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
import os.path
1818

1919
from framework.utils_vsock import make_blob, \
20-
check_host_connections, check_guest_connections
20+
check_host_connections, check_guest_connections, \
21+
HostEchoWorker
2122
from host_tools.network import SSHConnection
23+
import host_tools.logging as log_tools
2224

2325
VSOCK_UDS_PATH = "v.sock"
2426
ECHO_SERVER_PORT = 5252
2527
BLOB_SIZE = 20 * 1024 * 1024
28+
NEGATIVE_TEST_CONNECTION_COUNT = 100
2629

2730

2831
def test_vsock(
@@ -79,3 +82,100 @@ def test_vsock(
7982
def _make_host_port_path(uds_path, port):
8083
"""Build the path for a Unix socket, mapped to host vsock port `port`."""
8184
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

Comments
 (0)