# frozen_string_literal: true require 'ipaddr' module Facter module Resolvers class Networking < BaseResolver @log = Facter::Log.new(self) @semaphore = Mutex.new @fact_list ||= {} class << self private def post_resolve(fact_name) @fact_list.fetch(fact_name) { read_network_information(fact_name) } end def read_network_information(fact_name) require "#{ROOT_DIR}/lib/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)) iterate_list(adapter_addresses) set_interfaces_other_facts if @fact_list[:interfaces] @fact_list[fact_name] 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 @fact_list[:domain] ||= adapter_address[:DnsSuffix].read_wide_string_without_length name = adapter_address[:FriendlyName].read_wide_string_without_length.to_sym 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 result[:network].ipv6? bindings[:ipv4] << result if result[:network].ipv4? end def find_bindings(sock_addr, unicast, addr) return unless [NetworkingFFI::AF_INET, NetworkingFFI::AF_INET6].include?(sock_addr[:sa_family]) NetworkUtils.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]) && !NetworkUtils.ignored_ip_address(addr)) @fact_list[:primary] = name end end def set_interfaces_other_facts @fact_list[:interfaces].each do |interface_name, value| if value[:bindings] binding = find_valid_binding(value[:bindings]) populate_interface(binding, value) end if value[:bindings6] binding = find_valid_binding(value[:bindings6]) populate_interface(binding, value) end set_networking_other_facts(value, interface_name) end end def find_valid_binding(bindings) bindings.each do |binding| return binding unless NetworkUtils.ignored_ip_address(binding[:address]) end bindings.empty? ? nil : bindings.first end def populate_interface(bind, interface) return if !bind || bind.empty? if bind[:network].ipv6? interface[:ip6] = bind[:address] interface[:netmask6] = bind[:netmask] interface[:network6] = bind[:network] interface[:scope6] = NetworkUtils.get_scope(bind[:address]) else interface[:network] = bind[:network] interface[:netmask] = bind[:netmask] interface[:ip] = bind[:address] end end def set_networking_other_facts(value, interface_name) return unless @fact_list[:primary] == interface_name %i[mtu dhcp mac ip ip6 scope6 netmask netmask6 network network6].each do |key| @fact_list[key] = value[key] end end end end end end