From 2b6337d67d59827a289fe9004adb079106c1a22d Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Thu, 3 Sep 2026 17:14:28 -0300 Subject: [PATCH] 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> --- .../unit/networkutils_getipaddr_onlyv4.t | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 xCAT-test/unit/networkutils_getipaddr_onlyv4.t diff --git a/xCAT-test/unit/networkutils_getipaddr_onlyv4.t b/xCAT-test/unit/networkutils_getipaddr_onlyv4.t new file mode 100644 index 000000000..4e64315c6 --- /dev/null +++ b/xCAT-test/unit/networkutils_getipaddr_onlyv4.t @@ -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=
:/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();