Path: blob/main/tests/integration_tests/functional/test_net_config_space.py
1958 views
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.1# SPDX-License-Identifier: Apache-2.02"""Tests on devices config space."""34import os5import random6import re7import string8from threading import Thread9import subprocess10import platform1112import host_tools.logging as log_tools13import host_tools.network as net_tools # pylint: disable=import-error1415# pylint: disable=global-statement16PAYLOAD_DATA_SIZE = 20171819def test_net_change_mac_address(test_microvm_with_ssh, network_config,20change_net_config_space_bin):21"""Test changing the MAC address of the network device."""22global PAYLOAD_DATA_SIZE2324test_microvm = test_microvm_with_ssh25test_microvm.spawn()26test_microvm.basic_config(boot_args="ipv6.disable=1")2728# Data exchange interface ('eth0' in guest).29iface_id = '1'30_tap1, host_ip1, guest_ip1 = test_microvm.ssh_network_config(31network_config,32iface_id33)3435# Control interface ('eth1' in guest).36iface_id = '2'37_tap2, _, guest_ip2 = test_microvm.ssh_network_config(38network_config,39iface_id40)4142# Configure metrics, to get later the `tx_spoofed_mac_count`.43metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')44metrics_fifo = log_tools.Fifo(metrics_fifo_path)45response = test_microvm.metrics.put(46metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)47)48assert test_microvm.api_session.is_status_no_content(response.status_code)4950test_microvm.start()5152# Create the control ssh connection.53test_microvm.ssh_config['hostname'] = guest_ip254ssh_connection_ctl = net_tools.SSHConnection(test_microvm.ssh_config)5556# Start a server(host) - client(guest) communication with the following57# parameters.58host_port = 444459iterations = 160_exchange_data(61test_microvm.jailer,62ssh_connection_ctl,63host_ip1,64host_port,65iterations66)6768fc_metrics = test_microvm.flush_metrics(metrics_fifo)69assert fc_metrics['net']['tx_spoofed_mac_count'] == 07071# Change the MAC address of the network data interface.72# This change will be propagated only inside the net device kernel struct73# and will be used for ethernet frames formation when data is exchanged74# on the network interface.75mac = "06:05:04:03:02:01"76mac_hex = "0x060504030201"77guest_if1_name = net_tools.get_guest_net_if_name(ssh_connection_ctl,78guest_ip1)79assert guest_if1_name is not None80_change_guest_if_mac(ssh_connection_ctl, mac, guest_if1_name)8182_exchange_data(83test_microvm.jailer,84ssh_connection_ctl,85host_ip1,86host_port,87iterations88)8990# `tx_spoofed_mac_count` metric was incremented due to the MAC address91# change.92fc_metrics = test_microvm.flush_metrics(metrics_fifo)93assert fc_metrics['net']['tx_spoofed_mac_count'] > 09495net_addr_base = _get_net_mem_addr_base(ssh_connection_ctl,96guest_if1_name)97assert net_addr_base is not None9899# Write into '/dev/mem' the same mac address, byte by byte.100# This changes the MAC address physically, in the network device registers.101# After this step, the net device kernel struct MAC address will be the102# same with the MAC address stored in the network device registers. The103# `tx_spoofed_mac_count` metric shouldn't be incremented later on.104ssh_connection_ctl.scp_file(change_net_config_space_bin,105'change_net_config_space')106cmd = "chmod u+x change_net_config_space &&\107./change_net_config_space {} {}"108cmd = cmd.format(net_addr_base, mac_hex)109110# This should be executed successfully.111exit_code, stdout, _ = ssh_connection_ctl.execute_command(cmd)112assert exit_code == 0113assert stdout.read() == mac114115# Discard any parasite data exchange which might've been116# happened on the emulation thread while the config space117# was changed on the vCPU thread.118test_microvm.flush_metrics(metrics_fifo)119120_exchange_data(121test_microvm.jailer,122ssh_connection_ctl,123host_ip1,124host_port,125iterations126)127fc_metrics = test_microvm.flush_metrics(metrics_fifo)128assert fc_metrics['net']['tx_spoofed_mac_count'] == 0129130# Try again, just to be extra sure.131_exchange_data(132test_microvm.jailer,133ssh_connection_ctl,134host_ip1,135host_port,136iterations137)138fc_metrics = test_microvm.flush_metrics(metrics_fifo)139assert fc_metrics['net']['tx_spoofed_mac_count'] == 0140141142def _create_server(jailer, host_ip, port, iterations):143# Wait for `iterations` TCP segments, on one connection.144# This server has to run under the network namespace, initialized145# by the integration test microvm jailer.146# pylint: disable=global-statement147global PAYLOAD_DATA_SIZE148script = \149"import socket\n" \150"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n" \151"s.setsockopt(\n" \152" socket.SOL_SOCKET, socket.SO_REUSEADDR,\n" \153" s.getsockopt(socket.SOL_SOCKET,\n" \154" socket.SO_REUSEADDR) | 1\n" \155")\n" \156"s.bind(('{}', {}))\n" \157"s.listen(1)\n" \158"conn, addr = s.accept()\n" \159"recv_iterations = {}\n" \160"while recv_iterations > 0:\n" \161" data = conn.recv({})\n" \162" recv_iterations -= 1\n" \163"conn.close()\n" \164"s.close()"165166cmd = "python -c \"{}\"".format(167script.format(168host_ip,169port,170iterations,171PAYLOAD_DATA_SIZE172)173)174netns_cmd = jailer.netns_cmd_prefix() + cmd175exit_code = subprocess.call(netns_cmd, shell=True)176assert exit_code == 0177178179def _send_data_g2h(ssh_connection, host_ip, host_port, iterations, data,180retries):181script = \182"import socket\n" \183"import time\n" \184"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n" \185"retries={}\n" \186"while retries > 0:\n" \187" try:\n" \188" s.connect(('{}',{}))\n" \189" retries = 0\n" \190" except Exception as e:\n" \191" retries -= 1\n" \192" time.sleep(1)\n" \193" if retries == 0:\n" \194" exit(1)\n" \195"send_iterations={}\n" \196"while send_iterations > 0:\n" \197" s.sendall('{}')\n" \198" send_iterations -= 1\n" \199"s.close()"200201cmd = "python -c \"{}\"".format(202script.format(203retries,204host_ip,205str(host_port),206iterations,207data208)209)210211# Wait server to initialize.212exit_code, _, stderr = ssh_connection.execute_command(cmd)213# If this assert fails, a connection refused happened.214assert exit_code == 0215assert stderr.read() == ''216217218def _start_host_server_thread(jailer, host_ip, host_port, iterations):219thread = Thread(220target=_create_server,221args=(jailer, host_ip, host_port, iterations)222)223224thread.start()225return thread226227228def _exchange_data(229jailer,230ssh_control_connection,231host_ip,232host_port,233iterations234):235server_thread = _start_host_server_thread(236jailer,237host_ip,238host_port,239iterations240)241242# Generate random data.243letters = string.ascii_lowercase244data = ''.join(random.choice(letters) for i in range(PAYLOAD_DATA_SIZE))245246# We need to synchronize host server with guest client. Server thread has247# to start listening for incoming connections before the client tries to248# connect. To synchronize, we implement a polling mechanism, retrying to249# establish a connection, on the client side, mechanism to retry guest250# client socket connection, in case the server had not started yet.251_send_data_g2h(252ssh_control_connection,253host_ip,254host_port,255iterations,256data,257retries=5258)259260# Wait for host server to receive the data sent by the guest client.261server_thread.join()262263264def _change_guest_if_mac(ssh_connection, guest_if_mac, guest_if_name):265cmd = "ip link set dev {} address ".format(guest_if_name) + guest_if_mac266# The connection will be down, because changing the mac will issue down/up267# on the interface.268ssh_connection.execute_command(cmd)269270271def _get_net_mem_addr_base(ssh_connection, if_name):272"""Get the net device memory start address."""273if platform.machine() == "x86_64":274sys_virtio_mmio_cmdline = "/sys/devices/virtio-mmio-cmdline/"275cmd = "ls {} | grep virtio-mmio. | sed 's/virtio-mmio.//'"276exit_code, stdout, _ = ssh_connection.execute_command(277cmd.format(sys_virtio_mmio_cmdline)278)279assert exit_code == 0280virtio_devs_idx = stdout.read().split()281282cmd = "cat /proc/cmdline"283exit_code, cmd_line, _ = ssh_connection.execute_command(cmd)284assert exit_code == 0285pattern_dev = re.compile("(virtio_mmio.device=4K@0x[0-9a-f]+:[0-9]+)+")286pattern_addr = re.compile("virtio_mmio.device=4K@(0x[0-9a-f]+):[0-9]+")287devs_addr = []288for dev in re.findall(pattern_dev, cmd_line.read()):289matched_addr = pattern_addr.search(dev)290# The 1st group which matches this pattern291# is the device start address. `0` group is292# full match.293addr = matched_addr.group(1)294devs_addr.append(addr)295296cmd = "ls {}/virtio-mmio.{}/virtio{}/net"297for idx in virtio_devs_idx:298_, guest_if_name, _ = ssh_connection.execute_command(299cmd.format(sys_virtio_mmio_cmdline, idx, idx)300)301if guest_if_name.read().strip() == if_name:302return devs_addr[int(idx)]303elif platform.machine() == "aarch64":304sys_virtio_mmio_cmdline = "/sys/devices/platform"305cmd = "ls {} | grep .virtio_mmio".format(sys_virtio_mmio_cmdline)306rc, stdout, _ = ssh_connection.execute_command(cmd)307assert rc == 0308309virtio_devs = stdout.read().split()310devs_addr = list(map(lambda dev: dev.split(".")[0], virtio_devs))311312cmd = "ls {}/{}/virtio{}/net"313# Device start addresses lack the hex prefix and are not interpreted314# accordingly when parsed inside `change_config_space.c`.315hex_prefix = '0x'316for (idx, dev) in enumerate(virtio_devs):317_, guest_if_name, _ = ssh_connection.execute_command(318cmd.format(sys_virtio_mmio_cmdline, dev, idx)319)320if guest_if_name.read().strip() == if_name:321return hex_prefix + devs_addr[int(idx)]322323return None324325326