diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index acc57ce53a..388c5cd2a4 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -819,7 +819,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; default: - log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); + log_debug("Ignoring DHCP option %"PRIu8" while parsing.", code); break; } diff --git a/test/test-network/conf/25-dhcp-client-gateway-ipv6.network b/test/test-network/conf/25-dhcp-client-gateway-ipv6.network deleted file mode 100644 index 11a2fee050..0000000000 --- a/test/test-network/conf/25-dhcp-client-gateway-ipv6.network +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=veth99 - -[Network] -DHCP=ipv6 - -[Route] -Gateway=_ipv6ra -Destination=2001:1234:5:9fff:ff:ff:ff:ff/128 diff --git a/test/test-network/conf/25-dhcp-client-ipv4-ipv6.network b/test/test-network/conf/25-dhcp-client-ipv4-ipv6.network deleted file mode 100644 index fe5a7ab058..0000000000 --- a/test/test-network/conf/25-dhcp-client-ipv4-ipv6.network +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=veth99 - -[Network] -DHCP=ipv4 -# Even if the DHCPv6 client is disabled in config, IPv6AcceptRA=yes -# (which is enabled by default) implies the DHCPv6 client. diff --git a/test/test-network/conf/25-dhcp-client-ipv4-only.network b/test/test-network/conf/25-dhcp-client-ipv4-only.network index a71c5c9af1..23c6f047ce 100644 --- a/test/test-network/conf/25-dhcp-client-ipv4-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv4-only.network @@ -22,6 +22,10 @@ ClientIdentifier=mac VendorClassIdentifier=FooBarVendorTest Label=test-label ListenPort=5555 +SendDecline=yes +# DenyList= will be ignored +AllowList=192.168.5.0/24 192.168.6.0/24 +DenyList=192.168.5.0/24 [Route] Destination=192.168.5.0/24 diff --git a/test/test-network/conf/25-dhcp-client-ipv6-only.network b/test/test-network/conf/25-dhcp-client-ipv6-only.network index f2874281f8..eb5bd027ff 100644 --- a/test/test-network/conf/25-dhcp-client-ipv6-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv6-only.network @@ -5,3 +5,7 @@ Name=veth99 [Network] DHCP=ipv6 IPv6Token=::1a:2b:3c:4d + +[Route] +Gateway=_ipv6ra +Destination=2001:1234:5:9fff:ff:ff:ff:ff/128 diff --git a/test/test-network/conf/25-dhcp-client-reassign-static-routes-ipv6.network b/test/test-network/conf/25-dhcp-client-reassign-static-routes-ipv6.network deleted file mode 100644 index 8a8b087bf8..0000000000 --- a/test/test-network/conf/25-dhcp-client-reassign-static-routes-ipv6.network +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=veth99 - -[Network] -IPv6AcceptRA=yes - -[Route] -Destination=2600:0:0:1::/64 diff --git a/test/test-network/conf/25-dhcp-server-decline.network b/test/test-network/conf/25-dhcp-server-decline.network deleted file mode 100644 index cb8e04ab0d..0000000000 --- a/test/test-network/conf/25-dhcp-server-decline.network +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=veth-peer - -[Network] -Address=192.168.5.1/24 -IPv6AcceptRA=false -DHCPServer=yes - -[DHCPServer] -PoolOffset=10 -PoolSize=100 -DNS=192.168.5.1 -NTP=192.168.5.1 diff --git a/test/test-network/conf/25-dhcp-v4-server-veth-peer.network b/test/test-network/conf/25-dhcp-v4-server-veth-peer.network deleted file mode 100644 index 7e1f56018a..0000000000 --- a/test/test-network/conf/25-dhcp-v4-server-veth-peer.network +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=veth-peer - -[Network] -IPv6AcceptRA=no -Address=192.168.0.1/24 -Address=192.168.5.1/24 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ada539ca15..46c182be46 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -52,6 +52,7 @@ timedatectl_bin=shutil.which('timedatectl', path=which_paths) use_valgrind=False enable_debug=True env = {} +wait_online_env = {} asan_options=None lsan_options=None ubsan_options=None @@ -421,6 +422,15 @@ def read_link_attr(*args): with open(os.path.join('/sys/class/net/', *args), encoding='utf-8') as f: return f.readline().strip() +def get_ifindex_by_name(link): + return read_link_attr(f'{link}/ifindex') + +def read_link_state_file(link): + ifindex = get_ifindex_by_name(link) + path = os.path.join('/run/systemd/netif/links/', ifindex) + with open(path, encoding='utf-8') as f: + return f.read() + def read_bridge_port_attr(bridge, link, attribute): path_bridge = os.path.join('/sys/devices/virtual/net', bridge) path_port = 'lower_' + link + '/brport' @@ -546,23 +556,10 @@ def stop_by_pid_file(pid_file): def stop_dnsmasq(): stop_by_pid_file(dnsmasq_pid_file) -def dump_dnsmasq_log_file(): +def read_dnsmasq_log_file(): if os.path.exists(dnsmasq_log_file): - with open (dnsmasq_log_file, encoding='utf-8') as in_file: - print(in_file.read()) - -def search_words_in_dnsmasq_log(words, show_all=False): - if os.path.exists(dnsmasq_log_file): - with open (dnsmasq_log_file, encoding='utf-8') as in_file: - contents = in_file.read() - if show_all: - print(contents) - for line in contents.splitlines(): - if words in line: - in_file.close() - print(f"{words}, {line}") - return True - return False + with open(dnsmasq_log_file, encoding='utf-8') as f: + return f.read() def remove_dnsmasq_lease_file(): if os.path.exists(dnsmasq_lease_file): @@ -657,9 +654,10 @@ class Utilities(): if not link_exists(link): continue output = check_output(*networkctl_cmd, '-n', '0', 'status', link, env=env) - print(output) if re.search(rf'(?m)^\s*State:\s+{operstate}\s+\({setup_state}\)\s*$', output): return True + + print(output) if fail_assert: self.fail(f'Timed out waiting for {link} to reach state {operstate}/{setup_state}') return False @@ -695,7 +693,7 @@ class Utilities(): if ipv6: args += ['--ipv6'] try: - check_output(*args, env=env) + check_output(*args, env=wait_online_env) except subprocess.CalledProcessError: for link in links_with_operstate: name = link.split(':')[0] @@ -2360,8 +2358,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): copy_unit_to_networkd_unit_path('25-address-ipv4acd-veth99.network.d/conflict-address.conf') run(*networkctl_cmd, 'reload', env=env) time.sleep(1) - rc = call(*wait_online_cmd, '--timeout=10s', '--interface=veth99:routable', env=env) - self.assertTrue(rc == 1) + self.wait_operstate('veth99', operstate='routable', setup_state='configuring', setup_timeout=10) output = check_output('ip -4 address show dev veth99') print(output) @@ -3658,37 +3655,30 @@ class NetworkdStateFileTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['dummy98:routable']) - output = check_output(*networkctl_cmd, '--no-legend', 'list', 'dummy98', env=env) - print(output) - ifindex = output.split()[0] - - path = os.path.join('/run/systemd/netif/links/', ifindex) - self.assertTrue(os.path.exists(path)) - # make link state file updated check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env) # TODO: check json string check_output(*networkctl_cmd, '--json=short', 'status', env=env) - with open(path, encoding='utf-8') as f: - data = f.read() - self.assertRegex(data, r'IPV4_ADDRESS_STATE=routable') - self.assertRegex(data, r'IPV6_ADDRESS_STATE=routable') - self.assertRegex(data, r'ADMIN_STATE=configured') - self.assertRegex(data, r'OPER_STATE=routable') - self.assertRegex(data, r'REQUIRED_FOR_ONLINE=yes') - self.assertRegex(data, r'REQUIRED_OPER_STATE_FOR_ONLINE=routable') - self.assertRegex(data, r'REQUIRED_FAMILY_FOR_ONLINE=both') - self.assertRegex(data, r'ACTIVATION_POLICY=up') - self.assertRegex(data, r'NETWORK_FILE=/run/systemd/network/25-state-file-tests.network') - self.assertRegex(data, r'DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com \[1111:2222::3333\]:1234#ccc.com') - self.assertRegex(data, r'NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org') - self.assertRegex(data, r'DOMAINS=hogehoge') - self.assertRegex(data, r'ROUTE_DOMAINS=foofoo') - self.assertRegex(data, r'LLMNR=no') - self.assertRegex(data, r'MDNS=yes') - self.assertRegex(data, r'DNSSEC=no') + output = read_link_state_file('dummy98') + print(output) + self.assertIn('IPV4_ADDRESS_STATE=routable', output) + self.assertIn('IPV6_ADDRESS_STATE=routable', output) + self.assertIn('ADMIN_STATE=configured', output) + self.assertIn('OPER_STATE=routable', output) + self.assertIn('REQUIRED_FOR_ONLINE=yes', output) + self.assertIn('REQUIRED_OPER_STATE_FOR_ONLINE=routable', output) + self.assertIn('REQUIRED_FAMILY_FOR_ONLINE=both', output) + self.assertIn('ACTIVATION_POLICY=up', output) + self.assertIn('NETWORK_FILE=/run/systemd/network/25-state-file-tests.network', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) + self.assertIn('DOMAINS=hogehoge', output) + self.assertIn('ROUTE_DOMAINS=foofoo', output) + self.assertIn('LLMNR=no', output) + self.assertIn('MDNS=yes', output) + self.assertIn('DNSSEC=no', output) check_output(*resolvectl_cmd, 'dns', 'dummy98', '10.10.10.12#ccc.com', '10.10.10.13', '1111:2222::3333', env=env) check_output(*resolvectl_cmd, 'domain', 'dummy98', 'hogehogehoge', '~foofoofoo', env=env) @@ -3700,45 +3690,45 @@ class NetworkdStateFileTests(unittest.TestCase, Utilities): # TODO: check json string check_output(*networkctl_cmd, '--json=short', 'status', env=env) - with open(path, encoding='utf-8') as f: - data = f.read() - self.assertRegex(data, r'DNS=10.10.10.12#ccc.com 10.10.10.13 1111:2222::3333') - self.assertRegex(data, r'NTP=2.fedora.pool.ntp.org 3.fedora.pool.ntp.org') - self.assertRegex(data, r'DOMAINS=hogehogehoge') - self.assertRegex(data, r'ROUTE_DOMAINS=foofoofoo') - self.assertRegex(data, r'LLMNR=yes') - self.assertRegex(data, r'MDNS=no') - self.assertRegex(data, r'DNSSEC=yes') + output = read_link_state_file('dummy98') + print(output) + self.assertIn('DNS=10.10.10.12#ccc.com 10.10.10.13 1111:2222::3333', output) + self.assertIn('NTP=2.fedora.pool.ntp.org 3.fedora.pool.ntp.org', output) + self.assertIn('DOMAINS=hogehogehoge', output) + self.assertIn('ROUTE_DOMAINS=foofoofoo', output) + self.assertIn('LLMNR=yes', output) + self.assertIn('MDNS=no', output) + self.assertIn('DNSSEC=yes', output) check_output(*timedatectl_cmd, 'revert', 'dummy98', env=env) # TODO: check json string check_output(*networkctl_cmd, '--json=short', 'status', env=env) - with open(path, encoding='utf-8') as f: - data = f.read() - self.assertRegex(data, r'DNS=10.10.10.12#ccc.com 10.10.10.13 1111:2222::3333') - self.assertRegex(data, r'NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org') - self.assertRegex(data, r'DOMAINS=hogehogehoge') - self.assertRegex(data, r'ROUTE_DOMAINS=foofoofoo') - self.assertRegex(data, r'LLMNR=yes') - self.assertRegex(data, r'MDNS=no') - self.assertRegex(data, r'DNSSEC=yes') + output = read_link_state_file('dummy98') + print(output) + self.assertIn('DNS=10.10.10.12#ccc.com 10.10.10.13 1111:2222::3333', output) + self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) + self.assertIn('DOMAINS=hogehogehoge', output) + self.assertIn('ROUTE_DOMAINS=foofoofoo', output) + self.assertIn('LLMNR=yes', output) + self.assertIn('MDNS=no', output) + self.assertIn('DNSSEC=yes', output) check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env) # TODO: check json string check_output(*networkctl_cmd, '--json=short', 'status', env=env) - with open(path, encoding='utf-8') as f: - data = f.read() - self.assertRegex(data, r'DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com \[1111:2222::3333\]:1234#ccc.com') - self.assertRegex(data, r'NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org') - self.assertRegex(data, r'DOMAINS=hogehoge') - self.assertRegex(data, r'ROUTE_DOMAINS=foofoo') - self.assertRegex(data, r'LLMNR=no') - self.assertRegex(data, r'MDNS=yes') - self.assertRegex(data, r'DNSSEC=no') + output = read_link_state_file('dummy98') + print(output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) + self.assertIn('DOMAINS=hogehoge', output) + self.assertIn('ROUTE_DOMAINS=foofoo', output) + self.assertIn('LLMNR=no', output) + self.assertIn('MDNS=yes', output) + self.assertIn('DNSSEC=no', output) class NetworkdBondTests(unittest.TestCase, Utilities): links = [ @@ -4490,28 +4480,17 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): '25-veth.netdev', '25-vrf.netdev', '25-vrf.network', - '25-dhcp-client-allow-list.network', '25-dhcp-client-anonymize.network', - '25-dhcp-client-decline.network', - '25-dhcp-client-gateway-ipv6.network', '25-dhcp-client-gateway-onlink-implicit.network', - '25-dhcp-client-ipv4-ipv6.network', '25-dhcp-client-ipv4-only.network', '25-dhcp-client-ipv4-use-routes-use-gateway.network', '25-dhcp-client-ipv6-only.network', '25-dhcp-client-keep-configuration-dhcp-on-stop.network', '25-dhcp-client-keep-configuration-dhcp.network', - '25-dhcp-client-reassign-static-routes-ipv6.network', - '25-dhcp-client-use-dns-ipv4-and-ra.network', - '25-dhcp-client-use-dns-ipv4.network', - '25-dhcp-client-use-dns-no.network', - '25-dhcp-client-use-dns-yes.network', '25-dhcp-client-vrf.network', '25-dhcp-client-with-ipv4ll.network', '25-dhcp-client.network', - '25-dhcp-server-decline.network', '25-dhcp-server-veth-peer.network', - '25-dhcp-v4-server-veth-peer.network', '25-static.network'] def setUp(self): @@ -4543,6 +4522,11 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, r'inet6 2600::[0-9a-f:]*/128 scope global dynamic noprefixroute') self.assertNotIn('192.168.5', output) + # checking semi-static route + output = check_output('ip -6 route list dev veth99 2001:1234:5:9fff:ff:ff:ff:ff') + print(output) + self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd') + # Confirm that ipv6 token is not set in the kernel output = check_output('ip token show dev veth99') print(output) @@ -4587,22 +4571,20 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, '192.168.5.7 proto dhcp scope link src 192.168.5.11[0-9] metric 24') self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output) + print('## link state file') + output = read_link_state_file('veth99') + print(output) # checking DNS server and Domains - output = check_output(*networkctl_cmd, '--no-legend', 'list', 'veth99', env=env) - ifindex = output.split()[0] - path = os.path.join('/run/systemd/netif/links/', ifindex) - self.assertTrue(os.path.exists(path)) - with open(path, encoding='utf-8') as f: - data = f.read() - print(data) - self.assertIn('DNS=192.168.5.6 192.168.5.7', data) - self.assertIn('DOMAINS=example.com', data) + self.assertIn('DNS=192.168.5.6 192.168.5.7', output) + self.assertIn('DOMAINS=example.com', output) print('## dnsmasq log') - self.assertTrue(search_words_in_dnsmasq_log('vendor class: FooBarVendorTest', True)) - self.assertTrue(search_words_in_dnsmasq_log('DHCPDISCOVER(veth-peer) 12:34:56:78:9a:bc')) - self.assertTrue(search_words_in_dnsmasq_log('client provides name: test-hostname')) - self.assertTrue(search_words_in_dnsmasq_log('26:mtu')) + output = read_dnsmasq_log_file() + print(output) + self.assertIn('vendor class: FooBarVendorTest', output) + self.assertIn('DHCPDISCOVER(veth-peer) 12:34:56:78:9a:bc', output) + self.assertIn('client provides name: test-hostname', output) + self.assertIn('26:mtu', output) # change address range, DNS servers, and Domains stop_dnsmasq() @@ -4612,8 +4594,9 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): lease_time='2m') # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 - print('Wait for the dynamic address to be renewed') - time.sleep(125) + print('Wait for the DHCP lease to be expired') + self.wait_address_dropped('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4', timeout_sec=120) + self.wait_address('veth99', r'inet 192.168.5.12[0-9]*/24', ipv='-4') self.wait_online(['veth99:routable', 'veth-peer:routable']) @@ -4648,22 +4631,20 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, '192.168.5.8 proto dhcp scope link src 192.168.5.12[0-9] metric 24') self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output) + print('## link state file') + output = read_link_state_file('veth99') + print(output) # checking DNS server and Domains - output = check_output(*networkctl_cmd, '--no-legend', 'list', 'veth99', env=env) - ifindex = output.split()[0] - path = os.path.join('/run/systemd/netif/links/', ifindex) - self.assertTrue(os.path.exists(path)) - with open(path, encoding='utf-8') as f: - data = f.read() - print(data) - self.assertIn('DNS=192.168.5.1 192.168.5.7 192.168.5.8', data) - self.assertIn('DOMAINS=foo.example.com', data) + self.assertIn('DNS=192.168.5.1 192.168.5.7 192.168.5.8', output) + self.assertIn('DOMAINS=foo.example.com', output) print('## dnsmasq log') - self.assertTrue(search_words_in_dnsmasq_log('vendor class: FooBarVendorTest', True)) - self.assertTrue(search_words_in_dnsmasq_log('DHCPDISCOVER(veth-peer) 192.168.5.11')) - self.assertTrue(search_words_in_dnsmasq_log('client provides name: test-hostname')) - self.assertTrue(search_words_in_dnsmasq_log('26:mtu')) + output = read_dnsmasq_log_file() + print(output) + self.assertIn('vendor class: FooBarVendorTest', output) + self.assertIn('DHCPDISCOVER(veth-peer) 192.168.5.11', output) + self.assertIn('client provides name: test-hostname', output) + self.assertIn('26:mtu', output) def test_dhcp_client_ipv4_use_routes_gateway(self): for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4): @@ -4742,22 +4723,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): # TODO: check json string check_output(*networkctl_cmd, '--json=short', 'status', env=env) - def test_dhcp_client_ipv4_ipv6(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-ipv6.network') - start_networkd() - self.wait_online(['veth-peer:carrier']) - start_dnsmasq() - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - # link become 'routable' when at least one protocol provide an valid address. - 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') - - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env) - print(output) - self.assertRegex(output, '2600::') - self.assertRegex(output, '192.168.5') - def test_dhcp_client_settings_anonymize(self): copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network') start_networkd() @@ -4765,43 +4730,17 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_dnsmasq() self.wait_online(['veth99:routable', 'veth-peer:routable']) - self.assertFalse(search_words_in_dnsmasq_log('VendorClassIdentifier=SusantVendorTest', True)) - self.assertFalse(search_words_in_dnsmasq_log('test-hostname')) - self.assertFalse(search_words_in_dnsmasq_log('26:mtu')) - - def test_dhcp_client_reassign_static_routes_ipv6(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-reassign-static-routes-ipv6.network') - start_networkd() - self.wait_online(['veth-peer:carrier']) - start_dnsmasq(lease_time='2m') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - output = check_output('ip address show dev veth99 scope global') + print('## dnsmasq log') + output = read_dnsmasq_log_file() print(output) - self.assertRegex(output, r'inet6 2600::[0-9a-f]*/128 scope global (noprefixroute dynamic|dynamic noprefixroute)') - - output = check_output('ip -6 route show dev veth99') - print(output) - self.assertRegex(output, r'2600::/64 proto ra metric 1024') - self.assertRegex(output, r'2600:0:0:1::/64 proto static metric 1024 pref medium') - - stop_dnsmasq() - start_dnsmasq(ipv6_range='2600::30,2600::40', lease_time='2m') - - # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 - print('Wait for the dynamic address to be renewed') - time.sleep(125) - - self.wait_online(['veth99:routable']) - - output = check_output('ip -6 route show dev veth99') - print(output) - self.assertRegex(output, r'2600::/64 proto ra metric 1024') - self.assertRegex(output, r'2600:0:0:1::/64 proto static metric 1024 pref medium') + self.assertNotIn('VendorClassIdentifier=SusantVendorTest', output) + self.assertNotIn('test-hostname', output) + self.assertNotIn('26:mtu', output) def test_dhcp_keep_configuration_dhcp(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-v4-server-veth-peer.network', '25-dhcp-client-keep-configuration-dhcp.network') + copy_unit_to_networkd_unit_path('25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dhcp.network') start_networkd() self.wait_online(['veth-peer:carrier']) start_dnsmasq(lease_time='2m') @@ -4809,35 +4748,29 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') - - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env) - print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' + 'valid_lft forever preferred_lft forever') # Stopping dnsmasq as networkd won't be allowed to renew the DHCP lease. stop_dnsmasq() # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 - print('Wait for the dynamic address to be expired') - time.sleep(125) + print('Wait for the DHCP lease to be expired') + time.sleep(120) - print('The lease address should be kept after lease expired') + # The lease address should be kept after the lease expired output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' + 'valid_lft forever preferred_lft forever') - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env) - print(output) - self.assertRegex(output, r'192.168.5.*') + stop_networkd() - check_output('systemctl stop systemd-networkd.socket') - check_output('systemctl stop systemd-networkd.service') - - print('The lease address should be kept after networkd stopped') + # The lease address should be kept after networkd stopped output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' + 'valid_lft forever preferred_lft forever') with open(os.path.join(network_unit_file_path, '25-dhcp-client-keep-configuration-dhcp.network'), mode='a', encoding='utf-8') as f: f.write('[Network]\nDHCP=no\n') @@ -4845,17 +4778,16 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['veth99:routable', 'veth-peer:routable']) - print('Still the lease address should be kept after networkd restarted') + # Still the lease address should be kept after networkd restarted output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') - - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env) - print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' + 'valid_lft forever preferred_lft forever') def test_dhcp_keep_configuration_dhcp_on_stop(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-v4-server-veth-peer.network', '25-dhcp-client-keep-configuration-dhcp-on-stop.network') + copy_unit_to_networkd_unit_path('25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dhcp-on-stop.network') start_networkd() self.wait_online(['veth-peer:carrier']) start_dnsmasq(lease_time='2m') @@ -4863,22 +4795,21 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') stop_dnsmasq() - check_output('systemctl stop systemd-networkd.socket') - check_output('systemctl stop systemd-networkd.service') + stop_networkd() output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'192.168.5.*') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') - restart_networkd(3) + start_networkd(3) self.wait_online(['veth-peer:routable']) output = check_output('ip address show dev veth99 scope global') print(output) - self.assertNotRegex(output, r'192.168.5.*') + self.assertNotIn('192.168.5.', output) def test_dhcp_client_reuse_address_as_static(self): copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network') @@ -4892,13 +4823,9 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') output = check_output('ip address show dev veth99 scope global') - print(output) - self.assertRegex(output, '192.168.5') - self.assertRegex(output, '2600::') - - ipv4_address = re.search(r'192.168.5.[0-9]*/24', output) - ipv6_address = re.search(r'2600::[0-9a-f:]*/128', output) - static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address.group(), 'Address=' + ipv6_address.group()]) + ipv4_address = re.search(r'192.168.5.[0-9]*/24', output).group() + ipv6_address = re.search(r'2600::[0-9a-f:]*/128', output).group() + static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address, 'Address=' + ipv6_address]) print(static_network) remove_unit_from_networkd_path(['25-dhcp-client.network']) @@ -4906,20 +4833,18 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): with open(os.path.join(network_unit_file_path, '25-static.network'), mode='w', encoding='utf-8') as f: f.write(static_network) - # When networkd started, the links are already configured, so let's wait for 5 seconds - # the links to be re-configured. - restart_networkd(5) - self.wait_online(['veth99:routable', 'veth-peer:routable']) + restart_networkd() + self.wait_online(['veth99:routable']) output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, '192.168.5') - self.assertRegex(output, 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *' + 'valid_lft forever preferred_lft forever') output = check_output('ip -6 address show dev veth99 scope global') print(output) - self.assertRegex(output, '2600::') - self.assertRegex(output, 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *' + 'valid_lft forever preferred_lft forever') @expectedFailureIfModuleIsNotAvailable('vrf') def test_dhcp_client_vrf(self): @@ -4965,18 +4890,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertEqual(output, '') - def test_dhcp_client_gateway_ipv6(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-gateway-ipv6.network') - start_networkd() - self.wait_online(['veth-peer:carrier']) - start_dnsmasq() - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - output = check_output('ip -6 route list dev veth99 2001:1234:5:9fff:ff:ff:ff:ff') - print(output) - self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd') - def test_dhcp_client_gateway_onlink_implicit(self): copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-gateway-onlink-implicit.network') @@ -4996,44 +4909,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'onlink') - def test_dhcp_client_with_ipv4ll_with_dhcp_server(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-with-ipv4ll.network') - start_networkd() - self.wait_online(['veth-peer:carrier']) - start_dnsmasq(lease_time='2m') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - output = check_output('ip address show dev veth99') - print(output) - - output = check_output('ip -6 address show dev veth99 scope global dynamic') - self.assertNotRegex(output, r'inet6 2600::[0-9a-f]+/128 scope global dynamic') - output = check_output('ip -6 address show dev veth99 scope link') - self.assertRegex(output, r'inet6 .* scope link') - output = check_output('ip -4 address show dev veth99 scope global dynamic') - self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') - output = check_output('ip -4 address show dev veth99 scope link') - self.assertNotRegex(output, r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link') - - print('Wait for the dynamic address to be expired') - time.sleep(130) - - output = check_output('ip address show dev veth99') - print(output) - - output = check_output('ip -6 address show dev veth99 scope global dynamic') - self.assertNotRegex(output, r'inet6 2600::[0-9a-f]+/128 scope global dynamic') - output = check_output('ip -6 address show dev veth99 scope link') - self.assertRegex(output, r'inet6 .* scope link') - output = check_output('ip -4 address show dev veth99 scope global dynamic') - self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') - output = check_output('ip -4 address show dev veth99 scope link') - self.assertNotRegex(output, r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link') - - search_words_in_dnsmasq_log('DHCPOFFER', show_all=True) - - def test_dhcp_client_with_ipv4ll_without_dhcp_server(self): + def test_dhcp_client_with_ipv4ll(self): copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-with-ipv4ll.network') start_networkd() @@ -5041,116 +4917,80 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): # systemd-networkd to get the dhcpv4 transient failure event self.wait_online(['veth99:degraded', 'veth-peer:routable'], timeout='60s') - output = check_output('ip address show dev veth99') + output = check_output('ip -4 address show dev veth99') print(output) - - output = check_output('ip -6 address show dev veth99 scope global dynamic') - self.assertNotRegex(output, r'inet6 2600::[0-9a-f]+/128 scope global dynamic') - output = check_output('ip -6 address show dev veth99 scope link') - self.assertRegex(output, r'inet6 .* scope link') - output = check_output('ip -4 address show dev veth99 scope global dynamic') - self.assertNotRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') - output = check_output('ip -4 address show dev veth99 scope link') + self.assertNotIn('192.168.5.', output) self.assertRegex(output, r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link') start_dnsmasq(lease_time='2m') + print('Wait for a DHCP lease to be acquired and the IPv4LL address to be dropped') self.wait_address('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4') - self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.255\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_online(['veth99:routable']) - def test_dhcp_client_use_dns_yes(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-use-dns-yes.network') + output = check_output('ip -4 address show dev veth99') + print(output) + self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') + self.assertNotIn('169.254.', output) + self.assertNotIn('scope link', output) + + stop_dnsmasq() + print('Wait for the DHCP lease to be expired and an IPv4LL address to be acquired') + self.wait_address_dropped('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4', timeout_sec=120) + self.wait_address('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + + output = check_output('ip -4 address show dev veth99') + print(output) + self.assertNotIn('192.168.5.', output) + self.assertRegex(output, r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link') + + def test_dhcp_client_use_dns(self): + def check(self, ipv4, ipv6): + os.makedirs(os.path.join(network_unit_file_path, '25-dhcp-client.network.d'), exist_ok=True) + with open(os.path.join(network_unit_file_path, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + f.write('[DHCPv4]\nUseDNS=') + f.write('yes' if ipv4 else 'no') + f.write('\n[DHCPv6]\nUseDNS=') + f.write('yes' if ipv6 else 'no') + f.write('\n[IPv6AcceptRA]\nUseDNS=no') + + check_output(*networkctl_cmd, 'reload', env=env) + # 'networkctl reload' asynchronously reconfigure links. + # Hence, we need to wait for a short time for the link to be in configuring state. + time.sleep(1) + 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') + + # make resolved re-read the link state file + check_output(*resolvectl_cmd, 'revert', 'veth99', env=env) + + output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env) + print(output) + if ipv4: + self.assertIn('192.168.5.1', output) + else: + self.assertNotIn('192.168.5.1', output) + if ipv6: + self.assertIn('2600::1', output) + else: + self.assertNotIn('2600::1', output) + + # TODO: check json string + check_output(*networkctl_cmd, '--json=short', 'status', env=env) + + copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', dropins=False) start_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]') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - # link become 'routable' when at least one protocol provide an valid address. - 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') - - time.sleep(3) - output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env) - print(output) - self.assertRegex(output, '192.168.5.1') - self.assertRegex(output, '2600::1') - - # TODO: check json string - check_output(*networkctl_cmd, '--json=short', 'status', env=env) - - def test_dhcp_client_use_dns_no(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-use-dns-no.network') - - start_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]') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - # link become 'routable' when at least one protocol provide an valid address. - 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') - - time.sleep(3) - output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env) - print(output) - self.assertNotRegex(output, '192.168.5.1') - self.assertNotRegex(output, '2600::1') - - def test_dhcp_client_use_dns_ipv4(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-use-dns-ipv4.network') - - start_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]') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - # link become 'routable' when at least one protocol provide an valid address. - 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') - - time.sleep(3) - output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env) - print(output) - self.assertRegex(output, '192.168.5.1') - self.assertNotRegex(output, '2600::1') - - def test_dhcp_client_use_dns_ipv4_and_ra(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-use-dns-ipv4-and-ra.network') - - start_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]') - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - # link become 'routable' when at least one protocol provide an valid address. - 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') - - time.sleep(3) - output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env) - print(output) - self.assertRegex(output, '192.168.5.1') - self.assertRegex(output, '2600::1') - - def test_dhcp_client_decline(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-decline.network', '25-dhcp-client-decline.network') - - start_networkd() - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - output = check_output('ip -4 address show dev veth99 scope global dynamic') - print(output) - self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') - - def test_dhcp_client_allow_list(self): - copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-decline.network', '25-dhcp-client-allow-list.network') - - start_networkd() - self.wait_online(['veth99:routable', 'veth-peer:routable']) - - output = check_output('ip -4 address show dev veth99 scope global dynamic') - print(output) - self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + check(self, True, True) + check(self, True, False) + check(self, False, True) + check(self, False, False) class NetworkdDHCPPDTests(unittest.TestCase, Utilities): links = [ @@ -5613,17 +5453,8 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities): self.verify_dhcp4_6rd(tunnel_name) - # Test for renewing/rebinding lease - print('wait for 120 sec') - time.sleep(30) - print('wait for 90 sec') - time.sleep(30) - print('wait for 60 sec') - time.sleep(30) - print('wait for 30 sec') - time.sleep(30) - - dump_dnsmasq_log_file() + print('Wait for the DHCP lease to be expired') + time.sleep(120) self.wait_online(['veth99:routable', 'test1:routable', 'dummy97:routable', 'dummy98:routable', 'dummy99:degraded', 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable']) @@ -5882,8 +5713,6 @@ if __name__ == '__main__': timedatectl_cmd = [timedatectl_bin] wait_online_cmd = [wait_online_bin] - if enable_debug: - env.update({ 'SYSTEMD_LOG_LEVEL' : 'debug' }) if asan_options: env.update({ 'ASAN_OPTIONS' : asan_options }) if lsan_options: @@ -5891,5 +5720,9 @@ if __name__ == '__main__': if ubsan_options: env.update({ 'UBSAN_OPTIONS' : ubsan_options }) + wait_online_env = env.copy() + if enable_debug: + wait_online_env.update({ 'SYSTEMD_LOG_LEVEL' : 'debug' }) + sys.argv[1:] = unknown_args unittest.main(verbosity=3)