On Mon, 1 May 2023 21:06:57 +1000 David Gibson <david(a)gibson.dropbear.id.au> wrote:Depending on several possible NAT situations, udp_update_hdr6() has a few different paths for setting the destination IPv6 address. However, this isn't really separate from the selection of the IPv6 source address: if the adjusted source address is link-local then we need to use a link local destination, otherwise we need the global destination address. This fixes a small bug: it's theoretically possible, although unlikely, for the dns_match address to be link-local, in which case the previous code would have used the wrong destination. This does do slightly more work in the case of NATting packets originating on the host. Currently we always NAT those to a link-local address so we could statically set a link-local destination address, but we now recheck. However, as well as being simpler, the new approach is more robust if we want to allow non-link-local NAT-to-host addresses, which we do plan to in future. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/udp.c b/udp.c index 361f24c..9c96c0f 100644 --- a/udp.c +++ b/udp.c @@ -640,18 +640,13 @@ static size_t udp_update_hdr6(const struct ctx *c, int n, in_port_t dstport, b->ip6h.payload_len = htons(udp6_l2_mh_sock[n].msg_len + sizeof(b->uh)); - if (IN6_IS_ADDR_LINKLOCAL(src)) { - b->ip6h.daddr = c->ip6.addr_ll_seen; - } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match) && + if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match) && IN6_ARE_ADDR_EQUAL(src, &c->ip6.dns_host) && src_port == 53) { - b->ip6h.daddr = c->ip6.addr_seen; src = &c->ip6.dns_match; } else if (IN6_IS_ADDR_LOOPBACK(src) || IN6_ARE_ADDR_EQUAL(src, &c->ip6.addr_seen) || IN6_ARE_ADDR_EQUAL(src, &c->ip6.addr)) { - b->ip6h.daddr = c->ip6.addr_ll_seen; -This part...udp_tap_map[V6][src_port].ts = now->tv_sec; udp_tap_map[V6][src_port].flags |= PORT_LOCAL; @@ -671,11 +666,13 @@ static size_t udp_update_hdr6(const struct ctx *c, int n, in_port_t dstport, src = &c->ip6.gw; else src = &c->ip6.addr_ll; - } else { - b->ip6h.daddr = c->ip6.addr_seen; } b->ip6h.saddr = *src; + if (IN6_IS_ADDR_LINKLOCAL(src)) + b->ip6h.daddr = c->ip6.addr_ll_seen; + else + b->ip6h.daddr = c->ip6.addr_seen;is not equivalent to this, and I guess not by intention. The rationale for setting a link-local destination address if the source address (on the host) is the loopback address (commit 86b273150a47 "tcp, udp: Allow binding ports in init namespace to both tap and loopback"... not a great description) is that we might otherwise collide with a global unicast destination address also assigned to the host. A link-local destination should be the only safe option. After all, that's what link-local addresses are for: surely a packet with that source address is local to the "link" to the guest. If the source address matches the configured or observed address (presumably local), the same reasoning applies: we want to actually send that packet to the guest, and signal that it's local. I guess this change might explain the failures you see: if we already saw a non-link-local address from the guest, we'll use that as destination, and the packet won't reach the guest. The rest looks correct to me, and also the rest of the series... just, as you mentioned, if we want to generalise this right away (and I think it's a better approach), patches from 3/7 on will be substantially different. -- Stefano