Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3928c04
feat(ipv6): add EL10 IPv6-only provisioning support
viniciusferrao Oct 21, 2026
273593c
fix(ipv6): preserve shared-DUID Kea reservations
viniciusferrao Oct 21, 2026
5bc3f02
fix(ipv6): format Kea Control Agent IPv6 URLs
viniciusferrao Oct 21, 2026
4a28c9d
fix(ipv6): centralize address-safe ping result matching
viniciusferrao Oct 21, 2026
8149ddb
refactor(ipv6): reuse IPv6 server selection in boot paths
viniciusferrao Oct 21, 2026
3e3acf7
fix(ipv6): complete service-node DHCPv6 subnet intent
viniciusferrao Oct 21, 2026
deddacf
fix(ipv6): target authoritative DNS from Kea service nodes
viniciusferrao Oct 21, 2026
f95a9ec
fix(ipv6): keep IPv6-only nodes out of Kea DHCPv4 state
viniciusferrao Oct 21, 2026
54e07fc
fix(ipv6): honor disjoint DHCP ownership in Kea
viniciusferrao Oct 21, 2026
b1e3d41
fix(ipv6): the ported IPv6 tests measure a design that is not on master
dhilst Oct 21, 2026
7682983
test(ipv6): shell under test is written in bats, not in a .t
dhilst Oct 21, 2026
f101650
fix(ipv6): site.master takes the link-local address of the management…
dhilst Oct 21, 2026
d9763bd
fix(ipv6): the link-local order is applied to one resolver path of two
dhilst Oct 21, 2026
49696e9
fix(ipv6): makedhcp refuses an IPv6 network whose mask carries a slash
dhilst Oct 21, 2026
6f171a2
fix(ipv6): makedhcp reports an error for a node whose boot server is …
dhilst Oct 21, 2026
189311d
chore(build): ignore the files a build writes into the source tree
dhilst Oct 21, 2026
c82941a
test(ipv6): the link-local order of getipaddr has no test
dhilst Oct 22, 2026
1eb2ac6
test(ipv6): three makehosts IPv6 address tests are missing
dhilst Oct 22, 2026
ec24b6d
fix(ipv6): the Socket6 dependency contradicts the code that loads it
dhilst Oct 22, 2026
f423a2f
test(ipv6): sixteen IPv6 assertions match source text, not behaviour
dhilst Oct 22, 2026
0c7718a
test(ipv6): pre.rhels10 and a dead ddns routine have no assertions
dhilst Oct 22, 2026
605705f
fix(makehosts): an IPv6 node that changes address keeps both host ent…
dhilst Oct 22, 2026
e469ff0
test(xcat-core): catch a netboot handler that is not executable
dhilst Oct 22, 2026
ff432ba
fix(xcat-core): an EL10 diskless node powers off with no netroot handler
dhilst Oct 22, 2026
23afd1b
fix(xcat-core): a syslog target with a port is bracketed as if it wer…
dhilst Oct 23, 2026
86b0b18
Merge upstream/master into feat/ipv6-foundation-clean
dhilst Oct 23, 2026
f6c0206
fix(dhcp): the DUID-UUID xCAT writes is not the one a UEFI client sends
dhilst Oct 23, 2026
24903fb
test(xcat-core): nothing asserts that makedhcp serves an IPv6-only node
dhilst Oct 23, 2026
f4108a3
probe
dhilst Oct 23, 2026
6d4f1ff
Merge upstream/master into feat/ipv6-foundation-clean
dhilst Oct 23, 2026
cfca1dc
fix(xcat-core): an aarch64 node over IPv6 gets a boot file name witho…
dhilst Oct 23, 2026
6382f7b
test(xcat-core): the DHCPv6 DUID a node sends is not the one makedhcp…
dhilst Oct 24, 2026
9e23418
fix(xcat-core): a DHCPv6 reservation carries a DUID the client does n…
dhilst Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@ xCAT-client/share/doc

# Build residue: buildrpms.pl rewrites this at every run.
Gitinfo

# Build residue: buildrpms.pl and builddebs.pl write these into the source
# tree, so a "git add -A" after a build sweeps them into a commit.
dist/
perl-xCAT/pods
perl-xCAT/share/doc
perl-xCAT/share/man
xCAT-client/pods/man1/xcat.1.pod
xCAT-vlan/share
xCAT-test/share/man
xCAT-buildkit/share/man
pod2htmd.tmp
8 changes: 8 additions & 0 deletions docs/source/QA/makehosts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ There are 3 methods to configure aliases:

20.1.1.1 node1-hd

* For an IPv6 address, use ``!`` instead of ``:`` to separate the interface hostname and address: ::

chdef node1 otherinterfaces='node1-v6!2001:db8::101'

After executing ``makehosts -n``, the generated record is: ::

2001:db8::101 node1-v6

**Note**: If suffixes or aliases for the same IP are configured in both ``hosts`` table and ``nics`` table, will cause conflicts. ``makehosts`` will use values from ``nics`` table. The values from ``nics`` table will over-write that from ``hosts`` table to create ``/etc/hosts`` records.

Q: How to handle the same short hostname in different domains?
Expand Down
5 changes: 2 additions & 3 deletions docs/source/guides/admin-guides/references/man5/hosts.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ hosts Attributes:

\ **ip**\

The IP address of the node. This is only used in makehosts. The rest of xCAT uses system name resolution to resolve node names to IP addresses.
The IPv4 or IPv6 address literal of the node. makehosts and makedns use this value; the rest of xCAT uses system name resolution to resolve node names to IP addresses.



Expand All @@ -56,7 +56,7 @@ hosts Attributes:

\ **otherinterfaces**\

Other IP addresses to add for this node. Format: -<ext>:<ip>,<intfhostname>:<ip>,...
Other IP addresses to add for this node. Format: -<ext>:<IPv4>, <intfhostname>:<IPv4>, -<ext>!<IPv6>, or <intfhostname>!<IPv6>. A leading - derives the interface hostname from the node name; ! is required as the delimiter for IPv6 addresses.



Expand All @@ -79,4 +79,3 @@ SEE ALSO


\ **nodels(1)**\ , \ **chtab(8)**\ , \ **tabdump(8)**\ , \ **tabedit(8)**\

5 changes: 2 additions & 3 deletions docs/source/guides/admin-guides/references/man7/group.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ group Attributes:

\ **ip**\ (hosts.ip)

The IP address of the node. This is only used in makehosts. The rest of xCAT uses system name resolution to resolve node names to IP addresses.
The IPv4 or IPv6 address literal of the node. makehosts and makedns use this value; the rest of xCAT uses system name resolution to resolve node names to IP addresses.



Expand Down Expand Up @@ -705,7 +705,7 @@ group Attributes:

\ **otherinterfaces**\ (hosts.otherinterfaces)

Other IP addresses to add for this node. Format: -<ext>:<ip>,<intfhostname>:<ip>,...
Other IP addresses to add for this node. Format: -<ext>:<IPv4>, <intfhostname>:<IPv4>, -<ext>!<IPv6>, or <intfhostname>!<IPv6>. A leading - derives the interface hostname from the node name; ! is required as the delimiter for IPv6 addresses.



Expand Down Expand Up @@ -1392,4 +1392,3 @@ SEE ALSO


\ **mkdef(1)**\ , \ **chdef(1)**\ , \ **lsdef(1)**\ , \ **rmdef(1)**\

5 changes: 2 additions & 3 deletions docs/source/guides/admin-guides/references/man7/node.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ node Attributes:

\ **ip**\ (hosts.ip)

The IP address of the node. This is only used in makehosts. The rest of xCAT uses system name resolution to resolve node names to IP addresses.
The IPv4 or IPv6 address literal of the node. makehosts and makedns use this value; the rest of xCAT uses system name resolution to resolve node names to IP addresses.



Expand Down Expand Up @@ -711,7 +711,7 @@ node Attributes:

\ **otherinterfaces**\ (hosts.otherinterfaces)

Other IP addresses to add for this node. Format: -<ext>:<ip>,<intfhostname>:<ip>,...
Other IP addresses to add for this node. Format: -<ext>:<IPv4>, <intfhostname>:<IPv4>, -<ext>!<IPv6>, or <intfhostname>!<IPv6>. A leading - derives the interface hostname from the node name; ! is required as the delimiter for IPv6 addresses.



Expand Down Expand Up @@ -1428,4 +1428,3 @@ SEE ALSO


\ **mkdef(1)**\ , \ **chdef(1)**\ , \ **lsdef(1)**\ , \ **rmdef(1)**\

21 changes: 18 additions & 3 deletions docs/source/guides/admin-guides/references/man8/makehosts.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,31 @@ EXAMPLES
"compute","|node(\d+)|1.2.3.($1+0)|","|(.*)|($1).cluster.net|",,


Assuming the group "compute" contains node01, node02, etc., then in /etc/hosts they will be given
Assuming the group "compute" contains node01, node02, etc., then in /etc/hosts they will be given
IP addresses of 1.2.3.1, 1.2.3.2, etc.



3. Add an IPv6 primary address and an IPv6 additional interface for node01. The
exclamation mark separates an interface hostname from an IPv6 address because
the address itself contains colons. This example assumes the site domain is
example.com.

.. code-block:: perl

chdef node01 ip=2001:db8::10 otherinterfaces='node01-storage!2001:db8:1::10'
makehosts node01

The resulting entries include:

.. code-block:: perl

2001:db8::10 node01 node01.example.com
2001:db8:1::10 node01-storage node01-storage.example.com


********
SEE ALSO
********


hosts(5)|hosts.5, makedns(8)|makedns.8

Expand Down
39 changes: 35 additions & 4 deletions perl-xCAT/xCAT/DHCP/Backend/Kea.pm
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,17 @@ sub check_services {
}

sub upsert_reservations {
my ( $self, $config, $reservations ) = @_;
my ( $self, $config, $reservations, $deleted ) = @_;

# Remove stale matches before adding the complete requested set. A DHCPv6
# client DUID is valid in more than one subnet, so deleting while adding
# would otherwise remove an earlier reservation from the same batch.
foreach my $reservation (@$reservations) {
my $removed = $self->delete_reservations($config, $reservation);
push @$deleted, @$removed if $deleted;
}

foreach my $reservation (@$reservations) {
$self->delete_reservations($config, $reservation);
my $subnet = _find_subnet_by_id( $config, $reservation->{'subnet-id'} );
next unless $subnet;
$subnet->{reservations} ||= [];
Expand Down Expand Up @@ -740,7 +747,9 @@ sub control_agent_url {

my $host = $opts{host} || $self->{control_agent_host} || '127.0.0.1';
my $port = $opts{port} || $self->{control_agent_port} || 8000;
return "http://$host:$port/";
my $authority = xCAT::NetworkUtils->format_host_port($host, $port);
return unless $authority;
return "http://$authority/";
}

sub control_agent_command {
Expand Down Expand Up @@ -781,10 +790,32 @@ sub live_upsert_reservations {
my ( $self, $reservations, %opts ) = @_;

my $service = $opts{service} || ['dhcp4'];
foreach my $reservation (@$reservations) {
my @delete = (
@{ $opts{stale_reservations} || [] },
@$reservations,
);
my %seen;
foreach my $reservation (@delete) {
my ( $identifier_type, $identifier );
if ($reservation->{'hw-address'}) {
( $identifier_type, $identifier ) = ( 'hw-address', $reservation->{'hw-address'} );
} elsif ($reservation->{duid}) {
( $identifier_type, $identifier ) = ( 'duid', $reservation->{duid} );
} elsif ($reservation->{'ip-address'}) {
( $identifier_type, $identifier ) = ( 'ip-address', $reservation->{'ip-address'} );
} elsif (ref($reservation->{'ip-addresses'}) eq 'ARRAY' && @{ $reservation->{'ip-addresses'} }) {
( $identifier_type, $identifier ) = ( 'ip-address', $reservation->{'ip-addresses'}[0] );
}
my $key = join('|',
map { defined($_) ? lc($_) : '' }
$reservation->{'subnet-id'}, $identifier_type, $identifier,
);
next if $seen{$key}++;
my $delete = $self->_live_delete_reservation($reservation, service => $service, ignore_not_found => 1);
return $delete if $delete->{error};
}

foreach my $reservation (@$reservations) {
my ($stored) = @{ _render_entry_option_data( [$reservation] ) };
my $result = $self->control_agent_command(
'reservation-add',
Expand Down
175 changes: 172 additions & 3 deletions perl-xCAT/xCAT/DSHCore.pm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ use strict;
use Socket;

use xCAT::MsgUtils;
use xCAT::NetworkUtils;
use xCAT::Utils;
use Text::ParseWords qw(shellwords);

=head1 NAME

xCAT::DSHCore - distributed command execution and reachability helpers

=head1 FUNCTIONS

=head2 Process and ping helpers

#---------------------------------------------------------------------------

Expand Down Expand Up @@ -676,6 +686,166 @@ sub ping_hostnames
}


#---------------------------------------------------------------------------

=head3
partition_ping_targets

Resolves ping targets and separates them into the address family that
should be probed. IPv4 remains preferred for dual-stack targets so
existing behavior is preserved.

Arguments:
@targets - hostnames or address literals

Returns:
references to the IPv4, IPv6, and unresolved target arrays

=cut

#---------------------------------------------------------------------------

sub partition_ping_targets
{
my ($class, @targets) = @_;
my (@ipv4, @ipv6, @unresolved);

foreach my $target (@targets)
{
my $family = xCAT::NetworkUtils->node_address_family($target);
if (defined($family) && $family == 4)
{
push @ipv4, $target;
}
elsif (defined($family) && $family == 6)
{
push @ipv6, $target;
}
else
{
push @unresolved, $target;
}
}

return (\@ipv4, \@ipv6, \@unresolved);
}


#---------------------------------------------------------------------------

=head3
nmap_ping_command

Builds the nmap command pping runs for one address family.

Arguments:
$family - 4 or 6
$more_options - the site nmapoptions value, or undef
$targets - reference to the target list

Returns:
the command as a list

Comments:
The command is a list, not a string, so pping can exec it
without a shell. nmap needs -6 for an IPv6 target: it reads an
IPv6 literal as a hostname otherwise.

=cut

#---------------------------------------------------------------------------

sub nmap_ping_command
{
my ($class, $family, $more_options, $targets) = @_;

my @command = ('nmap');
push @command, '-6' if defined($family) && $family == 6;

# Port 22 is here for the unprivileged case (#4324).
push @command, qw(-PE --system-dns --send-ip -sP --unprivileged -PA80,443,22);
push @command, shellwords($more_options)
if defined($more_options) && length($more_options);
push @command, @{ $targets || [] };
return @command;
}


#---------------------------------------------------------------------------

=head3
fping_ping_command

Builds the fping command pping runs for one address family.

Arguments:
$family - 4 or 6
$binaries - reference to a hash with the fping and fping6
paths that exist on this host, either one undef
$targets - reference to the target list

Returns:
the command as a list, or an empty list when no fping on this
host can probe that family

Comments:
fping6 is a separate program on an older distribution. A
current fping takes -6 instead and ships no fping6.

=cut

#---------------------------------------------------------------------------

sub fping_ping_command
{
my ($class, $family, $binaries, $targets) = @_;
$binaries ||= {};

my @command;
if (defined($family) && $family == 6)
{
if ($binaries->{fping6})
{
@command = ($binaries->{fping6});
}
elsif ($binaries->{fping})
{
@command = ($binaries->{fping}, '-6');
}
}
elsif ($binaries->{fping})
{
@command = ($binaries->{fping});
}
return () unless @command;

push @command, '-a', @{ $targets || [] };
return @command;
}


#---------------------------------------------------------------------------

=head3
parse_pping_result_line

Parses the stable C<target: ping|noping> output contract. The greedy
target match intentionally accepts IPv6 address literals containing
colons.

=cut

#---------------------------------------------------------------------------

sub parse_pping_result_line
{
my ($class, $line) = @_;
return unless defined($line);
return ($1, $2) if $line =~ /^(.+):\s+(ping|noping)\s*$/;
return;
}


#---------------------------------------------------------------------------

=head3
Expand Down Expand Up @@ -735,9 +905,8 @@ sub pping_hostnames
my @no_response = ();
foreach my $line (@output)
{
my ($hostname, $result) = split ':', $line;
my ($token, $status) = split ' ', $result;
chomp($token);
my ($hostname, $token) = $class->parse_pping_result_line($line);
next unless defined($hostname);
if ($token ne 'ping') {
push @no_response, $hostname;
}
Expand Down
Loading