diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 9585111761..3be311d70b 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1380,6 +1380,16 @@ service type to CS6 (network control) or CS4 (Realtime). Defaults to CS6. + + MultiPathRoute=address[@name] [weight] + + Configures multipath route. Multipath routing is the technique of using multiple + alternative paths through a network. Takes gateway address. Optionally, takes a network + interface name or index separated with @, and a weight in 1..256 for + this multipath route separated with whitespace. This setting can be specified multiple + times. If an empty string is assigned, then the all previous assignments are cleared. + + diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index dcdab6c83e..6010648ec8 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1040,7 +1040,7 @@ int link_request_set_routes(Link *link) { for (phase = 0; phase < _PHASE_MAX; phase++) LIST_FOREACH(routes, rt, link->network->static_routes) { - if (in_addr_is_null(rt->family, &rt->gw) != (phase == PHASE_NON_GATEWAY)) + if ((in_addr_is_null(rt->family, &rt->gw) && ordered_set_isempty(rt->multipath_routes)) != (phase == PHASE_NON_GATEWAY)) continue; r = route_configure(rt, link, route_handler); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b06d32ec6d..04c618827f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -146,6 +146,7 @@ Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, Route.QuickAck, config_parse_quickack, 0, 0 Route.FastOpenNoCookie, config_parse_fast_open_no_cookie, 0, 0 Route.TTLPropagate, config_parse_route_ttl_propagate, 0, 0 +Route.MultiPathRoute, config_parse_multipath_route, 0, 0 NextHop.Id, config_parse_nexthop_id, 0, 0 NextHop.Gateway, config_parse_nexthop_gateway, 0, 0 DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 2a42628e5b..ecc8d219b7 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -144,6 +144,8 @@ void route_free(Route *route) { set_remove(route->link->routes_foreign, route); } + ordered_set_free_free(route->multipath_routes); + sd_event_source_unref(route->expire); free(route); @@ -516,6 +518,88 @@ int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { return 1; } +static int append_nexthop_one(Route *route, MultipathRoute *m, struct rtattr **rta, size_t offset) { + struct rtnexthop *rtnh; + struct rtattr *new_rta; + int r; + + assert(route); + assert(m); + assert(rta); + assert(*rta); + + new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + *rtnh = (struct rtnexthop) { + .rtnh_len = sizeof(*rtnh), + .rtnh_ifindex = m->ifindex, + .rtnh_hops = m->weight > 0 ? m->weight - 1 : 0, + }; + + (*rta)->rta_len += sizeof(struct rtnexthop); + + if (route->family == m->gateway.family) { + r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family)); + if (r < 0) + goto clear; + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family)); + } else { + r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); + if (r < 0) + goto clear; + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); + } + + return 0; + +clear: + (*rta)->rta_len -= sizeof(struct rtnexthop); + return r; +} + +static int append_nexthops(Route *route, sd_netlink_message *req) { + _cleanup_free_ struct rtattr *rta = NULL; + struct rtnexthop *rtnh; + MultipathRoute *m; + size_t offset; + Iterator i; + int r; + + if (ordered_set_isempty(route->multipath_routes)) + return 0; + + rta = new(struct rtattr, 1); + if (!rta) + return -ENOMEM; + + *rta = (struct rtattr) { + .rta_type = RTA_MULTIPATH, + .rta_len = RTA_LENGTH(0), + }; + offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; + + ORDERED_SET_FOREACH(m, route->multipath_routes, i) { + r = append_nexthop_one(route, m, &rta, offset); + if (r < 0) + return r; + + rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); + offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } + + r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); + if (r < 0) + return r; + + return 0; +} + int route_configure( Route *route, Link *link, @@ -699,6 +783,10 @@ int route_configure( if (r < 0) return log_link_error_errno(link, r, "Could not append RTA_METRICS attribute: %m"); + r = append_nexthops(route, req); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_MULTIPATH attribute: %m"); + r = netlink_call_async(link->manager->rtnl, NULL, req, callback, link_netlink_destroy_callback, link); if (r < 0) @@ -1480,6 +1568,113 @@ int config_parse_route_ttl_propagate( return 0; } +int config_parse_multipath_route( + 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_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_free_ char *word = NULL, *buf = NULL; + _cleanup_free_ MultipathRoute *m = NULL; + Network *network = userdata; + const char *p, *ip, *dev; + union in_addr_union a; + int family, r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + if (isempty(rvalue)) { + n->multipath_routes = ordered_set_free_free(n->multipath_routes); + return 0; + } + + m = new0(MultipathRoute, 1); + if (!m) + return log_oom(); + + p = rvalue; + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid multipath route option, ignoring assignment: %s", rvalue); + return 0; + } + + dev = strchr(word, '@'); + if (dev) { + buf = strndup(word, dev - word); + if (!buf) + return log_oom(); + ip = buf; + dev++; + } else + ip = word; + + r = in_addr_from_string_auto(ip, &family, &a); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + return 0; + } + m->gateway.address = a; + m->gateway.family = family; + + if (dev) { + r = parse_ifindex_or_ifname(dev, &m->ifindex); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid interface name or index, ignoring assignment: %s", dev); + return 0; + } + } + + if (!isempty(p)) { + r = safe_atou32(p, &m->weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + if (m->weight == 0 || m->weight > 256) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + } + + r = ordered_set_ensure_allocated(&n->multipath_routes, NULL); + if (r < 0) + return log_oom(); + + r = ordered_set_put(n->multipath_routes, m); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to store multipath route, ignoring assignment: %m"); + return 0; + } + + TAKE_PTR(m); + TAKE_PTR(n); + return 0; +} + int route_section_verify(Route *route, Network *network) { if (section_is_invalid(route->section)) return -EINVAL; diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 89d54020db..91bba368ee 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -10,6 +10,17 @@ typedef struct NetworkConfigSection NetworkConfigSection; #include "networkd-network.h" #include "networkd-util.h" +typedef struct MultipathRouteVia { + uint16_t family; + union in_addr_union address; +} _packed_ MultipathRouteVia; + +typedef struct MultipathRoute { + MultipathRouteVia gateway; + int ifindex; + uint32_t weight; +} MultipathRoute; + struct Route { Network *network; NetworkConfigSection *section; @@ -42,6 +53,7 @@ struct Route { union in_addr_union dst; union in_addr_union src; union in_addr_union prefsrc; + OrderedSet *multipath_routes; usec_t lifetime; sd_event_source *expire; @@ -96,3 +108,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_quickack); CONFIG_PARSER_PROTOTYPE(config_parse_fast_open_no_cookie); CONFIG_PARSER_PROTOTYPE(config_parse_route_ttl_propagate); CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu); +CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 4513bbb529..92d1e2b6e1 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -123,6 +123,7 @@ FastOpenNoCookie= Source= Metric= TTLPropagate= +MultiPathRoute= [Network] IPv6DuplicateAddressDetection= IPMasquerade=