class Facter::Resolvers::NetworkingLinux

Constants

DIRS
ROUTE_TYPES

Private Class Methods

add_binding(interface_data, interface_name, ip, netmask, ip_v4_type) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 153
def add_binding(interface_data, interface_name, ip, netmask, ip_v4_type)
  binding = Facter::Util::Resolvers::Networking.build_binding(ip, netmask)
  return if binding.nil?

  log.debug("Adding to interface #{interface_name}, binding:\n#{binding}")
  binding_key = ip_v4_type == true ? :bindings : :bindings6
  interface_data[binding_key] = [] if interface_data[binding_key].nil?
  interface_data[binding_key] << binding
end
add_binding_if_missing(interface_data, binding_key, route) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 215
def add_binding_if_missing(interface_data, binding_key, route)
  interface_binding = interface_data[binding_key]

  if interface_binding.nil?
    interface_data[binding_key] = [{ address: route[:ip] }]
  elsif interface_binding.none? { |binding| binding[:address] == route[:ip] }
    interface_binding << { address: route[:ip] }
  end
end
add_info_from_routing_table() click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 163
def add_info_from_routing_table
  routes4, routes6 = read_routing_table
  compare_ips(routes4, :bindings)
  compare_ips(routes6, :bindings6)
end
bond_master_of(interface_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 113
def bond_master_of(interface_name)
  content = Facter::Core::Execution.execute("ip link show #{interface_name}", logger: log)
  content.match(/master (\S*) /)&.captures&.first
end
compare_ips(routes, binding_key) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 206
def compare_ips(routes, binding_key)
  routes.each do |route|
    next unless @fact_list[:interfaces].key?(route[:interface])

    interface_data = @fact_list[:interfaces][route[:interface]]
    add_binding_if_missing(interface_data, binding_key, route)
  end
end
construct_route(parts) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 197
def construct_route(parts)
  route = {}
  dev_index = parts.find_index { |elem| elem == 'dev' }
  src_index = parts.find_index { |elem| elem == 'src' }
  route[:interface] = parts[dev_index + 1] if dev_index
  route[:ip] = parts[src_index + 1] if src_index
  route
end
delete_invalid_route_type(parts) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 192
def delete_invalid_route_type(parts)
  route_type = parts[0]
  parts.delete_at(0) if ROUTE_TYPES.include?(route_type)
end
dhcp(interface_name, index_and_mtu, interface_data) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 225
def dhcp(interface_name, index_and_mtu, interface_data)
  log.debug("Get DHCP for interface #{interface_name}")

  search_systemd_netif_leases(interface_data, index_and_mtu)
  search_dhclient_leases(interface_data, interface_name)
  search_internal_leases(interface_data, interface_name)
  search_with_dhcpcd_command(interface_data, interface_name)
end
interfaces_mtu_and_index() click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 37
def interfaces_mtu_and_index
  mtu_and_indexes = {}
  output = Facter::Core::Execution.execute('ip link show', logger: log)
  output.each_line do |line|
    next unless line.include?('mtu')

    parse_ip_command_line(line, mtu_and_indexes)
  end
  log.debug("Associated MTU and index in ip command: #{mtu_and_indexes}")
  mtu_and_indexes
end
ip_info_of(ifaddr) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 142
def ip_info_of(ifaddr)
  return if ifaddr.addr.nil? || ifaddr.netmask.nil?

  # ipv6 ips are retrieved as <ip>%<interface_name>
  ip = ifaddr.addr.ip_address.split('%').first
  netmask = ifaddr.netmask.ip_address
  [ip, netmask, ifaddr.addr.ipv4?]
rescue StandardError => e
  log.debug("Could not read binding data, got #{e}")
end
mac(ifaddr, interfaces) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 81
def mac(ifaddr, interfaces)
  return unless interfaces[ifaddr.name][:mac].nil?

  mac = search_for_mac(ifaddr)
  interfaces[ifaddr.name][:mac] = mac if mac
end
mac_from(ifaddr) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 118
def mac_from(ifaddr)
  if Socket.const_defined? :PF_LINK
    ifaddr.addr&.getnameinfo&.first # sometimes it returns localhost or ip
  elsif Socket.const_defined? :PF_PACKET
    return if ifaddr.addr.nil?

    search_mac_in_sockaddr(ifaddr)
  end
rescue StandardError => e
  log.debug("Could not read mac, got #{e}")
end
mac_from_bonded_interface(interface_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 94
def mac_from_bonded_interface(interface_name)
  master = bond_master_of(interface_name)
  return unless master

  output = Facter::Util::FileHelper.safe_read("/proc/net/bonding/#{master}", nil)
  return unless output

  found_match = false
  output.each_line do |line|
    if line.strip == "Slave Interface: #{interface_name}"
      found_match = true
    elsif line.include? 'Slave Interface'
      # if we reached the data block of another interface belonging to the bonded interface
      found_match = false
    end
    return Regexp.last_match(1) if found_match && line =~ /Permanent HW addr: (\S*)/
  end
end
mtu(ifaddr, interfaces, mtu_and_indexes) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 135
def mtu(ifaddr, interfaces, mtu_and_indexes)
  return unless interfaces[ifaddr.name][:mtu].nil?

  mtu = mtu_and_indexes.dig(ifaddr.name, :mtu)
  interfaces[ifaddr.name][:mtu] = mtu unless mtu.nil?
end
parse_ip_command_line(line, mtu_and_indexes) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 49
def parse_ip_command_line(line, mtu_and_indexes)
  mtu = line.match(/mtu (\d+)/)&.captures&.first&.to_i
  index_tokens = line.split(':')
  index = index_tokens[0].strip
  # vlans are displayed as <vlan_name>@<physical_interface>
  name = index_tokens[1].split('@').first.strip
  mtu_and_indexes[name] = { index: index, mtu: mtu }
end
parse_routes(output, ipv4_type) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 177
def parse_routes(output, ipv4_type)
  routes = []
  output.each_line do |line|
    parts = line.split(' ').compact
    next if parts.include?('linkdown')

    delete_invalid_route_type(parts)
    next if !ipv4_type && !parts[0].include?(':')

    route = construct_route(parts)
    routes << route unless route[:ip].nil?
  end
  routes.uniq
end
populate_interface_info(ifaddr, interfaces, mtu_and_indexes) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 68
def populate_interface_info(ifaddr, interfaces, mtu_and_indexes)
  interface_name = ifaddr.name
  interfaces[interface_name] = {} if interfaces[interface_name].nil?
  interface_data = interfaces[interface_name]

  mac(ifaddr, interfaces)
  mtu(ifaddr, interfaces, mtu_and_indexes)
  ip, netmask, ipv4_type = ip_info_of(ifaddr)
  add_binding(interface_data, interface_name, ip, netmask, ipv4_type)
  dhcp(interface_name, mtu_and_indexes[interface_name], interface_data) if interface_data[:dhcp].nil?
  log.debug("Found interface #{interface_name} with #{interface_data}")
end
post_resolve(fact_name, _options) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 22
def post_resolve(fact_name, _options)
  @fact_list.fetch(fact_name) { retrieve_network_info(fact_name) }

  @fact_list[fact_name]
end
read_routing_table() click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 169
def read_routing_table
  ipv4_output = Facter::Core::Execution.execute('ip route show', logger: log)
  ipv6_output = Facter::Core::Execution.execute('ip -6 route show', logger: log)
  routes4 = parse_routes(ipv4_output, true)
  routes6 = parse_routes(ipv6_output, false)
  [routes4, routes6]
end
retrieve_interfaces_with_socket(mtu_and_indexes) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 58
def retrieve_interfaces_with_socket(mtu_and_indexes)
  require 'socket'
  interfaces = {}
  Socket.getifaddrs.each do |ifaddr|
    populate_interface_info(ifaddr, interfaces, mtu_and_indexes)
  end

  @fact_list[:interfaces] = interfaces
end
retrieve_network_info(fact_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 28
def retrieve_network_info(fact_name)
  mtu_and_indexes = interfaces_mtu_and_index
  retrieve_interfaces_with_socket(mtu_and_indexes)
  add_info_from_routing_table
  retrieve_primary_interface
  Facter::Util::Resolvers::Networking.expand_main_bindings(@fact_list)
  @fact_list[fact_name]
end
retrieve_primary_interface() click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 285
def retrieve_primary_interface
  primary_helper = Facter::Util::Resolvers::Networking::PrimaryInterface
  primary_interface = primary_helper.read_from_proc_route
  primary_interface ||= primary_helper.read_from_ip_route
  primary_interface ||= primary_helper.find_in_interfaces(@fact_list[:interfaces])

  @fact_list[:primary_interface] = primary_interface
end
search_dhclient_leases(interface_data, interface_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 243
def search_dhclient_leases(interface_data, interface_name)
  return unless interface_data[:dhcp].nil?

  DIRS.each do |dir|
    next unless File.readable?(dir)

    lease_files = Dir.entries(dir).select { |file| file =~ /dhclient.*\.lease/ }
    next if lease_files.empty?

    lease_files.select do |file|
      content = Facter::Util::FileHelper.safe_read("#{dir}#{file}", nil)
      next unless content =~ /interface.*#{interface_name}/

      dhcp = content.match(/dhcp-server-identifier ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/)
      return interface_data[:dhcp] = dhcp[1] if dhcp
    end
  end
end
search_for_mac(ifaddr) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 88
def search_for_mac(ifaddr)
  mac = mac_from_bonded_interface(ifaddr.name)
  mac ||= mac_from(ifaddr)
  mac if !mac.nil? && mac != '00:00:00:00:00:00' && mac =~ /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/
end
search_internal_leases(interface_data, interface_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 262
def search_internal_leases(interface_data, interface_name)
  return if !interface_data[:dhcp].nil? || !File.readable?('/var/lib/NetworkManager/')

  files = Dir.entries('/var/lib/NetworkManager/').reject { |dir| dir =~ /^\.+$/ }
  lease_file = files.find { |file| file =~ /internal.*#{interface_name}\.lease/ }
  return unless lease_file

  dhcp = Facter::Util::FileHelper.safe_read("/var/lib/NetworkManager/#{lease_file}", nil)

  return unless dhcp

  dhcp = dhcp.match(/SERVER_ADDRESS=(.*)/)
  interface_data[:dhcp] = dhcp[1] if dhcp
end
search_mac_in_sockaddr(ifaddr) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 130
def search_mac_in_sockaddr(ifaddr)
  result = ifaddr.addr.inspect_sockaddr
  result&.match(/hwaddr=([\h:]+)/)&.captures&.first
end
search_systemd_netif_leases(interface_data, index_and_mtu) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 234
def search_systemd_netif_leases(interface_data, index_and_mtu)
  return if index_and_mtu.nil?

  index = index_and_mtu[:index]
  file_content = Facter::Util::FileHelper.safe_read("/run/systemd/netif/leases/#{index}", nil)
  dhcp = file_content.match(/SERVER_ADDRESS=(.*)/) if file_content
  interface_data[:dhcp] = dhcp[1] if dhcp
end
search_with_dhcpcd_command(interface_data, interface_name) click to toggle source
# File lib/facter/resolvers/networking_linux.rb, line 277
def search_with_dhcpcd_command(interface_data, interface_name)
  return unless interface_data[:dhcp].nil?

  output = Facter::Core::Execution.execute("dhcpcd -U #{interface_name}", logger: log)
  dhcp = output.match(/dhcp_server_identifier='(.*)'/)
  interface_data[:dhcp] = dhcp[1] if dhcp
end