net: add get_local_ip helper

Sometimes we need to have a fallback for gai (get_ip_from_hostname)
but cannot yet rely on configured networks (get_reachable_networks)
from kernel POV (those may not have been configured yet, e.g., on
boot), so the ones configured in /etc/network/interfaces would be
nice too then, as they're the ones that will get configured soon
anyway on boot.

Add a new helper that takes in all those sources and allows to return
a single (first found) or all of those addresses.

Still prioritize the address we get from getaddrinfo, as there the
admin has control through /etc/hosts, DNS and gai.conf and treat the
remaining ones as fallback.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2021-09-18 14:38:59 +02:00
parent ebbbb05e00
commit ac487a888b

View File

@ -626,6 +626,60 @@ sub get_reachable_networks {
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 = 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) = @_;