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')