public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH] ndp.c: Turn NDP responder into more declarative implementation
@ 2024-08-10 16:00 AbdAlRahman Gad
  2024-08-12 15:44 ` Stefano Brivio
  0 siblings, 1 reply; 2+ messages in thread
From: AbdAlRahman Gad @ 2024-08-10 16:00 UTC (permalink / raw)
  To: passt-dev; +Cc: AbdAlRahman Gad

- Add structs for NA, RA, NS, MTU, prefix info, option header,
  link-layer address, RDNSS, DNSSL and link-layer for RA message.

- Turn NA message from purely imperative, going byte by byte,
  to declarative by filling it's struct.

- Turn part of RA message into declarative.

- Move packet_add() to be before the call of ndp() in tap6_handler()
  if the protocol of the packet  is ICMPv6.

- Add a pool of packets as an additional parameter to ndp().

- Check the size of NS packet with packet_get() before sending an NA
  packet.

- Add documentation for the structs.

- Add an enum for NDP option types.

Link: https://bugs.passt.top/show_bug.cgi?id=21

Signed-off-by: AbdAlRahman Gad <abdobngad@gmail.com>
---
 ndp.c | 320 +++++++++++++++++++++++++++++++++++++++++++---------------
 ndp.h |   3 +-
 tap.c |   5 +-
 3 files changed, 246 insertions(+), 82 deletions(-)

diff --git a/ndp.c b/ndp.c
index cea3df5..5e9cb78 100644
--- a/ndp.c
+++ b/ndp.c
@@ -38,22 +38,199 @@
 #define NS	135
 #define NA	136
 
+enum ndp_option_types {
+	OPT_SRC_L2_ADDR		= 1,
+	OPT_TARGET_L2_ADDR	= 2,
+	OPT_PREFIX_INFO		= 3,
+	OPT_MTU			= 5,
+	OPT_RDNSS_TYPE		= 25,
+	OPT_DNSSL_TYPE		= 31,
+};
+
+/**
+ * struct opt_header - Option header
+ * @type:	Option type
+ * @len:	Option length, in units of 8 bytes
+*/
+struct opt_header {
+	uint8_t type;
+	uint8_t len;
+} __attribute__((packed));
+
+/**
+ * struct opt_l2_addr - Link-layer address
+ * @header:	Option header
+ * @mac:	MAC address
+ */
+struct opt_l2_addr {
+	struct opt_header header;
+	unsigned char mac[ETH_ALEN];
+} __attribute__((packed));
+
+/**
+ * struct ndp_na - NDP Neighbor Advertisement (NA) message
+ * @ih:			ICMPv6 header
+ * @target_addr:	Target IPv6 address
+ * @target_l2_addr:	Target link-layer address
+ */
+struct ndp_na {
+	struct icmp6hdr ih;
+	struct in6_addr target_addr;
+	struct opt_l2_addr target_l2_addr;
+} __attribute__((packed));
+
+/**
+ * struct opt_prefix_info - Prefix Information option
+ * @header:		Option header
+ * @prefix_len:		The number of leading bits in the Prefix that are valid
+ * @prefix_flags:	Flags associated with the prefix
+ * @valid_lifetime:	Valid lifetime (ms)
+ * @pref_lifetime:	Preferred lifetime (ms)
+ * @reserved:		Unused
+ */
+struct opt_prefix_info {
+	struct opt_header header;
+	uint8_t prefix_len;
+	uint8_t prefix_flags;
+	uint32_t valid_lifetime;
+	uint32_t pref_lifetime;
+	uint32_t reserved;
+} __attribute__((packed));
+
+/**
+ * struct mtu_opt - Maximum transmission unit (MTU) option
+ * @header:		Option header
+ * @reserved:		Unused
+ * @value:		MTU value, network order
+ */
+struct mtu_opt {
+	struct opt_header header;
+	uint16_t reserved;
+	uint32_t value;
+} __attribute__((packed));
+
+/**
+ * struct rdnss - Recursive DNS Server (RDNSS) option
+ * @header:	Option header
+ * @reserved:	Unused
+ * @lifetime:	Validity time (s)
+ * @dns:	List of DNS server addresses
+ */
+struct opt_rdnss {
+	struct opt_header header;
+	uint16_t reserved;
+	uint32_t lifetime;
+	struct in6_addr dns[MAXNS + 1];
+} __attribute__((packed));
+
+/**
+ * struct dnssl - DNS Search List (DNSSL) option
+ * @header:		Option header
+ * @reserved:		Unused
+ * @lifetime:		Validity time (s)
+ * @domains:		List of NULL-seperated search domains
+ */
+struct opt_dnssl {
+	struct opt_header header;
+	uint16_t reserved;
+	uint32_t lifetime;
+	unsigned char domains[MAXDNSRCH * NS_MAXDNAME];
+} __attribute__((packed));
+
+/**
+ * struct ndp_ra - NDP Router Advertisement (RA) message
+ * @ih:			ICMPv6 header
+ * @reachable:		Reachability time, after confirmation (ms)
+ * @retrans:		Time between retransmitted NS messages (ms)
+ * @prefix_info:	Prefix Information option
+ * @prefix:		IPv6 prefix
+ * @mtu:		MTU option
+ * @source_ll:		Target link-layer address
+ * @dns:		RDNSS and DNSSL options
+ */
+struct ndp_ra {
+	struct icmp6hdr ih;
+	uint32_t reachable;
+	uint32_t retrans;
+	struct opt_prefix_info prefix_info;
+	struct in6_addr prefix;
+	struct mtu_opt mtu;
+	struct opt_l2_addr source_ll;
+
+	unsigned char dns[sizeof(struct opt_rdnss) + sizeof(struct opt_dnssl)];
+} __attribute__((packed));
+
+/**
+ * struct ndp_ns - NDP Neighbor Solicitation (NS) message
+ * @ih:		 ICMPv6 header
+ * @target_addr: Target IPv6 address
+ */
+struct ndp_ns {
+	struct icmp6hdr ih;
+	struct in6_addr target_addr;
+} __attribute__((packed));
+
 /**
  * ndp() - Check for NDP solicitations, reply as needed
  * @c:		Execution context
  * @ih:		ICMPv6 header
- * @saddr	Source IPv6 address
+ * @saddr:	Source IPv6 address
+ * @p:		Packet pool
  *
  * Return: 0 if not handled here, 1 if handled, -1 on failure
  */
-int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
+int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
+	const struct pool *p)
 {
+	struct ndp_na na = {
+		.ih = {
+			.icmp6_type		= NA,
+			.icmp6_code		= 0,
+			.icmp6_router		= 1,
+			.icmp6_solicited	= 1,
+			.icmp6_override		= 1,
+		},
+		.target_l2_addr = {
+			.header	= {
+				.type		= OPT_TARGET_L2_ADDR,
+				.len		= 1,
+			},
+		}
+	};
+	struct ndp_ra ra = {
+		.ih = {
+			.icmp6_type		= RA,
+			.icmp6_code		= 0,
+			.icmp6_hop_limit	= 255,
+			/* RFC 8319 */
+			.icmp6_rt_lifetime	= htons_constant(65535),
+			.icmp6_addrconf_managed	= 1
+		},
+		.prefix_info = {
+			.header = {
+				.type		= OPT_PREFIX_INFO,
+				.len		= 4,
+			},
+			.prefix_len		= 64,
+			.prefix_flags		= 0xc0,	/* prefix flags: L, A */
+			.valid_lifetime		= ~0U,
+			.pref_lifetime		= ~0U,
+		},
+		.mtu = {
+			.header	= {
+				.type		= OPT_MTU,
+				.len		= 1,
+			},
+		},
+		.source_ll = {
+			.header = {
+				.type		= OPT_SRC_L2_ADDR,
+				.len		= 1,
+			},
+		},
+	};
 	const struct in6_addr *rsaddr; /* src addr for reply */
-	char buf[BUFSIZ] = { 0 };
-	struct ipv6hdr *ip6hr;
-	struct icmp6hdr *ihr;
-	struct ethhdr *ehr;
-	unsigned char *p;
+	unsigned char *ptr = NULL;
 	size_t dlen;
 
 	if (ih->icmp6_type < RS || ih->icmp6_type > NA)
@@ -62,28 +239,22 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
 	if (c->no_ndp)
 		return 1;
 
-	ehr = (struct ethhdr *)buf;
-	ip6hr = (struct ipv6hdr *)(ehr + 1);
-	ihr = (struct icmp6hdr *)(ip6hr + 1);
-
 	if (ih->icmp6_type == NS) {
+		struct ndp_ns *ns = packet_get(p, 0, 0, sizeof(struct ndp_ns),
+					       NULL);
+
+		if (!ns)
+			return -1;
+
 		if (IN6_IS_ADDR_UNSPECIFIED(saddr))
 			return 1;
 
 		info("NDP: received NS, sending NA");
-		ihr->icmp6_type = NA;
-		ihr->icmp6_code = 0;
-		ihr->icmp6_router = 1;
-		ihr->icmp6_solicited = 1;
-		ihr->icmp6_override = 1;
-
-		p = (unsigned char *)(ihr + 1);
-		memcpy(p, ih + 1, sizeof(struct in6_addr)); /* target address */
-		p += 16;
-		*p++ = 2;				    /* target ll */
-		*p++ = 1;				    /* length */
-		memcpy(p, c->mac, ETH_ALEN);
-		p += 6;
+
+		memcpy(&na.target_addr, &ns->target_addr,
+		       sizeof(na.target_addr));
+		memcpy(na.target_l2_addr.mac, c->mac, ETH_ALEN);
+
 	} else if (ih->icmp6_type == RS) {
 		size_t dns_s_len = 0;
 		int i, n;
@@ -92,91 +263,76 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
 			return 1;
 
 		info("NDP: received RS, sending RA");
-		ihr->icmp6_type = RA;
-		ihr->icmp6_code = 0;
-		ihr->icmp6_hop_limit = 255;
-		ihr->icmp6_rt_lifetime = htons(65535); /* RFC 8319 */
-		ihr->icmp6_addrconf_managed = 1;
-
-		p = (unsigned char *)(ihr + 1);
-		p += 8;				/* reachable, retrans time */
-		*p++ = 3;			/* prefix */
-		*p++ = 4;			/* length */
-		*p++ = 64;			/* prefix length */
-		*p++ = 0xc0;			/* prefix flags: L, A */
-		*(uint32_t *)p = (uint32_t)~0U;	/* lifetime */
-		p += 4;
-		*(uint32_t *)p = (uint32_t)~0U;	/* preferred lifetime */
-		p += 8;
-		memcpy(p, &c->ip6.addr, 8);	/* prefix */
-		p += 16;
-
-		if (c->mtu != -1) {
-			*p++ = 5;			/* type */
-			*p++ = 1;			/* length */
-			p += 2;				/* reserved */
-			*(uint32_t *)p = htonl(c->mtu);	/* MTU */
-			p += 4;
-		}
+		memcpy(&ra.prefix, &c->ip6.addr, sizeof(ra.prefix));
+
+		if (c->mtu != -1)
+			ra.mtu.value = htonl(c->mtu);
+
+		ptr = &ra.dns[0];
 
 		if (c->no_dhcp_dns)
 			goto dns_done;
 
 		for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n]); n++);
 		if (n) {
-			*p++ = 25;				/* RDNSS */
-			*p++ = 1 + 2 * n;			/* length */
-			p += 2;					/* reserved */
-			*(uint32_t *)p = (uint32_t)~0U;		/* lifetime */
-			p += 4;
-
+			struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr;
+			*rdnss = (struct opt_rdnss) {
+				.header = {
+					.type		= OPT_RDNSS_TYPE,
+					.len		= 1 + 2 * n,
+				},
+				.lifetime		= ~0U,
+			};
 			for (i = 0; i < n; i++) {
-				memcpy(p, &c->ip6.dns[i], 16);	/* address */
-				p += 16;
+				memcpy(&rdnss->dns[i], &c->ip6.dns[i],
+				       sizeof(rdnss->dns[i]));
 			}
+			ptr += offsetof(struct opt_rdnss, dns) +
+				   i * sizeof(rdnss->dns[0]);
 
 			for (n = 0; *c->dns_search[n].n; n++)
 				dns_s_len += strlen(c->dns_search[n].n) + 2;
 		}
 
 		if (!c->no_dhcp_dns_search && dns_s_len) {
-			*p++ = 31;				/* DNSSL */
-			*p++ = (dns_s_len + 8 - 1) / 8 + 1;	/* length */
-			p += 2;					/* reserved */
-			*(uint32_t *)p = (uint32_t)~0U;		/* lifetime */
-			p += 4;
+			struct opt_dnssl *dnssl = (struct opt_dnssl *)ptr;
+			*dnssl = (struct opt_dnssl) {
+				.header = {
+					.type = OPT_DNSSL_TYPE,
+					.len  = DIV_ROUND_UP(dns_s_len, 8) + 1,
+				},
+				.lifetime     = ~0U,
+			};
+			ptr = dnssl->domains;
 
 			for (i = 0; i < n; i++) {
+				size_t len;
 				char *dot;
 
-				*(p++) = '.';
+				*(ptr++) = '.';
 
-				strncpy((char *)p, c->dns_search[i].n,
-					sizeof(buf) -
-					((intptr_t)p - (intptr_t)buf));
-				for (dot = (char *)p - 1; *dot; dot++) {
+				len = sizeof(dnssl->domains) -
+				      (ptr - dnssl->domains);
+
+				strncpy((char *)ptr, c->dns_search[i].n, len);
+				for (dot = (char *)ptr - 1; *dot; dot++) {
 					if (*dot == '.')
 						*dot = strcspn(dot + 1, ".");
 				}
-				p += strlen(c->dns_search[i].n);
-				*(p++) = 0;
+				ptr += strlen(c->dns_search[i].n);
+				*(ptr++) = 0;
 			}
 
-			memset(p, 0, 8 - dns_s_len % 8);	/* padding */
-			p += 8 - dns_s_len % 8;
+			memset(ptr, 0, 8 - dns_s_len % 8);	/* padding */
+			ptr += 8 - dns_s_len % 8;
 		}
 
 dns_done:
-		*p++ = 1;			/* source ll */
-		*p++ = 1;			/* length */
-		memcpy(p, c->mac, ETH_ALEN);
-		p += 6;
+		memcpy(&ra.source_ll.mac, c->mac, ETH_ALEN);
 	} else {
 		return 1;
 	}
 
-	dlen = (uintptr_t)p - (uintptr_t)ihr - sizeof(*ihr);
-
 	if (IN6_IS_ADDR_LINKLOCAL(saddr))
 		c->ip6.addr_ll_seen = *saddr;
 	else
@@ -187,7 +343,13 @@ dns_done:
 	else
 		rsaddr = &c->ip6.addr_ll;
 
-	tap_icmp6_send(c, rsaddr, saddr, ihr, dlen + sizeof(*ihr));
+	if (ih->icmp6_type == NS) {
+		dlen = sizeof(struct ndp_na);
+		tap_icmp6_send(c, rsaddr, saddr, &na, dlen);
+	} else if (ih->icmp6_type == RS) {
+		dlen = ptr - (unsigned char *)&ra;
+		tap_icmp6_send(c, rsaddr, saddr, &ra, dlen);
+	}
 
 	return 1;
 }
diff --git a/ndp.h b/ndp.h
index b33cd69..a786441 100644
--- a/ndp.h
+++ b/ndp.h
@@ -6,6 +6,7 @@
 #ifndef NDP_H
 #define NDP_H
 
-int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr);
+int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
+	const struct pool *p);
 
 #endif /* NDP_H */
diff --git a/tap.c b/tap.c
index 5852705..87be3a6 100644
--- a/tap.c
+++ b/tap.c
@@ -808,12 +808,13 @@ resume:
 			if (l4len < sizeof(struct icmp6hdr))
 				continue;
 
-			if (ndp(c, (struct icmp6hdr *)l4h, saddr))
+			packet_add(pkt, l4len, l4h);
+
+			if (ndp(c, (struct icmp6hdr *)l4h, saddr, pkt))
 				continue;
 
 			tap_packet_debug(NULL, ip6h, NULL, proto, NULL, 1);
 
-			packet_add(pkt, l4len, l4h);
 			icmp_tap_handler(c, PIF_TAP, AF_INET6,
 					 saddr, daddr, pkt, now);
 			continue;
-- 
@@ -808,12 +808,13 @@ resume:
 			if (l4len < sizeof(struct icmp6hdr))
 				continue;
 
-			if (ndp(c, (struct icmp6hdr *)l4h, saddr))
+			packet_add(pkt, l4len, l4h);
+
+			if (ndp(c, (struct icmp6hdr *)l4h, saddr, pkt))
 				continue;
 
 			tap_packet_debug(NULL, ip6h, NULL, proto, NULL, 1);
 
-			packet_add(pkt, l4len, l4h);
 			icmp_tap_handler(c, PIF_TAP, AF_INET6,
 					 saddr, daddr, pkt, now);
 			continue;
-- 
2.46.0


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

* Re: [PATCH] ndp.c: Turn NDP responder into more declarative implementation
  2024-08-10 16:00 [PATCH] ndp.c: Turn NDP responder into more declarative implementation AbdAlRahman Gad
@ 2024-08-12 15:44 ` Stefano Brivio
  0 siblings, 0 replies; 2+ messages in thread
From: Stefano Brivio @ 2024-08-12 15:44 UTC (permalink / raw)
  To: AbdAlRahman Gad; +Cc: passt-dev

On Sat, 10 Aug 2024 19:00:12 +0300
AbdAlRahman Gad <abdobngad@gmail.com> wrote:

> - Add structs for NA, RA, NS, MTU, prefix info, option header,
>   link-layer address, RDNSS, DNSSL and link-layer for RA message.
> 
> - Turn NA message from purely imperative, going byte by byte,
>   to declarative by filling it's struct.
> 
> - Turn part of RA message into declarative.
> 
> - Move packet_add() to be before the call of ndp() in tap6_handler()
>   if the protocol of the packet  is ICMPv6.
> 
> - Add a pool of packets as an additional parameter to ndp().
> 
> - Check the size of NS packet with packet_get() before sending an NA
>   packet.
> 
> - Add documentation for the structs.
> 
> - Add an enum for NDP option types.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=21
> 
> Signed-off-by: AbdAlRahman Gad <abdobngad@gmail.com>

Thanks! It finally looks like code we don't have to be ashamed of. One
actual issue and three very small details:

> ---
>  ndp.c | 320 +++++++++++++++++++++++++++++++++++++++++++---------------
>  ndp.h |   3 +-
>  tap.c |   5 +-
>  3 files changed, 246 insertions(+), 82 deletions(-)
> 
> diff --git a/ndp.c b/ndp.c
> index cea3df5..5e9cb78 100644
> --- a/ndp.c
> +++ b/ndp.c
> @@ -38,22 +38,199 @@
>  #define NS	135
>  #define NA	136
>  
> +enum ndp_option_types {
> +	OPT_SRC_L2_ADDR		= 1,
> +	OPT_TARGET_L2_ADDR	= 2,
> +	OPT_PREFIX_INFO		= 3,
> +	OPT_MTU			= 5,
> +	OPT_RDNSS_TYPE		= 25,
> +	OPT_DNSSL_TYPE		= 31,
> +};
> +
> +/**
> + * struct opt_header - Option header
> + * @type:	Option type
> + * @len:	Option length, in units of 8 bytes
> +*/
> +struct opt_header {
> +	uint8_t type;
> +	uint8_t len;
> +} __attribute__((packed));
> +
> +/**
> + * struct opt_l2_addr - Link-layer address
> + * @header:	Option header
> + * @mac:	MAC address
> + */
> +struct opt_l2_addr {
> +	struct opt_header header;
> +	unsigned char mac[ETH_ALEN];
> +} __attribute__((packed));
> +
> +/**
> + * struct ndp_na - NDP Neighbor Advertisement (NA) message
> + * @ih:			ICMPv6 header
> + * @target_addr:	Target IPv6 address
> + * @target_l2_addr:	Target link-layer address
> + */
> +struct ndp_na {
> +	struct icmp6hdr ih;
> +	struct in6_addr target_addr;
> +	struct opt_l2_addr target_l2_addr;
> +} __attribute__((packed));
> +
> +/**
> + * struct opt_prefix_info - Prefix Information option
> + * @header:		Option header
> + * @prefix_len:		The number of leading bits in the Prefix that are valid
> + * @prefix_flags:	Flags associated with the prefix
> + * @valid_lifetime:	Valid lifetime (ms)
> + * @pref_lifetime:	Preferred lifetime (ms)
> + * @reserved:		Unused
> + */
> +struct opt_prefix_info {
> +	struct opt_header header;
> +	uint8_t prefix_len;
> +	uint8_t prefix_flags;
> +	uint32_t valid_lifetime;
> +	uint32_t pref_lifetime;
> +	uint32_t reserved;
> +} __attribute__((packed));
> +
> +/**
> + * struct mtu_opt - Maximum transmission unit (MTU) option
> + * @header:		Option header
> + * @reserved:		Unused
> + * @value:		MTU value, network order
> + */
> +struct mtu_opt {
> +	struct opt_header header;
> +	uint16_t reserved;
> +	uint32_t value;
> +} __attribute__((packed));
> +
> +/**
> + * struct rdnss - Recursive DNS Server (RDNSS) option
> + * @header:	Option header
> + * @reserved:	Unused
> + * @lifetime:	Validity time (s)
> + * @dns:	List of DNS server addresses
> + */
> +struct opt_rdnss {
> +	struct opt_header header;
> +	uint16_t reserved;
> +	uint32_t lifetime;
> +	struct in6_addr dns[MAXNS + 1];
> +} __attribute__((packed));
> +
> +/**
> + * struct dnssl - DNS Search List (DNSSL) option
> + * @header:		Option header
> + * @reserved:		Unused
> + * @lifetime:		Validity time (s)
> + * @domains:		List of NULL-seperated search domains
> + */
> +struct opt_dnssl {
> +	struct opt_header header;
> +	uint16_t reserved;
> +	uint32_t lifetime;
> +	unsigned char domains[MAXDNSRCH * NS_MAXDNAME];
> +} __attribute__((packed));
> +
> +/**
> + * struct ndp_ra - NDP Router Advertisement (RA) message
> + * @ih:			ICMPv6 header
> + * @reachable:		Reachability time, after confirmation (ms)
> + * @retrans:		Time between retransmitted NS messages (ms)
> + * @prefix_info:	Prefix Information option
> + * @prefix:		IPv6 prefix
> + * @mtu:		MTU option
> + * @source_ll:		Target link-layer address
> + * @dns:		RDNSS and DNSSL options
> + */
> +struct ndp_ra {
> +	struct icmp6hdr ih;
> +	uint32_t reachable;
> +	uint32_t retrans;
> +	struct opt_prefix_info prefix_info;
> +	struct in6_addr prefix;
> +	struct mtu_opt mtu;
> +	struct opt_l2_addr source_ll;
> +
> +	unsigned char dns[sizeof(struct opt_rdnss) + sizeof(struct opt_dnssl)];
> +} __attribute__((packed));
> +
> +/**
> + * struct ndp_ns - NDP Neighbor Solicitation (NS) message
> + * @ih:		 ICMPv6 header
> + * @target_addr: Target IPv6 address

Here and in the line above, for consistency, I'd rather use two tabs
instead of a tab and a space.

> + */
> +struct ndp_ns {
> +	struct icmp6hdr ih;
> +	struct in6_addr target_addr;
> +} __attribute__((packed));
> +
>  /**
>   * ndp() - Check for NDP solicitations, reply as needed
>   * @c:		Execution context
>   * @ih:		ICMPv6 header
> - * @saddr	Source IPv6 address
> + * @saddr:	Source IPv6 address
> + * @p:		Packet pool
>   *
>   * Return: 0 if not handled here, 1 if handled, -1 on failure
>   */
> -int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
> +int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
> +	const struct pool *p)
>  {
> +	struct ndp_na na = {
> +		.ih = {
> +			.icmp6_type		= NA,
> +			.icmp6_code		= 0,
> +			.icmp6_router		= 1,
> +			.icmp6_solicited	= 1,
> +			.icmp6_override		= 1,
> +		},
> +		.target_l2_addr = {
> +			.header	= {
> +				.type		= OPT_TARGET_L2_ADDR,
> +				.len		= 1,
> +			},
> +		}
> +	};
> +	struct ndp_ra ra = {
> +		.ih = {
> +			.icmp6_type		= RA,
> +			.icmp6_code		= 0,
> +			.icmp6_hop_limit	= 255,
> +			/* RFC 8319 */
> +			.icmp6_rt_lifetime	= htons_constant(65535),
> +			.icmp6_addrconf_managed	= 1

A comma at the end of this initialisation is not needed, but since you
added it to all the other lines (I also do), you could also have it
here for consistency.

> +		},
> +		.prefix_info = {
> +			.header = {
> +				.type		= OPT_PREFIX_INFO,
> +				.len		= 4,
> +			},
> +			.prefix_len		= 64,
> +			.prefix_flags		= 0xc0,	/* prefix flags: L, A */
> +			.valid_lifetime		= ~0U,
> +			.pref_lifetime		= ~0U,
> +		},
> +		.mtu = {
> +			.header	= {
> +				.type		= OPT_MTU,
> +				.len		= 1,
> +			},
> +		},
> +		.source_ll = {
> +			.header = {
> +				.type		= OPT_SRC_L2_ADDR,
> +				.len		= 1,
> +			},
> +		},
> +	};
>  	const struct in6_addr *rsaddr; /* src addr for reply */
> -	char buf[BUFSIZ] = { 0 };
> -	struct ipv6hdr *ip6hr;
> -	struct icmp6hdr *ihr;
> -	struct ethhdr *ehr;
> -	unsigned char *p;
> +	unsigned char *ptr = NULL;
>  	size_t dlen;
>  
>  	if (ih->icmp6_type < RS || ih->icmp6_type > NA)
> @@ -62,28 +239,22 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
>  	if (c->no_ndp)
>  		return 1;
>  
> -	ehr = (struct ethhdr *)buf;
> -	ip6hr = (struct ipv6hdr *)(ehr + 1);
> -	ihr = (struct icmp6hdr *)(ip6hr + 1);
> -
>  	if (ih->icmp6_type == NS) {
> +		struct ndp_ns *ns = packet_get(p, 0, 0, sizeof(struct ndp_ns),
> +					       NULL);
> +
> +		if (!ns)
> +			return -1;
> +
>  		if (IN6_IS_ADDR_UNSPECIFIED(saddr))
>  			return 1;
>  
>  		info("NDP: received NS, sending NA");
> -		ihr->icmp6_type = NA;
> -		ihr->icmp6_code = 0;
> -		ihr->icmp6_router = 1;
> -		ihr->icmp6_solicited = 1;
> -		ihr->icmp6_override = 1;
> -
> -		p = (unsigned char *)(ihr + 1);
> -		memcpy(p, ih + 1, sizeof(struct in6_addr)); /* target address */
> -		p += 16;
> -		*p++ = 2;				    /* target ll */
> -		*p++ = 1;				    /* length */
> -		memcpy(p, c->mac, ETH_ALEN);
> -		p += 6;
> +
> +		memcpy(&na.target_addr, &ns->target_addr,
> +		       sizeof(na.target_addr));
> +		memcpy(na.target_l2_addr.mac, c->mac, ETH_ALEN);
> +
>  	} else if (ih->icmp6_type == RS) {
>  		size_t dns_s_len = 0;
>  		int i, n;
> @@ -92,91 +263,76 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr)
>  			return 1;
>  
>  		info("NDP: received RS, sending RA");
> -		ihr->icmp6_type = RA;
> -		ihr->icmp6_code = 0;
> -		ihr->icmp6_hop_limit = 255;
> -		ihr->icmp6_rt_lifetime = htons(65535); /* RFC 8319 */
> -		ihr->icmp6_addrconf_managed = 1;
> -
> -		p = (unsigned char *)(ihr + 1);
> -		p += 8;				/* reachable, retrans time */
> -		*p++ = 3;			/* prefix */
> -		*p++ = 4;			/* length */
> -		*p++ = 64;			/* prefix length */
> -		*p++ = 0xc0;			/* prefix flags: L, A */
> -		*(uint32_t *)p = (uint32_t)~0U;	/* lifetime */
> -		p += 4;
> -		*(uint32_t *)p = (uint32_t)~0U;	/* preferred lifetime */
> -		p += 8;
> -		memcpy(p, &c->ip6.addr, 8);	/* prefix */
> -		p += 16;
> -
> -		if (c->mtu != -1) {
> -			*p++ = 5;			/* type */
> -			*p++ = 1;			/* length */
> -			p += 2;				/* reserved */
> -			*(uint32_t *)p = htonl(c->mtu);	/* MTU */
> -			p += 4;
> -		}
> +		memcpy(&ra.prefix, &c->ip6.addr, sizeof(ra.prefix));
> +
> +		if (c->mtu != -1)
> +			ra.mtu.value = htonl(c->mtu);

I just realised that this might send a zero-valued MTU option.

If c->mtu is -1, it means we don't want to assign any value. This
happens if the user specifies an option for --mtu / -m, with 0 as
value. From conf():

			if (!c->mtu) {
				c->mtu = -1;
				break;
			}

...but according to RFC 4861, there's no special value, so sending an
MTU option with value 0 (that's how this is initialised) is not really
something we should be doing. The original code was omitting the option
altogether, and I think we should keep doing it.

I tested this with -m 0, by the way, and the Linux kernel is happy with
it. It just ignores it, and I get 1500 bytes as MTU (default value), as
expected. But it might break different kernel versions or other
operating systems.

So I think we should also handle this option in a similar way as we
deal with the DNS options: if c->mtu is -1, we shouldn't add the option
at all.

You could replace the current 'dns' field in struct ndp_ra with
something like 'var' (for variable fields), or 'opts' (opt_opts sounds
pretty bad), which also includes the size of the MTU option. You could
then point ptr to it, and increase it if the MTU option is present.

> +
> +		ptr = &ra.dns[0];
>  
>  		if (c->no_dhcp_dns)
>  			goto dns_done;
>  
>  		for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n]); n++);
>  		if (n) {
> -			*p++ = 25;				/* RDNSS */
> -			*p++ = 1 + 2 * n;			/* length */
> -			p += 2;					/* reserved */
> -			*(uint32_t *)p = (uint32_t)~0U;		/* lifetime */
> -			p += 4;
> -
> +			struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr;
> +			*rdnss = (struct opt_rdnss) {
> +				.header = {
> +					.type		= OPT_RDNSS_TYPE,
> +					.len		= 1 + 2 * n,
> +				},
> +				.lifetime		= ~0U,
> +			};
>  			for (i = 0; i < n; i++) {
> -				memcpy(p, &c->ip6.dns[i], 16);	/* address */
> -				p += 16;
> +				memcpy(&rdnss->dns[i], &c->ip6.dns[i],
> +				       sizeof(rdnss->dns[i]));
>  			}
> +			ptr += offsetof(struct opt_rdnss, dns) +
> +				   i * sizeof(rdnss->dns[0]);

In the Linux kernel-like coding style, we align things logically, so if
i * sizeof(rdnss->dns[0]) is an operand of the same operation as
offsetof(struct opt_rdnss, dns), they should be aligned, like this:

			ptr += offsetof(struct opt_rdnss, dns) +
			       i * sizeof(rdnss->dns[0]);

>  
>  			for (n = 0; *c->dns_search[n].n; n++)
>  				dns_s_len += strlen(c->dns_search[n].n) + 2;
>  		}
>  
>  		if (!c->no_dhcp_dns_search && dns_s_len) {
> -			*p++ = 31;				/* DNSSL */
> -			*p++ = (dns_s_len + 8 - 1) / 8 + 1;	/* length */
> -			p += 2;					/* reserved */
> -			*(uint32_t *)p = (uint32_t)~0U;		/* lifetime */
> -			p += 4;
> +			struct opt_dnssl *dnssl = (struct opt_dnssl *)ptr;
> +			*dnssl = (struct opt_dnssl) {
> +				.header = {
> +					.type = OPT_DNSSL_TYPE,
> +					.len  = DIV_ROUND_UP(dns_s_len, 8) + 1,
> +				},
> +				.lifetime     = ~0U,
> +			};
> +			ptr = dnssl->domains;
>  
>  			for (i = 0; i < n; i++) {
> +				size_t len;
>  				char *dot;
>  
> -				*(p++) = '.';
> +				*(ptr++) = '.';
>  
> -				strncpy((char *)p, c->dns_search[i].n,
> -					sizeof(buf) -
> -					((intptr_t)p - (intptr_t)buf));
> -				for (dot = (char *)p - 1; *dot; dot++) {
> +				len = sizeof(dnssl->domains) -
> +				      (ptr - dnssl->domains);
> +
> +				strncpy((char *)ptr, c->dns_search[i].n, len);
> +				for (dot = (char *)ptr - 1; *dot; dot++) {
>  					if (*dot == '.')
>  						*dot = strcspn(dot + 1, ".");
>  				}
> -				p += strlen(c->dns_search[i].n);
> -				*(p++) = 0;
> +				ptr += strlen(c->dns_search[i].n);
> +				*(ptr++) = 0;
>  			}
>  
> -			memset(p, 0, 8 - dns_s_len % 8);	/* padding */
> -			p += 8 - dns_s_len % 8;
> +			memset(ptr, 0, 8 - dns_s_len % 8);	/* padding */
> +			ptr += 8 - dns_s_len % 8;
>  		}
>  
>  dns_done:
> -		*p++ = 1;			/* source ll */
> -		*p++ = 1;			/* length */
> -		memcpy(p, c->mac, ETH_ALEN);
> -		p += 6;
> +		memcpy(&ra.source_ll.mac, c->mac, ETH_ALEN);
>  	} else {
>  		return 1;
>  	}
>  
> -	dlen = (uintptr_t)p - (uintptr_t)ihr - sizeof(*ihr);
> -
>  	if (IN6_IS_ADDR_LINKLOCAL(saddr))
>  		c->ip6.addr_ll_seen = *saddr;
>  	else
> @@ -187,7 +343,13 @@ dns_done:
>  	else
>  		rsaddr = &c->ip6.addr_ll;
>  
> -	tap_icmp6_send(c, rsaddr, saddr, ihr, dlen + sizeof(*ihr));
> +	if (ih->icmp6_type == NS) {
> +		dlen = sizeof(struct ndp_na);
> +		tap_icmp6_send(c, rsaddr, saddr, &na, dlen);
> +	} else if (ih->icmp6_type == RS) {
> +		dlen = ptr - (unsigned char *)&ra;
> +		tap_icmp6_send(c, rsaddr, saddr, &ra, dlen);
> +	}
>  
>  	return 1;
>  }
> diff --git a/ndp.h b/ndp.h
> index b33cd69..a786441 100644
> --- a/ndp.h
> +++ b/ndp.h
> @@ -6,6 +6,7 @@
>  #ifndef NDP_H
>  #define NDP_H
>  
> -int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr);
> +int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
> +	const struct pool *p);
>  
>  #endif /* NDP_H */
> diff --git a/tap.c b/tap.c
> index 5852705..87be3a6 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -808,12 +808,13 @@ resume:
>  			if (l4len < sizeof(struct icmp6hdr))
>  				continue;
>  
> -			if (ndp(c, (struct icmp6hdr *)l4h, saddr))
> +			packet_add(pkt, l4len, l4h);
> +
> +			if (ndp(c, (struct icmp6hdr *)l4h, saddr, pkt))
>  				continue;
>  
>  			tap_packet_debug(NULL, ip6h, NULL, proto, NULL, 1);
>  
> -			packet_add(pkt, l4len, l4h);
>  			icmp_tap_handler(c, PIF_TAP, AF_INET6,
>  					 saddr, daddr, pkt, now);
>  			continue;

The rest looks great to me.

-- 
Stefano


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

end of thread, other threads:[~2024-08-12 15:44 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-08-10 16:00 [PATCH] ndp.c: Turn NDP responder into more declarative implementation AbdAlRahman Gad
2024-08-12 15:44 ` Stefano Brivio

Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).