X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FNetwork.pm;h=b14fdf1e6f399a50be7de880aeace50377a43dbc;hb=354ec8dee37d481ebae49b488349a8e932dce736;hp=f6ad4cde110545f583d4d4de9707f8a03a0ee4cf;hpb=29dde5f46b4bb616ed4814903c5d28427e668a4d;p=pve-common.git diff --git a/src/PVE/Network.pm b/src/PVE/Network.pm index f6ad4cd..b14fdf1 100644 --- a/src/PVE/Network.pm +++ b/src/PVE/Network.pm @@ -9,6 +9,7 @@ use PVE::Tools qw(run_command lock_file); use File::Basename; use IO::Socket::IP; +use JSON; use Net::IP; use NetAddr::IP qw(:lower); use POSIX qw(ECONNREFUSED); @@ -207,6 +208,14 @@ sub disable_ipv6 { close($fh); } +my $bridge_disable_interface_learning = sub { + my ($iface) = @_; + + PVE::ProcFSTools::write_proc_entry("/sys/class/net/$iface/brport/unicast_flood", "0"); + PVE::ProcFSTools::write_proc_entry("/sys/class/net/$iface/brport/learning", "0"); + +}; + my $bridge_add_interface = sub { my ($bridge, $iface, $tag, $trunks) = @_; @@ -268,6 +277,43 @@ my $activate_interface = sub { die "can't activate interface '$iface' - $@\n" if $@; }; +sub add_bridge_fdb { + my ($iface, $mac) = @_; + + my $learning = PVE::Tools::file_read_firstline("/sys/class/net/$iface/brport/learning"); + return if $learning; + + my ($vmid, $devid) = &$parse_tap_device_name($iface, 1); + return if !defined($vmid); + + PVE::Tools::run_command(['/sbin/bridge', 'fdb', 'append', $mac, 'dev', $iface, 'master', 'static']); + + my ($fwbr, $vethfw, $vethfwpeer, $ovsintport) = &$compute_fwbr_names($vmid, $devid); + + if (-d "/sys/class/net/$vethfwpeer") { + PVE::Tools::run_command(['/sbin/bridge', 'fdb', 'append', $mac, 'dev', $vethfwpeer, 'master', 'static']); + } + +} + +sub del_bridge_fdb { + my ($iface, $mac) = @_; + + my $learning = PVE::Tools::file_read_firstline("/sys/class/net/$iface/brport/learning"); + return if $learning; + + my ($vmid, $devid) = &$parse_tap_device_name($iface, 1); + return if !defined($vmid); + + PVE::Tools::run_command(['/sbin/bridge', 'fdb', 'del', $mac, 'dev', $iface, 'master', 'static']); + + my ($fwbr, $vethfw, $vethfwpeer, $ovsintport) = &$compute_fwbr_names($vmid, $devid); + + if (-d "/sys/class/net/$vethfwpeer") { + PVE::Tools::run_command(['/sbin/bridge', 'fdb', 'del', $mac, 'dev', $vethfwpeer, 'master', 'static']); + } +} + sub tap_create { my ($iface, $bridge) = @_; @@ -322,7 +368,7 @@ sub veth_delete { } my $create_firewall_bridge_linux = sub { - my ($iface, $bridge, $tag, $trunks) = @_; + my ($iface, $bridge, $tag, $trunks, $disablelearning) = @_; my ($vmid, $devid) = &$parse_tap_device_name($iface); my ($fwbr, $vethfw, $vethfwpeer) = &$compute_fwbr_names($vmid, $devid); @@ -333,14 +379,15 @@ my $create_firewall_bridge_linux = sub { copy_bridge_config($bridge, $fwbr); veth_create($vethfw, $vethfwpeer, $bridge); - &$bridge_add_interface($fwbr, $vethfw); &$bridge_add_interface($bridge, $vethfwpeer, $tag, $trunks); + &$bridge_disable_interface_learning($vethfwpeer) if $disablelearning; + &$bridge_add_interface($fwbr, $vethfw); &$bridge_add_interface($fwbr, $iface); }; my $create_firewall_bridge_ovs = sub { - my ($iface, $bridge, $tag, $trunks) = @_; + my ($iface, $bridge, $tag, $trunks, $disablelearning) = @_; my ($vmid, $devid) = &$parse_tap_device_name($iface); my ($fwbr, undef, undef, $ovsintport) = &$compute_fwbr_names($vmid, $devid); @@ -359,6 +406,7 @@ my $create_firewall_bridge_ovs = sub { PVE::Tools::run_command(['/sbin/ip', 'link', 'set', $ovsintport, 'mtu', $bridgemtu]); &$bridge_add_interface($fwbr, $ovsintport); + &$bridge_disable_interface_learning($ovsintport) if $disablelearning; }; my $cleanup_firewall_bridge = sub { @@ -383,7 +431,7 @@ my $cleanup_firewall_bridge = sub { }; sub tap_plug { - my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_; + my ($iface, $bridge, $tag, $firewall, $trunks, $rate, $disablelearning) = @_; #cleanup old port config from any openvswitch bridge eval {run_command("/usr/bin/ovs-vsctl del-port $iface", outfunc => sub {}, errfunc => sub {}) }; @@ -402,16 +450,17 @@ sub tap_plug { } if ($firewall) { - &$create_firewall_bridge_linux($iface, $bridge, $tag, $trunks); + &$create_firewall_bridge_linux($iface, $bridge, $tag, $trunks, $disablelearning); } else { &$bridge_add_interface($bridge, $iface, $tag, $trunks); } + &$bridge_disable_interface_learning($iface) if $disablelearning; } else { &$cleanup_firewall_bridge($iface); # remove stale devices if ($firewall) { - &$create_firewall_bridge_ovs($iface, $bridge, $tag, $trunks); + &$create_firewall_bridge_ovs($iface, $bridge, $tag, $trunks, $disablelearning); } else { &$ovs_bridge_add_port($bridge, $iface, $tag, undef, $trunks); } @@ -600,6 +649,84 @@ sub is_ip_in_cidr { return $overlap == $Net::IP::IP_B_IN_A_OVERLAP || $overlap == $Net::IP::IP_IDENTICAL; } +# get all currently configured addresses that have a global scope, i.e., are reachable from the +# outside of the host and thus are neither loopback nor link-local ones +# returns an array ref of: { addr => "IP", cidr => "IP/PREFIXLEN", family => "inet|inet6" } +sub get_reachable_networks { + my $raw = ''; + run_command([qw(ip -j addr show up scope global)], outfunc => sub { $raw .= shift }); + my $decoded = decode_json($raw); + + my $addrs = []; # filter/transform first so that we can sort correctly more easily below + for my $e ($decoded->@*) { + next if !$e->{addr_info} || grep { $_ eq 'LOOPBACK' } $e->{flags}->@*; + push $addrs->@*, grep { scalar(keys $_->%*) } $e->{addr_info}->@* + } + my $res = []; + for my $info (sort { $a->{family} cmp $b->{family} || $a->{local} cmp $b->{local} } $addrs->@*) { + push $res->@*, { + addr => $info->{local}, + cidr => "$info->{local}/$info->{prefixlen}", + family => $info->{family}, + }; + } + + return $res; +} + +# get one or all local IPs that are not loopback ones, able to pick up the following ones (in order) +# - the hostname primary resolves too, follows gai.conf (admin controlled) and will be prioritised +# - all configured in the interfaces configuration +# - all currently networks known to the kernel in the current (root) namespace +# returns a single address if no parameter is passed, and all found, grouped by type, if `all => 1` +# is passed. +sub get_local_ip { + my (%param) = @_; + + my $nodename = PVE::INotify::nodename(); + my $resolved_host = eval { get_ip_from_hostname($nodename) }; + + return $resolved_host if defined($resolved_host) && !$param{all}; + + my $all = { v4 => {}, v6 => {} }; # hash to avoid duplicates and group by type + + my $ifaces = PVE::INotify::read_file('interfaces', 1)->{data}->{ifaces}; + for my $if (values $ifaces->%*) { + next if $if->{type} eq 'loopback' || (!defined($if->{address}) && !defined($if->{address6})); + my ($v4, $v6) = ($if->{address}, $if->{address6}); + + return ($v4 // $v6) if !$param{all}; # prefer v4, admin can override $resolved_host via hosts/gai.conf + + $all->{v4}->{$v4} = 1 if defined($v4); + $all->{v6}->{$v6} = 1 if defined($v6); + } + + my $live = eval { get_reachable_networks() } // []; + for my $info ($live->@*) { + my $addr = $info->{addr}; + + return $addr if !$param{all}; + + if ($info->{family} eq 'inet') { + $all->{v4}->{$addr} = 1; + } else { + $all->{v6}->{$addr} = 1; + } + } + + return undef if !$param{all}; # getting here means no early return above triggered -> no IPs + + my $res = []; # order gai.conf controlled first, then group v4 and v6, simply lexically sorted + if ($resolved_host) { + push $res->@*, $resolved_host; + delete $all->{v4}->{$resolved_host}; + delete $all->{v6}->{$resolved_host}; + } + push $res->@*, sort { $a cmp $b } keys $all->{v4}->%*; + push $res->@*, sort { $a cmp $b } keys $all->{v6}->%*; + + return $res; +} sub get_local_ip_from_cidr { my ($cidr) = @_;