All of lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH 04/17] netconfig: Create routes from Router Advertisements
@ 2022-05-18 18:42 Denis Kenzior
  0 siblings, 0 replies; 3+ messages in thread
From: Denis Kenzior @ 2022-05-18 18:42 UTC (permalink / raw)
  To: ell

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

Hi Andrew,

On 5/13/22 17:47, Andrew Zaborowski wrote:
> 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(-)
> 

<snip>

> +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;
> +

So I think you meant v6_gateway_override here and I fixed this up accordingly. 
Send a fix if this wasn't correct.

Patches 1-4 applied, thanks.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 04/17] netconfig: Create routes from Router Advertisements
@ 2022-05-13 22:47 Andrew Zaborowski
  0 siblings, 0 replies; 3+ messages in thread
From: Andrew Zaborowski @ 2022-05-13 22:47 UTC (permalink / raw)
  To: ell

[-- 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

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 04/17] netconfig: Create routes from Router Advertisements
@ 2022-05-13 14:54 Andrew Zaborowski
  0 siblings, 0 replies; 3+ messages in thread
From: Andrew Zaborowski @ 2022-05-13 14:54 UTC (permalink / raw)
  To: ell

[-- Attachment #1: Type: text/plain, Size: 14010 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 | 294 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 278 insertions(+), 16 deletions(-)

diff --git a/ell/netconfig.c b/ell/netconfig.c
index 9ede190..243ecd8 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"
@@ -63,6 +68,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;
@@ -127,6 +134,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)
@@ -140,12 +174,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);
 
@@ -162,6 +193,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);
 }
@@ -171,7 +203,6 @@ static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
 						uint8_t prefix_len)
 {
 	struct in6_addr in6_addr;
-	char network[INET6_ADDRSTRLEN];
 	struct l_rtnl_route *v6_subnet_route;
 	struct l_rtnl_route *v6_default_route;
 
@@ -183,18 +214,13 @@ static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
 	/* Zero out host address bits to produce network address */
 	l_net_mask_prefix(in6_addr.s6_addr, prefix_len, 16);
 
-	if (L_WARN_ON(!inet_ntop(AF_INET6, &in6_addr, 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, &in6_addr,
+						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);
 
@@ -210,6 +236,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 =
@@ -356,6 +395,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;
@@ -378,6 +615,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;
 }
 
@@ -398,6 +642,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);
@@ -731,19 +976,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)
@@ -767,6 +1028,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

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-05-18 18:42 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-18 18:42 [PATCH 04/17] netconfig: Create routes from Router Advertisements Denis Kenzior
  -- strict thread matches above, loose matches on Subject: below --
2022-05-13 22:47 Andrew Zaborowski
2022-05-13 14:54 Andrew Zaborowski

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.