diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml index 018bde0fbf..6d1dfc78ce 100644 --- a/man/networkd.conf.xml +++ b/man/networkd.conf.xml @@ -90,6 +90,17 @@ + + ManageForeignNextHops= + A boolean. When true, systemd-networkd will remove nexthops + that are not configured in .network files (except for routes with protocol + kernel). When false, it will + not remove any foreign nexthops, keeping them even if they are not configured in a .network file. + Defaults to yes. + + + + RouteTable= Defines the route table name. Takes a whitespace-separated list of the pairs of diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 3436a32b11..0bad731b0d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1715,8 +1715,10 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix Id= - The id of the next hop. Takes an integer in the range 1…4294967295. If unspecified, - then automatically chosen by kernel. + The id of the next hop. Takes an integer in the range 1…4294967295. + This is mandatory if ManageForeignNextHops=no is specified in + networkd.conf5. + Otherwise, if unspecified, an unused ID will be automatically picked. diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 8542ffa6b5..c9e3c937f4 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -25,6 +25,7 @@ Network.SpeedMeter, config_parse_bool, Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) Network.ManageForeignRoutingPolicyRules, config_parse_bool, 0, offsetof(Manager, manage_foreign_rules) Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes) +Network.ManageForeignNextHops, config_parse_bool, 0, offsetof(Manager, manage_foreign_nexthops) Network.RouteTable, config_parse_route_table_names, 0, 0 Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Manager, ipv6_privacy_extensions) DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp_duid) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index fca5d76618..6ee01b28e0 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -591,6 +591,7 @@ int manager_new(Manager **ret, bool test_mode) { .online_state = _LINK_ONLINE_STATE_INVALID, .manage_foreign_routes = true, .manage_foreign_rules = true, + .manage_foreign_nexthops = true, .ethtool_fd = -EBADF, .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, @@ -867,6 +868,9 @@ static int manager_enumerate_nexthop(Manager *m) { assert(m); assert(m->rtnl); + if (!m->manage_foreign_nexthops) + return 0; + r = sd_rtnl_message_new_nexthop(m->rtnl, &req, RTM_GETNEXTHOP, 0, 0); if (r < 0) return r; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index fbef5289d2..a4eb7d78af 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -38,6 +38,7 @@ struct Manager { bool restarting; bool manage_foreign_routes; bool manage_foreign_rules; + bool manage_foreign_nexthops; Set *dirty_links; Set *new_wlan_ifindices; diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index e2ded28197..442e16b026 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -318,6 +318,10 @@ static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) { if (nexthop->id > 0) return 0; + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(manager->manage_foreign_nexthops); + /* Find the lowest unused ID. */ ORDERED_HASHMAP_FOREACH(network, manager->networks) { @@ -988,6 +992,13 @@ static int nexthop_section_verify(NextHop *nh) { if (section_is_invalid(nh->section)) return -EINVAL; + if (!nh->network->manager->manage_foreign_nexthops && nh->id == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [NextHop] section without specifying Id= is not supported " + "if ManageForeignNextHops=no is set in networkd.conf. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + if (!hashmap_isempty(nh->group)) { if (in_addr_is_set(nh->family, &nh->gw)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/network/networkd.conf b/src/network/networkd.conf index e5a5e88926..2994b8b70c 100644 --- a/src/network/networkd.conf +++ b/src/network/networkd.conf @@ -21,6 +21,7 @@ #SpeedMeterIntervalSec=10sec #ManageForeignRoutingPolicyRules=yes #ManageForeignRoutes=yes +#ManageForeignNextHops=yes #RouteTable= #IPv6PrivacyExtensions=no diff --git a/test/test-network/conf/networkd-manage-foreign-nexthops-no.conf b/test/test-network/conf/networkd-manage-foreign-nexthops-no.conf new file mode 100644 index 0000000000..9bf5d7eb5d --- /dev/null +++ b/test/test-network/conf/networkd-manage-foreign-nexthops-no.conf @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Network] +ManageForeignNextHops=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 2bfed29af6..b70795a908 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -3284,8 +3284,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): print(output) self.assertEqual(output, '') - self.tearDown() - def test_route_static(self): first = True for manage_foreign_routes in [True, False]: @@ -3844,74 +3842,86 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'inet 10.1.2.3/16 scope global dummy98') self.assertNotRegex(output, 'inet 10.2.3.4/16 scope global dynamic dummy98') - @expectedFailureIfNexthopIsNotAvailable() - def test_nexthop(self): - def check_nexthop(self): - self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable']) + def check_nexthop(self, manage_foreign_nexthops): + self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable']) - output = check_output('ip nexthop list dev veth99') - print(output) - self.assertIn('id 1 via 192.168.5.1 dev veth99', output) - self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output) - self.assertIn('id 3 dev veth99', output) - self.assertIn('id 4 dev veth99', output) - self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink') - self.assertIn('id 8 via fe80:0:222:4dff:ff:ff:ff:ff dev veth99', output) + output = check_output('ip nexthop list dev veth99') + print(output) + self.assertIn('id 1 via 192.168.5.1 dev veth99', output) + self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output) + self.assertIn('id 3 dev veth99', output) + self.assertIn('id 4 dev veth99', output) + self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink') + self.assertIn('id 8 via fe80:0:222:4dff:ff:ff:ff:ff dev veth99', output) + if manage_foreign_nexthops: self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99') - output = check_output('ip nexthop list dev dummy98') - print(output) - self.assertIn('id 20 via 192.168.20.1 dev dummy98', output) + output = check_output('ip nexthop list dev dummy98') + print(output) + self.assertIn('id 20 via 192.168.20.1 dev dummy98', output) + if manage_foreign_nexthops: + self.assertNotIn('id 42 via 192.168.20.2 dev dummy98', output) + else: + self.assertIn('id 42 via 192.168.20.2 dev dummy98', output) - # kernel manages blackhole nexthops on lo - output = check_output('ip nexthop list dev lo') - print(output) - self.assertIn('id 6 blackhole', output) - self.assertIn('id 7 blackhole', output) + # kernel manages blackhole nexthops on lo + output = check_output('ip nexthop list dev lo') + print(output) + self.assertIn('id 6 blackhole', output) + self.assertIn('id 7 blackhole', output) - # group nexthops are shown with -0 option - output = check_output('ip -0 nexthop list id 21') - print(output) - self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)') + # group nexthops are shown with -0 option + output = check_output('ip -0 nexthop list id 21') + print(output) + self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)') - output = check_output('ip route show dev veth99 10.10.10.10') - print(output) - self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output) + output = check_output('ip route show dev veth99 10.10.10.10') + print(output) + self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output) - output = check_output('ip route show dev veth99 10.10.10.11') - print(output) - self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output) + output = check_output('ip route show dev veth99 10.10.10.11') + print(output) + self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output) - output = check_output('ip route show dev veth99 10.10.10.12') - print(output) - self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output) + output = check_output('ip route show dev veth99 10.10.10.12') + print(output) + self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output) - output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1') - print(output) - self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) + output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1') + print(output) + self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) - output = check_output('ip route show 10.10.10.13') - print(output) - self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output) + output = check_output('ip route show 10.10.10.13') + print(output) + self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output) - output = check_output('ip -6 route show 2001:1234:5:8f62::2') - print(output) - self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output) + output = check_output('ip -6 route show 2001:1234:5:8f62::2') + print(output) + self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output) - output = check_output('ip route show 10.10.10.14') - print(output) - self.assertIn('10.10.10.14 nhid 21 proto static', output) - self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output) - self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output) + output = check_output('ip route show 10.10.10.14') + print(output) + self.assertIn('10.10.10.14 nhid 21 proto static', output) + self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output) + self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output) - output = check_output(*networkctl_cmd, '--json=short', 'status', env=env) - check_json(output) + output = check_output(*networkctl_cmd, '--json=short', 'status', env=env) + check_json(output) + + def _test_nexthop(self, manage_foreign_nexthops): + if not manage_foreign_nexthops: + copy_networkd_conf_dropin('networkd-manage-foreign-nexthops-no.conf') + + check_output('ip link add dummy98 type dummy') + check_output('ip link set dummy98 up') + check_output('ip address add 192.168.20.20/24 dev dummy98') + check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98') copy_network_unit('25-nexthop.network', '25-veth.netdev', '25-veth-peer.network', '12-dummy.netdev', '25-nexthop-dummy.network') start_networkd() - check_nexthop(self) + self.check_nexthop(manage_foreign_nexthops) remove_network_unit('25-nexthop.network') copy_network_unit('25-nexthop-nothing.network') @@ -3930,7 +3940,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): networkctl_reconfigure('dummy98') networkctl_reload() - check_nexthop(self) + self.check_nexthop(manage_foreign_nexthops) remove_link('veth99') time.sleep(2) @@ -3939,6 +3949,19 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): print(output) self.assertEqual(output, '') + @expectedFailureIfNexthopIsNotAvailable() + def test_nexthop(self): + first = True + for manage_foreign_nexthops in [True, False]: + if first: + first = False + else: + self.tearDown() + + print(f'### test_nexthop(manage_foreign_nexthops={manage_foreign_nexthops})') + with self.subTest(manage_foreign_nexthops=manage_foreign_nexthops): + self._test_nexthop(manage_foreign_nexthops) + class NetworkdTCTests(unittest.TestCase, Utilities): def setUp(self):