public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Jon Maloy <jmaloy@redhat.com>
To: sbrivio@redhat.com, dgibson@redhat.com,
	david@gibson.dropbear.id.au, jmaloy@redhat.com,
	passt-dev@passt.top
Subject: [PATCH v6 12/13] dhcpv6: Select addresses for DHCPv6 distribution
Date: Sat, 21 Mar 2026 20:43:32 -0400	[thread overview]
Message-ID: <20260322004333.365713-13-jmaloy@redhat.com> (raw)
In-Reply-To: <20260322004333.365713-1-jmaloy@redhat.com>

We introduce a CONF_ADDR_DHCP flag to mark if an added address is
eligible for DHCP 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 8415.

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.
---
 conf.c    | 36 +++++++++++++++------
 dhcpv6.c  | 97 ++++++++++++++++++++++++++++++++-----------------------
 fwd.c     |  4 +++
 migrate.c |  5 +++
 passt.h   |  1 +
 5 files changed, 94 insertions(+), 49 deletions(-)

diff --git a/conf.c b/conf.c
index 512fa38..de2fb7c 100644
--- a/conf.c
+++ b/conf.c
@@ -1213,24 +1213,42 @@ 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
+		/* Check what we have to advertise */
+		for_each_addr(a, c, AF_INET6) {
+			if (a->flags & CONF_ADDR_DHCPV6)
+				has_dhcpv6 = true;
+		}
+
+		if (c->no_ndp && !has_dhcpv6)
 			goto dns6;
 
 		a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL);
-		if (a)
+		if (!c->no_ndp && a) {
+			info("NDP:");
 			inany_ntop(&a->addr, buf, sizeof(buf));
-		info("    assign: %s", !a ? "" : buf);
+			info("    assign: %s", buf);
+		}
+
+		if (has_dhcpv6) {
+			info("DHCPv6:");
+			head = "assign";
+			for_each_addr(a, c, AF_INET6) {
+				if (!(a->flags & CONF_ADDR_DHCPV6))
+					continue;
+				inany_ntop(&a->addr, buf, sizeof(buf));
+				info("    %s: %s/%d", head, buf, a->prefix_len);
+				head = "      ";
+			}
+		}
+
 		inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf));
 		info("    router: %s", buf);
 		inet_ntop(AF_INET6, &c->ip6.our_tap_ll, buf, sizeof(buf));
diff --git a/dhcpv6.c b/dhcpv6.c
index 313c243..7c16da4 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 = {
@@ -543,6 +524,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, non-observed 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, AF_INET6) {
+		if (!(e->flags & CONF_ADDR_DHCPV6))
+			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
@@ -570,12 +587,14 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
 	struct opt_hdr client_id_storage;
 	/* cppcheck-suppress [variableScope,unmatchedSuppression] */
 	const struct in6_addr *src, *dst;
+	/* cppcheck-suppress [variableScope,unmatchedSuppression] */
 	struct opt_ia_na ia_storage;
 	const struct guest_addr *a;
 	struct msg_hdr mh_storage;
 	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);
@@ -626,6 +645,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:
@@ -679,12 +699,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);
 
@@ -701,7 +723,6 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
  */
 void dhcpv6_init(const struct ctx *c)
 {
-	const struct guest_addr *a;
 	time_t y2k = 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime() */
 	uint32_t duid_time;
 
@@ -714,8 +735,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 e1c85dd..f867398 100644
--- a/fwd.c
+++ b/fwd.c
@@ -301,6 +301,10 @@ void fwd_set_addr(struct ctx *c, const union inany_addr *addr,
 		if (inany_v4(addr)) {
 			if (!c->no_dhcp)
 				flags |= CONF_ADDR_DHCP;
+		} else {
+			/* DHCPv6 for IPv6 */
+			if (!c->no_dhcpv6)
+				flags |= CONF_ADDR_DHCPV6;
 		}
 	}
 
diff --git a/migrate.c b/migrate.c
index 1d1e0e6..105f624 100644
--- a/migrate.c
+++ b/migrate.c
@@ -53,6 +53,7 @@ struct migrate_seen_addrs_v2 {
 #define MIGRATE_ADDR_LINKLOCAL	BIT(2)
 #define MIGRATE_ADDR_OBSERVED	BIT(3)
 #define MIGRATE_ADDR_DHCP	BIT(4)
+#define MIGRATE_ADDR_DHCPV6	BIT(5)
 
 /**
  * struct migrate_addr_v3 - Wire format for a single address entry
@@ -86,6 +87,8 @@ static uint8_t flags_to_wire(uint8_t flags)
 		wire |= MIGRATE_ADDR_OBSERVED;
 	if (flags & CONF_ADDR_DHCP)
 		wire |= MIGRATE_ADDR_DHCP;
+	if (flags & CONF_ADDR_DHCPV6)
+		wire |= MIGRATE_ADDR_DHCPV6;
 
 	return wire;
 }
@@ -110,6 +113,8 @@ static uint8_t flags_from_wire(uint8_t wire)
 		flags |= CONF_ADDR_OBSERVED;
 	if (wire & MIGRATE_ADDR_DHCP)
 		flags |= CONF_ADDR_DHCP;
+	if (wire & MIGRATE_ADDR_DHCPV6)
+		flags |= CONF_ADDR_DHCPV6;
 
 	return flags;
 }
diff --git a/passt.h b/passt.h
index 5ea1715..c4c1f04 100644
--- a/passt.h
+++ b/passt.h
@@ -76,6 +76,7 @@ enum passt_modes {
 #define CONF_ADDR_LINKLOCAL	BIT(2)		/* Link-local address */
 #define CONF_ADDR_OBSERVED	BIT(3)		/* Seen in guest traffic */
 #define CONF_ADDR_DHCP		BIT(4)		/* Advertise via DHCP (IPv4) */
+#define CONF_ADDR_DHCPV6	BIT(5)		/* Advertise via DHCPv6 (IPv6) */
 
 /**
  * struct guest_addr - Unified IPv4/IPv6 address entry
-- 
2.52.0


  parent reply	other threads:[~2026-03-22  0:43 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-22  0:43 [PATCH v6 00/13] Introduce multiple addresses and late binding Jon Maloy
2026-03-22  0:43 ` [PATCH v6 01/13] conf: use a single buffer for print formatting in conf_print() Jon Maloy
2026-03-22  0:43 ` [PATCH v6 02/13] ip: Introduce unified multi-address data structures Jon Maloy
2026-03-22  0:43 ` [PATCH v6 03/13] fwd: Unify guest accessibility checks with unified address array Jon Maloy
2026-03-22  0:43 ` [PATCH v6 04/13] arp: Check all configured addresses in ARP filtering Jon Maloy
2026-03-22  0:43 ` [PATCH v6 05/13] conf: Allow multiple -a/--address options per address family Jon Maloy
2026-03-22  0:43 ` [PATCH v6 06/13] netlink, conf: Read all addresses from template interface at startup Jon Maloy
2026-03-22  0:43 ` [PATCH v6 07/13] ip: refactor function pasta_ns_conf() Jon Maloy
2026-03-22  0:43 ` [PATCH v6 08/13] ip: Track observed guest IPv4 addresses in unified address array Jon Maloy
2026-03-22  0:43 ` [PATCH v6 09/13] ip: Track observed guest IPv6 " Jon Maloy
2026-03-22  0:43 ` [PATCH v6 10/13] migrate: Update protocol to v3 for multi-address support Jon Maloy
2026-03-22  0:43 ` [PATCH v6 11/13] dhcp: Select address for DHCP distribution Jon Maloy
2026-03-22  0:43 ` Jon Maloy [this message]
2026-03-22  0:43 ` [PATCH v6 13/13] ndp: Support advertising multiple prefixes in Router Advertisements 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=20260322004333.365713-13-jmaloy@redhat.com \
    --to=jmaloy@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=dgibson@redhat.com \
    --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).