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;
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;
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);
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)
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
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)
}
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) {
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] = {