diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 35c897af398..ef4a0fd4305 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1629,6 +1629,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + L3MasterDevice= + + A boolean. Specifies whether the rule is to direct lookups to the tables associated with + level 3 master devices (also known as Virtual Routing and Forwarding or VRF devices). + For further details see + Virtual Routing and Forwarding (VRF). Defaults to false. + + + + + SourcePort= @@ -1951,7 +1963,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix ip route show table num. If unset and Type= is local, broadcast, anycast, or nat, then local is used. - In other cases, defaults to main. + In other cases, defaults to main. Ignored if L3MasterDevice= is true. diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index fd1f26c7980..f0650a0c886 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -180,6 +180,7 @@ RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0 +RoutingPolicyRule.L3MasterDevice, config_parse_routing_policy_rule_l3mdev, 0, 0 RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0 RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0 RoutingPolicyRule.SuppressInterfaceGroup, config_parse_routing_policy_rule_suppress_ifgroup, 0, 0 diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 914e288aecc..e8e154dd606 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -164,6 +164,8 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct in_addr_hash_func(&rule->from, rule->family, state); siphash24_compress_typesafe(rule->from_prefixlen, state); + siphash24_compress_boolean(rule->l3mdev, state); + in_addr_hash_func(&rule->to, rule->family, state); siphash24_compress_typesafe(rule->to_prefixlen, state); @@ -212,6 +214,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; + r = CMP(a->l3mdev, b->l3mdev); + if (r != 0) + return r; + r = CMP(a->to_prefixlen, b->to_prefixlen); if (r != 0) return r; @@ -476,19 +482,19 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } - if (rule->table < 256) { + if (rule->l3mdev) + r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); + else if (rule->table < 256) r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table); - if (r < 0) - return r; - } else { + else { r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); if (r < 0) return r; r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table); - if (r < 0) - return r; } + if (r < 0) + return r; if (rule->fwmark > 0) { r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark); @@ -544,6 +550,12 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } + if (rule->l3mdev) { + r = sd_netlink_message_append_u8(m, FRA_L3MDEV, 1); + if (r < 0) + return r; + } + if (rule->suppress_prefixlen >= 0) { r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen); if (r < 0) @@ -853,20 +865,17 @@ int link_request_static_routing_policy_rules(Link *link) { static const RoutingPolicyRule kernel_rules[] = { { .family = AF_INET, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET, .priority_set = true, .priority = 32767, .table = RT_TABLE_DEFAULT, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET6, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET6, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET6, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, }; static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) { assert(rule); - if (rule->l3mdev > 0) - /* Currently, [RoutingPolicyRule] does not explicitly set FRA_L3MDEV. So, if the flag - * is set, it is safe to treat the rule as created by kernel. */ - return true; - for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++) if (routing_policy_rule_equal(rule, &kernel_rules[i])) return true; @@ -1016,11 +1025,13 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man return 0; } - r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &tmp->l3mdev); + uint8_t l3mdev = 0; + r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &l3mdev); if (r < 0 && r != -ENODATA) { log_warning_errno(r, "rtnl: could not get FRA_L3MDEV attribute, ignoring: %m"); return 0; } + tmp->l3mdev = l3mdev != 0; r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport); if (r < 0 && r != -ENODATA) { @@ -1502,6 +1513,44 @@ int config_parse_routing_policy_rule_invert( return 0; } +int config_parse_routing_policy_rule_l3mdev( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule l3mdev, ignoring: %s", rvalue); + return 0; + } + + n->l3mdev = r; + + TAKE_PTR(n); + return 0; +} + int config_parse_routing_policy_rule_family( const char *unit, const char *filename, @@ -1734,12 +1783,6 @@ static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) { /* rule->family can be AF_UNSPEC only when Family=both. */ } - /* Currently, [RoutingPolicyRule] does not have a setting to set FRA_L3MDEV flag. Please also - * update routing_policy_rule_is_created_by_kernel() when a new setting which sets the flag is - * added in the future. */ - if (rule->l3mdev > 0) - assert_not_reached(); - return 0; } diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h index b6ce2fa19c4..85016bbc8a3 100644 --- a/src/network/networkd-routing-policy-rule.h +++ b/src/network/networkd-routing-policy-rule.h @@ -22,6 +22,7 @@ typedef struct RoutingPolicyRule { bool invert_rule; bool priority_set; + bool l3mdev; /* FRA_L3MDEV */ uint8_t tos; uint8_t type; @@ -29,7 +30,6 @@ typedef struct RoutingPolicyRule { uint8_t protocol; /* FRA_PROTOCOL */ uint8_t to_prefixlen; uint8_t from_prefixlen; - uint8_t l3mdev; /* FRA_L3MDEV */ uint32_t table; uint32_t fwmark; @@ -80,6 +80,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_fwmark_mask); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_l3mdev); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert); diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 93307c0bbd1..18d9c3b30f6 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -496,6 +496,7 @@ KernelVersion= Key= Kind= L2MissNotification= +L3MasterDevice= L3MissNotification= LACPTransmitRate= LLDP= diff --git a/test/test-network/conf/25-fibrule-l3mdev.network b/test/test-network/conf/25-fibrule-l3mdev.network new file mode 100644 index 00000000000..a1afcd21c87 --- /dev/null +++ b/test/test-network/conf/25-fibrule-l3mdev.network @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test1 + +[Network] +IPv6AcceptRA=no + +[RoutingPolicyRule] +Priority=1500 +L3MasterDevice=true + +[RoutingPolicyRule] +Priority=2000 +L3MasterDevice=true +Type=unreachable diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 7bf2f501453..3bb0166f4ee 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -199,6 +199,14 @@ def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable(): return f +def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable(): + def f(func): + rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev') + call_quiet('ip rule del not from 192.168.100.19 l3mdev') + return func if rc == 0 else unittest.expectedFailure(func) + + return f + def expectedFailureIfNexthopIsNotAvailable(): def f(func): rc = call_quiet('ip nexthop list') @@ -3106,6 +3114,17 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'tcp') self.assertRegex(output, 'lookup 7') + @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable() + def test_routing_policy_rule_l3mdev(self): + copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev') + start_networkd() + self.wait_online(['test1:degraded']) + + output = check_output('ip rule') + print(output) + self.assertIn('1500: from all lookup [l3mdev-table]', output) + self.assertIn('2000: from all lookup [l3mdev-table] unreachable', output) + @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() def test_routing_policy_rule_uidrange(self): copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev')