From: David Gibson <david@gibson.dropbear.id.au>
To: Jon Maloy <jmaloy@redhat.com>
Cc: sbrivio@redhat.com, dgibson@redhat.com, passt-dev@passt.top
Subject: Re: [PATCH v6 13/13] ndp: Support advertising multiple prefixes in Router Advertisements
Date: Wed, 25 Mar 2026 12:46:09 +1100 [thread overview]
Message-ID: <acM-YWCe2Gu1FQLK@zatzit> (raw)
In-Reply-To: <20260322004333.365713-14-jmaloy@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 10122 bytes --]
On Sat, Mar 21, 2026 at 08:43:33PM -0400, Jon Maloy wrote:
> 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 <jmaloy@redhat.com>
>
> ---
> v6: Adapted to previous changes in series
> ---
> conf.c | 19 ++++++---
> fwd.c | 3 ++
> migrate.c | 5 +++
> ndp.c | 121 ++++++++++++++++++++++++++++++++++++------------------
> passt.h | 1 +
> 5 files changed, 103 insertions(+), 46 deletions(-)
>
> diff --git a/conf.c b/conf.c
> index de2fb7c..b8dd40a 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1213,7 +1213,7 @@ static void conf_print(const struct ctx *c)
> }
>
> if (c->ifi6) {
> - bool has_dhcpv6 = false;
> + bool has_ndp = false, has_dhcpv6 = false;
> const char *head;
>
> if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
> @@ -1223,18 +1223,25 @@ static void conf_print(const struct ctx *c)
>
> /* Check what we have to advertise */
> for_each_addr(a, c, AF_INET6) {
> + if (a->flags & CONF_ADDR_NDP)
> + has_ndp = true;
> if (a->flags & CONF_ADDR_DHCPV6)
> has_dhcpv6 = true;
> }
>
> - if (c->no_ndp && !has_dhcpv6)
> + if (!has_ndp && !has_dhcpv6)
> goto dns6;
>
> - a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL);
> - if (!c->no_ndp && a) {
> + if (has_ndp) {
> info("NDP:");
> - inany_ntop(&a->addr, buf, sizeof(buf));
> - info(" assign: %s", buf);
> + head = "assign";
> + for_each_addr(a, c, AF_INET6) {
> + if (!(a->flags & CONF_ADDR_NDP))
> + 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 f867398..fe6e9d4 100644
> --- a/fwd.c
> +++ b/fwd.c
> @@ -305,6 +305,9 @@ void fwd_set_addr(struct ctx *c, const union inany_addr *addr,
> /* DHCPv6 for IPv6 */
> if (!c->no_dhcpv6)
> flags |= CONF_ADDR_DHCPV6;
> + /* NDP/RA only if /64 prefix, as required for SLAAC */
> + if (!c->no_ndp && prefix_len == 64)
> + flags |= CONF_ADDR_NDP;
> }
> }
>
> diff --git a/migrate.c b/migrate.c
> index 105f624..98c2d7e 100644
> --- a/migrate.c
> +++ b/migrate.c
> @@ -54,6 +54,7 @@ struct migrate_seen_addrs_v2 {
> #define MIGRATE_ADDR_OBSERVED BIT(3)
> #define MIGRATE_ADDR_DHCP BIT(4)
> #define MIGRATE_ADDR_DHCPV6 BIT(5)
> +#define MIGRATE_ADDR_NDP BIT(6)
>
> /**
> * struct migrate_addr_v3 - Wire format for a single address entry
> @@ -89,6 +90,8 @@ static uint8_t flags_to_wire(uint8_t flags)
> wire |= MIGRATE_ADDR_DHCP;
> if (flags & CONF_ADDR_DHCPV6)
> wire |= MIGRATE_ADDR_DHCPV6;
> + if (flags & CONF_ADDR_NDP)
> + wire |= MIGRATE_ADDR_NDP;
>
> return wire;
> }
> @@ -115,6 +118,8 @@ static uint8_t flags_from_wire(uint8_t wire)
> flags |= CONF_ADDR_DHCP;
> if (wire & MIGRATE_ADDR_DHCPV6)
> flags |= CONF_ADDR_DHCPV6;
> + if (wire & MIGRATE_ADDR_NDP)
> + flags |= CONF_ADDR_NDP;
>
> return flags;
> }
> diff --git a/ndp.c b/ndp.c
> index 3750fc5..f47286e 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
>
> @@ -82,7 +84,7 @@ struct ndp_na {
> } __attribute__((packed));
>
> /**
> - * struct opt_prefix_info - Prefix Information option
> + * struct opt_prefix_info - Prefix Information option header
Unrelated change.
> * @header: Option header
> * @prefix_len: The number of leading bits in the Prefix that are valid
> * @prefix_flags: Flags associated with the prefix
> @@ -99,6 +101,16 @@ struct opt_prefix_info {
> uint32_t reserved;
> } __attribute__((packed));
>
> +/**
> + * struct ndp_prefix - Complete 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 (required for SLAAC).
> + *
> + * 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, AF_INET6) {
> + if (!(a->flags & CONF_ADDR_NDP))
> + 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,31 +299,22 @@ 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 = fwd_get_addr(c, AF_INET6, 0, 0);
> - unsigned char *ptr = NULL;
> -
> - ASSERT(a);
>
> - ra.prefix = a->addr.a6;
> + /* Fill prefix options */
> + prefix_len = ndp_prefix_fill(c, (unsigned char *)(hdr + 1));
> + if (prefix_len == 0) {
> + /* No suitable prefixes to advertise */
> + return;
> + }
>
> - ptr = &ra.var[0];
> + /* Add source link-layer address option */
> + ptr = (unsigned char *)(hdr + 1) + prefix_len;
> + source_ll = (struct opt_l2_addr *)ptr;
> + source_ll->header.type = OPT_SRC_L2_ADDR;
> + source_ll->header.len = 1;
> + 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;
> @@ -345,10 +388,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 c4c1f04..c6f4406 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -77,6 +77,7 @@ enum passt_modes {
> #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) */
> +#define CONF_ADDR_NDP BIT(6) /* Advertise via NDP/RA (IPv6, /64) */
>
> /**
> * struct guest_addr - Unified IPv4/IPv6 address entry
> --
> 2.52.0
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
prev parent reply other threads:[~2026-03-25 1:46 UTC|newest]
Thread overview: 26+ 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-23 23:26 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 02/13] ip: Introduce unified multi-address data structures Jon Maloy
2026-03-24 3:31 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 03/13] fwd: Unify guest accessibility checks with unified address array Jon Maloy
2026-03-24 3:45 ` David Gibson
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-24 5:29 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 06/13] netlink, conf: Read all addresses from template interface at startup Jon Maloy
2026-03-24 5:36 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 07/13] ip: refactor function pasta_ns_conf() Jon Maloy
2026-03-24 5:49 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 08/13] ip: Track observed guest IPv4 addresses in unified address array Jon Maloy
2026-03-25 0:48 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 09/13] ip: Track observed guest IPv6 " Jon Maloy
2026-03-25 1:08 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 10/13] migrate: Update protocol to v3 for multi-address support Jon Maloy
2026-03-25 1:22 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 11/13] dhcp: Select address for DHCP distribution Jon Maloy
2026-03-25 1:26 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 12/13] dhcpv6: Select addresses for DHCPv6 distribution Jon Maloy
2026-03-25 1:40 ` David Gibson
2026-03-22 0:43 ` [PATCH v6 13/13] ndp: Support advertising multiple prefixes in Router Advertisements Jon Maloy
2026-03-25 1:46 ` David Gibson [this message]
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=acM-YWCe2Gu1FQLK@zatzit \
--to=david@gibson.dropbear.id.au \
--cc=dgibson@redhat.com \
--cc=jmaloy@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).