All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrew Zaborowski <andrew.zaborowski at intel.com>
To: ell at lists.01.org
Subject: [PATCH 04/17] netconfig: Create routes from Router Advertisements
Date: Sat, 14 May 2022 00:47:00 +0200	[thread overview]
Message-ID: <20220513224713.1447773-4-andrew.zaborowski@intel.com> (raw)
In-Reply-To: 20220513224713.1447773-1-andrew.zaborowski@intel.com

[-- Attachment #1: Type: text/plain, Size: 13944 bytes --]

If IPv6 is enabled, create the ICMP6 client and handle Router
Advertisements received by creating, updating and removing routes per
the data in the Router Advertisements.  Timeouts aren't handled yet.
---
 ell/netconfig.c | 293 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 278 insertions(+), 15 deletions(-)

diff --git a/ell/netconfig.c b/ell/netconfig.c
index 9375d13..6ad6394 100644
--- a/ell/netconfig.c
+++ b/ell/netconfig.c
@@ -27,14 +27,19 @@
 #include <linux/types.h>
 #include <netinet/ip.h>
 #include <arpa/inet.h>
+#include <netinet/icmp6.h>
 
 #include "private.h"
 #include "useful.h"
 #include "log.h"
 #include "dhcp.h"
 #include "dhcp-private.h"
+#include "icmp6.h"
+#include "icmp6-private.h"
+#include "dhcp6.h"
 #include "netlink.h"
 #include "rtnl.h"
+#include "rtnl-private.h"
 #include "queue.h"
 #include "time.h"
 #include "idle.h"
@@ -64,6 +69,8 @@ struct l_netconfig {
 	bool v4_configured;
 	struct l_dhcp_client *dhcp_client;
 	bool v6_configured;
+	struct l_icmp6_client *icmp6_client;
+	struct l_dhcp6_client *dhcp6_client;
 
 	/* These objects, if not NULL, are owned by @addresses and @routes */
 	struct l_rtnl_address *v4_address;
@@ -128,6 +135,33 @@ static void netconfig_emit_event(struct l_netconfig *nc, uint8_t family,
 		netconfig_update_cleanup(nc);
 }
 
+static struct l_rtnl_route *netconfig_route_new(struct l_netconfig *nc,
+						uint8_t family,
+						const void *dst,
+						uint8_t prefix_len,
+						const void *gw,
+						uint8_t protocol)
+{
+	struct l_rtnl_route *rt = l_new(struct l_rtnl_route, 1);
+
+	rt->family = family;
+	rt->scope = (family == AF_INET && dst) ?
+		RT_SCOPE_LINK : RT_SCOPE_UNIVERSE;
+	rt->protocol = protocol;
+	rt->lifetime = 0xffffffff;
+	rt->priority = nc->route_priority;
+
+	if (dst) {
+		memcpy(&rt->dst, dst, family == AF_INET ? 4 : 16);
+		rt->dst_prefix_len = prefix_len;
+	}
+
+	if (gw)
+		memcpy(&rt->gw, gw, family == AF_INET ? 4 : 16);
+
+	return rt;
+}
+
 static void netconfig_add_v4_routes(struct l_netconfig *nc, const char *ip,
 					uint8_t prefix_len, const char *gateway,
 					uint8_t rtm_protocol)
@@ -141,12 +175,9 @@ static void netconfig_add_v4_routes(struct l_netconfig *nc, const char *ip,
 		return;
 
 	in_addr.s_addr &= htonl(0xfffffffflu << (32 - prefix_len));
-	if (L_WARN_ON(!inet_ntop(AF_INET, &in_addr, network, INET_ADDRSTRLEN)))
-		return;
-
-	nc->v4_subnet_route = l_rtnl_route_new_prefix(network, prefix_len);
-	l_rtnl_route_set_protocol(nc->v4_subnet_route, rtm_protocol);
-	l_rtnl_route_set_priority(nc->v4_subnet_route, nc->route_priority);
+	nc->v4_subnet_route = netconfig_route_new(nc, AF_INET, network,
+							prefix_len, NULL,
+							rtm_protocol);
 	l_queue_push_tail(nc->routes.current, nc->v4_subnet_route);
 	l_queue_push_tail(nc->routes.added, nc->v4_subnet_route);
 
@@ -163,6 +194,7 @@ static void netconfig_add_v4_routes(struct l_netconfig *nc, const char *ip,
 	nc->v4_default_route = l_rtnl_route_new_gateway(gateway);
 	l_rtnl_route_set_protocol(nc->v4_default_route, rtm_protocol);
 	L_WARN_ON(!l_rtnl_route_set_prefsrc(nc->v4_default_route, ip));
+	l_rtnl_route_set_priority(nc->v4_default_route, nc->route_priority);
 	l_queue_push_tail(nc->routes.current, nc->v4_default_route);
 	l_queue_push_tail(nc->routes.added, nc->v4_default_route);
 }
@@ -173,7 +205,6 @@ static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
 {
 	struct in6_addr in6_addr;
 	const void *prefix;
-	char network[INET6_ADDRSTRLEN];
 	struct l_rtnl_route *v6_subnet_route;
 	struct l_rtnl_route *v6_default_route;
 
@@ -188,17 +219,13 @@ static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
 	 */
 	prefix = net_prefix_from_ipv6(in6_addr.s6_addr, prefix_len);
 
-	if (L_WARN_ON(!inet_ntop(AF_INET6, prefix, network, INET6_ADDRSTRLEN)))
-		return;
-
 	/*
 	 * One reason we add a subnet route instead of letting the kernel
 	 * do it, by not specifying IFA_F_NOPREFIXROUTE for the address,
 	 * is that that would force a 0 metric for the route.
 	 */
-	v6_subnet_route = l_rtnl_route_new_prefix(network, prefix_len);
-	l_rtnl_route_set_protocol(v6_subnet_route, RTPROT_STATIC);
-	l_rtnl_route_set_priority(v6_subnet_route, nc->route_priority);
+	v6_subnet_route = netconfig_route_new(nc, AF_INET6, prefix, prefix_len,
+						NULL, RTPROT_STATIC);
 	l_queue_push_tail(nc->routes.current, v6_subnet_route);
 	l_queue_push_tail(nc->routes.added, v6_subnet_route);
 
@@ -214,6 +241,19 @@ static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
 	l_queue_push_tail(nc->routes.added, v6_default_route);
 }
 
+static bool netconfig_route_exists(struct l_queue *list,
+					const struct l_rtnl_route *route)
+{
+	const struct l_queue_entry *entry;
+
+	for (entry = l_queue_get_entries(list); entry;
+			entry = entry->next)
+		if ((const struct l_rtnl_route *) entry->data == route)
+			return true;
+
+	return false;
+}
+
 static void netconfig_add_dhcp_address_routes(struct l_netconfig *nc)
 {
 	const struct l_dhcp_lease *lease =
@@ -360,6 +400,204 @@ static void netconfig_dhcp_event_handler(struct l_dhcp_client *client,
 	}
 }
 
+static struct l_rtnl_route *netconfig_find_icmp6_route(
+						struct l_netconfig *nc,
+						const uint8_t *gateway,
+						const struct route_info *dst)
+{
+	const struct l_queue_entry *entry;
+
+	for (entry = l_queue_get_entries(nc->routes.current); entry;
+			entry = entry->next) {
+		struct l_rtnl_route *route = entry->data;
+		const uint8_t *route_gateway;
+		const uint8_t *route_dst;
+		uint8_t route_prefix_len = 0;
+
+		if (l_rtnl_route_get_family(route) != AF_INET6 ||
+				l_rtnl_route_get_protocol(route) != RTPROT_RA)
+			continue;
+
+		route_gateway = l_rtnl_route_get_gateway_in_addr(route);
+		if ((gateway || route_gateway) &&
+				(!gateway || !route_gateway ||
+				 memcmp(gateway, route_gateway, 16)))
+			continue;
+
+		route_dst = l_rtnl_route_get_dst_in_addr(route,
+							&route_prefix_len);
+		if ((dst || route_prefix_len) &&
+				(!dst || !route_prefix_len ||
+				 dst->prefix_len != route_prefix_len ||
+				 memcmp(dst->address, route_dst, 16)))
+			continue;
+
+		return route;
+	}
+
+	return NULL;
+}
+
+static struct l_rtnl_route *netconfig_add_icmp6_route(struct l_netconfig *nc,
+						const uint8_t *gateway,
+						const struct route_info *dst,
+						uint8_t preference)
+{
+	struct l_rtnl_route *rt;
+
+	rt = netconfig_route_new(nc, AF_INET6, dst->address, dst->prefix_len,
+					gateway, RTPROT_RA);
+	if (L_WARN_ON(!rt))
+		return NULL;
+
+	l_rtnl_route_set_preference(rt, preference);
+	l_queue_push_tail(nc->routes.current, rt);
+	l_queue_push_tail(nc->routes.added, rt);
+	return rt;
+}
+
+static void netconfig_set_icmp6_route_data(struct l_netconfig *nc,
+						struct l_rtnl_route *rt,
+						uint64_t start_time,
+						uint32_t preferred_lifetime,
+						uint32_t valid_lifetime,
+						uint32_t mtu, bool updated)
+{
+	uint64_t expiry = start_time + valid_lifetime * L_USEC_PER_SEC;
+	uint64_t old_expiry = l_rtnl_route_get_expiry(rt);
+	bool differs = false;
+
+	if (mtu != l_rtnl_route_get_mtu(rt)) {
+		l_rtnl_route_set_mtu(rt, mtu);
+		differs = true;
+	}
+
+	/*
+	 * valid_lifetime of 0 from a route_info means the route is being
+	 * removed so we wouldn't be here.  valid_lifetime of 0xffffffff
+	 * means no timeout.  Check if the lifetime is changing between
+	 * finite and infinite, or two finite values that result in expiry
+	 * time difference of more than a second -- to avoid emitting
+	 * updates for changes resulting only from the valid_lifetime one
+	 * second resolution and RA transmission jitter.  As RFC4861
+	 * Section 6.2.7 puts it: "Due to link propagation delays and
+	 * potentially poorly synchronized clocks between the routers such
+	 * comparison SHOULD allow some time skew."  The RFC talks about
+	 * routers processing one another's RAs but the same logic applies
+	 * here.
+	 */
+	if (valid_lifetime == 0xffffffff)
+		expiry = 0;
+
+	if ((expiry || old_expiry) &&
+			(!expiry || !old_expiry ||
+			 l_time_diff(expiry, old_expiry) > L_USEC_PER_SEC)) {
+		l_rtnl_route_set_lifetime(rt, valid_lifetime);
+		l_rtnl_route_set_expiry(rt, expiry);
+		differs = true;
+	}
+
+	if (updated && differs && !netconfig_route_exists(nc->routes.added, rt))
+		l_queue_push_tail(nc->routes.updated, rt);
+}
+
+static void netconfig_remove_icmp6_route(struct l_netconfig *nc,
+						struct l_rtnl_route *route)
+{
+	l_queue_remove(nc->routes.current, route);
+	l_queue_push_tail(nc->routes.removed, route);
+}
+
+static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
+						enum l_icmp6_client_event event,
+						void *event_data,
+						void *user_data)
+{
+	struct l_netconfig *nc = user_data;
+	const struct l_icmp6_router *r;
+	struct l_rtnl_route *default_route;
+	unsigned int i;
+
+	if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND)
+		return;
+
+	r = event_data;
+
+	/*
+	 * Note: If this is the first RA received, the l_dhcp6_client
+	 * will have received the event before us and will be acting
+	 * on it by now.
+	 */
+
+	if (nc->v4_gateway_override)
+		return;
+
+	/* Process the default gateway information */
+	default_route = netconfig_find_icmp6_route(nc, r->address, NULL);
+
+	if (!default_route && r->lifetime) {
+		default_route = netconfig_add_icmp6_route(nc, r->address, NULL,
+								r->pref);
+		if (unlikely(!default_route))
+			return;
+
+		/*
+		 * r->lifetime is 16-bit only so there's no risk it gets
+		 * confused for the special 0xffffffff value in
+		 * netconfig_set_icmp6_route_data.
+		 */
+		netconfig_set_icmp6_route_data(nc, default_route, r->start_time,
+						r->lifetime, r->lifetime,
+						r->mtu, false);
+	} else if (default_route && r->lifetime)
+		netconfig_set_icmp6_route_data(nc, default_route, r->start_time,
+						r->lifetime, r->lifetime,
+						r->mtu, true);
+	else if (default_route && !r->lifetime)
+		netconfig_remove_icmp6_route(nc, default_route);
+
+	/*
+	 * Process the onlink and offlink routes, from the Router
+	 * Advertisement's Prefix Information options and Route
+	 * Information options respectively.
+	 */
+	for (i = 0; i < r->n_routes; i++) {
+		const struct route_info *info = &r->routes[i];
+		const uint8_t *gateway = info->onlink ? NULL : r->address;
+		struct l_rtnl_route *route =
+			netconfig_find_icmp6_route(nc, gateway, info);
+
+		if (!route && info->valid_lifetime) {
+			route = netconfig_add_icmp6_route(nc, gateway, info,
+							info->preference);
+			if (unlikely(!route))
+				continue;
+
+			netconfig_set_icmp6_route_data(nc, route, r->start_time,
+						info->preferred_lifetime,
+						info->valid_lifetime,
+						gateway ? r->mtu : 0, false);
+		} else if (route && info->valid_lifetime)
+			netconfig_set_icmp6_route_data(nc, route, r->start_time,
+						info->preferred_lifetime,
+						info->valid_lifetime,
+						gateway ? r->mtu : 0, true);
+		else if (route && !info->valid_lifetime)
+			netconfig_remove_icmp6_route(nc, route);
+	}
+
+	/*
+	 * Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE.
+	 * We should probably instead save the affected routes in separate
+	 * lists and add them to the _CONFIGURE event, suppressing any _UPDATE
+	 * events while nc->v6_configured is false.
+	 */
+	if (!l_queue_isempty(nc->routes.added) ||
+			!l_queue_isempty(nc->routes.updated) ||
+			!l_queue_isempty(nc->routes.removed))
+		netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_UPDATE);
+}
+
 LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex)
 {
 	struct l_netconfig *nc;
@@ -382,6 +620,13 @@ LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex)
 					netconfig_dhcp_event_handler,
 					nc, NULL);
 
+	nc->dhcp6_client = l_dhcp6_client_new(ifindex);
+
+	nc->icmp6_client = l_dhcp6_client_get_icmp6(nc->dhcp6_client);
+	l_icmp6_client_add_event_handler(nc->icmp6_client,
+					netconfig_icmp6_event_handler,
+					nc, NULL);
+
 	return nc;
 }
 
@@ -402,6 +647,7 @@ LIB_EXPORT void l_netconfig_destroy(struct l_netconfig *netconfig)
 	l_netconfig_set_domain_names_override(netconfig, AF_INET6, NULL);
 
 	l_dhcp_client_destroy(netconfig->dhcp_client);
+	l_dhcp6_client_destroy(netconfig->dhcp6_client);
 	l_netconfig_set_event_handler(netconfig, NULL, NULL, NULL);
 	l_queue_destroy(netconfig->addresses.current, NULL);
 	l_queue_destroy(netconfig->addresses.added, NULL);
@@ -735,19 +981,35 @@ configure_ipv6:
 	if (!netconfig->v6_enabled)
 		goto done;
 
-	if (netconfig->v6_static_addr && !netconfig->do_static_work) {
+	if (netconfig->v6_static_addr) {
 		/*
 		 * We're basically ready to configure the interface
 		 * but do this in an idle callback.
 		 */
-		netconfig->do_static_work = l_idle_create(
+		if (!netconfig->do_static_work)
+			netconfig->do_static_work = l_idle_create(
 						netconfig_do_static_config,
 						netconfig, NULL);
+
+		goto done;
 	}
 
+	if (!l_dhcp6_client_start(netconfig->dhcp6_client))
+		goto stop_ipv4;
+
 done:
 	netconfig->started = true;
 	return true;
+
+stop_ipv4:
+	if (netconfig->v4_enabled) {
+		if (netconfig->v4_static_addr)
+			l_idle_remove(l_steal_ptr(netconfig->do_static_work));
+		else
+			l_dhcp_client_stop(netconfig->dhcp_client);
+	}
+
+	return false;
 }
 
 LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig)
@@ -771,6 +1033,7 @@ LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig)
 	netconfig->v6_address = NULL;
 
 	l_dhcp_client_stop(netconfig->dhcp_client);
+	l_dhcp6_client_stop(netconfig->dhcp6_client);
 }
 
 LIB_EXPORT struct l_dhcp_client *l_netconfig_get_dhcp_client(
-- 
2.32.0

             reply	other threads:[~2022-05-13 22:47 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-13 22:47 Andrew Zaborowski [this message]
  -- strict thread matches above, loose matches on Subject: below --
2022-05-18 18:42 [PATCH 04/17] netconfig: Create routes from Router Advertisements Denis Kenzior
2022-05-13 14:54 Andrew Zaborowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220513224713.1447773-4-andrew.zaborowski@intel.com \
    --to=unknown@example.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.