From 33d9ac6eeb1b0b31da0f30e5250729145f5c700d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:09:22 -0300 Subject: [PATCH 01/10] refactor(networkutils): rename isIpaddr to isIpv4addr --- perl-xCAT/xCAT/FSPUtils.pm | 4 ++-- perl-xCAT/xCAT/NetworkUtils.pm | 20 +++++++++++++++++--- perl-xCAT/xCAT/PPCconn.pm | 2 +- perl-xCAT/xCAT/PPCdb.pm | 6 +++--- xCAT-probe/lib/perl/LogParse.pm | 6 +++--- xCAT-probe/subcmds/discovery | 2 +- xCAT-probe/subcmds/xcatmn | 4 ++-- xCAT-server/lib/xcat/plugins/DBobjectdefs.pm | 2 +- xCAT-server/lib/xcat/plugins/dhcp.pm | 2 +- xCAT-server/lib/xcat/plugins/hosts.pm | 8 ++++---- xCAT-server/lib/xcat/plugins/openbmc.pm | 8 ++++---- xCAT-server/lib/xcat/plugins/openbmc2.pm | 6 +++--- 12 files changed, 42 insertions(+), 28 deletions(-) diff --git a/perl-xCAT/xCAT/FSPUtils.pm b/perl-xCAT/xCAT/FSPUtils.pm index 397f50fe2..4eb296a61 100644 --- a/perl-xCAT/xCAT/FSPUtils.pm +++ b/perl-xCAT/xCAT/FSPUtils.pm @@ -139,7 +139,7 @@ sub getIPaddress my $type = shift; my $nodetocheck = shift; my $port = shift; - if (xCAT::NetworkUtils::isIpaddr($nodetocheck)) { + if (xCAT::NetworkUtils::isIpv4addr($nodetocheck)) { return $nodetocheck; } my $side = "[A|B]"; @@ -201,7 +201,7 @@ sub getIPaddress if ($tmp_s and $tmp_s =~ /^$side-$port$/i) { $tmp_s =~ s/a/A/; $tmp_s =~ s/b/B/; - if (xCAT::NetworkUtils::isIpaddr($tmp_n)) { + if (xCAT::NetworkUtils::isIpv4addr($tmp_n)) { $node_side_pairs{$tmp_s} = $tmp_n; $children_num++; } else { diff --git a/perl-xCAT/xCAT/NetworkUtils.pm b/perl-xCAT/xCAT/NetworkUtils.pm index 02b0fe8ea..b007789e2 100644 --- a/perl-xCAT/xCAT/NetworkUtils.pm +++ b/perl-xCAT/xCAT/NetworkUtils.pm @@ -1687,7 +1687,7 @@ sub getNodeIPaddress } # Quick return if pass in an IP - return $nodetocheck if (xCAT::NetworkUtils->isIpaddr($nodetocheck)); + return $nodetocheck if (xCAT::NetworkUtils->isIpv4addr($nodetocheck)); my $nodeip = xCAT::NetworkUtils->getipaddr($nodetocheck); if (!$nodeip) @@ -2132,7 +2132,7 @@ sub validate_ip #------------------------------------------------------------------------------- -=head3 isIpaddr +=head3 isIpv4addr returns 1 if parameter is has a valid IP address form. @@ -2154,7 +2154,7 @@ sub validate_ip =cut #------------------------------------------------------------------------------- -sub isIpaddr +sub isIpv4addr { my $addr = shift; if (($addr) && ($addr =~ /xCAT::NetworkUtils/)) @@ -2183,6 +2183,20 @@ sub isIpaddr } } +#------------------------------------------------------------------------------- + +=head3 isIpaddr + + Deprecated alias for isIpv4addr, kept for external callers. + +=cut + +#------------------------------------------------------------------------------- +sub isIpaddr +{ + return isIpv4addr(@_); +} + diff --git a/perl-xCAT/xCAT/PPCconn.pm b/perl-xCAT/xCAT/PPCconn.pm index 84c3dad9c..47d396e1e 100644 --- a/perl-xCAT/xCAT/PPCconn.pm +++ b/perl-xCAT/xCAT/PPCconn.pm @@ -520,7 +520,7 @@ sub mkhwconn for my $nn (@newnodes) { my $node_ip; - unless (xCAT::NetworkUtils->isIpaddr($nn)) { + unless (xCAT::NetworkUtils->isIpv4addr($nn)) { $node_ip = xCAT::NetworkUtils::getNodeIPaddress($nn); } else { $node_ip = $nn; diff --git a/perl-xCAT/xCAT/PPCdb.pm b/perl-xCAT/xCAT/PPCdb.pm index 51967544d..ff8226a55 100644 --- a/perl-xCAT/xCAT/PPCdb.pm +++ b/perl-xCAT/xCAT/PPCdb.pm @@ -1106,7 +1106,7 @@ sub get_host { } if ($tmpmtm eq $mtm and $tmpsn eq $sn) { - my $ifip = xCAT::NetworkUtils->isIpaddr($oldnode); + my $ifip = xCAT::NetworkUtils->isIpv4addr($oldnode); if ($ifip) { # which means that the node is defined by the new lsslp if ($tmpside eq $side) { # match! which means that node is the same as the new one if ($ip eq $tmpip) { #which means that the ip is not changed @@ -1230,7 +1230,7 @@ sub get_host { } # not matched, use the new name - my $ifip = xCAT::NetworkUtils->isIpaddr($nodename); + my $ifip = xCAT::NetworkUtils->isIpv4addr($nodename); unless ($ifip) { return $nodename; } else { @@ -1338,7 +1338,7 @@ sub read_from_table { @{ $::OLD_DATA_CACHE{ $entry->{node} } }[1] = @{ $vpdhash{ $entry->{node} } }[1]; #sn @{ $::OLD_DATA_CACHE{ $entry->{node} } }[2] = @{ $vpdhash{ $entry->{node} } }[2]; #side # find node ip address, check node name first, then check hosts table - my $ifip = xCAT::NetworkUtils->isIpaddr($entry->{node}); + my $ifip = xCAT::NetworkUtils->isIpv4addr($entry->{node}); if ($ifip) { @{ $::OLD_DATA_CACHE{ $entry->{node} } }[3] = $entry->{node}; #ip diff --git a/xCAT-probe/lib/perl/LogParse.pm b/xCAT-probe/lib/perl/LogParse.pm index c2a9f6273..ba0b350b6 100644 --- a/xCAT-probe/lib/perl/LogParse.pm +++ b/xCAT-probe/lib/perl/LogParse.pm @@ -487,7 +487,7 @@ sub obtain_log_content { if ($split_line[0] =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(.+)-(.+)/) { $log_content{time_record} = "$4:$5:$6"; $log_content{time} = $self->convert_to_epoch_seconds($split_line[0]); - if (!xCAT::NetworkUtils->isIpaddr($split_line[1])) { + if (!xCAT::NetworkUtils->isIpv4addr($split_line[1])) { my @sender_tmp = split(/\./, $split_line[1]); $log_content{sender} = $sender_tmp[0]; } else { @@ -515,7 +515,7 @@ sub obtain_log_content { my $timestamp = join(" ", @split_line[ 0 .. 2 ]); $log_content{time_record} = $split_line[2]; $log_content{time} = $self->convert_to_epoch_seconds($timestamp); - if (!xCAT::NetworkUtils->isIpaddr($split_line[3])) { + if (!xCAT::NetworkUtils->isIpv4addr($split_line[3])) { my @sender_tmp = split(/\./, $split_line[3]); $log_content{sender} = $sender_tmp[0]; } else { @@ -546,7 +546,7 @@ sub obtain_log_content { $log_content{time_record} = "$4:$5:$6"; } $log_content{time} = $self->convert_to_epoch_seconds($split_line[3]); - if (!xCAT::NetworkUtils->isIpaddr($split_line[0])) { + if (!xCAT::NetworkUtils->isIpv4addr($split_line[0])) { my @sender_tmp = split(/\./, $split_line[0]); $log_content{sender} = $sender_tmp[0]; } else { diff --git a/xCAT-probe/subcmds/discovery b/xCAT-probe/subcmds/discovery index b8a82df6c..f352c18f2 100755 --- a/xCAT-probe/subcmds/discovery +++ b/xCAT-probe/subcmds/discovery @@ -478,7 +478,7 @@ sub do_pre_check { exit 1; } - if (!xCAT::NetworkUtils->isIpaddr("$masteripinsite")) { + if (!xCAT::NetworkUtils->isIpv4addr("$masteripinsite")) { probe_utils->send_msg("stdout", "f", $msg); probe_utils->send_msg("stdout", "d", "The value of 'master' in 'site' table isn't an IP address"); exit 1; diff --git a/xCAT-probe/subcmds/xcatmn b/xCAT-probe/subcmds/xcatmn index a52209fe1..8b5e1b950 100755 --- a/xCAT-probe/subcmds/xcatmn +++ b/xCAT-probe/subcmds/xcatmn @@ -401,7 +401,7 @@ sub check_site_table { $rst = 1; } } else { - if (($attr eq "master") and (!xCAT::NetworkUtils->isIpaddr("$value"))) { + if (($attr eq "master") and (!xCAT::NetworkUtils->isIpv4addr("$value"))) { my $masterip = xCAT::NetworkUtils->getipaddr("$value"); if (! defined $masterip){ push @$error_ref, "The value of 'master' in 'site' table isn't an IP address and can not be resolved to a IP address"; @@ -571,7 +571,7 @@ sub check_directory { my $mountip; if ($mountoutput =~ /(.+):$sitetable_ref->{$dir} on $sitetable_ref->{$dir} /) { my $mountsource = $1; - if (xCAT::NetworkUtils->isIpaddr($mountsource)) { + if (xCAT::NetworkUtils->isIpv4addr($mountsource)) { $mountip = $mountsource; } else { $mountip = xCAT::NetworkUtils->getipaddr($mountsource); diff --git a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm index 6c154768a..c0bfdc2a9 100644 --- a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm +++ b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm @@ -4932,7 +4932,7 @@ sub isobjnamevalid{ $objtype="node" unless(defined $objtype and ($objtype ne "")); if($objtype eq "node"){ #the ip address as a valid node object name is a hack for p7IH support - if(($objname !~ /^[a-zA-Z0-9-_]+$/) and !xCAT::NetworkUtils->isIpaddr($objname)){ + if(($objname !~ /^[a-zA-Z0-9-_]+$/) and !xCAT::NetworkUtils->isIpv4addr($objname)){ return 0; } } elsif ($objtype eq "group"){ diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index c6afb2adb..03115a2a3 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -2424,7 +2424,7 @@ sub process_request foreach my $node (@{ $req->{node} }) { #need to change the way of finding IP for nodes - my $ifip = xCAT::NetworkUtils->isIpaddr($node); + my $ifip = xCAT::NetworkUtils->isIpv4addr($node); if ($ifip) { $ip_hash->{$node} = $node; diff --git a/xCAT-server/lib/xcat/plugins/hosts.pm b/xCAT-server/lib/xcat/plugins/hosts.pm index 596b73ac0..8ed4c68c8 100644 --- a/xCAT-server/lib/xcat/plugins/hosts.pm +++ b/xCAT-server/lib/xcat/plugins/hosts.pm @@ -297,7 +297,7 @@ sub addotherinterfaces } else { ($itf, $ip) = split(/:/, $_); } - if ($ip && xCAT::NetworkUtils->isIpaddr($ip)) + if ($ip && xCAT::NetworkUtils->isIpv4addr($ip)) { if ($itf =~ /^-/) { @@ -330,7 +330,7 @@ sub delotherinterfaces } else { ($itf, $ip) = split(/:/, $_); } - if ($ip && xCAT::NetworkUtils->isIpaddr($ip)) + if ($ip && xCAT::NetworkUtils->isIpv4addr($ip)) { if ($itf =~ /^-/) { @@ -393,7 +393,7 @@ sub add_hosts_content { } else { - if (xCAT::NetworkUtils->isIpaddr($ip)) + if (xCAT::NetworkUtils->isIpv4addr($ip)) { addnode $callback, $nodename, $ip, $ref->{hostnames}, $domain; } @@ -632,7 +632,7 @@ sub process_request $domain = $::XCATSITEVALS{domain}; } - if (xCAT::NetworkUtils->isIpaddr($_->{ip})) + if (xCAT::NetworkUtils->isIpv4addr($_->{ip})) { addnode $callback, $_->{node}, $_->{ip}, $_->{hostnames}, $domain; } diff --git a/xCAT-server/lib/xcat/plugins/openbmc.pm b/xCAT-server/lib/xcat/plugins/openbmc.pm index f02436a79..0dc450910 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc.pm @@ -1359,7 +1359,7 @@ sub rspconfig_parse_address_port { return (undef, undef, "Invalid parameter: $value"); } - return (undef, undef, "Invalid parameter: $address") if (!xCAT::NetworkUtils->isIpaddr($address)); + return (undef, undef, "Invalid parameter: $address") if (!xCAT::NetworkUtils->isIpv4addr($address)); return (undef, undef, "Invalid parameter: $port") if (!rspconfig_valid_port($port) or $port == 0); return ($address, $port, undef); } @@ -1678,12 +1678,12 @@ sub parse_args { my $nodes_num = @$noderange; return ([ 1, "Invalid parameter for option $key" ]) if (!$value and $key ne ("ntpservers")); - return ([ 1, "Invalid parameter for option $key: $value" ]) if (($key eq "netmask") and !xCAT::NetworkUtils->isIpaddr($value)); - return ([ 1, "Invalid parameter for option $key: $value" ]) if (($key eq "gateway") and ($value !~ "0.0.0.0" and !xCAT::NetworkUtils->isIpaddr($value))); + return ([ 1, "Invalid parameter for option $key: $value" ]) if (($key eq "netmask") and !xCAT::NetworkUtils->isIpv4addr($value)); + return ([ 1, "Invalid parameter for option $key: $value" ]) if (($key eq "gateway") and ($value !~ "0.0.0.0" and !xCAT::NetworkUtils->isIpv4addr($value))); if ($key eq "ip") { return ([ 1, "Can not configure more than 1 nodes' ip at the same time" ]) if ($nodes_num >= 2 and $value ne "dhcp"); if ($value ne "dhcp" ) { - if (!xCAT::NetworkUtils->isIpaddr($value)) { + if (!xCAT::NetworkUtils->isIpv4addr($value)) { return ([ 1, "Invalid parameter for option $key: $value" ]); } else { $all_subcommand .= $key . ","; diff --git a/xCAT-server/lib/xcat/plugins/openbmc2.pm b/xCAT-server/lib/xcat/plugins/openbmc2.pm index 6bf4f8e28..0b1ff6183 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc2.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc2.pm @@ -324,12 +324,12 @@ sub parse_args { return([1, "Invalid parameter for option $key: $value"]); } } elsif ($key eq "netmask") { - if (!xCAT::NetworkUtils->isIpaddr($value)) { + if (!xCAT::NetworkUtils->isIpv4addr($value)) { return ([ 1, "Invalid parameter for option $key: $value" ]); } $set_net_info{"netmask"} = 1; } elsif ($key eq "gateway") { - if ($value ne "0.0.0.0" and !xCAT::NetworkUtils->isIpaddr($value)) { + if ($value ne "0.0.0.0" and !xCAT::NetworkUtils->isIpv4addr($value)) { return ([ 1, "Invalid parameter for option $key: $value" ]); } $set_net_info{"gateway"} = 1; @@ -339,7 +339,7 @@ sub parse_args { if ($value ne "dhcp") { if (@$noderange > 1) { return ([ 1, "Can not configure more than 1 nodes' ip at the same time" ]); - } elsif (!xCAT::NetworkUtils->isIpaddr($value)) { + } elsif (!xCAT::NetworkUtils->isIpv4addr($value)) { return ([ 1, "Invalid parameter for option $key: $value" ]); } $set_net_info{"ip"} = 1; From 7928e47ed359bc340d3acd34d831ec800e655d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:09:23 -0300 Subject: [PATCH 02/10] refactor(networkutils): add isIpv6addr helper --- perl-xCAT/xCAT/NetworkUtils.pm | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/perl-xCAT/xCAT/NetworkUtils.pm b/perl-xCAT/xCAT/NetworkUtils.pm index b007789e2..f4b95e30a 100644 --- a/perl-xCAT/xCAT/NetworkUtils.pm +++ b/perl-xCAT/xCAT/NetworkUtils.pm @@ -2197,6 +2197,61 @@ sub isIpaddr return isIpv4addr(@_); } +#------------------------------------------------------------------------------- + +=head3 isIpv6addr + + returns 1 if the value is a valid IPv6 address. + + Arguments: + IPv6 address string + Returns: + 1 - valid IPv6 address + 0 - not a valid IPv6 address + Globals: + none + Error: + none + Example: + if (xCAT::NetworkUtils->isIpv6addr($ip)) { blah; } + Comments: + Zone identifiers such as fe80::1%eth0 are not accepted. + +=cut + +#------------------------------------------------------------------------------- +sub isIpv6addr +{ + my $value = shift; + if (($value) && ($value =~ /xCAT::NetworkUtils/)) + { + $value = shift; + } + + unless (defined($value) and length($value)) + { + return 0; + } + + # inet_pton handling of zone identifiers is platform-dependent + if ($value =~ /%/) + { + return 0; + } + + my $packed_address; + if (defined &Socket::inet_pton) + { + $packed_address = eval { Socket::inet_pton(Socket::AF_INET6(), $value) }; + } + elsif (defined &Socket6::inet_pton) + { + # Perl 5.8 core Socket has no inet_pton; Socket6 provides it + $packed_address = eval { Socket6::inet_pton(Socket6::AF_INET6(), $value) }; + } + return defined($packed_address) ? 1 : 0; +} + From f8ea7c33d24ad2a9a49f236c8a3a301de04ee9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:09:23 -0300 Subject: [PATCH 03/10] refactor(networkutils): add shared isValidIp helper --- perl-xCAT/xCAT/NetworkUtils.pm | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/perl-xCAT/xCAT/NetworkUtils.pm b/perl-xCAT/xCAT/NetworkUtils.pm index f4b95e30a..a04bf8d95 100644 --- a/perl-xCAT/xCAT/NetworkUtils.pm +++ b/perl-xCAT/xCAT/NetworkUtils.pm @@ -2252,6 +2252,40 @@ sub isIpv6addr return defined($packed_address) ? 1 : 0; } +#------------------------------------------------------------------------------- + +=head3 isValidIp + + returns 1 if the value is a valid IPv4 or IPv6 address. + + Arguments: + IP address string + Returns: + 1 - valid IP address + 0 - not a valid IP address + Globals: + none + Error: + none + Example: + if (xCAT::NetworkUtils->isValidIp($ip)) { blah; } + Comments: + IPv4 values follow the isIpv4addr rules. + +=cut + +#------------------------------------------------------------------------------- +sub isValidIp +{ + my $value = shift; + if (($value) && ($value =~ /xCAT::NetworkUtils/)) + { + $value = shift; + } + + return (isIpv4addr($value) or isIpv6addr($value)) ? 1 : 0; +} + From 015c0a14cc609d1fb86975bcdbd0d0571ddf9bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:10:17 -0300 Subject: [PATCH 04/10] fix(instutils): group nodes only under usable server addresses --- perl-xCAT/xCAT/InstUtils.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perl-xCAT/xCAT/InstUtils.pm b/perl-xCAT/xCAT/InstUtils.pm index ce5a8a112..488558c53 100644 --- a/perl-xCAT/xCAT/InstUtils.pm +++ b/perl-xCAT/xCAT/InstUtils.pm @@ -771,9 +771,9 @@ sub get_server_nodes else{ next; } - chomp $serv; + chomp $serv if $serv; - if (xCAT::NetworkUtils->validate_ip($serv)) { + if (xCAT::NetworkUtils->isValidIp($serv)) { push(@{ $servernodes{$serv} }, $node); } } From b6718793939eea1f0681ecfaa6f564187b04605c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:10:17 -0300 Subject: [PATCH 05/10] fix(anaconda): resolve image and master addresses only for hostnames --- xCAT-server/lib/xcat/plugins/anaconda.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xCAT-server/lib/xcat/plugins/anaconda.pm b/xCAT-server/lib/xcat/plugins/anaconda.pm index 9107f26ed..08f24c2d0 100644 --- a/xCAT-server/lib/xcat/plugins/anaconda.pm +++ b/xCAT-server/lib/xcat/plugins/anaconda.pm @@ -680,7 +680,7 @@ sub mknetboot } my $imgsrvip; - unless($imgsrv eq '!myipfn!' or xCAT::NetworkUtils->validate_ip($imgsrv)==0){ + unless ($imgsrv eq '!myipfn!' or xCAT::NetworkUtils->isValidIp($imgsrv)) { # if imgsrv is hostname, convert it to ip address # the host name might not be resolved inside initrd $imgsrvip = xCAT::NetworkUtils->getipaddr($imgsrv); @@ -690,7 +690,7 @@ sub mknetboot } my $xcatmasterip; - if (xCAT::NetworkUtils->validate_ip($xcatmaster)) { + unless (xCAT::NetworkUtils->isValidIp($xcatmaster)) { # if xcatmaster is hostname, convert it to ip address # the host name might not be resolved inside initrd $xcatmasterip = xCAT::NetworkUtils->getipaddr($xcatmaster); From 28e019aa5c5ecb31ad54b48cfe848d415cae1584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:38:26 -0300 Subject: [PATCH 06/10] fix(ppcmac): validate network addresses with isValidIp validate_ip accepted any all-zero address. Only the gateway may be all zeros, which lpar_netboot uses when no router is needed. --- perl-xCAT/xCAT/PPCmac.pm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/perl-xCAT/xCAT/PPCmac.pm b/perl-xCAT/xCAT/PPCmac.pm index 75b11ee0f..6ceb50a4e 100644 --- a/perl-xCAT/xCAT/PPCmac.pm +++ b/perl-xCAT/xCAT/PPCmac.pm @@ -114,8 +114,7 @@ sub parse_args { } else { $server = xCAT::ServiceNodeUtils->getSNformattedhash($node, "xcat", "node", "primary"); foreach my $key (keys %$server) { - my $valid_ip = xCAT::NetworkUtils->validate_ip($key); - if ($valid_ip) { + unless (xCAT::NetworkUtils->isValidIp($key)) { ################################################### # Service node is returned as hostname, Convert # hostname to IP @@ -224,9 +223,13 @@ sub parse_args { if (scalar(@network) != 3) { return (usage()); } - my $result = xCAT::NetworkUtils->validate_ip($opt{C}, $opt{G}, $opt{S}); - if (@$result[0]) { - return (usage(@$result[1])); + foreach my $key (qw(C G S)) { + my $ip = $opt{$key}; + + # lpar_netboot accepts an all-zero gateway when no router is needed + next if $key eq 'G' and $ip eq '0.0.0.0'; + next if xCAT::NetworkUtils->isValidIp($ip); + return (usage("Invalid IP address: $ip")); } } } elsif ((exists($opt{S}) || exists($opt{G}) || exists($opt{C})) && !exists($opt{D})) { From 9a26f64c49a082df7f133cfaa83180176b6ecc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:40:32 -0300 Subject: [PATCH 07/10] test(ppcmac): cover ping address validation --- xCAT-test/unit/ppcmac_ping_addresses.t | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 xCAT-test/unit/ppcmac_ping_addresses.t diff --git a/xCAT-test/unit/ppcmac_ping_addresses.t b/xCAT-test/unit/ppcmac_ping_addresses.t new file mode 100755 index 000000000..662907d12 --- /dev/null +++ b/xCAT-test/unit/ppcmac_ping_addresses.t @@ -0,0 +1,85 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Test::More; + +my $repo_root = $ENV{XCAT_REPO_ROOT} + || File::Spec->catdir( $FindBin::Bin, '..', '..' ); +unshift @INC, File::Spec->catdir( $repo_root, 'perl-xCAT' ); + +# parse_args never opens the database, so keep the table layer and the +# driver out of the test +BEGIN { + package xCAT::Table; + sub import { } + sub new { return bless {}, shift; } + $INC{'xCAT/Table.pm'} = 1; + $INC{'DBI.pm'} = 1; +} + +require xCAT::PPCmac; + +# xcatd loads the usage table before it dispatches a plugin +require xCAT::Usage; + +# both endpoints resolve through the networks table; one subnet is enough +# to reach the address checks +no warnings qw(once redefine); +local *xCAT::DBobjUtils::getNetwkInfo = sub { + my ( $class, $endpoints ) = @_; + return map { $_ => { net => '192.0.2.0', gateway => '192.0.2.1' } } + @{$endpoints}; +}; + +sub parse { + my (@args) = @_; + my $request = { command => 'getmacs', node => ['lpar1'], arg => \@args }; + return xCAT::PPCmac::parse_args($request); +} + +sub rejection { + my ($result) = @_; + return ref($result) eq 'ARRAY' ? $result->[0] : undef; +} + +subtest 'valid addresses reach the ping options' => sub { + my $result = parse(qw(-D -S 192.0.2.10 -G 192.0.2.1 -C 192.0.2.20)); + is( ref $result, 'HASH', 'IPv4 endpoints are accepted' ); + is_deeply( + [ @{$result}{qw(C G S)} ], + [qw(192.0.2.20 192.0.2.1 192.0.2.10)], + 'the addresses are kept as given' + ); + $result = parse(qw(-D -S 2001:db8::10 -G 2001:db8::1 -C 2001:db8::20)); + is( ref $result, 'HASH', 'IPv6 endpoints are accepted' ); +}; + +subtest 'only the gateway may be all zeros' => sub { + my $result = parse(qw(-D -S 192.0.2.10 -G 0.0.0.0 -C 192.0.2.20)); + is( ref $result, 'HASH', 'an all-zero gateway is accepted' ); + is( $result->{G}, '0.0.0.0', 'the gateway is passed through' ); + + is( rejection( parse(qw(-D -S 0.0.0.0 -G 192.0.2.1 -C 192.0.2.20)) ), + 'Invalid IP address: 0.0.0.0', 'an all-zero server is rejected' ); + is( rejection( parse(qw(-D -S 0.0.0.0 -G 0.0.0.0 -C 192.0.2.20)) ), + 'Invalid IP address: 0.0.0.0', + 'an all-zero server is rejected even when the gateway is all zeros' ); + is( rejection( parse(qw(-D -S 192.0.2.10 -G 0.0.0.0 -C 0.0.0.0)) ), + 'Invalid IP address: 0.0.0.0', + 'an all-zero client is rejected even when the gateway is all zeros' ); +}; + +subtest 'malformed addresses are rejected' => sub { + is( rejection( parse(qw(-D -S 192.0.2.10 -G 192.0.2.1 -C 192.168.001.010)) ), + 'Invalid IP address: 192.168.001.010', + 'leading-zero octets are rejected' ); + is( rejection( parse(qw(-D -S 192.0.2.10 -G 192.0.2.1 -C 999.1.1.1)) ), + 'Invalid IP address: 999.1.1.1', 'an octet above 255 is rejected' ); + is( rejection( parse(qw(-D -S not:an:ip -G 192.0.2.1 -C 192.0.2.20)) ), + 'Invalid IP address: not:an:ip', 'colon text is not an address' ); +}; + +done_testing(); From 63572c5ca18625456f3ba204c0b90eba5385813c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:10:18 -0300 Subject: [PATCH 08/10] fix(profilednodes): validate addresses with isValidIp --- xCAT-server/lib/xcat/plugins/profilednodes.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xCAT-server/lib/xcat/plugins/profilednodes.pm b/xCAT-server/lib/xcat/plugins/profilednodes.pm index 38c49c51d..1f4a6d159 100644 --- a/xCAT-server/lib/xcat/plugins/profilednodes.pm +++ b/xCAT-server/lib/xcat/plugins/profilednodes.pm @@ -1354,7 +1354,7 @@ Usage: if (exists $allips{ $args_dict{'ip'} }) { setrsp_errormsg("The specified IP address $args_dict{'ip'} already exists in the IP address database. You must use a different IP address."); return; - } elsif ((xCAT::NetworkUtils->validate_ip($args_dict{'ip'}))[0]->[0]) { + } elsif (!xCAT::NetworkUtils->isValidIp($args_dict{'ip'})) { setrsp_errormsg("The specified IP address $args_dict{'ip'} is invalid. You must use a valid IP address."); return; } @@ -2597,7 +2597,7 @@ sub validate_node_entry { } elsif ($_ eq "ip") { if (exists $allips{ $node_entry{$_} }) { $errmsg .= "IP address $node_entry{$_} already exists in the database or in the node information file.\n"; - } elsif ((xCAT::NetworkUtils->validate_ip($node_entry{$_}))[0]->[0]) { + } elsif (!xCAT::NetworkUtils->isValidIp($node_entry{$_})) { $errmsg .= "IP address $node_entry{$_} is invalid. You must use a valid IP address.\n"; } else { @@ -2747,7 +2747,7 @@ sub validate_node_entry { $nic_ip = $nic_and_ips[1]; if (exists $allips{$nic_ip}) { $errmsg .= "IP address $nic_ip already exists in the database or in the node information file.\n"; - } elsif ((xCAT::NetworkUtils->validate_ip($nic_ip))[0]->[0]) { + } elsif (!xCAT::NetworkUtils->isValidIp($nic_ip)) { $errmsg .= "IP address $nic_ip is invalid. You must use a valid IP address.\n"; } else { From 728a8ab3f49c9b881ee3dc10073e0f38cdb55526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:18:28 -0300 Subject: [PATCH 09/10] refactor(networkutils): remove the legacy validate_ip helper Every in-tree caller now uses isValidIp. The old helper accepted any value with a colon as a valid address, so there is no reason to keep it as a wrapper. --- perl-xCAT/xCAT/NetworkUtils.pm | 47 ---------------------------------- perl-xCAT/xCAT/Utils.pm | 1 - 2 files changed, 48 deletions(-) diff --git a/perl-xCAT/xCAT/NetworkUtils.pm b/perl-xCAT/xCAT/NetworkUtils.pm index a04bf8d95..f8b363ca0 100644 --- a/perl-xCAT/xCAT/NetworkUtils.pm +++ b/perl-xCAT/xCAT/NetworkUtils.pm @@ -2085,53 +2085,6 @@ sub toIP #------------------------------------------------------------------------------- -=head3 validate_ip - Validate list of IPs - Arguments: - List of IPs - Returns: - 1 - Invalid IP address in the list - 0 - IP addresses are all valid - Globals: - none - Error: - none - Example: - if (xCAT::NetworkUtils->validate_ip($IP)) {} - Comments: - none -=cut - -#------------------------------------------------------------------------------- -sub validate_ip -{ - my ($class, @IPs) = @_; - foreach (@IPs) { - my $ip = $_; - - #TODO need more check for IPv6 address - if ($ip =~ /:/) - { - return ([0]); - } - ################################### - # Length is 4 for IPv4 addresses - ################################### - my (@octets) = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - if (scalar(@octets) != 4) { - return ([ 1, "Invalid IP address1: $ip" ]); - } - foreach my $octet (@octets) { - if (($octet < 0) or ($octet > 255)) { - return ([ 1, "Invalid IP address2: $ip" ]); - } - } - } - return ([0]); -} - -#------------------------------------------------------------------------------- - =head3 isIpv4addr returns 1 if parameter is has a valid IP address form. diff --git a/perl-xCAT/xCAT/Utils.pm b/perl-xCAT/xCAT/Utils.pm index 65c253a7d..95ea03b39 100644 --- a/perl-xCAT/xCAT/Utils.pm +++ b/perl-xCAT/xCAT/Utils.pm @@ -104,7 +104,6 @@ our @EXPORT_OK = qw(genpassword runcmd3 natural_sort_cmp); # xCAT::Utils::get_subnet_aix ====> xCAT::NetworkUtils::get_subnet_aix # xCAT::Utils->determinehostname ====> xCAT::NetworkUtils->determinehostname # xCAT::Utils::toIP ====> xCAT::NetworkUtils::toIP -# xCAT::Utils->validate_ip ====> xCAT::NetworkUtils->validate_ip # xCAT::Utils->getFacingIP ====> xCAT::NetworkUtils->getFacingIP # xCAT::Utils->isIpaddr ====> xCAT::NetworkUtils->isIpaddr # xCAT::Utils::getNodeNetworkCfg ====> xCAT::NetworkUtils::getNodeNetworkCfg From 19b9c5b46b46dde9ae82a00b816cee7b05d7a21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:10:58 -0300 Subject: [PATCH 10/10] test(networkutils): cover shared IP address validation --- xCAT-test/unit/networkutils_ip_validation.t | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 xCAT-test/unit/networkutils_ip_validation.t diff --git a/xCAT-test/unit/networkutils_ip_validation.t b/xCAT-test/unit/networkutils_ip_validation.t new file mode 100755 index 000000000..66c117d10 --- /dev/null +++ b/xCAT-test/unit/networkutils_ip_validation.t @@ -0,0 +1,112 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Test::More; + +my $repo_root = $ENV{XCAT_REPO_ROOT} + || File::Spec->catdir( $FindBin::Bin, '..', '..' ); +unshift @INC, File::Spec->catdir( $repo_root, 'perl-xCAT' ); +require xCAT::NetworkUtils; + +subtest 'isIpv4addr accepts host addresses' => sub { + my @accepted = qw(10.0.0.1 192.168.0.1 172.16.254.10 255.255.255.255); + foreach my $address (@accepted) { + ok( xCAT::NetworkUtils->isIpv4addr($address), "$address is accepted" ); + } + ok( xCAT::NetworkUtils::isIpv4addr('10.0.0.1'), + 'the function form works without a class argument' ); + ok( xCAT::NetworkUtils->isIpaddr('10.0.0.1'), + 'the deprecated isIpaddr alias still answers' ); + ok( !xCAT::NetworkUtils->isIpaddr('fe80::1'), + 'the alias keeps the strict IPv4 contract' ); +}; + +subtest 'isIpv4addr rejects invalid or ambiguous forms' => sub { + my @rejected = ( + [ '192.168.001.010', 'leading-zero octets are octal to some resolvers' ], + [ '0.1.2.3', 'a host address cannot start with zero' ], + [ '0.0.0.0', 'all-zero is a call-site policy, not a host address' ], + [ '256.1.1.1', 'octet above 255' ], + [ '1.2.3', 'three-part form' ], + [ '1.2.3.4.5', 'five-part form' ], + [ 'fe80::1', 'IPv6 is outside the IPv4 contract' ], + [ 'bmc.example.test', 'hostnames are not addresses' ], + [ '', 'empty value' ], + ); + foreach my $case (@rejected) { + my ( $value, $reason ) = @{$case}; + ok( !xCAT::NetworkUtils->isIpv4addr($value), "'$value' is rejected: $reason" ); + } + ok( !xCAT::NetworkUtils->isIpv4addr(undef), 'undef is rejected' ); +}; + +subtest 'isIpv6addr validates IPv6 syntax' => sub { + my @accepted = ( 'fe80::1', '::1', '::', '2001:db8::1', '::ffff:10.1.1.1' ); + foreach my $address (@accepted) { + ok( xCAT::NetworkUtils->isIpv6addr($address), "$address is accepted" ); + } + my @rejected = ( + [ 'not:an:ip', 'colon text is not an address' ], + [ ':::::', 'malformed compression' ], + [ 'fe80::zzzz', 'invalid hex digits' ], + [ 'fe80::1%eth0', 'zone identifiers are platform-dependent and rejected' ], + [ '10.0.0.1', 'IPv4 is outside the IPv6 contract' ], + [ '', 'empty value' ], + ); + foreach my $case (@rejected) { + my ( $value, $reason ) = @{$case}; + ok( !xCAT::NetworkUtils->isIpv6addr($value), "'$value' is rejected: $reason" ); + } + ok( !xCAT::NetworkUtils->isIpv6addr(undef), 'undef is rejected' ); + ok( xCAT::NetworkUtils::isIpv6addr('::1'), + 'the function form works without a class argument' ); +}; + +sub socket6_fallback_ok { + ok( !defined &Socket::inet_pton, 'core inet_pton is absent' ); + ok( xCAT::NetworkUtils->isIpv6addr('2001:db8::1'), + 'a valid IPv6 address is accepted through Socket6' ); + ok( !xCAT::NetworkUtils->isIpv6addr('fe80::zzzz'), + 'an invalid IPv6 address is still rejected through Socket6' ); + return; +} + +subtest 'isIpv6addr falls back to Socket6 without core inet_pton' => sub { + no warnings qw(once redefine); + if ( defined &Socket6::inet_pton ) { + local *Socket::inet_pton; + socket6_fallback_ok(); + return; + } + plan skip_all => 'neither core inet_pton nor Socket6 is available' + unless defined &Socket::inet_pton; + + # lend the core implementation to Socket6 so the fallback path runs + # where Socket6 is not installed + my $core_pton = \&Socket::inet_pton; + my $af6 = Socket::AF_INET6(); + local *Socket6::inet_pton = sub { return $core_pton->(@_) }; + local *Socket6::AF_INET6 = sub { return $af6 }; + local *Socket::inet_pton; + socket6_fallback_ok(); +}; + +subtest 'isValidIp accepts both families and nothing else' => sub { + ok( xCAT::NetworkUtils->isValidIp('10.0.0.1'), 'IPv4 delegates to isIpv4addr' ); + ok( xCAT::NetworkUtils->isValidIp('fe80::1'), 'IPv6 delegates to isIpv6addr' ); + my @rejected = ( + 'not:an:ip', 'fe80::1%eth0', '192.168.001.010', '0.0.0.0', + 'bmc.example.test', '', + ); + foreach my $value (@rejected) { + ok( !xCAT::NetworkUtils->isValidIp($value), "'$value' is rejected" ); + } + ok( !xCAT::NetworkUtils->isValidIp(undef), 'undef is rejected' ); + ok( xCAT::NetworkUtils::isValidIp('10.0.0.1'), + 'the function form works without a class argument' ); +}; + +done_testing();