From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH installer v4 11/30] install: factor out ifupdown2 config generation into own subroutine
Date: Mon, 2 Nov 2026 13:45:17 +0100 [thread overview]
Message-ID: <20261102124539.1008075-12-c.heiss@proxmox.com> (raw)
In-Reply-To: <20261102124539.1008075-1-c.heiss@proxmox.com>
Makes it more readable and to add more logic in the future, as well as
being now unit-testable.
While at it, unify the `address` and `gateway` and keep it of the
branched paths for bridged/unbridged. Makes things simpler and the
final configuration more predictable in layout.
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v3 -> v4:
* no changes
Changes v2 -> v3:
* only generate config in subroutine, keep the writing of
/etc/network/interfaces in the main logic
* fix missing translation of $ethdev to pinned name, by passing it
directly as a parameter
* add initial testsuite for ifupdown2 config generation (including
additional tests from @Thomas)
* simplify config generation a bit by moving address and gateway
setup into common path
Changes v1 -> v2:
* no changes
Proxmox/Install.pm | 134 ++++++++++----------
test/Makefile | 6 +-
test/generate-ifupdown2-config.pl | 197 ++++++++++++++++++++++++++++++
3 files changed, 270 insertions(+), 67 deletions(-)
create mode 100755 test/generate-ifupdown2-config.pl
diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm
index 7bb8e29..a98571f 100644
--- a/Proxmox/Install.pm
+++ b/Proxmox/Install.pm
@@ -798,6 +798,71 @@ my sub setup_proxmox_first_boot_service {
}
}
+sub generate_ifupdown2_config {
+ my $run_env = Proxmox::Install::RunEnv::get();
+ my $iso_env = Proxmox::Install::ISOEnv::get();
+
+ my $ip_version = Proxmox::Install::Config::get_ip_version();
+ my $ntype = $ip_version == 4 ? 'inet' : 'inet6';
+
+ my $interfaces = $run_env->{network}->{interfaces};
+ my $ethdev = Proxmox::Install::Config::get_mngmt_nic();
+ my $cidr = Proxmox::Install::Config::get_cidr();
+ my $gateway = Proxmox::Install::Config::get_gateway();
+
+ my $netif_pin_map = Proxmox::Install::Config::get_network_interface_pin_map();
+
+ # frontends may pass the original kernel name of an interface that gets pinned
+ if (defined($netif_pin_map) && defined($interfaces->{$ethdev})) {
+ $ethdev = $netif_pin_map->{ $interfaces->{$ethdev}->{mac} } // $ethdev;
+ }
+
+ my $ifaces = "auto lo\niface lo inet loopback\n\n";
+
+ # iproute2 omits the gateway and protocol keys for some routes; route devices are original
+ # kernel interface names, while $ethdev can be the pinned one
+ my $gateways6 = $run_env->{network}->{routes}->{gateways6} // [];
+ my $is_gateway6_from_ra = grep {
+ my $dev = $_->{dev} // '';
+ if (defined($netif_pin_map) && defined($interfaces->{$dev})) {
+ $dev = $netif_pin_map->{ $interfaces->{$dev}->{mac} } // $dev;
+ }
+ $dev eq $ethdev
+ && ($_->{gateway} // '') eq $gateway
+ && ($_->{protocol} // '') eq 'ra';
+ } $gateways6->@*;
+
+ if ($iso_env->{cfg}->{bridged_network}) {
+ $ifaces .= "iface $ethdev $ntype manual\n";
+
+ $ifaces .=
+ "\nauto vmbr0\niface vmbr0 $ntype static\n"
+ . "\tbridge-ports $ethdev\n"
+ . "\tbridge-stp off\n"
+ . "\tbridge-fd 0\n";
+ } else {
+ $ifaces .= "auto $ethdev\n" . #
+ "iface $ethdev $ntype static\n";
+ }
+
+ $ifaces .= "\taddress $cidr\n";
+ if (!$is_gateway6_from_ra) {
+ $ifaces .= "\tgateway $gateway\n";
+ }
+
+ foreach my $iface (sort keys $interfaces->%*) {
+ my $if = $interfaces->{$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";
+ }
+
+ $ifaces .= "\n\nsource /etc/network/interfaces.d/*\n";
+ return $ifaces;
+}
+
sub extract_data {
my $iso_env = Proxmox::Install::ISOEnv::get();
my $run_env = Proxmox::Install::RunEnv::get();
@@ -1138,21 +1203,11 @@ sub extract_data {
syscmd("/bin/hostname $hostname") if !is_test_mode();
- # configure interfaces
-
- my $ifaces = "auto lo\niface lo inet loopback\n\n";
-
- my $ip_version = Proxmox::Install::Config::get_ip_version();
- my $ntype = $ip_version == 4 ? 'inet' : 'inet6';
-
- my $ethdev = Proxmox::Install::Config::get_mngmt_nic();
- 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;
@@ -1178,64 +1233,11 @@ sub extract_data {
"$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;
- }
}
}
- my $interfaces = $run_env->{network}->{interfaces};
-
- # iproute2 omits the gateway and protocol keys for some routes; route devices are original
- # kernel interface names, while $ethdev can be the pinned one
- my $gateways6 = $run_env->{network}->{routes}->{gateways6} // [];
- my $is_gateway6_from_ra = grep {
- my $dev = $_->{dev} // '';
- if (defined($netif_pin_map) && defined($interfaces->{$dev})) {
- $dev = $netif_pin_map->{ $interfaces->{$dev}->{mac} } // $dev;
- }
- $dev eq $ethdev
- && ($_->{gateway} // '') eq $gateway
- && ($_->{protocol} // '') eq 'ra';
- } $gateways6->@*;
-
- if ($iso_env->{cfg}->{bridged_network}) {
- $ifaces .= "iface $ethdev $ntype manual\n";
-
- $ifaces .=
- "\nauto vmbr0\niface vmbr0 $ntype static\n"
- . "\taddress $cidr\n"
- . "\tgateway $gateway\n"
- . "\tbridge-ports $ethdev\n"
- . "\tbridge-stp off\n"
- . "\tbridge-fd 0\n";
- } else {
- $ifaces .= "auto $ethdev\n" . #
- "iface $ethdev $ntype static\n" . #
- "\taddress $cidr\n";
-
- if (!$is_gateway6_from_ra) {
- $ifaces .= "\tgateway $gateway\n";
- }
- }
-
- foreach my $iface (sort keys $interfaces->%*) {
- my $if = $interfaces->{$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";
- }
-
- $ifaces .= "\n\nsource /etc/network/interfaces.d/*\n";
-
- file_write_all("$targetdir/etc/network/interfaces", $ifaces);
+ # configure interfaces
+ file_write_all("$targetdir/etc/network/interfaces", generate_ifupdown2_config());
# configure dns
diff --git a/test/Makefile b/test/Makefile
index bfe7674..9727244 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -5,7 +5,7 @@ export PERLLIB=..
.PHONY: check
check: test-zfs-arc-max test-run-command test-parse-fqdn test-ui2-stdio \
test-zfs-get-pool-list test-parse-kernel-cmdline \
- test-validate-link-pin-map
+ test-validate-link-pin-map test-generate-ifupdown2-config
.PHONY: test-zfs-arc-max
test-zfs-arc-max:
@@ -34,3 +34,7 @@ test-parse-kernel-cmdline:
.PHONY: test-validate-link-pin-map
test-validate-link-pin-map:
./validate-link-pin-map.pl
+
+.PHONY: test-generate-ifupdown2-config
+test-generate-ifupdown2-config:
+ ./generate-ifupdown2-config.pl
diff --git a/test/generate-ifupdown2-config.pl b/test/generate-ifupdown2-config.pl
new file mode 100755
index 0000000..5c1794e
--- /dev/null
+++ b/test/generate-ifupdown2-config.pl
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::MockModule qw(strict);
+
+use Proxmox::Install;
+use Proxmox::Install::Config;
+
+use Proxmox::Log;
+Proxmox::Log::init("/tmp/install.log");
+
+my $proxmox_install_runenv = Test::MockModule->new('Proxmox::Install::RunEnv');
+my $proxmox_install_isoenv = Test::MockModule->new('Proxmox::Install::ISOEnv');
+
+my $current_cfg;
+my sub set_config {
+ my (%cfg) = @_;
+
+ $current_cfg = \%cfg;
+ $proxmox_install_isoenv->redefine(
+ get => sub {
+ return {
+ product => $cfg{bridge} ? 'pve' : 'pbs',
+ cfg => {
+ product => $cfg{bridge} ? 'pve' : 'pbs',
+ bridged_network => $cfg{bridge},
+ },
+ };
+ },
+ );
+}
+
+my sub is_generated {
+ my ($expected, $msg) = @_;
+
+ is(
+ Proxmox::Install::generate_ifupdown2_config(), <<EOT,
+auto lo
+iface lo inet loopback
+
+$expected
+
+source /etc/network/interfaces.d/*
+EOT
+ "$msg (bridge:$current_cfg->{bridge})",
+ );
+}
+
+# global config
+$proxmox_install_runenv->redefine(
+ get => sub {
+ return {
+ network => {
+ interfaces => {
+ eno1 => {
+ mac => '12:34:56:ab:cd:ef',
+ index => 1,
+ name => 'eno1',
+ },
+ eno2 => {
+ mac => 'ab:cd:ef:12:34:56',
+ index => 2,
+ name => 'eno2',
+ },
+ },
+ },
+ };
+ },
+);
+set_config(bridge => 0);
+Proxmox::Install::Config::merge({
+ mngmt_nic => 'eno1',
+ cidr => '192.168.0.10/24',
+ gateway => '192.168.0.1',
+});
+
+###
+# There's essentially a matrix to test w.r.t the overall networking setup:
+#
+# +-------+--------+
+# | test# | bridge |
+# +=======+========+
+# | 1 | 0 |
+# | 2 | 1 |
+# +-------+--------+
+###
+
+# 1.
+set_config(bridge => 0);
+is_generated(
+ <<EOT
+auto eno1
+iface eno1 inet static
+ address 192.168.0.10/24
+ gateway 192.168.0.1
+
+iface eno2 inet manual
+EOT
+ , 'host network setup',
+);
+
+# 2.
+set_config(bridge => 1);
+is_generated(
+ <<EOT
+iface eno1 inet manual
+
+auto vmbr0
+iface vmbr0 inet static
+ bridge-ports eno1
+ bridge-stp off
+ bridge-fd 0
+ address 192.168.0.10/24
+ gateway 192.168.0.1
+
+iface eno2 inet manual
+EOT
+ , 'host network setup',
+);
+
+# interface name pinning test
+Proxmox::Install::Config::merge({
+ network_interface_pin_map => {
+ '12:34:56:ab:cd:ef' => 'nic0',
+ 'ab:cd:ef:12:34:56' => 'nic1',
+ },
+});
+is_generated(
+ <<EOT
+iface nic0 inet manual
+
+auto vmbr0
+iface vmbr0 inet static
+ bridge-ports nic0
+ bridge-stp off
+ bridge-fd 0
+ address 192.168.0.10/24
+ gateway 192.168.0.1
+
+iface nic1 inet manual
+EOT
+ , 'management bridge uses pinned interface name',
+);
+Proxmox::Install::Config::merge({
+ network_interface_pin_map => undef,
+});
+
+# A gateway learned via IPv6 router advertisements is not written out
+Proxmox::Install::Config::merge({
+ mngmt_nic => 'eno1',
+ cidr => '2001:db8::1/64',
+ gateway => 'fe80::1',
+});
+$proxmox_install_runenv->redefine(
+ get => sub {
+ return {
+ network => {
+ interfaces => {
+ eno1 => {
+ mac => '12:34:56:ab:cd:ef',
+ index => 1,
+ name => 'eno1',
+ },
+ eno2 => {
+ mac => 'ab:cd:ef:12:34:56',
+ index => 2,
+ name => 'eno2',
+ },
+ },
+ routes => {
+ gateways6 => [{ dev => 'eno1', gateway => 'fe80::1', protocol => 'ra' }],
+ },
+ },
+ };
+ },
+);
+
+is_generated(
+ <<EOT
+iface eno1 inet6 manual
+
+auto vmbr0
+iface vmbr0 inet6 static
+ bridge-ports eno1
+ bridge-stp off
+ bridge-fd 0
+ address 2001:db8::1/64
+
+iface eno2 inet6 manual
+EOT
+ , 'gateway learned from IPv6 router advertisements is not configured statically',
+);
+
+done_testing();
--
2.55.0
next prev parent reply other threads:[~2026-11-02 12:47 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 12:45 [PATCH installer/proxmox v4 00/30] fix #2164: add bond setup across all installers Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 01/30] installer-types: clippy: use .is_multiple_of() instead of manual modulo Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 02/30] installer-types: use `MacAddress` type where applicable Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 03/30] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 04/30] installer-types: answer: add options for configuring management bond Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 05/30] installer-types: answer: add options for configuring management vlan Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 06/30] installer-types: bump fetch schema for bond and VLAN answer options Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 07/30] installer-types: bump version to 0.3.0-1 Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 08/30] network-api: derive Display and FromStr for network bond types Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 09/30] install: run make tidy Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 10/30] tree-wide: use `MacAddress` type instead of string for MAC addresses Christoph Heiss
2026-11-02 12:45 ` Christoph Heiss [this message]
2026-11-02 12:45 ` [PATCH installer v4 12/30] install: do not rely on interface name map to be fully populated Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 13/30] install: config: add option for setting up bond on management interface Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 14/30] install: config: add option for setting up vlan " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 15/30] install: network: set up management bond if requested Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 16/30] install: network: set up vlan on management interface " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 17/30] gui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 18/30] gui: network: add bond setup options to " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 19/30] gui: network: add vlan setup options to advanced network dialog Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 20/30] auto: pass through management network bond options to low-level installer Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 21/30] auto: pass through management network vlan " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 22/30] tui: drop long obsolete comment about logo formatting Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 23/30] tui: views: add widget for displaying a group of checkboxes Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 24/30] tui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 25/30] tui: network: add bond setup options to advanced network dialog Christoph Heiss
2026-11-02 14:55 ` Lukas Sichert
2026-11-02 12:45 ` [PATCH installer v4 26/30] tui: network: add vlan " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 27/30] gui: network: split advanced options dialog into tabs Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 28/30] tui: " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 29/30] gui: network: list bond member candidates sharing a MAC address once Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 30/30] tui: " 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=20261102124539.1008075-12-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox