summaryrefslogtreecommitdiff
path: root/lib/ohai/plugins/network.rb
blob: 88c72226b7ae49994c93de52557d2c49026d641e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#
# Author:: Adam Jacob (<adam@chef.io>)
# 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 "ipaddress"
  require "ohai/mixin/network_constants"
  include Ohai::Mixin::NetworkConstants

  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,
        ( family == "inet" ? v[:ipaddress].to_u32 : v[:ipaddress].to_u128 ),
      ]
    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::NetworkConstants::FAMILIES[family] + "_interface"
    gw_attr = Ohai::Mixin::NetworkConstants::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.select do |v|
            network_contains_address(network[gw_attr], v[:ipaddress], v[:iface])
          end.first
          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
    results = {}

    network Mash.new unless network
    network[:interfaces] = Mash.new unless network[:interfaces]
    counters Mash.new unless counters
    counters[:network] = Mash.new unless counters[:network]

    # inet family is processed before inet6 to give ipv4 precedence
    Ohai::Mixin::NetworkConstants::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