From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=IAkpXjU1; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 2973C5A0626 for ; Mon, 13 Apr 2026 02:53:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1776041617; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=L2TTnvVnoW2AjME7NBQqFC8qC3x5BhOM0vp/036ibWo=; b=IAkpXjU1rGI1ZD61ZabvjPBP7t36Nw0W9H0X6gfWRGTWa3VXJ3PtFDR2QuGkcai5vFsmZ+ flW4UKxyKhW1QFU5xDbfCndXOcbpZxbRIL/L1PekFIQSgcU17EGFMsawbelYd7DnyG/39N d0EBIyd/jreEKgUZQQAbBZ//rw52CRY= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-319-yFmZaeUMPf2pTQ9m2nCT-w-1; Sun, 12 Apr 2026 20:53:35 -0400 X-MC-Unique: yFmZaeUMPf2pTQ9m2nCT-w-1 X-Mimecast-MFC-AGG-ID: yFmZaeUMPf2pTQ9m2nCT-w_1776041614 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B422F19560B2; Mon, 13 Apr 2026 00:53:34 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.64.70]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 007093000C16; Mon, 13 Apr 2026 00:53:33 +0000 (UTC) From: Jon Maloy To: sbrivio@redhat.com, david@gibson.dropbear.id.au, jmaloy@redhat.com, passt-dev@passt.top Subject: [PATCH v7 12/13] dhcpv6: Select addresses for DHCPv6 distribution Date: Sun, 12 Apr 2026 20:53:18 -0400 Message-ID: <20260413005319.3295910-13-jmaloy@redhat.com> In-Reply-To: <20260413005319.3295910-1-jmaloy@redhat.com> References: <20260413005319.3295910-1-jmaloy@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: HavOTDD7vy1mF3LECW_D-ptnZHOWV7kP_6ENU6AjOe0_1776041614 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: IFQTCZSZ3DOLIYYCKYTTS5T3N7G3NROZ X-Message-ID-Hash: IFQTCZSZ3DOLIYYCKYTTS5T3N7G3NROZ X-MailFrom: jmaloy@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: We introduce a CONF_ADDR_DHCPV6 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 --- 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 --- conf.c | 35 ++++++++++++++++---- dhcpv6.c | 96 ++++++++++++++++++++++++++++++++----------------------- fwd.c | 3 ++ migrate.c | 5 +++ passt.h | 1 + 5 files changed, 93 insertions(+), 47 deletions(-) diff --git a/conf.c b/conf.c index 612df07..7c705de 100644 --- a/conf.c +++ b/conf.c @@ -1216,21 +1216,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 + for_each_addr(a, c->addrs, c->addr_count, 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 (!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_DHCPV6)) + continue; + info(" %s: %s/%d", head, + inany_ntop(&a->addr, buf, sizeof(buf)), + a->prefix_len); + head = " "; + } + } + if (a) info(" assign: %s", inany_ntop(&a->addr, buf, sizeof(buf))); diff --git a/dhcpv6.c b/dhcpv6.c index 447aaba..546a3ea 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_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 @@ -573,6 +590,7 @@ 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); @@ -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: @@ -671,12 +690,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); @@ -693,7 +714,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; @@ -706,8 +726,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 39e52c4..2b444fb 100644 --- a/fwd.c +++ b/fwd.c @@ -299,6 +299,9 @@ void fwd_set_addr(struct ctx *c, const union inany_addr *addr, (flags & CONF_ADDR_HOST && !(flags & CONF_ADDR_LINKLOCAL))) if (!c->no_dhcp) flags |= CONF_ADDR_DHCP; + } else if (!(flags & CONF_ADDR_LINKLOCAL)) { + if (!c->no_dhcpv6) + flags |= CONF_ADDR_DHCPV6; } /* Add to head or tail, depending on flag */ diff --git a/migrate.c b/migrate.c index afdc8b4..adcbc63 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 - Migration format for a single address entry @@ -86,6 +87,8 @@ static uint8_t flags_to_migration(uint8_t flags) migration |= MIGRATE_ADDR_OBSERVED; if (flags & CONF_ADDR_DHCP) migration |= MIGRATE_ADDR_DHCP; + if (flags & CONF_ADDR_DHCPV6) + migration |= MIGRATE_ADDR_DHCPV6; return migration; } @@ -110,6 +113,8 @@ static uint8_t flags_from_migration(uint8_t migration) flags |= CONF_ADDR_OBSERVED; if (migration & MIGRATE_ADDR_DHCP) flags |= CONF_ADDR_DHCP; + if (migration & MIGRATE_ADDR_DHCPV6) + flags |= CONF_ADDR_DHCPV6; return flags; } diff --git a/passt.h b/passt.h index 9508c2a..028eb7c 100644 --- a/passt.h +++ b/passt.h @@ -84,6 +84,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_DHCP BIT(5) /* Advertise via DHCP (IPv4) */ +#define CONF_ADDR_DHCPV6 BIT(6) /* Advertise via DHCPv6 (IPv6) */ }; /** -- 2.52.0