diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2024-02-23 04:03:26 +0100 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2024-04-04 22:57:54 +0200 |
commit | 6df0059441e51ae9df302834ca6976afade4f603 (patch) | |
tree | 0e3a633b23613b8b5c55fbb3830d5d8b2c2d8cf6 /src | |
parent | sd-ndisc: add basic support of Redirect message (diff) | |
download | systemd-6df0059441e51ae9df302834ca6976afade4f603.tar.xz systemd-6df0059441e51ae9df302834ca6976afade4f603.zip |
network/ndisc: add basic support for Redirect message
Closes #31438.
Diffstat (limited to 'src')
-rw-r--r-- | src/network/networkd-link.h | 1 | ||||
-rw-r--r-- | src/network/networkd-ndisc.c | 317 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-network.c | 1 | ||||
-rw-r--r-- | src/network/networkd-network.h | 1 |
5 files changed, 321 insertions, 0 deletions
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 01931f6773..9d15b4acce 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -166,6 +166,7 @@ typedef struct Link { Set *ndisc_dnssl; Set *ndisc_captive_portals; Set *ndisc_pref64; + Set *ndisc_redirects; unsigned ndisc_messages; bool ndisc_configured:1; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 6be0c0f9d9..8956c7438a 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -378,6 +378,292 @@ static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router * return 0; } +static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) { + _cleanup_(route_unrefp) Route *route = NULL; + struct in6_addr gateway, destination; + int r; + + assert(rd); + assert(ret); + + r = sd_ndisc_redirect_get_target_address(rd, &gateway); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(rd, &destination); + if (r < 0) + return r; + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET6; + if (!in6_addr_equal(&gateway, &destination)) { + route->nexthop.gw.in6 = gateway; + route->nexthop.family = AF_INET6; + } + route->dst.in6 = destination; + route->dst_prefixlen = 128; + + *ret = TAKE_PTR(route); + return 0; +} + +static int ndisc_request_redirect_route(Link *link, sd_ndisc_redirect *rd) { + struct in6_addr router, sender; + int r; + + assert(link); + assert(link->ndisc_default_router); + assert(rd); + + r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &router); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, &router)) + return 0; + + _cleanup_(route_unrefp) Route *route = NULL; + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + route->protocol = RTPROT_REDIRECT; + route->protocol_set = true; /* To make ndisc_request_route() not override the protocol. */ + + /* Redirect message does not have the lifetime, let's use the lifetime of the default router, and + * update the lifetime of the redirect route every time when we receive RA. */ + return ndisc_request_route(route, link, link->ndisc_default_router); +} + +static int ndisc_remove_redirect_route(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(route_unrefp) Route *route = NULL; + int r; + + assert(link); + assert(rd); + + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + return ndisc_remove_route(route, link); +} + +static void ndisc_redirect_hash_func(const sd_ndisc_redirect *x, struct siphash *state) { + struct in6_addr dest = {}; + + assert(x); + assert(state); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest); + + siphash24_compress_typesafe(dest, state); +} + +static int ndisc_redirect_compare_func(const sd_ndisc_redirect *x, const sd_ndisc_redirect *y) { + struct in6_addr dest_x = {}, dest_y = {}; + + assert(x); + assert(y); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest_x); + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) y, &dest_y); + + return memcmp(&dest_x, &dest_y, sizeof(dest_x)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_redirect_hash_ops, + sd_ndisc_redirect, + ndisc_redirect_hash_func, + ndisc_redirect_compare_func, + sd_ndisc_redirect_unref); + +static int ndisc_redirect_equal(sd_ndisc_redirect *x, sd_ndisc_redirect *y) { + struct in6_addr a, b; + int r; + + assert(x); + assert(y); + + r = sd_ndisc_redirect_get_destination_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(y, &b); + if (r < 0) + return r; + + if (!in6_addr_equal(&a, &b)) + return false; + + r = sd_ndisc_redirect_get_target_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_target_address(y, &b); + if (r < 0) + return r; + + return in6_addr_equal(&a, &b); +} + +static int ndisc_redirect_drop_conflict(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *existing = NULL; + int r; + + assert(link); + assert(rd); + + existing = set_remove(link->ndisc_redirects, rd); + if (!existing) + return 0; + + r = ndisc_redirect_equal(rd, existing); + if (r != 0) + return r; + + return ndisc_remove_redirect_route(link, existing); +} + +static int ndisc_redirect_handler(Link *link, sd_ndisc_redirect *rd) { + struct in6_addr router, sender; + usec_t lifetime_usec, now_usec; + int r; + + assert(link); + assert(link->network); + assert(rd); + + if (!link->network->ndisc_use_redirect) + return 0; + + /* Ignore all Redirect messages from non-default router. */ + + if (!link->ndisc_default_router) + return 0; + + r = sd_ndisc_router_get_lifetime_timestamp(link->ndisc_default_router, CLOCK_BOOTTIME, &lifetime_usec); + if (r < 0) + return r; + + r = sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec); + if (r < 0) + return r; + + if (lifetime_usec <= now_usec) + return 0; /* The default router is outdated. Ignore the redirect message. */ + + r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &router); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, &router)) + return 0; /* The redirect message is sent from a non-default router. */ + + /* OK, the Redirect message is sent from the current default router. */ + + r = ndisc_redirect_drop_conflict(link, rd); + if (r < 0) + return r; + + r = set_ensure_put(&link->ndisc_redirects, &ndisc_redirect_hash_ops, rd); + if (r < 0) + return r; + + sd_ndisc_redirect_ref(rd); + + return ndisc_request_redirect_route(link, rd); +} + +static int ndisc_router_update_redirect(Link *link) { + int r, ret = 0; + + assert(link); + + /* Reconfigure redirect routes to update their lifetime. */ + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + r = ndisc_request_redirect_route(link, rd); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to update lifetime of the Redirect route: %m")); + } + + return ret; +} + +static int ndisc_drop_redirect(Link *link, const struct in6_addr *router, bool remove) { + int r; + + assert(link); + + /* If the router is purged, then drop the redirect routes configured with the Redirect message sent + * by the router. */ + + if (!router) + return 0; + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + struct in6_addr sender; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, router)) + continue; + + if (remove) { + r = ndisc_remove_redirect_route(link, rd); + if (r < 0) + return r; + } + + sd_ndisc_redirect_unref(set_remove(link->ndisc_redirects, rd)); + } + + return 0; +} + +static int ndisc_update_redirect_sender(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) { + int r; + + assert(link); + assert(original_address); + assert(current_address); + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + struct in6_addr sender; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, original_address)) + continue; + + r = sd_ndisc_redirect_set_sender_address(rd, current_address); + if (r < 0) + return r; + } + + return 0; +} + static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { _cleanup_(route_unrefp) Route *route = NULL; struct in6_addr gateway; @@ -1534,6 +1820,10 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated default router, ignoring: %m")); + r = ndisc_drop_redirect(link, router, /* remove = */ false); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated Redirect messages, ignoring: %m")); + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) continue; @@ -1744,6 +2034,7 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { struct in6_addr router; usec_t timestamp_usec; + bool is_default; int r; assert(link); @@ -1784,6 +2075,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { r = ndisc_remember_default_router(link, rt); if (r < 0) return r; + is_default = r; r = ndisc_start_dhcp6_client(link, rt); if (r < 0) @@ -1813,6 +2105,17 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + if (is_default) { + r = ndisc_router_update_redirect(link); + if (r < 0) + return r; + + } else if (sd_ndisc_router_get_lifetime(rt, NULL) <= 0) { + r = ndisc_drop_redirect(link, &router, /* remove = */ true); + if (r < 0) + return r; + } + if (link->ndisc_messages == 0) link->ndisc_configured = true; else @@ -1875,6 +2178,10 @@ static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *n if (r < 0) return r; + r = ndisc_update_redirect_sender(link, &original_address, ¤t_address); + if (r < 0) + return r; + Route *route; SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) @@ -1979,6 +2286,15 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, v } break; + case SD_NDISC_EVENT_REDIRECT: + r = ndisc_redirect_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + log_link_warning_errno(link, r, "Failed to process Redirect message: %m"); + link_enter_failed(link); + return; + } + break; + case SD_NDISC_EVENT_TIMEOUT: log_link_debug(link, "NDisc handler get timeout event"); if (link->ndisc_messages == 0) { @@ -2114,6 +2430,7 @@ void ndisc_flush(Link *link) { link->ndisc_dnssl = set_free(link->ndisc_dnssl); link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_pref64 = set_free(link->ndisc_pref64); + link->ndisc_redirects = set_free(link->ndisc_redirects); } static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9b37000bff..2f9855465f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -292,6 +292,7 @@ DHCPv6.RapidCommit, config_parse_bool, DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release) DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context) +IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect) IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway) IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix) IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_autonomous_prefix) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index eaa3676ede..880856f596 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -474,6 +474,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ipv4_rp_filter = _IP_REVERSE_PATH_FILTER_INVALID, .ndisc = -1, + .ndisc_use_redirect = true, .ndisc_use_dns = true, .ndisc_use_gateway = true, .ndisc_use_captive_portal = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 723091fc8a..eacf3a3dd6 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -337,6 +337,7 @@ struct Network { /* NDisc support */ int ndisc; + bool ndisc_use_redirect; bool ndisc_use_dns; bool ndisc_use_gateway; bool ndisc_use_route_prefix; |