diff options
-rw-r--r-- | src/network/networkd-json.c | 82 | ||||
-rw-r--r-- | src/network/networkd-json.h | 1 | ||||
-rw-r--r-- | src/network/networkd-ndisc.c | 295 | ||||
-rw-r--r-- | src/network/networkd-nexthop.c | 80 | ||||
-rw-r--r-- | src/network/networkd-nexthop.h | 26 | ||||
-rw-r--r-- | src/network/networkd-serialize.c | 65 | ||||
-rwxr-xr-x | test/test-network/systemd-networkd-tests.py | 102 |
7 files changed, 578 insertions, 73 deletions
diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 460fbe5968..160bcbdcca 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -164,45 +164,58 @@ static int nexthop_group_build_json(NextHop *nexthop, sd_json_variant **ret) { return 0; } -static int nexthop_append_json(NextHop *n, sd_json_variant **array) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *group = NULL; - _cleanup_free_ char *flags = NULL, *protocol = NULL, *state = NULL; +static int nexthop_append_json(NextHop *n, bool serializing, sd_json_variant **array) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; assert(n); assert(array); - r = route_flags_to_string_alloc(n->flags, &flags); + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_UNSIGNED("ID", n->id), + SD_JSON_BUILD_PAIR_INTEGER("Family", n->family), + SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)), + JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &n->provider, n->family)); if (r < 0) return r; - r = route_protocol_to_string_alloc(n->protocol, &protocol); - if (r < 0) - return r; + if (!serializing) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *group = NULL; + _cleanup_free_ char *flags = NULL, *protocol = NULL, *state = NULL; - r = network_config_state_to_string_alloc(n->state, &state); - if (r < 0) - return r; + r = route_flags_to_string_alloc(n->flags, &flags); + if (r < 0) + return r; - r = nexthop_group_build_json(n, &group); - if (r < 0) - return r; + r = route_protocol_to_string_alloc(n->protocol, &protocol); + if (r < 0) + return r; - return sd_json_variant_append_arraybo( - array, - SD_JSON_BUILD_PAIR_UNSIGNED("ID", n->id), - JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw.address, n->family), - SD_JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags), - SD_JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)), - SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", n->protocol), - SD_JSON_BUILD_PAIR_STRING("ProtocolString", protocol), - SD_JSON_BUILD_PAIR_BOOLEAN("Blackhole", n->blackhole), - JSON_BUILD_PAIR_VARIANT_NON_NULL("Group", group), - SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)), - SD_JSON_BUILD_PAIR_STRING("ConfigState", state)); + r = network_config_state_to_string_alloc(n->state, &state); + if (r < 0) + return r; + + r = nexthop_group_build_json(n, &group); + if (r < 0) + return r; + + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw.address, n->family), + SD_JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags), + SD_JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)), + SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", n->protocol), + SD_JSON_BUILD_PAIR_STRING("ProtocolString", protocol), + SD_JSON_BUILD_PAIR_BOOLEAN("Blackhole", n->blackhole), + JSON_BUILD_PAIR_VARIANT_NON_NULL("Group", group), + SD_JSON_BUILD_PAIR_STRING("ConfigState", state)); + } + + return sd_json_variant_append_array(array, v); } -static int nexthops_append_json(Manager *manager, int ifindex, sd_json_variant **v) { +int nexthops_append_json(Manager *manager, int ifindex, sd_json_variant **v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; NextHop *nexthop; int r; @@ -211,10 +224,21 @@ static int nexthops_append_json(Manager *manager, int ifindex, sd_json_variant * assert(v); HASHMAP_FOREACH(nexthop, manager->nexthops_by_id) { - if (nexthop->ifindex != ifindex) - continue; + if (ifindex >= 0) { + if (nexthop->ifindex != ifindex) + continue; + } else { + /* negative ifindex means we are serializing now. */ + + if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN) + continue; + if (!nexthop_exists(nexthop)) + continue; + + log_nexthop_debug(nexthop, "Serializing", manager); + } - r = nexthop_append_json(nexthop, &array); + r = nexthop_append_json(nexthop, /* serializing = */ ifindex < 0, &array); if (r < 0) return r; } diff --git a/src/network/networkd-json.h b/src/network/networkd-json.h index e8be60458f..ada2500e39 100644 --- a/src/network/networkd-json.h +++ b/src/network/networkd-json.h @@ -9,6 +9,7 @@ typedef struct Link Link; typedef struct Manager Manager; int addresses_append_json(Link *link, bool serializing, sd_json_variant **v); +int nexthops_append_json(Manager *manager, int ifindex, sd_json_variant **v); int routes_append_json(Manager *manager, int ifindex, sd_json_variant **v); int link_build_json(Link *link, sd_json_variant **ret); int manager_build_json(Manager *manager, sd_json_variant **ret); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 3871618e5f..d46fc4a4e5 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -18,6 +18,7 @@ #include "networkd-dhcp6.h" #include "networkd-manager.h" #include "networkd-ndisc.h" +#include "networkd-nexthop.h" #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-state-file.h" @@ -149,6 +150,290 @@ static int ndisc_check_ready(Link *link) { return 0; } +static int ndisc_remove_unused_nexthop(Link *link, NextHop *nexthop) { + int r; + + assert(link); + assert(link->manager); + assert(link->ifindex > 0); + assert(nexthop); + + if (nexthop->source != NETWORK_CONFIG_SOURCE_NDISC) + return 0; + + if (nexthop->ifindex != link->ifindex) + return 0; + + Route *route; + SET_FOREACH(route, nexthop->routes) + if (route_exists(route) || route_is_requesting(route)) + return 0; + + Request *req; + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->type != REQUEST_TYPE_ROUTE) + continue; + + route = ASSERT_PTR(req->userdata); + if (route->nexthop_id == nexthop->id) + return 0; + } + + r = nexthop_remove_and_cancel(nexthop, link->manager); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to remove unused nexthop: %m"); + + return 0; +} + +static int ndisc_remove_unused_nexthop_by_id(Link *link, uint32_t id) { + assert(link); + assert(link->manager); + + if (id == 0) + return 0; + + NextHop *nexthop; + if (nexthop_get_by_id(link->manager, id, &nexthop) < 0) + return 0; + + return ndisc_remove_unused_nexthop(link, nexthop); +} + +static int ndisc_remove_unused_nexthops(Link *link) { + int ret = 0; + + assert(link); + assert(link->manager); + + NextHop *nexthop; + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) + RET_GATHER(ret, ndisc_remove_unused_nexthop(link, nexthop)); + + return ret; +} + +#define NDISC_NEXTHOP_APP_ID SD_ID128_MAKE(76,d2,0f,1f,76,1e,44,d1,97,3a,52,5c,05,68,b5,0d) + +static uint32_t ndisc_generate_nexthop_id(NextHop *nexthop, Link *link, sd_id128_t app_id, uint64_t trial) { + assert(nexthop); + assert(link); + + struct siphash state; + siphash24_init(&state, app_id.bytes); + siphash24_compress_typesafe(nexthop->protocol, &state); + siphash24_compress_string(link->ifname, &state); + siphash24_compress_typesafe(nexthop->gw.address.in6, &state); + siphash24_compress_typesafe(nexthop->provider.in6, &state); + uint64_t n = htole64(trial); + siphash24_compress_typesafe(n, &state); + + uint64_t result = htole64(siphash24_finalize(&state)); + return (uint32_t) ((result & 0xffffffff) ^ (result >> 32)); +} + +static bool ndisc_nexthop_equal(NextHop *a, NextHop *b) { + assert(a); + assert(b); + + if (a->source != b->source) + return false; + if (a->protocol != b->protocol) + return false; + if (a->ifindex != b->ifindex) + return false; + if (!in6_addr_equal(&a->provider.in6, &b->provider.in6)) + return false; + if (!in6_addr_equal(&a->gw.address.in6, &b->gw.address.in6)) + return false; + + return true; +} + +static bool ndisc_take_nexthop_id(NextHop *nexthop, NextHop *existing, Manager *manager) { + assert(nexthop); + assert(existing); + assert(manager); + + if (!ndisc_nexthop_equal(nexthop, existing)) + return false; + + log_nexthop_debug(existing, "Found matching", manager); + nexthop->id = existing->id; + return true; +} + +static int ndisc_nexthop_find_id(NextHop *nexthop, Link *link) { + NextHop *n; + Request *req; + int r; + + assert(nexthop); + assert(link); + assert(link->manager); + + sd_id128_t app_id; + r = sd_id128_get_machine_app_specific(NDISC_NEXTHOP_APP_ID, &app_id); + if (r < 0) + return r; + + uint32_t id = ndisc_generate_nexthop_id(nexthop, link, app_id, 0); + if (nexthop_get_by_id(link->manager, id, &n) >= 0 && + ndisc_take_nexthop_id(nexthop, n, link->manager)) + return true; + if (nexthop_get_request_by_id(link->manager, id, &req) >= 0 && + ndisc_take_nexthop_id(nexthop, req->userdata, link->manager)) + return true; + + HASHMAP_FOREACH(n, link->manager->nexthops_by_id) + if (ndisc_take_nexthop_id(nexthop, n, link->manager)) + return true; + + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->type != REQUEST_TYPE_NEXTHOP) + continue; + + if (ndisc_take_nexthop_id(nexthop, req->userdata, link->manager)) + return true; + } + + return false; +} + +static int ndisc_nexthop_new(Route *route, Link *link, NextHop **ret) { + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; + int r; + + assert(route); + assert(link); + assert(ret); + + r = nexthop_new(&nexthop); + if (r < 0) + return r; + + nexthop->source = NETWORK_CONFIG_SOURCE_NDISC; + nexthop->provider = route->provider; + nexthop->protocol = route->protocol == RTPROT_REDIRECT ? RTPROT_REDIRECT : RTPROT_RA; + nexthop->family = AF_INET6; + nexthop->gw.address = route->nexthop.gw; + nexthop->ifindex = link->ifindex; + + r = ndisc_nexthop_find_id(nexthop, link); + if (r < 0) + return r; + + *ret = TAKE_PTR(nexthop); + return 0; +} + +static int ndisc_nexthop_acquire_id(NextHop *nexthop, Link *link) { + int r; + + assert(nexthop); + assert(nexthop->id == 0); + assert(link); + assert(link->manager); + + sd_id128_t app_id; + r = sd_id128_get_machine_app_specific(NDISC_NEXTHOP_APP_ID, &app_id); + if (r < 0) + return r; + + for (uint64_t trial = 0; trial < 100; trial++) { + uint32_t id = ndisc_generate_nexthop_id(nexthop, link, app_id, trial); + if (id == 0) + continue; + + if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(id))) + continue; /* The ID is already used in a .network file. */ + + if (nexthop_get_by_id(link->manager, id, NULL) >= 0) + continue; /* The ID is already used by an existing nexthop. */ + + if (nexthop_get_request_by_id(link->manager, id, NULL) >= 0) + continue; /* The ID is already used by a nexthop being requested. */ + + log_link_debug(link, "Generated new ndisc nexthop ID for %s with trial %"PRIu64": %"PRIu32, + IN6_ADDR_TO_STRING(&nexthop->gw.address.in6), trial, id); + nexthop->id = id; + return 0; + } + + return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "Cannot find free nexthop ID for %s.", + IN6_ADDR_TO_STRING(&nexthop->gw.address.in6)); +} + +static int ndisc_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, NextHop *nexthop) { + int r; + + assert(link); + + r = nexthop_configure_handler_internal(m, link, "Could not set NDisc route"); + if (r <= 0) + return r; + + r = ndisc_check_ready(link); + if (r < 0) + link_enter_failed(link); + + return 1; +} + +static int ndisc_request_nexthop(NextHop *nexthop, Link *link) { + int r; + + assert(nexthop); + assert(link); + + if (nexthop->id > 0) + return 0; + + r = ndisc_nexthop_acquire_id(nexthop, link); + if (r < 0) + return r; + + r = link_request_nexthop(link, nexthop, &link->ndisc_messages, ndisc_nexthop_handler); + if (r < 0) + return r; + if (r > 0) + link->ndisc_configured = false; + + return 0; +} + +static int ndisc_set_route_nexthop(Route *route, Link *link, bool request) { + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; + int r; + + assert(route); + assert(link); + assert(link->manager); + + if (!link->manager->manage_foreign_nexthops) + goto finalize; + + if (route->nexthop.family != AF_INET6 || in6_addr_is_null(&route->nexthop.gw.in6)) + goto finalize; + + r = ndisc_nexthop_new(route, link, &nexthop); + if (r < 0) + return r; + + if (nexthop->id == 0 && !request) + goto finalize; + + r = ndisc_request_nexthop(nexthop, link); + if (r < 0) + return r; + + route->nexthop = (RouteNextHop) {}; + route->nexthop_id = nexthop->id; + +finalize: + return route_adjust_nexthops(route, link); +} + static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) { int r; @@ -200,7 +485,7 @@ static int ndisc_request_route(Route *route, Link *link) { if (r < 0) return r; - r = route_adjust_nexthops(route, link); + r = ndisc_set_route_nexthop(route, link, /* request = */ true); if (r < 0) return r; @@ -322,7 +607,7 @@ static int ndisc_remove_route(Route *route, Link *link) { assert(link); assert(link->manager); - r = route_adjust_nexthops(route, link); + r = ndisc_set_route_nexthop(route, link, /* request = */ false); if (r < 0) return r; @@ -362,7 +647,7 @@ static int ndisc_remove_route(Route *route, Link *link) { } } - return ret; + return RET_GATHER(ret, ndisc_remove_unused_nexthop_by_id(link, route->nexthop_id)); } static int ndisc_remove_router_route(Route *route, Link *link, sd_ndisc_router *rt) { @@ -2112,6 +2397,8 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m")); } + RET_GATHER(ret, ndisc_remove_unused_nexthops(link)); + SET_FOREACH(address, link->addresses) { if (address->source != NETWORK_CONFIG_SOURCE_NDISC) continue; @@ -2772,6 +3059,8 @@ int link_drop_ndisc_config(Link *link, Network *network) { if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove SLAAC route, ignoring: %m")); } + + RET_GATHER(ret, ndisc_remove_unused_nexthops(link)); } /* If SLAAC address is disabled, drop all addresses. */ diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 0d6f881ca0..7675ea4c55 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -105,7 +105,6 @@ static NextHop* nexthop_free(NextHop *nexthop) { } DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop, nexthop, nexthop_free); -DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( nexthop_hash_ops, @@ -123,7 +122,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( NextHop, nexthop_detach); -static int nexthop_new(NextHop **ret) { +int nexthop_new(NextHop **ret) { _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; nexthop = new(NextHop, 1); @@ -339,7 +338,7 @@ static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) { return -ENOENT; } -static int nexthop_get_request_by_id(Manager *manager, uint32_t id, Request **ret) { +int nexthop_get_request_by_id(Manager *manager, uint32_t id, Request **ret) { Request *req; assert(manager); @@ -459,7 +458,7 @@ static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) { return -EBUSY; } -static void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *manager) { +void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *manager) { _cleanup_free_ char *state = NULL, *group = NULL, *flags = NULL; struct nexthop_grp *nhg; Link *link = NULL; @@ -569,6 +568,33 @@ int nexthop_remove(NextHop *nexthop, Manager *manager) { return 0; } +int nexthop_remove_and_cancel(NextHop *nexthop, Manager *manager) { + _cleanup_(request_unrefp) Request *req = NULL; + bool waiting = false; + + assert(nexthop); + assert(nexthop->id > 0); + assert(manager); + + /* If the nexthop is remembered by the manager, then use the remembered object. */ + (void) nexthop_get_by_id(manager, nexthop->id, &nexthop); + + /* Cancel the request for the nexthop. If the request is already called but we have not received the + * notification about the request, then explicitly remove the nexthop. */ + if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0) { + request_ref(req); /* avoid the request freed by request_detach() */ + waiting = req->waiting_reply; + request_detach(req); + nexthop_cancel_requesting(nexthop); + } + + /* If we know that the nexthop will come or already exists, remove it. */ + if (waiting || (nexthop->manager && nexthop_exists(nexthop))) + return nexthop_remove(nexthop, manager); + + return 0; +} + static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; @@ -633,19 +659,32 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { return request_call_netlink_async(link->manager->rtnl, m, req); } -static int static_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, NextHop *nexthop) { +int nexthop_configure_handler_internal(sd_netlink_message *m, Link *link, const char *error_msg) { int r; assert(m); assert(link); + assert(error_msg); r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_message_warning_errno(link, m, r, "Could not set nexthop"); + log_link_message_warning_errno(link, m, r, error_msg); link_enter_failed(link); - return 1; + return 0; } + return 1; +} + +static int static_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, NextHop *nexthop) { + int r; + + assert(link); + + r = nexthop_configure_handler_internal(m, link, "Failed to set static nexthop"); + if (r <= 0) + return r; + if (link->static_nexthop_messages == 0) { log_link_debug(link, "Nexthops set"); link->static_nexthops_configured = true; @@ -740,7 +779,12 @@ static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { return 1; } -static int link_request_nexthop(Link *link, const NextHop *nexthop) { +int link_request_nexthop( + Link *link, + const NextHop *nexthop, + unsigned *message_counter, + nexthop_netlink_handler_t netlink_handler) { + _cleanup_(nexthop_unrefp) NextHop *tmp = NULL; NextHop *existing = NULL; int r; @@ -780,8 +824,8 @@ static int link_request_nexthop(Link *link, const NextHop *nexthop) { nexthop_hash_func, nexthop_compare_func, nexthop_process_request, - &link->static_nexthop_messages, - static_nexthop_handler, + message_counter, + netlink_handler, NULL); if (r <= 0) return r; @@ -807,7 +851,7 @@ int link_request_static_nexthops(Link *link, bool only_ipv4) { if (only_ipv4 && nh->family != AF_INET) continue; - r = link_request_nexthop(link, nh); + r = link_request_nexthop(link, nh, &link->static_nexthop_messages, static_nexthop_handler); if (r < 0) return log_link_warning_errno(link, r, "Could not request nexthop: %m"); } @@ -866,16 +910,17 @@ int link_drop_nexthops(Link *link, bool only_static) { if (!nexthop_exists(nexthop)) continue; - if (only_static) { - if (nexthop->source != NETWORK_CONFIG_SOURCE_STATIC) + if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN) { + if (only_static) continue; - } else { + /* Do not mark foreign nexthop when KeepConfiguration= is enabled. */ - if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN && - link->network && + if (link->network && FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) continue; - } + + } else if (nexthop->source != NETWORK_CONFIG_SOURCE_STATIC) + continue; /* Ignore dynamically configurad nexthops. */ /* Ignore nexthops bound to other links. */ if (nexthop->ifindex > 0 && nexthop->ifindex != link->ifindex) @@ -1106,6 +1151,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, NextHop *n = ASSERT_PTR(req->userdata); nexthop->source = n->source; + nexthop->provider = n->provider; } r = sd_rtnl_message_get_family(message, &nexthop->family); diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h index de3f64c374..1466e2702f 100644 --- a/src/network/networkd-nexthop.h +++ b/src/network/networkd-nexthop.h @@ -16,13 +16,21 @@ typedef struct Link Link; typedef struct Manager Manager; typedef struct Network Network; - -typedef struct NextHop { +typedef struct NextHop NextHop; +typedef int (*nexthop_netlink_handler_t)( + sd_netlink *rtnl, + sd_netlink_message *m, + Request *req, + Link *link, + NextHop *address); + +struct NextHop { Network *network; Manager *manager; ConfigSection *section; NetworkConfigSource source; NetworkConfigState state; + union in_addr_union provider; /* DHCP server or router address */ unsigned n_ref; @@ -44,12 +52,17 @@ typedef struct NextHop { /* For managing routes and nexthops that depend on this nexthop. */ Set *nexthops; Set *routes; -} NextHop; +}; + +void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *manager); +int nexthop_new(NextHop **ret); NextHop* nexthop_ref(NextHop *nexthop); NextHop* nexthop_unref(NextHop *nexthop); +DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref); int nexthop_remove(NextHop *nexthop, Manager *manager); +int nexthop_remove_and_cancel(NextHop *nexthop, Manager *manager); int network_drop_invalid_nexthops(Network *network); @@ -62,9 +75,16 @@ static inline int link_drop_static_nexthops(Link *link) { } void link_forget_nexthops(Link *link); +int nexthop_configure_handler_internal(sd_netlink_message *m, Link *link, const char *error_msg); +int link_request_nexthop( + Link *link, + const NextHop *nexthop, + unsigned *message_counter, + nexthop_netlink_handler_t netlink_handler); int link_request_static_nexthops(Link *link, bool only_ipv4); int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret); +int nexthop_get_request_by_id(Manager *manager, uint32_t id, Request **ret); int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret); int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); int manager_build_nexthop_ids(Manager *manager); diff --git a/src/network/networkd-serialize.c b/src/network/networkd-serialize.c index 8cc74baae9..26a38bd4dd 100644 --- a/src/network/networkd-serialize.c +++ b/src/network/networkd-serialize.c @@ -11,6 +11,7 @@ #include "networkd-json.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "networkd-nexthop.h" #include "networkd-route.h" #include "networkd-serialize.h" @@ -49,6 +50,10 @@ int manager_serialize(Manager *manager) { if (r < 0) return r; + r = nexthops_append_json(manager, /* ifindex = */ -1, &v); + if (r < 0) + return r; + r = routes_append_json(manager, /* ifindex = */ -1, &v); if (r < 0) return r; @@ -230,6 +235,63 @@ static int manager_deserialize_link(Manager *manager, sd_json_variant *v) { return ret; } +typedef struct NextHopParam { + uint32_t id; + int family; + NetworkConfigSource source; + struct iovec provider; +} NextHopParam; + +static void nexthop_param_done(NextHopParam *p) { + assert(p); + + iovec_done(&p->provider); +} + +static int manager_deserialize_nexthop(Manager *manager, sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "ID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NextHopParam, id), SD_JSON_MANDATORY }, + { "Family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(NextHopParam, family), SD_JSON_MANDATORY }, + { "ConfigSource", SD_JSON_VARIANT_STRING, json_dispatch_network_config_source, offsetof(NextHopParam, source), SD_JSON_MANDATORY }, + { "ConfigProvider", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(NextHopParam, provider), 0 }, + {}, + }; + + int r; + + assert(manager); + assert(v); + + _cleanup_(nexthop_param_done) NextHopParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_debug_errno(r, "Failed to dispatch nexthop from json variant: %m"); + + if (p.id == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Dispatched nexthop ID is zero."); + + if (p.provider.iov_len != 0 && p.provider.iov_len != FAMILY_ADDRESS_SIZE(p.family)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Dispatched provider address size (%zu) is incompatible with the family (%s).", + p.provider.iov_len, af_to_ipv4_ipv6(p.family)); + + NextHop *nexthop; + r = nexthop_get_by_id(manager, p.id, &nexthop); + if (r < 0) { + log_debug_errno(r, "Cannot find deserialized nexthop (ID=%"PRIu32"): %m", p.id); + return 0; /* Already removed? */ + } + + if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN) + return 0; /* Huh?? Already deserialized?? */ + + nexthop->source = p.source; + memcpy_safe(&nexthop->provider, p.provider.iov_base, p.provider.iov_len); + + log_nexthop_debug(nexthop, "Deserialized", manager); + return 0; +} + typedef struct RouteParam { Route route; @@ -395,6 +457,9 @@ int manager_deserialize(Manager *manager) { JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Interfaces")) RET_GATHER(ret, manager_deserialize_link(manager, i)); + JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "NextHops")) + RET_GATHER(ret, manager_deserialize_nexthop(manager, i)); + JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Routes")) RET_GATHER(ret, manager_deserialize_route(manager, i)); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 74096b47a2..65561a0138 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -6259,8 +6259,8 @@ class NetworkdRATests(unittest.TestCase, Utilities): check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::2 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') self.wait_route_dropped('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', '2002:da8:1:1:1a:2b:3c:4d via fe80::1 proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', '2002:da8:1:2:1a:2b:3c:4d via fe80::2 proto redirect', ipv='-6', timeout_sec=10) + self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', ipv='-6', timeout_sec=10) + self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', ipv='-6', timeout_sec=10) # Send Neighbor Advertisement without the router flag to announce the default router is not available anymore. # Then, verify that all redirect routes and the default route are dropped. @@ -6412,14 +6412,14 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10) self.wait_address('client', '2002:da8:1:98:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10) - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a99 proto ra metric 512', ipv='-6', timeout_sec=10) - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a98 proto ra metric 2048', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 512', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 2048', ipv='-6', timeout_sec=10) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a99 proto ra metric 512 expires [0-9]*sec pref high') - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a98 proto ra metric 2048 expires [0-9]*sec pref low') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 512 expires [0-9]*sec pref high') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 2048 expires [0-9]*sec pref low') with open(os.path.join(network_unit_dir, '25-veth-client.network'), mode='a', encoding='utf-8') as f: f.write('\n[Link]\nMACAddress=12:34:56:78:9a:01\n[IPv6AcceptRA]\nRouteMetric=100:200:300\n') @@ -6429,14 +6429,14 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a01/64', ipv='-6', timeout_sec=10) self.wait_address('client', '2002:da8:1:98:1034:56ff:fe78:9a01/64', ipv='-6', timeout_sec=10) - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a99 proto ra metric 100', ipv='-6', timeout_sec=10) - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a98 proto ra metric 300', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 100', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 300', ipv='-6', timeout_sec=10) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a99 proto ra metric 100 expires [0-9]*sec pref high') - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a98 proto ra metric 300 expires [0-9]*sec pref low') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 100 expires [0-9]*sec pref high') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 300 expires [0-9]*sec pref low') self.assertNotIn('metric 512', output) self.assertNotIn('metric 2048', output) @@ -6444,20 +6444,41 @@ class NetworkdRATests(unittest.TestCase, Utilities): remove_network_unit('25-veth-router-high.network', '25-veth-router-low.network') copy_network_unit('25-veth-router-high2.network', '25-veth-router-low2.network') networkctl_reload() - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a99 proto ra metric 300', ipv='-6', timeout_sec=10) - self.wait_route('client', 'default via fe80::1034:56ff:fe78:9a98 proto ra metric 100', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 300', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 100', ipv='-6', timeout_sec=10) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a99 proto ra metric 300 expires [0-9]*sec pref low') - self.assertRegex(output, r'default via fe80::1034:56ff:fe78:9a98 proto ra metric 100 expires [0-9]*sec pref high') - self.assertNotRegex(output, 'default via fe80::1034:56ff:fe78:9a99 proto ra metric 100') - self.assertNotRegex(output, 'default via fe80::1034:56ff:fe78:9a98 proto ra metric 300') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 300 expires [0-9]*sec pref low') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 100 expires [0-9]*sec pref high') + self.assertNotRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 100') + self.assertNotRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 300') self.assertNotIn('metric 512', output) self.assertNotIn('metric 2048', output) - def test_ndisc_vs_static_route(self): + # Use the same preference, and check if the two routes are not coalesced. See issue #33470. + with open(os.path.join(network_unit_dir, '25-veth-router-high2.network'), mode='a', encoding='utf-8') as f: + f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') + with open(os.path.join(network_unit_dir, '25-veth-router-low2.network'), mode='a', encoding='utf-8') as f: + f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') + networkctl_reload() + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 200', ipv='-6', timeout_sec=10) + self.wait_route('client', r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 200', ipv='-6', timeout_sec=10) + + print('### ip -6 route show dev client default') + output = check_output('ip -6 route show dev client default') + print(output) + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric 200 expires [0-9]*sec pref medium') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric 200 expires [0-9]*sec pref medium') + self.assertNotIn('pref high', output) + self.assertNotIn('pref low', output) + self.assertNotIn('metric 512', output) + self.assertNotIn('metric 2048', output) + + def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): + if not manage_foreign_nexthops: + copy_networkd_conf_dropin('networkd-manage-foreign-nexthops-no.conf') copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network') start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -6467,13 +6488,24 @@ class NetworkdRATests(unittest.TestCase, Utilities): output = check_output('ip -6 route show dev veth99 default') print(output) self.assertIn('via fe80::1034:56ff:fe78:9abd proto static metric 256 pref medium', output) - self.assertNotIn('proto ra', output) + if manage_foreign_nexthops: + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') + else: + self.assertNotIn('proto ra', output) + + print('### ip -6 nexthop show dev veth99') + output = check_output('ip -6 nexthop show dev veth99') + print(output) + if manage_foreign_nexthops: + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') + else: + self.assertEqual(output, '') # Also check if the static route is protected from RA with zero lifetime with open(os.path.join(network_unit_dir, '25-ipv6-prefix.network'), mode='a', encoding='utf-8') as f: f.write('\n[Network]\nIPv6SendRA=no\n') networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime - self.wait_route_dropped('veth99', 'default via fe80::1034:56ff:fe78:9abd proto ra metric 256', ipv='-6', timeout_sec=10) + self.wait_route_dropped('veth99', r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', ipv='-6', timeout_sec=10) print('### ip -6 route show dev veth99 default') output = check_output('ip -6 route show dev veth99 default') @@ -6481,6 +6513,24 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.assertIn('via fe80::1034:56ff:fe78:9abd proto static metric 256 pref medium', output) self.assertNotIn('proto ra', output) + # Check if nexthop is removed. + print('### ip -6 nexthop show dev veth99') + output = check_output('ip -6 nexthop show dev veth99') + print(output) + self.assertEqual(output, '') + + def test_ndisc_vs_static_route(self): + first = True + for manage_foreign_nexthops in [True, False]: + if first: + first = False + else: + self.tearDown() + + print(f'### test_ndisc_vs_static_route(manage_foreign_nexthops={manage_foreign_nexthops})') + with self.subTest(manage_foreign_nexthops=manage_foreign_nexthops): + self._test_ndisc_vs_static_route(manage_foreign_nexthops) + # radvd supports captive portal since v2.20. # https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") @@ -8382,10 +8432,15 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): self.assertIn('2001:db8:0:1::/64 proto ra', output) self.assertNotIn('2001:db8:0:2::/64 proto ra', output) self.assertNotIn('2001:db8:0:3::/64 proto ra', output) - self.assertRegex(output, '2001:db0:fff::/64 via fe80::1034:56ff:fe78:9abc') + self.assertRegex(output, r'2001:db0:fff::/64 nhid [0-9]* via fe80::1034:56ff:fe78:9abc') self.assertNotIn('2001:db1:fff::/64', output) self.assertNotIn('2001:db2:fff::/64', output) + print('### ip -6 nexthop show dev veth-peer') + output = check_output('ip -6 nexthop show dev veth-peer') + print(output) + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') print(output) @@ -8434,9 +8489,14 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): print(output) self.assertIn('2001:db8:0:1::/64 proto ra', output) self.assertNotIn('2001:db8:0:2::/64 proto ra', output) - self.assertRegex(output, '2001:db0:fff::/64 via fe80::1034:56ff:fe78:9abc') + self.assertRegex(output, r'2001:db0:fff::/64 nhid [0-9]* via fe80::1034:56ff:fe78:9abc') self.assertNotIn('2001:db1:fff::/64', output) + print('### ip -6 nexthop show dev veth-peer') + output = check_output('ip -6 nexthop show dev veth-peer') + print(output) + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') print(output) |