[pve-devel] [PATCH common] interfaces: support stanzas without types/methods
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Apr 23 15:03:17 CEST 2024
This is allowed in ifupdown2 and previously interfaces named
'vmbr\d+' were recognized as bridges even if they used this mode.
With commit e68ebda4f109 this is no longer the case.
Fixes: e68ebda4f109 ("fix #545: interfaces: allow arbitrary bridge names in network config")
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
The `__interface_to_string portion` is much better viewied with `-w`
src/PVE/INotify.pm | 97 +++++++++++++------
.../t.ifupdown2-typeless.pl | 47 +++++++++
2 files changed, 117 insertions(+), 27 deletions(-)
create mode 100644 test/etc_network_interfaces/t.ifupdown2-typeless.pl
diff --git a/src/PVE/INotify.pm b/src/PVE/INotify.pm
index 4568593..8a4a810 100644
--- a/src/PVE/INotify.pm
+++ b/src/PVE/INotify.pm
@@ -912,23 +912,29 @@ sub __read_etc_network_interfaces {
# FIXME: handle those differently? auto makes it required on-boot, vs. best-effort
$ifaces->{$_}->{autostart} = 1 for split (/\s+/, $2);
- } elsif ($line =~ m/^\s*iface\s+(\S+)\s+(inet6?)\s+(\S+)\s*$/) {
+ } elsif ($line =~ m/^\s*iface\s+(\S+)(?:\s+(inet6?)\s+(\S+))?\s*$/) {
my $i = $1;
my $family = $2;
my $f = { method => $3 }; # by family, merged to $d with a $suffix
- (my $suffix = $family) =~ s/^inet//;
+ my $suffix = $family;
+ $suffix =~ s/^inet// if defined $suffix;
my $d = $ifaces->{$i} ||= {};
$d->{priority} = $priority++ if !$d->{priority};
+
+ # $family may be undef, an undef family means we have a stanza
+ # without an `inet` or `inet6` section
push @{$d->{families}}, $family;
+
while (defined ($line = <$fh>)) {
$line =~ s/\s+$//; # drop trailing whitespaces
if ($line =~ m/^\s*#(.*?)\s*$/) {
- $f->{comments} = '' if !$f->{comments};
+ my $pushto = defined($suffix) ? $f : $d;
+ $pushto->{comments} = '' if !$pushto->{comments};
my $comment = decode('UTF-8', $1);
- $f->{comments} .= "$comment\n";
+ $pushto->{comments} .= "$comment\n";
} elsif ($line =~ m/^\s*(?:(?:iface|mapping|auto|source|source-directory)\s|allow-)/) {
last;
} elsif ($line =~ m/^\s*((\S+)\s+(.+))$/) {
@@ -967,7 +973,17 @@ sub __read_etc_network_interfaces {
};
if ($id eq 'address' || $id eq 'netmask' || $id eq 'broadcast' || $id eq 'gateway') {
- $f->{$id} = $value;
+ if (defined($suffix)) {
+ $d->{$id.$suffix} = $value;
+ } elsif ($id ne 'netmask') {
+ if ($value =~ /:/) {
+ $d->{$id.'6'} = $value;
+ } else {
+ $d->{$id} = $value;
+ }
+ } else {
+ $d->{$id} = $value;
+ }
} elsif ($simple_options->{$id}) {
$d->{$id} = $value;
} elsif ($id eq 'slaves' || $id eq 'bridge_ports') {
@@ -1002,13 +1018,16 @@ sub __read_etc_network_interfaces {
} elsif ($id eq 'vxlan-remoteip') {
push @{$d->{$id}}, $value;
} else {
- push @{$f->{options}}, $option;
+ my $pushto = defined($suffix) ? $f : $d;
+ push @{$pushto->{options}}, $option;
}
} else {
last;
}
}
- $d->{"$_$suffix"} = $f->{$_} for keys $f->%*;
+ if (defined($suffix)) {
+ $d->{"$_$suffix"} = $f->{$_} for keys $f->%*;
+ }
last SECTION if !defined($line);
redo SECTION;
} elsif ($line =~ /\w/) {
@@ -1227,24 +1246,37 @@ sub _get_cidr {
sub __interface_to_string {
my ($iface, $d, $family, $first_block, $ifupdown2) = @_;
- (my $suffix = $family) =~ s/^inet//;
+ my $suffix = $family;
+ $suffix =~ s/^inet// if defined($suffix);
- return '' if !($d && $d->{"method$suffix"});
+ return '' if $family && !($d && $d->{"method$suffix"});
- my $raw = "iface $iface $family " . $d->{"method$suffix"} . "\n";
+ my $raw = "iface $iface";
+ $raw .= " $family " . $d->{"method$suffix"} if defined $family;
+ $raw .= "\n";
- if (my $addr = $d->{"address$suffix"}) {
- if ($addr !~ /\/\d+$/ && $d->{"netmask$suffix"}) {
- if ($d->{"netmask$suffix"} =~ m/^\d+$/) {
- $addr .= "/" . $d->{"netmask$suffix"};
- } elsif (my $mask = PVE::JSONSchema::get_netmask_bits($d->{"netmask$suffix"})) {
- $addr .= "/" . $mask;
+ my $add_addr = sub {
+ my ($suffix) = @_;
+ if (my $addr = $d->{"address$suffix"}) {
+ if ($addr !~ /\/\d+$/ && $d->{"netmask$suffix"}) {
+ if ($d->{"netmask$suffix"} =~ m/^\d+$/) {
+ $addr .= "/" . $d->{"netmask$suffix"};
+ } elsif (my $mask = PVE::JSONSchema::get_netmask_bits($d->{"netmask$suffix"})) {
+ $addr .= "/" . $mask;
+ }
}
+ $raw .= "\taddress ${addr}\n";
}
- $raw .= "\taddress ${addr}\n";
- }
- $raw .= "\tgateway " . $d->{"gateway$suffix"} . "\n" if $d->{"gateway$suffix"};
+ $raw .= "\tgateway " . $d->{"gateway$suffix"} . "\n" if $d->{"gateway$suffix"};
+ };
+
+ if ($family) {
+ $add_addr->($suffix);
+ } else {
+ $add_addr->('');
+ $add_addr->('6');
+ }
my $done = {
type => 1, priority => 1, method => 1, active => 1, exists => 1, comments => 1,
@@ -1413,14 +1445,25 @@ sub __interface_to_string {
}
}
- foreach my $option (@{$d->{"options$suffix"}}) {
- $raw .= "\t$option\n";
- }
+ my $add_options_comments = sub {
+ my ($suffix) = @_;
+
+ foreach my $option (@{$d->{"options$suffix"}}) {
+ $raw .= "\t$option\n";
+ }
- # add comments
- my $comments = $d->{"comments$suffix"} || '';
- foreach my $cl (split(/\n/, $comments)) {
- $raw .= "#$cl\n";
+ # add comments
+ my $comments = $d->{"comments$suffix"} || '';
+ foreach my $cl (split(/\n/, $comments)) {
+ $raw .= "#$cl\n";
+ }
+ };
+
+ if ($family) {
+ $add_options_comments->($suffix);
+ } else {
+ $add_options_comments->('');
+ $add_options_comments->('6');
}
$raw .= "\n";
@@ -1750,7 +1793,7 @@ NETWORKDOC
}
# if 'inet6' is the only family
- if (scalar($d->{families}->@*) == 1 && $d->{families}[0] eq 'inet6') {
+ if (scalar($d->{families}->@*) == 1 && defined($d->{families}->[0]) && $d->{families}->[0] eq 'inet6') {
$d->{comments6} = delete $d->{comments};
}
diff --git a/test/etc_network_interfaces/t.ifupdown2-typeless.pl b/test/etc_network_interfaces/t.ifupdown2-typeless.pl
new file mode 100644
index 0000000..d0ec5e6
--- /dev/null
+++ b/test/etc_network_interfaces/t.ifupdown2-typeless.pl
@@ -0,0 +1,47 @@
+my $ip = '10.0.0.2/24';
+my $gw = '10.0.0.1';
+my $ip6 = 'fc05::1:2/112';
+my $gw6 = 'fc05::1:1';
+
+r(load('base') . <<"EOF");
+auto vmbr1
+iface vmbr1
+ address 1.2.3.4/24
+ address fccc::a:1/64
+ gateway 1.2.3.1
+ gateway fccc::1
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+# Comment
+
+EOF
+
+my $run = 'first';
+my $ifaces = $config->{ifaces};
+
+my $ck = sub {
+ my ($i, $v, $e) = @_;
+ $ifaces->{$i}->{$v} eq $e
+ or die "$run run: $i variable $v: got \"$ifaces->{$i}->{$v}\", expected: $e\n";
+};
+
+my $check_config = sub {
+ $ck->('vmbr1', type => 'bridge');
+ $ck->('vmbr1', cidr => '1.2.3.4/24');
+ $ck->('vmbr1', gateway => '1.2.3.1');
+ $ck->('vmbr1', cidr6 => 'fccc::a:1/64');
+ $ck->('vmbr1', gateway6 => 'fccc::1');
+};
+
+$check_config->();
+
+# idempotency
+save('idem', w());
+r(load('idem'));
+expect load('idem');
+
+$run = 'second';
+$check_config->();
+
+1;
--
2.39.2
More information about the pve-devel
mailing list