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 A8E931FF0B4 for ; Tue, 08 Sep 2026 12:18:30 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 7FC1B21595; Tue, 08 Sep 2026 12:18:18 +0200 (CEST) From: Christoph Heiss To: pve-devel@lists.proxmox.com Subject: [PATCH installer 10/15] gui: network: add bond setup options to advanced options dialog Date: Tue, 8 Sep 2026 12:16:24 +0200 Message-ID: <20260908101647.1057780-11-c.heiss@proxmox.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260908101647.1057780-1-c.heiss@proxmox.com> References: <20260908101647.1057780-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1788862662343 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.365 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) 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: T5WS6MJFKFVCVI43CG3OWBS4PSYNYWSP X-Message-ID-Hash: T5WS6MJFKFVCVI43CG3OWBS4PSYNYWSP X-MailFrom: c.heiss@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: Partially fixes #2164 [0]. Adds a second frame to the advanced network options dialog, allowing to enable bonding and setting all the required options for it; such as members, mode, hash policy and primary bond member, as required. [0] https://bugzilla.proxmox.com/show_bug.cgi?id=2164 Signed-off-by: Christoph Heiss --- proxinstall | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 232 insertions(+), 6 deletions(-) diff --git a/proxinstall b/proxinstall index e3a4b20..d90d0ba 100755 --- a/proxinstall +++ b/proxinstall @@ -85,7 +85,9 @@ my @steps = ( ); # GUI global variables -my $gtk_state = {}; +my $gtk_state = { + network_bonding_enabled => 0, +}; my $target_hds; # only for the summary view @@ -468,6 +470,179 @@ my sub create_network_interface_pin_frame { return ($frame, $on_done); } +my sub create_network_bond_frame { + my $frame = Gtk3::Frame->new('Management Bond'); + + my $vbox = Gtk3::Box->new('vertical', 5); + $frame->add($vbox); + $vbox->set_margin_start(10); + $vbox->set_margin_end(10); + $vbox->set_margin_top(5); + $vbox->set_margin_bottom(5); + + my $enable_checkbox = Gtk3::CheckButton->new('Enable bonding for management interface'); + $enable_checkbox->set_active($gtk_state->{network_bonding_enabled}); + $vbox->pack_start($enable_checkbox, 1, 0, 5); + + my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt(); + my $interfaces = Proxmox::Install::RunEnv::get()->{network}->{interfaces}; + my $name_mapping = Proxmox::Install::Config::get_network_interface_pin_map(); + + my $mode_combo = Gtk3::ComboBoxText->new(); + my @modes = qw(active-backup balance-alb balance-rr balance-tlb balance-xor broadcast 802.3ad); + for my $m (@modes) { + $mode_combo->append_text($m); + } + for my $i (0 .. $#modes) { + if ($modes[$i] eq $bond_opts->{mode}) { + $mode_combo->set_active($i); + last; + } + } + + my $hash_policy_combo = Gtk3::ComboBoxText->new(); + my @hash_policies = qw(layer2 layer2+3 layer3+4); + for my $p (@hash_policies) { + $hash_policy_combo->append_text($p); + } + for my $i (0 .. $#hash_policies) { + if ($hash_policies[$i] eq $bond_opts->{hash_policy}) { + $hash_policy_combo->set_active($i); + last; + } + } + + my $primary_if_input = Gtk3::Entry->new(); + $primary_if_input->set_tooltip_text('Name of the primary interface to set on the bond.'); + + my $primary_if = $bond_opts->{primary_interface}; + if (defined($primary_if)) { + $primary_if_input->set_text($name_mapping->{$primary_if} // $interfaces->{$primary_if}); + } + + my $on_mode_changed = sub { + my $mode = $mode_combo->get_active_text(); + $hash_policy_combo->set_sensitive($mode eq '802.3ad' || $mode eq 'balance-xor'); + $primary_if_input->set_sensitive($mode eq 'active-backup'); + }; + $mode_combo->signal_connect(changed => $on_mode_changed); + + my $opts_grid = $create_label_widget_grid->([ + ['Bond Mode', $mode_combo], + ['Hash Policy', $hash_policy_combo], + ['Primary Interface', $primary_if_input], + ]); + + $vbox->pack_start($opts_grid, 0, 0, 5); + + my $if_label = Gtk3::Label->new('Bond Interfaces:'); + $if_label->set_xalign(0.0); + $if_label->set_margin_top(5); + $vbox->pack_start($if_label, 0, 0, 0); + + my $checkboxes = {}; + for my $iface (values %$interfaces) { + next if !defined($iface->{pinned_id}); + + my $checkbox = Gtk3::CheckButton->new(''); + + my $is_member = grep { $_ eq $iface->{mac} } $bond_opts->{members}->@*; + $checkbox->set_active($is_member); + + $vbox->pack_start($checkbox, 0, 0, 5); + $checkboxes->{ $iface->{mac} } = $checkbox; + } + + my $set_sensitive = sub { + my ($active) = @_; + + $opts_grid->foreach(sub { + $_[0]->set_sensitive($active); + }); + + if ($active) { + # if activating all inputs, observe the rules applied by the mode change handler + $on_mode_changed->(Proxmox::Install::Config::get_mngmt_bond_opt('mode')); + } + + $if_label->set_sensitive($active); + for my $cb (values %$checkboxes) { + $cb->set_sensitive($active); + } + }; + $set_sensitive->($gtk_state->{network_bonding_enabled}); + + $enable_checkbox->signal_connect( + toggled => sub { + my $active = !!$enable_checkbox->get_active(); + + $gtk_state->{network_bonding_enabled} = $active; + $set_sensitive->($active); + }, + ); + + my $refresh_if_names = sub { + my ($pinning_active) = @_; + my $mapping = Proxmox::Install::Config::get_network_interface_pin_map(); + + for my $iface (values %$interfaces) { + next if !defined($checkboxes->{ $iface->{mac} }); + + my $name = + $pinning_active + ? $name_mapping->{ $iface->{mac} } + : $iface->{name}; + + my $label = "$name ($iface->{mac}, $iface->{driver}, $iface->{state})"; + $checkboxes->{ $iface->{mac} }->set_label($label); + } + }; + $refresh_if_names->($gtk_state->{network_pinning_enabled}); + + my $on_done = sub { + if (!$enable_checkbox->get_active()) { + Proxmox::Install::Config::set_mngmt_bond_opt('members', []); + return; + } + + my @members = + grep { $checkboxes->{$_}->get_active() } keys %$checkboxes; + + if (scalar(@members) < 2) { + die "at least 2 member interfaces are required for setting up bond\n"; + } + + Proxmox::Install::Config::set_mngmt_nic('bond0'); + Proxmox::Install::Config::set_mngmt_bond_opt('members', \@members); + Proxmox::Install::Config::set_mngmt_bond_opt('mode', $mode_combo->get_active_text()); + Proxmox::Install::Config::set_mngmt_bond_opt( + 'hash_policy', + $hash_policy_combo->get_active_text(), + ); + + my $primary_if = $primary_if_input->get_text(); + if (length($primary_if) > 0) { + # resolve the name to its corresponding MAC address + if ($gtk_state->{network_pinning_enabled}) { + my $mapping = Proxmox::Install::Config::get_network_interface_pin_map(); + while (my ($mac, $name) = each %$mapping) { + if ($primary_if eq $name) { + $primary_if = $mac; + last; + } + } + } else { + my $interfaces = Proxmox::Install::RunEnv::get()->{network}->{interfaces}; + $primary_if = $interfaces->{$primary_if}; + } + + Proxmox::Install::Config::set_mngmt_bond_opt('primary_interface', $primary_if); + } + }; + + return ($frame, $refresh_if_names, $on_done); +} + my sub create_network_advanced_options_view { my ($done_cb) = @_; @@ -486,9 +661,11 @@ my sub create_network_advanced_options_view { my $hbox = Gtk3::Box->new('vertical', 0); - my ($pin_frame, $pin_on_done) = create_network_interface_pin_frame(sub {}); + my ($bond_frame, $bond_refresh, $bond_on_done) = create_network_bond_frame(); + my ($pin_frame, $pin_on_done) = create_network_interface_pin_frame($bond_refresh); $hbox->pack_start($pin_frame, 0, 0, 10); + $hbox->pack_start($bond_frame, 0, 0, 10); $scrolled_window->add($hbox); $content->pack_start($scrolled_window, 1, 1, 10); @@ -498,6 +675,7 @@ my sub create_network_advanced_options_view { response => sub { eval { $pin_on_done->(); + $bond_on_done->(); }; if ($@) { Proxmox::UI::message($@, $dialog); @@ -573,7 +751,31 @@ sub create_ipconf_view { $device_cb->pack_start($cell, 0); $device_cb->add_attribute($cell, 'text', 1); + my $device_bond_label = Gtk3::Label->new('bond0'); + $device_bond_label->set_justify('center'); + $device_bond_label->set_margin_top(5); + $device_bond_label->set_margin_bottom(5); + my $refresh_device_cb = sub { + my $bond_active = Proxmox::Install::Config::get_mngmt_bond_enabled(); + if ($bond_active) { + my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt(); + + $device_bond_label->set_text( + "bond0 (mode: $bond_opts->{mode}, " + . scalar($bond_opts->{members}->@*) + . " members)", + ); + + $device_cb->set_visible(0); + $device_cb->set_no_show_all(1); + + return; + } + + $device_cb->set_visible(1); + $device_cb->set_no_show_all(0); + # clear all entries and re-add them with their new names my $active = $device_cb->get_active(); $device_model->clear(); @@ -711,6 +913,7 @@ sub create_ipconf_view { $grid->attach($label, 0, 0, 1, 1); $grid->attach($device_cb, 1, 0, 2, 1); + $grid->attach($device_bond_label, 1, 0, 2, 1); my $fqdn = Proxmox::Install::Config::get_fqdn(); my $hostname = $run_env->{network}->{hostname} || $iso_env->{product}; @@ -834,11 +1037,34 @@ sub create_ack_view { my $country = Proxmox::Install::Config::get_country(); my $mngmt_nic = Proxmox::Install::Config::get_mngmt_nic(); - my $iface = Proxmox::Install::RunEnv::get('network')->{interfaces}->{$mngmt_nic}; - + my $all_ifaces = Proxmox::Install::RunEnv::get('network')->{interfaces}; + my $iface = $all_ifaces->{$mngmt_nic}; my $nic_mapping = Proxmox::Install::Config::get_network_interface_pin_map(); - my $interface = - $gtk_state->{network_pinning_enabled} ? $nic_mapping->{ $iface->{mac} } : $iface->{name}; + + my $bond_opts = Proxmox::Install::Config::get_mngmt_bond_opt(); + + my $interface; + if (Proxmox::Install::Config::get_mngmt_bond_enabled()) { + my @member_names; + + # translate all bond members MACs to their final interface name, either pinned or not + for my $mac ($bond_opts->{members}->@*) { + if ($gtk_state->{network_pinning_enabled}) { + push @member_names, $nic_mapping->{$mac}; + } else { + my ($if) = grep { $_->{mac} eq $mac } values %$all_ifaces; + push @member_names, $if->{name}; + } + } + + my $members = join(' | ', @member_names); + $interface = "bond0 (mode: $bond_opts->{mode}, interfaces: $members)"; + } else { + $interface = + $gtk_state->{network_pinning_enabled} + ? $nic_mapping->{ $iface->{mac} } + : $iface->{name}; + } my %config_values = ( __target_hd__ => join(' | ', $target_hds->@*), -- 2.55.0