From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 290C61FF138 for ; Wed, 18 Feb 2026 11:23:43 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1E85518116; Wed, 18 Feb 2026 11:24:40 +0100 (CET) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH pve-network 2/3] sdn: evpn: accept untracked IPv6 NA on EVPN vnet bridges Date: Wed, 18 Feb 2026 11:23:46 +0100 Message-ID: <20260218102350.211294-3-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260218102350.211294-1-h.laimer@proxmox.com> References: <20260218102350.211294-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1771410231781 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.061 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: WE5WPCNPK326N5EEHYFADK3GIIFUPHS2 X-Message-ID-Hash: WE5WPCNPK326N5EEHYFADK3GIIFUPHS2 X-MailFrom: h.laimer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: In EVPN setups with per-node anycast first-hop gateways, guests may use the shared gateway link-local address as next-hop, while return traffic targets the guest GUA. If an exit node has no neighbor entry for that GUA, it sends an NS. Only the exit-node kernel tracks that NS state. Because ND traffic is seen across EVPN nodes, a non-exit node can receive the guest's NA without a matching local INCOMPLETE entry and can treat it as untracked. Ignoring that NA prevents neighbor learning and can break IPv6 return traffic. Set `accept_untracked_na=2`[1] on EVPN vnet bridges that have IPv6 subnets so valid NA replies are accepted in this distributed gateway topology. Router Advertisements can trigger this, but RA presence is neither a necessary nor a sufficient selector. Keying this to EVPN vnets with IPv6 subnets is therefore more robust, even though it is broader than just looking at whether RAs are enabled. Without this, deployments depend on pre-populated neighbor state (for example guest-initiated traffic/pings first), which is fragile and causes intermittent first-packet IPv6 failures. [1] https://docs.kernel.org/networking/ip-sysctl.html#proc-sys-net-ipv6-variables Signed-off-by: Hannes Laimer --- src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 14 +++++++++++++- .../evpn/exitnode_snat/expected_sdn_interfaces | 1 + .../evpn/exitnodenullroute/expected_sdn_interfaces | 1 + .../zones/evpn/ipv4ipv6/expected_sdn_interfaces | 1 + .../evpn/ipv4ipv6nogateway/expected_sdn_interfaces | 1 + src/test/zones/evpn/ipv6/expected_sdn_interfaces | 1 + .../evpn/ipv6underlay/expected_sdn_interfaces | 1 + 7 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm index 8e7ddfd..c2895bc 100644 --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -219,6 +219,7 @@ sub generate_sdn_config { my $address = {}; my $ipv4 = undef; my $ipv6 = undef; + my $has_ipv6_subnet = undef; my $enable_forward_v4 = undef; my $enable_forward_v6 = undef; my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); @@ -226,6 +227,7 @@ sub generate_sdn_config { my $subnet = $subnets->{$subnetid}; my $cidr = $subnet->{cidr}; my $mask = $subnet->{mask}; + my ($subnet_ip) = split(/\//, $cidr); my $gateway = $subnet->{gateway}; if ($gateway) { @@ -233,9 +235,16 @@ sub generate_sdn_config { $address->{$gateway} = 1; } + $has_ipv6_subnet = 1 if $subnet_ip && Net::IP::ip_is_ipv6($subnet_ip); + my $iptables = undef; my $checkrouteip = undef; - my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; + my $ipversion = 4; + if ($gateway) { + $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; + } elsif ($subnet_ip) { + $ipversion = Net::IP::ip_is_ipv6($subnet_ip) ? 6 : 4; + } if ($ipversion == 6) { $ipv6 = 1; @@ -278,6 +287,9 @@ sub generate_sdn_config { push @iface_config, "ip-forward on" if $enable_forward_v4; push @iface_config, "ip6-forward on" if $enable_forward_v6; push @iface_config, "arp-accept on" if $ipv4 || $ipv6; + push @iface_config, + "post-up echo 2 > /proc/sys/net/ipv6/conf/$vnetid/accept_untracked_na || true" + if $has_ipv6_subnet; push @iface_config, "vrf $vrf_iface" if $vrf_iface; push(@{ $config->{$vnetid} }, @iface_config) if !$config->{$vnetid}; diff --git a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces index 47df77a..e63c409 100644 --- a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces +++ b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces @@ -28,6 +28,7 @@ iface myvnet2 mtu 1450 ip6-forward on arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet2/accept_untracked_na || true vrf vrf_myzone auto vrf_myzone diff --git a/src/test/zones/evpn/exitnodenullroute/expected_sdn_interfaces b/src/test/zones/evpn/exitnodenullroute/expected_sdn_interfaces index 4bf5ccf..81a3b39 100644 --- a/src/test/zones/evpn/exitnodenullroute/expected_sdn_interfaces +++ b/src/test/zones/evpn/exitnodenullroute/expected_sdn_interfaces @@ -15,6 +15,7 @@ iface myvnet ip-forward on ip6-forward on arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet/accept_untracked_na || true vrf vrf_myzone auto myvnet2 diff --git a/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces index 7a5d741..7b1727b 100644 --- a/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces +++ b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces @@ -12,6 +12,7 @@ iface myvnet ip-forward on ip6-forward on arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet/accept_untracked_na || true vrf vrf_myzone auto vrf_myzone diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces index 378fa77..4d904be 100644 --- a/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces +++ b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces @@ -8,6 +8,7 @@ iface myvnet bridge_fd 0 mtu 1450 arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet/accept_untracked_na || true vrf vrf_myzone auto vrf_myzone diff --git a/src/test/zones/evpn/ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv6/expected_sdn_interfaces index b2bdbfe..f776122 100644 --- a/src/test/zones/evpn/ipv6/expected_sdn_interfaces +++ b/src/test/zones/evpn/ipv6/expected_sdn_interfaces @@ -10,6 +10,7 @@ iface myvnet mtu 1450 ip6-forward on arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet/accept_untracked_na || true vrf vrf_myzone auto vrf_myzone diff --git a/src/test/zones/evpn/ipv6underlay/expected_sdn_interfaces b/src/test/zones/evpn/ipv6underlay/expected_sdn_interfaces index 3b91f75..ab5988f 100644 --- a/src/test/zones/evpn/ipv6underlay/expected_sdn_interfaces +++ b/src/test/zones/evpn/ipv6underlay/expected_sdn_interfaces @@ -10,6 +10,7 @@ iface myvnet mtu 1450 ip6-forward on arp-accept on + post-up echo 2 > /proc/sys/net/ipv6/conf/myvnet/accept_untracked_na || true vrf vrf_myzone auto vrf_myzone -- 2.47.3