5
0
mirror of git://git.proxmox.com/git/pve-common.git synced 2025-03-11 20:58:41 +03:00

interfaces: support stanzas without types/methods

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@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2024-04-23 15:03:17 +02:00 committed by Thomas Lamprecht
parent a061517cdd
commit b518bbd5f9
2 changed files with 117 additions and 27 deletions

View File

@ -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) = @_;
# add comments
my $comments = $d->{"comments$suffix"} || '';
foreach my $cl (split(/\n/, $comments)) {
$raw .= "#$cl\n";
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";
}
};
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};
}

View File

@ -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;