diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index c0874ce9f..0d579aa57 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -154,6 +154,24 @@ sub ipIsDynamic { return 0; #it isn't in any of the dynamic ranges we are aware of } +sub _reject_dynamic_node_ip +{ + my ( $node, $ip, $family, $cb ) = @_; + + return 0 unless ipIsDynamic($ip); + + my $address = $family eq 'IPv6' ? 'IPv6 address' : 'IP'; + $cb->( + { + error => [ + "Node $node has $address $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table." + ], + errorcode => [1] + } + ); + return 1; +} + sub handled_commands { return { makedhcp => "dhcp", }; @@ -588,8 +606,7 @@ sub addnode6 { $uuid =~ s/:\z//; $uuid =~ s/^/00:04:/; my $ip = getipaddr($node); - if ($ip and $ip =~ /:/ and ipIsDynamic($ip)) { - $callback->({ error => ["Node $node has IPv6 address $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] }); + if ( $ip and $ip =~ /:/ and _reject_dynamic_node_ip( $node, $ip, 'IPv6', $callback ) ) { return; } if ($ip and $ip =~ /:/) { @@ -811,16 +828,8 @@ sub addnode ); } - if ($ip and $ip ne 'DENIED' and ipIsDynamic($ip)) + if ( $ip and $ip ne 'DENIED' and _reject_dynamic_node_ip( $node, $ip, 'IPv4', $callback ) ) { - $callback->( - { - error => [ - "Node $node has IP $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table." - ], - errorcode => [1] - } - ); next; } my $doiscsi = 0; @@ -3197,8 +3206,7 @@ sub kea_node_reservations next; } - if (ipIsDynamic($ip)) { - $callback->({ error => ["Node $node has IP $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] }); + if ( _reject_dynamic_node_ip( $node, $ip, 'IPv4', $callback ) ) { next; } @@ -3353,8 +3361,7 @@ sub kea_node_reservations6 my $ip = getipaddr($hname, OnlyV6 => 1); next unless $ip; - if (ipIsDynamic($ip)) { - $callback->({ error => ["Node $node has IPv6 address $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] }); + if ( _reject_dynamic_node_ip( $node, $ip, 'IPv6', $callback ) ) { next; } diff --git a/xCAT-test/unit/dhcp_kea_plugin_intent.t b/xCAT-test/unit/dhcp_kea_plugin_intent.t index 844df362e..839de3bec 100644 --- a/xCAT-test/unit/dhcp_kea_plugin_intent.t +++ b/xCAT-test/unit/dhcp_kea_plugin_intent.t @@ -883,4 +883,164 @@ foreach my $case (@invalid_mac_cases) { ok( !grep( { ( $_->{hostname} || '' ) eq '*NOIP*' } @{ $res6 || [] } ), 'no IPv6 reservation carries the *NOIP* sentinel as a hostname' ); } +{ + my %range_tables = ( + noderes => DHCPKeaResTable->new( { range01 => {} } ), + chain => DHCPKeaResTable->new( { range01 => {} } ), + nodetype => DHCPKeaResTable->new( { range01 => {} } ), + iscsi => DHCPKeaResTable->new( {} ), + vpd => DHCPKeaResTable->new( {} ), + mac => DHCPKeaResTable->new( + { + range01 => { + mac => '00:11:22:33:44:55!dynamic-host|00:11:22:33:44:66!static-host', + }, + } + ), + ); + + no warnings 'redefine'; + local *xCAT::Table::new = sub { + my ( $class, $name ) = @_; + return $range_tables{$name}; + }; + my $use_ipv4_v6_fallback = 0; + local *xCAT_plugin::dhcp::getipaddr = sub { + my ( $host, %opt ) = @_; + if ( $opt{OnlyV6} ) { + return '192.0.2.150' if $use_ipv4_v6_fallback && $host eq 'dynamic-host'; + return $host eq 'dynamic-host' ? '2001:db8::150' : '2001:db8::25'; + } + return $host eq 'dynamic-host' ? '192.0.2.150' : '192.0.2.25'; + }; + local *xCAT_plugin::dhcp::ipIsDynamic = sub { + my ($ip) = @_; + return $ip eq '192.0.2.150' || $ip eq '2001:db8::150'; + }; + local *xCAT_plugin::dhcp::kea_next_server_for_node = sub { return; }; + local *xCAT_plugin::dhcp::kea_boot_for_node = sub { return {}; }; + local *xCAT::MsgUtils::message = sub { return; }; + local *xCAT::MsgUtils::trace = sub { return; }; + + my @errors; + my $capture_response = sub { + my $response = shift; + push @errors, @{ $response->{error} || [] }; + }; + my $saved_umask = umask; + my $saved_ignorecase = $Getopt::Long::ignorecase; + { + local @ARGV; + xCAT_plugin::dhcp::process_request( + { + _xcatpreprocessed => [0], + arg => [ '-q', '-a' ], + }, + $capture_response + ); + } + umask $saved_umask; + $Getopt::Long::ignorecase = $saved_ignorecase; + Getopt::Long::Configure('pass_through'); + @errors = (); + + my $backend = bless {}, 'DHCPKeaResBackend'; + my $reservations4 = xCAT_plugin::dhcp::kea_build_node_reservations( $backend, {}, ['range01'] ); + is_deeply( + [ map { $_->{'ip-address'} } @$reservations4 ], + ['192.0.2.25'], + 'Kea IPv4 omits a reservation whose address is in a dynamic range' + ); + is_deeply( + \@errors, + [ + 'Node range01 has IP 192.0.2.150 which is inside the DHCP dynamic range. ' + . 'Move the node IP outside the dynamic range or adjust the range in the networks table.' + ], + 'Kea IPv4 reports the dynamic-range conflict and continues with later interfaces' + ); + + @errors = (); + my $reservations6 = xCAT_plugin::dhcp::kea_build_node_reservations6( $backend, {}, ['range01'] ); + is_deeply( + [ map { $_->{'ip-addresses'}->[0] } @$reservations6 ], + ['2001:db8::25'], + 'Kea IPv6 omits a reservation whose address is in a dynamic range' + ); + is_deeply( + \@errors, + [ + 'Node range01 has IPv6 address 2001:db8::150 which is inside the DHCP dynamic range. ' + . 'Move the node IP outside the dynamic range or adjust the range in the networks table.' + ], + 'Kea IPv6 reports the dynamic-range conflict and continues with later interfaces' + ); + + $use_ipv4_v6_fallback = 1; + @errors = (); + $reservations6 = xCAT_plugin::dhcp::kea_build_node_reservations6( $backend, {}, ['range01'] ); + is_deeply( + [ map { $_->{'ip-addresses'}->[0] } @$reservations6 ], + ['2001:db8::25'], + 'Kea IPv6 still omits a dynamic IPv4 fallback result' + ); + is_deeply( + \@errors, + [ + 'Node range01 has IPv6 address 192.0.2.150 which is inside the DHCP dynamic range. ' + . 'Move the node IP outside the dynamic range or adjust the range in the networks table.' + ], + 'Kea IPv6 preserves dynamic-range checking when OnlyV6 falls back to IPv4' + ); +} + +{ + no warnings 'redefine'; + my ( @checked, @responses ); + local *xCAT_plugin::dhcp::ipIsDynamic = sub { + my ($ip) = @_; + push @checked, $ip; + return $ip eq '192.0.2.150' || $ip eq '2001:db8::150'; + }; + my $capture_response = sub { push @responses, shift; }; + + ok( + !xCAT_plugin::dhcp::_reject_dynamic_node_ip( 'range01', '192.0.2.25', 'IPv4', $capture_response ), + 'an IPv4 address outside the dynamic range is accepted' + ); + ok( + xCAT_plugin::dhcp::_reject_dynamic_node_ip( 'range01', '192.0.2.150', 'IPv4', $capture_response ), + 'an IPv4 address inside the dynamic range is rejected' + ); + ok( + xCAT_plugin::dhcp::_reject_dynamic_node_ip( 'range01', '2001:db8::150', 'IPv6', $capture_response ), + 'an IPv6 address inside the dynamic range is rejected' + ); + is_deeply( + \@checked, + [ '192.0.2.25', '192.0.2.150', '2001:db8::150' ], + 'the shared helper checks each caller-approved address' + ); + is_deeply( + \@responses, + [ + { + error => [ + 'Node range01 has IP 192.0.2.150 which is inside the DHCP dynamic range. ' + . 'Move the node IP outside the dynamic range or adjust the range in the networks table.' + ], + errorcode => [1], + }, + { + error => [ + 'Node range01 has IPv6 address 2001:db8::150 which is inside the DHCP dynamic range. ' + . 'Move the node IP outside the dynamic range or adjust the range in the networks table.' + ], + errorcode => [1], + }, + ], + 'dynamic IPv4 and IPv6 addresses keep the existing callback payloads' + ); +} + done_testing();