From 50ba4e401aa00e30e3f7261360f11e7ff7c0c73e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 16 Apr 2024 16:23:36 +0900 Subject: [PATCH 1/5] sd-radv: refuse packet from the same interface Prompted by https://github.com/systemd/systemd/pull/32267#discussion_r1566721306. --- src/libsystemd-network/radv-internal.h | 1 + src/libsystemd-network/sd-radv.c | 16 ++++++++++++++++ src/network/networkd-radv.c | 4 ++++ src/systemd/sd-radv.h | 1 + 4 files changed, 22 insertions(+) diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 8091a5d882b..d2ec912d496 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -98,6 +98,7 @@ struct sd_radv { int ifindex; char *ifname; + struct in6_addr ipv6ll; sd_event *event; int event_priority; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 79210f9e2ca..62b69e633ff 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -260,6 +260,10 @@ static int radv_process_packet(sd_radv *ra, ICMP6Packet *packet) { r = sd_ndisc_router_solicit_get_sender_address(rs, &src); if (r < 0 && r != -ENODATA) /* null address is allowed */ return log_radv_errno(ra, r, "Failed to get sender address of RS, ignoring: %m"); + if (r >= 0 && in6_addr_equal(&src, &ra->ipv6ll)) + /* This should be definitely caused by a misconfiguration. If we send RA to ourself, the + * kernel complains about that. Let's ignore the packet. */ + return log_radv_errno(ra, SYNTHETIC_ERRNO(EADDRINUSE), "Received RS from the same interface, ignoring."); r = radv_send_router(ra, &src, ra->lifetime_usec); if (r < 0) @@ -472,6 +476,18 @@ int sd_radv_get_ifname(sd_radv *ra, const char **ret) { return 0; } +int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr) { + assert_return(ra, -EINVAL); + assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL); + + if (addr) + ra->ipv6ll = *addr; + else + zero(ra->ipv6ll); + + return 0; +} + int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) { assert_return(ra, -EINVAL); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index b137b9384f7..4291165a913 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -769,6 +769,10 @@ int radv_start(Link *link) { return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); } + r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); + if (r < 0) + return r; + log_link_debug(link, "Starting IPv6 Router Advertisements"); return sd_radv_start(link->radv); } diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h index 79cbb897516..6d17dcc7f6d 100644 --- a/src/systemd/sd-radv.h +++ b/src/systemd/sd-radv.h @@ -52,6 +52,7 @@ int sd_radv_is_running(sd_radv *ra); int sd_radv_set_ifindex(sd_radv *ra, int interface_index); int sd_radv_set_ifname(sd_radv *ra, const char *interface_name); int sd_radv_get_ifname(sd_radv *ra, const char **ret); +int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr); int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr); int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu); int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit); From 769f9744b75e7733e09c956bb84b85abf37e7314 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 16 Apr 2024 16:28:44 +0900 Subject: [PATCH 2/5] network/ndisc: disable Neighbor discovery client if RADV is enabled Running both sd-ndisc and sd-radv should be mostly a misconfiguration, but may not. So, let's only disable sd-ndisc by default when sd-radv is enabled, but allow when both are explicitly requested. --- man/systemd.network.xml | 13 +++++++------ src/network/networkd-ndisc.c | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 38ab30fb4d4..5e8361f69af 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -888,12 +888,13 @@ Table=1234 IPv6AcceptRA= - Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the - interface. If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they - may trigger the start of the DHCPv6 client if the relevant flags are set in the RA data, or - if no routers are found on the link. The default is to disable RA reception for bridge - devices or when IP forwarding is enabled, and to enable it otherwise. Cannot be enabled on - devices aggregated in a bond device or when link-local addressing is disabled. + Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the interface. + If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they may trigger the + start of the DHCPv6 client if the relevant flags are set in the RA data, or if no routers are found + on the link. Defaults to false for bridge devices, when IP forwarding is enabled, + IPv6SendRA= or KeepMaster= is enabled. Otherwise, enabled by + default. Cannot be enabled on devices aggregated in a bond device or when link-local addressing is + disabled. Further settings for the IPv6 RA support may be configured in the [IPv6AcceptRA] section, see below. diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 7e74712680f..4720f616d58 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -53,9 +53,14 @@ bool link_ndisc_enabled(Link *link) { if (!link_may_have_ipv6ll(link, /* check_multicast = */ true)) return false; + /* Honor explicitly specified value. */ if (link->network->ndisc >= 0) return link->network->ndisc; + /* Disable if RADV is enabled. */ + if (link_radv_enabled(link)) + return false; + /* Accept RAs if IPv6 forwarding is disabled, and ignore RAs if IPv6 forwarding is enabled. */ int t = link_get_ip_forwarding(link, AF_INET6); if (t >= 0) From 864c7980c09cc7b42503ee1af33a0e6d868d9f09 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 16 Apr 2024 17:44:17 +0900 Subject: [PATCH 3/5] test-network: drop trailing spaces Follow-up for fb573007430ab0dbe45517b58837d2fa5cfa1a48. --- test/test-network/systemd-networkd-tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index cf601c84afc..05254152448 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -6933,8 +6933,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): check(self, True, False) check(self, False, True) check(self, False, False) - - def test_dhcp_client_default_use_domains(self): + + 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: @@ -6942,7 +6942,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): 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', @@ -6968,7 +6968,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): else: print(output) self.fail('unexpected domain setting in resolved...') - + stop_dnsmasq() remove_networkd_conf_dropin('default_use_domains.conf') From 45c2bbbaea2745d08715714efe8104989a3de3ac Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 16 Apr 2024 17:45:15 +0900 Subject: [PATCH 4/5] test-network: introduce check_networkd_log() helper function --- test/test-network/systemd-networkd-tests.py | 62 +++++---------------- 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 05254152448..946816a90b5 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1182,6 +1182,14 @@ class Utilities(): print(output) self.assertRegex(output, r'.*elements = { [^}]*' + contents + r'[^}]* }.*') + def check_networkd_log(self, contents, since=None, trial=20): + for _ in range(trial): + if contents in read_networkd_log(since=since): + break + time.sleep(0.5) + else: + self.fail(f'"{contents}" not found in journal.') + class NetworkctlTests(unittest.TestCase, Utilities): def setUp(self): @@ -1373,8 +1381,7 @@ class NetworkctlTests(unittest.TestCase, Utilities): self.assertIn('Network File: /run/systemd/network/11-test-unit-file.network', output) self.assertIn('/run/systemd/network/11-test-unit-file.network.d/dropin.conf', output) - output = read_networkd_log() - self.assertIn('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).', output) + self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).') # 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. @@ -1624,16 +1631,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): self.wait_online('bond99:off') self.wait_operstate('vlan99', operstate='off', setup_state='configuring', setup_timeout=10) - # The commit b05e52000b4eee764b383cc3031da0a3739e996e adds ", ignoring". To make it easily confirmed - # that the issue is fixed by the commit, let's allow to match both string. - log_re = re.compile('vlan99: Could not bring up interface(, ignoring|): Network is down$', re.MULTILINE) - for i in range(20): - if i > 0: - time.sleep(0.5) - if log_re.search(read_networkd_log()): - break - else: - self.fail() + self.check_networkd_log('vlan99: Could not bring up interface, ignoring: Network is down') copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '21-dummy-bond-slave.network') networkctl_reload() @@ -5612,13 +5610,7 @@ class NetworkdRATests(unittest.TestCase, Utilities): start_networkd() self.wait_online('veth-peer:degraded') - for _ in range(20): - output = read_networkd_log() - if 'veth99: NDISC: Started IPv6 Router Solicitation client' in output: - break - time.sleep(0.5) - else: - self.fail('sd-ndisc does not started on veth99.') + self.check_networkd_log('veth99: NDISC: Started IPv6 Router Solicitation client') check_output(f'{test_ndisc_send} --interface veth-peer --type ra --lifetime 1hour --mtu 1400') self.check_ndisc_mtu(1400) @@ -6032,13 +6024,7 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): self.wait_online('bridge-relay:routable', 'client-peer:enslaved') # For issue #30763. - expect = 'bridge-relay: DHCPv4 server: STARTED' - for _ in range(20): - if expect in read_networkd_log(): - break - time.sleep(0.5) - else: - self.fail() + self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') class NetworkdDHCPClientTests(unittest.TestCase, Utilities): @@ -6510,37 +6496,19 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): since = datetime.datetime.now() start_dnsmasq() - expect = 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.' - for _ in range(20): - if expect in read_networkd_log(since=since): - break - time.sleep(0.5) - else: - self.fail() + self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) copy_network_unit('25-dhcp-client-allow-list.network.d/00-allow-list.conf') since = datetime.datetime.now() networkctl_reload() - expect = 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.' - for _ in range(20): - if expect in read_networkd_log(since=since): - break - time.sleep(0.5) - else: - self.fail() + self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) copy_network_unit('25-dhcp-client-allow-list.network.d/10-deny-list.conf') since = datetime.datetime.now() networkctl_reload() - expect = 'veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.' - for _ in range(20): - if expect in read_networkd_log(since=since): - break - time.sleep(0.5) - else: - self.fail() + self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', since=since) @unittest.skipUnless("--dhcp-rapid-commit" in run("dnsmasq --help").stdout, reason="dnsmasq is missing dhcp-rapid-commit support") def test_dhcp_client_rapid_commit(self): From 3b4eeccd27c88398dcf429d687a1a2f706218b1e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 16 Apr 2024 17:47:18 +0900 Subject: [PATCH 5/5] test-network: add test case of RS sent by the same interface --- test/test-network/systemd-networkd-tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 946816a90b5..e14a2613df1 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5591,6 +5591,12 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.assertIn('2002:da8:1:1:1a:2b:3c:4d via fe80::1 proto redirect', output) self.assertIn('2002:da8:1:2:1a:2b:3c:4d via fe80::2 proto redirect', output) + # Check if sd-radv refuses RS from the same interface. + # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306 + since = datetime.datetime.now() + check_output(f'{test_ndisc_send} --interface veth-peer --type rs --dest {veth_peer_ipv6ll}') + self.check_networkd_log('veth-peer: RADV: Received RS from the same interface, ignoring.', since=since) + def check_ndisc_mtu(self, mtu): for _ in range(20): output = read_ipv6_sysctl_attr('veth99', 'mtu')