diff options
author | Luca Boccassi <bluca@debian.org> | 2024-08-21 12:46:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-21 12:46:37 +0200 |
commit | bdf75118bade008b6a465173c02933eb377aef0d (patch) | |
tree | b7b519b1410be6844a65f15699dfe67164852127 | |
parent | Merge pull request #34018 from yuwata/network-address-label (diff) | |
parent | test-network: add test for ManageForeignRoutingPolicyRules= (diff) | |
download | systemd-bdf75118bade008b6a465173c02933eb377aef0d.tar.xz systemd-bdf75118bade008b6a465173c02933eb377aef0d.zip |
Merge pull request #34049 from yuwata/network-routing-policy-rule
network: further rework for routing policy rule
-rw-r--r-- | man/systemd.network.xml | 18 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-message.c | 28 | ||||
-rw-r--r-- | src/network/networkd-json.c | 2 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-queue.c | 12 | ||||
-rw-r--r-- | src/network/networkd-routing-policy-rule.c | 532 | ||||
-rw-r--r-- | src/network/networkd-routing-policy-rule.h | 10 | ||||
-rw-r--r-- | src/systemd/sd-netlink.h | 1 | ||||
-rw-r--r-- | test/test-network/conf/25-routing-policy-rule-test1.network | 16 | ||||
-rw-r--r-- | test/test-network/conf/networkd-manage-foreign-rules-no.conf | 3 | ||||
-rwxr-xr-x | test/test-network/systemd-networkd-tests.py | 98 |
11 files changed, 536 insertions, 185 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 6f6746b13b..734a4f7c0b 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1729,6 +1729,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting> </varlistentry> <varlistentry> + <term><varname>GoTo=</varname></term> + <listitem> + <para>Specifies the target priority used by <literal>goto</literal> type of rule. Takes an integer + in the range 1…4294967295. This must be larger than the priority of this rule specified in + <varname>Priority=</varname>. When specified, <varname>Type=goto</varname> is implied. This is + mandatory when <varname>Type=goto</varname>.</para> + + <xi:include href="version-info.xml" xpointer="v257"/> + </listitem> + </varlistentry> + + <varlistentry> <term><varname>IncomingInterface=</varname></term> <listitem> <para>Specifies incoming device to match. If the interface is loopback, the rule only matches @@ -1853,8 +1865,10 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting> <term><varname>Type=</varname></term> <listitem> <para>Specifies Routing Policy Database (RPDB) rule type. Takes one of - <literal>blackhole</literal>, <literal>unreachable</literal> or <literal>prohibit</literal>. - </para> + <literal>table</literal>, <literal>goto</literal>, <literal>nop</literal>, + <literal>blackhole</literal>, <literal>unreachable</literal>, or <literal>prohibit</literal>. + When <literal>goto</literal>, the target priority must be specified in <varname>GoTo=</varname>. + Defaults to <literal>table</literal>.</para> <xi:include href="version-info.xml" xpointer="v248"/> </listitem> diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 49d000d47e..25bcbd9640 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -928,6 +928,34 @@ int sd_netlink_message_read_u32(sd_netlink_message *m, uint16_t attr_type, uint3 return 0; } +int sd_netlink_message_read_u64(sd_netlink_message *m, uint16_t attr_type, uint64_t *ret) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U64); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, &net_byteorder); + if (r < 0) + return r; + + if ((size_t) r < sizeof(uint64_t)) + return -EIO; + + if (ret) { + if (net_byteorder) + *ret = be64toh(*(uint64_t *) attr_data); + else + *ret = *(uint64_t *) attr_data; + } + + return 0; +} + int sd_netlink_message_read_ether_addr(sd_netlink_message *m, uint16_t attr_type, struct ether_addr *data) { void *attr_data; int r; diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 0a57e6aee0..4eafc62c7e 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -308,7 +308,7 @@ static int routing_policy_rule_append_json(RoutingPolicyRule *rule, sd_json_vari SD_JSON_BUILD_PAIR_STRING("ProtocolString", protocol), SD_JSON_BUILD_PAIR_UNSIGNED("TOS", rule->tos), SD_JSON_BUILD_PAIR_UNSIGNED("Type", rule->type), - SD_JSON_BUILD_PAIR_STRING("TypeString", fr_act_type_full_to_string(rule->type)), + SD_JSON_BUILD_PAIR_STRING("TypeString", fr_act_type_to_string(rule->type)), SD_JSON_BUILD_PAIR_UNSIGNED("IPProtocol", rule->ipproto), SD_JSON_BUILD_PAIR_STRING("IPProtocolString", ip_protocol_to_name(rule->ipproto)), SD_JSON_BUILD_PAIR_UNSIGNED("Priority", rule->priority), diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index aa849fe535..b2794f9efd 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -178,6 +178,7 @@ Neighbor.LinkLayerAddress, config_parse_neighbor_lladdr, Neighbor.MACAddress, config_parse_neighbor_lladdr, 0, 0 /* deprecated */ RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0 RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0 +RoutingPolicyRule.GoTo, config_parse_routing_policy_rule_goto, 0, 0 RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0 RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0 RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0 diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 7943ab5fb0..dcb9bd0549 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -58,7 +58,11 @@ static void request_hash_func(const Request *req, struct siphash *state) { siphash24_compress_typesafe(req->type, state); - if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + if (!IN_SET(req->type, + REQUEST_TYPE_NEXTHOP, + REQUEST_TYPE_ROUTE, + REQUEST_TYPE_ROUTING_POLICY_RULE)) { + siphash24_compress_boolean(req->link, state); if (req->link) siphash24_compress_typesafe(req->link->ifindex, state); @@ -81,7 +85,11 @@ static int request_compare_func(const struct Request *a, const struct Request *b if (r != 0) return r; - if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + if (!IN_SET(a->type, + REQUEST_TYPE_NEXTHOP, + REQUEST_TYPE_ROUTE, + REQUEST_TYPE_ROUTING_POLICY_RULE)) { + r = CMP(!!a->link, !!b->link); if (r != 0) return r; diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 8e7cc47118..44d85d000f 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -25,12 +25,6 @@ #include "user-util.h" static const char *const fr_act_type_table[__FR_ACT_MAX] = { - [FR_ACT_BLACKHOLE] = "blackhole", - [FR_ACT_UNREACHABLE] = "unreachable", - [FR_ACT_PROHIBIT] = "prohibit", -}; - -static const char *const fr_act_type_full_table[__FR_ACT_MAX] = { [FR_ACT_TO_TBL] = "table", [FR_ACT_GOTO] = "goto", [FR_ACT_NOP] = "nop", @@ -40,8 +34,7 @@ static const char *const fr_act_type_full_table[__FR_ACT_MAX] = { }; assert_cc(__FR_ACT_MAX <= UINT8_MAX); -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(fr_act_type, int); -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(fr_act_type_full, int); +DEFINE_STRING_TABLE_LOOKUP(fr_act_type, int); static RoutingPolicyRule* routing_policy_rule_detach_impl(RoutingPolicyRule *rule) { assert(rule); @@ -162,7 +155,7 @@ static int routing_policy_rule_new_static(Network *network, const char *filename return 0; } -static int routing_policy_rule_dup(const RoutingPolicyRule *src, RoutingPolicyRule **ret) { +static int routing_policy_rule_dup(const RoutingPolicyRule *src, int family, RoutingPolicyRule **ret) { _cleanup_(routing_policy_rule_unrefp) RoutingPolicyRule *dest = NULL; assert(src); @@ -179,6 +172,9 @@ static int routing_policy_rule_dup(const RoutingPolicyRule *src, RoutingPolicyRu dest->section = NULL; dest->iif = dest->oif = NULL; + /* Set family. */ + dest->family = family; + if (src->iif) { dest->iif = strdup(src->iif); if (!dest->iif) @@ -209,7 +205,7 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash24_compress_typesafe(rule->suppress_ifgroup, state); siphash24_compress_typesafe(rule->suppress_prefixlen, state); siphash24_compress_typesafe(rule->fwmask, state); - /* FRA_TUN_ID */ + siphash24_compress_typesafe(rule->tunnel_id, state); /* fr_net (network namespace) */ siphash24_compress_typesafe(rule->l3mdev, state); siphash24_compress_typesafe(rule->uid_range, state); @@ -222,20 +218,22 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash24_compress_typesafe(rule->from_prefixlen, state); siphash24_compress_typesafe(rule->to_prefixlen, state); siphash24_compress_typesafe(rule->tos, state); - /* FRA_FLOW (IPv4 only) */ + siphash24_compress_typesafe(rule->realms, state); in_addr_hash_func(&rule->from, rule->family, state); in_addr_hash_func(&rule->to, rule->family, state); } -static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const RoutingPolicyRule *b) { +static int routing_policy_rule_compare_func_full(const RoutingPolicyRule *a, const RoutingPolicyRule *b, bool all) { int r; assert(a); assert(b); - r = CMP(a->family, b->family); - if (r != 0) - return r; + if (all) { + r = CMP(a->family, b->family); + if (r != 0) + return r; + } r = CMP(a->type, b->type); if (r != 0) @@ -245,9 +243,11 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; - r = CMP(a->priority, b->priority); - if (r != 0) - return r; + if (all) { + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + } r = strcmp_ptr(a->iif, b->iif); if (r != 0) @@ -273,6 +273,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; + r = CMP(a->tunnel_id, b->tunnel_id); + if (r != 0) + return r; + r = CMP(a->l3mdev, b->l3mdev); if (r != 0) return r; @@ -309,60 +313,124 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; - r = memcmp(&a->from, &b->from, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->realms, b->realms); if (r != 0) return r; - r = memcmp(&a->to, &b->to, FAMILY_ADDRESS_SIZE(a->family)); - if (r != 0) - return r; + if (all) { + r = memcmp(&a->from, &b->from, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = memcmp(&a->to, &b->to, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + } return 0; } -static bool routing_policy_rule_equal(const RoutingPolicyRule *rule1, const RoutingPolicyRule *rule2) { - if (rule1 == rule2) - return true; +static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const RoutingPolicyRule *b) { + return routing_policy_rule_compare_func_full(a, b, /* all = */ true); +} + +static bool routing_policy_rule_equal(const RoutingPolicyRule *a, const RoutingPolicyRule *b, int family, uint32_t priority) { + assert(a); + assert(b); - if (!rule1 || !rule2) + if (a->family != AF_UNSPEC && a->family != family) + return false; + if (b->family != AF_UNSPEC && b->family != family) return false; - return routing_policy_rule_compare_func(rule1, rule2) == 0; + if (a->priority_set && a->priority != priority) + return false; + if (b->priority_set && b->priority != priority) + return false; + + return routing_policy_rule_compare_func_full(a, b, /* all = */ false) == 0; } -static int routing_policy_rule_get(Manager *m, const RoutingPolicyRule *in, RoutingPolicyRule **ret) { +static bool routing_policy_rule_can_update(const RoutingPolicyRule *existing, const RoutingPolicyRule *requesting, int family) { + assert(existing); + assert(requesting); + + if (!routing_policy_rule_equal(existing, requesting, family, existing->priority)) + return false; + + /* These flags cannot be updated. */ + if ((existing->flags ^ requesting->flags) & (FIB_RULE_PERMANENT|FIB_RULE_INVERT)) + return false; + + /* GOTO target cannot be updated. */ + if (existing->type == FR_ACT_GOTO && existing->priority_goto != requesting->priority_goto) + return false; + + return true; +} + +static int routing_policy_rule_get(Manager *m, const RoutingPolicyRule *in, int family, RoutingPolicyRule **ret) { RoutingPolicyRule *rule; assert(m); assert(in); + assert(in->family == AF_UNSPEC || in->family == family); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (in->priority_set && in->family != AF_UNSPEC) { + rule = set_get(m->rules, in); + if (!rule) + return -ENOENT; - rule = set_get(m->rules, in); - if (rule) { if (ret) *ret = rule; return 0; } - if (in->priority_set) - return -ENOENT; + SET_FOREACH(rule, m->rules) + if (routing_policy_rule_equal(in, rule, family, rule->priority)) { + if (ret) + *ret = rule; + return 0; + } - /* Also find rules configured without priority. */ - SET_FOREACH(rule, m->rules) { - uint32_t priority; - bool found; + return -ENOENT; +} - if (rule->priority_set) - /* The rule is configured with priority. */ - continue; +static int routing_policy_rule_get_request(Manager *m, const RoutingPolicyRule *in, int family, Request **ret) { + Request *req; - priority = rule->priority; - rule->priority = 0; - found = routing_policy_rule_equal(rule, in); - rule->priority = priority; + assert(m); + assert(in); + assert(in->family == AF_UNSPEC || in->family == family); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (in->priority_set && in->family != AF_UNSPEC) { + req = ordered_set_get( + m->request_queue, + &(Request) { + .type = REQUEST_TYPE_ROUTING_POLICY_RULE, + .userdata = (void*) in, + .hash_func = (hash_func_t) routing_policy_rule_hash_func, + .compare_func = (compare_func_t) routing_policy_rule_compare_func, + }); + if (!req) + return -ENOENT; - if (found) { + if (ret) + *ret = req; + return 0; + } + + ORDERED_SET_FOREACH(req, m->request_queue) { + + if (req->type != REQUEST_TYPE_ROUTING_POLICY_RULE) + continue; + + RoutingPolicyRule *rule = ASSERT_PTR(req->userdata); + if (routing_policy_rule_equal(in, rule, family, rule->priority)) { if (ret) - *ret = rule; + *ret = req; return 0; } } @@ -416,6 +484,21 @@ static int routing_policy_rule_acquire_priority(Manager *manager, RoutingPolicyR return r; } + Request *req; + ORDERED_SET_FOREACH(req, manager->request_queue) { + if (req->type != REQUEST_TYPE_ROUTING_POLICY_RULE) + continue; + + tmp = ASSERT_PTR(req->userdata); + if (tmp->family != rule->family) + continue; + if (tmp->priority == 0 || tmp->priority > 32765) + continue; + r = set_ensure_put(&priorities, NULL, UINT32_TO_PTR(tmp->priority)); + if (r < 0) + return r; + } + ORDERED_HASHMAP_FOREACH(network, manager->networks) HASHMAP_FOREACH(tmp, network->rules_by_section) { if (tmp->family != AF_UNSPEC && tmp->family != rule->family) @@ -429,11 +512,13 @@ static int routing_policy_rule_acquire_priority(Manager *manager, RoutingPolicyR return r; } - for (priority = 32765; priority > 0; priority--) + /* priority must be smaller than goto target */ + for (priority = rule->type == FR_ACT_GOTO ? rule->priority_goto - 1 : 32765; priority > 0; priority--) if (!set_contains(priorities, UINT32_TO_PTR(priority))) break; rule->priority = priority; + rule->priority_set = true; return 0; } @@ -584,33 +669,76 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule if (r < 0) return r; + if (rule->type == FR_ACT_GOTO) { + r = sd_netlink_message_append_u32(m, FRA_GOTO, rule->priority_goto); + if (r < 0) + return r; + } + + if (rule->realms > 0) { + r = sd_netlink_message_append_u32(m, FRA_FLOW, rule->realms); + if (r < 0) + return r; + } + + if (rule->tunnel_id > 0) { + r = sd_netlink_message_append_u64(m, FRA_TUN_ID, htobe64(rule->tunnel_id)); + if (r < 0) + return r; + } + return 0; } -static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { +static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); + assert(rreq); + + Manager *manager = ASSERT_PTR(rreq->manager); + RoutingPolicyRule *rule = ASSERT_PTR(rreq->userdata); r = sd_netlink_message_get_errno(m); - if (r < 0) - log_message_warning_errno(m, r, "Could not drop routing policy rule"); + if (r < 0) { + log_message_full_errno(m, + (r == -ENOENT || !rule->manager) ? LOG_DEBUG : LOG_WARNING, + r, "Could not drop routing policy rule, ignoring"); + + if (rule->manager) { + /* If the rule cannot be removed, then assume the rule is already removed. */ + log_routing_policy_rule_debug(rule, "Forgetting", NULL, manager); + + Request *req; + if (routing_policy_rule_get_request(manager, rule, rule->family, &req) >= 0) + routing_policy_rule_enter_removed(req->userdata); + + routing_policy_rule_detach(rule); + } + } return 1; } -static int routing_policy_rule_remove(RoutingPolicyRule *rule) { +static int routing_policy_rule_remove(RoutingPolicyRule *rule, Manager *manager) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; assert(rule); - assert(rule->manager); - assert(rule->manager->rtnl); assert(IN_SET(rule->family, AF_INET, AF_INET6)); + assert(manager); + assert(manager->rtnl); + + /* If the rule is remembered, then use the remembered object. */ + (void) routing_policy_rule_get(manager, rule, rule->family, &rule); + + /* We cannot remove rules with the permanent flag. */ + if (FLAGS_SET(rule->flags, FIB_RULE_PERMANENT)) + return 0; - log_routing_policy_rule_debug(rule, "Removing", NULL, rule->manager); + log_routing_policy_rule_debug(rule, "Removing", NULL, manager); - r = sd_rtnl_message_new_routing_policy_rule(rule->manager->rtnl, &m, RTM_DELRULE, rule->family); + r = sd_rtnl_message_new_routing_policy_rule(manager->rtnl, &m, RTM_DELRULE, rule->family); if (r < 0) return log_warning_errno(r, "Could not allocate netlink message: %m"); @@ -618,11 +746,10 @@ static int routing_policy_rule_remove(RoutingPolicyRule *rule) { if (r < 0) return log_warning_errno(r, "Could not create netlink message: %m"); - r = netlink_call_async(rule->manager->rtnl, NULL, m, - routing_policy_rule_remove_handler, - NULL, NULL); + r = manager_remove_request_add(manager, rule, routing_policy_rule, + manager->rtnl, m, routing_policy_rule_remove_handler); if (r < 0) - return log_warning_errno(r, "Could not send netlink message: %m"); + return log_warning_errno(r, "Could not queue rtnetlink message: %m"); routing_policy_rule_enter_removing(rule); return 0; @@ -653,6 +780,23 @@ static int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, Re return request_call_netlink_async(link->manager->rtnl, m, req); } +static void manager_unmark_routing_policy_rule(Manager *m, const RoutingPolicyRule *rule, int family) { + RoutingPolicyRule *existing; + + assert(m); + assert(rule); + assert(rule->family == AF_UNSPEC || rule->family == family); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (routing_policy_rule_get(m, rule, family, &existing) < 0) + return; + + if (!routing_policy_rule_can_update(existing, rule, rule->family)) + return; + + routing_policy_rule_unmark(existing); +} + static void manager_mark_routing_policy_rules(Manager *m, bool foreign, const Link *except) { RoutingPolicyRule *rule; Link *link; @@ -685,22 +829,12 @@ static void manager_mark_routing_policy_rules(Manager *m, bool foreign, const Li continue; HASHMAP_FOREACH(rule, link->network->rules_by_section) { - RoutingPolicyRule *existing; - - if (IN_SET(rule->family, AF_INET, AF_INET6)) { - if (routing_policy_rule_get(m, rule, &existing) >= 0) - routing_policy_rule_unmark(existing); - } else { - /* The case Family=both. */ - rule->family = AF_INET; - if (routing_policy_rule_get(m, rule, &existing) >= 0) - routing_policy_rule_unmark(existing); - - rule->family = AF_INET6; - if (routing_policy_rule_get(m, rule, &existing) >= 0) - routing_policy_rule_unmark(existing); - - rule->family = AF_UNSPEC; + if (IN_SET(rule->family, AF_INET, AF_INET6)) + manager_unmark_routing_policy_rule(m, rule, rule->family); + else { + assert(rule->address_family == ADDRESS_FAMILY_YES); + manager_unmark_routing_policy_rule(m, rule, AF_INET); + manager_unmark_routing_policy_rule(m, rule, AF_INET6); } } } @@ -718,7 +852,7 @@ int manager_drop_routing_policy_rules_internal(Manager *m, bool foreign, const L if (!routing_policy_rule_is_marked(rule)) continue; - RET_GATHER(r, routing_policy_rule_remove(rule)); + RET_GATHER(r, routing_policy_rule_remove(rule, m)); } return r; @@ -741,10 +875,12 @@ void link_foreignize_routing_policy_rules(Link *link) { } static int routing_policy_rule_process_request(Request *req, Link *link, RoutingPolicyRule *rule) { + RoutingPolicyRule *existing; int r; assert(req); assert(link); + assert(link->manager); assert(rule); if (!link_is_ready_to_configure(link, false)) @@ -755,6 +891,9 @@ static int routing_policy_rule_process_request(Request *req, Link *link, Routing return log_link_warning_errno(link, r, "Failed to configure routing policy rule: %m"); routing_policy_rule_enter_configuring(rule); + if (routing_policy_rule_get(link->manager, rule, rule->family, &existing) >= 0) + routing_policy_rule_enter_configuring(existing); + return 1; } @@ -769,16 +908,24 @@ static int static_routing_policy_rule_configure_handler( assert(m); assert(link); + assert(rule); r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { + if (r == -EEXIST) { + RoutingPolicyRule *existing; + + if (routing_policy_rule_get(link->manager, rule, rule->family, &existing) >= 0) { + existing->source = rule->source; + routing_policy_rule_enter_configured(existing); + } + } else if (r < 0) { log_link_message_warning_errno(link, m, r, "Could not add routing policy rule"); link_enter_failed(link); return 1; } if (link->static_routing_policy_rule_messages == 0) { - log_link_debug(link, "Routing policy rule configured"); + log_link_debug(link, "Routing policy rule configured."); link->static_routing_policy_rules_configured = true; link_check_ready(link); } @@ -786,37 +933,44 @@ static int static_routing_policy_rule_configure_handler( return 1; } -static int link_request_routing_policy_rule(Link *link, RoutingPolicyRule *rule) { - RoutingPolicyRule *existing; +static int link_request_routing_policy_rule(Link *link, const RoutingPolicyRule *rule, int family) { + _cleanup_(routing_policy_rule_unrefp) RoutingPolicyRule *tmp = NULL; + RoutingPolicyRule *existing = NULL; int r; assert(link); assert(link->manager); assert(rule); assert(rule->source != NETWORK_CONFIG_SOURCE_FOREIGN); + assert(rule->family == AF_UNSPEC || rule->family == family); + assert(IN_SET(family, AF_INET, AF_INET6)); - if (routing_policy_rule_get(link->manager, rule, &existing) < 0) { - _cleanup_(routing_policy_rule_unrefp) RoutingPolicyRule *tmp = NULL; + if (routing_policy_rule_get_request(link->manager, rule, family, NULL) >= 0) + return 0; /* already requested, skipping. */ - r = routing_policy_rule_dup(rule, &tmp); - if (r < 0) - return r; + r = routing_policy_rule_dup(rule, family, &tmp); + if (r < 0) + return r; + if (routing_policy_rule_get(link->manager, tmp, family, &existing) < 0) { r = routing_policy_rule_acquire_priority(link->manager, tmp); if (r < 0) return r; + } else { + /* Copy priority from existing rule. */ + if (!tmp->priority_set) { + tmp->priority_set = true; + tmp->priority = existing->priority; + } - r = routing_policy_rule_attach(link->manager, tmp); - if (r < 0) - return r; - - existing = tmp; - } else - existing->source = rule->source; + /* Copy state for logging below. */ + tmp->state = existing->state; + } - log_routing_policy_rule_debug(existing, "Requesting", link, link->manager); + log_routing_policy_rule_debug(tmp, "Requesting", link, link->manager); r = link_queue_request_safe(link, REQUEST_TYPE_ROUTING_POLICY_RULE, - existing, NULL, + tmp, + routing_policy_rule_unref, routing_policy_rule_hash_func, routing_policy_rule_compare_func, routing_policy_rule_process_request, @@ -826,27 +980,31 @@ static int link_request_routing_policy_rule(Link *link, RoutingPolicyRule *rule) if (r <= 0) return r; - routing_policy_rule_enter_requesting(existing); + routing_policy_rule_enter_requesting(tmp); + if (existing) + routing_policy_rule_enter_requesting(existing); + + TAKE_PTR(tmp); return 1; } -static int link_request_static_routing_policy_rule(Link *link, RoutingPolicyRule *rule) { +static int link_request_static_routing_policy_rule(Link *link, const RoutingPolicyRule *rule) { int r; if (IN_SET(rule->family, AF_INET, AF_INET6)) - return link_request_routing_policy_rule(link, rule); + return link_request_routing_policy_rule(link, rule, rule->family); - rule->family = AF_INET; - r = link_request_routing_policy_rule(link, rule); - if (r < 0) { - rule->family = AF_UNSPEC; + assert(rule->address_family == ADDRESS_FAMILY_YES); + + r = link_request_routing_policy_rule(link, rule, AF_INET); + if (r < 0) return r; - } - rule->family = AF_INET6; - r = link_request_routing_policy_rule(link, rule); - rule->family = AF_UNSPEC; - return r; + r = link_request_routing_policy_rule(link, rule, AF_INET6); + if (r < 0) + return r; + + return 0; } int link_request_static_routing_policy_rules(Link *link) { @@ -868,7 +1026,7 @@ int link_request_static_routing_policy_rules(Link *link) { link->static_routing_policy_rules_configured = true; link_check_ready(link); } else { - log_link_debug(link, "Requesting routing policy rules"); + log_link_debug(link, "Requesting routing policy rules."); link_set_state(link, LINK_STATE_CONFIGURING); } @@ -888,8 +1046,8 @@ static const RoutingPolicyRule kernel_rules[] = { static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) { assert(rule); - for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++) - if (routing_policy_rule_equal(rule, &kernel_rules[i])) + FOREACH_ELEMENT(i, kernel_rules) + if (routing_policy_rule_equal(rule, i, i->family, i->priority)) return true; return false; @@ -897,8 +1055,9 @@ static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *ru int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { _cleanup_(routing_policy_rule_unrefp) RoutingPolicyRule *tmp = NULL; + bool adjust_protocol = false, is_new = false; RoutingPolicyRule *rule = NULL; - bool adjust_protocol = false; + Request *req = NULL; uint16_t type; int r; @@ -961,12 +1120,6 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man } } - r = sd_rtnl_message_routing_policy_rule_get_flags(message, &tmp->flags); - if (r < 0) { - log_warning_errno(r, "rtnl: received rule message without valid flag, ignoring: %m"); - return 0; - } - r = sd_netlink_message_read_u32(message, FRA_FWMARK, &tmp->fwmark); if (r < 0 && r != -ENODATA) { log_warning_errno(r, "rtnl: could not get FRA_FWMARK attribute, ignoring: %m"); @@ -1079,47 +1232,81 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man if (r >= 0) tmp->suppress_ifgroup = (int32_t) suppress_ifgroup; + r = sd_netlink_message_read_u64(message, FRA_TUN_ID, &tmp->tunnel_id); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_TUN_ID attribute, ignoring: %m"); + return 0; + } + if (r >= 0) + tmp->tunnel_id = be64toh(tmp->tunnel_id); + + r = sd_netlink_message_read_u32(message, FRA_FLOW, &tmp->realms); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_FLOW attribute, ignoring: %m"); + return 0; + } + if (adjust_protocol) /* As .network files does not have setting to specify protocol, we can assume the * protocol of the received rule is RTPROT_KERNEL or RTPROT_STATIC. */ tmp->protocol = routing_policy_rule_is_created_by_kernel(tmp) ? RTPROT_KERNEL : RTPROT_STATIC; - (void) routing_policy_rule_get(m, tmp, &rule); + (void) routing_policy_rule_get(m, tmp, tmp->family, &rule); + (void) routing_policy_rule_get_request(m, tmp, tmp->family, &req); - switch (type) { - case RTM_NEWRULE: - if (rule) { - routing_policy_rule_enter_configured(rule); - log_routing_policy_rule_debug(rule, "Received remembered", NULL, m); - } else if (!m->manage_foreign_rules) { - routing_policy_rule_enter_configured(tmp); - log_routing_policy_rule_debug(tmp, "Ignoring received", NULL, m); - } else { - routing_policy_rule_enter_configured(tmp); - log_routing_policy_rule_debug(tmp, "Remembering", NULL, m); - r = routing_policy_rule_attach(m, tmp); - if (r < 0) { - log_warning_errno(r, "Could not remember foreign rule, ignoring: %m"); - return 0; - } - } - break; - case RTM_DELRULE: + if (type == RTM_DELRULE) { if (rule) { routing_policy_rule_enter_removed(rule); - if (rule->state == 0) { - log_routing_policy_rule_debug(rule, "Forgetting", NULL, m); - routing_policy_rule_detach(rule); - } else - log_routing_policy_rule_debug(rule, "Removed", NULL, m); + log_routing_policy_rule_debug(rule, "Forgetting removed", NULL, m); + routing_policy_rule_detach(rule); } else log_routing_policy_rule_debug(tmp, "Kernel removed unknown", NULL, m); - break; - default: - assert_not_reached(); + if (req) + routing_policy_rule_enter_removed(req->userdata); + + return 0; + } + + if (!rule) { + if (!req && !m->manage_foreign_rules) { + routing_policy_rule_enter_configured(tmp); + log_routing_policy_rule_debug(tmp, "Ignoring received", NULL, m); + return 0; + } + + /* If we did not know the rule, then save it. */ + r = routing_policy_rule_attach(m, tmp); + if (r < 0) { + log_warning_errno(r, "Failed to save received routing policy rule, ignoring: %m"); + return 0; + } + + rule = tmp; + is_new = true; } + /* Also update information that cannot be obtained through netlink notification. */ + if (req && req->waiting_reply) { + RoutingPolicyRule *req_rule = ASSERT_PTR(req->userdata); + + rule->source = req_rule->source; + } + + /* Then, update miscellaneous info from netlink notification. */ + r = sd_rtnl_message_routing_policy_rule_get_flags(message, &rule->flags); + if (r < 0) + log_debug_errno(r, "rtnl: received rule message without valid flag, ignoring: %m"); + + r = sd_netlink_message_read_u32(message, FRA_GOTO, &rule->priority_goto); + if (r < 0 && r != -ENODATA) + log_debug_errno(r, "rtnl: could not get FRA_GOTO attribute, ignoring: %m"); + + routing_policy_rule_enter_configured(rule); + if (req) + routing_policy_rule_enter_configured(req->userdata); + + log_routing_policy_rule_debug(rule, is_new ? "Remembering" : "Received remembered", NULL, m); return 1; } @@ -1239,6 +1426,48 @@ int config_parse_routing_policy_rule_priority( return 0; } +int config_parse_routing_policy_rule_goto( + 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_unref_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = ASSERT_PTR(userdata); + uint32_t priority; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &priority); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue); + return 0; + } + if (priority <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid goto target priority, ignoring assignment."); + return 0; + } + + n->type = FR_ACT_GOTO; + n->priority_goto = priority; + + TAKE_PTR(n); + return 0; +} + int config_parse_routing_policy_rule_table( const char *unit, const char *filename, @@ -1795,6 +2024,23 @@ static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) { if (rule->l3mdev) rule->table = RT_TABLE_UNSPEC; + if (rule->type == FR_ACT_GOTO) { + if (rule->priority_goto <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Type=goto is specified but the target priority GoTo= is unspecified. " + "Ignoring [RoutingPolicyRule] section from line %u.", + rule->section->filename, + rule->section->line); + + if (rule->priority_set && rule->priority >= rule->priority_goto) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: goto target priority %"PRIu32" must be larger than the priority of this rule %"PRIu32". " + "Ignoring [RoutingPolicyRule] section from line %u.", + rule->section->filename, + rule->priority_goto, rule->priority, + rule->section->line); + } + return 0; } diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h index 7047cc4e9b..c7a0512b44 100644 --- a/src/network/networkd-routing-policy-rule.h +++ b/src/network/networkd-routing-policy-rule.h @@ -35,12 +35,12 @@ typedef struct RoutingPolicyRule { union in_addr_union to; /* FRA_DST */ union in_addr_union from; /* FRA_SRC */ char *iif; /* FRA_IIFNAME */ - /* FRA_GOTO */ + uint32_t priority_goto; /* FRA_GOTO */ bool priority_set; uint32_t priority; /* FRA_PRIORITY */ uint32_t fwmark; /* FRA_FWMARK */ - /* FRA_FLOW */ - /* FRA_TUN_ID */ + uint32_t realms; /* FRA_FLOW (IPv4 only) */ + uint64_t tunnel_id; /* FRA_TUN_ID */ int32_t suppress_ifgroup; /* FRA_SUPPRESS_IFGROUP */ int32_t suppress_prefixlen; /* FRA_SUPPRESS_PREFIXLEN */ uint32_t table; /* FRA_TABLE, also used in struct fib_rule_hdr */ @@ -54,7 +54,8 @@ typedef struct RoutingPolicyRule { struct fib_rule_port_range dport; /* FRA_DPORT_RANGE */ } RoutingPolicyRule; -const char* fr_act_type_full_to_string(int t) _const_; +int fr_act_type_from_string(const char *s) _pure_; +const char* fr_act_type_to_string(int t) _const_; RoutingPolicyRule* routing_policy_rule_ref(RoutingPolicyRule *rule); RoutingPolicyRule* routing_policy_rule_unref(RoutingPolicyRule *rule); @@ -81,6 +82,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_table); 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_goto); 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); diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 34db2bbf1c..b091725510 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -112,6 +112,7 @@ int sd_netlink_message_read_strv(sd_netlink_message *m, uint16_t container_type, int sd_netlink_message_read_u8(sd_netlink_message *m, uint16_t attr_type, uint8_t *data); int sd_netlink_message_read_u16(sd_netlink_message *m, uint16_t attr_type, uint16_t *data); int sd_netlink_message_read_u32(sd_netlink_message *m, uint16_t attr_type, uint32_t *data); +int sd_netlink_message_read_u64(sd_netlink_message *m, uint16_t attr_type, uint64_t *ret); int sd_netlink_message_read_ether_addr(sd_netlink_message *m, uint16_t attr_type, struct ether_addr *data); int sd_netlink_message_read_cache_info(sd_netlink_message *m, uint16_t attr_type, struct ifa_cacheinfo *info); int sd_netlink_message_read_in_addr(sd_netlink_message *m, uint16_t attr_type, struct in_addr *data); diff --git a/test/test-network/conf/25-routing-policy-rule-test1.network b/test/test-network/conf/25-routing-policy-rule-test1.network index 7d6e17cf6c..2cec4334fd 100644 --- a/test/test-network/conf/25-routing-policy-rule-test1.network +++ b/test/test-network/conf/25-routing-policy-rule-test1.network @@ -27,7 +27,23 @@ Priority=101 Family=both [RoutingPolicyRule] +Type=table IncomingInterface=test1 From=0.0.0.0/8 Table=10 Priority=102 + +[RoutingPolicyRule] +Type=goto +IncomingInterface=test1 +From=10.0.0.0/16 +Priority=103 +GoTo=111 +Table=11 + +[RoutingPolicyRule] +Type=nop +IncomingInterface=test1 +From=10.1.0.0/16 +Priority=104 +Table=12 diff --git a/test/test-network/conf/networkd-manage-foreign-rules-no.conf b/test/test-network/conf/networkd-manage-foreign-rules-no.conf new file mode 100644 index 0000000000..b376889fc8 --- /dev/null +++ b/test/test-network/conf/networkd-manage-foreign-rules-no.conf @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Network] +ManageForeignRoutingPolicyRules=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 3cf743dec2..fae7b58fcc 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -645,7 +645,7 @@ def flush_routing_policy_rules(): have = True print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') print(f'# {line}') - words = line.replace('lookup [l3mdev-table]', 'l3mdev').split() + words = line.replace('lookup [l3mdev-table]', 'l3mdev').replace('[detached]', '').split() priority = words[0].rstrip(':') call(f'ip -{ipv} rule del priority {priority} ' + ' '.join(words[1:])) @@ -3210,43 +3210,80 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertNotRegex(output, '192.168.0.1') self.assertRegex(output, routable_map[carrier]) - def test_routing_policy_rule(self): - copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev') - start_networkd() - self.wait_online('test1:degraded') - + def check_routing_policy_rule_test1(self): output = check_output('ip rule list iif test1 priority 111') print(output) - self.assertRegex(output, '111:') - self.assertRegex(output, 'from 192.168.100.18') - self.assertRegex(output, r'tos (0x08|throughput)\s') - self.assertRegex(output, 'iif test1') - self.assertRegex(output, 'oif test1') - self.assertRegex(output, 'lookup 7') + self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') - output = check_output('ip rule list iif test1 priority 101') + output = check_output('ip -6 rule list iif test1 priority 100') print(output) - self.assertRegex(output, '101:') - self.assertRegex(output, 'from all') - self.assertRegex(output, 'iif test1') - self.assertRegex(output, 'lookup 9') + self.assertIn('100: from all iif test1 lookup 8', output) - output = check_output('ip -6 rule list iif test1 priority 100') + output = check_output('ip rule list iif test1 priority 101') print(output) - self.assertRegex(output, '100:') - self.assertRegex(output, 'from all') - self.assertRegex(output, 'iif test1') - self.assertRegex(output, 'lookup 8') + self.assertIn('101: from all iif test1 lookup 9', output) output = check_output('ip rule list iif test1 priority 102') print(output) - self.assertRegex(output, '102:') - self.assertRegex(output, 'from 0.0.0.0/8') - self.assertRegex(output, 'iif test1') - self.assertRegex(output, 'lookup 10') + self.assertIn('102: from 0.0.0.0/8 iif test1 lookup 10', output) + + output = check_output('ip rule list iif test1 priority 103') + print(output) + self.assertIn('103: from 10.0.0.0/16 iif test1 lookup 11 goto 111', output) + output = check_output('ip rule list iif test1 priority 104') + print(output) + self.assertIn('104: from 10.1.0.0/16 iif test1 lookup 12 nop', output) + + def check_routing_policy_rule_dummy98(self): + output = check_output('ip rule list table 8') + print(output) + self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') + + def _test_routing_policy_rule(self, manage_foreign_routes): + if not manage_foreign_routes: + copy_networkd_conf_dropin('networkd-manage-foreign-rules-no.conf') + copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev') + + stop_networkd() + + check_output('ip -4 rule add priority 20001 table 9999 from 10.10.0.0/16') + check_output('ip -6 rule add priority 20001 table 9999 from 2001:db8:0:1::/64') + + start_networkd() + self.wait_online('test1:degraded') + + self.check_routing_policy_rule_test1() check_json(networkctl_json()) + output = check_output('ip -4 rule list priority 20001 table 9999 from 10.10.0.0/16') + print(output) + if manage_foreign_routes: + self.assertEqual(output, '') + else: + self.assertIn(output, '20001: from 10.10.0.0/16 lookup 9999') + check_output('ip -4 rule del priority 20001 table 9999 from 10.10.0.0/16') + + output = check_output('ip -6 rule list priority 20001 table 9999 from 2001:db8:0:1::/64') + print(output) + if manage_foreign_routes: + self.assertEqual(output, '') + else: + self.assertIn(output, '20001: from 2001:db8:0:1::/64 lookup 9999') + check_output('ip -6 rule del priority 20001 table 9999 from 2001:db8:0:1::/64') + + def test_routing_policy_rule(self): + first = True + for manage_foreign_routes in [True, False]: + if first: + first = False + else: + self.tearDown() + + print(f'### test_routing_policy_rule(manage_foreign_routes={manage_foreign_routes})') + with self.subTest(manage_foreign_routes=manage_foreign_routes): + self._test_routing_policy_rule(manage_foreign_routes) + def test_routing_policy_rule_issue_11280(self): copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev', '25-routing-policy-rule-dummy98.network', '12-dummy.netdev') @@ -3255,13 +3292,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): restart_networkd(show_logs=(trial > 0)) self.wait_online('test1:degraded', 'dummy98:degraded') - output = check_output('ip rule list table 7') - print(output) - self.assertRegex(output, '111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') - - output = check_output('ip rule list table 8') - print(output) - self.assertRegex(output, '112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') + self.check_routing_policy_rule_test1() + self.check_routing_policy_rule_dummy98() def test_routing_policy_rule_reconfigure(self): copy_network_unit('25-routing-policy-rule-reconfigure2.network', '11-dummy.netdev') |