public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Jon Maloy <jmaloy@redhat.com>
To: sbrivio@redhat.com, david@gibson.dropbear.id.au,
	jmaloy@redhat.com, passt-dev@passt.top
Subject: [PATCH v8 12/14] dhcpv6: Select addresses for DHCPv6 distribution
Date: Thu, 25 Jun 2026 22:45:17 -0400	[thread overview]
Message-ID: <20260626024519.3701556-13-jmaloy@redhat.com> (raw)
In-Reply-To: <20260626024519.3701556-1-jmaloy@redhat.com>

We introduce a CONF_ADDR_DHCPV6OFFER flag to mark if an added address is
eligible for DHCPv6 advertisement. By doing this once and for all
in the fwd_set_addr() function, the DHCPv6 code only needs to check
for this flag to know that all criteria for advertisement are fulfilled.

We update the code in dhcpv6.c both to use the new flag and to make
it possible to send multiple addresses in a single reply message,
per RFC 9915.

We also let the conf_print() function use this flag to identify and
print the eligible addresses.

Signed-off-by: Jon Maloy <jmaloy@redhat.com>

---
v6: -Refactored the DHCPv6 response structure to use a variable-length
     buffer for IA_ADDR options, hopefully making this part of the code
     slightly clearer.

v7: -Adapted to previous changes in this series
    -Some minor changes based on feedback

v8: -Moved to earlier in series.
    -Refactored branch for advertisement eligibility
    -Renamed CONF_ADDR_DHCP to CONF_ADDR_DHCPOFFER (Stefano)
---
 conf.c   | 34 +++++++++++++++-----
 dhcpv6.c | 98 ++++++++++++++++++++++++++++++++------------------------
 fwd.c    |  3 ++
 passt.h  |  1 +
 4 files changed, 87 insertions(+), 49 deletions(-)

diff --git a/conf.c b/conf.c
index f30c238e..74368c2b 100644
--- a/conf.c
+++ b/conf.c
@@ -868,25 +868,43 @@ static void conf_print(const struct ctx *c)
 	}
 
 	if (c->ifi6) {
+		bool has_dhcpv6 = false;
+		const char *head;
+
 		if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
 			info("    NAT to host ::1: %s",
 			     inet_ntop(AF_INET6, &c->ip6.map_host_loopback,
 				       buf, sizeof(buf)));
 
-		if (!c->no_ndp && !c->no_dhcpv6)
-			info("NDP/DHCPv6:");
-		else if (!c->no_dhcpv6)
-			info("DHCPv6:");
-		else if (!c->no_ndp)
-			info("NDP:");
-		else
+		for_each_addr(a, c->addrs, c->addr_count, AF_INET6) {
+			if (a->flags & CONF_ADDR_DHCPV6OFFER)
+				has_dhcpv6 = true;
+		}
+
+		if (c->no_ndp && !has_dhcpv6)
 			goto dns6;
 
 		a = fwd_get_addr(c, AF_INET6, CONF_ADDR_ANY,
 				 CONF_ADDR_LINKLOCAL);
-		if (a)
+		if (!c->no_ndp && a) {
+			info("NDP:");
 			info("    assign: %s",
 			     inany_ntop(&a->addr, buf, sizeof(buf)));
+		}
+
+		if (has_dhcpv6) {
+			info("DHCPv6:");
+			head = "assign: ";
+			for_each_addr(a, c->addrs, c->addr_count, AF_INET6) {
+				if (!(a->flags & CONF_ADDR_DHCPV6OFFER))
+					continue;
+				info("    %s %s/%d", head,
+				     inany_ntop(&a->addr, buf, sizeof(buf)),
+				     a->prefix_len);
+				head = "        ";
+			}
+		}
+
 		info("    router: %s",
 		     inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf)));
 		info("    our link-local: %s",
diff --git a/dhcpv6.c b/dhcpv6.c
index f5de90cd..b704e59f 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -31,6 +31,8 @@
 #include "passt.h"
 #include "tap.h"
 #include "log.h"
+#include "fwd.h"
+#include "conf.h"
 
 /**
  * struct opt_hdr - DHCPv6 option header
@@ -202,56 +204,35 @@ struct msg_hdr {
 	uint32_t xid:24;
 } __attribute__((__packed__));
 
+/* Maximum variable part size: ia_addrs + client_id + dns + search + fqdn */
+#define RESP_VAR_MAX	(MAX_GUEST_ADDRS * sizeof(struct opt_ia_addr) + \
+			 sizeof(struct opt_client_id) + \
+			 sizeof(struct opt_dns_servers) + \
+			 sizeof(struct opt_dns_search) + \
+			 sizeof(struct opt_client_fqdn))
+
 /**
  * struct resp_t - Normal advertise and reply message
  * @hdr:		DHCP message header
  * @server_id:		Server Identifier option
  * @ia_na:		Non-temporary Address option
- * @ia_addr:		Address for IA_NA
- * @client_id:		Client Identifier, variable length
- * @dns_servers:	DNS Recursive Name Server, here just for storage size
- * @dns_search:		Domain Search List, here just for storage size
- * @client_fqdn:	Client FQDN, variable length
+ * @var:		Variable part: IA_ADDRs, client_id, dns, search, fqdn
  */
 static struct resp_t {
 	struct msg_hdr hdr;
 
 	struct opt_server_id server_id;
 	struct opt_ia_na ia_na;
-	struct opt_ia_addr ia_addr;
-	struct opt_client_id client_id;
-	struct opt_dns_servers dns_servers;
-	struct opt_dns_search dns_search;
-	struct opt_client_fqdn client_fqdn;
+	uint8_t var[RESP_VAR_MAX];
 } __attribute__((__packed__)) resp = {
 	{ 0 },
 	SERVER_ID,
 
-	{ { OPT_IA_NA,		OPT_SIZE_CONV(sizeof(struct opt_ia_na) +
-					      sizeof(struct opt_ia_addr) -
-					      sizeof(struct opt_hdr)) },
+	{ { OPT_IA_NA,		0 },  /* Length set dynamically */
 	  1, (uint32_t)~0U, (uint32_t)~0U
 	},
 
-	{ { OPT_IAAADR,		OPT_SIZE(ia_addr) },
-	  IN6ADDR_ANY_INIT, (uint32_t)~0U, (uint32_t)~0U
-	},
-
-	{ { OPT_CLIENTID,	0, },
-	  { 0 }
-	},
-
-	{ { OPT_DNS_SERVERS,	0, },
-	  { IN6ADDR_ANY_INIT }
-	},
-
-	{ { OPT_DNS_SEARCH,	0, },
-	  { 0 },
-	},
-
-	{ { OPT_CLIENT_FQDN, 0, },
-	  0, { 0 },
-	},
+	{ 0 },  /* Variable part filled dynamically */
 };
 
 static const struct opt_status_code sc_not_on_link = {
@@ -540,6 +521,42 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data,
 	return offset + sizeof(struct opt_hdr) + opt_len;
 }
 
+/**
+ * dhcpv6_ia_addr_fill() - Fill IA_ADDR options for all suitable addresses
+ * @c:		Execution context
+ *
+ * Fills IA_ADDRs in resp.var with all non-linklocal host or user provided
+ * addresses and updates resp.ia_na.hdr.l with the correct length.
+ *
+ * Return: number of addresses filled
+ */
+static int dhcpv6_ia_addr_fill(const struct ctx *c)
+{
+	struct opt_ia_addr *ia_addr = (struct opt_ia_addr *)resp.var;
+	const struct guest_addr *e;
+	int count = 0;
+
+	for_each_addr(e, c->addrs, c->addr_count, AF_INET6) {
+		if (!(e->flags & CONF_ADDR_DHCPV6OFFER))
+			continue;
+
+		ia_addr[count].hdr.t = OPT_IAAADR;
+		ia_addr[count].hdr.l = htons(sizeof(struct opt_ia_addr) -
+					     sizeof(struct opt_hdr));
+		ia_addr[count].addr = e->addr.a6;
+		ia_addr[count].pref_lifetime = (uint32_t)~0U;
+		ia_addr[count].valid_lifetime = (uint32_t)~0U;
+		count++;
+	}
+
+	/* Update IA_NA length: header fields + all IA_ADDRs */
+	resp.ia_na.hdr.l = htons(sizeof(struct opt_ia_na) -
+				 sizeof(struct opt_hdr) +
+				 count * sizeof(struct opt_ia_addr));
+
+	return count;
+}
+
 /**
  * dhcpv6() - Check if this is a DHCPv6 message, reply as needed
  * @c:		Execution context
@@ -573,9 +590,10 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
 	const struct msg_hdr *mh;
 	struct udphdr uh_storage;
 	const struct udphdr *uh;
+	int addr_count;
 	size_t mlen, n;
 
-	a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL);
+	a = fwd_get_addr(c, AF_INET6, CONF_ADDR_ANY, CONF_ADDR_LINKLOCAL);
 
 	uh = IOV_REMOVE_HEADER(data, uh_storage);
 	if (!uh)
@@ -618,6 +636,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
 	if (ia && ntohs(ia->hdr.l) < MIN(OPT_VSIZE(ia_na), OPT_VSIZE(ia_ta)))
 		return -1;
 
+	addr_count = dhcpv6_ia_addr_fill(c);
 	resp.hdr.type = TYPE_REPLY;
 	switch (mh->type) {
 	case TYPE_REQUEST:
@@ -673,12 +692,14 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
 	if (ia)
 		resp.ia_na.iaid = ((struct opt_ia_na *)ia)->iaid;
 
+	/* Client_id goes right after the used IA_ADDRs */
+	n = offsetof(struct resp_t, var) +
+	    addr_count * sizeof(struct opt_ia_addr);
 	iov_to_buf(&client_id_base.iov[0], client_id_base.cnt,
-		   client_id_base.off, &resp.client_id,
+		   client_id_base.off, (char *)&resp + n,
 		   ntohs(client_id->l) + sizeof(struct opt_hdr));
 
-	n = offsetof(struct resp_t, client_id) +
-	    sizeof(struct opt_hdr) + ntohs(client_id->l);
+	n += sizeof(struct opt_hdr) + ntohs(client_id->l);
 	n = dhcpv6_dns_fill(c, (char *)&resp, n);
 	n = dhcpv6_client_fqdn_fill(data, c, (char *)&resp, n);
 
@@ -696,7 +717,6 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
 void dhcpv6_init(const struct ctx *c)
 {
 	time_t y2k = 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime() */
-	const struct guest_addr *a;
 	uint32_t duid_time;
 
 	duid_time = htonl(difftime(time(NULL), y2k));
@@ -708,8 +728,4 @@ void dhcpv6_init(const struct ctx *c)
 	       c->our_tap_mac, sizeof(c->our_tap_mac));
 	memcpy(resp_not_on_link.server_id.duid_lladdr,
 	       c->our_tap_mac, sizeof(c->our_tap_mac));
-
-	a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL);
-	if (a)
-		resp.ia_addr.addr = a->addr.a6;
 }
diff --git a/fwd.c b/fwd.c
index 669fc237..2ebd5f24 100644
--- a/fwd.c
+++ b/fwd.c
@@ -298,6 +298,9 @@ void fwd_set_addr(struct ctx *c, const union inany_addr *addr,
 		if (inany_v4(addr)) {
 			if (!c->no_dhcp)
 				flags |= CONF_ADDR_DHCPOFFER;
+		} else {
+			if (!c->no_dhcpv6)
+				flags |= CONF_ADDR_DHCPV6OFFER;
 		}
 	}
 
diff --git a/passt.h b/passt.h
index b323bd48..5fa21385 100644
--- a/passt.h
+++ b/passt.h
@@ -83,6 +83,7 @@ struct guest_addr {
 #define CONF_ADDR_LINKLOCAL	BIT(3)		/* Link-local address */
 #define CONF_ADDR_OBSERVED	BIT(4)		/* Seen in guest traffic */
 #define CONF_ADDR_DHCPOFFER	BIT(5)		/* Advertise via DHCP (IPv4) */
+#define CONF_ADDR_DHCPV6OFFER	BIT(6)		/* Advertise via DHCPv6 */
 #define CONF_ADDR_ANY		0xff		/* Match any flag */
 };
 
-- 
2.52.0


  parent reply	other threads:[~2026-06-26  2:45 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-26  2:45 [PATCH v8 00/14] Introduce multiple addresses Jon Maloy
2026-06-26  2:45 ` [PATCH v8 01/14] dhcpv6: Fix reply destination to match client's source address Jon Maloy
2026-06-26  2:45 ` [PATCH v8 02/14] passt, pasta: Introduce unified multi-address data structures Jon Maloy
2026-06-26  2:45 ` [PATCH v8 03/14] tap, conf: Replace addr_fixed with CONF_ADDR_USER flag check Jon Maloy
2026-06-26  2:45 ` [PATCH v8 04/14] fwd: Unify guest accessibility checks with unified address array Jon Maloy
2026-06-26  2:45 ` [PATCH v8 05/14] arp: Check all configured addresses in ARP filtering Jon Maloy
2026-06-26  2:45 ` [PATCH v8 06/14] conf: Allow multiple -a/--address options per address family Jon Maloy
2026-06-26  2:45 ` [PATCH v8 07/14] netlink, conf: Read all addresses from template interface at startup Jon Maloy
2026-06-26  2:45 ` [PATCH v8 08/14] netlink, pasta: refactor function pasta_ns_conf() Jon Maloy
2026-06-26  2:45 ` [PATCH v8 09/14] conf, pasta: Track observed guest IPv4 addresses in unified address array Jon Maloy
2026-06-26  2:45 ` [PATCH v8 10/14] conf, pasta: Track observed guest IPv6 " Jon Maloy
2026-06-26  2:45 ` [PATCH v8 11/14] dhcp: Select address for DHCP distribution Jon Maloy
2026-06-26  2:45 ` Jon Maloy [this message]
2026-06-26  2:45 ` [PATCH v8 13/14] ndp: Support advertising multiple prefixes in Router Advertisements Jon Maloy
2026-06-26  2:45 ` [PATCH v8 14/14] migrate: Update protocol to v3 for multi-address support Jon Maloy

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=20260626024519.3701556-13-jmaloy@redhat.com \
    --to=jmaloy@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    --cc=sbrivio@redhat.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.
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).