diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2021-09-06 16:09:38 +0900 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2021-09-24 21:46:32 +0900 |
commit | 3b6a3bdebfb555754fdc6ee507e3f6964de7b61c (patch) | |
tree | b904859c12346774a65f0704d8f42f197045b23e /src/network/networkd-route.c | |
parent | c7e445abd59c808520cf5f09f059b993f0773aaa (diff) | |
download | systemd-3b6a3bdebfb555754fdc6ee507e3f6964de7b61c.tar.gz |
network: use NetworkConfigSource/State to manage addresses and routes
This also fixes #20146.
Diffstat (limited to 'src/network/networkd-route.c')
-rw-r--r-- | src/network/networkd-route.c | 1163 |
1 files changed, 543 insertions, 620 deletions
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index c94887ef8a..009afa1b29 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "netlink-util.h" +#include "networkd-address.h" #include "networkd-ipv4ll.h" #include "networkd-manager.h" #include "networkd-network.h" @@ -226,6 +227,7 @@ static int route_new_static(Network *network, const char *filename, unsigned sec route->protocol = RTPROT_STATIC; route->network = network; route->section = TAKE_PTR(n); + route->source = NETWORK_CONFIG_SOURCE_STATIC; r = hashmap_ensure_put(&network->routes_by_section, &network_config_hash_ops, route->section, route); if (r < 0) @@ -246,26 +248,11 @@ Route *route_free(Route *route) { network_config_section_free(route->section); - if (route->link) { - NDiscRoute *n; - + 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); - set_remove(route->link->dhcp6_routes, route); - set_remove(route->link->dhcp6_routes_old, route); - set_remove(route->link->dhcp6_pd_routes, route); - set_remove(route->link->dhcp6_pd_routes_old, route); - SET_FOREACH(n, route->link->ndisc_routes) - if (route_equal(n->route, route)) - free(set_remove(route->link->ndisc_routes, n)); - } - - if (route->manager) { + + if (route->manager) set_remove(route->manager->routes, route); - set_remove(route->manager->routes_foreign, route); - } ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free); @@ -414,87 +401,77 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( route_compare_func, route_free); -bool route_equal(const Route *r1, const Route *r2) { - if (r1 == r2) - return true; +static bool route_type_is_reject(const Route *route) { + assert(route); - if (!r1 || !r2) - return false; + return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); +} + +static bool route_needs_convert(const Route *route) { + assert(route); - return route_compare_func(r1, r2) == 0; + return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes); } -static int route_get(const Manager *manager, const Link *link, const Route *in, Route **ret) { - Route *existing; +static int route_add(Manager *manager, Link *link, Route *route) { + int r; - assert(manager || link); - assert(in); + assert(route); - existing = set_get(link ? link->routes : manager->routes, in); - if (existing) { - if (ret) - *ret = existing; - return 1; - } + if (route_type_is_reject(route)) { + assert(manager); - existing = set_get(link ? link->routes_foreign : manager->routes_foreign, in); - if (existing) { - if (ret) - *ret = existing; - return 0; + r = set_ensure_put(&manager->routes, &route_hash_ops, route); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + route->manager = manager; + } else { + assert(link); + + r = set_ensure_put(&link->routes, &route_hash_ops, route); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + route->link = link; } - return -ENOENT; + return 0; } -static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, const NextHop *nh, uint8_t nh_weight) { - assert(dest); - assert(src); +int route_get(Manager *manager, Link *link, const Route *in, Route **ret) { + Route *route; - /* This only copies entries used by the above hash and compare functions. */ - - dest->family = src->family; - dest->src = src->src; - dest->src_prefixlen = src->src_prefixlen; - dest->dst = src->dst; - dest->dst_prefixlen = src->dst_prefixlen; - dest->prefsrc = src->prefsrc; - dest->scope = src->scope; - dest->protocol = src->protocol; - if (nh && nh->blackhole) - dest->type = RTN_BLACKHOLE; - else - dest->type = src->type; - dest->tos = src->tos; - dest->priority = src->priority; - dest->table = src->table; - dest->initcwnd = src->initcwnd; - dest->initrwnd = src->initrwnd; - dest->lifetime = src->lifetime; - dest->advmss = src->advmss; - 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 = nh_weight != UINT8_MAX ? nh_weight : src->gw_weight; - } else if (m) { - dest->gw_family = m->gateway.family; - dest->gw = m->gateway.address; - dest->gw_weight = m->weight; + assert(in); + + if (route_type_is_reject(in)) { + if (!manager) + return -ENOENT; + + route = set_get(manager->routes, in); } else { - dest->gw_family = src->gw_family; - dest->gw = src->gw; - dest->gw_weight = src->gw_weight; + if (!link) + return -ENOENT; + + route = set_get(link->routes, in); } + if (!route) + return -ENOENT; + + if (ret) + *ret = route; + + return 0; } int route_dup(const Route *src, Route **ret) { _cleanup_(route_freep) Route *dest = NULL; - MultipathRoute *m; - int r; + + /* This does not copy mulipath routes. */ assert(src); assert(ret); @@ -511,278 +488,261 @@ int route_dup(const Route *src, Route **ret) { dest->multipath_routes = NULL; dest->expire = NULL; - ORDERED_SET_FOREACH(m, src->multipath_routes) { - _cleanup_(multipath_route_freep) MultipathRoute *n = NULL; + *ret = TAKE_PTR(dest); + return 0; +} - r = multipath_route_dup(m, &n); - if (r < 0) - return r; +static void route_apply_nexthop(Route *route, const NextHop *nh, uint8_t nh_weight) { + assert(route); + assert(nh); + assert(hashmap_isempty(nh->group)); - r = ordered_set_ensure_put(&dest->multipath_routes, NULL, n); - if (r < 0) - return r; + route->gw_family = nh->family; + route->gw = nh->gw; - TAKE_PTR(n); - } + if (nh_weight != UINT8_MAX) + route->gw_weight = nh_weight; - *ret = TAKE_PTR(dest); - return 0; + if (nh->blackhole) + route->type = RTN_BLACKHOLE; } -static int route_add_internal(Manager *manager, Link *link, Set **routes, const Route *in, Route **ret) { - _cleanup_(route_freep) Route *route = NULL; - int r; +static void route_apply_multipath_route(Route *route, const MultipathRoute *m) { + assert(route); + assert(m); - assert(manager || link); - assert(routes); - assert(in); + route->gw_family = m->gateway.family; + route->gw = m->gateway.address; + route->gw_weight = m->weight; +} - r = route_new(&route); - if (r < 0) - return r; +static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) { + int r; - route_copy(route, in, NULL, NULL, UINT8_MAX); + assert(manager); + assert(m); - r = set_ensure_put(routes, &route_hash_ops, route); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + if (m->ifname) { + r = link_get_by_name(manager, m->ifname, ret); + return r < 0 ? r : 1; - route->link = link; - route->manager = manager; + } else if (m->ifindex > 0) { /* Always ignore ifindex if ifname is set. */ + r = link_get_by_index(manager, m->ifindex, ret); + return r < 0 ? r : 1; + } if (ret) - *ret = route; - - route = NULL; - + *ret = NULL; return 0; } -static int route_add_foreign(Manager *manager, Link *link, const Route *in, Route **ret) { - assert(manager || link); - return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret); -} +typedef struct ConvertedRoutes { + size_t n; + Route **routes; + Link **links; +} ConvertedRoutes; -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; +static ConvertedRoutes *converted_routes_free(ConvertedRoutes *c) { + if (!c) + return NULL; - assert(manager || link); - assert(in); + for (size_t i = 0; i < c->n; i++) + route_free(c->routes[i]); - if (nh) { - assert(hashmap_isempty(nh->group)); + free(c->routes); + free(c->links); - r = route_new(&tmp); - if (r < 0) - return r; + return mfree(c); +} - route_copy(tmp, in, NULL, nh, nh_weight); - in = tmp; - } else if (m) { - assert(link && (m->ifindex == 0 || m->ifindex == link->ifindex)); +DEFINE_TRIVIAL_CLEANUP_FUNC(ConvertedRoutes*, converted_routes_free); - r = route_new(&tmp); - if (r < 0) - return r; +static int converted_routes_new(size_t n, ConvertedRoutes **ret) { + _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; + _cleanup_free_ Route **routes = NULL; + _cleanup_free_ Link **links = NULL; - route_copy(tmp, in, m, NULL, UINT8_MAX); - in = tmp; - } + assert(n > 0); + assert(ret); - r = route_get(manager, link, in, &route); - if (r == -ENOENT) { - /* Route does not exist, create a new one */ - r = route_add_internal(manager, link, link ? &link->routes : &manager->routes, in, &route); - if (r < 0) - return r; - } else if (r == 0) { - /* Take over a foreign route */ - r = set_ensure_put(link ? &link->routes : &manager->routes, &route_hash_ops, route); - if (r < 0) - return r; + routes = new0(Route*, n); + if (!routes) + return -ENOMEM; - set_remove(link ? link->routes_foreign : manager->routes_foreign, route); - } else if (r == 1) { - /* Route exists, do nothing */ - ; - } else - return r; + links = new0(Link*, n); + if (!links) + return -ENOMEM; - if (ret) - *ret = route; - return 0; -} + c = new(ConvertedRoutes, 1); + if (!c) + return -ENOMEM; -static bool route_type_is_reject(const Route *route) { - assert(route); + *c = (ConvertedRoutes) { + .n = n, + .routes = TAKE_PTR(routes), + .links = TAKE_PTR(links), + }; - return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); + *ret = TAKE_PTR(c); + return 0; } -static int link_has_route_one(Link *link, const Route *route, const NextHop *nh, uint8_t nh_weight) { - _cleanup_(route_freep) Route *tmp = NULL; +static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) { + _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; int r; - assert(link); + assert(manager); assert(route); - assert(nh); + assert(ret); - r = route_new(&tmp); - if (r < 0) - return r; + if (!route_needs_convert(route)) { + *ret = NULL; + return 0; + } - route_copy(tmp, route, NULL, nh, nh_weight); + if (route->nexthop_id > 0) { + struct nexthop_grp *nhg; + NextHop *nh; - 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; -} + r = manager_get_nexthop_by_id(manager, route->nexthop_id, &nh); + if (r < 0) + return r; -int link_has_route(Link *link, const Route *route) { - MultipathRoute *m; - int r; + if (hashmap_isempty(nh->group)) { + r = converted_routes_new(1, &c); + if (r < 0) + return r; - assert(link); - assert(route); + r = route_dup(route, &c->routes[0]); + if (r < 0) + return r; - if (route->nexthop_id > 0) { - struct nexthop_grp *nhg; - NextHop *nh; + route_apply_nexthop(c->routes[0], nh, UINT8_MAX); + c->links[0] = nh->link; - if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) - return false; + *ret = TAKE_PTR(c); + return 1; + } - if (hashmap_isempty(nh->group)) - return link_has_route_one(link, route, nh, UINT8_MAX); + r = converted_routes_new(hashmap_size(nh->group), &c); + if (r < 0) + return r; + size_t i = 0; HASHMAP_FOREACH(nhg, nh->group) { NextHop *h; - if (manager_get_nexthop_by_id(link->manager, nhg->id, &h) < 0) - return false; + r = manager_get_nexthop_by_id(manager, nhg->id, &h); + if (r < 0) + return r; - r = link_has_route_one(link, route, h, nhg->weight); - if (r <= 0) + r = route_dup(route, &c->routes[i]); + if (r < 0) return r; + + route_apply_nexthop(c->routes[i], h, nhg->weight); + c->links[i] = h->link; + + i++; } - return true; - } + *ret = TAKE_PTR(c); + return 1; - if (ordered_set_isempty(route->multipath_routes)) { - if (route_type_is_reject(route)) - return route_get(link->manager, NULL, route, NULL) >= 0; - else - return route_get(NULL, link, route, NULL) >= 0; } - ORDERED_SET_FOREACH(m, route->multipath_routes) { - _cleanup_(route_freep) Route *tmp = NULL; - Link *l; - - if (m->ifname) { - if (link_get_by_name(link->manager, m->ifname, &l) < 0) - return false; + assert(!ordered_set_isempty(route->multipath_routes)); - m->ifindex = l->ifindex; - } else - l = link; + r = converted_routes_new(ordered_set_size(route->multipath_routes), &c); + if (r < 0) + return r; - r = route_new(&tmp); + size_t i = 0; + MultipathRoute *m; + ORDERED_SET_FOREACH(m, route->multipath_routes) { + r = route_dup(route, &c->routes[i]); if (r < 0) return r; - route_copy(tmp, route, m, NULL, UINT8_MAX); + route_apply_multipath_route(c->routes[i], m); - if (route_get(NULL, l, tmp, NULL) < 0) - return false; + r = multipath_route_get_link(manager, m, &c->links[i]); + if (r < 0) + return r; + + i++; } - return true; + *ret = TAKE_PTR(c); + return 1; } -static bool route_address_is_reachable(const Route *route, int family, const union in_addr_union *address) { - assert(route); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (route->family != family) - return false; - - if (!in_addr_is_set(route->family, &route->dst)) - return false; +void link_mark_routes(Link *link, NetworkConfigSource source, const struct in6_addr *router) { + Route *route; - return in_addr_prefix_intersect( - route->family, - &route->dst, - route->dst_prefixlen, - address, - FAMILY_ADDRESS_SIZE(family) * 8) > 0; -} + assert(link); -static bool prefix_route_address_is_reachable(const Address *a, int family, const union in_addr_union *address) { - assert(a); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); + SET_FOREACH(route, link->routes) { + if (route->source != source) + continue; - if (a->family != family) - return false; - if (!address_is_ready(a)) - return false; - if (FLAGS_SET(a->flags, IFA_F_NOPREFIXROUTE)) - return false; - if (in_addr_is_set(a->family, &a->in_addr_peer)) - return false; + if (source == NETWORK_CONFIG_SOURCE_NDISC && + router && !in6_addr_equal(router, &route->provider.in6)) + continue; - return in_addr_prefix_intersect( - family, - &a->in_addr, - a->prefixlen, - address, - FAMILY_ADDRESS_SIZE(family) * 8) > 0; + route_mark(route); + } } static bool link_address_is_reachable(Link *link, int family, const union in_addr_union *address) { Route *route; + Address *a; assert(link); assert(link->manager); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); - - SET_FOREACH(route, link->routes) - if (route_address_is_reachable(route, family, address)) - return true; - SET_FOREACH(route, link->routes_foreign) - if (route_address_is_reachable(route, family, address)) + SET_FOREACH(route, link->routes) { + if (!route_exists(route)) + continue; + if (route->family != family) + continue; + if (!in_addr_is_set(route->family, &route->dst)) + continue; + if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, address) > 0) return true; + } + + if (link->manager->manage_foreign_routes) + return false; /* If we do not manage foreign routes, then there may exist a prefix route we do not know, * which was created on configuring an address. Hence, also check the addresses. */ - if (!link->manager->manage_foreign_routes) { - Address *a; - - SET_FOREACH(a, link->addresses) - if (prefix_route_address_is_reachable(a, family, address)) - return true; - SET_FOREACH(a, link->addresses_foreign) - if (prefix_route_address_is_reachable(a, family, address)) - return true; + SET_FOREACH(a, link->addresses) { + if (!address_is_ready(a)) + continue; + if (a->family != family) + continue; + if (FLAGS_SET(a->flags, IFA_F_NOPREFIXROUTE)) + continue; + if (in_addr_is_set(a->family, &a->in_addr_peer)) + continue; + if (in_addr_prefix_covers(family, &a->in_addr, a->prefixlen, address) > 0) + return true; } return false; } -static Route *routes_get_default_gateway(Set *routes, int family, Route *gw) { +static Route *link_find_default_gateway(Link *link, int family, Route *gw) { Route *route; - SET_FOREACH(route, routes) { + assert(link); + + SET_FOREACH(route, link->routes) { + if (!route_exists(route)) + continue; if (family != AF_UNSPEC && route->family != family) continue; if (route->dst_prefixlen != 0) @@ -826,20 +786,22 @@ int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { if (link->state != LINK_STATE_CONFIGURED) continue; - gw = routes_get_default_gateway(link->routes, family, gw); - gw = routes_get_default_gateway(link->routes_foreign, family, gw); + gw = link_find_default_gateway(link, family, gw); } if (!gw) return -ENOENT; - assert(gw->link); - *ret = gw->link; + if (ret) { + assert(gw->link); + *ret = gw->link; + } + return 0; } static void log_route_debug(const Route *route, const char *str, const Link *link, const Manager *manager) { - _cleanup_free_ char *dst = NULL, *src = NULL, *gw_alloc = NULL, *prefsrc = NULL, + _cleanup_free_ char *state = NULL, *dst = NULL, *src = NULL, *gw_alloc = NULL, *prefsrc = NULL, *table = NULL, *scope = NULL, *proto = NULL; const char *gw = NULL; @@ -852,6 +814,7 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin if (!DEBUG_LOGGING) return; + (void) network_config_state_to_string_alloc(route->state, &state); if (in_addr_is_set(route->family, &route->dst)) (void) in_addr_prefix_to_string(route->family, &route->dst, route->dst_prefixlen, &dst); if (in_addr_is_set(route->family, &route->src)) @@ -889,8 +852,10 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin (void) route_protocol_full_to_string_alloc(route->protocol, &proto); log_link_debug(link, - "%s route: dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32, - str, strna(dst), strna(src), strna(gw), strna(prefsrc), + "%s %s route (%s): dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, " + "proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32, + str, strna(network_config_source_to_string(route->source)), strna(state), + strna(dst), strna(src), strna(gw), strna(prefsrc), strna(scope), strna(table), strna(proto), strna(route_type_to_string(route->type)), route->nexthop_id, route->priority); @@ -1002,16 +967,14 @@ static int route_set_netlink_message(const Route *route, sd_netlink_message *req return 0; } -static int link_route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { int r; assert(m); - assert(link); - assert(link->route_remove_messages > 0); - link->route_remove_messages--; + /* link may be NULL. */ - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) return 0; r = sd_netlink_message_get_errno(m); @@ -1021,32 +984,19 @@ static int link_route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Li return 1; } -static int manager_route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Manager *manager) { - int r; - - assert(m); - assert(manager); - assert(manager->route_remove_messages > 0); - - manager->route_remove_messages--; - - r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ESRCH) - log_message_warning_errno(m, r, "Could not drop route, ignoring"); - - return 1; -} - -int route_remove(const Route *route, Manager *manager, Link *link) { +int route_remove(Route *route) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; unsigned char type; + Manager *manager; + Link *link; int r; - assert(link || manager); + assert(route); + assert(route->manager || (route->link && route->link->manager)); assert(IN_SET(route->family, AF_INET, AF_INET6)); - if (!manager) - manager = link->manager; + link = route->link; + manager = route->manager ?: link->manager; log_route_debug(route, "Removing", link, manager); @@ -1075,100 +1025,85 @@ int route_remove(const Route *route, Manager *manager, Link *link) { if (r < 0) return r; - if (link) { - r = netlink_call_async(manager->rtnl, NULL, req, - link_route_remove_handler, - link_netlink_destroy_callback, link); - if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); - link->route_remove_messages++; - } else { - r = netlink_call_async(manager->rtnl, NULL, req, - manager_route_remove_handler, - NULL, manager); - if (r < 0) - return log_error_errno(r, "Could not send rtnetlink message: %m"); + r = netlink_call_async(manager->rtnl, NULL, req, route_remove_handler, + link ? link_netlink_destroy_callback : NULL, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - manager->route_remove_messages++; - } + link_ref(link); + route_enter_removing(route); return 0; } -static bool link_has_static_route(const Link *link, const Route *route) { - Route *net_route; - - assert(link); - assert(route); +static int manager_drop_routes(Manager *manager, bool foreign, const Link *except) { + Route *route; + Link *link; + int k, r; - if (!link->network) - return false; + assert(manager); - HASHMAP_FOREACH(net_route, link->network->routes_by_section) - if (route_equal(net_route, route)) - return true; + /* First, mark all routes. */ + SET_FOREACH(route, manager->routes) { + /* Do not touch routes managed by the kernel. */ + if (route->protocol == RTPROT_KERNEL) + continue; - return false; -} + /* When 'foreign' is true, do not remove routes we configured. */ + if (foreign && route->source != NETWORK_CONFIG_SOURCE_FOREIGN) + continue; -static bool links_have_static_route(const Manager *manager, const Route *route, const Link *except) { - Link *link; + /* Ignore routes not assigned yet or already removed. */ + if (!route_exists(route)) + continue; - assert(manager); + route_mark(route); + } + /* Then, unmark all routes requested by active links. */ HASHMAP_FOREACH(link, manager->links_by_index) { if (link == except) continue; - if (link_has_static_route(link, route)) - return true; - } - - return false; -} + if (!link->network) + continue; -static int manager_drop_routes_internal(Manager *manager, bool foreign, const Link *except) { - Route *route; - int k, r = 0; - Set *routes; + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + continue; - assert(manager); + HASHMAP_FOREACH(route, link->network->routes_by_section) { + _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; + Route *existing; - routes = foreign ? manager->routes_foreign : manager->routes; - SET_FOREACH(route, routes) { - if (route->removing) - continue; + r = route_convert(manager, route, &converted); + if (r < 0) + continue; + if (r == 0) { + if (route_get(manager, NULL, route, &existing) >= 0) + route_unmark(existing); + continue; + } - /* Do not touch routes managed by the kernel. */ - if (route->protocol == RTPROT_KERNEL) - continue; + for (size_t i = 0; i < converted->n; i++) + if (route_get(manager, NULL, converted->routes[i], &existing) >= 0) + route_unmark(existing); + } + } - /* The route will be configured later, or already configured by a link. */ - if (links_have_static_route(manager, route, except)) + /* Finally, remove all marked routes. */ + r = 0; + SET_FOREACH(route, manager->routes) { + if (!route_is_marked(route)) continue; - /* The existing links do not have the route. Let's drop this now. It may be - * re-configured later. */ - k = route_remove(route, manager, NULL); + k = route_remove(route); if (k < 0 && r >= 0) r = k; - - route->removing = true; } return r; } -static int manager_drop_foreign_routes(Manager *manager) { - return manager_drop_routes_internal(manager, true, NULL); -} - -static int manager_drop_routes(Manager *manager, const Link *except) { - return manager_drop_routes_internal(manager, false, except); -} - static bool route_by_kernel(const Route *route) { assert(route); @@ -1189,16 +1124,24 @@ static bool route_by_kernel(const Route *route) { int link_drop_foreign_routes(Link *link) { Route *route; - int k, r = 0; + int k, r; assert(link); assert(link->manager); - SET_FOREACH(route, link->routes_foreign) { + SET_FOREACH(route, link->routes) { /* do not touch routes managed by the kernel */ if (route_by_kernel(route)) continue; + /* Do not remove routes we configured. */ + if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN) + continue; + + /* Ignore routes not assigned yet or already removed. */ + if (!route_exists(route)) + continue; + if (route->protocol == RTPROT_STATIC && link->network && FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) continue; @@ -1207,15 +1150,38 @@ int link_drop_foreign_routes(Link *link) { FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) continue; - if (link_has_static_route(link, route)) - k = route_add(NULL, link, route, NULL, NULL, UINT8_MAX, NULL); - else - k = route_remove(route, NULL, link); + route_mark(route); + } + + HASHMAP_FOREACH(route, link->network->routes_by_section) { + _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; + Route *existing; + + r = route_convert(link->manager, route, &converted); + if (r < 0) + continue; + if (r == 0) { + if (route_get(NULL, link, route, &existing) >= 0) + route_unmark(existing); + continue; + } + + for (size_t i = 0; i < converted->n; i++) + if (route_get(NULL, link, converted->routes[i], &existing) >= 0) + route_unmark(existing); + } + + r = 0; + SET_FOREACH(route, link->routes) { + if (!route_is_marked(route)) + continue; + + k = route_remove(route); if (k < 0 && r >= 0) r = k; } - k = manager_drop_foreign_routes(link->manager); + k = manager_drop_routes(link->manager, /* foreign = */ true, NULL); if (k < 0 && r >= 0) r = k; @@ -1230,15 +1196,18 @@ int link_drop_routes(Link *link) { SET_FOREACH(route, link->routes) { /* do not touch routes managed by the kernel */ - if (route->protocol == RTPROT_KERNEL) + if (route_by_kernel(route)) + continue; + + if (!route_exists(route)) continue; - k = route_remove(route, NULL, link); + k = route_remove(route); if (k < 0 && r >= 0) r = k; } - k = manager_drop_routes(link->manager, link); + k = manager_drop_routes(link->manager, /* foreign = */ false, link); if (k < 0 && r >= 0) r = k; @@ -1250,131 +1219,37 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat int r; assert(route); + assert(route->link); - r = route_remove(route, route->manager, route->link); + r = route_remove(route); if (r < 0) { log_link_warning_errno(route->link, r, "Could not remove route: %m"); - route_free(route); + link_enter_failed(route->link); } return 1; } -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_disable_unrefp) sd_event_source *expire = NULL; - Route *nr; - int r; - - assert(link); - assert(link->manager); - assert(route); - assert(!(m && nh)); - assert(ret); - - if (route_type_is_reject(route) || (nh && nh->blackhole)) - 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_by_index(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, 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"); +static int route_setup_timer(Route *route) { + Manager *manager; /* TODO: drop expiration handling once it can be pushed into the kernel */ - if (nr->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) { - r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), - nr->lifetime, 0, route_expire_handler, nr); - if (r < 0) - return log_link_error_errno(link, r, "Could not arm expiration timer: %m"); - } - - sd_event_source_disable_unref(nr->expire); - nr->expire = TAKE_PTR(expire); - - *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); + assert(route->manager || (route->link && route->link->manager)); - 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; + manager = route->manager ?: route->link->manager; - 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(); + if (route->lifetime == USEC_INFINITY) + return 0; - 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(); + if (kernel_route_expiration_supported()) + return 0; - r = route_add_and_setup_timer_one(link, route, NULL, nh, UINT8_MAX, routes); - if (r < 0) - return r; - } + sd_event_source_disable_unref(route->expire); - *ret_n_routes = n_routes; - *ret_routes = TAKE_PTR(routes); - return 0; + return sd_event_add_time(manager->event, &route->expire, clock_boottime_or_monotonic(), + route->lifetime, 0, route_expire_handler, route); } static int append_nexthop_one(const Link *link, const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { @@ -1485,27 +1360,18 @@ int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Li static int route_configure( const Route *route, Link *link, - link_netlink_message_handler_t callback, - unsigned *ret_n_routes, - Route ***ret_routes) { + link_netlink_message_handler_t callback) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - _cleanup_free_ Route **routes = NULL; - unsigned n_routes = 0; /* avoid false maybe-uninitialized warning */ int r; + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); assert(link); assert(link->manager); assert(link->manager->rtnl); assert(link->ifindex > 0); - assert(IN_SET(route->family, AF_INET, AF_INET6)); assert(callback); - assert(!!ret_n_routes == !!ret_routes); - - if (route_get(link->manager, link, route, NULL) <= 0 && - set_size(link->routes) >= routes_max()) - return log_link_error_errno(link, SYNTHETIC_ERRNO(E2BIG), - "Too many routes are configured, refusing: %m"); log_route_debug(route, "Configuring", link, link->manager); @@ -1589,23 +1455,34 @@ static int route_configure( return log_link_error_errno(link, r, "Could not append RTA_MULTIPATH attribute: %m"); } - 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) return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); link_ref(link); + return 0; +} - if (ret_routes) { - *ret_n_routes = n_routes; - *ret_routes = TAKE_PTR(routes); - } +void route_cancel_request(Route *route) { + Request req; - return r; + assert(route); + + if (!route_is_requesting(route)) + return; + + if (!route->link) + return; + + req = (Request) { + .link = route->link, + .type = REQUEST_TYPE_ROUTE, + .route = route, + }; + + request_drop(ordered_set_get(route->link->manager->request_queue, &req)); + route_cancel_requesting(route); } static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { @@ -1637,13 +1514,60 @@ int link_request_route( link_netlink_message_handler_t netlink_handler, Request **ret) { + Route *existing; + int r; + assert(link); assert(link->manager); assert(route); + assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); + assert(!route_needs_convert(route)); + + if (route_get(link->manager, link, route, &existing) < 0) { + _cleanup_(route_freep) Route *tmp = NULL; + + if (consume_object) + tmp = route; + else { + r = route_dup(route, &tmp); + if (r < 0) + return r; + } + + r = route_add(link->manager, link, tmp); + if (r < 0) + return r; + + existing = TAKE_PTR(tmp); + } else { + existing->source = route->source; + existing->provider = route->provider; + if (consume_object) + route_free(route); + } + + log_route_debug(existing, "Requesting", link, link->manager); + r = link_queue_request(link, REQUEST_TYPE_ROUTE, existing, false, + message_counter, netlink_handler, ret); + if (r <= 0) + return r; + + route_enter_requesting(existing); + return 1; +} + +static int link_request_static_route(Link *link, Route *route) { + assert(link); + assert(link->manager); + assert(route); + + if (!route_needs_convert(route)) + return link_request_route(link, route, false, &link->static_route_messages, + static_route_handler, NULL); log_route_debug(route, "Requesting", link, link->manager); - return link_queue_request(link, REQUEST_TYPE_ROUTE, route, consume_object, - message_counter, netlink_handler, ret); + return link_queue_request(link, REQUEST_TYPE_ROUTE, route, false, + &link->static_route_messages, static_route_handler, NULL); } int link_request_static_routes(Link *link, bool only_ipv4) { @@ -1662,8 +1586,7 @@ int link_request_static_routes(Link *link, bool only_ipv4) { if (only_ipv4 && route->family != AF_INET) continue; - r = link_request_route(link, route, false, &link->static_route_messages, - static_route_handler, NULL); + r = link_request_static_route(link, route); if (r < 0) return r; } @@ -1696,15 +1619,20 @@ bool gateway_is_ready(Link *link, int onlink, int family, const union in_addr_un } static int route_is_ready_to_configure(const Route *route, Link *link) { - MultipathRoute *m; - NextHop *nh = NULL; int r; assert(route); assert(link); + if (!link_is_ready_to_configure(link, false)) + return false; + + if (set_size(link->routes) >= routes_max()) + return false; + if (route->nexthop_id > 0) { struct nexthop_grp *nhg; + NextHop *nh; if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) return false; @@ -1723,20 +1651,6 @@ static int route_is_ready_to_configure(const Route *route, Link *link) { } } - if (route_type_is_reject(route)) { - if (link->manager->route_remove_messages > 0) - return false; - } else { - Link *l; - - HASHMAP_FOREACH(l, link->manager->links_by_index) { - if (l->address_remove_messages > 0) - return false; - if (l->route_remove_messages > 0) - return false; - } - } - if (in_addr_is_set(route->family, &route->prefsrc) > 0) { r = manager_has_address(link->manager, route->family, &route->prefsrc, route->family == AF_INET6); if (r <= 0) @@ -1746,21 +1660,20 @@ static int route_is_ready_to_configure(const Route *route, Link *link) { if (!gateway_is_ready(link, route->gateway_onlink, route->gw_family, &route->gw)) return false; + MultipathRoute *m; ORDERED_SET_FOREACH(m, route->multipath_routes) { union in_addr_union a = m->gateway.address; Link *l = NULL; - if (m->ifname) { - if (link_get_by_name(link->manager, m->ifname, &l) < 0) + r = multipath_route_get_link(link->manager, m, &l); + if (r < 0) + return false; + if (r > 0) { + if (!link_is_ready_to_configure(l, true)) return false; m->ifindex = l->ifindex; - } else if (m->ifindex > 0) { - if (link_get_by_index(link->manager, m->ifindex, &l) < 0) - return false; } - if (l && !link_is_ready_to_configure(l, true)) - return false; if (!gateway_is_ready(l ?: link, route->gateway_onlink, m->gateway.family, &a)) return false; @@ -1770,8 +1683,9 @@ static int route_is_ready_to_configure(const Route *route, Link *link) { } int request_process_route(Request *req) { - _cleanup_free_ Route **routes = NULL; - unsigned n_routes; + _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; + Route *route; + Link *link; int r; assert(req); @@ -1779,113 +1693,116 @@ int request_process_route(Request *req) { assert(req->route); assert(req->type == REQUEST_TYPE_ROUTE); - if (!link_is_ready_to_configure(req->link, false)) + link = req->link; + route = req->route; + + r = route_is_ready_to_configure(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to check if route is ready to configure: %m"); + if (r == 0) return 0; - r = route_is_ready_to_configure(req->route, req->link); - if (r <= 0) - return r; + if (route_needs_convert(route)) { + r = route_convert(link->manager, route, &converted); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to convert route: %m"); - r = route_configure(req->route, req->link, req->netlink_handler, - req->after_configure ? &n_routes : NULL, - req->after_configure ? &routes : NULL); - if (r < 0) - return r; + assert(r > 0); + assert(converted); - /* To prevent a double decrement on failure in after_configure(). */ - req->message_counter = NULL; + for (size_t i = 0; i < converted->n; i++) { + Route *existing; - if (req->after_configure) { - assert(n_routes > 0); + if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) { + _cleanup_(route_freep) Route *tmp = NULL; - for (unsigned i = 0; i < n_routes; i++) { - r = req->after_configure(req, routes[i]); - if (r < 0) - return r; + r = route_dup(converted->routes[i], &tmp); + if (r < 0) + return log_oom(); + + r = route_add(link->manager, converted->links[i] ?: link, tmp); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to add route: %m"); + + TAKE_PTR(tmp); + } else { + existing->source = converted->routes[i]->source; + existing->provider = converted->routes[i]->provider; + } } + } else { + r = route_setup_timer(route); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to setup timer for route: %m"); } + r = route_configure(route, link, req->netlink_handler); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure route: %m"); + + if (converted) + for (size_t i = 0; i < converted->n; i++) { + Route *existing; + + assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0); + route_enter_configuring(existing); + } + else + route_enter_configuring(route); + return 1; } -static int process_route_one(Manager *manager, Link *link, uint16_t type, const Route *tmp, const MultipathRoute *m) { - _cleanup_(route_freep) Route *nr = NULL; +static int process_route_one(Manager *manager, Link *link, uint16_t type, Route *in) { + _cleanup_(route_freep) Route *tmp = in; Route *route = NULL; - NextHop *nh = NULL; int r; assert(manager); assert(tmp); assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); - (void) manager_get_nexthop_by_id(manager, tmp->nexthop_id, &nh); - - 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."); - - if (nh->link) - link = nh->link; - - r = route_new(&nr); - if (r < 0) - return log_oom(); - - route_copy(nr, tmp, NULL, nh, UINT8_MAX); - - tmp = nr; - } else if (m) { - if (link) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), - "rtnl: received route contains both RTA_OIF and RTA_MULTIPATH, ignoring."); - - if (m->ifindex <= 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "rtnl: received multipath route with invalid ifindex, ignoring."); - - r = link_get_by_index(manager, m->ifindex, &link); - if (r < 0) { - log_warning_errno(r, "rtnl: received multipath route for link (%d) we do not know, ignoring: %m", m->ifindex); - return 0; - } - - r = route_new(&nr); - if (r < 0) - return log_oom(); - - route_copy(nr, tmp, m, NULL, UINT8_MAX); - - tmp = nr; - } + /* link may be NULL. This consumes 'in'. */ (void) route_get(manager, link, tmp, &route); switch (type) { case RTM_NEWROUTE: - if (!route) { - if (!manager->manage_foreign_routes) - log_route_debug(tmp, "Ignoring received foreign", link, manager); - else { - /* A route appeared that we did not request */ - log_route_debug(tmp, "Remembering foreign", link, manager); - r = route_add_foreign(manager, link, tmp, NULL); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); - return 0; - } + if (route) { + route_enter_configured(route); + log_route_debug(route, "Received remembered", link, manager); + + } else if (!manager->manage_foreign_routes) { + route_enter_configured(tmp); + log_route_debug(tmp, "Ignoring received", link, manager); + + } else { + /* A route appeared that we did not request */ + route_enter_configured(tmp); + log_route_debug(tmp, "Received new", link, manager); + r = route_add(manager, link, tmp); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); + return 0; } - } else - log_route_debug(tmp, "Received remembered", link, manager); + TAKE_PTR(tmp); + } break; case RTM_DELROUTE: - log_route_debug(tmp, - route ? "Forgetting" : - manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received foreign", - link, manager); - route_free(route); + if (route) { + route_enter_removed(route); + if (route->state == 0) { + log_route_debug(route, "Forgetting", link, manager); + route_free(route); + } else + log_route_debug(route, "Removed", link, manager); + } else + log_route_debug(tmp, + manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received", + link, manager); + break; default: @@ -1896,7 +1813,7 @@ static int process_route_one(Manager *manager, Link *link, uint16_t type, const } int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; + _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; _cleanup_(route_freep) Route *tmp = NULL; _cleanup_free_ void *rta_multipath = NULL; Link *link = NULL; @@ -2088,7 +2005,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); return 0; } else if (r >= 0) { - r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &multipath_routes); + r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &tmp->multipath_routes); if (r < 0) { log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); return 0; @@ -2101,18 +2018,21 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma if (route_type_is_reject(tmp)) link = NULL; - if (ordered_set_isempty(multipath_routes)) - (void) process_route_one(m, link, type, tmp, NULL); - else { - MultipathRoute *mr; + if (!route_needs_convert(tmp)) + return process_route_one(m, link, type, TAKE_PTR(tmp)); - ORDERED_SET_FOREACH(mr, multipath_routes) { - r = process_route_one(m, link, type, tmp, mr); - if (r < 0) - break; - } + r = route_convert(m, tmp, &converted); + if (r < 0) { + log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m"); + return 0; } + assert(r > 0); + assert(converted); + + for (size_t i = 0; i < converted->n; i++) + (void) process_route_one(m, converted->links[i] ?: link, type, TAKE_PTR(converted->routes[i])); + return 1; } @@ -3094,6 +3014,9 @@ static int route_section_verify(Route *route, Network *network) { if (section_is_invalid(route->section)) return -EINVAL; + /* Currently, we do not support static route with finite lifetime. */ + assert(route->lifetime == USEC_INFINITY); + if (route->gateway_from_dhcp_or_ra) { if (route->gw_family == AF_UNSPEC) { /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ |