X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=src%2FPVE%2FINotify.pm;h=bd2b15a5328e82495dce6e2311ff0e5f8be8ca05;hp=cf6ef57ab6608baf4506a571143e2fa774203c13;hb=HEAD;hpb=87ff065c98a211ea4ced5e65f83fe4aa4bc51782 diff --git a/src/PVE/INotify.pm b/src/PVE/INotify.pm index cf6ef57..8a4a810 100644 --- a/src/PVE/INotify.pm +++ b/src/PVE/INotify.pm @@ -22,10 +22,11 @@ use PVE::Network; use PVE::ProcFSTools; use PVE::SafeSyslog; use PVE::Tools; +use PVE::RESTEnvironment qw(log_warn); use base 'Exporter'; -our @EXPORT_OK = qw(read_file write_file register_file); +our @EXPORT_OK = qw(read_file write_file register_file nodename); my $ccache; my $ccachemap; @@ -500,13 +501,10 @@ sub inotify_init { } my $cached_nodename; - sub nodename { - return $cached_nodename if $cached_nodename; my ($sysname, $nodename) = POSIX::uname(); - $nodename =~ s/\..*$//; # strip domain part, if any die "unable to read node name\n" if !$nodename; @@ -723,14 +721,15 @@ register_file('active', "/var/log/pve/tasks/active", \&write_active_workers); -our $bond_modes = { 'balance-rr' => 0, - 'active-backup' => 1, - 'balance-xor' => 2, - 'broadcast' => 3, - '802.3ad' => 4, - 'balance-tlb' => 5, - 'balance-alb' => 6, - }; +our $bond_modes = { + 'balance-rr' => 0, + 'active-backup' => 1, + 'balance-xor' => 2, + 'broadcast' => 3, + '802.3ad' => 4, + 'balance-tlb' => 5, + 'balance-alb' => 6, +}; my $ovs_bond_modes = { 'active-backup' => 1, @@ -883,7 +882,7 @@ sub __read_etc_network_interfaces { 'bridge-fd' => 'bridge_fd', 'bridge-stp' => 'bridge_stp', 'bridge-ports' => 'bridge_ports', - 'bridge-vids' => 'bridge_vids' + 'bridge-vids' => 'bridge_vids', }; my $line; @@ -903,39 +902,40 @@ sub __read_etc_network_interfaces { SECTION: while (defined ($line = <$fh>)) { chomp ($line); next if $line =~ m/^\s*#/; - next if $line =~ m/^\s*(allow-hotplug)\s+(.*)$/; - if ($line =~ m/^\s*(auto|allow-ovs)\s+(.*)$/) { - my @aa = split (/\s+/, $2); + if ($line =~ m/^\s*(allow-auto|auto|allow-ovs)\s+(.*)$/) { - foreach my $a (@aa) { - $ifaces->{$a}->{autostart} = 1; - } + $ifaces->{$_}->{autostart} = 1 for split (/\s+/, $2); + + } elsif ($line =~ m/^\s*(allow-hotplug)\s+(.*)$/) { - } elsif ($line =~ m/^\s*iface\s+(\S+)\s+(inet6?)\s+(\S+)\s*$/) { + # 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*$/) { 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"; - } elsif ($line =~ m/^\s*(?:iface\s - |mapping\s - |auto\s - |allow- - |source\s - |source-directory\s - )/x) { + $pushto->{comments} .= "$comment\n"; + } elsif ($line =~ m/^\s*(?:(?:iface|mapping|auto|source|source-directory)\s|allow-)/) { last; } elsif ($line =~ m/^\s*((\S+)\s+(.+))$/) { my $option = $1; @@ -957,9 +957,11 @@ sub __read_etc_network_interfaces { 'bridge-arp-nd-suppress' => 1, 'bridge-unicast-flood' => 1, 'bridge-multicast-flood' => 1, + 'bridge-disable-mac-learning' => 1, 'bond_miimon' => 1, 'bond_xmit_hash_policy' => 1, 'bond-primary' => 1, + 'link-type' => 1, 'uplink-id' => 1, 'vlan-protocol' => 1, 'vlan-raw-device' => 1, @@ -967,10 +969,21 @@ sub __read_etc_network_interfaces { 'vxlan-id' => 1, 'vxlan-svcnodeip' => 1, 'vxlan-physdev' => 1, - 'vxlan-local-tunnelip' => 1 }; + 'vxlan-local-tunnelip' => 1, + }; - if (($id eq 'address') || ($id eq 'netmask') || ($id eq 'broadcast') || ($id eq 'gateway')) { - $f->{$id} = $value; + if ($id eq 'address' || $id eq 'netmask' || $id eq 'broadcast' || $id eq 'gateway') { + 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') { @@ -996,8 +1009,7 @@ sub __read_etc_network_interfaces { } elsif ($id eq 'bond_mode') { # always use names foreach my $bm (keys %$bond_modes) { - my $id = $bond_modes->{$bm}; - if ($id eq $value) { + if ($bond_modes->{$bm} eq $value) { $value = $bm; last; } @@ -1006,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->{$_} foreach (keys %$f); + if (defined($suffix)) { + $d->{"$_$suffix"} = $f->{$_} for keys $f->%*; + } last SECTION if !defined($line); redo SECTION; } elsif ($line =~ /\w/) { @@ -1027,16 +1042,28 @@ sub __read_etc_network_interfaces { } if (!$ifaces->{lo}) { - $ifaces->{lo}->{priority} = 1; - $ifaces->{lo}->{method} = 'loopback'; - $ifaces->{lo}->{type} = 'loopback'; - $ifaces->{lo}->{autostart} = 1; + $ifaces->{lo} = { + priority => 1, + method => 'loopback', + type => 'loopback', + autostart => 1, + }; } - foreach my $iface (keys %$ifaces) { + foreach my $iface (sort keys %$ifaces) { my $d = $ifaces->{$iface}; $d->{type} = 'unknown'; - if ($iface =~ m/^bond\d+$/) { + if (defined $d->{'bridge_ports'}) { + $d->{type} = 'bridge'; + if (!defined ($d->{bridge_stp})) { + $d->{bridge_stp} = 'off'; + } + if (!defined($d->{bridge_fd}) && $d->{bridge_stp} eq 'off') { + $d->{bridge_fd} = 0; + } + } elsif ($d->{ovs_type} && $d->{ovs_type} eq 'OVSBridge') { + $d->{type} = $d->{ovs_type}; + } elsif ($iface =~ m/^bond\d+$/) { if (!$d->{ovs_type}) { $d->{type} = 'bond'; } elsif ($d->{ovs_type} eq 'OVSBond') { @@ -1056,19 +1083,6 @@ sub __read_etc_network_interfaces { my $tag = &$extract_ovs_option($d, 'tag'); $d->{ovs_tag} = $tag if defined($tag); } - } elsif ($iface =~ m/^vmbr\d+$/) { - if (!$d->{ovs_type}) { - $d->{type} = 'bridge'; - - if (!defined ($d->{bridge_fd})) { - $d->{bridge_fd} = 0; - } - if (!defined ($d->{bridge_stp})) { - $d->{bridge_stp} = 'off'; - } - } elsif ($d->{ovs_type} eq 'OVSBridge') { - $d->{type} = $d->{ovs_type}; - } } elsif ($iface =~ m/^(\S+):\d+$/) { $d->{type} = 'alias'; if (defined ($ifaces->{$1})) { @@ -1077,10 +1091,30 @@ sub __read_etc_network_interfaces { $ifaces->{$1}->{exists} = 0; $d->{exists} = 0; } - } elsif ($iface =~ m/^(\S+)\.\d+$/ || $d->{'vlan-raw-device'}) { + } elsif ($iface =~ m/^(\S+)\.(\d+)$/) { + $d->{type} = 'vlan'; + + my ($dev, $id) = ($1, $2); + $d->{'vlan-raw-device'} = $dev if defined($dev) && !$d->{'vlan-raw-device'}; + $d->{'vlan-id'} = $id if $id; # VLAN id 0 is not valid, so truthy check it is + + my $raw_iface = $d->{'vlan-raw-device'}; + + if (defined ($ifaces->{$raw_iface})) { + $d->{exists} = $ifaces->{$raw_iface}->{exists}; + } else { + $ifaces->{$raw_iface}->{exists} = 0; + $d->{exists} = 0; + } + } elsif ($d->{'vlan-raw-device'}) { $d->{type} = 'vlan'; - my $raw_iface = $d->{'vlan-raw-device'} ? $d->{'vlan-raw-device'} : $1; + if ($iface =~ m/^vlan(\d+)$/) { + $d->{'vlan-id'} = $1 if $1; # VLAN id 0 is not valid, so truthy check it is + } + + my $raw_iface = $d->{'vlan-raw-device'}; + if (defined ($ifaces->{$raw_iface})) { $d->{exists} = $ifaces->{$raw_iface}->{exists}; } else { @@ -1106,9 +1140,14 @@ sub __read_etc_network_interfaces { my $tag = &$extract_ovs_option($d, 'tag'); $d->{ovs_tag} = $tag if defined($tag); } + } elsif (defined($d->{'link-type'})) { + $d->{type} = $d->{'link-type'} if $d->{'link-type'} eq 'dummy'; } } + log_warn("detected a interface $iface that is not a bridge!") + if !($d->{type} eq 'OVSBridge' || $d->{type} eq 'bridge') && $iface =~ m/^vmbr\d+$/; + # map address and netmask to cidr if (my $addr = $d->{address}) { if (_address_is_cidr($addr)) { @@ -1143,6 +1182,10 @@ sub __read_etc_network_interfaces { $d->{method} = 'manual' if !$d->{method}; $d->{method6} = 'manual' if !$d->{method6}; + if (my $comments6 = delete $d->{comments6}) { + $d->{comments} = ($d->{comments} // '') . $comments6; + } + $d->{families} ||= ['inet']; } @@ -1203,34 +1246,44 @@ sub _get_cidr { sub __interface_to_string { my ($iface, $d, $family, $first_block, $ifupdown2) = @_; - (my $suffix = $family) =~ s/^inet//; - - return '' if !($d && $d->{"method$suffix"}); + my $suffix = $family; + $suffix =~ s/^inet// if defined($suffix); - my $raw = ''; - - $raw .= "iface $iface $family " . $d->{"method$suffix"} . "\n"; + return '' if $family && !($d && $d->{"method$suffix"}); - if (my $addr = $d->{"address$suffix"}) { + my $raw = "iface $iface"; + $raw .= " $family " . $d->{"method$suffix"} if defined $family; + $raw .= "\n"; - 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, autostart => 1, options => 1, - address => 1, netmask => 1, gateway => 1, broadcast => 1, - method6 => 1, families => 1, options6 => 1, - address6 => 1, netmask6 => 1, gateway6 => 1, broadcast6 => 1, 'uplink-id' => 1 }; + 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, + method6 => 1, families => 1, options6 => 1, comments6 => 1, address6 => 1, + netmask6 => 1, gateway6 => 1, broadcast6 => 1, 'uplink-id' => 1, + }; if (!$first_block) { # not printing out options @@ -1241,24 +1294,36 @@ sub __interface_to_string { $raw .= "\tbridge-ports $ports\n"; $done->{bridge_ports} = 1; - my $v = defined($d->{bridge_stp}) ? $d->{bridge_stp} : 'off'; - $raw .= "\tbridge-stp $v\n"; + my $br_stp = defined($d->{bridge_stp}) ? $d->{bridge_stp} : 'off'; + my $no_stp = $br_stp eq 'off'; + + $raw .= "\tbridge-stp $br_stp\n"; $done->{bridge_stp} = 1; - $v = defined($d->{bridge_fd}) ? $d->{bridge_fd} : 0; - $raw .= "\tbridge-fd $v\n"; + # NOTE: forwarding delay must be 2 <= FD <= 30 if STP is enabled + if (defined(my $br_fd = $d->{bridge_fd})) { + if ($no_stp || ($br_fd >= 2 && $br_fd <= 30)) { + $raw .= "\tbridge-fd $br_fd\n"; + } else { + # only complain if the user actually set a value, but not for default fallback below + warn "'$iface': ignoring 'bridge_fd' value '$br_fd', outside of allowed range 2-30\n"; + } + } elsif ($no_stp) { + $raw .= "\tbridge-fd 0\n"; + } $done->{bridge_fd} = 1; - if( defined($d->{bridge_vlan_aware})) { + if (defined($d->{bridge_vlan_aware})) { $raw .= "\tbridge-vlan-aware yes\n"; - $v = defined($d->{bridge_vids}) ? $d->{bridge_vids} : "2-4094"; - $raw .= "\tbridge-vids $v\n"; + my $vlans = defined($d->{bridge_vids}) ? $d->{bridge_vids} : "2-4094"; + $raw .= "\tbridge-vids $vlans\n"; } $done->{bridge_vlan_aware} = 1; $done->{bridge_vids} = 1; $raw .= "\tmtu $d->{mtu}\n" if $d->{mtu}; $done->{mtu} = 1; + $done->{'bridge-disable-mac-learning'} = 1; } elsif ($d->{type} eq 'bond') { @@ -1321,8 +1386,7 @@ sub __interface_to_string { $raw .= "\tovs_mtu $d->{mtu}\n" if $d->{mtu}; $done->{mtu} = 1; - } elsif ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || - $d->{type} eq 'OVSBond') { + } elsif ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') { $d->{autostart} = 0; # started by the bridge @@ -1381,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"; @@ -1433,8 +1508,7 @@ sub __write_etc_network_interfaces { # delete unused OVS ports foreach my $iface (keys %$ifaces) { my $d = $ifaces->{$iface}; - if ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || - $d->{type} eq 'OVSBond') { + if ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') { my $brname = $used_ports->{$iface}; if (!$brname || !$ifaces->{$brname}) { if ($iface =~ /^$PVE::Network::PHYSICAL_NIC_RE/) { @@ -1463,8 +1537,7 @@ sub __write_etc_network_interfaces { if ($d->{type} eq 'OVSBridge' && $d->{ovs_ports}) { foreach my $p (split (/\s+/, $d->{ovs_ports})) { my $n = $ifaces->{$p}; - die "OVS bridge '$iface' - unable to find port '$p'\n" - if !$n; + die "OVS bridge '$iface' - unable to find port '$p'\n" if !$n; $n->{autostart} = 0; if ($n->{type} eq 'eth') { $n->{type} = 'OVSPort'; @@ -1488,10 +1561,9 @@ sub __write_etc_network_interfaces { foreach my $p (split (/\s+/, $d->{ovs_bonds})) { my $n = $ifaces->{$p}; $n->{autostart} = 1; - die "OVS bond '$iface' - unable to find slave '$p'\n" - if !$n; - die "OVS bond '$iface' - wrong interface type on slave '$p' " . - "('$n->{type}' != 'eth')\n" if $n->{type} ne 'eth'; + die "OVS bond '$iface' - unable to find slave '$p'\n" if !$n; + die "OVS bond '$iface' - wrong interface type on slave '$p' ('$n->{type}' != 'eth')\n" + if $n->{type} ne 'eth'; &$check_mtu($ifaces, $iface, $p); } } @@ -1554,6 +1626,8 @@ sub __write_etc_network_interfaces { $p = $1; $vlanid = $2; delete $d->{'vlan-raw-device'} if $d->{'vlan-raw-device'}; + delete $d->{'vlan-id'} if $d->{'vlan-id'}; + } else { die "missing vlan-raw-device option" if !$d->{'vlan-raw-device'}; $p = $d->{'vlan-raw-device'}; @@ -1619,7 +1693,7 @@ sub __write_etc_network_interfaces { die "bridge '$iface' - unable to find bridge port '$p'\n" if !$n; die "iface $p - ip address can't be set on interface if bridged in $iface\n" if ($n->{method} && $n->{method} eq 'static' && $n->{address} ne '0.0.0.0') || - ($n->{method6} && $n->{method6} eq 'static' && $n->{address} ne '::'); + ($n->{method6} && $n->{method6} eq 'static' && $n->{address6} ne '::'); &$check_mtu($ifaces_copy, $p, $iface); $bridgeports->{$p} = $iface; } @@ -1658,6 +1732,7 @@ NETWORKDOC my $if_type_hash = { loopback => 100000, + dummy => 100000, eth => 200000, OVSPort => 200000, OVSIntPort => 300000, @@ -1717,6 +1792,11 @@ NETWORKDOC } } + # if 'inet6' is the only family + if (scalar($d->{families}->@*) == 1 && defined($d->{families}->[0]) && $d->{families}->[0] eq 'inet6') { + $d->{comments6} = delete $d->{comments}; + } + my $i = 0; # some options should be printed only once $raw .= __interface_to_string($iface, $d, $_, !$i++, $ifupdown2) foreach @{$d->{families}}; } @@ -1745,74 +1825,4 @@ sub read_iscsi_initiatorname { register_file('initiatorname', "/etc/iscsi/initiatorname.iscsi", \&read_iscsi_initiatorname); -sub read_apt_auth { - my ($filename, $fd) = @_; - - local $/; - - my $raw = defined($fd) ? <$fd> : ''; - - $raw =~ s/^\s+//; - - - my @tokens = split(/\s+/, $raw); - - my $data = {}; - - my $machine; - while (defined(my $tok = shift @tokens)) { - - $machine = shift @tokens if $tok eq 'machine'; - next if !$machine; - $data->{$machine} = {} if !$data->{$machine}; - - $data->{$machine}->{login} = shift @tokens if $tok eq 'login'; - $data->{$machine}->{password} = shift @tokens if $tok eq 'password'; - }; - - return $data; -} - -my $format_apt_auth_data = sub { - my $data = shift; - - my $raw = ''; - - foreach my $machine (sort keys %$data) { - my $d = $data->{$machine}; - $raw .= "machine $machine\n"; - $raw .= " login $d->{login}\n" if $d->{login}; - $raw .= " password $d->{password}\n" if $d->{password}; - $raw .= "\n"; - } - - return $raw; -}; - -sub write_apt_auth { - my ($filename, $fh, $data) = @_; - - my $raw = &$format_apt_auth_data($data); - - die "write failed: $!" unless print $fh "$raw\n"; - - return $data; -} - -sub update_apt_auth { - my ($filename, $fh, $data) = @_; - - my $orig = read_apt_auth($filename, $fh); - - foreach my $machine (keys %$data) { - $orig->{$machine} = $data->{$machine}; - } - - return &$format_apt_auth_data($orig); -} - -register_file('apt-auth', "/etc/apt/auth.conf", - \&read_apt_auth, \&write_apt_auth, - \&update_apt_auth, perm => 0640); - 1;