From: Yu Watanabe Date: Thu, 14 Oct 2021 17:51:18 +0000 (+0900) Subject: test-network: add test cases for DHCPv6 prefix delegation X-Git-Tag: v250-rc1~420^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=caad88a22bcbf3062c062f01105a8959b809f2ad;p=thirdparty%2Fsystemd.git test-network: add test cases for DHCPv6 prefix delegation --- diff --git a/test/test-network/conf/13-dummy.netdev b/test/test-network/conf/13-dummy.netdev new file mode 100644 index 00000000000..5f34b2f5640 --- /dev/null +++ b/test/test-network/conf/13-dummy.netdev @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Name=dummy99 +Kind=dummy diff --git a/test/test-network/conf/25-veth-downstream.netdev b/test/test-network/conf/25-veth-downstream.netdev new file mode 100644 index 00000000000..1329d915c75 --- /dev/null +++ b/test/test-network/conf/25-veth-downstream.netdev @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Name=veth98 +Kind=veth +MACAddress=12:34:56:78:9a:be + +[Peer] +Name=veth98-peer +MACAddress=12:34:56:78:9a:bf diff --git a/test/test-network/conf/dhcp6pd-downstream-dummy97.network b/test/test-network/conf/dhcp6pd-downstream-dummy97.network new file mode 100644 index 00000000000..01174f3bb0c --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-dummy97.network @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy97 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=no +DHCPv6PrefixDelegation=yes + +[DHCPv6PrefixDelegation] +UplinkInterface=veth99 +SubnetId=6 +Announce=no +Token=eui64 +Token=::1a:2b:3c:4d diff --git a/test/test-network/conf/dhcp6pd-downstream-dummy98.network b/test/test-network/conf/dhcp6pd-downstream-dummy98.network new file mode 100644 index 00000000000..8260e0cc7c8 --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-dummy98.network @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy98 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=no +DHCPv6PrefixDelegation=yes + +[DHCPv6PrefixDelegation] +UplinkInterface=veth99 +SubnetId=3 +Announce=no +Token=eui64 +Token=::1a:2b:3c:4d diff --git a/test/test-network/conf/dhcp6pd-downstream-dummy99.network b/test/test-network/conf/dhcp6pd-downstream-dummy99.network new file mode 100644 index 00000000000..ad56f262622 --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-dummy99.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy99 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=no +DHCPv6PrefixDelegation=yes + +[DHCPv6PrefixDelegation] +UplinkInterface=veth99 +Assign=no +Announce=no diff --git a/test/test-network/conf/dhcp6pd-downstream-test1.network b/test/test-network/conf/dhcp6pd-downstream-test1.network new file mode 100644 index 00000000000..78ddc72e2d9 --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-test1.network @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test1 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=no +DHCPv6PrefixDelegation=yes + +[DHCPv6PrefixDelegation] +UplinkInterface=veth99 +SubnetId=0 +Announce=no +Token=eui64 +Token=::1a:2b:3c:4d diff --git a/test/test-network/conf/dhcp6pd-downstream-veth98-peer.network b/test/test-network/conf/dhcp6pd-downstream-veth98-peer.network new file mode 100644 index 00000000000..db0aec1809a --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-veth98-peer.network @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth98-peer + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=yes + +[IPv6AcceptRA] +Token=eui64 +Token=::1a:2b:3c:4e diff --git a/test/test-network/conf/dhcp6pd-downstream-veth98.network b/test/test-network/conf/dhcp6pd-downstream-veth98.network new file mode 100644 index 00000000000..14c543d1bcf --- /dev/null +++ b/test/test-network/conf/dhcp6pd-downstream-veth98.network @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth98 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=no +DHCPv6PrefixDelegation=yes +IPv6SendRA=yes + +[DHCPv6PrefixDelegation] +UplinkInterface=veth99 +SubnetId=9 +Announce=yes +Token=eui64 +Token=::1a:2b:3c:4d + +[IPv6SendRA] +UplinkInterface=:none +EmitDNS=no +EmitDomains=no diff --git a/test/test-network/conf/dhcp6pd-server.network b/test/test-network/conf/dhcp6pd-server.network new file mode 100644 index 00000000000..f7a852665e8 --- /dev/null +++ b/test/test-network/conf/dhcp6pd-server.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth-peer + +[Network] +IPv6AcceptRA=no +Address=3ffe:501:ffff:100::1/64 diff --git a/test/test-network/conf/dhcp6pd-upstream.network b/test/test-network/conf/dhcp6pd-upstream.network new file mode 100644 index 00000000000..dd26f2ae5fc --- /dev/null +++ b/test/test-network/conf/dhcp6pd-upstream.network @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth99 + +[Network] +IPv6PrivacyExtensions=yes +IPv6AcceptRA=no +DHCP=ipv6 +DHCPv6PrefixDelegation=yes + +[DHCPv6] +WithoutRA=solicit + +[DHCPv6PrefixDelegation] +UplinkInterface=:self +SubnetId=10 +Announce=no +Token=eui64 +Token=::1a:2b:3c:4d diff --git a/test/test-network/conf/isc-dhcpd-dhcp6pd.conf b/test/test-network/conf/isc-dhcpd-dhcp6pd.conf new file mode 100644 index 00000000000..c95b00e0044 --- /dev/null +++ b/test/test-network/conf/isc-dhcpd-dhcp6pd.conf @@ -0,0 +1,21 @@ +default-lease-time 2592000; +preferred-lifetime 604800; + +option dhcp-renewal-time 3600; +option dhcp-rebinding-time 7200; + +# Enable RFC 5007 support (same than for DHCPv4) +allow leasequery; + +option dhcp6.name-servers 3ffe:501:ffff:100:200:ff:fe00:3f3e; +option dhcp6.domain-search "test.example.com","example.com"; + +option dhcp6.info-refresh-time 21600; + +subnet6 3ffe:501:ffff:100::/64 { + # Addresses available to clients + range6 3ffe:501:ffff:100::10 3ffe:501:ffff:100::100; + + # Some /64 prefixes available for Prefix Delegation (RFC 3633) + prefix6 3ffe:501:ffff:200:: 3ffe:501:ffff:f00:: /56; +} diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ac5447b9ffd..fc46c31c5da 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -14,6 +14,7 @@ import sys import time import unittest from shutil import copytree +from pathlib import Path network_unit_file_path='/run/systemd/network' networkd_runtime_directory='/run/systemd/netif' @@ -26,6 +27,9 @@ dnsmasq_pid_file='/run/networkd-ci/test-dnsmasq.pid' dnsmasq_log_file='/run/networkd-ci/test-dnsmasq.log' dnsmasq_lease_file='/run/networkd-ci/test-dnsmasq.lease' +isc_dhcpd_pid_file='/run/networkd-ci/test-isc-dhcpd.pid' +isc_dhcpd_lease_file='/run/networkd-ci/test-isc-dhcpd.lease' + systemd_lib_paths=['/usr/lib/systemd', '/lib/systemd'] which_paths=':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':')) @@ -531,6 +535,17 @@ def remove_dnsmasq_log_file(): if os.path.exists(dnsmasq_log_file): os.remove(dnsmasq_log_file) +def start_isc_dhcpd(interface, conf_file): + conf_file_path = os.path.join(networkd_ci_path, conf_file) + isc_dhcpd_command = f'dhcpd -6 -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' + Path(isc_dhcpd_lease_file).touch() + check_output(isc_dhcpd_command) + +def stop_isc_dhcpd(): + stop_by_pid_file(isc_dhcpd_pid_file) + if os.path.exists(isc_dhcpd_lease_file): + os.remove(isc_dhcpd_lease_file) + def remove_networkd_state_files(): if os.path.exists(os.path.join(networkd_runtime_directory, 'state')): os.remove(os.path.join(networkd_runtime_directory, 'state')) @@ -4846,6 +4861,184 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') +class NetworkdDHCP6PDTests(unittest.TestCase, Utilities): + links = [ + 'dummy97', + 'dummy98', + 'dummy99', + 'test1', + 'veth98', + 'veth99', + ] + + units = [ + '11-dummy.netdev', + '12-dummy.netdev', + '13-dummy.netdev', + '25-veth.netdev', + '25-veth-downstream.netdev', + 'dhcp6pd-downstream-dummy97.network', + 'dhcp6pd-downstream-dummy98.network', + 'dhcp6pd-downstream-dummy99.network', + 'dhcp6pd-downstream-test1.network', + 'dhcp6pd-downstream-veth98.network', + 'dhcp6pd-downstream-veth98-peer.network', + 'dhcp6pd-server.network', + 'dhcp6pd-upstream.network', + ] + + def setUp(self): + stop_isc_dhcpd() + remove_links(self.links) + stop_networkd(show_logs=False) + + def tearDown(self): + stop_isc_dhcpd() + remove_links(self.links) + remove_unit_from_networkd_path(self.units) + stop_networkd(show_logs=True) + + def test_dhcp6pd(self): + copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp6pd-server.network', 'dhcp6pd-upstream.network', + '25-veth-downstream.netdev', 'dhcp6pd-downstream-veth98.network', 'dhcp6pd-downstream-veth98-peer.network', + '11-dummy.netdev', 'dhcp6pd-downstream-test1.network', + 'dhcp6pd-downstream-dummy97.network', + '12-dummy.netdev', 'dhcp6pd-downstream-dummy98.network', + '13-dummy.netdev', 'dhcp6pd-downstream-dummy99.network') + + start_networkd() + self.wait_online(['veth-peer:carrier']) + start_isc_dhcpd('veth-peer', 'isc-dhcpd-dhcp6pd.conf') + self.wait_online(['veth-peer:routable', 'veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', + 'veth98:routable', 'veth98-peer:routable']) + + print('### ip -6 address show dev veth-peer scope global') + output = check_output('ip -6 address show dev veth-peer scope global') + print(output) + self.assertIn('inet6 3ffe:501:ffff:100::1/64 scope global', output) + + print('### ip -6 address show dev veth99 scope global') + output = check_output('ip -6 address show dev veth99 scope global') + print(output) + # IA_NA + self.assertRegex(output, 'inet6 3ffe:501:ffff:100::[0-9]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (Token=eui64) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + # Note that the temporary addresses may appear after the link enters configured state + self.wait_address('veth99', 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 address show dev test1 scope global') + output = check_output('ip -6 address show dev test1 scope global') + print(output) + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + self.wait_address('test1', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 address show dev dummy98 scope global') + output = check_output('ip -6 address show dev dummy98 scope global') + print(output) + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]03:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]03:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 address show dev veth98 scope global') + output = check_output('ip -6 address show dev veth98 scope global') + print(output) + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (Token=eui64) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + self.wait_address('veth98', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 address show dev veth98-peer scope global') + output = check_output('ip -6 address show dev veth98-peer scope global') + print(output) + # NDisc address (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + # NDisc address (Token=eui64) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + # NDisc address (temporary) + self.wait_address('veth98-peer', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 address show dev dummy99 scope global') + output = check_output('ip -6 address show dev dummy98 scope global') + print(output) + + print('### ip -6 route show type unreachable') + output = check_output('ip -6 route show type unreachable') + print(output) + self.assertRegex(output, 'unreachable 3ffe:501:ffff:[2-9a-f]00::/56 dev lo proto dhcp') + + print('### ip -6 route show dev veth99') + output = check_output('ip -6 route show dev veth99') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]10::/64 proto kernel metric [0-9]* expires') + + print('### ip -6 route show dev test1') + output = check_output('ip -6 route show dev test1') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]00::/64 proto kernel metric [0-9]* expires') + + print('### ip -6 route show dev dummy98') + output = check_output('ip -6 route show dev dummy98') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]03::/64 proto kernel metric [0-9]* expires') + + print('### ip -6 route show dev dummy99') + output = check_output('ip -6 route show dev dummy99') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]01::/64 proto dhcp metric [0-9]* expires') + + print('### ip -6 route show dev veth98') + output = check_output('ip -6 route show dev veth98') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]09::/64 proto kernel metric [0-9]* expires') + + print('### ip -6 route show dev veth98-peer') + output = check_output('ip -6 route show dev veth98-peer') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]09::/64 proto ra metric [0-9]* expires') + + # Test case for a downstream which appears later + check_output('ip link add dummy97 type dummy') + self.wait_online(['dummy97:routable']) + + print('### ip -6 address show dev dummy97 scope global') + output = check_output('ip -6 address show dev dummy97 scope global') + print(output) + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]06:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + self.wait_address('dummy97', 'inet6 3ffe:501:ffff:[2-9a-f]06:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 route show dev dummy97') + output = check_output('ip -6 route show dev dummy97') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]06::/64 proto kernel metric [0-9]* expires') + + # Test case for reconfigure + check_output(*networkctl_cmd, 'reconfigure', 'dummy98', env=env) + self.wait_online(['dummy98:routable']) + + print('### ip -6 address show dev dummy98 scope global') + output = check_output('ip -6 address show dev dummy98 scope global') + print(output) + # address in IA_PD (Token=static) + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]03:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + # address in IA_PD (temporary) + self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]03:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + + print('### ip -6 route show dev dummy98') + output = check_output('ip -6 route show dev dummy98') + print(output) + self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]03::/64 proto kernel metric [0-9]* expires') + class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): links = [ 'dummy98',