diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 3f816d7fa2..67fc725f98 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -200,6 +200,17 @@ bool link_is_ready_to_configure(Link *link, bool allow_unmanaged) { return check_ready_for_all_sr_iov_ports(link, allow_unmanaged, link_is_ready_to_configure_one); } +bool link_is_ready_to_configure_by_name(Manager *manager, const char *name, bool allow_unmanaged) { + assert(manager); + assert(name); + + Link *link; + if (link_get_by_name(manager, name, &link) < 0) + return false; + + return link_is_ready_to_configure(link, allow_unmanaged); +} + void link_ntp_settings_clear(Link *link) { link->ntp = strv_free(link->ntp); } diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 113217cdb6..c5806cea33 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -225,6 +225,7 @@ typedef struct Link { typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*); bool link_is_ready_to_configure(Link *link, bool allow_unmanaged); +bool link_is_ready_to_configure_by_name(Manager *manager, const char *name, bool allow_unmanaged); void link_ntp_settings_clear(Link *link); void link_dns_settings_clear(Link *link); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 0c95b7d34f..2cac730a41 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -872,6 +872,31 @@ int link_drop_routing_policy_rules(Link *link, bool only_static) { return r; } +static int routing_policy_rule_is_ready_to_configure(const RoutingPolicyRule *rule, Link *link) { + assert(rule); + assert(link); + assert(link->manager); + + /* For routing policy rules, it is not necessary to check operstate and friends of the interface. + * Hence, here we refuse to configure rules only when the interface is already removed, or in the + * failed state. */ + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return false; + + /* Strictly speaking checking existence of IIF and OIF below is not necessary. But, configuring + * routing policy rule with non-existent IIF or OIF is mostly meaningless, and 'ip rule' command + * shows [detached] for such rules, that may confuse users. Let's postpone to configure if one of + * IIF/OIF does not exist. */ + + if (rule->iif && !link_is_ready_to_configure_by_name(link->manager, rule->iif, /* allow_unmanaged = */ true)) + return false; + + if (rule->oif && !link_is_ready_to_configure_by_name(link->manager, rule->oif, /* allow_unmanaged = */ true)) + return false; + + return true; +} + static int routing_policy_rule_process_request(Request *req, Link *link, RoutingPolicyRule *rule) { RoutingPolicyRule *existing; int r; @@ -881,7 +906,7 @@ static int routing_policy_rule_process_request(Request *req, Link *link, Routing assert(link->manager); assert(rule); - if (!link_is_ready_to_configure(link, false)) + if (!routing_policy_rule_is_ready_to_configure(rule, link)) return 0; r = routing_policy_rule_configure(rule, link, req); diff --git a/test/test-network/conf/25-routing-policy-rule-manual.network b/test/test-network/conf/25-routing-policy-rule-manual.network new file mode 100644 index 0000000000..fa1328189a --- /dev/null +++ b/test/test-network/conf/25-routing-policy-rule-manual.network @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test1 +Name=test2 + +[Link] +ActivationPolicy=manual + +[Network] +IPv6AcceptRA=no + +[RoutingPolicyRule] +Family=both +Priority=10 +SuppressPrefixLength=0 +Table=51819 + +[RoutingPolicyRule] +Family=both +FirewallMark=911 +InvertRule=true +Priority=11 +Table=51820 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index c569e0d2ca..a1f066c4be 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -3999,6 +3999,45 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): print(output) self.assertIn('10113: from all iif test1 lookup 1011', output) + def test_routing_policy_rule_manual(self): + # For issue #36244. + copy_network_unit( + '11-dummy.netdev', + '25-routing-policy-rule-manual.network') + start_networkd() + self.wait_operstate('test1', operstate='off', setup_state='configuring', setup_timeout=20) + + check_output('ip link add test2 type dummy') + self.wait_operstate('test2', operstate='off', setup_state='configuring', setup_timeout=20) + + networkctl('up', 'test2') + self.wait_online('test2:degraded') + + # The request for the routing policy rules are bound to test1. Hence, we need to wait for the rules + # being configured explicitly. + for _ in range(20): + time.sleep(0.5) + + output = check_output('ip -4 rule list table 51819') + if output != '10: from all lookup 51819 suppress_prefixlength 0 proto static': + continue + + output = check_output('ip -6 rule list table 51819') + if output != '10: from all lookup 51819 suppress_prefixlength 0 proto static': + continue + + output = check_output('ip -4 rule list table 51820') + if output != '11: not from all fwmark 0x38f lookup 51820 proto static': + continue + + output = check_output('ip -6 rule list table 51820') + if output != '11: not from all fwmark 0x38f lookup 51820 proto static': + continue + + break + else: + self.assertFalse(True) + @expectedFailureIfRoutingPolicyPortRangeIsNotAvailable() def test_routing_policy_rule_port_range(self): copy_network_unit('25-fibrule-port-range.network', '11-dummy.netdev')