# # Author:: Adam Jacob () # Copyright:: Copyright (c) 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Ohai.plugin(:NetworkAddresses) do require_relative "../mixin/network_helper" include Ohai::Mixin::NetworkHelper provides "ipaddress", "ip6address", "macaddress" depends "network/interfaces" # from interface data create array of hashes with ipaddress, scope, and iface # sorted by scope, prefixlen and then ipaddress where longest prefixes first def sorted_ips(family = "inet") raise "bad family #{family}" unless %w{inet inet6}.include? family # priority of ipv6 link scopes to sort by later scope_prio = [ "global", "site", "link", "host", "node", nil ] # grab ipaddress, scope, and iface for sorting later ipaddresses = [] Mash[network["interfaces"]].each do |iface, iface_v| next if iface_v.nil? || !iface_v.key?("addresses") iface_v["addresses"].each do |addr, addr_v| next if addr_v.nil? || (not addr_v.key? "family") || addr_v["family"] != family ipaddresses << { ipaddress: addr_v["prefixlen"] ? IPAddress("#{addr}/#{addr_v["prefixlen"]}") : IPAddress("#{addr}/#{addr_v["netmask"]}"), scope: addr_v["scope"].nil? ? nil : addr_v["scope"].downcase, iface: iface, } end end # sort ip addresses by scope, by prefixlen and then by ip address # 128 - prefixlen: longest prefixes first ipaddresses.sort_by do |v| [ ( scope_prio.index(v[:scope]) || 999999 ), 128 - v[:ipaddress].prefix.to_i, v[:ipaddress].to_i, ] end end # finds ip address / interface for interface with default route based on # passed in family. returns [ipaddress, interface] uses 1st ip if no default # route is found def find_ip(family = "inet") ips = sorted_ips(family) # return if there aren't any #{family} addresses! return [ nil, nil ] if ips.empty? # shortcuts to access default #{family} interface and gateway int_attr = Ohai::Mixin::NetworkHelper::FAMILIES[family] + "_interface" gw_attr = Ohai::Mixin::NetworkHelper::FAMILIES[family] + "_gateway" if network[int_attr] # working with the address(es) of the default network interface gw_if_ips = ips.select do |v| v[:iface] == network[int_attr] end if gw_if_ips.empty? logger.warn("Plugin Network: [#{family}] no ip address on #{network[int_attr]}") elsif network[gw_attr] && network["interfaces"][network[int_attr]] && network["interfaces"][network[int_attr]]["addresses"] if [ "0.0.0.0", "::", /^fe80:/ ].any? { |pat| pat === network[gw_attr] } # link level default route logger.trace("Plugin Network: link level default #{family} route, picking ip from #{network[gw_attr]}") r = gw_if_ips.first else # checking network masks r = gw_if_ips.find do |v| network_contains_address(network[gw_attr], v[:ipaddress], v[:iface]) end if r.nil? r = gw_if_ips.first logger.trace("Plugin Network: [#{family}] no ipaddress/mask on #{network[int_attr]} matching the gateway #{network[gw_attr]}, picking #{r[:ipaddress]}") else logger.trace("Plugin Network: [#{family}] Using default interface #{network[int_attr]} and default gateway #{network[gw_attr]} to set the default ip to #{r[:ipaddress]}") end end else # return the first ip address on network[int_attr] r = gw_if_ips.first end else r = ips.first logger.trace("Plugin Network: [#{family}] no default interface, picking the first ipaddress") end return [ nil, nil ] if r.nil? || r.empty? [ r[:ipaddress].to_s, r[:iface] ] end # select mac address of first interface with family of lladdr def find_mac_from_iface(iface) r = network["interfaces"][iface]["addresses"].select { |k, v| v["family"] == "lladdr" } r.nil? || r.first.nil? ? nil : r.first.first end # address_to_match: String # ipaddress: IPAddress # iface: String def network_contains_address(address_to_match, ipaddress, iface) if ( peer = network["interfaces"][iface]["addresses"][ipaddress.to_s][:peer] ) IPAddress(peer) == IPAddress(address_to_match) else ipaddress.include? IPAddress(address_to_match) end end # ipaddress, ip6address and macaddress are set for each interface by the # #{os}::network plugin. atm it is expected macaddress is set at the same # time as ipaddress. if ipaddress is set and macaddress is nil, that means # the interface ipaddress is bound to has the NOARP flag collect_data do require "ipaddress" unless defined?(IPAddress) results = {} network Mash.new unless network network[:interfaces] ||= Mash.new counters Mash.new unless counters counters[:network] ||= Mash.new # inet family is processed before inet6 to give ipv4 precedence Ohai::Mixin::NetworkHelper::FAMILIES.keys.sort.each do |family| r = {} # find the ip/interface with the default route for this family (r["ip"], r["iface"]) = find_ip(family) r["mac"] = find_mac_from_iface(r["iface"]) unless r["iface"].nil? # don't overwrite attributes if they've already been set by the "#{os}::network" plugin if (family == "inet") && ipaddress.nil? if r["ip"].nil? logger.warn("Plugin Network: unable to detect ipaddress") else ipaddress r["ip"] end elsif (family == "inet6") && ip6address.nil? if r["ip"].nil? logger.trace("Plugin Network: unable to detect ip6address") else ip6address r["ip"] end end # set the macaddress [only if we haven't already]. this allows the #{os}::network plugin to set macaddress # otherwise we set macaddress on a first-found basis (and we started with ipv4) if macaddress.nil? if r["mac"] logger.trace("Plugin Network: setting macaddress to '#{r["mac"]}' from interface '#{r["iface"]}' for family '#{family}'") macaddress r["mac"] else logger.trace("Plugin Network: unable to detect macaddress for family '#{family}'") end end results[family] = r end if results["inet"]["iface"] && results["inet6"]["iface"] && (results["inet"]["iface"] != results["inet6"]["iface"]) logger.trace("Plugin Network: ipaddress and ip6address are set from different interfaces (#{results["inet"]["iface"]} & #{results["inet6"]["iface"]})") end end end