systemd_lib_paths = ['/usr/lib/systemd', '/lib/systemd']
which_paths = ':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':'))
-systemd_source_dir = None
networkd_bin = shutil.which('systemd-networkd', path=which_paths)
resolved_bin = shutil.which('systemd-resolved', path=which_paths)
timesyncd_bin = shutil.which('systemd-timesyncd', path=which_paths)
-udevd_bin = shutil.which('systemd-udevd', path=which_paths)
wait_online_bin = shutil.which('systemd-networkd-wait-online', path=which_paths)
networkctl_bin = shutil.which('networkctl', path=which_paths)
resolvectl_bin = shutil.which('resolvectl', path=which_paths)
timedatectl_bin = shutil.which('timedatectl', path=which_paths)
udevadm_bin = shutil.which('udevadm', path=which_paths)
-systemd_udev_rules_build_dir = None
+test_ndisc_send = None
+build_dir = None
+source_dir = None
use_valgrind = False
valgrind_cmd = ''
return version.parse(kver) >= version.parse(min_kernel_version)
-def udev_reload():
- check_output(*udevadm_cmd, 'control', '--reload')
-
def copy_network_unit(*units, copy_dropins=True):
"""
Copy networkd unit files into the testbed.
has_link = True
if has_link:
- udev_reload()
+ udevadm_reload()
def copy_credential(src, target):
mkdir_p(credstore_dir)
has_link = True
if has_link:
- udev_reload()
+ udevadm_reload()
def clear_network_units():
has_link = False
rm_rf(network_unit_dir)
if has_link:
- udev_reload()
+ udevadm_reload()
def copy_networkd_conf_dropin(*dropins):
"""Copy networkd.conf dropin files into the testbed."""
rm_rf(networkd_conf_dropin_dir)
def setup_systemd_udev_rules():
- if not systemd_udev_rules_build_dir:
+ if not build_dir:
return
mkdir_p(udev_rules_dir)
- for path in [systemd_udev_rules_build_dir, os.path.join(systemd_source_dir, "rules.d")]:
+ for path in [build_dir, source_dir]:
+ path = os.path.join(path, "rules.d")
print(f"Copying udev rules from {path} to {udev_rules_dir}")
for rule in os.listdir(path):
continue
cp(os.path.join(path, rule), udev_rules_dir)
+def clear_networkd_state_files():
+ rm_rf('/var/lib/systemd/network/')
+
def copy_udev_rule(*rules):
"""Copy udev rules"""
mkdir_p(udev_rules_dir)
f.write('\n'.join(contents))
def create_service_dropin(service, command, additional_settings=None):
- drop_in = [
- '[Service]',
- 'ExecStart=',
- f'ExecStart=!!{valgrind_cmd}{command}',
- ]
+ drop_in = ['[Service]']
+ if command:
+ drop_in += [
+ 'ExecStart=',
+ f'ExecStart=!!{valgrind_cmd}{command}',
+ ]
if enable_debug:
drop_in += ['Environment=SYSTEMD_LOG_LEVEL=debug']
if asan_options:
create_unit_dropin(f'{service}.service', drop_in)
+def setup_system_units():
+ if build_dir:
+ mkdir_p('/run/systemd/system/')
+
+ for unit in [
+ 'systemd-networkd.service',
+ 'systemd-networkd.socket',
+ 'systemd-networkd-persistent-storage.service',
+ 'systemd-resolved.service',
+ 'systemd-timesyncd.service',
+ 'systemd-udevd.service',
+ ]:
+ for path in [build_dir, source_dir]:
+ fullpath = os.path.join(os.path.join(path, "units"), unit)
+ if os.path.exists(fullpath):
+ print(f"Copying unit file from {fullpath} to /run/systemd/system/")
+ cp(fullpath, '/run/systemd/system/')
+ break
+
+ create_service_dropin('systemd-networkd', networkd_bin,
+ ['[Service]',
+ 'Restart=no',
+ 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes',
+ '[Unit]',
+ 'StartLimitIntervalSec=0'])
+ create_service_dropin('systemd-resolved', resolved_bin)
+ create_service_dropin('systemd-timesyncd', timesyncd_bin)
+
+ # TODO: also run udevd with sanitizers, valgrind, or coverage
+ create_unit_dropin(
+ 'systemd-udevd.service',
+ [
+ '[Service]',
+ 'ExecStart=',
+ f'ExecStart=!!@{udevadm_bin} systemd-udevd',
+ ]
+ )
+ create_unit_dropin(
+ 'systemd-networkd.socket',
+ [
+ '[Unit]',
+ 'StartLimitIntervalSec=0',
+ ]
+ )
+ create_unit_dropin(
+ 'systemd-networkd-persistent-storage.service',
+ [
+ '[Unit]',
+ 'StartLimitIntervalSec=0',
+ '[Service]',
+ 'ExecStart=',
+ f'ExecStart={networkctl_bin} persistent-storage yes',
+ 'ExecStop=',
+ f'ExecStop={networkctl_bin} persistent-storage no'
+ ]
+ )
+
+ check_output('systemctl daemon-reload')
+ print(check_output('systemctl cat systemd-networkd.service'))
+ print(check_output('systemctl cat systemd-networkd-persistent-storage.service'))
+ print(check_output('systemctl cat systemd-resolved.service'))
+ print(check_output('systemctl cat systemd-timesyncd.service'))
+ print(check_output('systemctl cat systemd-udevd.service'))
+ check_output('systemctl restart systemd-resolved.service')
+ check_output('systemctl restart systemd-timesyncd.service')
+ check_output('systemctl restart systemd-udevd.service')
+
+def clear_system_units():
+ def rm_unit(name):
+ rm_f(f'/run/systemd/system/{name}')
+ rm_rf(f'/run/systemd/system/{name}.d')
+
+ rm_unit('systemd-networkd.service')
+ rm_unit('systemd-networkd.socket')
+ rm_unit('systemd-networkd-persistent-storage.service')
+ rm_unit('systemd-resolved.service')
+ rm_unit('systemd-timesyncd.service')
+ rm_unit('systemd-udevd.service')
+ check_output('systemctl daemon-reload')
+ check_output('systemctl restart systemd-udevd.service')
+
def link_exists(link):
return call_quiet(f'ip link show {link}') == 0
def timedatectl(*args):
return check_output(*(timedatectl_cmd + list(args)), env=env)
+def udevadm(*args):
+ return check_output(*(udevadm_cmd + list(args)))
+
+def udevadm_reload():
+ udevadm('control', '--reload')
+
+def udevadm_trigger(*args, action='add'):
+ udevadm('trigger', '--settle', f'--action={action}', *args)
+
def setup_common():
print()
# 6. remove configs
clear_network_units()
clear_networkd_conf_dropins()
+ clear_networkd_state_files()
# 7. flush settings
flush_fou_ports()
clear_network_units()
clear_networkd_conf_dropins()
+ clear_networkd_state_files()
clear_udev_rules()
setup_systemd_udev_rules()
save_routing_policy_rules()
save_timezone()
- create_service_dropin('systemd-networkd', networkd_bin,
- ['[Service]',
- 'Restart=no',
- 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes',
- '[Unit]',
- 'StartLimitIntervalSec=0'])
- create_service_dropin('systemd-resolved', resolved_bin)
- create_service_dropin('systemd-timesyncd', timesyncd_bin)
-
- # TODO: also run udevd with sanitizers, valgrind, or coverage
- #create_service_dropin('systemd-udevd', udevd_bin,
- # f'{udevadm_bin} control --reload --timeout 0')
- create_unit_dropin(
- 'systemd-udevd.service',
- [
- '[Service]',
- 'ExecStart=',
- f'ExecStart=!!@{udevd_bin} systemd-udevd',
- ]
- )
- create_unit_dropin(
- 'systemd-networkd.socket',
- [
- '[Unit]',
- 'StartLimitIntervalSec=0',
- ]
- )
-
- check_output('systemctl daemon-reload')
- print(check_output('systemctl cat systemd-networkd.service'))
- print(check_output('systemctl cat systemd-resolved.service'))
- print(check_output('systemctl cat systemd-timesyncd.service'))
- print(check_output('systemctl cat systemd-udevd.service'))
- check_output('systemctl restart systemd-resolved.service')
- check_output('systemctl restart systemd-timesyncd.service')
- check_output('systemctl restart systemd-udevd.service')
+ setup_system_units()
def tearDownModule():
rm_rf(networkd_ci_temp_dir)
clear_udev_rules()
clear_network_units()
clear_networkd_conf_dropins()
+ clear_networkd_state_files()
restore_timezone()
- rm_rf('/run/systemd/system/systemd-networkd.service.d')
- rm_rf('/run/systemd/system/systemd-networkd.socket.d')
- rm_rf('/run/systemd/system/systemd-resolved.service.d')
- rm_rf('/run/systemd/system/systemd-timesyncd.service.d')
- rm_rf('/run/systemd/system/systemd-udevd.service.d')
- check_output('systemctl daemon-reload')
- check_output('systemctl restart systemd-udevd.service')
+ clear_system_units()
restore_active_units()
class Utilities():
self.assertRegex(output, route_regex)
+ def wait_route_dropped(self, link, route_regex, table='main', ipv='', timeout_sec=100):
+ for i in range(timeout_sec):
+ if i > 0:
+ time.sleep(1)
+ output = check_output(f'ip {ipv} route show dev {link} table {table}')
+ if not re.search(route_regex, output):
+ break
+
+ self.assertNotRegex(output, route_regex)
+
def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0'):
if not shutil.which('selinuxenabled'):
print('## Checking NetLabel skipped: selinuxenabled command not found.')
# This test may be run on the system that has older udevd than 70f32a260b5ebb68c19ecadf5d69b3844896ba55 (v249).
# In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property.
# Let's reprocess the interface and drop the property.
- check_output(*udevadm_cmd, 'trigger', '--settle', '--action=add', '/sys/class/net/lo')
+ udevadm_trigger('/sys/class/net/lo')
output = networkctl_status('lo')
print(output)
self.assertIn('Link File: n/a', output)
check_output('ip link set dev dummy98-1 down')
check_output('ip link set dev dummy98-1 name dummy98-2')
- check_output(*udevadm_cmd, 'trigger', '--action=add', '/sys/class/net/dummy98-2')
+ udevadm_trigger('/sys/class/net/dummy98-2')
self.wait_address('dummy98-2', '10.0.2.2/16', ipv='-4', timeout_sec=10)
self.wait_online('dummy98-2:routable')
print(output)
self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10')
+ self.check_link_attr('bond97', 'bonding', 'peer_notif_delay', '300000')
def test_vlan(self):
copy_network_unit('21-vlan.netdev', '11-dummy.netdev',
self.wait_online('ifb99:degraded')
+ @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test")
+ def test_rps_cpu_1(self):
+ copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-1.link')
+ start_networkd()
+
+ self.wait_online('dummy98:carrier')
+
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 2)
+
+ @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test")
+ def test_rps_cpu_0_1(self):
+ copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-0-1.link')
+ start_networkd()
+
+ self.wait_online('dummy98:carrier')
+
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 3)
+
+ @unittest.skipUnless(os.cpu_count() >= 4, reason="CPU count should be >= 4 to pass this test")
+ def test_rps_cpu_multi(self):
+ copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-multi.link')
+ start_networkd()
+
+ self.wait_online('dummy98:carrier')
+
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 15)
+
+ def test_rps_cpu(self):
+ cpu_count = os.cpu_count()
+
+ copy_network_unit('12-dummy.netdev', '12-dummy.network')
+ start_networkd()
+
+ self.wait_online('dummy98:carrier')
+
+ # 0
+ copy_network_unit('25-rps-cpu-0.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 1)
+ remove_network_unit('25-rps-cpu-0.link')
+
+ # all
+ copy_network_unit('25-rps-cpu-all.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}')
+ remove_network_unit('25-rps-cpu-all.link')
+
+ # disable
+ copy_network_unit('24-rps-cpu-disable.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 0)
+ remove_network_unit('24-rps-cpu-disable.link')
+
+ # set all again
+ copy_network_unit('25-rps-cpu-all.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}')
+ remove_network_unit('25-rps-cpu-all.link')
+
+ # empty -> unchanged
+ copy_network_unit('24-rps-cpu-empty.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}')
+ remove_network_unit('24-rps-cpu-empty.link')
+
+ # 0, then empty -> unchanged
+ copy_network_unit('25-rps-cpu-0-empty.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}')
+ remove_network_unit('25-rps-cpu-0-empty.link')
+
+ # 0, then invalid -> 0
+ copy_network_unit('25-rps-cpu-0-invalid.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 1)
+ remove_network_unit('25-rps-cpu-0-invalid.link')
+
+ # invalid -> unchanged
+ copy_network_unit('24-rps-cpu-invalid.link')
+ udevadm_trigger('/sys/class/net/dummy98')
+ output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus')
+ print(output)
+ self.assertEqual(int(output.replace(',', ''), base=16), 1)
+ remove_network_unit('24-rps-cpu-invalid.link')
+
class NetworkdL2TPTests(unittest.TestCase, Utilities):
def setUp(self):
with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
f.write('[Link]\nSR-IOVVirtualFunctions=4\n')
- udev_reload()
- check_output(*udevadm_cmd, 'trigger', '--action=add', '--settle', f'/sys/devices/netdevsim99/net/{ifname}')
+ udevadm_reload()
+ udevadm_trigger(f'/sys/devices/netdevsim99/net/{ifname}')
output = check_output('ip link show dev eni99np1')
print(output)
with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
f.write('[Link]\nSR-IOVVirtualFunctions=\n')
- udev_reload()
- check_output(*udevadm_cmd, 'trigger', '--action=add', '--settle', f'/sys/devices/netdevsim99/net/{ifname}')
+ udevadm_reload()
+ udevadm_trigger(f'/sys/devices/netdevsim99/net/{ifname}')
output = check_output('ip link show dev eni99np1')
print(output)
with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
f.write('[Link]\nSR-IOVVirtualFunctions=2\n')
- udev_reload()
- check_output(*udevadm_cmd, 'trigger', '--action=add', '--settle', f'/sys/devices/netdevsim99/net/{ifname}')
+ udevadm_reload()
+ udevadm_trigger(f'/sys/devices/netdevsim99/net/{ifname}')
output = check_output('ip link show dev eni99np1')
print(output)
with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
f.write('[Link]\nSR-IOVVirtualFunctions=\n')
- udev_reload()
- check_output(*udevadm_cmd, 'trigger', '--action=add', '--settle', f'/sys/devices/netdevsim99/net/{ifname}')
+ udevadm_reload()
+ udevadm_trigger(f'/sys/devices/netdevsim99/net/{ifname}')
output = check_output('ip link show dev eni99np1')
print(output)
output = networkctl('lldp')
print(output)
- if re.search(r'veth99 .* veth-peer', output):
+ if re.search(r'veth99 .* veth-peer .* .......a...', output):
break
else:
self.fail()
+ # With interface name
+ output = networkctl('lldp', 'veth99');
+ print(output)
+ self.assertRegex(output, r'veth99 .* veth-peer .* .......a...')
+
+ # With interface name pattern
+ output = networkctl('lldp', 've*9');
+ print(output)
+ self.assertRegex(output, r'veth99 .* veth-peer .* .......a...')
+
+ # json format
+ output = networkctl('--json=short', 'lldp')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":128', output)
+
+ # json format with interface name
+ output = networkctl('--json=short', 'lldp', 'veth99')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":128', output)
+
+ # json format with interface name pattern
+ output = networkctl('--json=short', 'lldp', 've*9')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":128', output)
+
+ # LLDP neighbors in status
+ output = networkctl_status('veth99')
+ print(output)
+ self.assertRegex(output, r'Connected To: .* on port veth-peer')
+
+ # enable forwarding, to enable the Router flag
+ with open(os.path.join(network_unit_dir, '23-emit-lldp.network'), mode='a', encoding='utf-8') as f:
+ f.write('[Network]\nIPv4Forwarding=yes\n')
+
+ networkctl_reload()
+ self.wait_online('veth-peer:degraded')
+
+ for trial in range(10):
+ if trial > 0:
+ time.sleep(1)
+
+ output = networkctl('lldp')
+ print(output)
+ if re.search(r'veth99 .* veth-peer .* ....r......', output):
+ break
+ else:
+ self.fail()
+
+ # With interface name
+ output = networkctl('lldp', 'veth99');
+ print(output)
+ self.assertRegex(output, r'veth99 .* veth-peer .* ....r......')
+
+ # With interface name pattern
+ output = networkctl('lldp', 've*9');
+ print(output)
+ self.assertRegex(output, r'veth99 .* veth-peer .* ....r......')
+
+ # json format
+ output = networkctl('--json=short', 'lldp')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":16', output)
+
+ # json format with interface name
+ output = networkctl('--json=short', 'lldp', 'veth99')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":16', output)
+
+ # json format with interface name pattern
+ output = networkctl('--json=short', 'lldp', 've*9')
+ print(output)
+ self.assertIn('"InterfaceName":"veth99"', output)
+ self.assertIn('"PortID":"veth-peer"', output)
+ self.assertIn('"EnabledCapabilities":16', output)
+
+ # LLDP neighbors in status
+ output = networkctl_status('veth99')
+ print(output)
+ self.assertRegex(output, r'Connected To: .* on port veth-peer')
+
class NetworkdRATests(unittest.TestCase, Utilities):
def setUp(self):
self.check_ipv6_token_static()
+ def test_ndisc_redirect(self):
+ if not os.path.exists(test_ndisc_send):
+ self.skipTest(f"{test_ndisc_send} does not exist.")
+
+ copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
+ start_networkd()
+
+ self.check_ipv6_token_static()
+
+ # Introduce two redirect routes.
+ check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:1:1a:2b:3c:4d --redirect-destination 2002:da8:1:1:1a:2b:3c:4d')
+ check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::1 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d')
+ self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10)
+ self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::1 proto redirect', ipv='-6', timeout_sec=10)
+
+ # Change the target address of the redirects.
+ check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::2 --redirect-destination 2002:da8:1:1:1a:2b:3c:4d')
+ check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::3 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d')
+ self.wait_route_dropped('veth99', r'2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10)
+ self.wait_route_dropped('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::1 proto redirect', ipv='-6', timeout_sec=10)
+ self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d via 2002:da8:1::2 proto redirect', ipv='-6', timeout_sec=10)
+ self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::3 proto redirect', ipv='-6', timeout_sec=10)
+
+ # Send Neighbor Advertisement without the router flag to announce the default router is not available anymore.
+ # Then, verify that all redirect routes and the default route are dropped.
+ output = check_output('ip -6 address show dev veth-peer scope link')
+ veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group()
+ print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}')
+ check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no')
+ self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10)
+ self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10)
+
def test_ipv6_token_prefixstable(self):
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
start_networkd()
def tearDown(self):
tear_down_common()
- def test_dhcp_server(self):
- copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
- start_networkd()
- self.wait_online('veth99:routable', 'veth-peer:routable')
-
+ def check_dhcp_server(self, persist_leases=True):
output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertRegex(output, 'NTP: 192.168.5.1\n *192.168.5.11')
output = networkctl_status('veth-peer')
+ print(output)
self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*")
+ if persist_leases:
+ with open('/var/lib/systemd/network/dhcp-server-lease/veth-peer', encoding='utf-8') as f:
+ check_json(f.read())
+ else:
+ self.assertFalse(os.path.exists('/var/lib/systemd/network/dhcp-server-lease/veth-peer'))
+
+ def test_dhcp_server(self):
+ copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
+ start_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ self.check_dhcp_server()
+
+ networkctl_reconfigure('veth-peer')
+ self.wait_online('veth-peer:routable')
+
+ for _ in range(10):
+ output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
+ if 'Offered DHCP leases: 192.168.5.' in output:
+ break
+ time.sleep(.2)
+ else:
+ self.fail()
+
+ def test_dhcp_server_persist_leases_no(self):
+ copy_networkd_conf_dropin('persist-leases-no.conf')
+ copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
+ start_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ self.check_dhcp_server(persist_leases=False)
+
+ remove_networkd_conf_dropin('persist-leases-no.conf')
+ with open(os.path.join(network_unit_dir, '25-dhcp-server.network'), mode='a', encoding='utf-8') as f:
+ f.write('[DHCPServer]\nPersistLeases=no')
+ restart_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ self.check_dhcp_server(persist_leases=False)
+
def test_dhcp_server_null_server_address(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network')
start_networkd()
output = networkctl_status('veth-peer')
self.assertIn(f'Offered DHCP leases: {client_address}', output)
+ # Check if the same addresses are used even if the service is restarted.
+ restart_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ output = check_output('ip -4 address show dev veth-peer')
+ print(output)
+ self.assertIn(f'{server_address}', output)
+
+ output = check_output('ip -4 address show dev veth99')
+ print(output)
+ self.assertIn(f'{client_address}', output)
+
+ output = networkctl_status('veth99')
+ print(output)
+ self.assertRegex(output, rf'Address: {client_address} \(DHCP4 via {server_address}\)')
+ self.assertIn(f'Gateway: {server_address}', output)
+ self.assertIn(f'DNS: {server_address}', output)
+ self.assertIn(f'NTP: {server_address}', output)
+
+ output = networkctl_status('veth-peer')
+ self.assertIn(f'Offered DHCP leases: {client_address}', output)
+
def test_dhcp_server_with_uplink(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network',
'12-dummy.netdev', '25-dhcp-server-uplink.network')
print(output)
self.assertRegex(output, r'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
- def test_replay_agent_on_bridge(self):
+ def test_relay_agent_on_bridge(self):
copy_network_unit('25-agent-bridge.netdev',
'25-agent-veth-client.netdev',
'25-agent-bridge.network',
check(self, True, False)
check(self, False, True)
check(self, False, False)
+
+ def test_dhcp_client_default_use_domains(self):
+ def check(self, ipv4, ipv6):
+ mkdir_p(networkd_conf_dropin_dir)
+ with open(os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), mode='w', encoding='utf-8') as f:
+ f.write('[DHCPv4]\nUseDomains=')
+ f.write('yes\n' if ipv4 else 'no\n')
+ f.write('[DHCPv6]\nUseDomains=')
+ f.write('yes\n' if ipv6 else 'no\n')
+
+ restart_networkd()
+ self.wait_online('veth-peer:carrier')
+ start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1',
+ '--dhcp-option=option6:dns-server,[2600::1]',
+ '--dhcp-option=option:domain-search,example.com',
+ '--dhcp-option=option6:domain-search,example.com')
+
+ self.wait_online('veth99:routable')
+
+ # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
+ self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
+ self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
+
+ for _ in range(20):
+ output = resolvectl('domain', 'veth99')
+ if ipv4 or ipv6:
+ if 'example.com' in output:
+ break
+ else:
+ if 'example.com' not in output:
+ break
+ time.sleep(0.5)
+ else:
+ print(output)
+ self.fail('unexpected domain setting in resolved...')
+
+ stop_dnsmasq()
+ remove_networkd_conf_dropin('default_use_domains.conf')
+
+ copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
+ check(self, True, True)
+ check(self, True, False)
+ check(self, False, True)
+ check(self, False, False)
def test_dhcp_client_use_captive_portal(self):
def check(self, ipv4, ipv6):
parser = argparse.ArgumentParser()
parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir')
parser.add_argument('--source-dir', help='Path to source dir/git tree', dest='source_dir')
- parser.add_argument('--networkd', help='Path to systemd-networkd', dest='networkd_bin')
- parser.add_argument('--resolved', help='Path to systemd-resolved', dest='resolved_bin')
- parser.add_argument('--timesyncd', help='Path to systemd-timesyncd', dest='timesyncd_bin')
- parser.add_argument('--udevd', help='Path to systemd-udevd', dest='udevd_bin')
- parser.add_argument('--wait-online', help='Path to systemd-networkd-wait-online', dest='wait_online_bin')
- parser.add_argument('--networkctl', help='Path to networkctl', dest='networkctl_bin')
- parser.add_argument('--resolvectl', help='Path to resolvectl', dest='resolvectl_bin')
- parser.add_argument('--timedatectl', help='Path to timedatectl', dest='timedatectl_bin')
- parser.add_argument('--udevadm', help='Path to udevadm', dest='udevadm_bin')
parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind)
parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug)
parser.add_argument('--asan-options', help='ASAN options', dest='asan_options')
ns, unknown_args = parser.parse_known_args(namespace=unittest)
if ns.build_dir:
- if ns.networkd_bin or ns.resolved_bin or ns.timesyncd_bin or ns.udevd_bin or \
- ns.wait_online_bin or ns.networkctl_bin or ns.resolvectl_bin or ns.timedatectl_bin or ns.udevadm_bin:
- print('WARNING: --networkd, --resolved, --timesyncd, --udevd, --wait-online, --networkctl, --resolvectl, --timedatectl, or --udevadm options are ignored when --build-dir is specified.')
networkd_bin = os.path.join(ns.build_dir, 'systemd-networkd')
resolved_bin = os.path.join(ns.build_dir, 'systemd-resolved')
timesyncd_bin = os.path.join(ns.build_dir, 'systemd-timesyncd')
- udevd_bin = os.path.join(ns.build_dir, 'udevadm')
wait_online_bin = os.path.join(ns.build_dir, 'systemd-networkd-wait-online')
networkctl_bin = os.path.join(ns.build_dir, 'networkctl')
resolvectl_bin = os.path.join(ns.build_dir, 'resolvectl')
timedatectl_bin = os.path.join(ns.build_dir, 'timedatectl')
udevadm_bin = os.path.join(ns.build_dir, 'udevadm')
- systemd_udev_rules_build_dir = os.path.join(ns.build_dir, 'rules.d')
- else:
- if ns.networkd_bin:
- networkd_bin = ns.networkd_bin
- if ns.resolved_bin:
- resolved_bin = ns.resolved_bin
- if ns.timesyncd_bin:
- timesyncd_bin = ns.timesyncd_bin
- if ns.udevd_bin:
- udevd_bin = ns.udevd_bin
- if ns.wait_online_bin:
- wait_online_bin = ns.wait_online_bin
- if ns.networkctl_bin:
- networkctl_bin = ns.networkctl_bin
- if ns.resolvectl_bin:
- resolvectl_bin = ns.resolvectl_bin
- if ns.timedatectl_bin:
- timedatectl_bin = ns.timedatectl_bin
- if ns.udevadm_bin:
- udevadm_bin = ns.udevadm_bin
+ build_dir = ns.build_dir
if ns.source_dir:
- systemd_source_dir = ns.source_dir
+ source_dir = ns.source_dir
else:
- systemd_source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
- if not os.path.exists(os.path.join(systemd_source_dir, "meson_options.txt")):
- raise RuntimeError(f"{systemd_source_dir} doesn't appear to be a systemd source tree")
+ source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
+ assert os.path.exists(os.path.join(source_dir, "meson_options.txt")), f"{source_dir} doesn't appear to be a systemd source tree."
use_valgrind = ns.use_valgrind
enable_debug = ns.enable_debug
udevadm_cmd = valgrind_cmd.split() + [udevadm_bin]
wait_online_cmd = valgrind_cmd.split() + [wait_online_bin]
+ if build_dir:
+ test_ndisc_send = os.path.normpath(os.path.join(build_dir, 'test-ndisc-send'))
+ else:
+ test_ndisc_send = '/usr/lib/tests/test-ndisc-send'
+
if asan_options:
env.update({'ASAN_OPTIONS': asan_options})
if lsan_options: