Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/functional/test_net_config_space.py
1958 views
1
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Tests on devices config space."""
4
5
import os
6
import random
7
import re
8
import string
9
from threading import Thread
10
import subprocess
11
import platform
12
13
import host_tools.logging as log_tools
14
import host_tools.network as net_tools # pylint: disable=import-error
15
16
# pylint: disable=global-statement
17
PAYLOAD_DATA_SIZE = 20
18
19
20
def test_net_change_mac_address(test_microvm_with_ssh, network_config,
21
change_net_config_space_bin):
22
"""Test changing the MAC address of the network device."""
23
global PAYLOAD_DATA_SIZE
24
25
test_microvm = test_microvm_with_ssh
26
test_microvm.spawn()
27
test_microvm.basic_config(boot_args="ipv6.disable=1")
28
29
# Data exchange interface ('eth0' in guest).
30
iface_id = '1'
31
_tap1, host_ip1, guest_ip1 = test_microvm.ssh_network_config(
32
network_config,
33
iface_id
34
)
35
36
# Control interface ('eth1' in guest).
37
iface_id = '2'
38
_tap2, _, guest_ip2 = test_microvm.ssh_network_config(
39
network_config,
40
iface_id
41
)
42
43
# Configure metrics, to get later the `tx_spoofed_mac_count`.
44
metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')
45
metrics_fifo = log_tools.Fifo(metrics_fifo_path)
46
response = test_microvm.metrics.put(
47
metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)
48
)
49
assert test_microvm.api_session.is_status_no_content(response.status_code)
50
51
test_microvm.start()
52
53
# Create the control ssh connection.
54
test_microvm.ssh_config['hostname'] = guest_ip2
55
ssh_connection_ctl = net_tools.SSHConnection(test_microvm.ssh_config)
56
57
# Start a server(host) - client(guest) communication with the following
58
# parameters.
59
host_port = 4444
60
iterations = 1
61
_exchange_data(
62
test_microvm.jailer,
63
ssh_connection_ctl,
64
host_ip1,
65
host_port,
66
iterations
67
)
68
69
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
70
assert fc_metrics['net']['tx_spoofed_mac_count'] == 0
71
72
# Change the MAC address of the network data interface.
73
# This change will be propagated only inside the net device kernel struct
74
# and will be used for ethernet frames formation when data is exchanged
75
# on the network interface.
76
mac = "06:05:04:03:02:01"
77
mac_hex = "0x060504030201"
78
guest_if1_name = net_tools.get_guest_net_if_name(ssh_connection_ctl,
79
guest_ip1)
80
assert guest_if1_name is not None
81
_change_guest_if_mac(ssh_connection_ctl, mac, guest_if1_name)
82
83
_exchange_data(
84
test_microvm.jailer,
85
ssh_connection_ctl,
86
host_ip1,
87
host_port,
88
iterations
89
)
90
91
# `tx_spoofed_mac_count` metric was incremented due to the MAC address
92
# change.
93
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
94
assert fc_metrics['net']['tx_spoofed_mac_count'] > 0
95
96
net_addr_base = _get_net_mem_addr_base(ssh_connection_ctl,
97
guest_if1_name)
98
assert net_addr_base is not None
99
100
# Write into '/dev/mem' the same mac address, byte by byte.
101
# This changes the MAC address physically, in the network device registers.
102
# After this step, the net device kernel struct MAC address will be the
103
# same with the MAC address stored in the network device registers. The
104
# `tx_spoofed_mac_count` metric shouldn't be incremented later on.
105
ssh_connection_ctl.scp_file(change_net_config_space_bin,
106
'change_net_config_space')
107
cmd = "chmod u+x change_net_config_space &&\
108
./change_net_config_space {} {}"
109
cmd = cmd.format(net_addr_base, mac_hex)
110
111
# This should be executed successfully.
112
exit_code, stdout, _ = ssh_connection_ctl.execute_command(cmd)
113
assert exit_code == 0
114
assert stdout.read() == mac
115
116
# Discard any parasite data exchange which might've been
117
# happened on the emulation thread while the config space
118
# was changed on the vCPU thread.
119
test_microvm.flush_metrics(metrics_fifo)
120
121
_exchange_data(
122
test_microvm.jailer,
123
ssh_connection_ctl,
124
host_ip1,
125
host_port,
126
iterations
127
)
128
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
129
assert fc_metrics['net']['tx_spoofed_mac_count'] == 0
130
131
# Try again, just to be extra sure.
132
_exchange_data(
133
test_microvm.jailer,
134
ssh_connection_ctl,
135
host_ip1,
136
host_port,
137
iterations
138
)
139
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
140
assert fc_metrics['net']['tx_spoofed_mac_count'] == 0
141
142
143
def _create_server(jailer, host_ip, port, iterations):
144
# Wait for `iterations` TCP segments, on one connection.
145
# This server has to run under the network namespace, initialized
146
# by the integration test microvm jailer.
147
# pylint: disable=global-statement
148
global PAYLOAD_DATA_SIZE
149
script = \
150
"import socket\n" \
151
"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n" \
152
"s.setsockopt(\n" \
153
" socket.SOL_SOCKET, socket.SO_REUSEADDR,\n" \
154
" s.getsockopt(socket.SOL_SOCKET,\n" \
155
" socket.SO_REUSEADDR) | 1\n" \
156
")\n" \
157
"s.bind(('{}', {}))\n" \
158
"s.listen(1)\n" \
159
"conn, addr = s.accept()\n" \
160
"recv_iterations = {}\n" \
161
"while recv_iterations > 0:\n" \
162
" data = conn.recv({})\n" \
163
" recv_iterations -= 1\n" \
164
"conn.close()\n" \
165
"s.close()"
166
167
cmd = "python -c \"{}\"".format(
168
script.format(
169
host_ip,
170
port,
171
iterations,
172
PAYLOAD_DATA_SIZE
173
)
174
)
175
netns_cmd = jailer.netns_cmd_prefix() + cmd
176
exit_code = subprocess.call(netns_cmd, shell=True)
177
assert exit_code == 0
178
179
180
def _send_data_g2h(ssh_connection, host_ip, host_port, iterations, data,
181
retries):
182
script = \
183
"import socket\n" \
184
"import time\n" \
185
"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n" \
186
"retries={}\n" \
187
"while retries > 0:\n" \
188
" try:\n" \
189
" s.connect(('{}',{}))\n" \
190
" retries = 0\n" \
191
" except Exception as e:\n" \
192
" retries -= 1\n" \
193
" time.sleep(1)\n" \
194
" if retries == 0:\n" \
195
" exit(1)\n" \
196
"send_iterations={}\n" \
197
"while send_iterations > 0:\n" \
198
" s.sendall('{}')\n" \
199
" send_iterations -= 1\n" \
200
"s.close()"
201
202
cmd = "python -c \"{}\"".format(
203
script.format(
204
retries,
205
host_ip,
206
str(host_port),
207
iterations,
208
data
209
)
210
)
211
212
# Wait server to initialize.
213
exit_code, _, stderr = ssh_connection.execute_command(cmd)
214
# If this assert fails, a connection refused happened.
215
assert exit_code == 0
216
assert stderr.read() == ''
217
218
219
def _start_host_server_thread(jailer, host_ip, host_port, iterations):
220
thread = Thread(
221
target=_create_server,
222
args=(jailer, host_ip, host_port, iterations)
223
)
224
225
thread.start()
226
return thread
227
228
229
def _exchange_data(
230
jailer,
231
ssh_control_connection,
232
host_ip,
233
host_port,
234
iterations
235
):
236
server_thread = _start_host_server_thread(
237
jailer,
238
host_ip,
239
host_port,
240
iterations
241
)
242
243
# Generate random data.
244
letters = string.ascii_lowercase
245
data = ''.join(random.choice(letters) for i in range(PAYLOAD_DATA_SIZE))
246
247
# We need to synchronize host server with guest client. Server thread has
248
# to start listening for incoming connections before the client tries to
249
# connect. To synchronize, we implement a polling mechanism, retrying to
250
# establish a connection, on the client side, mechanism to retry guest
251
# client socket connection, in case the server had not started yet.
252
_send_data_g2h(
253
ssh_control_connection,
254
host_ip,
255
host_port,
256
iterations,
257
data,
258
retries=5
259
)
260
261
# Wait for host server to receive the data sent by the guest client.
262
server_thread.join()
263
264
265
def _change_guest_if_mac(ssh_connection, guest_if_mac, guest_if_name):
266
cmd = "ip link set dev {} address ".format(guest_if_name) + guest_if_mac
267
# The connection will be down, because changing the mac will issue down/up
268
# on the interface.
269
ssh_connection.execute_command(cmd)
270
271
272
def _get_net_mem_addr_base(ssh_connection, if_name):
273
"""Get the net device memory start address."""
274
if platform.machine() == "x86_64":
275
sys_virtio_mmio_cmdline = "/sys/devices/virtio-mmio-cmdline/"
276
cmd = "ls {} | grep virtio-mmio. | sed 's/virtio-mmio.//'"
277
exit_code, stdout, _ = ssh_connection.execute_command(
278
cmd.format(sys_virtio_mmio_cmdline)
279
)
280
assert exit_code == 0
281
virtio_devs_idx = stdout.read().split()
282
283
cmd = "cat /proc/cmdline"
284
exit_code, cmd_line, _ = ssh_connection.execute_command(cmd)
285
assert exit_code == 0
286
pattern_dev = re.compile("(virtio_mmio.device=4K@0x[0-9a-f]+:[0-9]+)+")
287
pattern_addr = re.compile("virtio_mmio.device=4K@(0x[0-9a-f]+):[0-9]+")
288
devs_addr = []
289
for dev in re.findall(pattern_dev, cmd_line.read()):
290
matched_addr = pattern_addr.search(dev)
291
# The 1st group which matches this pattern
292
# is the device start address. `0` group is
293
# full match.
294
addr = matched_addr.group(1)
295
devs_addr.append(addr)
296
297
cmd = "ls {}/virtio-mmio.{}/virtio{}/net"
298
for idx in virtio_devs_idx:
299
_, guest_if_name, _ = ssh_connection.execute_command(
300
cmd.format(sys_virtio_mmio_cmdline, idx, idx)
301
)
302
if guest_if_name.read().strip() == if_name:
303
return devs_addr[int(idx)]
304
elif platform.machine() == "aarch64":
305
sys_virtio_mmio_cmdline = "/sys/devices/platform"
306
cmd = "ls {} | grep .virtio_mmio".format(sys_virtio_mmio_cmdline)
307
rc, stdout, _ = ssh_connection.execute_command(cmd)
308
assert rc == 0
309
310
virtio_devs = stdout.read().split()
311
devs_addr = list(map(lambda dev: dev.split(".")[0], virtio_devs))
312
313
cmd = "ls {}/{}/virtio{}/net"
314
# Device start addresses lack the hex prefix and are not interpreted
315
# accordingly when parsed inside `change_config_space.c`.
316
hex_prefix = '0x'
317
for (idx, dev) in enumerate(virtio_devs):
318
_, guest_if_name, _ = ssh_connection.execute_command(
319
cmd.format(sys_virtio_mmio_cmdline, dev, idx)
320
)
321
if guest_if_name.read().strip() == if_name:
322
return hex_prefix + devs_addr[int(idx)]
323
324
return None
325
326