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=XihZKw3t; 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 3C7D05A0652 for ; Fri, 30 Jan 2026 22:45:10 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1769809509; 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=+QfvMpTbolLUbqBMc8kA5dlvNk7qXWU9SI+9+KHsmBU=; b=XihZKw3t8JV3s3nyBW4F8BGR7rRBd75CvjRglkpDn47FGrdy/QiV8S96VIZIUazBiX5VaB Xa+eurFwKmWfb8CV8V4tnw9Fug/wgupz5UNT5/N32azZjnVKc8R4g3yYBwe2cN6mG0iGA2 RwDeaxcqgppVw7RiiU2kPhIbuIr+qUY= Received: from mx-prod-mc-08.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-510--vrzqiZRME6EWlQlZfaEQw-1; Fri, 30 Jan 2026 16:45:07 -0500 X-MC-Unique: -vrzqiZRME6EWlQlZfaEQw-1 X-Mimecast-MFC-AGG-ID: -vrzqiZRME6EWlQlZfaEQw_1769809506 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (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-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id ADD771800372; Fri, 30 Jan 2026 21:45:06 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.65.201]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id B25EB18002A6; Fri, 30 Jan 2026 21:45:05 +0000 (UTC) From: Jon Maloy To: sbrivio@redhat.com, dgibson@redhat.com, david@gibson.dropbear.id.au, jmaloy@redhat.com, passt-dev@passt.top Subject: [PATCH v3 11/11] conf: Select addresses for DHCP and NDP distribution Date: Fri, 30 Jan 2026 16:44:47 -0500 Message-ID: <20260130214447.2540791-12-jmaloy@redhat.com> In-Reply-To: <20260130214447.2540791-1-jmaloy@redhat.com> References: <20260130214447.2540791-1-jmaloy@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 0kePzOTl1W3nV8xt-wh4qQaOdtOxD5ZEBIxhFvS-_os_1769809506 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: REDDNW57QWD5OEBEVIJ6K45B3ZSN5CIE X-Message-ID-Hash: REDDNW57QWD5OEBEVIJ6K45B3ZSN5CIE 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: As a part of the multiple address support feature, we add a new algorithm for DHCP/DHCPv6/NDP address selection and refactor the conf_print() function accordingly. The fwd_first_usable_addr() and fwd_select_addr() functions now take a context pointer and address family parameter to work with the unified address array using for_each_addr(). Signed-off-by: Jon Maloy --- conf.c | 60 +++++++++++++++++++++++++---------------- dhcp.c | 17 +++++++++--- dhcp.h | 2 +- dhcpv6.c | 17 ++++++++++-- fwd.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fwd.h | 6 +++++ ndp.c | 15 ++++++++--- ndp.h | 4 +-- passt.h | 3 +++ 9 files changed, 171 insertions(+), 35 deletions(-) diff --git a/conf.c b/conf.c index 6892cea..4c53d33 100644 --- a/conf.c +++ b/conf.c @@ -46,6 +46,7 @@ #include "lineread.h" #include "isolation.h" #include "log.h" +#include "fwd.h" #include "vhost_user.h" #define NETNS_RUN_DIR "/run/netns" @@ -1099,10 +1100,12 @@ enum passt_modes conf_mode(int argc, char *argv[]) static void conf_print(const struct ctx *c) { char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; - char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ]; + char bufmac[ETH_ADDRSTRLEN]; int i; if (c->ifi4 > 0 || c->ifi6 > 0) { + char ifn[IFNAMSIZ]; + info("Template interface: %s%s%s%s%s", c->ifi4 > 0 ? if_indextoname(c->ifi4, ifn) : "", c->ifi4 > 0 ? " (IPv4)" : "", @@ -1144,20 +1147,22 @@ static void conf_print(const struct ctx *c) buf4, sizeof(buf4))); if (!c->no_dhcp) { - struct inany_addr_entry *e = first_v4(c); - uint32_t mask; - - mask = IN4_MASK(e->prefix_len); - - info("DHCP:"); - info(" assign: %s", - inet_ntop(AF_INET, inany_v4(&e->addr), - buf4, sizeof(buf4))); - info(" mask: %s", - inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); - info(" router: %s", - inet_ntop(AF_INET, &c->ip4.guest_gw, - buf4, sizeof(buf4))); + const struct inany_addr_entry *e; + + e = fwd_first_usable_addr(c, AF_INET, + INANY_ADDR_OBSERVED); + if (e) { + uint32_t mask = IN4_MASK(e->prefix_len); + + info("DHCP:"); + info(" assign: %s", + inany_ntop(&e->addr, buf4, sizeof(buf4))); + info(" mask: %s", + inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); + info(" router: %s", + inet_ntop(AF_INET, &c->ip4.guest_gw, + buf4, sizeof(buf4))); + } } for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) { @@ -1189,14 +1194,23 @@ static void conf_print(const struct ctx *c) else goto dns6; - info(" assign: %s", - inet_ntop(AF_INET6, &first_v6(c)->addr.a6, - buf6, sizeof(buf6))); - info(" router: %s", - inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); - info(" our link-local: %s", - inet_ntop(AF_INET6, &c->ip6.our_tap_ll, - buf6, sizeof(buf6))); + { + const struct inany_addr_entry *e; + + e = fwd_first_usable_addr(c, AF_INET6, + INANY_ADDR_OBSERVED | + INANY_ADDR_LINKLOCAL); + if (e) { + info(" assign: %s", + inany_ntop(&e->addr, buf6, sizeof(buf6))); + info(" router: %s", + inet_ntop(AF_INET6, &c->ip6.guest_gw, + buf6, sizeof(buf6))); + info(" our link-local: %s", + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, + buf6, sizeof(buf6))); + } + } dns6: for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i]); i++) { diff --git a/dhcp.c b/dhcp.c index 933a8cf..31ea4f1 100644 --- a/dhcp.c +++ b/dhcp.c @@ -31,6 +31,7 @@ #include "passt.h" #include "tap.h" #include "log.h" +#include "fwd.h" #include "dhcp.h" /** @@ -300,8 +301,9 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len) * * Return: 0 if it's not a DHCP message, 1 if handled, -1 on failure */ -int dhcp(const struct ctx *c, struct iov_tail *data) +int dhcp(struct ctx *c, struct iov_tail *data) { + union inany_addr yiaddr = { 0 }; char macstr[ETH_ADDRSTRLEN]; size_t mlen, dlen, opt_len; struct in_addr mask, dst; @@ -314,7 +316,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) struct msg m_storage; struct msg const *m; struct msg reply; - unsigned int i; + int i; eh = IOV_REMOVE_HEADER(data, eh_storage); iph = IOV_PEEK_HEADER(data, iph_storage); @@ -344,6 +346,15 @@ int dhcp(const struct ctx *c, struct iov_tail *data) m->op != BOOTREQUEST) return -1; + /* Select address to offer */ + { + const struct inany_addr_entry *e; + + e = fwd_select_addr(c, AF_INET, INANY_ADDR_DHCP, + INANY_ADDR_OBSERVED); + if (e) + yiaddr = e->addr; + } reply.op = BOOTREPLY; reply.htype = m->htype; reply.hlen = m->hlen; @@ -352,7 +363,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) reply.secs = 0; reply.flags = m->flags; reply.ciaddr = m->ciaddr; - reply.yiaddr = *inany_v4(&first_v4(c)->addr); + reply.yiaddr = *inany_v4(&yiaddr); reply.siaddr = 0; reply.giaddr = m->giaddr; memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); diff --git a/dhcp.h b/dhcp.h index cd50c99..7326c7d 100644 --- a/dhcp.h +++ b/dhcp.h @@ -6,7 +6,7 @@ #ifndef DHCP_H #define DHCP_H -int dhcp(const struct ctx *c, struct iov_tail *data); +int dhcp(struct ctx *c, struct iov_tail *data); void dhcp_init(void); #endif /* DHCP_H */ diff --git a/dhcpv6.c b/dhcpv6.c index 67307d9..32494e9 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -31,6 +31,7 @@ #include "passt.h" #include "tap.h" #include "log.h" +#include "fwd.h" /** * struct opt_hdr - DHCPv6 option header @@ -564,6 +565,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, struct opt_hdr client_id_storage; /* cppcheck-suppress [variableScope,unmatchedSuppression] */ struct opt_ia_na ia_storage; + struct in6_addr *ia_addr = NULL; const struct in6_addr *src; struct msg_hdr mh_storage; const struct msg_hdr *mh; @@ -612,6 +614,17 @@ 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; + /* Select address to assign */ + { + struct inany_addr_entry *e; + + e = fwd_select_addr(c, AF_INET6, INANY_ADDR_DHCP6, + INANY_ADDR_OBSERVED | INANY_ADDR_LINKLOCAL); + if (e) { + ia_addr = &e->addr.a6; + resp.ia_addr.addr = *ia_addr; + } + } resp.hdr.type = TYPE_REPLY; switch (mh->type) { case TYPE_REQUEST: @@ -624,7 +637,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, if (mh->type == TYPE_CONFIRM && server_id) return -1; - if (dhcpv6_ia_notonlink(data, &first_v6(c)->addr.a6)) { + if (dhcpv6_ia_notonlink(data, ia_addr)) { dhcpv6_send_ia_notonlink(c, data, &client_id_base, ntohs(client_id->l), mh->xid); @@ -701,5 +714,5 @@ void dhcpv6_init(const struct ctx *c) memcpy(resp_not_on_link.server_id.duid_lladdr, c->our_tap_mac, sizeof(c->our_tap_mac)); - resp.ia_addr.addr = first_v6(c)->addr.a6; + /* Address is set dynamically in dhcpv6() */ } diff --git a/fwd.c b/fwd.c index fc26eba..e41ae9b 100644 --- a/fwd.c +++ b/fwd.c @@ -491,6 +491,88 @@ static bool is_dns_flow(uint8_t proto, const struct flowside *ini) ((ini->oport == 53) || (ini->oport == 853)); } +/** + * fwd_first_usable_addr() - Find first assignable address (read-only) + * @c: Execution context + * @af: Address family (AF_INET or AF_INET6) + * @skip: Flags to skip (e.g., INANY_ADDR_LINKLOCAL | INANY_ADDR_OBSERVED) + * + * Prefers CONFIGURED over HOST addresses. + * + * Return: pointer to first usable address, or NULL + */ +const struct inany_addr_entry * +fwd_first_usable_addr(const struct ctx *c, int af, int skip) +{ + const struct inany_addr_entry *e; + + for_each_addr(c, e, af) { + if (e->flags & skip) + continue; + if (e->flags & INANY_ADDR_CONFIGURED) + return e; + } + + for_each_addr(c, e, af) { + if (e->flags & skip) + continue; + if (e->flags & INANY_ADDR_HOST) + return e; + } + + return NULL; +} + +/** + * fwd_select_addr() - Select address for protocol assignment (DHCP/NDP/DHCPv6) + * @c: Execution context + * @af: Address family (AF_INET or AF_INET6) + * @assign_flag: Flag to set on selected address (INANY_ADDR_DHCP, etc.) + * @skip: Flags to skip (e.g., INANY_ADDR_LINKLOCAL | INANY_ADDR_OBSERVED) + * + * Selection priority: + * 1. Address already having @assign_flag set + * 2. Address with INANY_ADDR_CONFIGURED (user -a option) + * 3. Address with INANY_ADDR_HOST (from template interface) + * + * Return: pointer to selected address entry, or NULL if none found + */ +struct inany_addr_entry *fwd_select_addr(struct ctx *c, int af, + int assign_flag, int skip) +{ + struct inany_addr_entry *e; + + /* First: find address already having assign_flag */ + for_each_addr(c, e, af) { + if (e->flags & skip) + continue; + if (e->flags & assign_flag) + return e; + } + + /* Second: find CONFIGURED address, set assign_flag */ + for_each_addr(c, e, af) { + if (e->flags & skip) + continue; + if (e->flags & INANY_ADDR_CONFIGURED) { + e->flags |= assign_flag; + return e; + } + } + + /* Third: find HOST address, set assign_flag */ + for_each_addr(c, e, af) { + if (e->flags & skip) + continue; + if (e->flags & INANY_ADDR_HOST) { + e->flags |= assign_flag; + return e; + } + } + + return NULL; +} + /** * fwd_guest_addr4() - Get first observed IPv4 guest address * @c: Execution context diff --git a/fwd.h b/fwd.h index 7792582..91b15dc 100644 --- a/fwd.h +++ b/fwd.h @@ -9,6 +9,7 @@ union inany_addr; struct flowside; +struct inany_addr_entry; /* Number of ports for both TCP and UDP */ #define NUM_PORTS (1U << 16) @@ -63,4 +64,9 @@ void fwd_neigh_mac_get(const struct ctx *c, const union inany_addr *addr, uint8_t *mac); void fwd_neigh_table_init(const struct ctx *c); +const struct inany_addr_entry *fwd_first_usable_addr(const struct ctx *c, + int af, int skip); +struct inany_addr_entry *fwd_select_addr(struct ctx *c, int af, + int assign_flag, int skip); + #endif /* FWD_H */ diff --git a/ndp.c b/ndp.c index f60c298..2211f86 100644 --- a/ndp.c +++ b/ndp.c @@ -32,6 +32,7 @@ #include "passt.h" #include "tap.h" #include "log.h" +#include "fwd.h" #define RT_LIFETIME 65535 @@ -236,7 +237,7 @@ void ndp_unsolicited_na(const struct ctx *c, const struct in6_addr *addr) * @c: Execution context * @dst: IPv6 address to send the RA to */ -static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) +static void ndp_ra(struct ctx *c, const struct in6_addr *dst) { struct ndp_ra ra = { .ih = { @@ -257,7 +258,7 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) .valid_lifetime = ~0U, .pref_lifetime = ~0U, }, - .prefix = first_v6(c)->addr.a6, + .prefix = IN6ADDR_ANY_INIT, .source_ll = { .header = { .type = OPT_SRC_L2_ADDR, @@ -265,8 +266,14 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) }, }, }; + struct inany_addr_entry *e; unsigned char *ptr = NULL; + /* Select address/prefix to advertise */ + e = fwd_select_addr(c, AF_INET6, INANY_ADDR_NDP, INANY_ADDR_LINKLOCAL); + if (e) + ra.prefix = e->addr.a6; + ptr = &ra.var[0]; if (c->mtu) { @@ -353,7 +360,7 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) * * Return: 0 if not handled here, 1 if handled, -1 on failure */ -int ndp(const struct ctx *c, const struct in6_addr *saddr, +int ndp(struct ctx *c, const struct in6_addr *saddr, struct iov_tail *data) { struct icmp6hdr ih_storage; @@ -407,7 +414,7 @@ static time_t next_ra; * @c: Execution context * @now: Current (monotonic) time */ -void ndp_timer(const struct ctx *c, const struct timespec *now) +void ndp_timer(struct ctx *c, const struct timespec *now) { time_t max_rtr_adv_interval = DEFAULT_MAX_RTR_ADV_INTERVAL; time_t min_rtr_adv_interval, interval; diff --git a/ndp.h b/ndp.h index 56b756d..726aef6 100644 --- a/ndp.h +++ b/ndp.h @@ -8,9 +8,9 @@ struct icmp6hdr; -int ndp(const struct ctx *c, const struct in6_addr *saddr, +int ndp(struct ctx *c, const struct in6_addr *saddr, struct iov_tail *data); -void ndp_timer(const struct ctx *c, const struct timespec *now); +void ndp_timer(struct ctx *c, const struct timespec *now); void ndp_send_init_req(const struct ctx *c); void ndp_unsolicited_na(const struct ctx *c, const struct in6_addr *addr); diff --git a/passt.h b/passt.h index 7040781..f5f8c68 100644 --- a/passt.h +++ b/passt.h @@ -71,6 +71,9 @@ enum passt_modes { #define INANY_ADDR_HOST BIT(1) /* From host interface */ #define INANY_ADDR_OBSERVED BIT(2) /* Seen in guest traffic */ #define INANY_ADDR_LINKLOCAL BIT(3) /* Link-local address */ +#define INANY_ADDR_DHCP BIT(4) /* IPv4: assigned via DHCP */ +#define INANY_ADDR_DHCP6 BIT(5) /* IPv6: assigned via DHCPv6 */ +#define INANY_ADDR_NDP BIT(6) /* IPv6: prefix via NDP/RA */ /** * for_each_addr() - Iterate over addresses in unified array -- 2.52.0