]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: nexthop: add Group= setting to configure multipath route with group nexthop
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 14 May 2021 01:15:23 +0000 (10:15 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 21 May 2021 19:59:40 +0000 (04:59 +0900)
man/systemd.network.xml
src/network/networkd-network-gperf.gperf
src/network/networkd-nexthop.c
src/network/networkd-nexthop.h
src/network/networkd-route.c
test/fuzz/fuzz-network-parser/directives.network

index 2b8c1984dabbb14d588f9492a155ff43a86d4e32..61acea1a8b5a09a767adeb2acd248663390796fd 100644 (file)
@@ -1386,6 +1386,19 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
             <literal>no</literal>.</para>
           </listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>Group=</varname></term>
+          <listitem>
+            <para>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
+            (<literal><replaceable>id</replaceable><optional>:<replaceable>weight</replaceable></optional></literal>).
+            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 <varname>Gateway=</varname>,
+            <varname>Family=</varname>, <varname>Blackhole=</varname>. This setting can be specified
+            multiple times. If an empty string is assigned, then the all previous assignments are
+            cleared. Defaults to unset.</para>
+          </listitem>
+        </varlistentry>
       </variablelist>
   </refsect1>
 
index cfa53503d431b432e0b9db1b16d26fa73a7a8b7f..64b77a0276907cf181ec2beaa12d2dae5a224b7b 100644 (file)
@@ -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)
index 91f0eb303232bd67793784648768d53469d791c6..2bd1cec0bd59fe92cc3f56c6e9ef83b415d5812d 100644 (file)
@@ -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;
+}
index b2e7b45366481661cd3dda24b9674480379262c1..fee186aed2411cd9a39daac019f67956f8fb6eaa 100644 (file)
@@ -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);
index 635f9a5c1763818c36565e4c3beb794c0f10e7e3..257255d4e6222e4c7f019f4094f39e1a71a2a3fe 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <linux/icmpv6.h>
 #include <linux/ipv6_route.h>
+#include <linux/nexthop.h>
 
 #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;
         }
index 84717985ed22c93a5d0f025617597c25d8a03c8b..6d2d5f8054eec6fa5be0b1774092aaefa0ab1f05 100644 (file)
@@ -372,6 +372,7 @@ Gateway=
 Family=
 OnLink=
 Blackhole=
+Group=
 [QDisc]
 Parent=
 Handle=