From: Yu Watanabe Date: Fri, 14 May 2021 01:15:23 +0000 (+0900) Subject: network: nexthop: add Group= setting to configure multipath route with group nexthop X-Git-Tag: v249-rc1~176^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=228c3e21e957d2ff0ee63143cfe4eb0b3d88959b;p=thirdparty%2Fsystemd.git network: nexthop: add Group= setting to configure multipath route with group nexthop --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 2b8c1984dab..61acea1a8b5 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1386,6 +1386,19 @@ IPv6Token=prefixstable:2002:da8:1:: no. + + Group= + + Takes a whitespace separated list of nexthop IDs. Each ID must be in the range + 1…4294967295. Optionally, each nexthop ID can take a weight after a colon + (id:weight). + The weight must be in the range 1…255. If the weight is not specified, then it is assumed + that the weight is 1. This setting cannot be specified with Gateway=, + Family=, Blackhole=. This setting can be specified + multiple times. If an empty string is assigned, then the all previous assignments are + cleared. Defaults to unset. + + diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index cfa53503d43..64b77a02769 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -194,6 +194,7 @@ NextHop.Gateway, config_parse_nexthop_gateway, NextHop.Family, config_parse_nexthop_family, 0, 0 NextHop.OnLink, config_parse_nexthop_onlink, 0, 0 NextHop.Blackhole, config_parse_nexthop_blackhole, 0, 0 +NextHop.Group, config_parse_nexthop_group, 0, 0 DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) DHCPv4.UseDNS, config_parse_dhcp_use_dns, 0, 0 DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 91f0eb30323..2bd1cec0bd5 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -44,6 +44,8 @@ NextHop *nexthop_free(NextHop *nexthop) { hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); } + hashmap_free_free(nexthop->group); + return mfree(nexthop); } @@ -164,17 +166,41 @@ static bool nexthop_equal(const NextHop *a, const NextHop *b) { return nexthop_compare_func(a, b) == 0; } -static void nexthop_copy(NextHop *dest, const NextHop *src) { - assert(dest); +static int nexthop_dup(const NextHop *src, NextHop **ret) { + _cleanup_(nexthop_freep) NextHop *dest = NULL; + struct nexthop_grp *nhg; + int r; + assert(src); + assert(ret); + + dest = newdup(NextHop, src, 1); + if (!dest) + return -ENOMEM; + + /* unset all pointers */ + dest->manager = NULL; + dest->link = NULL; + dest->network = NULL; + dest->section = NULL; + dest->group = NULL; - /* This only copies entries used in the above hash and compare functions. */ + HASHMAP_FOREACH(nhg, src->group) { + _cleanup_free_ struct nexthop_grp *g = NULL; - dest->protocol = src->protocol; - dest->id = src->id; - dest->blackhole = src->blackhole; - dest->family = src->family; - dest->gw = src->gw; + g = newdup(struct nexthop_grp, nhg, 1); + if (!g) + return -ENOMEM; + + r = hashmap_ensure_put(&dest->group, NULL, UINT32_TO_PTR(g->id), g); + if (r < 0) + return r; + if (r > 0) + TAKE_PTR(g); + } + + *ret = TAKE_PTR(dest); + return 0; } int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) { @@ -225,12 +251,10 @@ static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, co assert(nexthops); assert(in); - r = nexthop_new(&nexthop); + r = nexthop_dup(in, &nexthop); if (r < 0) return r; - nexthop_copy(nexthop, in); - r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop); if (r < 0) return r; @@ -252,33 +276,40 @@ static int nexthop_add_foreign(Manager *manager, Link *link, const NextHop *in, return nexthop_add_internal(manager, link, link ? &link->nexthops_foreign : &manager->nexthops_foreign, in, ret); } +static bool nexthop_has_link(const NextHop *nexthop) { + return !nexthop->blackhole && hashmap_isempty(nexthop->group); +} + static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) { + bool by_manager; NextHop *nexthop; int r; assert(link); assert(in); - if (in->blackhole) + by_manager = !nexthop_has_link(in); + + if (by_manager) r = nexthop_get(link->manager, NULL, in, &nexthop); else r = nexthop_get(NULL, link, in, &nexthop); if (r == -ENOENT) { /* NextHop does not exist, create a new one */ r = nexthop_add_internal(link->manager, - in->blackhole ? NULL : link, - in->blackhole ? &link->manager->nexthops : &link->nexthops, + by_manager ? NULL : link, + by_manager ? &link->manager->nexthops : &link->nexthops, in, &nexthop); if (r < 0) return r; } else if (r == 0) { /* Take over a foreign nexthop */ - r = set_ensure_put(in->blackhole ? &link->manager->nexthops : &link->nexthops, + r = set_ensure_put(by_manager ? &link->manager->nexthops : &link->nexthops, &nexthop_hash_ops, nexthop); if (r < 0) return r; - set_remove(in->blackhole ? link->manager->nexthops_foreign : link->nexthops_foreign, nexthop); + set_remove(by_manager ? link->manager->nexthops_foreign : link->nexthops_foreign, nexthop); } else if (r == 1) { /* NextHop exists, do nothing */ ; @@ -337,7 +368,8 @@ set_manager: } static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *str, const Link *link) { - _cleanup_free_ char *gw = NULL; + _cleanup_free_ char *gw = NULL, *new_id = NULL, *group = NULL; + struct nexthop_grp *nhg; assert(nexthop); assert(str); @@ -347,14 +379,16 @@ static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *s if (!DEBUG_LOGGING) return; + if (nexthop->id != id) + (void) asprintf(&new_id, "→%"PRIu32, id); + (void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw); - if (nexthop->id == id) - log_link_debug(link, "%s nexthop: id: %"PRIu32", gw: %s, blackhole: %s", - str, nexthop->id, strna(gw), yes_no(nexthop->blackhole)); - else - log_link_debug(link, "%s nexthop: id: %"PRIu32"→%"PRIu32", gw: %s, blackhole: %s", - str, nexthop->id, id, strna(gw), yes_no(nexthop->blackhole)); + HASHMAP_FOREACH(nhg, nexthop->group) + (void) strextendf_with_separator(&group, ",", "%"PRIu32":%"PRIu32, nhg->id, nhg->weight+1); + + log_link_debug(link, "%s nexthop: id: %"PRIu32"%s, gw: %s, blackhole: %s, group: %s", + str, nexthop->id, strempty(new_id), strna(gw), yes_no(nexthop->blackhole), strna(group)); } static int link_nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { @@ -448,7 +482,7 @@ static int nexthop_configure( assert(link->manager); assert(link->manager->rtnl); assert(link->ifindex > 0); - assert(IN_SET(nexthop->family, AF_INET, AF_INET6)); + assert(IN_SET(nexthop->family, AF_UNSPEC, AF_INET, AF_INET6)); assert(callback); log_nexthop_debug(nexthop, nexthop->id, "Configuring", link); @@ -465,7 +499,23 @@ static int nexthop_configure( return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); } - if (nexthop->blackhole) { + if (!hashmap_isempty(nexthop->group)) { + _cleanup_free_ struct nexthop_grp *group = NULL; + struct nexthop_grp *p, *nhg; + + group = new(struct nexthop_grp, hashmap_size(nexthop->group)); + if (!group) + return log_oom(); + + p = group; + HASHMAP_FOREACH(nhg, nexthop->group) + *p++ = *nhg; + + r = sd_netlink_message_append_data(req, NHA_GROUP, group, sizeof(struct nexthop_grp) * hashmap_size(nexthop->group)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_GROUP attribute: %m"); + + } else if (nexthop->blackhole) { r = sd_netlink_message_append_flag(req, NHA_BLACKHOLE); if (r < 0) return log_link_error_errno(link, r, "Could not append NHA_BLACKHOLE attribute: %m"); @@ -693,13 +743,15 @@ int link_drop_nexthops(Link *link) { } static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { + struct nexthop_grp *nhg; + assert(link); assert(nexthop); if (!link_is_ready_to_configure(link, false)) return false; - if (nexthop->blackhole) { + if (!nexthop_has_link(nexthop)) { if (link->manager->nexthop_remove_messages > 0) return false; } else { @@ -721,6 +773,11 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { } } + /* All group members must be configured first. */ + HASHMAP_FOREACH(nhg, nexthop->group) + if (manager_get_nexthop_by_id(link->manager, nhg->id, NULL) < 0) + return false; + if (nexthop->id == 0) { Request *req; @@ -770,7 +827,9 @@ int request_process_nexthop(Request *req) { int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { _cleanup_(nexthop_freep) NextHop *tmp = NULL; + _cleanup_free_ void *raw_group = NULL; NextHop *nexthop = NULL; + size_t raw_group_size; uint32_t ifindex; uint16_t type; Link *link = NULL; @@ -823,7 +882,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) { log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m"); return 0; - } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + } else if (!IN_SET(tmp->family, AF_UNSPEC, AF_INET, AF_INET6)) { log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); return 0; } @@ -834,10 +893,56 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, return 0; } - r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); + r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group); if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m"); return 0; + } else if (r >= 0) { + struct nexthop_grp *group = raw_group; + size_t n_group; + + if (raw_group_size == 0 || raw_group_size % sizeof(struct nexthop_grp) != 0) { + log_link_warning(link, "rtnl: received nexthop message with invalid nexthop group size, ignoring."); + return 0; + } + + assert((uintptr_t) group % __alignof__(struct nexthop_grp) == 0); + + n_group = raw_group_size / sizeof(struct nexthop_grp); + for (size_t i = 0; i < n_group; i++) { + _cleanup_free_ struct nexthop_grp *nhg = NULL; + + if (group[i].id == 0) { + log_link_warning(link, "rtnl: received nexthop message with invalid ID in group, ignoring."); + return 0; + } + if (group[i].weight > 254) { + log_link_warning(link, "rtnl: received nexthop message with invalid weight in group, ignoring."); + return 0; + } + + nhg = newdup(struct nexthop_grp, group + i, 1); + if (!nhg) + return log_oom(); + + r = hashmap_ensure_put(&tmp->group, NULL, UINT32_TO_PTR(nhg->id), nhg); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to store nexthop group, ignoring: %m"); + return 0; + } + if (r > 0) + TAKE_PTR(nhg); + } + } + + if (tmp->family != AF_UNSPEC) { + r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); + return 0; + } } r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE); @@ -859,9 +964,9 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, return 0; } - /* All blackhole nexthops are managed by Manager. Note that the linux kernel does not set - * NHA_OID attribute when NHA_BLACKHOLE is set. Just for safety. */ - if (tmp->blackhole) + /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not + * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */ + if (!nexthop_has_link(tmp)) link = NULL; r = nexthop_get(m, link, tmp, &nexthop); @@ -913,8 +1018,26 @@ static int nexthop_section_verify(NextHop *nh) { if (section_is_invalid(nh->section)) return -EINVAL; - if (nh->family == AF_UNSPEC) - /* When no Gateway= is specified, assume IPv4. */ + if (!hashmap_isempty(nh->group)) { + if (in_addr_is_set(nh->family, &nh->gw)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: nexthop group cannot have gateway address. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + + if (nh->family != AF_UNSPEC) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: nexthop group cannot have Family= setting. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + + if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: nexthop group cannot be a blackhole. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + } else if (nh->family == AF_UNSPEC) + /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */ nh->family = AF_INET; if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) @@ -1190,3 +1313,107 @@ int config_parse_nexthop_blackhole( TAKE_PTR(n); return 0; } + +int config_parse_nexthop_group( + 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_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = nexthop_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + if (isempty(rvalue)) { + n->group = hashmap_free_free(n->group); + TAKE_PTR(n); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ struct nexthop_grp *nhg = NULL; + _cleanup_free_ char *word = NULL; + uint32_t w; + char *sep; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + if (r == 0) + break; + + nhg = new0(struct nexthop_grp, 1); + if (!nhg) + return log_oom(); + + sep = strchr(word, ':'); + if (sep) { + *sep++ = '\0'; + r = safe_atou32(sep, &w); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse weight for nexthop group, ignoring assignment: %s:%s", + word, sep); + continue; + } + if (w == 0 || w > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid weight for nexthop group, ignoring assignment: %s:%s", + word, sep); + continue; + } + /* See comments in config_parse_multipath_route(). */ + nhg->weight = w - 1; + } + + r = safe_atou32(word, &nhg->id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse nexthop ID in %s=, ignoring assignment: %s%s%s", + lvalue, word, sep ? ":" : "", strempty(sep)); + continue; + } + if (nhg->id == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Nexthop ID in %s= must be positive, ignoring assignment: %s%s%s", + lvalue, word, sep ? ":" : "", strempty(sep)); + continue; + } + + r = hashmap_ensure_put(&n->group, NULL, UINT32_TO_PTR(nhg->id), nhg); + if (r == -ENOMEM) + return log_oom(); + if (r == -EEXIST) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Nexthop ID %"PRIu32" is specified multiple times in %s=, ignoring assignment: %s%s%s", + nhg->id, lvalue, word, sep ? ":" : "", strempty(sep)); + continue; + } + assert(r > 0); + TAKE_PTR(nhg); + } + + TAKE_PTR(n); + return 0; +} diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h index b2e7b453664..fee186aed24 100644 --- a/src/network/networkd-nexthop.h +++ b/src/network/networkd-nexthop.h @@ -9,6 +9,7 @@ #include "sd-netlink.h" #include "conf-parser.h" +#include "hashmap.h" #include "in-addr-util.h" #include "networkd-util.h" @@ -31,6 +32,7 @@ typedef struct NextHop { int family; union in_addr_union gw; int onlink; + Hashmap *group; } NextHop; NextHop *nexthop_free(NextHop *nexthop); @@ -51,3 +53,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_gateway); CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_family); CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_onlink); CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_blackhole); +CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_group); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 635f9a5c176..257255d4e62 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -2,6 +2,7 @@ #include #include +#include #include "alloc-util.h" #include "netlink-util.h" @@ -468,7 +469,7 @@ static int route_get(const Manager *manager, const Link *link, const Route *in, return -ENOENT; } -static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, const NextHop *nh) { +static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, const NextHop *nh, uint8_t nh_weight) { assert(dest); assert(src); @@ -496,9 +497,11 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, c dest->nexthop_id = src->nexthop_id; if (nh) { + assert(hashmap_isempty(nh->group)); + dest->gw_family = nh->family; dest->gw = nh->gw; - dest->gw_weight = src->gw_weight; + dest->gw_weight = nh_weight != UINT8_MAX ? nh_weight : src->gw_weight; } else if (m) { dest->gw_family = m->gateway.family; dest->gw = m->gateway.address; @@ -560,7 +563,7 @@ static int route_add_internal(Manager *manager, Link *link, Set **routes, const if (r < 0) return r; - route_copy(route, in, NULL, NULL); + route_copy(route, in, NULL, NULL, UINT8_MAX); r = set_ensure_put(routes, &route_hash_ops, route); if (r < 0) @@ -584,7 +587,7 @@ static int route_add_foreign(Manager *manager, Link *link, const Route *in, Rout return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); } -static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, const NextHop *nh, Route **ret) { +static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, const NextHop *nh, uint8_t nh_weight, Route **ret) { _cleanup_(route_freep) Route *tmp = NULL; Route *route; int r; @@ -593,11 +596,13 @@ static int route_add(Manager *manager, Link *link, const Route *in, const Multip assert(in); if (nh) { + assert(hashmap_isempty(nh->group)); + r = route_new(&tmp); if (r < 0) return r; - route_copy(tmp, in, NULL, nh); + route_copy(tmp, in, NULL, nh, nh_weight); in = tmp; } else if (m) { assert(link && (m->ifindex == 0 || m->ifindex == link->ifindex)); @@ -606,7 +611,7 @@ static int route_add(Manager *manager, Link *link, const Route *in, const Multip if (r < 0) return r; - route_copy(tmp, in, m, NULL); + route_copy(tmp, in, m, NULL, UINT8_MAX); in = tmp; } @@ -640,6 +645,26 @@ static bool route_type_is_reject(const Route *route) { return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); } +static int link_has_route_one(Link *link, const Route *route, const NextHop *nh, uint8_t nh_weight) { + _cleanup_(route_freep) Route *tmp = NULL; + int r; + + assert(link); + assert(route); + assert(nh); + + r = route_new(&tmp); + if (r < 0) + return r; + + route_copy(tmp, route, NULL, nh, nh_weight); + + if (route_type_is_reject(route) || (nh && nh->blackhole)) + return route_get(link->manager, NULL, tmp, NULL) >= 0; + else + return route_get(NULL, link, tmp, NULL) >= 0; +} + int link_has_route(Link *link, const Route *route) { MultipathRoute *m; int r; @@ -648,22 +673,27 @@ int link_has_route(Link *link, const Route *route) { assert(route); if (route->nexthop_id > 0) { - _cleanup_(route_freep) Route *tmp = NULL; + struct nexthop_grp *nhg; NextHop *nh; if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) return false; - r = route_new(&tmp); - if (r < 0) - return r; + if (hashmap_isempty(nh->group)) + return link_has_route_one(link, route, nh, UINT8_MAX); - route_copy(tmp, route, NULL, nh); + HASHMAP_FOREACH(nhg, nh->group) { + NextHop *h; - if (route_type_is_reject(route) || (nh && nh->blackhole)) - return route_get(link->manager, NULL, tmp, NULL) >= 0; - else - return route_get(NULL, link, tmp, NULL) >= 0; + if (manager_get_nexthop_by_id(link->manager, nhg->id, &h) < 0) + return false; + + r = link_has_route_one(link, route, h, nhg->weight); + if (r <= 0) + return r; + } + + return true; } if (ordered_set_isempty(route->multipath_routes)) { @@ -692,7 +722,7 @@ int link_has_route(Link *link, const Route *route) { if (r < 0) return r; - route_copy(tmp, route, m, NULL); + route_copy(tmp, route, m, NULL, UINT8_MAX); if (route_get(NULL, l, tmp, NULL) < 0) return false; @@ -1102,7 +1132,7 @@ int link_drop_foreign_routes(Link *link) { continue; if (link_has_static_route(link, route)) - k = route_add(NULL, link, route, NULL, NULL, NULL); + k = route_add(NULL, link, route, NULL, NULL, UINT8_MAX, NULL); else k = route_remove(route, NULL, link); if (k < 0 && r >= 0) @@ -1154,31 +1184,34 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat return 1; } -static int route_add_and_setup_timer(Link *link, const Route *route, const MultipathRoute *m, Route **ret) { +static int route_add_and_setup_timer_one(Link *link, const Route *route, const MultipathRoute *m, const NextHop *nh, uint8_t nh_weight, Route **ret) { _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; - NextHop *nh = NULL; Route *nr; int r; assert(link); assert(link->manager); assert(route); - - (void) manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh); + assert(!(m && nh)); + assert(ret); if (route_type_is_reject(route) || (nh && nh->blackhole)) - r = route_add(link->manager, NULL, route, NULL, nh, &nr); - else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex) - r = route_add(NULL, link, route, m, nh, &nr); - else { + r = route_add(link->manager, NULL, route, NULL, nh, nh_weight, &nr); + else if (nh) { + assert(nh->link); + assert(hashmap_isempty(nh->group)); + + r = route_add(NULL, nh->link, route, NULL, nh, nh_weight, &nr); + } else if (m && m->ifindex != 0 && m->ifindex != link->ifindex) { Link *link_gw; r = link_get(link->manager, m->ifindex, &link_gw); if (r < 0) return log_link_error_errno(link, r, "Failed to get link with ifindex %d: %m", m->ifindex); - r = route_add(NULL, link_gw, route, m, NULL, &nr); - } + r = route_add(NULL, link_gw, route, m, NULL, UINT8_MAX, &nr); + } else + r = route_add(NULL, link, route, m, NULL, UINT8_MAX, &nr); if (r < 0) return log_link_error_errno(link, r, "Could not add route: %m"); @@ -1193,9 +1226,78 @@ static int route_add_and_setup_timer(Link *link, const Route *route, const Multi sd_event_source_unref(nr->expire); nr->expire = TAKE_PTR(expire); - if (ret) - *ret = nr; + *ret = nr; + return 0; +} + +static int route_add_and_setup_timer(Link *link, const Route *route, unsigned *ret_n_routes, Route ***ret_routes) { + _cleanup_free_ Route **routes = NULL; + unsigned n_routes; + NextHop *nh; + Route **p; + int r; + + assert(link); + assert(route); + assert(ret_n_routes); + assert(ret_routes); + + if (route->nexthop_id > 0) { + r = manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh); + if (r < 0) + return log_link_error_errno(link, r, "Could not get nexthop by ID %"PRIu32": %m", route->nexthop_id); + } else + nh = NULL; + + if (nh && !hashmap_isempty(nh->group)) { + struct nexthop_grp *nhg; + + n_routes = hashmap_size(nh->group); + p = routes = new(Route*, n_routes); + if (!routes) + return log_oom(); + HASHMAP_FOREACH(nhg, nh->group) { + NextHop *h; + + r = manager_get_nexthop_by_id(link->manager, nhg->id, &h); + if (r < 0) + return log_link_error_errno(link, r, "Could not get nexthop group member by ID %"PRIu32": %m", nhg->id); + + /* The nexthop h may be a blackhole nexthop. In that case, h->link is NULL. */ + r = route_add_and_setup_timer_one(h->link ?: link, route, NULL, h, nhg->weight, p++); + if (r < 0) + return r; + } + } else if (!ordered_set_isempty(route->multipath_routes)) { + MultipathRoute *m; + + assert(!nh); + assert(!in_addr_is_set(route->gw_family, &route->gw)); + + n_routes = ordered_set_size(route->multipath_routes); + p = routes = new(Route*, n_routes); + if (!routes) + return log_oom(); + + ORDERED_SET_FOREACH(m, route->multipath_routes) { + r = route_add_and_setup_timer_one(link, route, m, NULL, UINT8_MAX, p++); + if (r < 0) + return r; + } + } else { + n_routes = 1; + routes = new(Route*, n_routes); + if (!routes) + return log_oom(); + + r = route_add_and_setup_timer_one(link, route, NULL, nh, UINT8_MAX, routes); + if (r < 0) + return r; + } + + *ret_n_routes = n_routes; + *ret_routes = TAKE_PTR(routes); return 0; } @@ -1398,43 +1500,19 @@ static int route_configure( if (r < 0) return log_link_error_errno(link, r, "Could not append RTA_METRICS attribute: %m"); - if (route->nexthop_id != 0 || - in_addr_is_set(route->gw_family, &route->gw) || - ordered_set_isempty(route->multipath_routes)) { - - if (ret_routes) { - n_routes = 1; - routes = new(Route*, n_routes); - if (!routes) - return log_oom(); - } - - r = route_add_and_setup_timer(link, route, NULL, routes); - if (r < 0) - return r; - } else { - MultipathRoute *m; - Route **p; + if (!ordered_set_isempty(route->multipath_routes)) { + assert(route->nexthop_id == 0); + assert(!in_addr_is_set(route->gw_family, &route->gw)); r = append_nexthops(route, req); if (r < 0) return log_link_error_errno(link, r, "Could not append RTA_MULTIPATH attribute: %m"); - - if (ret_routes) { - n_routes = ordered_set_size(route->multipath_routes); - routes = new(Route*, n_routes); - if (!routes) - return log_oom(); - } - - p = routes; - ORDERED_SET_FOREACH(m, route->multipath_routes) { - r = route_add_and_setup_timer(link, route, m, ret_routes ? p++ : NULL); - if (r < 0) - return r; - } } + r = route_add_and_setup_timer(link, route, &n_routes, &routes); + if (r < 0) + return r; + r = netlink_call_async(link->manager->rtnl, NULL, req, callback, link_netlink_destroy_callback, link); if (r < 0) @@ -1447,7 +1525,7 @@ static int route_configure( *ret_routes = TAKE_PTR(routes); } - return 0; + return r; } static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { @@ -1529,9 +1607,16 @@ static int route_is_ready_to_configure(const Route *route, Link *link) { assert(route); assert(link); - if (route->nexthop_id > 0 && - manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) - return false; + if (route->nexthop_id > 0) { + struct nexthop_grp *nhg; + + if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) + return false; + + HASHMAP_FOREACH(nhg, nh->group) + if (manager_get_nexthop_by_id(link->manager, nhg->id, NULL) < 0) + return false; + } if (route_type_is_reject(route) || (nh && nh->blackhole)) { if (nh && link->manager->nexthop_remove_messages > 0) @@ -1637,18 +1722,19 @@ static int process_route_one(Manager *manager, Link *link, uint16_t type, const (void) manager_get_nexthop_by_id(manager, tmp->nexthop_id, &nh); - if (nh) { - if (link && link != nh->link) + if (nh && hashmap_isempty(nh->group)) { + if (link && nh->link && link != nh->link) return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), "rtnl: received RTA_OIF and ifindex of nexthop corresponding to RTA_NH_ID do not match, ignoring."); - link = nh->link; + if (nh->link) + link = nh->link; r = route_new(&nr); if (r < 0) return log_oom(); - route_copy(nr, tmp, NULL, nh); + route_copy(nr, tmp, NULL, nh, UINT8_MAX); tmp = nr; } else if (m) { @@ -1670,7 +1756,7 @@ static int process_route_one(Manager *manager, Link *link, uint16_t type, const if (r < 0) return log_oom(); - route_copy(nr, tmp, m, NULL); + route_copy(nr, tmp, m, NULL, UINT8_MAX); tmp = nr; } diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 84717985ed2..6d2d5f8054e 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -372,6 +372,7 @@ Gateway= Family= OnLink= Blackhole= +Group= [QDisc] Parent= Handle=