From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 06F0C1FF0AB for ; Wed, 07 Oct 2026 08:46:09 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id E890F21351; Wed, 07 Oct 2026 08:46:05 +0200 (CEST) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH pve-network] sdn: zones: vxlan: enforce a single address family in the underlay Date: Wed, 7 Oct 2026 08:45:53 +0200 Message-ID: <20261007064553.164447-1-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1791355559241 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.492 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_MAILER 2 Automated Mailer Tag Left in Email RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: 7NWZXXN3KYQYV26TP7OEQXBW4EQQUEGN X-Message-ID-Hash: 7NWZXXN3KYQYV26TP7OEQXBW4EQQUEGN 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: Mixed peer lists are refused. The kernel fixes a vxlan device's address family at creation from vxlan-local-tunnelip, IPv4 without one, and refuses flood entries of the other family. ifupdown2 drops that error, so peers of the other family are silently missing. Only fabrics where all nodes share an address family can be used. If all nodes have both, IPv6 is preferred. Signed-off-by: Hannes Laimer --- should only be applied after [1], without it we cant have a v6 vxlan device to begin with [1] https://lore.proxmox.com/pve-devel/20260925134050.754528-1-h.laimer@proxmox.com/ src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 11 +--- src/PVE/Network/SDN/Zones/Plugin.pm | 13 +++++ src/PVE/Network/SDN/Zones/VxlanPlugin.pm | 19 +++++-- .../vxlan/fabric_ipv4/expected_sdn_interfaces | 27 ++++++++++ src/test/zones/vxlan/fabric_ipv4/interfaces | 5 ++ src/test/zones/vxlan/fabric_ipv4/sdn_config | 50 +++++++++++++++++ .../vxlan/fabric_ipv6/expected_sdn_interfaces | 33 ++++++++++++ src/test/zones/vxlan/fabric_ipv6/interfaces | 5 ++ src/test/zones/vxlan/fabric_ipv6/sdn_config | 54 +++++++++++++++++++ 9 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 src/test/zones/vxlan/fabric_ipv4/expected_sdn_interfaces create mode 100644 src/test/zones/vxlan/fabric_ipv4/interfaces create mode 100644 src/test/zones/vxlan/fabric_ipv4/sdn_config create mode 100644 src/test/zones/vxlan/fabric_ipv6/expected_sdn_interfaces create mode 100644 src/test/zones/vxlan/fabric_ipv6/interfaces create mode 100644 src/test/zones/vxlan/fabric_ipv6/sdn_config diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm index 4220cb67..e1c69603 100644 --- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm @@ -690,15 +690,8 @@ sub on_update_hook { || !($controller->{peers} || $controller->{fabric}); if ($controller->{peers}) { my @peers = PVE::Tools::split_list($controller->{peers}); - my $family; - - foreach my $peer (@peers) { - my $peer_family = Net::IP::ip_is_ipv6($peer) ? 6 : 4; - if (defined($family) && $family != $peer_family) { - die "peers must contain only IPv4 or only IPv6 addresses\n"; - } - $family = $peer_family; - } + die "peers must contain only IPv4 or only IPv6 addresses\n" + if !PVE::Network::SDN::Zones::Plugin::peers_family(\@peers); } } } diff --git a/src/PVE/Network/SDN/Zones/Plugin.pm b/src/PVE/Network/SDN/Zones/Plugin.pm index 74a3384c..85167b2c 100644 --- a/src/PVE/Network/SDN/Zones/Plugin.pm +++ b/src/PVE/Network/SDN/Zones/Plugin.pm @@ -286,6 +286,19 @@ sub ip_family { return defined($ip) ? ip_get_version($ip) : undef; } +# the family shared by every peer, undef when the list mixes families +sub peers_family { + my ($peers) = @_; + + my $family; + for my $peer (@$peers) { + my $peer_family = ip_family($peer); + return undef if defined($family) && $family != $peer_family; + $family = $peer_family; + } + return $family; +} + sub get_iface_addresses { my ($iface_cfg) = @_; diff --git a/src/PVE/Network/SDN/Zones/VxlanPlugin.pm b/src/PVE/Network/SDN/Zones/VxlanPlugin.pm index 8eb7e1c1..88d0c3b3 100644 --- a/src/PVE/Network/SDN/Zones/VxlanPlugin.pm +++ b/src/PVE/Network/SDN/Zones/VxlanPlugin.pm @@ -104,15 +104,20 @@ sub generate_sdn_config { my $current_node = eval { $config->get_node($plugin_config->{fabric}, $local_node) }; die "could not configure VXLAN zone $plugin_config->{id}: $@" if $@; + my $addr_key = PVE::Network::SDN::Controllers::EvpnPlugin::fabric_addr_key($nodes); die - "Node $local_node requires an IP in the fabric $fabric->{id} to configure the VXLAN zone $plugin_config->{id}" - if !$current_node->{ip}; + "Fabric $fabric->{id} has no consistent address family for all nodes (need all v6 or all v4)" + if !$addr_key; + + die + "Node $local_node requires a $addr_key address in the fabric $fabric->{id} to configure the VXLAN zone $plugin_config->{id}" + if !$current_node->{$addr_key}; for my $node (values %$nodes) { - push @peers, $node->{ip} if $node->{ip}; + push @peers, $node->{$addr_key} if $node->{$addr_key}; } - $ifaceip = $current_node->{ip}; + $ifaceip = $current_node->{$addr_key}; } else { die "neither peers nor fabric configured for VXLAN zone $plugin_config->{id}"; } @@ -164,6 +169,12 @@ sub on_update_hook { fabric => "must have exactly one of peers / fabric defined", }); } + + if ($zone->{peers}) { + my @peers = PVE::Tools::split_list($zone->{peers}); + raise_param_exc({ peers => "must contain only IPv4 or only IPv6 addresses" }) + if !PVE::Network::SDN::Zones::Plugin::peers_family(\@peers); + } } sub vnet_update_hook { diff --git a/src/test/zones/vxlan/fabric_ipv4/expected_sdn_interfaces b/src/test/zones/vxlan/fabric_ipv4/expected_sdn_interfaces new file mode 100644 index 00000000..61eeb710 --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv4/expected_sdn_interfaces @@ -0,0 +1,27 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 172.20.3.1 + vxlan_remoteip 172.20.3.2 + vxlan_remoteip 172.20.3.3 + mtu 1450 + +auto dummy_test +iface dummy_test inet static + address 172.20.3.1/32 + link-type dummy + ip-forward 1 + +auto ens18 +iface ens18 inet static + address 172.20.3.1/32 + ip-forward 1 diff --git a/src/test/zones/vxlan/fabric_ipv4/interfaces b/src/test/zones/vxlan/fabric_ipv4/interfaces new file mode 100644 index 00000000..68b6a886 --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv4/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/vxlan/fabric_ipv4/sdn_config b/src/test/zones/vxlan/fabric_ipv4/sdn_config new file mode 100644 index 00000000..eaaf3a9f --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv4/sdn_config @@ -0,0 +1,50 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { + myzone => { + ipam => "pve", + type => "vxlan", + fabric => "test", + }, + }, + }, + fabrics => { + ids => { + test => { + type => 'openfabric_fabric', + id => 'test', + ip_prefix => '172.20.3.0/24', + }, + test_localhost => { + id => 'test_localhost', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.1', + }, + test_node2 => { + id => 'test_node2', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.2', + }, + test_node3 => { + id => 'test_node3', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.3', + }, + }, + }, +} diff --git a/src/test/zones/vxlan/fabric_ipv6/expected_sdn_interfaces b/src/test/zones/vxlan/fabric_ipv6/expected_sdn_interfaces new file mode 100644 index 00000000..44b368a3 --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv6/expected_sdn_interfaces @@ -0,0 +1,33 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip fd00::1 + vxlan_remoteip fd00::2 + vxlan_remoteip fd00::3 + mtu 1450 + +auto dummy_test +iface dummy_test inet static + address 172.20.3.1/32 + link-type dummy + ip-forward 1 + +auto dummy_test +iface dummy_test inet6 static + address fd00::1/128 + link-type dummy + ip-forward 1 + +auto ens18 +iface ens18 inet static + address 172.20.3.1/32 + ip-forward 1 diff --git a/src/test/zones/vxlan/fabric_ipv6/interfaces b/src/test/zones/vxlan/fabric_ipv6/interfaces new file mode 100644 index 00000000..68b6a886 --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv6/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/vxlan/fabric_ipv6/sdn_config b/src/test/zones/vxlan/fabric_ipv6/sdn_config new file mode 100644 index 00000000..f463a886 --- /dev/null +++ b/src/test/zones/vxlan/fabric_ipv6/sdn_config @@ -0,0 +1,54 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { + myzone => { + ipam => "pve", + type => "vxlan", + fabric => "test", + }, + }, + }, + fabrics => { + ids => { + test => { + type => 'openfabric_fabric', + id => 'test', + ip_prefix => '172.20.3.0/24', + ip6_prefix => 'fd00::/64', + }, + test_localhost => { + id => 'test_localhost', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.1', + ip6 => 'fd00::1', + }, + test_node2 => { + id => 'test_node2', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.2', + ip6 => 'fd00::2', + }, + test_node3 => { + id => 'test_node3', + type => 'openfabric_node', + interfaces => [ + 'name=ens18', + ], + ip => '172.20.3.3', + ip6 => 'fd00::3', + }, + }, + }, +} -- 2.47.3