From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id C53051FF16F for ; Tue, 14 Oct 2025 15:22:47 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id DB8C83F00; Tue, 14 Oct 2025 15:22:23 +0200 (CEST) From: Christoph Heiss To: pve-devel@lists.proxmox.com Date: Tue, 14 Oct 2025 15:21:47 +0200 Message-ID: <20251014132207.1171073-3-c.heiss@proxmox.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251014132207.1171073-1-c.heiss@proxmox.com> References: <20251014132207.1171073-1-c.heiss@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1760448095821 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.038 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH installer 02/14] install: add support for network interface name pinning X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" From: Thomas Lamprecht Adds the (low-level) part for pinning network interface names during installation based on their MAC address. Adds a new option `pin_interface_name_map`, which maps MAC addresses to their respective new interface name. Co-authored-by: Thomas Lamprecht Signed-off-by: Christoph Heiss --- Proxmox/Install.pm | 47 +++++++++++++++- Proxmox/Install/Config.pm | 8 +++ Proxmox/Install/RunEnv.pm | 4 ++ Proxmox/Sys/Net.pm | 54 +++++++++++++++++++ ...n_from_dhcp_no_default_domain.run-env.json | 27 ++++++---- .../no_fqdn_from_dhcp.run-env.json | 27 ++++++---- .../tests/resources/run-env-info.json | 29 ++++++---- 7 files changed, 167 insertions(+), 29 deletions(-) diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm index 2ebd376..8d02c88 100644 --- a/Proxmox/Install.pm +++ b/Proxmox/Install.pm @@ -1101,6 +1101,49 @@ sub extract_data { my $cidr = Proxmox::Install::Config::get_cidr(); my $gateway = Proxmox::Install::Config::get_gateway(); + # configure pinned names for all network interfaces, if enabled + + my $netif_pin_map = Proxmox::Install::Config::get_network_interface_pin_map(); + if (defined($netif_pin_map)) { + mkdir "$targetdir/usr/local/lib/systemd", 0755; + mkdir "$targetdir/usr/local/lib/systemd/network", 0755; + + my $ip_links = Proxmox::Sys::Net::ip_link_details(); + + for my $ifname (sort keys $run_env->{network}->{interfaces}->%*) { + next + if !Proxmox::Sys::Net::ip_link_is_physical($ip_links->{$ifname}) + || Proxmox::Sys::Net::iface_is_vf($ifname); + + my $if = $run_env->{network}->{interfaces}->{$ifname}; + + if (!defined($netif_pin_map->{ $if->{mac} })) { + # each installer frontend must ensure that either + # - pinning is disabled by never setting the map or + # - setting a pinned name for each + die "no network interface name set for '$if->{mac}'!"; + } + + my $pinned_name = $netif_pin_map->{ $if->{mac} }; + my $link_file_content = Proxmox::Sys::Net::get_pin_link_file_content( + $if->{mac}, $pinned_name, + ); + + file_write_all( + "$targetdir/usr/local/lib/systemd/network/50-pmx-${pinned_name}.link", + $link_file_content, + ); + + # ensure that we use the correct interface name when writing out + # /etc/network/interfaces below - just in case the interface name + # is pinned and get_mngmt_nic() returned the original interface name. + # better be safe than sorry + if ($ethdev eq $ifname) { + $ethdev = $pinned_name; + } + } + } + if ($iso_env->{cfg}->{bridged_network}) { $ifaces .= "iface $ethdev $ntype manual\n"; @@ -1121,7 +1164,9 @@ sub extract_data { my $ipconf = $run_env->{ipconf}; foreach my $iface (sort keys %{ $ipconf->{ifaces} }) { - my $name = $ipconf->{ifaces}->{$iface}->{name}; + my $if = $ipconf->{ifaces}->{$iface}; + my $name = defined($netif_pin_map) ? $netif_pin_map->{ $if->{mac} } : $if->{name}; + next if $name eq $ethdev; $ifaces .= "\niface $name $ntype manual\n"; diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm index da476da..7b81fcf 100644 --- a/Proxmox/Install/Config.pm +++ b/Proxmox/Install/Config.pm @@ -101,6 +101,11 @@ my sub init_cfg { # network related mngmt_nic => undef, + + # maps mac address -> custom name + # if set to a hash, enables interface name pinning for all interfaces + network_interface_pin_map => undef, + hostname => undef, domain => undef, cidr => undef, @@ -248,6 +253,9 @@ sub get_root_ssh_keys { return get('root_ssh_keys'); } sub set_mngmt_nic { set_key('mngmt_nic', $_[0]); } sub get_mngmt_nic { return get('mngmt_nic'); } +sub set_network_interface_pin_map { set_key('network_interface_pin_map', $_[0]); } +sub get_network_interface_pin_map { return get('network_interface_pin_map'); } + sub set_hostname { set_key('hostname', $_[0]); } sub get_hostname { return get('hostname'); } diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index 540bec7..f0fa6ec 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -86,6 +86,7 @@ my sub query_netdevs : prototype() { # FIXME: not the same as the battle proven way we used in the installer for years? my $interfaces = fromjs(qx/ip --json address show/); + my $pinned_counter = 0; for my $if (@$interfaces) { my ($index, $name, $state, $mac, $addresses) = $if->@{qw(ifindex ifname operstate address addr_info)}; @@ -115,10 +116,13 @@ my sub query_netdevs : prototype() { $ifs->{$name} = { index => $index, name => $name, + pinned_id => "${pinned_counter}", mac => $mac, state => uc($state), }; $ifs->{$name}->{addresses} = \@valid_addrs if @valid_addrs; + + $pinned_counter++; } return $ifs; diff --git a/Proxmox/Sys/Net.pm b/Proxmox/Sys/Net.pm index 6fe99ec..2183d27 100644 --- a/Proxmox/Sys/Net.pm +++ b/Proxmox/Sys/Net.pm @@ -3,7 +3,9 @@ package Proxmox::Sys::Net; use strict; use warnings; +use Proxmox::Sys::Command; use Proxmox::Sys::Udev; +use JSON qw(); use base qw(Exporter); our @EXPORT_OK = qw(parse_ip_address parse_ip_mask parse_fqdn); @@ -129,9 +131,59 @@ sub parse_ip_mask { return; } +sub iface_is_vf { + my ($iface_name) = @_; + return -l "/sys/class/net/$iface_name/device/physfn"; +} + +# Duplicated from pve-common/src/PVE/Network.pm for now +sub ip_link_details { + my $link_json = ''; + + Proxmox::Sys::Command::run_command( + ['ip', '-details', '-json', 'link', 'show'], + sub { + $link_json .= shift; + return; + }, + ); + + my $links = JSON::decode_json($link_json); + my %ip_links = map { $_->{ifname} => $_ } $links->@*; + + return \%ip_links; +} + +# Duplicated from pve-common/src/PVE/Network.pm from now +sub ip_link_is_physical { + my ($ip_link) = @_; + + # ether alone isn't enough, as virtual interfaces can also have link_type + # ether + return $ip_link->{link_type} eq 'ether' + && (!defined($ip_link->{linkinfo}) || !defined($ip_link->{linkinfo}->{info_kind})); +} + +my $LINK_FILE_TEMPLATE = <{"$index"} = { name => $name, + pinned_id => "${pinned_counter}", driver => $driver, flags => $flags, state => $state, mac => $mac, }; + $pinned_counter++; my $addresses = `ip -o a s $name`; for my $addr_line (split /\n/, $addresses) { diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/fqdn_from_dhcp_no_default_domain.run-env.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/fqdn_from_dhcp_no_default_domain.run-env.json index ef14419..e7c2205 100644 --- a/proxmox-auto-installer/tests/resources/parse_answer_fail/fqdn_from_dhcp_no_default_domain.run-env.json +++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/fqdn_from_dhcp_no_default_domain.run-env.json @@ -181,55 +181,64 @@ "index": 4, "mac": "b4:2e:99:ac:ad:b4", "name": "eno1", - "state": "UP" + "state": "UP", + "pinned_id": "0" }, "eno2": { "index": 6, "mac": "b4:2e:99:ac:ad:b5", "name": "eno2", - "state": "UP" + "state": "UP", + "pinned_id": "1" }, "enp129s0f0np0": { "index": 7, "mac": "1c:34:da:5c:5e:24", "name": "enp129s0f0np0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "2" }, "enp129s0f1np1": { "index": 8, "mac": "1c:34:da:5c:5e:25", "name": "enp129s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "3" }, "enp193s0f0np0": { "index": 9, "mac": "24:8a:07:1e:05:bc", "name": "enp193s0f0np0", - "state": "UP" + "state": "UP", + "pinned_id": "4" }, "enp193s0f1np1": { "index": 10, "mac": "24:8a:07:1e:05:bd", "name": "enp193s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "5" }, "enp65s0f0": { "index": 2, "mac": "a0:36:9f:0a:b3:82", "name": "enp65s0f0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "6" }, "enp65s0f1": { "index": 3, "mac": "a0:36:9f:0a:b3:83", "name": "enp65s0f1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "7" }, "enx5a4732ddc747": { "index": 5, "mac": "5a:47:32:dd:c7:47", "name": "enx5a4732ddc747", - "state": "UNKNOWN" + "state": "UNKNOWN", + "pinned_id": "8" } }, "routes": { diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/no_fqdn_from_dhcp.run-env.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/no_fqdn_from_dhcp.run-env.json index e3cea3e..47d7bde 100644 --- a/proxmox-auto-installer/tests/resources/parse_answer_fail/no_fqdn_from_dhcp.run-env.json +++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/no_fqdn_from_dhcp.run-env.json @@ -180,55 +180,64 @@ "index": 4, "mac": "b4:2e:99:ac:ad:b4", "name": "eno1", - "state": "UP" + "state": "UP", + "pinned_id": "0" }, "eno2": { "index": 6, "mac": "b4:2e:99:ac:ad:b5", "name": "eno2", - "state": "UP" + "state": "UP", + "pinned_id": "1" }, "enp129s0f0np0": { "index": 7, "mac": "1c:34:da:5c:5e:24", "name": "enp129s0f0np0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "2" }, "enp129s0f1np1": { "index": 8, "mac": "1c:34:da:5c:5e:25", "name": "enp129s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "3" }, "enp193s0f0np0": { "index": 9, "mac": "24:8a:07:1e:05:bc", "name": "enp193s0f0np0", - "state": "UP" + "state": "UP", + "pinned_id": "4" }, "enp193s0f1np1": { "index": 10, "mac": "24:8a:07:1e:05:bd", "name": "enp193s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "5" }, "enp65s0f0": { "index": 2, "mac": "a0:36:9f:0a:b3:82", "name": "enp65s0f0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "6" }, "enp65s0f1": { "index": 3, "mac": "a0:36:9f:0a:b3:83", "name": "enp65s0f1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "7" }, "enx5a4732ddc747": { "index": 5, "mac": "5a:47:32:dd:c7:47", "name": "enx5a4732ddc747", - "state": "UNKNOWN" + "state": "UNKNOWN", + "pinned_id": "8" } }, "routes": { diff --git a/proxmox-auto-installer/tests/resources/run-env-info.json b/proxmox-auto-installer/tests/resources/run-env-info.json index 5a8d80a..5291721 100644 --- a/proxmox-auto-installer/tests/resources/run-env-info.json +++ b/proxmox-auto-installer/tests/resources/run-env-info.json @@ -181,55 +181,64 @@ "index": 4, "mac": "b4:2e:99:ac:ad:b4", "name": "eno1", - "state": "UP" + "state": "UP", + "pinned_id": "0" }, "eno2": { "index": 6, "mac": "b4:2e:99:ac:ad:b5", "name": "eno2", - "state": "UP" + "state": "UP", + "pinned_id": "1" }, "enp129s0f0np0": { "index": 7, "mac": "1c:34:da:5c:5e:24", "name": "enp129s0f0np0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "2" }, "enp129s0f1np1": { "index": 8, "mac": "1c:34:da:5c:5e:25", "name": "enp129s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "3" }, "enp193s0f0np0": { "index": 9, "mac": "24:8a:07:1e:05:bc", "name": "enp193s0f0np0", - "state": "UP" + "state": "UP", + "pinned_id": "4" }, "enp193s0f1np1": { "index": 10, "mac": "24:8a:07:1e:05:bd", "name": "enp193s0f1np1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "5" }, "enp65s0f0": { "index": 2, "mac": "a0:36:9f:0a:b3:82", "name": "enp65s0f0", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "6" }, - "en p65s0f1": { + "enp65s0f1": { "index": 3, "mac": "a0:36:9f:0a:b3:83", "name": "enp65s0f1", - "state": "DOWN" + "state": "DOWN", + "pinned_id": "7" }, "enx5a4732ddc747": { "index": 5, "mac": "5a:47:32:dd:c7:47", "name": "enx5a4732ddc747", - "state": "UNKNOWN" + "state": "UNKNOWN", + "pinned_id": "8" } }, "routes": { -- 2.51.0 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel