diff --git a/test/test-network/conf/25-dhcp-relay-downstream.network b/test/test-network/conf/25-dhcp-relay-downstream.network new file mode 100644 index 0000000000..49ff765e95 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-downstream.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-down + +[Network] +Address=198.51.100.10/24 +IPv6AcceptRA=no +DHCPRelay=downstream + +[DHCPRelay] +AgentAddress=198.51.100.10 +GatewayAddress=198.51.100.10 +CircuitId=string:test-dhcp-relay-circuit-id +VirtualSubnetSelection=string:subnet-hoge diff --git a/test/test-network/conf/25-dhcp-relay-upstream.network b/test/test-network/conf/25-dhcp-relay-upstream.network new file mode 100644 index 0000000000..d84f6a71fe --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-upstream.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-up + +[Network] +Address=192.0.2.10/24 +IPv6AcceptRA=no +DHCPRelay=upstream + +[DHCPRelay] +AgentAddress=192.0.2.10 +InterfacePriority=100 diff --git a/test/test-network/conf/25-dhcp-relay.conf b/test/test-network/conf/25-dhcp-relay.conf new file mode 100644 index 0000000000..5d6bcb2c4a --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[DHCPRelay] +ServerAddress=192.0.2.1 +OverrideServerIdentifier=yes +RemoteId=string:test-dhcp-relay-remote-id diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8609a63916..28c418460c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1222,6 +1222,7 @@ def tear_down_common(): # 3. remove network namespace call_quiet('ip netns del ns99') call_quiet('ip netns del ns-bridge') + call_quiet('ip netns del ns-relay') call_quiet('ip netns del ns-server') # 4. remove links @@ -8393,6 +8394,115 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): # For issue #30763. self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') + def test_sd_dhcp_relay(self): + check_output('ip netns add ns-relay') + check_output('ip netns add ns-server') + + check_output('ip link add relay-down type veth peer client') + check_output('ip link set relay-down netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-down up') + + check_output('ip link add relay-up type veth peer server') + check_output('ip link set relay-up netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-up up') + + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + with tempfile.TemporaryDirectory() as tmp: + conf_dir = pathlib.Path(tmp) / 'run/systemd/networkd.conf.d' + unit_dir = pathlib.Path(tmp) / 'run/systemd/network' + netif_dir = pathlib.Path(tmp) / 'run/systemd/netif' + mkdir_p(conf_dir) + mkdir_p(unit_dir) + mkdir_p(netif_dir) + + # Do not use shutil.chown(), as it fails when running with sanitizers. + check_output(f'chown systemd-network:systemd-network {netif_dir}') + + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay.conf', conf_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-downstream.network', unit_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-upstream.network', unit_dir) + + cmd = [ + 'systemd-run', + '--unit=networkd-test-dhcp-relay.service', + '--service-type=notify-reload', + '-p', 'User=systemd-network', + '-p', 'FileDescriptorStoreMax=512', + '-p', 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', 'CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', f'BindPaths={conf_dir}:/run/systemd/networkd.conf.d', + '-p', f'BindPaths={unit_dir}:/run/systemd/network', + '-p', f'BindPaths={netif_dir}:/run/systemd/netif', + '-p', 'TemporaryFileSystem=/run/dbus', + '-p', 'TemporaryFileSystem=/run/systemd/report', + '-p', 'TemporaryFileSystem=/run/systemd/resolve.hook', + '-p', 'TemporaryFileSystem=/var/lib/systemd/network', + '-p', 'TemporaryFileSystem=/etc/systemd/network', + '-p', 'TemporaryFileSystem=/usr/lib/systemd/network', + '-p', 'ReadOnlyPaths=/sys', + '-p', 'NetworkNamespacePath=/run/netns/ns-relay', + ] # fmt: skip + if enable_debug: + cmd += ['-p', 'Environment=SYSTEMD_LOG_LEVEL=debug'] + if asan_options: + cmd += ['-p', f'Environment=ASAN_OPTIONS={asan_options}'] + if lsan_options: + cmd += ['-p', f'Environment=LSAN_OPTIONS={lsan_options}'] + if ubsan_options: + cmd += ['-p', f'Environment=UBSAN_OPTIONS={ubsan_options}'] + if asan_options or lsan_options or ubsan_options: + cmd += ['-p', 'TimeoutStopFailureMode=abort'] + + cmd += [networkd_bin] + + try: + check_output(*cmd) + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + + copy_network_unit('25-dhcp-client-simple.network') + start_networkd() + self.wait_online('client:routable') + + print('## ip -4 address show dev client scope global') + output = check_output('ip -4 address show dev client scope global') + print(output) + self.assertRegex(output, r'198\.51\.100\.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + # fmt: off + self.assertRegex(output, r'default via 198\.51\.100\.10 proto dhcp src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.0/24 proto kernel scope link src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.10 proto dhcp scope link src 198\.51\.100\.10[0-9]') + # fmt: on + + print('## dnsmasq log') + output = read_dnsmasq_log_file() + print(output) + # The option 82 is logged as agent-info since dnsmasq-2.92, otherwise logged as agent-id. + self.assertRegex(output, r'option: 82 (agent-id|agent-info)') + + print('## journal of networkd-test-dhcp-relay.service') + check_output('journalctl --sync') + output = check_output('journalctl --no-hostname --output=short-monotonic --unit=networkd-test-dhcp-relay.service -I') # fmt: skip + self.assertIn('relay-down: DHCPv4 relay: Received BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Forwarded BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Received BOOTREPLY', output) + self.assertIn('relay-down: DHCPv4 relay: Forwarded BOOTREPLY', output) + finally: + call_quiet('systemctl stop networkd-test-dhcp-relay.service') + call_quiet('systemctl reset-failed networkd-test-dhcp-relay.service') + class NetworkdDHCPClientTests(unittest.TestCase, Utilities): def setUp(self):