On Sun, Feb 22, 2026 at 12:44:44PM -0500, 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 > --- > ndp.c | 127 +++++++++++++++++++++++++++++++++++++++------------------- > 1 file changed, 85 insertions(+), 42 deletions(-) > > diff --git a/ndp.c b/ndp.c > index ed8c6ae..cf64f3f 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 > * @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: header + prefixes + source_ll + mtu + rdnss + dnssl */ > +#define NDP_RA_MAX_SIZE (sizeof(struct ndp_ra_hdr) + \ > + INANY_MAX_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,46 @@ 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) > +{ > + int skip = CONF_ADDR_OBSERVED | CONF_ADDR_LINKLOCAL; > + const struct inany_addr_entry *e; > + struct ndp_prefix *p; > + size_t offset = 0; > + > + for_each_addr(e, c, AF_INET6) { > + if (e->flags & skip) Again, this will skip OBSERVED addresses that are also USER|HOST. > + continue; > + /* SLAAC requires /64 prefix */ > + if (e->prefix_len != 64) > + 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 = e->addr.a6; > + > + offset += sizeof(struct ndp_prefix); > + } > + > + return offset; > +} > + > /** > * ndp_ra() - Send an NDP Router Advertisement (RA) message > * @c: Execution context > @@ -238,7 +286,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,32 +303,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, > - }, > - .prefix = IN6ADDR_ANY_INIT, > - .source_ll = { > - .header = { > - .type = OPT_SRC_L2_ADDR, > - .len = 1, > - }, > - }, > }; > - struct inany_addr_entry *e = first_v6(c); > - unsigned char *ptr = NULL; > - > - ASSERT(e); > > - ra.prefix = e->addr.a6; > + /* Fill prefix options */ > + prefix_len = ndp_prefix_fill(c, buf + sizeof(struct ndp_ra_hdr)); > + if (prefix_len == 0) { > + /* No suitable prefixes to advertise */ > + return; > + } > > - ptr = &ra.var[0]; > + /* Add source link-layer address option */ > + ptr = buf + sizeof(struct ndp_ra_hdr) + prefix_len; Somewhat idiomatic trick you can use here is ptr = (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; > @@ -346,10 +392,7 @@ 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); > } > > /** > -- > 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