X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=src%2FPVE%2FINotify.pm;h=8cc00f80df28b0e4e94b846a6d33eb4f3ec3d890;hp=fbedc506918a4ab7f528951426a62cce5aaec800;hb=c8e5d28e29a932a3729ec3396a0081080041c866;hpb=b51b16e6f58de4cb385bd461d97866b3d94c93ec diff --git a/src/PVE/INotify.pm b/src/PVE/INotify.pm index fbedc50..8cc00f8 100644 --- a/src/PVE/INotify.pm +++ b/src/PVE/INotify.pm @@ -340,7 +340,7 @@ sub register_file { sub register_regex { my ($dir, $regex, $parser, $writer, $update, %options) = @_; - die "can't register regex after initify_init" if $inotify; + die "can't register regex after inotify_init" if $inotify; my $uid = "$dir/$regex"; die "regular expression '$uid' already added :ERROR" if defined ($ccacheregex->{$uid}); @@ -540,7 +540,7 @@ sub read_etc_resolv_conf { chomp $line; if ($line =~ m/^(search|domain)\s+(\S+)\s*/) { $res->{search} = $2; - } elsif ($line =~ m/^nameserver\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*/) { + } elsif ($line =~ m/^\s*nameserver\s+($PVE::Tools::IPRE)\s*/) { $nscount++; if ($nscount <= 3) { $res->{"dns$nscount"} = $1; @@ -648,9 +648,9 @@ sub write_active_workers { my $saved = $task->{saved} ? 1 : 0; if ($task->{endtime}) { if ($task->{status}) { - $raw .= sprintf("$upid $saved %08X $task->{status}\n", $task->{endtime}); + $raw .= sprintf("%s %s %08X %s\n", $upid, $saved, $task->{endtime}, $task->{status}); } else { - $raw .= sprintf("$upid $saved %08X\n", $task->{endtime}); + $raw .= sprintf("%s %s %08X\n", $upid, $saved, $task->{endtime}); } } else { $raw .= "$upid $saved\n"; @@ -665,7 +665,7 @@ register_file('active', "/var/log/pve/tasks/active", \&write_active_workers); -my $bond_modes = { 'balance-rr' => 0, +our $bond_modes = { 'balance-rr' => 0, 'active-backup' => 1, 'balance-xor' => 2, 'broadcast' => 3, @@ -745,58 +745,112 @@ my $extract_ovs_option = sub { return $v; }; +# config => { +# ifaces => { +# $ifname => { +# exists => BOOL, +# active => BOOL, +# autostart => BOOL, +# priority => INT, +# +# type => "eth" | "bridge" | "bond" | "loopback" | "OVS*" | ... , +# +# families => ["inet", "inet6", ...], +# +# method => "manual" | "static" | "dhcp" | ... , +# address => IP, +# netmask => SUBNET, +# broadcast => IP, +# gateway => IP, +# comments => [ "..." ], +# +# method6 => "manual" | "static" | "dhcp" | ... , +# address6 => IP, +# netmask6 => SUBNET, +# gateway6 => IP, +# comments6 => [ "..." ], +# +# , # like bridge_ports, ovs_* +# +# # extra/unknown options stored by-family: +# options => { ... } +# options6 => { ... } +# } +# }, +# options => [ +# # mappings end up here as well, as we don't need to understand them +# [priority,line] +# ] +# } sub read_etc_network_interfaces { my ($filename, $fh) = @_; + my $proc_net_dev = IO::File->new('/proc/net/dev', 'r'); + my $active = PVE::Network::get_active_interfaces(); + return __read_etc_network_interfaces($fh, $proc_net_dev, $active); +} + +sub __read_etc_network_interfaces { + my ($fh, $proc_net_dev, $active_ifaces) = @_; - my $ifaces = {}; + my $config = {}; + my $ifaces = $config->{ifaces} = {}; + my $options = $config->{options} = []; my $line; - if (my $fd2 = IO::File->new("/proc/net/dev", "r")) { - while (defined ($line = <$fd2>)) { - if ($line =~ m/^\s*(eth\d+):.*/) { + if ($proc_net_dev) { + while (defined ($line = <$proc_net_dev>)) { + if ($line =~ m/^\s*(eth\d+|en[^:.]+):.*/) { $ifaces->{$1}->{exists} = 1; } } - close($fd2); + close($proc_net_dev); } # we try to keep order inside the file my $priority = 2; # 1 is reserved for lo - my $gateway = 0; - - while (defined ($line = <$fh>)) { + SECTION: while (defined ($line = <$fh>)) { chomp ($line); - next if $line =~ m/^#/; + next if $line =~ m/^\s*#/; - if ($line =~ m/^auto\s+(.*)$/) { + if ($line =~ m/^\s*auto\s+(.*)$/) { my @aa = split (/\s+/, $1); foreach my $a (@aa) { $ifaces->{$a}->{autostart} = 1; } - } elsif ($line =~ m/^iface\s+(\S+)\s+inet\s+(\S+)\s*$/) { + } elsif ($line =~ m/^\s*iface\s+(\S+)\s+(inet6?)\s+(\S+)\s*$/) { my $i = $1; - $ifaces->{$i}->{method} = $2; - $ifaces->{$i}->{priority} = $priority++; + my $family = $2; + my $f = { method => $3 }; # by family, merged to $d with a $suffix + (my $suffix = $family) =~ s/^inet//; + + my $d = $ifaces->{$i} ||= {}; + $d->{priority} = $priority++ if !$d->{priority}; + push @{$d->{families}}, $family; - my $d = $ifaces->{$i}; while (defined ($line = <$fh>)) { - if ($line =~ m/^\s*#(.*)\s*$/) { + chomp $line; + if ($line =~ m/^\s*#(.*?)\s*$/) { # NOTE: we use 'comments' instead of 'comment' to # avoid automatic utf8 conversion - $d->{comments} = '' if !$d->{comments}; - $d->{comments} .= "$1\n"; - } elsif ($line =~ m/^\s+((\S+)\s+(.+))$/) { + $f->{comments} = '' if !$f->{comments}; + $f->{comments} .= "$1\n"; + } elsif ($line =~ m/^\s*(?:iface\s + |mapping\s + |auto\s + |allow- + |source\s + |source-directory\s + )/x) { + last; + } elsif ($line =~ m/^\s*((\S+)\s+(.+))$/) { my $option = $1; my ($id, $value) = ($2, $3); - if (($id eq 'address') || ($id eq 'netmask') || ($id eq 'broadcast')) { - $d->{$id} = $value; - } elsif ($id eq 'gateway') { - $d->{$id} = $value; - $gateway = 1; + if (($id eq 'address') || ($id eq 'netmask') || ($id eq 'broadcast') || ($id eq 'gateway')) { + $f->{$id} = $value; } elsif ($id eq 'ovs_type' || $id eq 'ovs_options'|| $id eq 'ovs_bridge' || $id eq 'ovs_bonds' || $id eq 'ovs_ports') { $d->{$id} = $value; @@ -807,7 +861,11 @@ sub read_etc_network_interfaces { $devs->{$p} = 1; } my $str = join (' ', sort keys %{$devs}); - $d->{$id} = $str || ''; + if ($d->{$id}) { + $d->{$id} .= ' ' . $str if $str; + } else { + $d->{$id} = $str || ''; + } } elsif ($id eq 'bridge_stp') { if ($value =~ m/^\s*(on|yes)\s*$/i) { $d->{$id} = 'on'; @@ -816,6 +874,8 @@ sub read_etc_network_interfaces { } } elsif ($id eq 'bridge_fd') { $d->{$id} = $value; + } elsif ($id eq 'bridge_vlan_aware') { + $d->{$id} = 1; } elsif ($id eq 'bond_miimon') { $d->{$id} = $value; } elsif ($id eq 'bond_xmit_hash_policy') { @@ -831,16 +891,25 @@ sub read_etc_network_interfaces { } $d->{$id} = $value; } else { - push @{$d->{options}}, $option; + push @{$f->{options}}, $option; } } else { last; } } + $d->{"$_$suffix"} = $f->{$_} foreach (keys %$f); + last SECTION if !defined($line); + redo SECTION; + } elsif ($line =~ /\w/) { + push @$options, [$priority++, $line]; } } - + foreach my $ifname (@$active_ifaces) { + if (my $iface = $ifaces->{$ifname}) { + $iface->{active} = 1; + } + } if (!$ifaces->{lo}) { $ifaces->{lo}->{priority} = 1; @@ -896,7 +965,15 @@ sub read_etc_network_interfaces { $ifaces->{$1}->{exists} = 0; $d->{exists} = 0; } - } elsif ($iface =~ m/^eth\d+$/) { + } elsif ($iface =~ m/^(\S+)\.\d+$/) { + $d->{type} = 'vlan'; + if (defined ($ifaces->{$1})) { + $d->{exists} = $ifaces->{$1}->{exists}; + } else { + $ifaces->{$1}->{exists} = 0; + $d->{exists} = 0; + } + } elsif ($iface =~ m/^(?:eth\d+|en[^:.]+)$/) { if (!$d->{ovs_type}) { $d->{type} = 'eth'; } elsif ($d->{ovs_type} eq 'OVSPort') { @@ -919,38 +996,62 @@ sub read_etc_network_interfaces { } $d->{method} = 'manual' if !$d->{method}; + $d->{method6} = 'manual' if !$d->{method6}; + + $d->{families} ||= ['inet']; } - if (my $fd2 = IO::File->new("/proc/net/if_inet6", "r")) { - while (defined ($line = <$fd2>)) { - if ($line =~ m/^[a-f0-9]{32}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+(\S+)$/) { - $ifaces->{$1}->{active} = 1 if defined($ifaces->{$1}); + # OVS bridges create "allow-$BRIDGE $IFACE" lines which we need to remove + # from the {options} hash for them to be removed correctly. + @$options = grep {defined($_)} map { + my ($pri, $line) = @$_; + if ($line =~ /^allow-(\S+)\s+(.*)$/) { + my $bridge = $1; + my @ports = split(/\s+/, $2); + if (defined(my $br = $ifaces->{$bridge})) { + # if this port is part of a bridge, remove it + my %in_ovs_ports = map {$_=>1} split(/\s+/, $br->{ovs_ports}); + @ports = grep { not $in_ovs_ports{$_} } @ports; + } + # create the allow line for the remaining ports, or delete if empty + if (@ports) { + [$pri, "allow-$bridge " . join(' ', @ports)]; + } else { + undef; } + } else { + # don't modify other lines + $_; } - close ($fd2); - } + } @$options; - return $ifaces; + return $config; } sub __interface_to_string { - my ($iface, $d) = @_; + my ($iface, $d, $family, $first_block) = @_; + + (my $suffix = $family) =~ s/^inet//; - return '' if !($d && $d->{method}); + return '' if !($d && $d->{"method$suffix"}); my $raw = ''; - $raw .= "iface $iface inet $d->{method}\n"; - $raw .= "\taddress $d->{address}\n" if $d->{address}; - $raw .= "\tnetmask $d->{netmask}\n" if $d->{netmask}; - $raw .= "\tgateway $d->{gateway}\n" if $d->{gateway}; - $raw .= "\tbroadcast $d->{broadcast}\n" if $d->{broadcast}; + $raw .= "iface $iface $family " . $d->{"method$suffix"} . "\n"; + $raw .= "\taddress " . $d->{"address$suffix"} . "\n" if $d->{"address$suffix"}; + $raw .= "\tnetmask " . $d->{"netmask$suffix"} . "\n" if $d->{"netmask$suffix"}; + $raw .= "\tgateway " . $d->{"gateway$suffix"} . "\n" if $d->{"gateway$suffix"}; + $raw .= "\tbroadcast " . $d->{"broadcast$suffix"} . "\n" if $d->{"broadcast$suffix"}; my $done = { type => 1, priority => 1, method => 1, active => 1, exists => 1, comments => 1, autostart => 1, options => 1, - address => 1, netmask => 1, gateway => 1, broadcast => 1 }; - - if ($d->{type} eq 'bridge') { + address => 1, netmask => 1, gateway => 1, broadcast => 1, + method6 => 1, families => 1, options6 => 1, + address6 => 1, netmask6 => 1, gateway6 => 1, broadcast6 => 1 }; + + if (!$first_block) { + # not printing out options + } elsif ($d->{type} eq 'bridge') { my $ports = $d->{bridge_ports} || 'none'; $raw .= "\tbridge_ports $ports\n"; @@ -963,6 +1064,11 @@ sub __interface_to_string { $v = defined($d->{bridge_fd}) ? $d->{bridge_fd} : 0; $raw .= "\tbridge_fd $v\n"; $done->{bridge_fd} = 1; + + if( defined($d->{bridge_vlan_aware})) { + $raw .= "\tbridge_vlan_aware yes\n"; + } + $done->{bridge_vlan_aware} = 1; } elsif ($d->{type} eq 'bond') { @@ -991,7 +1097,6 @@ sub __interface_to_string { $raw .= "\tovs_ports $d->{ovs_ports}\n" if $d->{ovs_ports}; $done->{ovs_ports} = 1; - } elsif ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') { @@ -1025,48 +1130,51 @@ sub __interface_to_string { $done->{ovs_bonds} = 1; } - if ($d->{ovs_bridge}) { - $raw = "allow-$d->{ovs_bridge} $iface\n$raw"; - } - $raw .= "\tovs_type $d->{type}\n"; $done->{ovs_type} = 1; if ($d->{ovs_bridge}) { + $raw = "allow-$d->{ovs_bridge} $iface\n$raw"; $raw .= "\tovs_bridge $d->{ovs_bridge}\n"; $done->{ovs_bridge} = 1; } - # fixme: use Data::Dumper; print Dumper($d); } - # print other settings - foreach my $k (keys %$d) { - next if $done->{$k}; - next if !$d->{$k}; - $raw .= "\t$k $d->{$k}\n"; + if ($first_block) { + # print other settings + foreach my $k (keys %$d) { + next if $done->{$k}; + next if !$d->{$k}; + $raw .= "\t$k $d->{$k}\n"; + } } - foreach my $option (@{$d->{options}}) { + foreach my $option (@{$d->{"options$suffix"}}) { $raw .= "\t$option\n"; } # add comments - my $comments = $d->{comments} || ''; + my $comments = $d->{"comments$suffix"} || ''; foreach my $cl (split(/\n/, $comments)) { $raw .= "#$cl\n"; } - if ($d->{autostart}) { - $raw = "auto $iface\n$raw"; - } - $raw .= "\n"; return $raw; } + sub write_etc_network_interfaces { - my ($filename, $fh, $ifaces) = @_; + my ($filename, $fh, $config) = @_; + my $raw = __write_etc_network_interfaces($config); + PVE::Tools::safe_print($filename, $fh, $raw); +} +sub __write_etc_network_interfaces { + my ($config) = @_; + + my $ifaces = $config->{ifaces}; + my @options = @{$config->{options}}; my $used_ports = {}; @@ -1092,7 +1200,14 @@ sub write_etc_network_interfaces { $d->{type} eq 'OVSBond') { my $brname = $used_ports->{$iface}; if (!$brname || !$ifaces->{$brname}) { - delete $ifaces->{$iface}; + if ($iface =~ /^(?:eth|en)/) { + $ifaces->{$iface} = { type => 'eth', + exists => 1, + method => 'manual', + families => ['inet'] }; + } else { + delete $ifaces->{$iface}; + } next; } my $bd = $ifaces->{$brname}; @@ -1111,6 +1226,7 @@ sub write_etc_network_interfaces { my $n = $ifaces->{$p}; die "OVS bridge '$iface' - unable to find port '$p'\n" if !$n; + $n->{autostart} = 0; if ($n->{type} eq 'eth') { $n->{type} = 'OVSPort'; $n->{ovs_bridge} = $iface; @@ -1138,66 +1254,88 @@ sub write_etc_network_interfaces { } } - my $raw = "# network interface settings\n"; + my $raw = <<'NETWORKDOC'; +# network interface settings; autogenerated +# Please do NOT modify this file directly, unless you know what +# you're doing. +# +# If you want to manage part of the network configuration manually, +# please utilize the 'source' or 'source-directory' directives to do +# so. +# PVE will preserve these directives, but will NOT its network +# configuration from sourced files, so do not attempt to move any of +# the PVE managed interfaces into external files! + +NETWORKDOC my $printed = {}; my $if_type_hash = { - unknown => 0, - loopback => 10, - eth => 20, - bond => 30, - bridge => 40, + loopback => 100000, + eth => 200000, + bond => 300000, + bridge => 400000, }; my $lookup_type_prio = sub { my $iface = shift; - my $alias = 0; - if ($iface =~ m/^(\S+):\d+$/) { + my $child = 0; + if ($iface =~ m/^(\S+)(\.|:)\d+$/) { $iface = $1; - $alias = 1; + $child = 1; } my $pri; if ($iface eq 'lo') { $pri = $if_type_hash->{loopback}; - } elsif ($iface =~ m/^eth\d+$/) { - $pri = $if_type_hash->{eth} + $alias; + } elsif ($iface =~ m/^(?:eth\d+|en[^:.]+)$/) { + $pri = $if_type_hash->{eth} + $child; } elsif ($iface =~ m/^bond\d+$/) { - $pri = $if_type_hash->{bond} + $alias; + $pri = $if_type_hash->{bond} + $child; } elsif ($iface =~ m/^vmbr\d+$/) { - $pri = $if_type_hash->{bridge} + $alias; + $pri = $if_type_hash->{bridge} + $child; } - return $pri || ($if_type_hash->{unknown} + $alias); + return $pri; }; foreach my $iface (sort { my $ref1 = $ifaces->{$a}; my $ref2 = $ifaces->{$b}; - my $p1 = &$lookup_type_prio($a); - my $p2 = &$lookup_type_prio($b); + my $tp1 = &$lookup_type_prio($a); + my $tp2 = &$lookup_type_prio($b); - return $p1 <=> $p2 if $p1 != $p2; + # Only recognized types are in relation to each other. If one type + # is unknown then only consider the interfaces' priority attributes. + $tp1 = $tp2 = 0 if !defined($tp1) || !defined($tp2); - $p1 = $ref1->{priority} || 100000; - $p2 = $ref2->{priority} || 100000; + my $p1 = $tp1 + ($ref1->{priority} // 50000); + my $p2 = $tp2 + ($ref2->{priority} // 50000); return $p1 <=> $p2 if $p1 != $p2; return $a cmp $b; - } keys %$ifaces) { + } keys %$ifaces) { + next if $printed->{$iface}; my $d = $ifaces->{$iface}; - - next if $printed->{$iface}; + my $pri = $d->{priority} // 0; + if (@options && $options[0]->[0] < $pri) { + do { + $raw .= (shift @options)->[1] . "\n"; + } while (@options && $options[0]->[0] < $pri); + $raw .= "\n"; + } $printed->{$iface} = 1; - $raw .= __interface_to_string($iface, $d); + $raw .= "auto $iface\n" if $d->{autostart}; + my $i = 0; # some options should be printed only once + $raw .= __interface_to_string($iface, $d, $_, !$i++) foreach @{$d->{families}}; } - - PVE::Tools::safe_print($filename, $fh, $raw); + + $raw .= $_->[1] . "\n" foreach @options; + return $raw; } register_file('interfaces', "/etc/network/interfaces",