From 52ebe09fea340e207cf4e34a04f836a9cba82593 Mon Sep 17 00:00:00 2001 From: Shim Shtein Date: Wed, 27 Nov 2024 06:53:31 -0500 Subject: [PATCH] Fixes #38046 - Make sure IPv6 interface can be primary --- app/services/fact_parser.rb | 29 +++++++++++++++-------------- test/unit/fact_parser_test.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/app/services/fact_parser.rb b/app/services/fact_parser.rb index 1deeeada866..54e070b632b 100644 --- a/app/services/fact_parser.rb +++ b/app/services/fact_parser.rb @@ -78,7 +78,7 @@ def interfaces # tries to detect primary interface among interfaces using host name def suggested_primary_interface(host) # we search among interface with ip and mac if we didn't find it by name - potential = interfaces.select { |_, values| values[:ipaddress].present? && values[:macaddress].present? } + potential = interfaces.select { |_, values| (values[:ipaddress].present? || values[:ipaddress6].present?) && values[:macaddress].present? } find_interface_by_name(host.name) || find_physical_interface(potential) || find_virtual_interface(potential) || potential.first || interfaces.first end @@ -139,20 +139,21 @@ def bios def find_interface_by_name(host_name) resolver = Resolv::DNS.new resolver.timeouts = PRIMARY_INTERFACE_RESOLVE_TIMEOUTS - interfaces.detect do |int, values| - if (ip = values[:ipaddress]).present? - begin - logger.debug { "Resolving fact '#{ip}' via DNS to match reported hostname #{host_name}" } - if resolver.getnames(ip).any? { |name| name.to_s == host_name } - logger.debug { "Match: '#{ip}', interface #{int} is selected as primary" } - return [int, values] - end - rescue Resolv::ResolvError => e - logger.debug { "Could not resolv name for #{ip} because of #{e} #{e.message}" } - nil - end - end + interfaces.find do |int, values| + [values[:ipaddress], values[:ipaddress6]].find { |ip| try_resolve(int, ip, host_name, resolver) } + end + end + + def try_resolve(int, ip, host_name, resolver) + return nil unless ip + logger.debug { "Resolving fact '#{ip}' via DNS to match reported hostname #{host_name}" } + if resolver.getnames(ip).any? { |name| name.to_s == host_name } + logger.debug { "Match: '#{ip}', interface #{int} is selected as primary" } + true end + rescue Resolv::ResolvError => e + logger.debug { "Could not resolv name for #{ip} because of #{e} #{e.message}" } + nil end def find_physical_interface(interfaces) diff --git a/test/unit/fact_parser_test.rb b/test/unit/fact_parser_test.rb index a57246e7680..1804606eab1 100644 --- a/test/unit/fact_parser_test.rb +++ b/test/unit/fact_parser_test.rb @@ -297,6 +297,22 @@ class FactParserTest < ActiveSupport::TestCase assert_equal '00:00:00:00:00:12', found.last[:macaddress] end + test "#suggested_primary_interface detects primary interface using DNS IPv6" do + parser.stubs(:interfaces).returns({ + 'br0' => {'ipaddress6' => 'fe80::10', 'macaddress' => '00:00:00:00:00:10'}, + 'em1' => {'ipaddress6' => 'fe80::20', 'macaddress' => '00:00:00:00:00:20'}, + 'em2' => {'ipaddress6' => 'fe80::30', 'macaddress' => '00:00:00:00:00:30'}, + 'bond0' => {'ipaddress6' => 'fe80::40', 'macaddress' => '00:00:00:00:00:40'}, + }.with_indifferent_access) + + Resolv::DNS.any_instance.stubs(:getnames).returns([]) + Resolv::DNS.any_instance.expects(:getnames).with('fe80::30').returns([host.name]) + found = parser.suggested_primary_interface(host) + assert_equal 'em2', found.first + assert_equal 'fe80::30', found.last[:ipaddress6] + assert_equal '00:00:00:00:00:30', found.last[:macaddress] + end + test "#suggested_primary_interface primary interface detection falls back to physical with ip and mac" do parser.stubs(:interfaces).returns({ 'br0' => {'ipaddress' => '30.0.0.30', 'macaddress' => '00:00:00:00:00:30'}, @@ -313,6 +329,22 @@ class FactParserTest < ActiveSupport::TestCase assert_equal '00:00:00:00:00:10', found.last[:macaddress] end + test "#suggested_primary_interface primary interface detection falls back to physical with ip and mac" do + parser.stubs(:interfaces).returns({ + 'br0' => {'ipaddress6' => 'fe80::10', 'macaddress' => '00:00:00:00:00:30'}, + 'em0' => {'ipaddress6' => '', 'macaddress' => ''}, + 'em1' => {'ipaddress6' => 'fe80::20', 'macaddress' => '00:00:00:00:00:10'}, + 'em2' => {'ipaddress6' => 'fe80::30', 'macaddress' => '00:00:00:00:00:12'}, + 'bond0' => {'ipaddress6' => 'fe80::40', 'macaddress' => '00:00:00:00:00:15'}, + }.with_indifferent_access) + + Resolv::DNS.any_instance.stubs(:getnames).returns([]) + found = parser.suggested_primary_interface(host) + assert_equal 'em1', found.first + assert_equal 'fe80::20', found.last[:ipaddress6] + assert_equal '00:00:00:00:00:10', found.last[:macaddress] + end + test "#suggested_primary_interface primary interface detection falls back to first with ip and mac if no physical" do parser.stubs(:interfaces).returns({ 'bond1' => {'ipaddress' => '', 'macaddress' => ''},