From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202602 header.b=EpwzOs3l; dkim-atps=neutral Received: from mail.ozlabs.org (gandalf.ozlabs.org [150.107.74.76]) by passt.top (Postfix) with ESMTPS id C45725A0265 for ; Wed, 25 Mar 2026 02:46:16 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202602; t=1774403174; bh=zc8pE6hiJ1H8kEdSO5wu3CA/JxBXJiStNjBjcwY4jls=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=EpwzOs3lOCxXjNgJJO/1BnGwz3u++6WtqA3ICt7G8jNYMHxWUDJeGxkM2xvUPAexM d4hFyOLJxv2I4tOisZOOtEU2KICR6iFnC48vzutib7yyndt6HCr1qUkvnilSzsSKxP 44ubwrrpKF/OrxdoX1Ov6haZtqqQLgkqdP8YzidM6Z7WuYu0r0EJqFldi9+x6uN4c4 TjxWSdWTO81CaKRRnwHDpGhs5Wwgdv5ZLknmDY95KsvYusFt7fzT6e+V9vqwnHCYOB jK62chrYx04TWVCNKongjhbIfZhxBw5bDKjtv2B8tCWdWvyrqNy/UdLrJOSotLEhqa 6MpOCsVF905VQ== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4fgV9y4Q8Qz4wSJ; Wed, 25 Mar 2026 12:46:14 +1100 (AEDT) Date: Wed, 25 Mar 2026 12:46:09 +1100 From: David Gibson To: Jon Maloy Subject: Re: [PATCH v6 13/13] ndp: Support advertising multiple prefixes in Router Advertisements Message-ID: References: <20260322004333.365713-1-jmaloy@redhat.com> <20260322004333.365713-14-jmaloy@redhat.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="QoIi+OrOh+/Q5dMy" Content-Disposition: inline In-Reply-To: <20260322004333.365713-14-jmaloy@redhat.com> Message-ID-Hash: 7U5CSH4QFQAV4ZNK22SX5BZ4YC5BDS7N X-Message-ID-Hash: 7U5CSH4QFQAV4ZNK22SX5BZ4YC5BDS7N X-MailFrom: dgibson@gandalf.ozlabs.org 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 CC: sbrivio@redhat.com, dgibson@redhat.com, passt-dev@passt.top 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: --QoIi+OrOh+/Q5dMy Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable 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 !=3D 64, are excluded. >=20 > Signed-off-by: Jon Maloy >=20 > --- > 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(-) >=20 > 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) > } > =20 > if (c->ifi6) { > - bool has_dhcpv6 =3D false; > + bool has_ndp =3D false, has_dhcpv6 =3D false; > const char *head; > =20 > if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) > @@ -1223,18 +1223,25 @@ static void conf_print(const struct ctx *c) > =20 > /* Check what we have to advertise */ > for_each_addr(a, c, AF_INET6) { > + if (a->flags & CONF_ADDR_NDP) > + has_ndp =3D true; > if (a->flags & CONF_ADDR_DHCPV6) > has_dhcpv6 =3D true; > } > =20 > - if (c->no_ndp && !has_dhcpv6) > + if (!has_ndp && !has_dhcpv6) > goto dns6; > =20 > - a =3D 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 =3D "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 =3D " "; > + } > } > =20 > 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_ad= dr *addr, > /* DHCPv6 for IPv6 */ > if (!c->no_dhcpv6) > flags |=3D CONF_ADDR_DHCPV6; > + /* NDP/RA only if /64 prefix, as required for SLAAC */ > + if (!c->no_ndp && prefix_len =3D=3D 64) > + flags |=3D CONF_ADDR_NDP; > } > } > =20 > 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) > =20 > /** > * 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 |=3D MIGRATE_ADDR_DHCP; > if (flags & CONF_ADDR_DHCPV6) > wire |=3D MIGRATE_ADDR_DHCPV6; > + if (flags & CONF_ADDR_NDP) > + wire |=3D MIGRATE_ADDR_NDP; > =20 > return wire; > } > @@ -115,6 +118,8 @@ static uint8_t flags_from_wire(uint8_t wire) > flags |=3D CONF_ADDR_DHCP; > if (wire & MIGRATE_ADDR_DHCPV6) > flags |=3D CONF_ADDR_DHCPV6; > + if (wire & MIGRATE_ADDR_NDP) > + flags |=3D CONF_ADDR_NDP; > =20 > 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" > =20 > #define RT_LIFETIME 65535 > =20 > @@ -82,7 +84,7 @@ struct ndp_na { > } __attribute__((packed)); > =20 > /** > - * 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)); > =20 > +/** > + * 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)); > =20 > /** > - * 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__)); > =20 > - 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 + d= nssl */ > +#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)) > =20 > /** > * struct ndp_ns - NDP Neighbor Solicitation (NS) message > @@ -231,6 +239,42 @@ void ndp_unsolicited_na(const struct ctx *c, const s= truct in6_addr *addr) > ndp_na(c, &in6addr_ll_all_nodes, addr); > } > =20 > +/** > + * 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 =3D=3D 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 =3D 0; > + > + for_each_addr(a, c, AF_INET6) { > + if (!(a->flags & CONF_ADDR_NDP)) > + continue; > + > + p =3D (struct ndp_prefix *)(buf + offset); > + p->info.header.type =3D OPT_PREFIX_INFO; > + p->info.header.len =3D 4; /* 4 * 8 =3D 32 bytes */ > + p->info.prefix_len =3D 64; > + p->info.prefix_flags =3D 0xc0; /* L, A flags */ > + p->info.valid_lifetime =3D ~0U; > + p->info.pref_lifetime =3D ~0U; > + p->info.reserved =3D 0; > + p->prefix =3D a->addr.a6; > + > + offset +=3D 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 s= truct in6_addr *addr) > */ > static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) > { > - struct ndp_ra ra =3D { > + unsigned char buf[NDP_RA_MAX_SIZE] > + __attribute__((__aligned__(__alignof__(struct in6_addr)))); > + struct ndp_ra_hdr *hdr =3D (struct ndp_ra_hdr *)buf; > + struct opt_l2_addr *source_ll; > + unsigned char *ptr; > + size_t prefix_len; > + > + /* Build RA header */ > + *hdr =3D (struct ndp_ra_hdr){ > .ih =3D { > .icmp6_type =3D RA, > .icmp6_code =3D 0, > @@ -247,31 +299,22 @@ static void ndp_ra(const struct ctx *c, const struc= t in6_addr *dst) > .icmp6_rt_lifetime =3D htons_constant(RT_LIFETIME), > .icmp6_addrconf_managed =3D 1, > }, > - .prefix_info =3D { > - .header =3D { > - .type =3D OPT_PREFIX_INFO, > - .len =3D 4, > - }, > - .prefix_len =3D 64, > - .prefix_flags =3D 0xc0, /* prefix flags: L, A */ > - .valid_lifetime =3D ~0U, > - .pref_lifetime =3D ~0U, > - }, > - .source_ll =3D { > - .header =3D { > - .type =3D OPT_SRC_L2_ADDR, > - .len =3D 1, > - }, > - }, > }; > - const struct guest_addr *a =3D fwd_get_addr(c, AF_INET6, 0, 0); > - unsigned char *ptr =3D NULL; > - > - ASSERT(a); > =20 > - ra.prefix =3D a->addr.a6; > + /* Fill prefix options */ > + prefix_len =3D ndp_prefix_fill(c, (unsigned char *)(hdr + 1)); > + if (prefix_len =3D=3D 0) { > + /* No suitable prefixes to advertise */ > + return; > + } > =20 > - ptr =3D &ra.var[0]; > + /* Add source link-layer address option */ > + ptr =3D (unsigned char *)(hdr + 1) + prefix_len; > + source_ll =3D (struct opt_l2_addr *)ptr; > + source_ll->header.type =3D OPT_SRC_L2_ADDR; > + source_ll->header.len =3D 1; > + memcpy(source_ll->mac, c->our_tap_mac, ETH_ALEN); > + ptr +=3D sizeof(struct opt_l2_addr); > =20 > if (c->mtu) { > struct opt_mtu *mtu =3D (struct opt_mtu *)ptr; > @@ -345,10 +388,8 @@ static void ndp_ra(const struct ctx *c, const struct= in6_addr *dst) > } > } > =20 > - 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); > } > =20 > /** > 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) */ > =20 > /** > * struct guest_addr - Unified IPv4/IPv6 address entry > --=20 > 2.52.0 >=20 --=20 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 --QoIi+OrOh+/Q5dMy Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmnDPmAACgkQzQJF27ox 2GeYBw//a3ro3SFpEwLhkxIADDJfQK0zIWxlrRjxGNRqIkZwnd4ffKggPrdNtDdY Q54sjQj4uM6kHNkpCkVsYnlJocDbP84MHCG4WKcT3n3u38tapTjdMAx0ANu6DIy+ sN/5OZgDQmpKDezFv0/u9l83Bn22U4jkWgL0wY1sq3/2muuh1xBwOjhBoX0fJVdH 3fM8nI0d9tIUsyAjDX8vtGIiSWc6tPMfFCixRDs7rtFnr5Zqs4oJFK3GeNRvmGLL ODkWpli3NzVUknj8/r20Ggp65c3Bj8s6dBWgZH+Ahz3OVbOEOAvqNbGGxkEtjYOA CtIyXxOYu/vUPrwg2rzcsD3PlhGbhitb8ko/qxC4tnXcff3+7Ju0Nucr4vS0Zob0 S01u0Fk1YW9fitVHVklJbDWmL4EeaWMtEavgvYV4c63p5gWgzjjrIPpfdWz/WIEM jmB6tpXZjZrhmequLec5aw3uXyXD9HDmgq/oAG04evxoRA75zN852/dPeJONxc8l XM6Lx+5FkIid/xQdCofpuGrh9H6NGtTY226H99K/GXW1HObqAWVGihOziW4jWEZp bVlAFCUiPLj5QdKbFl+km0rTCitDKUkEM8Ysz/DJm6LfQ45OteyBtqDRROxlb3hD wMJRXm7+02XyX/Fkcs+BmU+CfUlxRIqT7UVV0U9Vp7n9wwZb4E8= =6WNe -----END PGP SIGNATURE----- --QoIi+OrOh+/Q5dMy--