From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH installer 02/14] install: add support for network interface name pinning
Date: Tue, 14 Oct 2025 15:21:47 +0200 [thread overview]
Message-ID: <20251014132207.1171073-3-c.heiss@proxmox.com> (raw)
In-Reply-To: <20251014132207.1171073-1-c.heiss@proxmox.com>
From: Thomas Lamprecht <t.lamprecht@proxmox.com>
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 <t.lamprecht@proxmox.com>
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
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 = <<EOF;
+# setup by the Proxmox installer.
+[Match]
+MACAddress=%s
+Type=ether
+
+[Link]
+Name=%s
+EOF
+
+sub get_pin_link_file_content {
+ my ($mac, $pin_name) = @_;
+
+ return sprintf($LINK_FILE_TEMPLATE, $mac, $pin_name);
+}
+
sub get_ip_config {
my $ifaces = {};
my $default;
+ my $pinned_counter = 0;
my $links = `ip -o l`;
foreach my $l (split /\n/, $links) {
@@ -144,11 +196,13 @@ sub get_ip_config {
$ifaces->{"$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
next prev parent reply other threads:[~2025-10-14 13:22 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-14 13:21 [pve-devel] [PATCH installer 00/14] support " Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 01/14] test: parse-kernel-cmdline: fix module import statement Christoph Heiss
2025-10-14 13:21 ` Christoph Heiss [this message]
2025-10-14 13:21 ` [pve-devel] [PATCH installer 03/14] run env: network: add kernel driver name to network interface info Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 04/14] common: utils: fix clippy warnings Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 05/14] common: setup: simplify network address list serialization Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 06/14] common: implement support for `network_interface_pin_map` config Christoph Heiss
2025-10-21 14:05 ` Michael Köppl
2025-10-22 9:37 ` Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 07/14] auto: add support for pinning network interface names Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 08/14] assistant: verify network settings in `validate-answer` subcommand Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 09/14] post-hook: avoid redundant Option<bool> for (de-)serialization Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 10/14] post-hook: add network interface name and pinning status Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 11/14] tui: views: move network options view to own module Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 12/14] tui: views: form: allow attaching user-defined data to children Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 13/14] tui: add support for pinning network interface names Christoph Heiss
2025-10-14 13:21 ` [pve-devel] [PATCH installer 14/14] gui: " Christoph Heiss
2025-10-14 15:04 ` Maximiliano Sandoval
2025-10-16 12:01 ` Christoph Heiss
2025-10-21 14:05 ` Michael Köppl
2025-10-22 9:40 ` Christoph Heiss
2025-10-21 14:04 ` [pve-devel] [PATCH installer 00/14] support network interface name pinning Michael Köppl
2025-10-22 9:46 ` Christoph Heiss
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251014132207.1171073-3-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.