From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id A338C1FF0AA for ; Tue, 20 Oct 2026 16:04:15 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 53F5D216FC; Tue, 20 Oct 2026 16:04:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792410643; x=1793015443; darn=lists.proxmox.com; h=content-transfer-encoding:content-type:mime-version:message-id:date :to:subject:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Ges8Vo/MXBhiIuG/szi6NuLqmlhDcHqRtZmu1ic80go=; b=Ftxb4alVXlzoSmgONHz8uQ6Wa40f1YqXfftOTgcziD1qHsn0vxQk+dL3HaYCtom1c4 RDAuy3Bl6GEQvAbUXOE2zRtqBL6AKXVo8PajNkSPG+q3w4UqW+7e2WCSZUEiagV4du/s Qven2ixaFrZSDY6689AUEUYlaxcg4kzZnZM4onrgcK9KHvqvJwZlDYJLM7Grx2LRpb3b RwAozjNBdELc0TVzaf5D4AHuC3GtVfzKUOiHxe7wz2zPPAUuOhxo8RolrVqrWn07HtaX m8IFHYSvwMuf+b5/TCQoLBF2hOUDqcoOT0/VIeLy8lw3ynNzPHcdYCqcxNF9/SHS+mzX /tHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792410643; x=1793015443; h=content-transfer-encoding:content-type:mime-version:message-id:date :to:subject:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Ges8Vo/MXBhiIuG/szi6NuLqmlhDcHqRtZmu1ic80go=; b=vugxZLZxCPmAEC2IgyV6wjcrT14O3OabaK3Nlhd+6YzY203CqToDZluGLNMWmGI2nm //rSiMrR21zpaMbOc0zbh9SmRBJAWcUznA765UZQlsFybuOmI0Ni7nkcJJpWq0KDfV8m 5QXMWo5d3vucyR6NR+CouxZFavMBKLpuhYtx48sC2y+Dkwjsc5GJZ1J0a8HRBSRq0cGF 8ohIFw+QOyEeozUE+Kxls5e6vQtvV6Rff6cbTHGtGfKj4mSoQSdOdaSC12122lDTOGrc /xYJklwAxRAm+rSchQkafRXCnp7/lAtRxxxXatdUjvR3dncLA53qMgUZZ6H2OYPQdI1J Ej5g== X-Gm-Message-State: AFq9FYJQlibMJCAoLKTa+u5mKueNY4s67ZAl0KeaEM3FloGUE8+9VaVR TDtRvk+6SUO28ukiSfGvtbtEM/K3qrQLnj0Q63TSf4dTiZJN249xaIYY5t6Ey1vM X-Gm-Gg: AYBFou0ebaON7kRdLNIKMHskTBKOIS+9h9ltcWkMgd+W2wT4VVIr2MCyGFz07NJbqM9 fZWMBDUcJhcIYhzek5u9gOmNcrTQwAEWEq9BWz7d/Q1vAp1A/mlqpFWZ3uZ0nWQEed+hSsNEClc fZu9IAtM3Ibcl6im+KLSfEAt06ln62hrP46OXDIDkPHmdm6u6W9jv7iCWtbHaMdjn97+5b7XMg+ p/oYPfQiJKJjLTUqsR3j1+wpaULL7bKz5Lc320WI5gcLkq4LgX/MpUbrdagCq1q1I7uS7qxEO4i nz7HLUsVMxpI4kCHDd6kYKw+9uVVX7RMjfqNcr55nsQ/b18Hg0jxqoqy5MCFS6SED2nS809UVg9 HfZYTvP1lWfjwQ2JIoecYJU8sdIzcjvlAE4tmKZ6OEqAFnyEmHYwvBuV8BkXIv1aLz4qNDpXDOR gipQaPRT7On6ps/zAFZ8zq/tgtajyni8UBdoNN30RPmYxA1vUclXz/E69r0WvHD1/NR8p9kjkRu dC0vuDsuJ9LdbnES4XnY8zJbzDRrMpIOeQSZKiOdmbdDJYzluLW+0P2yghCgAPBDaJxc7h+bFte PgOENBOs0hEebc0N3FebczYEiq0udLAvxqIloNS0oMPHmbqXKg== X-Received: by 2002:a17:907:c48b:b0:c41:3e8b:8605 with SMTP id a640c23a62f3a-c41a08dfc69mr515353666b.43.1792410642659; Mon, 19 Oct 2026 04:50:42 -0700 (PDT) From: Dzidula Gati Subject: [PATCH network] sdn: zones: simple, evpn: do not stack duplicate snat rules To: pve-devel@lists.proxmox.com Date: Mon, 19 Oct 2026 11:50:39 +0000 Message-ID: <179241063976.803488.17141942444436193145@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy FREEMAIL_FROM 0.001 Sender email is commonly abused enduser mail provider RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: dzidulajubilee@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: 6DS7X6ESSZAGWD7F5V6MZTGPBI7FM4V3 X-Message-ID-Hash: 6DS7X6ESSZAGWD7F5V6MZTGPBI7FM4V3 X-Mailman-Approved-At: Tue, 20 Oct 2026 16:03:32 +0200 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The SNAT and conntrack zone rules are installed with plain `iptables -A`/`-I` in post-up. ifupdown2 re-runs post-up when an existing interface is reconfigured by `ifreload -a` (e.g. on every SDN apply), without running the old post-down first. Each apply therefore adds another copy of the same rules. Since post-down only removes a single exact match, the extra copies are never cleaned up. If the SNAT source address changed in the meantime (e.g. the node's IP was changed and SDN re-applied), the stale rules also stay in front of the new one, keep matching all traffic first and break outbound connectivity of the guests until they are removed by hand or the node is rebooted. Check whether the rule already exists before adding it, so that re-running post-up is idempotent. This does not remove rules that were left behind with an outdated source address, but it stops new duplicates from being created. Signed-off-by: Dzidula Gati --- Tested on PVE 9.2 / ifupdown2 3.3.0: the full test suite passes (zones/ipams/dns/subnets/vnets_blackbox, 235/235). Verified in a network namespace that running the generated post-up commands repeatedly leaves exactly one rule, and a single post-down removes it again. src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 15 +++++++++------ src/PVE/Network/SDN/Zones/SimplePlugin.pm | 15 +++++++++------ .../evpn/exitnode_snat/expected_sdn_interfaces | 8 ++++---- .../zones/simple/ipv4snat/expected_sdn_interfaces | 4 ++-- .../zones/simple/ipv6snat/expected_sdn_interfaces | 4 ++-- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm index 0e79707..5b4efbc 100644 --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -282,15 +282,18 @@ sub generate_sdn_config { } if ($outip && $outiface && $is_evpn_gateway) { #use snat, faster than masquerade + #check before adding, so that re-running post-up on ifreload does not + #stack duplicate rules + my $snat_rule = + "POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; push @iface_config, - "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - push @iface_config, - "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + "post-up $iptables -t nat -C $snat_rule 2>/dev/null || $iptables -t nat -A $snat_rule"; + push @iface_config, "post-down $iptables -t nat -D $snat_rule"; #add conntrack zone once on outgoing interface + my $ct_rule = "PREROUTING -i fwbr+ -j CT --zone 1"; push @iface_config, - "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; - push @iface_config, - "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; + "post-up $iptables -t raw -C $ct_rule 2>/dev/null || $iptables -t raw -I $ct_rule"; + push @iface_config, "post-down $iptables -t raw -D $ct_rule"; } } } diff --git a/src/PVE/Network/SDN/Zones/SimplePlugin.pm b/src/PVE/Network/SDN/Zones/SimplePlugin.pm index 347eee9..dac1b55 100644 --- a/src/PVE/Network/SDN/Zones/SimplePlugin.pm +++ b/src/PVE/Network/SDN/Zones/SimplePlugin.pm @@ -125,15 +125,18 @@ sub generate_sdn_config { } if ($outip && $outiface) { #use snat, faster than masquerade + #check before adding, so that re-running post-up on ifreload does not + #stack duplicate rules + my $snat_rule = + "POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; push @iface_config, - "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - push @iface_config, - "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + "post-up $iptables -t nat -C $snat_rule 2>/dev/null || $iptables -t nat -A $snat_rule"; + push @iface_config, "post-down $iptables -t nat -D $snat_rule"; #add conntrack zone once on outgoing interface + my $ct_rule = "PREROUTING -i fwbr+ -j CT --zone 1"; push @iface_config, - "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; - push @iface_config, - "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; + "post-up $iptables -t raw -C $ct_rule 2>/dev/null || $iptables -t raw -I $ct_rule"; + push @iface_config, "post-down $iptables -t raw -D $ct_rule"; } } } diff --git a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces index 0d7d174..8c69da4 100644 --- a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces +++ b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces @@ -3,9 +3,9 @@ auto myvnet iface myvnet address 10.0.0.1/24 - post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up iptables -t nat -C POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 2>/dev/null || iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-up iptables -t raw -C PREROUTING -i fwbr+ -j CT --zone 1 2>/dev/null || iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 bridge_ports vxlan_myvnet bridge_stp off @@ -18,9 +18,9 @@ iface myvnet auto myvnet2 iface myvnet2 address 2a08:2142:302:3::1/64 - post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 + post-up ip6tables -t nat -C POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 2>/dev/null || ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 - post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-up ip6tables -t raw -C PREROUTING -i fwbr+ -j CT --zone 1 2>/dev/null || ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 bridge_ports vxlan_myvnet2 bridge_stp off diff --git a/src/test/zones/simple/ipv4snat/expected_sdn_interfaces b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces index 69d7986..c527d41 100644 --- a/src/test/zones/simple/ipv4snat/expected_sdn_interfaces +++ b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces @@ -3,9 +3,9 @@ auto myvnet iface myvnet address 10.0.0.1/24 - post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up iptables -t nat -C POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 2>/dev/null || iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-up iptables -t raw -C PREROUTING -i fwbr+ -j CT --zone 1 2>/dev/null || iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 bridge_ports none bridge_stp off diff --git a/src/test/zones/simple/ipv6snat/expected_sdn_interfaces b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces index 5f6d40b..7075c51 100644 --- a/src/test/zones/simple/ipv6snat/expected_sdn_interfaces +++ b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces @@ -3,9 +3,9 @@ auto myvnet iface myvnet address 2a08:2142:302:3::1/64 - post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 + post-up ip6tables -t nat -C POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 2>/dev/null || ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 2001:db8::2 - post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-up ip6tables -t raw -C PREROUTING -i fwbr+ -j CT --zone 1 2>/dev/null || ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 bridge_ports none bridge_stp off -- 2.43.0