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=AHcZY9uO; 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 C7AEB5A026D for ; Fri, 26 Jun 2026 04:45:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1782441942; 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=CjTqALTMFZkVWSu2V8wmFGljCoRGCp/brC/o8MasLB8=; b=AHcZY9uOWoEH+zvK5Dv8DVqtZalrTdidtQUgZ9NZa74dUV4pZD0E0NkMbtOcT93e1Bi9wr cs2YT3yFycLAUzZ33Q6xQPGnwSMVKLup/gzyaAkvQ8MLWJ/oiB0w3pul4ELkunoVlSBukz 3nnTCRBBrmmUFQi/tpy4A05aKzkWuwQ= 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-661-nIuPYKX7MtqM-akGeZPzsA-1; Thu, 25 Jun 2026 22:45:36 -0400 X-MC-Unique: nIuPYKX7MtqM-akGeZPzsA-1 X-Mimecast-MFC-AGG-ID: nIuPYKX7MtqM-akGeZPzsA_1782441936 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-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 123AF1955DBF; Fri, 26 Jun 2026 02:45:36 +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 55B0E1956087; Fri, 26 Jun 2026 02:45:35 +0000 (UTC) From: Jon Maloy To: sbrivio@redhat.com, david@gibson.dropbear.id.au, jmaloy@redhat.com, passt-dev@passt.top Subject: [PATCH v8 13/14] ndp: Support advertising multiple prefixes in Router Advertisements Date: Thu, 25 Jun 2026 22:45:18 -0400 Message-ID: <20260626024519.3701556-14-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: AykLlJZ_gTtUmdVS1MAOlW4NKK-rw9ulXllG0qtYA5k_1782441936 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: EZH7VXOJOIKNC2JEMDXEEXKXMCW2FFJY X-Message-ID-Hash: EZH7VXOJOIKNC2JEMDXEEXKXMCW2FFJY 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 extend NDP to advertise all suitable IPv6 prefixes in Router Advertisements, per RFC 4861. Observed and link-local addresses, plus addresses with a prefix length != 64, are excluded. Signed-off-by: Jon Maloy --- v6: -Adapted to previous changes in series v7: -Adapted to previous changes in series -Use struct initializer for source link-layer address option -Other minor fixes based on feedback from Stefano v8: -Adapted to previous changes in series -Moved to before migration v3 commit -Some other minor fixes due to feedback from David. --- conf.c | 20 ++++++---- fwd.c | 2 + ndp.c | 120 +++++++++++++++++++++++++++++++++++++------------------- passt.h | 1 + 4 files changed, 96 insertions(+), 47 deletions(-) diff --git a/conf.c b/conf.c index 74368c2b..05e1ef14 100644 --- a/conf.c +++ b/conf.c @@ -868,7 +868,7 @@ static void conf_print(const struct ctx *c) } if (c->ifi6) { - bool has_dhcpv6 = false; + bool has_slaac = false, has_dhcpv6 = false; const char *head; if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) @@ -877,19 +877,25 @@ static void conf_print(const struct ctx *c) buf, sizeof(buf))); for_each_addr(a, c->addrs, c->addr_count, AF_INET6) { + if (a->flags & CONF_ADDR_SLAAC) + has_slaac = true; if (a->flags & CONF_ADDR_DHCPV6OFFER) has_dhcpv6 = true; } - if (c->no_ndp && !has_dhcpv6) + if (!has_slaac && !has_dhcpv6) goto dns6; - a = fwd_get_addr(c, AF_INET6, CONF_ADDR_ANY, - CONF_ADDR_LINKLOCAL); - if (!c->no_ndp && a) { + if (has_slaac) { info("NDP:"); - info(" assign: %s", - inany_ntop(&a->addr, buf, sizeof(buf))); + head = "assign: "; + for_each_addr(a, c->addrs, c->addr_count, AF_INET6) { + if (!(a->flags & CONF_ADDR_SLAAC)) + continue; + inany_ntop(&a->addr, buf, sizeof(buf)); + info(" %s %s/%d", head, buf, a->prefix_len); + head = " "; + } } if (has_dhcpv6) { diff --git a/fwd.c b/fwd.c index 2ebd5f24..d4100042 100644 --- a/fwd.c +++ b/fwd.c @@ -301,6 +301,8 @@ void fwd_set_addr(struct ctx *c, const union inany_addr *addr, } else { if (!c->no_dhcpv6) flags |= CONF_ADDR_DHCPV6OFFER; + if (!c->no_ndp && prefix_len == 64) + flags |= CONF_ADDR_SLAAC; } } diff --git a/ndp.c b/ndp.c index a6a79055..d548a019 100644 --- a/ndp.c +++ b/ndp.c @@ -32,6 +32,8 @@ #include "passt.h" #include "tap.h" #include "log.h" +#include "fwd.h" +#include "conf.h" #define RT_LIFETIME 65535 @@ -99,6 +101,16 @@ struct opt_prefix_info { uint32_t reserved; } __attribute__((packed)); +/** + * struct ndp_prefix - Prefix Information option with prefix + * @info: Prefix Information option header + * @prefix: IPv6 prefix + */ +struct ndp_prefix { + struct opt_prefix_info info; + struct in6_addr prefix; +} __attribute__((__packed__)); + /** * struct opt_mtu - Maximum transmission unit (MTU) option * @header: Option header @@ -140,27 +152,23 @@ struct opt_dnssl { } __attribute__((packed)); /** - * struct ndp_ra - NDP Router Advertisement (RA) message + * struct ndp_ra_hdr - NDP Router Advertisement fixed header * @ih: ICMPv6 header * @reachable: Reachability time, after confirmation (ms) * @retrans: Time between retransmitted NS messages (ms) - * @prefix_info: Prefix Information option - * @prefix: IPv6 prefix - * @mtu: MTU option - * @source_ll: Target link-layer address - * @var: Variable fields */ -struct ndp_ra { +struct ndp_ra_hdr { struct icmp6hdr ih; uint32_t reachable; uint32_t retrans; - struct opt_prefix_info prefix_info; - struct in6_addr prefix; - struct opt_l2_addr source_ll; +} __attribute__((__packed__)); - unsigned char var[sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) + - sizeof(struct opt_dnssl)]; -} __attribute__((packed, aligned(__alignof__(struct in6_addr)))); +/* Maximum RA message size: hdr + prefixes + source_ll + mtu + rdnss + dnssl */ +#define NDP_RA_MAX_SIZE (sizeof(struct ndp_ra_hdr) + \ + MAX_GUEST_ADDRS * sizeof(struct ndp_prefix) + \ + sizeof(struct opt_l2_addr) + \ + sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) + \ + sizeof(struct opt_dnssl)) /** * struct ndp_ns - NDP Neighbor Solicitation (NS) message @@ -231,6 +239,42 @@ void ndp_unsolicited_na(const struct ctx *c, const struct in6_addr *addr) ndp_na(c, &in6addr_ll_all_nodes, addr); } +/** + * ndp_prefix_fill() - Fill prefix options for all suitable addresses + * @c: Execution context + * @buf: Buffer to write prefix options into + * + * Fills buffer with Prefix Information options for all non-linklocal, + * non-observed addresses with prefix_len == 64 + * + * Return: number of bytes written + */ +static size_t ndp_prefix_fill(const struct ctx *c, unsigned char *buf) +{ + const struct guest_addr *a; + struct ndp_prefix *p; + size_t offset = 0; + + for_each_addr(a, c->addrs, c->addr_count, AF_INET6) { + if (!(a->flags & CONF_ADDR_SLAAC)) + continue; + + p = (struct ndp_prefix *)(buf + offset); + p->info.header.type = OPT_PREFIX_INFO; + p->info.header.len = 4; /* 4 * 8 = 32 bytes */ + p->info.prefix_len = 64; + p->info.prefix_flags = 0xc0; /* L, A flags */ + p->info.valid_lifetime = ~0U; + p->info.pref_lifetime = ~0U; + p->info.reserved = 0; + p->prefix = a->addr.a6; + + offset += sizeof(struct ndp_prefix); + } + + return offset; +} + /** * ndp_ra() - Send an NDP Router Advertisement (RA) message * @c: Execution context @@ -238,7 +282,15 @@ void ndp_unsolicited_na(const struct ctx *c, const struct in6_addr *addr) */ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) { - struct ndp_ra ra = { + unsigned char buf[NDP_RA_MAX_SIZE] + __attribute__((__aligned__(__alignof__(struct in6_addr)))); + struct ndp_ra_hdr *hdr = (struct ndp_ra_hdr *)buf; + struct opt_l2_addr *source_ll; + unsigned char *ptr; + size_t prefix_len; + + /* Build RA header */ + *hdr = (struct ndp_ra_hdr){ .ih = { .icmp6_type = RA, .icmp6_code = 0, @@ -247,33 +299,23 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) .icmp6_rt_lifetime = htons_constant(RT_LIFETIME), .icmp6_addrconf_managed = 1, }, - .prefix_info = { - .header = { - .type = OPT_PREFIX_INFO, - .len = 4, - }, - .prefix_len = 64, - .prefix_flags = 0xc0, /* prefix flags: L, A */ - .valid_lifetime = ~0U, - .pref_lifetime = ~0U, - }, - .source_ll = { - .header = { - .type = OPT_SRC_L2_ADDR, - .len = 1, - }, - }, }; - const struct guest_addr *a; - unsigned char *ptr = NULL; - a = fwd_get_addr(c, AF_INET6, CONF_ADDR_ANY, 0); - if (!a) - return; + /* Fill prefix options */ + prefix_len = ndp_prefix_fill(c, (unsigned char *)(hdr + 1)); - ra.prefix = a->addr.a6; + /* Add source link-layer address option */ + ptr = (unsigned char *)(hdr + 1) + prefix_len; + source_ll = (struct opt_l2_addr *)ptr; + *source_ll = (struct opt_l2_addr) { + .header = { + .type = OPT_SRC_L2_ADDR, + .len = 1, + }, + }; - ptr = &ra.var[0]; + memcpy(source_ll->mac, c->our_tap_mac, ETH_ALEN); + ptr += sizeof(struct opt_l2_addr); if (c->mtu) { struct opt_mtu *mtu = (struct opt_mtu *)ptr; @@ -347,10 +389,8 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) } } - memcpy(&ra.source_ll.mac, c->our_tap_mac, ETH_ALEN); - /* NOLINTNEXTLINE(clang-analyzer-security.PointerSub) */ - ndp_send(c, dst, &ra, ptr - (unsigned char *)&ra); + ndp_send(c, dst, buf, ptr - buf); } /** diff --git a/passt.h b/passt.h index 5fa21385..3dc5234e 100644 --- a/passt.h +++ b/passt.h @@ -84,6 +84,7 @@ struct guest_addr { #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_SLAAC BIT(7) /* Advertise via NDP/RA (/64) */ #define CONF_ADDR_ANY 0xff /* Match any flag */ }; -- 2.52.0