2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-04 20:17:55 +00:00

test(networkutils): capture OnlyV4 being answered with a cached IPv6 address

getipaddr answers from %::hostiphash before it resolves. The bypass tests
OnlyV6 and GetAllAddresses and does not test OnlyV4, so a caller that asks for
IPv4 is handed whatever the first lookup cached.

An unrestricted lookup passes AF_UNSPEC to getaddrinfo, so on a dual-stack
management node with an AAAA record it caches the IPv6 address. xcatd is
long-lived and the hash is a global, so one earlier caller poisons every OnlyV4
caller after it.

debian.pm then writes nfsroot=2001:db8::1:/install, which is not a parseable
nfsroot, and the Subiquity install never mounts. dhcp.pm and mknb.pm hold four
more OnlyV4 callers with the same exposure.

The test also pins what the fix must not break: an IPv4 cache entry is still
served to an OnlyV4 caller, and an unrestricted caller still gets its cache hit
whatever family it holds.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-03 17:14:28 -03:00
parent 05fc81f7b5
commit 2b6337d67d
@@ -0,0 +1,47 @@
#!/usr/bin/env perl
#
# getipaddr caches a resolved address in %::hostiphash and answers later calls
# from it. The cache bypass tests OnlyV6 and GetAllAddresses, and does not test
# OnlyV4, so a caller that asks for IPv4 can be handed a cached IPv6 address.
#
# The cache is filled by whichever lookup ran first. An unrestricted lookup
# passes AF_UNSPEC to getaddrinfo, and on a dual-stack management node with an
# AAAA record that answers with the IPv6 address, which is then stored. xcatd is
# long-lived and %::hostiphash is a global, so any earlier caller in the process
# poisons every OnlyV4 caller that follows.
#
# What it costs: debian.pm builds the Subiquity install command line with
# getipaddr($host, OnlyV4 => 1) and writes nfsroot=<address>:/install. With an
# IPv6 address that renders as nfsroot=2001:db8::1:/install, which is not a
# parseable nfsroot, so the installer never mounts and the node never installs.
# dhcp.pm and mknb.pm carry four more OnlyV4 callers with the same exposure.
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../../perl-xCAT";
use Test::More;
use xCAT::NetworkUtils;
# The address an unrestricted lookup left behind on a dual-stack host.
$::hostiphash{'mn.cluster'}{hostip} = '2001:db8::1';
my $only_v4 = xCAT::NetworkUtils->getipaddr('mn.cluster', OnlyV4 => 1);
ok(!defined($only_v4) || $only_v4 !~ /:/,
'OnlyV4 does not return the IPv6 address an earlier lookup cached')
or diag("getipaddr returned '$only_v4', which renders as nfsroot=$only_v4:/install");
# An IPv4 entry must still be served from the cache: the bypass is about the
# family of the cached answer, not about disabling the cache for OnlyV4.
$::hostiphash{'v4.cluster'}{hostip} = '10.1.2.3';
is(xCAT::NetworkUtils->getipaddr('v4.cluster', OnlyV4 => 1), '10.1.2.3',
'an IPv4 cache entry is still served to an OnlyV4 caller');
# An unrestricted caller keeps its cache hit, whatever family it holds.
is(xCAT::NetworkUtils->getipaddr('mn.cluster'), '2001:db8::1',
'a caller that did not ask for IPv4 still gets the cached address');
done_testing();