]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network/ndisc: add basic support for Redirect message
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 23 Feb 2024 03:03:26 +0000 (12:03 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 4 Apr 2024 20:57:54 +0000 (05:57 +0900)
Closes #31438.

man/systemd.network.xml
src/network/networkd-link.h
src/network/networkd-ndisc.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h

index 4f0510030a7dccad4260b711603440fef3d14d82..3c887b01059a1c5ae58d4d57a95e4789d4047d6b 100644 (file)
@@ -3191,6 +3191,16 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
     with the <varname>IPv6AcceptRA=</varname> setting described above:</para>
 
     <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>UseRedirect=</varname></term>
+        <listitem>
+          <para>When true (the default), Redirect message sent by the current first-hop router will be
+          accepted, and configures routes to redirected nodes will be configured.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Token=</varname></term>
         <listitem>
index 01931f6773e923ede2406901d22d953fbdf86cbe..9d15b4acce915266a6dca71858279c6020a97aee 100644 (file)
@@ -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;
 
index 6be0c0f9d9b1309ad6ab9ea7d6a2593bfa5826c0..8956c7438abd153bd6a28589321fb0baba888503 100644 (file)
@@ -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, &current_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] = {
index 9b37000bffe5902839e5e1f6a7c2dc24484a5e5b..2f9855465fe5b63db1701eef34eb6877cfa3eb5a 100644 (file)
@@ -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)
index eaa3676ede8efcfd33afa8d49ab93e07a117c1d2..880856f596d91a261a1dada57ae5744ae5e39079 100644 (file)
@@ -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,
index 723091fc8ae13974cad07c31253f9396d12f0d88..eacf3a3dd614ead0be852e013cc41d34d6821e22 100644 (file)
@@ -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;