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=G7Mz0IeF; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by passt.top (Postfix) with ESMTPS id 86D675A061B for ; Fri, 26 Jun 2026 04:45:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1782441937; 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=GelT/k3dDwZsKZ7ybA2Ia36f2FwUXfBsfHqfXG/jgts=; b=G7Mz0IeFAnKwsIdmtX8w7HLsQStzHTD3p+idNx1bDJeKi8+KDxhe7eI7wRzqcwPcvs1Ua4 NV14THOrJ2WoZjEFrYj0u/KBDGU/tzoBYswDDK88U/ARh97Ka7hwF5EIBeBc8exjj6xxYG Fgy7y/6913iVNWDFPc3icWjac6CxcO0= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-22-5gIbkfoGOLGoGGgOUmRjSw-1; Thu, 25 Jun 2026 22:45:36 -0400 X-MC-Unique: 5gIbkfoGOLGoGGgOUmRjSw-1 X-Mimecast-MFC-AGG-ID: 5gIbkfoGOLGoGGgOUmRjSw_1782441935 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (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-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2216A1873C2B; Fri, 26 Jun 2026 02:45:35 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.88.44]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 547CC1956087; Fri, 26 Jun 2026 02:45:34 +0000 (UTC) From: Jon Maloy 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 Message-ID: <20260626024519.3701556-13-jmaloy@redhat.com> In-Reply-To: <20260626024519.3701556-1-jmaloy@redhat.com> References: <20260626024519.3701556-1-jmaloy@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: -4XKzf6hSpVer_ztjt_uUWbvCKv829M1_qIQuHRAyqE_1782441935 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: S7KOQLEK66LM7VYAP7DZ2E6UVLDATSXK X-Message-ID-Hash: S7KOQLEK66LM7VYAP7DZ2E6UVLDATSXK 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_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 --- 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