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=ej+jLvvA; dkim-atps=neutral Received: from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id 4BD025A0271 for ; Wed, 13 May 2026 07:04:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202602; t=1778648682; bh=66EqeecGWf+Yv2ilgbyOwHq2nTTfA9QsbRiq0kvcVGo=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=ej+jLvvA1Onmi9/Ff1HeJ5gvEs+T9D2VG78AwTgFeswggwGDbN4RfJu7revsOHHBA Ni1bAlQguM/6F9vHzmcUK+tRfkJJbS4IJkM9eWseO/X8WwMXVGOChLc9wAHYMqYIRX gJTEMfYZlb5quKZmF4ZAkgETtBtKLji5AzG2v/7R0ju5QYWCci4eZAetkr0ZhToIP7 V+sqmjXCLnzwIVpyZlgAIq90Sgjd6gaM4KzOiaZeRM/zpDFpGFtv4OHjZ6D3oRObax 0lfpMUBx3rtJ9hiLTBWgKg00NhL9hwy41kBkpneu/25/sBLsJ924qX4e20XX2ae3Cy A6CVu82aWuFUg== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4gFhGL4J1Yz4wCX; Wed, 13 May 2026 15:04:42 +1000 (AEST) Date: Wed, 13 May 2026 15:04:35 +1000 From: David Gibson To: Stefano Brivio Subject: Re: [PATCH RFT] fwd: Only do inbound IPv6 NAT to map_host_loopback / map_guest_addr with matching scope Message-ID: References: <20260507043149.1989693-1-sbrivio@redhat.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="tOr8KxWtVYifCkwr" Content-Disposition: inline In-Reply-To: <20260507043149.1989693-1-sbrivio@redhat.com> Message-ID-Hash: IDS2ST6N3L2ZJO6HTQQSC5TRN54LFATG X-Message-ID-Hash: IDS2ST6N3L2ZJO6HTQQSC5TRN54LFATG 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: passt-dev@passt.top, Paul Holzinger 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: --tOr8KxWtVYifCkwr Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Thu, May 07, 2026 at 06:31:49AM +0200, Stefano Brivio wrote: > I'm sharing this mostly for debugging / investigation of: >=20 > https://github.com/containers/container-libs/pull/755#issuecomment-4390= 420134 >=20 > even though the change is probably correct and needed regardless of > that. >=20 > If we have map_guest_addr or map_host_loopback addresses set for IPv6, > before using them for inbound NAT from the host, make sure they match > the scope of the original packet, otherwise we might unexpectedly > turn global unicast addresses into link-local ones for packets coming > from the host itself. >=20 > Link: https://github.com/containers/container-libs/pull/755#issuecomment-= 4390420134 > Signed-off-by: Stefano Brivio There's a real problem here. However, I don't think this patch really addresses it. Details below. > --- > fwd.c | 20 ++++++++++++++++++-- > 1 file changed, 18 insertions(+), 2 deletions(-) >=20 > diff --git a/fwd.c b/fwd.c > index 0697435..d224c0a 100644 > --- a/fwd.c > +++ b/fwd.c > @@ -974,6 +974,20 @@ uint8_t fwd_nat_from_splice(const struct fwd_rule *r= ule, uint8_t proto, > return PIF_HOST; > } > =20 > +/** > + * fwd_scope6_match() - Check if the IPv6 scope of two addresses match > + * @a: First address > + * @b: Second address > + * > + * Return: true for two IPv6 link-local or both not link-local, false ot= herwise > + * > + * NOTE: This currently ignores any other difference in scope > + */ Nit: we probably want this helper (or ones like it) in ip.h and/or inany.h. > +bool fwd_scope6_match(const struct in6_addr *a, const struct in6_addr *b) > +{ > + return IN6_IS_ADDR_LINKLOCAL(a) =3D=3D IN6_IS_ADDR_LINKLOCAL(b); This considers only linklocal vs. not linklocal. Officially those are the only two scopes for unicast IPv6. But... site-local unicast used to exist, and while it's deprecated we have once seen it in the wild. There's also host-local scope which I'm not sure is a term used by IPv6, but is used by kernel netlinkg, and kind of exists in practice (::1 and nothing else is host local). > +} > + > /** > * nat_inbound() - Apply address translation for inbound (HOST to TAP) > * @c: Execution context > @@ -993,13 +1007,15 @@ bool nat_inbound(const struct ctx *c, const union = inany_addr *addr, > /* Specifically 127.0.0.1, not 127.0.0.0/8 */ > *translated =3D inany_from_v4(c->ip4.map_host_loopback); > } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback) && > - inany_equals6(addr, &in6addr_loopback)) { > + inany_equals6(addr, &in6addr_loopback) && > + fwd_scope6_match(&addr->a6, &c->ip6.map_host_loopback)) { This test will always be false: we just checked that addr =3D=3D ::1, which is not link-local (it's host-local, if we're admitting that category). > translated->a6 =3D c->ip6.map_host_loopback; > } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && > inany_equals4(addr, &c->ip4.addr)) { > *translated =3D inany_from_v4(c->ip4.map_guest_addr); > } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) && > - inany_equals6(addr, &c->ip6.addr)) { > + inany_equals6(addr, &c->ip6.addr) && > + fwd_scope6_match(&addr->a6, &c->ip6.map_guest_addr)) { This may be usually be right in practice, but it's kind of by accident. The problem with both these checks is that they compare the scope of a host side address (addr) with the scope of a guest side address (c->map_*). That's not what matters: what matters is that source and destination on the tap side have the same scope. In flow table terms, that is, on a single flowside oaddr and eaddr must have the same scope (or must they? see later). The scopes on one side of the flow don't need to match the scopes on the other side of the flow. In fact we need to allow them to be different: --map-host-loopback is *always* transforming a host-scope flow on the outside to something else on the inside (either link-scope or global-scope will work, as long as it's the same for both addresses). We don't do it yet, but I can imagine cases where it would be useful to translate a flow that's global-scope on the outside to local-scope on the inside (because for some reason we want to or have to hide the external peer's address from the guest). Or from local-scope on the outside to global-scope on the inside (because the outside flow is using local-scope addresses which are not meaningful to the guest). This has some tricky implications for what we do about assigning addresses for "local mode" or any future variant where we need to assign a guest address, but can't take one from the host. If we assign a link-local address, as was our plan, that implies under this assumption that the guest can only talk to link-local machines. In practice that would mean only the host (via -map-*) or in future things we added explicit NATs, where the guest side address is link-local. The guest would have no ability to contact the internet at large. At least once we have the netlink monitor, maybe that's ok. While the host has no address, the guest has only link local, so it can only talk to the host (or explicitly configured forwards/NATs). But the host has no connectivity anyway, so there's nothing else to talk to anyway. When the host gets connectivity we add a global-scope guest address, so it gets connectivity too. If that's not good enough, I can only see two approaches, neither of which look great. a) For incoming connections from the world, to a guest with only an LL address, we NAT *both* source and destination address (ugh, the bookkeeping). Outgoing connections to the world are only possible for targets where we've preconfigured a NAT. b) We _do_ allow different scopes on the two guest-side addresses. This implies that the guest *expects and requires* their gateway (us) to SNAT them. I suspect that the guest simply won't allow (b) to work for IPv6, but it might for IPv4, since most things don't actually look for RFC3927 addresses, and NAT is much more expected in general. > translated->a6 =3D c->ip6.map_guest_addr; > } else if (fwd_guest_accessible(c, addr)) { > *translated =3D *addr; > --=20 > 2.43.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 --tOr8KxWtVYifCkwr Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmoEBmIACgkQzQJF27ox 2GfcYQ//UECW20qyvJvIRoBpFNb1dF9wZWVao+OTe6EhicSfVnf69PHpNuauHir8 9sES8w7HWjjh8zm5Q31uGo5eedUcJP5O3kXgnkIsBa361q8tqYOOTDOl+JBcGaIm 5ym/NVrBvRejXmdDfkIG5Ft6E2hK1tZ7EmiWSbWPmvus/vkbjE2SMgzw+IkX1r+J H47VrJhmhrek83EF7CyNpd7e2hz6U3ynubTdge3hBBx05sKihhIDd30PyMnmmvJN 3cWY36CgMZLM0E129HKvlGIeGfLimCPS8b0cm6lLxiQVZt2baQz/58lyGwykHXR/ UoFbmlBDowKG3z4xDio4lYtK3qeC+fhuWyR75g363gGKMsszlCKRVhwAMIB8re6F eYgx34KAt87NgT7NfRWjoKLbl1xBM2p224kJWn2YET46EekybQwU+SNS+fuUlWWN D8B1I2pSBqRPxRnBDtzudlCD4qttT0o/SYZNLmmKjz/mbXLMkRWKcRrHKevjU6zS ewFcUG9jGXSc6SQ6/xN3GRR52FHN/8Y3KyDFFvfEGRDLUi3f6TKxUyLs7PPDbdJT UbqjRuVESGIAcSLPB9wHjLXu4zgIQIFtx6drFLOK5pYI1dTa7qQdxkKMidL6N/6V JXesfkIOG+gkArJQrwHusxuWFCanwwlAGRTU44oV2sqHIVgEU+8= =kpo9 -----END PGP SIGNATURE----- --tOr8KxWtVYifCkwr--