summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2020-07-21 16:06:51 +0200
committerYu Watanabe <watanabe.yu+github@gmail.com>2020-07-28 19:05:05 +0200
commit6e537f62d71edea5bfcd2e2675c35ee840ffdc1f (patch)
tree3d7691b0942f551e4526ad0405e78ee5a31e2f8e /src/network
parentnetwork: make address/route_configure optionally return created Address/Route... (diff)
downloadsystemd-6e537f62d71edea5bfcd2e2675c35ee840ffdc1f.tar.xz
systemd-6e537f62d71edea5bfcd2e2675c35ee840ffdc1f.zip
network: dhcp4: release old lease after the new address become ready
Previously, on DHCPv4 address renewal, the old address may be removed while the new address is not ready yet. This also simplifies the logic of removing address and routes.
Diffstat (limited to 'src/network')
-rw-r--r--src/network/networkd-address.c4
-rw-r--r--src/network/networkd-dhcp4.c466
-rw-r--r--src/network/networkd-link.c3
-rw-r--r--src/network/networkd-link.h5
-rw-r--r--src/network/networkd-route.c2
5 files changed, 118 insertions, 362 deletions
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 19a007906b..6fc3074eac 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -125,6 +125,10 @@ void address_free(Address *address) {
if (address->link && !address->acd) {
set_remove(address->link->addresses, address);
set_remove(address->link->addresses_foreign, address);
+ if (address->link->dhcp_address == address)
+ address->link->dhcp_address = NULL;
+ if (address->link->dhcp_address_old == address)
+ address->link->dhcp_address_old = NULL;
if (in_addr_equal(AF_INET6, &address->in_addr, (const union in_addr_union *) &address->link->ipv6ll_address))
memzero(&address->link->ipv6ll_address, sizeof(struct in6_addr));
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index e076965ee6..7576aaf62c 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -20,38 +20,58 @@
#include "sysctl-util.h"
#include "web-util.h"
-static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all, link_netlink_message_handler_t callback);
-static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all, link_netlink_message_handler_t callback);
-static int dhcp_remove_dns_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all, link_netlink_message_handler_t callback);
-static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, link_netlink_message_handler_t callback);
static int dhcp4_update_address(Link *link, bool announce);
static int dhcp4_remove_all(Link *link);
+static int dhcp4_release_old_lease(Link *link, bool force);
-static void dhcp4_release_old_lease(Link *link) {
- struct in_addr address = {}, address_old = {};
+static int dhcp4_address_callback(Address *address) {
+ assert(address);
+ assert(address->link);
- assert(link);
+ /* Do not call this callback again. */
+ address->callback = NULL;
- if (!link->dhcp_lease_old)
- return;
+ return dhcp4_release_old_lease(address->link, true);
+}
- assert(link->dhcp_lease);
+static int dhcp4_release_old_lease(Link *link, bool force) {
+ Route *route;
+ Iterator i;
+ int k, r = 0;
+
+ assert(link);
- (void) sd_dhcp_lease_get_address(link->dhcp_lease_old, &address_old);
- (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address);
+ if (!link->dhcp_address_old && set_isempty(link->dhcp_routes_old))
+ return 0;
- (void) dhcp_remove_routes(link, link->dhcp_lease_old, &address_old, false, NULL);
- (void) dhcp_remove_router(link, link->dhcp_lease_old, &address_old, false, NULL);
- (void) dhcp_remove_dns_routes(link, link->dhcp_lease_old, &address_old, false, NULL);
+ if (!force && (link->dhcp_address && !address_is_ready(link->dhcp_address))) {
+ log_link_debug(link, "New DHCPv4 address is not ready. The old lease will be removed later.");
+ link->dhcp_address->callback = dhcp4_address_callback;
+ return 0;
+ }
- if (!in4_addr_equal(&address_old, &address))
- (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old, NULL);
+ log_link_debug(link, "Removing old DHCPv4 address and routes.");
- link->dhcp_lease_old = sd_dhcp_lease_unref(link->dhcp_lease_old);
link_dirty(link);
+
+ SET_FOREACH(route, link->dhcp_routes_old, i) {
+ k = route_remove(route, link, NULL);
+ if (k < 0)
+ r = k;
+ }
+
+ if (link->dhcp_address_old) {
+ k = address_remove(link->dhcp_address_old, link, NULL);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
}
static void dhcp4_check_ready(Link *link) {
+ int r;
+
if (link->network->dhcp_send_decline && !link->dhcp4_address_bind)
return;
@@ -59,8 +79,14 @@ static void dhcp4_check_ready(Link *link) {
return;
link->dhcp4_configured = true;
+
/* New address and routes are configured now. Let's release old lease. */
- dhcp4_release_old_lease(link);
+ r = dhcp4_release_old_lease(link, false);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
link_check_ready(link);
}
@@ -124,27 +150,25 @@ static bool link_prefixroute(Link *link) {
link->manager->dhcp4_prefix_root_cannot_set_table;
}
-static int dhcp_route_configure(Route **route, Link *link) {
+static int dhcp_route_configure(Route *route, Link *link) {
+ Route *ret;
int r;
assert(route);
- assert(*route);
assert(link);
- if (set_contains(link->dhcp_routes, *route))
- return 0;
-
- r = route_configure(*route, link, dhcp4_route_handler);
- if (r <= 0)
- return r;
+ r = route_configure(route, link, dhcp4_route_handler, &ret);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set DHCPv4 route: %m");
link->dhcp4_messages++;
- r = set_put(link->dhcp_routes, *route);
+ r = set_ensure_put(&link->dhcp_routes, &route_hash_ops, ret);
if (r < 0)
- return r;
+ return log_link_error_errno(link, r, "Failed to store DHCPv4 route: %m");
+
+ (void) set_remove(link->dhcp_routes_old, ret);
- TAKE_PTR(*route);
return 0;
}
@@ -187,7 +211,7 @@ static int link_set_dns_routes(Link *link, const struct in_addr *address) {
route->priority = link->network->dhcp_route_metric;
route->table = table;
- r = dhcp_route_configure(&route, link);
+ r = dhcp_route_configure(route, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set route to DNS server: %m");
}
@@ -231,6 +255,7 @@ static int link_set_dhcp_routes(Link *link) {
struct in_addr address;
int r, n, i;
uint32_t table;
+ Route *rt;
assert(link);
@@ -245,12 +270,11 @@ static int link_set_dhcp_routes(Link *link) {
* the addresses now, let's not configure the routes either. */
return 0;
- r = set_ensure_allocated(&link->dhcp_routes, &route_hash_ops);
- if (r < 0)
- return log_oom();
-
- /* Clear old entries in case the set was already allocated */
- set_clear(link->dhcp_routes);
+ while ((rt = set_steal_first(link->dhcp_routes))) {
+ r = set_ensure_put(&link->dhcp_routes_old, &route_hash_ops, rt);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to store old DHCPv4 route: %m");
+ }
table = link_get_dhcp_route_table(link);
@@ -265,7 +289,7 @@ static int link_set_dhcp_routes(Link *link) {
if (r < 0)
return log_link_error_errno(link, r, "Could not create prefix route: %m");
- r = dhcp_route_configure(&prefix_route, link);
+ r = dhcp_route_configure(prefix_route, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set prefix route: %m");
}
@@ -316,7 +340,7 @@ static int link_set_dhcp_routes(Link *link) {
if (set_contains(link->dhcp_routes, route))
continue;
- r = dhcp_route_configure(&route, link);
+ r = dhcp_route_configure(route, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set route: %m");
}
@@ -356,7 +380,7 @@ static int link_set_dhcp_routes(Link *link) {
route_gw->table = table;
route_gw->mtu = link->network->dhcp_route_mtu;
- r = dhcp_route_configure(&route_gw, link);
+ r = dhcp_route_configure(route_gw, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set host route: %m");
@@ -372,12 +396,11 @@ static int link_set_dhcp_routes(Link *link) {
route->table = table;
route->mtu = link->network->dhcp_route_mtu;
- r = dhcp_route_configure(&route, link);
+ r = dhcp_route_configure(route, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set router: %m");
}
- Route *rt;
LIST_FOREACH(routes, rt, link->network->static_routes) {
if (!rt->gateway_from_dhcp)
continue;
@@ -387,272 +410,15 @@ static int link_set_dhcp_routes(Link *link) {
rt->gw.in = router[0];
- r = route_configure(rt, link, dhcp4_route_handler);
+ r = dhcp_route_configure(rt, link);
if (r < 0)
return log_link_error_errno(link, r, "Could not set gateway: %m");
- if (r > 0)
- link->dhcp4_messages++;
}
}
return link_set_dns_routes(link, &address);
}
-static int dhcp_route_remove(Route *route, Link *link, link_netlink_message_handler_t callback) {
- int r;
-
- r = route_remove(route, link, callback);
- if (r < 0)
- return r;
-
- if (callback)
- link->dhcp4_remove_messages++;
-
- return 0;
-}
-
-static int dhcp_remove_routes(
- Link *link,
- sd_dhcp_lease *lease,
- const struct in_addr *address,
- bool remove_all,
- link_netlink_message_handler_t callback) {
-
- _cleanup_free_ sd_dhcp_route **routes = NULL;
- uint32_t table;
- int n, i, r;
-
- assert(link);
- assert(address);
-
- if (!link->network->dhcp_use_routes)
- return 0;
-
- n = sd_dhcp_lease_get_routes(lease, &routes);
- if (IN_SET(n, 0, -ENODATA))
- return 0;
- else if (n < 0)
- return log_link_error_errno(link, n, "DHCP error: Failed to get routes: %m");
-
- table = link_get_dhcp_route_table(link);
-
- for (i = 0; i < n; i++) {
- _cleanup_(route_freep) Route *route = NULL;
-
- r = route_new(&route);
- if (r < 0)
- return log_oom();
-
- route->family = AF_INET;
- assert_se(sd_dhcp_route_get_gateway(routes[i], &route->gw.in) >= 0);
- assert_se(sd_dhcp_route_get_destination(routes[i], &route->dst.in) >= 0);
- assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &route->dst_prefixlen) >= 0);
- route->priority = link->network->dhcp_route_metric;
- route->table = table;
- route->scope = route_scope_from_address(route, address);
- if (IN_SET(route->scope, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE))
- route->prefsrc.in = *address;
-
- if (!remove_all && set_contains(link->dhcp_routes, route))
- continue;
-
- r = dhcp_route_remove(route, link, callback);
- if (r < 0)
- return r;
- }
-
- return n;
-}
-
-static int dhcp_remove_router(
- Link *link,
- sd_dhcp_lease *lease,
- const struct in_addr *address,
- bool remove_all,
- link_netlink_message_handler_t callback) {
-
- _cleanup_(route_freep) Route *route_gw = NULL, *route = NULL;
- const struct in_addr *router;
- uint32_t table;
- int r;
-
- assert(link);
- assert(address);
-
- if (!link->network->dhcp_use_gateway)
- return 0;
-
- r = sd_dhcp_lease_get_router(lease, &router);
- if (IN_SET(r, 0, -ENODATA)) {
- log_link_debug(link, "DHCP: No gateway received from DHCP server.");
- return 0;
- } else if (r < 0)
- return log_link_error_errno(link, r, "DHCP error: could not get gateway: %m");
- else if (in4_addr_is_null(&router[0])) {
- log_link_info(link, "DHCP: Received gateway is null, ignoring.");
- return 0;
- }
-
- table = link_get_dhcp_route_table(link);
-
- r = route_new(&route_gw);
- if (r < 0)
- return log_oom();
-
- route_gw->family = AF_INET;
- route_gw->dst.in = router[0];
- route_gw->dst_prefixlen = 32;
- route_gw->prefsrc.in = *address;
- route_gw->scope = RT_SCOPE_LINK;
- route_gw->protocol = RTPROT_DHCP;
- route_gw->priority = link->network->dhcp_route_metric;
- route_gw->table = table;
-
- if (remove_all || !set_contains(link->dhcp_routes, route_gw)) {
- r = dhcp_route_remove(route_gw, link, callback);
- if (r < 0)
- return r;
- }
-
- r = route_new(&route);
- if (r < 0)
- return log_oom();
-
- route->family = AF_INET;
- route->gw.in = router[0];
- route->prefsrc.in = *address;
- route->protocol = RTPROT_DHCP;
- route->priority = link->network->dhcp_route_metric;
- route->table = table;
-
- if (remove_all || !set_contains(link->dhcp_routes, route)) {
- r = dhcp_route_remove(route, link, callback);
- if (r < 0)
- return r;
- }
-
- Route *rt;
- LIST_FOREACH(routes, rt, link->network->static_routes) {
- if (!rt->gateway_from_dhcp)
- continue;
-
- if (rt->family != AF_INET)
- continue;
-
- if (!remove_all && in4_addr_equal(router, &rt->gw.in))
- continue;
-
- r = dhcp_route_remove(rt, link, callback);
- if (r < 0)
- return r;
- }
-
- return 0;
-}
-
-static int dhcp_remove_dns_routes(
- Link *link,
- sd_dhcp_lease *lease,
- const struct in_addr *address,
- bool remove_all,
- link_netlink_message_handler_t callback) {
-
- const struct in_addr *dns;
- uint32_t table;
- int i, n, r;
-
- assert(link);
- assert(lease);
- assert(link->network);
-
- if (!link->network->dhcp_use_dns ||
- !link->network->dhcp_routes_to_dns)
- return 0;
-
- n = sd_dhcp_lease_get_dns(lease, &dns);
- if (IN_SET(n, 0, -ENODATA))
- return 0;
- if (n < 0)
- return log_link_warning_errno(link, n, "DHCP error: could not get DNS servers: %m");
-
- table = link_get_dhcp_route_table(link);
-
- for (i = 0; i < n; i ++) {
- _cleanup_(route_freep) Route *route = NULL;
-
- r = route_new(&route);
- if (r < 0)
- return log_link_error_errno(link, r, "Could not allocate route: %m");
-
- route->family = AF_INET;
- route->dst.in = dns[i];
- route->dst_prefixlen = 32;
- route->prefsrc.in = *address;
- route->scope = RT_SCOPE_LINK;
- route->protocol = RTPROT_DHCP;
- route->priority = link->network->dhcp_route_metric;
- route->table = table;
-
- if (!remove_all && set_contains(link->dhcp_routes, route))
- continue;
-
- r = dhcp_route_remove(route, link, callback);
- if (r < 0)
- return r;
- }
-
- if (!link_prefixroute(link)) {
- _cleanup_(route_freep) Route *prefix_route = NULL;
-
- r = dhcp_prefix_route_from_lease(lease, table, address, &prefix_route);
- if (r < 0)
- return log_link_warning_errno(link, r, "Could not create prefix route: %m");
-
- if (remove_all || !set_contains(link->dhcp_routes, prefix_route)) {
- r = dhcp_route_remove(prefix_route, link, callback);
- if (r < 0)
- return r;
- }
- }
-
- return 0;
-}
-
-static int dhcp_remove_address(
- Link *link, sd_dhcp_lease *lease,
- const struct in_addr *address,
- link_netlink_message_handler_t callback) {
-
- _cleanup_(address_freep) Address *a = NULL;
- struct in_addr netmask;
- int r;
-
- assert(link);
- assert(address);
-
- if (in4_addr_is_null(address))
- return 0;
-
- r = address_new(&a);
- if (r < 0)
- return log_oom();
-
- a->family = AF_INET;
- a->in_addr.in = *address;
-
- if (sd_dhcp_lease_get_netmask(lease, &netmask) >= 0)
- a->prefixlen = in4_addr_netmask_to_prefixlen(&netmask);
-
- r = address_remove(a, link, callback);
- if (r < 0)
- return r;
-
- if (callback)
- link->dhcp4_remove_messages++;
-
- return 0;
-}
-
static int dhcp_reset_mtu(Link *link) {
uint16_t mtu;
int r;
@@ -756,37 +522,33 @@ static int dhcp4_remove_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
}
static int dhcp4_remove_all(Link *link) {
- struct in_addr address;
- int r;
+ Route *route;
+ Iterator i;
+ int k, r = 0;
assert(link);
- assert(link->dhcp_lease);
- r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
- if (r < 0)
- return log_link_error_errno(link, r, "Failed to get DHCPv4 address: %m");
-
- r = dhcp_remove_routes(link, link->dhcp_lease, &address, true, dhcp4_remove_route_handler);
- if (r < 0)
- return r;
-
- r = dhcp_remove_router(link, link->dhcp_lease, &address, true, dhcp4_remove_route_handler);
- if (r < 0)
- return r;
-
- r = dhcp_remove_dns_routes(link, link->dhcp_lease, &address, true, dhcp4_remove_route_handler);
- if (r < 0)
- return r;
+ SET_FOREACH(route, link->dhcp_routes, i) {
+ k = route_remove(route, link, dhcp4_remove_route_handler);
+ if (k < 0)
+ r = k;
+ else
+ link->dhcp4_remove_messages++;
+ }
- r = dhcp_remove_address(link, link->dhcp_lease, &address, dhcp4_remove_address_handler);
- if (r < 0)
- return r;
+ if (link->dhcp_address) {
+ k = address_remove(link->dhcp_address, link, dhcp4_remove_address_handler);
+ if (k < 0)
+ r = k;
+ else
+ link->dhcp4_remove_messages++;
+ }
- return 0;
+ return r;
}
static int dhcp_lease_lost(Link *link) {
- int r;
+ int k, r;
assert(link);
assert(link->dhcp_lease);
@@ -796,24 +558,26 @@ static int dhcp_lease_lost(Link *link) {
link->dhcp4_configured = false;
/* dhcp_lease_lost() may be called during renewing IP address. */
- dhcp4_release_old_lease(link);
+ k = dhcp4_release_old_lease(link, true);
+ if (k < 0)
+ r = k;
- r = dhcp4_remove_all(link);
- if (r < 0)
- return r;
+ k = dhcp4_remove_all(link);
+ if (k < 0)
+ r = k;
- r = dhcp_reset_mtu(link);
- if (r < 0)
- return r;
+ k = dhcp_reset_mtu(link);
+ if (k < 0)
+ r = k;
- r = dhcp_reset_hostname(link);
- if (r < 0)
- return r;
+ k = dhcp_reset_hostname(link);
+ if (k < 0)
+ r = k;
link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease);
link_dirty(link);
- return 0;
+ return r;
}
static void dhcp_address_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
@@ -975,6 +739,7 @@ static int dhcp4_update_address(Link *link, bool announce) {
uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
struct in_addr address, netmask;
unsigned prefixlen;
+ Address *ret;
int r;
assert(link);
@@ -1049,9 +814,13 @@ static int dhcp4_update_address(Link *link, bool announce) {
/* allow reusing an existing address and simply update its lifetime
* in case it already exists */
- r = address_configure(addr, link, dhcp4_address_handler, true);
+ r = address_configure(addr, link, dhcp4_address_handler, true, &ret);
if (r < 0)
- return r;
+ return log_link_error_errno(link, r, "Failed to set DHCPv4 address: %m");
+
+ if (!address_equal(link->dhcp_address, ret))
+ link->dhcp_address_old = link->dhcp_address;
+ link->dhcp_address = ret;
return 0;
}
@@ -1151,32 +920,11 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
static int dhcp_lease_ip_change(sd_dhcp_client *client, Link *link) {
int r;
- link->dhcp_lease_old = TAKE_PTR(link->dhcp_lease);
-
- /* On IP address change, to keep the connectability, we would like to assign new address and
- * routes, and then release old lease. There are two possible success paths:
- *
- * 1. new address and routes are configured.
- * -> handled by dhcp_release_old_lease() in dhcp4_route_handler().
- * 2. new address is configured and no route is requested.
- * -> handled by dhcp_release_old_lease() in dhcp4_address_handler().
- *
- * On error in assigning new address and routes, then the link always enters to the failed
- * state. And link_enter_failed() leads to the DHCP client to be stopped. So,
- * dhcp_release_old_lease() will be also called by link_stop_clients().
- */
-
r = dhcp_lease_acquired(client, link);
- if (r < 0) {
- /* If it fails, then the new address is not configured yet.
- * So, let's simply drop the old lease. */
- sd_dhcp_lease_unref(link->dhcp_lease);
- link->dhcp_lease = TAKE_PTR(link->dhcp_lease_old);
+ if (r < 0)
(void) dhcp_lease_lost(link);
- return r;
- }
- return 0;
+ return r;
}
static int dhcp_server_is_deny_listed(Link *link, sd_dhcp_client *client) {
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 23870eab3f..6db57b9f3d 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -691,7 +691,6 @@ static void link_free_engines(Link *link) {
link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server);
link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client);
link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease);
- link->dhcp_routes = set_free(link->dhcp_routes);
link->lldp = sd_lldp_unref(link->lldp);
@@ -713,6 +712,8 @@ static Link *link_free(Link *link) {
link->routes = set_free(link->routes);
link->routes_foreign = set_free(link->routes_foreign);
+ link->dhcp_routes = set_free(link->dhcp_routes);
+ link->dhcp_routes_old = set_free(link->dhcp_routes_old);
link->nexthops = set_free(link->nexthops);
link->nexthops_foreign = set_free(link->nexthops_foreign);
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 4534180128..f837ec2a24 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -95,8 +95,9 @@ typedef struct Link {
Set *nexthops_foreign;
sd_dhcp_client *dhcp_client;
- sd_dhcp_lease *dhcp_lease, *dhcp_lease_old;
- Set *dhcp_routes;
+ sd_dhcp_lease *dhcp_lease;
+ Address *dhcp_address, *dhcp_address_old;
+ Set *dhcp_routes, *dhcp_routes_old;
char *lease_file;
uint32_t original_mtu;
unsigned dhcp4_messages;
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index d8739b7679..322e819bc1 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -144,6 +144,8 @@ void route_free(Route *route) {
if (route->link) {
set_remove(route->link->routes, route);
set_remove(route->link->routes_foreign, route);
+ set_remove(route->link->dhcp_routes, route);
+ set_remove(route->link->dhcp_routes_old, route);
}
ordered_set_free_free(route->multipath_routes);