lib/facter/resolvers/windows/networking.rb
# frozen_string_literal: true
module Facter
module Resolvers
module Windows
class Networking < BaseResolver
@log = Facter::Log.new(self)
init_resolver
class << self
private
def post_resolve(fact_name, _options)
@fact_list.fetch(fact_name) { read_network_information(fact_name) }
end
def read_network_information(fact_name)
require_relative '../../../facter/resolvers/windows/ffi/networking_ffi'
size_ptr = FFI::MemoryPointer.new(NetworkingFFI::BUFFER_LENGTH)
adapter_addresses = FFI::MemoryPointer.new(IpAdapterAddressesLh.size, NetworkingFFI::BUFFER_LENGTH)
flags = NetworkingFFI::GAA_FLAG_SKIP_ANYCAST |
NetworkingFFI::GAA_FLAG_SKIP_MULTICAST | NetworkingFFI::GAA_FLAG_SKIP_DNS_SERVER
return unless (adapter_addresses = get_adapter_addresses(size_ptr, adapter_addresses, flags))
retrieve_domain_from_registry
iterate_list(adapter_addresses)
Facter::Util::Resolvers::Networking.expand_main_bindings(@fact_list)
@fact_list[fact_name]
rescue LoadError => e
@log.debug("The ffi gem has not been installed: #{e}")
end
def get_adapter_addresses(size_ptr, adapter_addresses, flags)
error = nil
3.times do
error = NetworkingFFI::GetAdaptersAddresses(NetworkingFFI::AF_UNSPEC, flags,
FFI::Pointer::NULL, adapter_addresses, size_ptr)
break if error == NetworkingFFI::ERROR_SUCCES
if error == NetworkingFFI::ERROR_BUFFER_OVERFLOW
adapter_addresses = FFI::MemoryPointer.new(IpAdapterAddressesLh.size, NetworkingFFI::BUFFER_LENGTH)
else
@log.debug 'Unable to retrieve networking facts!'
return nil
end
end
return nil unless error.zero?
adapter_addresses
end
def adapter_down?(adapter)
adapter[:OperStatus] != NetworkingFFI::IF_OPER_STATUS_UP ||
![NetworkingFFI::IF_TYPE_ETHERNET_CSMACD, NetworkingFFI::IF_TYPE_IEEE80211].include?(adapter[:IfType])
end
def retrieve_dhcp_server(adapter)
if !(adapter[:Flags] & NetworkingFFI::IP_ADAPTER_DHCP_ENABLED).zero? &&
adapter[:Union][:Struct][:Length] >= IpAdapterAddressesLh.size
NetworkUtils.address_to_string(adapter[:Dhcpv4Server])
end
end
def iterate_list(adapter_addresses)
net_interface = {}
IpAdapterAddressesLh.read_list(adapter_addresses) do |adapter_address|
if adapter_down?(adapter_address)
adapter_address = IpAdapterAddressesLh.new(adapter_address[:Next])
next
end
if !@fact_list[:domain] || @fact_list[:domain].empty?
@fact_list[:domain] = adapter_address[:DnsSuffix]
.read_wide_string_without_length(replace_invalid_chars: true)
end
name = adapter_address[:FriendlyName].read_wide_string_without_length(replace_invalid_chars: true)
net_interface[name] = build_interface_info(adapter_address, name)
end
@fact_list[:interfaces] = net_interface unless net_interface.empty?
end
def build_interface_info(adapter_address, name)
hash = {}
hash[:dhcp] = retrieve_dhcp_server(adapter_address)
hash[:mtu] = adapter_address[:Mtu]
bindings = find_ip_addresses(adapter_address[:FirstUnicastAddress], name)
hash[:bindings] = bindings[:ipv4] unless bindings[:ipv4].empty?
hash[:bindings6] = bindings[:ipv6] unless bindings[:ipv6].empty?
hash[:mac] = NetworkUtils.find_mac_address(adapter_address)
hash
end
def find_ip_addresses(unicast_addresses, name)
bindings = {}
bindings[:ipv6] = []
bindings[:ipv4] = []
IpAdapterUnicastAddressLH.read_list(unicast_addresses) do |unicast|
addr = NetworkUtils.address_to_string(unicast[:Address])
unless addr
unicast = IpAdapterUnicastAddressLH.new(unicast[:Next])
next
end
sock_addr = SockAddr.new(unicast[:Address][:lpSockaddr])
add_ip_data(addr, unicast, sock_addr, bindings)
find_primary_interface(sock_addr, name, addr)
end
bindings
end
def add_ip_data(addr, unicast, sock_addr, bindings)
result = find_bindings(sock_addr, unicast, addr)
return unless result
bindings[:ipv6] << result if sock_addr[:sa_family] == NetworkingFFI::AF_INET6
bindings[:ipv4] << result if sock_addr[:sa_family] == NetworkingFFI::AF_INET
end
def find_bindings(sock_addr, unicast, addr)
return unless [NetworkingFFI::AF_INET, NetworkingFFI::AF_INET6].include?(sock_addr[:sa_family])
Facter::Util::Resolvers::Networking.build_binding(addr, unicast[:OnLinkPrefixLength])
end
def find_primary_interface(sock_addr, name, addr)
if !@fact_list[:primary_interface] &&
([NetworkingFFI::AF_INET, NetworkingFFI::AF_INET6].include?(sock_addr[:sa_family]) &&
!::Facter::Util::Resolvers::Networking.ignored_ip_address(addr))
@fact_list[:primary_interface] = name.to_s
end
end
def retrieve_domain_from_registry
::Win32::Registry::HKEY_LOCAL_MACHINE.open(
'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters'
) do |key|
domain = key['Domain']
@fact_list[:domain] = domain if domain
end
rescue Win32::Registry::Error
@log.debug('Could not read TCPIP Parameters from registry')
nil
end
end
end
end
end
end