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 <[email protected]>
---
 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




Reply via email to