* [PATCH pve-common 1/3] iproute2: restructure get_physical_bridge_ports
2026-07-09 13:28 [PATCH common/network 0/3] Performance improvements for applying VLAN / QinQ zones Stefan Hanreich
@ 2026-07-09 13:28 ` Stefan Hanreich
2026-07-09 13:28 ` [PATCH pve-common 2/3] iproute2: add helper for getting bridge port mappings Stefan Hanreich
2026-07-09 13:28 ` [PATCH pve-network 3/3] fix #7738: zones: use pre-computed bridge port list Stefan Hanreich
2 siblings, 0 replies; 4+ messages in thread
From: Stefan Hanreich @ 2026-07-09 13:28 UTC (permalink / raw)
To: pve-devel
By checking first if the interface is a member of the bridge, instead
of determining its type, a lot of unnecessary checks are not performed
since checking for membership first is a lot more discriminatory. When
applying a SDN configuration with 100 VLAN VNets on a non-VLAN-aware
bridge, this saves a significant amount of statements executed:
without this optimization:
Executed 438813 statements in 110ms
with this optimization:
Executed 3693 statements in 10.2ms
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
Notes:
This is a remnant from an earlier approach where only the
ip_link_details output was used, but the bridge ports were still
recalculated. Nevertheless I decided to still include this patch
instead of dropping it, since other call sites also profit from it.
src/PVE/IPRoute2.pm | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/PVE/IPRoute2.pm b/src/PVE/IPRoute2.pm
index 53a17b0..ae837dc 100644
--- a/src/PVE/IPRoute2.pm
+++ b/src/PVE/IPRoute2.pm
@@ -83,11 +83,11 @@ sub get_physical_bridge_ports($bridge, $ip_links = undef) {
}
return grep {
- (ip_link_is_physical($ip_links->{$_})
+ defined($ip_links->{$_}->{master})
+ && $ip_links->{$_}->{master} eq $bridge
+ && (ip_link_is_physical($ip_links->{$_})
|| ip_link_is_bond($ip_links->{$_})
|| ip_link_is_vlan($ip_links->{$_}))
- && defined($ip_links->{$_}->{master})
- && $ip_links->{$_}->{master} eq $bridge
} keys $ip_links->%*;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH pve-network 3/3] fix #7738: zones: use pre-computed bridge port list
2026-07-09 13:28 [PATCH common/network 0/3] Performance improvements for applying VLAN / QinQ zones Stefan Hanreich
2026-07-09 13:28 ` [PATCH pve-common 1/3] iproute2: restructure get_physical_bridge_ports Stefan Hanreich
2026-07-09 13:28 ` [PATCH pve-common 2/3] iproute2: add helper for getting bridge port mappings Stefan Hanreich
@ 2026-07-09 13:28 ` Stefan Hanreich
2 siblings, 0 replies; 4+ messages in thread
From: Stefan Hanreich @ 2026-07-09 13:28 UTC (permalink / raw)
To: pve-devel
The VLAN and QinQ zone utilized ip_link_details to obtain information
about the physical ports contained on a bridge. Instead of executing
iproute2 and computing the bridge ports for every VNet, precalculate
the bridge port list using the new helper in pve-common and then
re-use the ouput obtained at the start of the run.
Remove the get_bridge_ifaces function, since there are no longer any
call sites for this function.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/Network/SDN/Zones.pm | 3 +++
src/PVE/Network/SDN/Zones/Plugin.pm | 7 +------
src/PVE/Network/SDN/Zones/QinQPlugin.pm | 5 +++--
src/PVE/Network/SDN/Zones/VlanPlugin.pm | 5 +++--
src/test/run_test_zones.pl | 13 ++++++++++---
5 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/src/PVE/Network/SDN/Zones.pm b/src/PVE/Network/SDN/Zones.pm
index 4c1468cf..f1fb32d9 100644
--- a/src/PVE/Network/SDN/Zones.pm
+++ b/src/PVE/Network/SDN/Zones.pm
@@ -7,6 +7,7 @@ use JSON;
use PVE::Tools qw(extract_param dir_glob_regex run_command);
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::IPRoute2;
use PVE::Network;
use PVE::Network::SDN::Vnets;
@@ -117,6 +118,7 @@ sub generate_etc_network_config {
return if !$vnet_cfg && !$zone_cfg;
my $interfaces_config = PVE::INotify::read_file('interfaces');
+ my $bridge_ports = PVE::IPRoute2::get_bridge_port_mapping();
#generate configuration
my $config = {};
@@ -157,6 +159,7 @@ sub generate_etc_network_config {
$subnet_cfg,
$interfaces_config,
$config,
+ $bridge_ports,
);
};
if (my $err = $@) {
diff --git a/src/PVE/Network/SDN/Zones/Plugin.pm b/src/PVE/Network/SDN/Zones/Plugin.pm
index 74a3384c..184fca4e 100644
--- a/src/PVE/Network/SDN/Zones/Plugin.pm
+++ b/src/PVE/Network/SDN/Zones/Plugin.pm
@@ -5,7 +5,6 @@ use warnings;
use PVE::Tools qw(run_command);
use Net::IP qw(ip_get_version);
-use PVE::IPRoute2;
use PVE::JSONSchema;
use PVE::Cluster;
use PVE::Network;
@@ -112,6 +111,7 @@ sub generate_sdn_config {
$subnet_cfg,
$interfaces_config,
$config,
+ $bridge_ports,
) = @_;
die "please implement inside plugin";
@@ -385,11 +385,6 @@ sub is_vlanaware {
return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering");
}
-sub get_bridge_ifaces {
- my ($bridge) = @_;
- return PVE::IPRoute2::get_physical_bridge_ports($bridge);
-}
-
sub datacenter_config {
return PVE::Cluster::cfs_read_file('datacenter.cfg');
}
diff --git a/src/PVE/Network/SDN/Zones/QinQPlugin.pm b/src/PVE/Network/SDN/Zones/QinQPlugin.pm
index a75940c6..642420e9 100644
--- a/src/PVE/Network/SDN/Zones/QinQPlugin.pm
+++ b/src/PVE/Network/SDN/Zones/QinQPlugin.pm
@@ -64,6 +64,7 @@ sub generate_sdn_config {
$subnet_cfg,
$interfaces_config,
$config,
+ $bridge_ports,
) = @_;
my ($bridge, $mtu, $stag) = $plugin_config->@{ 'bridge', 'mtu', 'tag' };
@@ -128,9 +129,9 @@ sub generate_sdn_config {
} else {
# eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet
- my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
+ my $bridge_ifaces = $bridge_ports->{$bridge};
- for my $bridge_iface (@bridge_ifaces) {
+ for my $bridge_iface ($bridge_ifaces->@*) {
# use named vlan interface to avoid too long names
my $svlan_iface = "sv_$zoneid";
diff --git a/src/PVE/Network/SDN/Zones/VlanPlugin.pm b/src/PVE/Network/SDN/Zones/VlanPlugin.pm
index 9102b34f..271b3fab 100644
--- a/src/PVE/Network/SDN/Zones/VlanPlugin.pm
+++ b/src/PVE/Network/SDN/Zones/VlanPlugin.pm
@@ -63,6 +63,7 @@ sub generate_sdn_config {
$subnet_cfg,
$interfaces_config,
$config,
+ $bridge_ports,
) = @_;
my $bridge = $plugin_config->{bridge};
@@ -112,10 +113,10 @@ sub generate_sdn_config {
my $bridgevlan = $bridge . "v" . $tag;
- my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
+ my $bridge_ifaces = $bridge_ports->{$bridge};
my $bridge_ports = "";
- foreach my $bridge_iface (@bridge_ifaces) {
+ foreach my $bridge_iface ($bridge_ifaces->@*) {
$bridge_ports .= " $bridge_iface.$tag";
}
diff --git a/src/test/run_test_zones.pl b/src/test/run_test_zones.pl
index dd458b77..f8ef7375 100755
--- a/src/test/run_test_zones.pl
+++ b/src/test/run_test_zones.pl
@@ -55,6 +55,16 @@ foreach my $test (@tests) {
},
);
+ my $pve_common_iproute;
+ $pve_common_iproute = Test::MockModule->new('PVE::IPRoute2');
+ $pve_common_iproute->mock(
+ get_bridge_port_mapping => sub {
+ return {
+ vmbr0 => ["eth0"],
+ },;
+ },
+ );
+
my $pve_common_network;
$pve_common_network = Test::MockModule->new('PVE::Network');
$pve_common_network->mock(
@@ -94,9 +104,6 @@ foreach my $test (@tests) {
is_vlanaware => sub {
return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'};
},
- get_bridge_ifaces => sub {
- return ('eth0');
- },
find_bridge => sub {
return;
},
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread