]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #32299 from yuwata/network-radv-ignore-rs-from-the-same-interface
authorLuca Boccassi <bluca@debian.org>
Thu, 18 Apr 2024 21:45:06 +0000 (23:45 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Apr 2024 21:45:06 +0000 (23:45 +0200)
network/radv: ignore RS message from the same interface

man/systemd.network.xml
src/libsystemd-network/radv-internal.h
src/libsystemd-network/sd-radv.c
src/network/networkd-ndisc.c
src/network/networkd-radv.c
src/systemd/sd-radv.h
test/test-network/systemd-networkd-tests.py

index 38ab30fb4d4a41f21bd23d7b83eb9cac8d87c91e..5e8361f69af1be42d8157b63841db2ef173d1170 100644 (file)
@@ -888,12 +888,13 @@ Table=1234</programlisting></para>
       <varlistentry>
         <term><varname>IPv6AcceptRA=</varname></term>
         <listitem>
-          <para>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.</para>
+          <para>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,
+          <varname>IPv6SendRA=</varname> or <varname>KeepMaster=</varname> is enabled. Otherwise, enabled by
+          default. Cannot be enabled on devices aggregated in a bond device or when link-local addressing is
+          disabled.</para>
 
           <para>Further settings for the IPv6 RA support may be configured in the [IPv6AcceptRA]
           section, see below.</para>
index 8091a5d882b09fa9ef3ce671bf3e138470fd0668..d2ec912d496cc7d6db025bb82c32a33ff55688eb 100644 (file)
@@ -98,6 +98,7 @@ struct sd_radv {
 
         int ifindex;
         char *ifname;
+        struct in6_addr ipv6ll;
 
         sd_event *event;
         int event_priority;
index 6a71ce14ff4e6ceb64f650b57a72be3fcb56c759..053dd0653b1ad6c33ae23db67230b6ce78a6e8e1 100644 (file)
@@ -283,6 +283,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);
         if (r < 0)
@@ -495,6 +499,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);
 
index 7e74712680fde39a50baefd9ace9acfee0465a9e..4720f616d583f82ab3501fb91416d8d281ebcd83 100644 (file)
@@ -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)
index b137b9384f7f56746d0a56d5ae1feebd00bb1525..4291165a913c26d38091f1b4bb372ae648fef388 100644 (file)
@@ -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);
 }
index 79cbb8975161eb9a09ed61a89bf696fe844097e5..6d17dcc7f6d82dd81d29032e15d874209c71e8eb 100644 (file)
@@ -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);
index cf601c84afc8b8b208cbeb3967549b12abd23cd5..e14a2613df13a74a6873eefee3948174da27739b 100755 (executable)
@@ -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()
@@ -5593,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')
@@ -5612,13 +5616,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 +6030,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 +6502,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):
@@ -6933,8 +6907,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 +6916,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 +6942,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')