ohai 18.2.6 → 19.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +28 -28
  3. data/LICENSE +201 -201
  4. data/bin/ohai +25 -25
  5. data/lib/ohai/application.rb +189 -189
  6. data/lib/ohai/common/dmi.rb +167 -167
  7. data/lib/ohai/config.rb +51 -51
  8. data/lib/ohai/dsl/plugin/versionvii.rb +203 -203
  9. data/lib/ohai/dsl/plugin.rb +232 -232
  10. data/lib/ohai/dsl.rb +22 -22
  11. data/lib/ohai/exception.rb +36 -36
  12. data/lib/ohai/hints.rb +68 -68
  13. data/lib/ohai/loader.rb +178 -178
  14. data/lib/ohai/log.rb +34 -34
  15. data/lib/ohai/mash.rb +22 -22
  16. data/lib/ohai/mixin/alibaba_metadata.rb +83 -83
  17. data/lib/ohai/mixin/azure_metadata.rb +105 -105
  18. data/lib/ohai/mixin/chef_utils_wiring.rb +52 -52
  19. data/lib/ohai/mixin/command.rb +4 -4
  20. data/lib/ohai/mixin/constant_helper.rb +55 -55
  21. data/lib/ohai/mixin/dmi_decode.rb +54 -54
  22. data/lib/ohai/mixin/do_metadata.rb +48 -48
  23. data/lib/ohai/mixin/ec2_metadata.rb +264 -270
  24. data/lib/ohai/mixin/gce_metadata.rb +79 -79
  25. data/lib/ohai/mixin/http_helper.rb +64 -64
  26. data/lib/ohai/mixin/json_helper.rb +36 -36
  27. data/lib/ohai/mixin/network_helper.rb +92 -92
  28. data/lib/ohai/mixin/oci_metadata.rb +60 -60
  29. data/lib/ohai/mixin/os.rb +128 -128
  30. data/lib/ohai/mixin/scaleway_metadata.rb +51 -51
  31. data/lib/ohai/mixin/seconds_to_human.rb +52 -52
  32. data/lib/ohai/mixin/shell_out.rb +51 -51
  33. data/lib/ohai/mixin/softlayer_metadata.rb +74 -74
  34. data/lib/ohai/mixin/string.rb +31 -31
  35. data/lib/ohai/mixin/train_helpers.rb +36 -36
  36. data/lib/ohai/mixin/which.rb +39 -39
  37. data/lib/ohai/plugin_config.rb +47 -47
  38. data/lib/ohai/plugins/aix/kernel.rb +50 -50
  39. data/lib/ohai/plugins/aix/memory.rb +37 -37
  40. data/lib/ohai/plugins/aix/network.rb +142 -142
  41. data/lib/ohai/plugins/aix/platform.rb +30 -30
  42. data/lib/ohai/plugins/aix/uptime.rb +54 -54
  43. data/lib/ohai/plugins/aix/virtualization.rb +154 -154
  44. data/lib/ohai/plugins/alibaba.rb +72 -72
  45. data/lib/ohai/plugins/azure.rb +154 -154
  46. data/lib/ohai/plugins/bsd/virtualization.rb +121 -121
  47. data/lib/ohai/plugins/c.rb +178 -178
  48. data/lib/ohai/plugins/chef.rb +50 -50
  49. data/lib/ohai/plugins/cloud.rb +379 -379
  50. data/lib/ohai/plugins/command.rb +26 -26
  51. data/lib/ohai/plugins/cpu.rb +642 -642
  52. data/lib/ohai/plugins/darwin/hardware.rb +99 -99
  53. data/lib/ohai/plugins/darwin/memory.rb +62 -62
  54. data/lib/ohai/plugins/darwin/network.rb +207 -207
  55. data/lib/ohai/plugins/darwin/platform.rb +40 -40
  56. data/lib/ohai/plugins/darwin/virtualization.rb +104 -104
  57. data/lib/ohai/plugins/digital_ocean.rb +67 -67
  58. data/lib/ohai/plugins/dmi.rb +134 -134
  59. data/lib/ohai/plugins/docker.rb +58 -58
  60. data/lib/ohai/plugins/dragonflybsd/memory.rb +60 -60
  61. data/lib/ohai/plugins/dragonflybsd/network.rb +128 -128
  62. data/lib/ohai/plugins/dragonflybsd/platform.rb +28 -28
  63. data/lib/ohai/plugins/ec2.rb +148 -148
  64. data/lib/ohai/plugins/elixir.rb +36 -36
  65. data/lib/ohai/plugins/erlang.rb +60 -60
  66. data/lib/ohai/plugins/eucalyptus.rb +86 -86
  67. data/lib/ohai/plugins/filesystem.rb +753 -753
  68. data/lib/ohai/plugins/fips.rb +36 -36
  69. data/lib/ohai/plugins/freebsd/memory.rb +60 -60
  70. data/lib/ohai/plugins/freebsd/network.rb +128 -128
  71. data/lib/ohai/plugins/freebsd/platform.rb +28 -28
  72. data/lib/ohai/plugins/gce.rb +89 -89
  73. data/lib/ohai/plugins/go.rb +34 -34
  74. data/lib/ohai/plugins/groovy.rb +38 -38
  75. data/lib/ohai/plugins/grub2.rb +40 -40
  76. data/lib/ohai/plugins/habitat.rb +73 -73
  77. data/lib/ohai/plugins/haskell.rb +96 -96
  78. data/lib/ohai/plugins/hostname.rb +133 -133
  79. data/lib/ohai/plugins/init_package.rb +26 -26
  80. data/lib/ohai/plugins/java.rb +78 -78
  81. data/lib/ohai/plugins/kernel.rb +292 -292
  82. data/lib/ohai/plugins/keys.rb +27 -27
  83. data/lib/ohai/plugins/languages.rb +26 -26
  84. data/lib/ohai/plugins/libvirt.rb +114 -114
  85. data/lib/ohai/plugins/linode.rb +73 -73
  86. data/lib/ohai/plugins/linux/block_device.rb +48 -48
  87. data/lib/ohai/plugins/linux/hostnamectl.rb +34 -34
  88. data/lib/ohai/plugins/linux/interrupts.rb +84 -83
  89. data/lib/ohai/plugins/linux/ipc.rb +52 -52
  90. data/lib/ohai/plugins/linux/livepatch.rb +38 -38
  91. data/lib/ohai/plugins/linux/lsb.rb +46 -46
  92. data/lib/ohai/plugins/linux/lspci.rb +80 -80
  93. data/lib/ohai/plugins/linux/machineid.rb +36 -36
  94. data/lib/ohai/plugins/linux/mdadm.rb +120 -120
  95. data/lib/ohai/plugins/linux/memory.rb +106 -106
  96. data/lib/ohai/plugins/linux/network.rb +879 -879
  97. data/lib/ohai/plugins/linux/os_release.rb +38 -38
  98. data/lib/ohai/plugins/linux/platform.rb +314 -314
  99. data/lib/ohai/plugins/linux/selinux.rb +69 -69
  100. data/lib/ohai/plugins/linux/sessions.rb +54 -54
  101. data/lib/ohai/plugins/linux/sysctl.rb +39 -39
  102. data/lib/ohai/plugins/linux/systemd_paths.rb +36 -36
  103. data/lib/ohai/plugins/linux/tc.rb +61 -61
  104. data/lib/ohai/plugins/linux/virtualization.rb +300 -300
  105. data/lib/ohai/plugins/lua.rb +39 -39
  106. data/lib/ohai/plugins/mono.rb +50 -50
  107. data/lib/ohai/plugins/netbsd/memory.rb +99 -99
  108. data/lib/ohai/plugins/netbsd/network.rb +122 -122
  109. data/lib/ohai/plugins/netbsd/platform.rb +28 -28
  110. data/lib/ohai/plugins/network.rb +186 -186
  111. data/lib/ohai/plugins/nodejs.rb +40 -40
  112. data/lib/ohai/plugins/oci.rb +94 -94
  113. data/lib/ohai/plugins/ohai.rb +29 -29
  114. data/lib/ohai/plugins/ohai_time.rb +26 -26
  115. data/lib/ohai/plugins/openbsd/memory.rb +99 -99
  116. data/lib/ohai/plugins/openbsd/network.rb +122 -122
  117. data/lib/ohai/plugins/openbsd/platform.rb +28 -28
  118. data/lib/ohai/plugins/openstack.rb +84 -84
  119. data/lib/ohai/plugins/os.rb +55 -55
  120. data/lib/ohai/plugins/packages.rb +234 -234
  121. data/lib/ohai/plugins/passwd.rb +104 -104
  122. data/lib/ohai/plugins/perl.rb +45 -45
  123. data/lib/ohai/plugins/php.rb +52 -52
  124. data/lib/ohai/plugins/platform.rb +41 -41
  125. data/lib/ohai/plugins/powershell.rb +82 -82
  126. data/lib/ohai/plugins/ps.rb +35 -35
  127. data/lib/ohai/plugins/python.rb +43 -43
  128. data/lib/ohai/plugins/rackspace.rb +177 -177
  129. data/lib/ohai/plugins/root_group.rb +41 -41
  130. data/lib/ohai/plugins/rpm.rb +121 -121
  131. data/lib/ohai/plugins/ruby.rb +66 -66
  132. data/lib/ohai/plugins/rust.rb +34 -34
  133. data/lib/ohai/plugins/scala.rb +38 -38
  134. data/lib/ohai/plugins/scaleway.rb +58 -58
  135. data/lib/ohai/plugins/scsi.rb +52 -52
  136. data/lib/ohai/plugins/shard.rb +142 -142
  137. data/lib/ohai/plugins/shells.rb +32 -32
  138. data/lib/ohai/plugins/softlayer.rb +48 -48
  139. data/lib/ohai/plugins/solaris2/dmi.rb +191 -191
  140. data/lib/ohai/plugins/solaris2/memory.rb +32 -32
  141. data/lib/ohai/plugins/solaris2/network.rb +192 -192
  142. data/lib/ohai/plugins/solaris2/platform.rb +58 -58
  143. data/lib/ohai/plugins/solaris2/virtualization.rb +90 -90
  144. data/lib/ohai/plugins/ssh_host_key.rb +84 -84
  145. data/lib/ohai/plugins/sysconf.rb +46 -46
  146. data/lib/ohai/plugins/timezone.rb +45 -45
  147. data/lib/ohai/plugins/train.rb +35 -35
  148. data/lib/ohai/plugins/uptime.rb +95 -95
  149. data/lib/ohai/plugins/virtualbox.rb +197 -197
  150. data/lib/ohai/plugins/vmware.rb +109 -109
  151. data/lib/ohai/plugins/windows/dmi.rb +95 -95
  152. data/lib/ohai/plugins/windows/drivers.rb +52 -52
  153. data/lib/ohai/plugins/windows/memory.rb +39 -39
  154. data/lib/ohai/plugins/windows/network.rb +222 -222
  155. data/lib/ohai/plugins/windows/platform.rb +34 -34
  156. data/lib/ohai/plugins/windows/system_enclosure.rb +29 -29
  157. data/lib/ohai/plugins/windows/virtualization.rb +45 -45
  158. data/lib/ohai/plugins/zpools.rb +94 -94
  159. data/lib/ohai/provides_map.rb +208 -208
  160. data/lib/ohai/runner.rb +128 -126
  161. data/lib/ohai/system.rb +258 -258
  162. data/lib/ohai/train_transport.rb +29 -29
  163. data/lib/ohai/util/file_helper.rb +6 -6
  164. data/lib/ohai/util/ip_helper.rb +56 -56
  165. data/lib/ohai/util/win32.rb +47 -47
  166. data/lib/ohai/version.rb +23 -23
  167. data/lib/ohai.rb +23 -23
  168. data/ohai.gemspec +35 -35
  169. metadata +9 -15
@@ -1,879 +1,879 @@
1
- # frozen_string_literal: true
2
- #
3
- # Author:: Adam Jacob (<adam@chef.io>)
4
- # Author:: Chris Read <chris.read@gmail.com>
5
- # Copyright:: Copyright (c) Chef Software Inc.
6
- # License:: Apache License, Version 2.0
7
- #
8
- # Licensed under the Apache License, Version 2.0 (the "License");
9
- # you may not use this file except in compliance with the License.
10
- # You may obtain a copy of the License at
11
- #
12
- # http://www.apache.org/licenses/LICENSE-2.0
13
- #
14
- # Unless required by applicable law or agreed to in writing, software
15
- # distributed under the License is distributed on an "AS IS" BASIS,
16
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
- # See the License for the specific language governing permissions and
18
- # limitations under the License.
19
- #
20
-
21
- Ohai.plugin(:Network) do
22
- provides "network", "network/interfaces"
23
- provides "counters/network", "counters/network/interfaces"
24
- provides "ipaddress", "ip6address", "macaddress"
25
-
26
- def linux_encaps_lookup(encap)
27
- return "Loopback" if encap.eql?("Local Loopback") || encap.eql?("loopback")
28
- return "PPP" if encap.eql?("Point-to-Point Protocol")
29
- return "SLIP" if encap.eql?("Serial Line IP")
30
- return "VJSLIP" if encap.eql?("VJ Serial Line IP")
31
- return "IPIP" if encap.eql?("IPIP Tunnel")
32
- return "6to4" if encap.eql?("IPv6-in-IPv4")
33
- return "Ethernet" if encap.eql?("ether")
34
-
35
- encap
36
- end
37
-
38
- def ipv6_enabled?
39
- file_exist? "/proc/net/if_inet6"
40
- end
41
-
42
- def ethtool_binary_path
43
- @ethtool ||= which("ethtool")
44
- end
45
-
46
- def is_openvz?
47
- @openvz ||= file_directory?("/proc/vz")
48
- end
49
-
50
- def is_openvz_host?
51
- is_openvz? && file_directory?("/proc/bc")
52
- end
53
-
54
- def extract_neighbors(family, iface, neigh_attr)
55
- so = shell_out("ip -f #{family[:name]} neigh show")
56
- so.stdout.lines do |line|
57
- if line =~ /^([a-f0-9\:\.]+)\s+dev\s+([^\s]+)\s+lladdr\s+([a-fA-F0-9\:]+)/
58
- interface = iface[$2]
59
- unless interface
60
- logger.warn("neighbor list has entries for unknown interface #{interface}")
61
- next
62
- end
63
- interface[neigh_attr] ||= Mash.new
64
- interface[neigh_attr][$1] = $3.downcase
65
- end
66
- end
67
- iface
68
- end
69
-
70
- # checking the routing tables
71
- # why ?
72
- # 1) to set the default gateway and default interfaces attributes
73
- # 2) on some occasions, the best way to select node[:ipaddress] is to look at
74
- # the routing table source field.
75
- # 3) and since we're at it, let's populate some :routes attributes
76
- # (going to do that for both inet and inet6 addresses)
77
- def check_routing_table(family, iface, default_route_table)
78
- so = shell_out("ip -o -f #{family[:name]} route show table #{default_route_table}")
79
- so.stdout.lines do |line|
80
- line.strip!
81
- logger.trace("Plugin Network: Parsing #{line}")
82
- if line.include?("\\")
83
- # If we have multipath routing, then the first part will be a normal
84
- # looking route:
85
- # default proto ra metric 1024 <other options>
86
- # Each successive part after that is a hop without those options.
87
- # So the first thing we do is grab that first part, and split it into
88
- # the route destination ("default"), and the route options.
89
- parts = line.split("\\")
90
- route_dest, dest_opts = parts.first.split(nil, 2)
91
- # Then all the route endings, generally just nexthops.
92
- route_endings = parts[1..]
93
- if dest_opts && !dest_opts.empty?
94
- # Route options like proto, metric, etc. only appear once for each
95
- # multipath configuration. Prepend this information to the route
96
- # endings so the code below will assign the fields properly.
97
- route_endings.map! { |e| e.include?("nexthop") ? "#{dest_opts} #{e}" : e }
98
- end
99
- elsif line =~ /^([^\s]+)\s(.*)$/
100
- route_dest = $1
101
- route_endings = [$2]
102
- else
103
- next
104
- end
105
- route_endings.each do |route_ending|
106
- route_entry = Mash.new(destination: route_dest,
107
- family: family[:name])
108
- route_int = nil
109
- if route_ending =~ /\bdev\s+([^\s]+)\b/
110
- route_int = $1
111
- end
112
- # does any known interface own the src address?
113
- # we try to infer the interface/device from its address if it isn't specified
114
- # we want to override the interface set via nexthop but only if possible
115
- if line =~ /\bsrc\s+([^\s]+)\b/ && (!route_int || line.include?("nexthop"))
116
- # only clobber previously set route_int if we find a match
117
- if (match = iface.select { |name, intf| intf.fetch("addresses", {}).any? { |addr, _| addr == $1 } }.keys.first)
118
- route_int = match
119
- route_entry[:inferred] = true
120
- end
121
- end
122
-
123
- unless route_int
124
- logger.trace("Plugin Network: Skipping route entry without a device: '#{line}'")
125
- next
126
- end
127
- route_int = "venet0:0" if is_openvz? && !is_openvz_host? && route_int == "venet0" && iface["venet0:0"]
128
-
129
- unless iface[route_int]
130
- logger.trace("Plugin Network: Skipping previously unseen interface from 'ip route show': #{route_int}")
131
- next
132
- end
133
-
134
- %w{via scope metric proto src}.each do |k|
135
- # http://rubular.com/r/pwTNp65VFf
136
- route_entry[k] = $1 if route_ending =~ /\b#{k}\s+([^\s]+)/
137
- end
138
- # https://rubular.com/r/k1sMrRn5yLjgVi
139
- route_entry["via"] = $1 if route_ending =~ /\bvia\s+inet6\s+([^\s]+)/
140
-
141
- # a sanity check, especially for Linux-VServer, OpenVZ and LXC:
142
- # don't report the route entry if the src address isn't set on the node
143
- # unless the interface has no addresses of this type at all
144
- if route_entry[:src]
145
- addr = iface[route_int][:addresses]
146
- unless addr.nil? || addr.key?(route_entry[:src]) ||
147
- addr.values.all? { |a| a["family"] != family[:name] }
148
- logger.trace("Plugin Network: Skipping route entry whose src does not match the interface IP")
149
- next
150
- end
151
- end
152
-
153
- iface[route_int][:routes] = [] unless iface[route_int][:routes]
154
- iface[route_int][:routes] << route_entry
155
- end
156
- end
157
- iface
158
- end
159
-
160
- # now looking at the routes to set the default attributes
161
- # for information, default routes can be of this form :
162
- # - default via 10.0.2.4 dev br0
163
- # - default dev br0 scope link
164
- # - default dev eth0 scope link src 1.1.1.1
165
- # - default via 10.0.3.1 dev eth1 src 10.0.3.2 metric 10
166
- # - default via 10.0.4.1 dev eth2 src 10.0.4.2 metric 20
167
-
168
- # using a temporary var to hold routes and their interface name
169
- def parse_routes(family, iface)
170
- iface.filter_map do |i, iv|
171
- next unless iv[:routes]
172
-
173
- iv[:routes].filter_map do |r|
174
- r.merge(dev: i) if r[:family] == family[:name]
175
- end
176
- end.flatten
177
- end
178
-
179
- # determine layer 1 details for the interface using ethtool
180
- def ethernet_layer_one(iface)
181
- return iface unless ethtool_binary_path
182
-
183
- keys = %w{Speed Duplex Port Transceiver Auto-negotiation MDI-X}
184
- iface.each_key do |tmp_int|
185
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
186
-
187
- so = shell_out("#{ethtool_binary_path} #{tmp_int}")
188
- so.stdout.lines do |line|
189
- line.chomp!
190
- logger.trace("Plugin Network: Parsing ethtool output: #{line}")
191
- line.lstrip!
192
- k, v = line.split(": ")
193
- next unless keys.include? k
194
-
195
- k.downcase!.tr!("-", "_")
196
- if k == "speed"
197
- k = "link_speed" # This is not necessarily the maximum speed the NIC supports
198
- v = v[/\d+/].to_i
199
- end
200
- iface[tmp_int][k] = v
201
- end
202
- end
203
- iface
204
- end
205
-
206
- # determine ring parameters for the interface using ethtool
207
- def ethernet_ring_parameters(iface)
208
- return iface unless ethtool_binary_path
209
-
210
- iface.each_key do |tmp_int|
211
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
212
-
213
- so = shell_out("#{ethtool_binary_path} -g #{tmp_int}")
214
- logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
215
- type = nil
216
- iface[tmp_int]["ring_params"] = {}
217
- so.stdout.lines.each do |line|
218
- next if line.start_with?("Ring parameters for")
219
- next if line.strip.nil?
220
-
221
- if line.include?("Pre-set maximums")
222
- type = "max"
223
- next
224
- end
225
- if line.include?("Current hardware settings")
226
- type = "current"
227
- next
228
- end
229
- key, val = line.split(/:\s+/)
230
- if type && val
231
- ring_key = "#{type}_#{key.downcase.tr(" ", "_")}"
232
- iface[tmp_int]["ring_params"][ring_key] = val.to_i
233
- end
234
- end
235
- end
236
- iface
237
- end
238
-
239
- # determine channel parameters for the interface using ethtool
240
- def ethernet_channel_parameters(iface)
241
- return iface unless ethtool_binary_path
242
-
243
- iface.each_key do |tmp_int|
244
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
245
-
246
- so = shell_out("#{ethtool_binary_path} -l #{tmp_int}")
247
- logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
248
- type = nil
249
- iface[tmp_int]["channel_params"] = {}
250
- so.stdout.lines.each do |line|
251
- next if line.start_with?("Channel parameters for")
252
- next if line.strip.nil?
253
-
254
- if line.include?("Pre-set maximums")
255
- type = "max"
256
- next
257
- end
258
- if line.include?("Current hardware settings")
259
- type = "current"
260
- next
261
- end
262
- key, val = line.split(/:\s+/)
263
- if type && val
264
- channel_key = "#{type}_#{key.downcase.tr(" ", "_")}"
265
- iface[tmp_int]["channel_params"][channel_key] = val.to_i
266
- end
267
- end
268
- end
269
- iface
270
- end
271
-
272
- # determine coalesce parameters for the interface using ethtool
273
- def ethernet_coalesce_parameters(iface)
274
- return iface unless ethtool_binary_path
275
-
276
- iface.each_key do |tmp_int|
277
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
278
-
279
- so = shell_out("#{ethtool_binary_path} -c #{tmp_int}")
280
- logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
281
- iface[tmp_int]["coalesce_params"] = {}
282
- so.stdout.lines.each do |line|
283
- next if line.start_with?("Coalesce parameters for")
284
- next if line.strip.nil?
285
-
286
- if line.start_with?("Adaptive")
287
- _, adaptive_rx, _, adaptive_tx = line.split(/:\s+|\s+TX|\n/)
288
- iface[tmp_int]["coalesce_params"]["adaptive_rx"] = adaptive_rx
289
- iface[tmp_int]["coalesce_params"]["adaptive_tx"] = adaptive_tx
290
- next
291
- end
292
- key, val = line.split(/:\s+/)
293
- if val
294
- coalesce_key = key.downcase.tr(" ", "_").to_s
295
- iface[tmp_int]["coalesce_params"][coalesce_key] = val.to_i
296
- end
297
- end
298
- end
299
- iface
300
- end
301
-
302
- # determine offload features for the interface using ethtool
303
- def ethernet_offload_parameters(iface)
304
- return iface unless ethtool_binary_path
305
-
306
- iface.each_key do |tmp_int|
307
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
308
-
309
- so = shell_out("#{ethtool_binary_path} -k #{tmp_int}")
310
- Ohai::Log.debug("Plugin Network: Parsing ethtool output: #{so.stdout}")
311
- iface[tmp_int]["offload_params"] = {}
312
- so.stdout.lines.each do |line|
313
- next if line.start_with?("Features for")
314
- next if line.strip.nil?
315
-
316
- key, val = line.split(/:\s+/)
317
- if val
318
- offload_key = key.downcase.strip.tr(" ", "_").to_s
319
- iface[tmp_int]["offload_params"][offload_key] = val.downcase.gsub(/\[.*\]/, "").strip.to_s
320
- end
321
- end
322
- end
323
- iface
324
- end
325
-
326
- # determine pause parameters for the interface using ethtool
327
- def ethernet_pause_parameters(iface)
328
- return iface unless ethtool_binary_path
329
-
330
- iface.each_key do |tmp_int|
331
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
332
-
333
- so = shell_out("#{ethtool_binary_path} -a #{tmp_int}")
334
- logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
335
- iface[tmp_int]["pause_params"] = {}
336
- so.stdout.lines.each do |line|
337
- next if line.start_with?("Pause parameters for")
338
- next if line.strip.nil?
339
-
340
- key, val = line.split(/:\s+/)
341
- if val
342
- pause_key = "#{key.downcase.tr(" ", "_")}"
343
- iface[tmp_int]["pause_params"][pause_key] = val.strip.eql? "on"
344
- end
345
- end
346
- end
347
- iface
348
- end
349
-
350
- # determine driver info for the interface using ethtool
351
- def ethernet_driver_info(iface)
352
- return iface unless ethtool_binary_path
353
-
354
- iface.each_key do |tmp_int|
355
- next unless iface[tmp_int][:encapsulation] == "Ethernet"
356
-
357
- so = shell_out("#{ethtool_binary_path} -i #{tmp_int}")
358
- logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
359
- iface[tmp_int]["driver_info"] = {}
360
- so.stdout.lines.each do |line|
361
- next if line.strip.nil?
362
-
363
- key, val = line.split(/:\s+/)
364
- if val.nil?
365
- val = ""
366
- end
367
- driver_key = key.downcase.tr(" ", "_").to_s
368
- iface[tmp_int]["driver_info"][driver_key] = val.chomp
369
- end
370
- end
371
- iface
372
- end
373
-
374
- # determine link stats, vlans, queue length, and state for an interface using ip
375
- def link_statistics(iface, net_counters)
376
- so = shell_out("ip -d -s link")
377
- tmp_int = nil
378
- on_rx = true
379
- xdp_mode = nil
380
- so.stdout.lines do |line|
381
- if line =~ IPROUTE_INT_REGEX
382
- tmp_int = $2
383
- iface[tmp_int] ||= Mash.new
384
- net_counters[tmp_int] ||= Mash.new
385
- end
386
-
387
- if /^\s+(ip6tnl|ipip)/.match?(line)
388
- iface[tmp_int][:tunnel_info] = {}
389
- words = line.split
390
- words.each_with_index do |word, index|
391
- case word
392
- when "external"
393
- iface[tmp_int][:tunnel_info][word] = true
394
- when "any", "ipip6", "ip6ip6"
395
- iface[tmp_int][:tunnel_info][:proto] = word
396
- when "remote",
397
- "local",
398
- "encaplimit",
399
- "hoplimit",
400
- "tclass",
401
- "flowlabel",
402
- "addrgenmode",
403
- "numtxqueues",
404
- "numrxqueues",
405
- "gso_max_size",
406
- "gso_max_segs"
407
- iface[tmp_int][:tunnel_info][word] = words[index + 1]
408
- end
409
- end
410
- end
411
-
412
- if line =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
413
- int = on_rx ? :rx : :tx
414
- net_counters[tmp_int][int] ||= Mash.new
415
- net_counters[tmp_int][int][:bytes] = $1
416
- net_counters[tmp_int][int][:packets] = $2
417
- net_counters[tmp_int][int][:errors] = $3
418
- net_counters[tmp_int][int][:drop] = $4
419
- if int == :rx
420
- net_counters[tmp_int][int][:overrun] = $5
421
- else
422
- net_counters[tmp_int][int][:carrier] = $5
423
- net_counters[tmp_int][int][:collisions] = $6
424
- end
425
-
426
- on_rx = !on_rx
427
- end
428
-
429
- if line =~ /qlen (\d+)/
430
- net_counters[tmp_int][:tx] ||= Mash.new
431
- net_counters[tmp_int][:tx][:queuelen] = $1
432
- end
433
-
434
- if line =~ /vlan id (\d+)/ || line =~ /vlan protocol ([\w\.]+) id (\d+)/
435
- if $2
436
- tmp_prot = $1
437
- tmp_id = $2
438
- else
439
- tmp_id = $1
440
- end
441
- iface[tmp_int][:vlan] ||= Mash.new
442
- iface[tmp_int][:vlan][:id] = tmp_id
443
- iface[tmp_int][:vlan][:protocol] = tmp_prot if tmp_prot
444
-
445
- vlan_flags = line.scan(/(REORDER_HDR|GVRP|LOOSE_BINDING)/)
446
- if vlan_flags.length > 0
447
- iface[tmp_int][:vlan][:flags] = vlan_flags.flatten.uniq
448
- end
449
- end
450
-
451
- # https://rubular.com/r/JRp6lNANmpcLV5
452
- if line =~ /\sstate (\w+)/
453
- iface[tmp_int]["state"] = $1.downcase
454
- end
455
-
456
- if line.include?("xdp")
457
- mode = line.scan(/\s(xdp|xdpgeneric|xdpoffload|xdpmulti)\s/).flatten[0]
458
- # Fetches and sets the mode from the first line.
459
- unless mode.nil?
460
- iface[tmp_int][:xdp] = {}
461
- if mode.eql?("xdp")
462
- # In case of xdpdrv, mode is xdp,
463
- # to keep it consistent, keeping mode as xdpdrv.
464
- mode = "xdpdrv"
465
- end
466
- xdp_mode = mode
467
- iface[tmp_int][:xdp][:attached] = []
468
- end
469
-
470
- if line =~ %r{prog/(\w+) id (\d+) tag (\w+)}
471
- mode = $1.eql?("xdp") ? xdp_mode : $1
472
- iface[tmp_int][:xdp][:attached] << {
473
- mode: mode,
474
- id: $2,
475
- tag: $3,
476
- }
477
- end
478
- end
479
- end
480
- iface
481
- end
482
-
483
- def match_iproute(iface, line, cint)
484
- if line =~ IPROUTE_INT_REGEX
485
- cint = $2
486
- iface[cint] = Mash.new
487
- if cint =~ /^(\w+?)(\d+.*)/
488
- iface[cint][:type] = $1
489
- iface[cint][:number] = $2
490
- end
491
-
492
- if line =~ /mtu (\d+)/
493
- iface[cint][:mtu] = $1
494
- end
495
-
496
- flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|LOWER_UP|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)/)
497
- if flags.length > 1
498
- iface[cint][:flags] = flags.flatten.uniq
499
- end
500
- end
501
- cint
502
- end
503
-
504
- def parse_ip_addr(iface)
505
- so = shell_out("ip addr")
506
- cint = nil
507
- so.stdout.lines do |line|
508
- cint = match_iproute(iface, line, cint)
509
-
510
- parse_ip_addr_link_line(cint, iface, line)
511
- cint = parse_ip_addr_inet_line(cint, iface, line)
512
- parse_ip_addr_inet6_line(cint, iface, line)
513
- end
514
- end
515
-
516
- def parse_ip_addr_link_line(cint, iface, line)
517
- if line =~ %r{link/(\w+) ([\da-f\:]+) }
518
- iface[cint][:encapsulation] = linux_encaps_lookup($1)
519
- unless $2 == "00:00:00:00:00:00"
520
- iface[cint][:addresses] ||= Mash.new
521
- iface[cint][:addresses][$2.upcase] = { "family" => "lladdr" }
522
- end
523
- end
524
- end
525
-
526
- def parse_ip_addr_inet_line(cint, iface, line)
527
- if line =~ %r{inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(/(\d{1,2}))?}
528
- tmp_addr, tmp_prefix = $1, $3
529
- tmp_prefix ||= "32"
530
- original_int = nil
531
-
532
- # Are we a formerly aliased interface?
533
- if line =~ /#{cint}:(\d+)$/
534
- sub_int = $1
535
- alias_int = "#{cint}:#{sub_int}"
536
- original_int = cint
537
- cint = alias_int
538
- end
539
-
540
- iface[cint] ||= Mash.new # Create the fake alias interface if needed
541
- iface[cint][:addresses] ||= Mash.new
542
- iface[cint][:addresses][tmp_addr] = { "family" => "inet", "prefixlen" => tmp_prefix }
543
- iface[cint][:addresses][tmp_addr][:netmask] = IPAddr.new("255.255.255.255").mask(tmp_prefix.to_i).to_s
544
-
545
- if line =~ /peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
546
- iface[cint][:addresses][tmp_addr][:peer] = $1
547
- end
548
-
549
- if line =~ /brd (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
550
- iface[cint][:addresses][tmp_addr][:broadcast] = $1
551
- end
552
-
553
- if line =~ /scope (\w+)/
554
- iface[cint][:addresses][tmp_addr][:scope] = ($1.eql?("host") ? "Node" : $1.capitalize)
555
- end
556
-
557
- # If we found we were an alias interface, restore cint to its original value
558
- cint = original_int unless original_int.nil?
559
- end
560
- cint
561
- end
562
-
563
- def parse_ip_addr_inet6_line(cint, iface, line)
564
- if line =~ %r{inet6 ([a-f0-9\:]+)/(\d+) scope (\w+)( .*)?}
565
- iface[cint][:addresses] ||= Mash.new
566
- tmp_addr = $1
567
- tags = $4 || ""
568
- tags = tags.split
569
-
570
- iface[cint][:addresses][tmp_addr] = {
571
- "family" => "inet6",
572
- "prefixlen" => $2,
573
- "scope" => ($3.eql?("host") ? "Node" : $3.capitalize),
574
- "tags" => tags,
575
- }
576
- end
577
- end
578
-
579
- # returns the macaddress for interface from a hash of interfaces (iface elsewhere in this file)
580
- def get_mac_for_interface(interfaces, interface)
581
- interfaces[interface][:addresses].find { |k, v| v["family"] == "lladdr" }.first unless interfaces[interface][:addresses].nil? || interfaces[interface][:flags].include?("NOARP")
582
- end
583
-
584
- # returns the default route with the lowest metric (unspecified metric is 0)
585
- def choose_default_route(routes)
586
- routes.select do |r|
587
- r[:destination] == "default"
588
- end.min do |x, y|
589
- (x[:metric].nil? ? 0 : x[:metric].to_i) <=> (y[:metric].nil? ? 0 : y[:metric].to_i)
590
- end
591
- end
592
-
593
- def interface_has_no_addresses_in_family?(iface, family)
594
- return true if iface[:addresses].nil?
595
-
596
- iface[:addresses].values.all? { |addr| addr["family"] != family }
597
- end
598
-
599
- def interface_have_address?(iface, address)
600
- return false if iface[:addresses].nil?
601
-
602
- iface[:addresses].key?(address)
603
- end
604
-
605
- def interface_address_not_link_level?(iface, address)
606
- !(iface[:addresses][address][:scope].casecmp("link") == 0)
607
- end
608
-
609
- def interface_valid_for_route?(iface, address, family)
610
- return true if interface_has_no_addresses_in_family?(iface, family)
611
-
612
- interface_have_address?(iface, address) && interface_address_not_link_level?(iface, address)
613
- end
614
-
615
- def route_is_valid_default_route?(route, default_route)
616
- # if the route destination is a default route, it's good
617
- return true if route[:destination] == "default"
618
-
619
- return false if default_route[:via].nil?
620
-
621
- dest_ipaddr = IPAddr.new(route[:destination])
622
- default_route_via = IPAddr.new(default_route[:via])
623
-
624
- # check if nexthop is the same address family
625
- return false if dest_ipaddr.ipv4? != default_route_via.ipv4?
626
-
627
- # the default route has a gateway and the route matches the gateway
628
- dest_ipaddr.include?(default_route_via)
629
- end
630
-
631
- # ipv4/ipv6 routes are different enough that having a single algorithm to select the favored route for both creates unnecessary complexity
632
- # this method attempts to deduce the route that is most important to the user, which is later used to deduce the favored values for {ip,mac,ip6}address
633
- # we only consider routes that are default routes, or those routes that get us to the gateway for a default route
634
- def favored_default_route_linux(routes, iface, default_route, family)
635
- routes.select do |r|
636
- if family[:name] == "inet"
637
- # the route must have a source address
638
- next if r[:src].nil? || r[:src].empty?
639
-
640
- # the interface specified in the route must exist
641
- route_interface = iface[r[:dev]]
642
- next if route_interface.nil? # the interface specified in the route must exist
643
-
644
- # the interface must have no addresses, or if it has the source address, the address must not
645
- # be a link-level address
646
- next unless interface_valid_for_route?(route_interface, r[:src], "inet")
647
-
648
- # the route must either be a default route, or it must have a gateway which is accessible via the route
649
- next unless route_is_valid_default_route?(r, default_route)
650
-
651
- true
652
- elsif family[:name] == "inet6"
653
- iface[r[:dev]] &&
654
- iface[r[:dev]][:state] == "up" &&
655
- route_is_valid_default_route?(r, default_route)
656
- end
657
- end.min_by do |r|
658
- # sorting the selected routes:
659
- # - getting default routes first
660
- # - then sort by metric
661
- # - then sort by if the device was inferred or not (preferring explicit to inferred)
662
- # - then by prefixlen
663
- [
664
- r[:destination] == "default" ? 0 : 1,
665
- r[:metric].nil? ? 0 : r[:metric].to_i,
666
- r[:inferred] ? 1 : 0,
667
- # for some reason IPAddress doesn't accept "::/0", it doesn't like prefix==0
668
- # just a quick workaround: use 0 if IPAddress fails
669
- begin
670
- IPAddress( r[:destination] == "default" ? family[:default_route] : r[:destination] ).prefix
671
- rescue
672
- 0
673
- end,
674
- ]
675
- end
676
- end
677
-
678
- # Both the network plugin and this plugin (linux/network) are run on linux. This plugin runs first.
679
- # If the 'ip' binary is available, this plugin may set {ip,mac,ip6}address. The network plugin should not overwrite these.
680
- # The older code section below that relies on the deprecated net-tools, e.g. netstat and ifconfig, provides less functionality.
681
- collect_data(:linux) do
682
- require "ipaddr" unless defined?(IPAddr)
683
-
684
- iface = Mash.new
685
- net_counters = Mash.new
686
-
687
- network Mash.new unless network
688
- network[:interfaces] ||= Mash.new
689
- counters Mash.new unless counters
690
- counters[:network] ||= Mash.new
691
-
692
- # ohai.plugin[:network][:default_route_table] = 'default'
693
- if configuration(:default_route_table).nil? || configuration(:default_route_table).empty?
694
- default_route_table = "main"
695
- else
696
- default_route_table = configuration(:default_route_table)
697
- end
698
- logger.trace("Plugin Network: default route table is '#{default_route_table}'")
699
-
700
- # Match the lead line for an interface from iproute2
701
- # 3: eth0.11@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
702
- # The '@eth0:' portion doesn't exist on primary interfaces and thus is optional in the regex
703
- IPROUTE_INT_REGEX ||= /^(\d+): ([0-9a-zA-Z@:\.\-_]*?)(@[0-9a-zA-Z\-_]+|):\s/.freeze
704
-
705
- if which("ip")
706
- # families to get default routes from
707
- families = [{
708
- name: "inet",
709
- default_route: "0.0.0.0/0",
710
- default_prefix: :default,
711
- neighbour_attribute: :arp,
712
- }]
713
-
714
- if ipv6_enabled?
715
- families << {
716
- name: "inet6",
717
- default_route: "::/0",
718
- default_prefix: :default_inet6,
719
- neighbour_attribute: :neighbour_inet6,
720
- }
721
- end
722
-
723
- parse_ip_addr(iface)
724
-
725
- iface = link_statistics(iface, net_counters)
726
-
727
- families.each do |family|
728
- neigh_attr = family[:neighbour_attribute]
729
- default_prefix = family[:default_prefix]
730
-
731
- iface = extract_neighbors(family, iface, neigh_attr)
732
-
733
- iface = check_routing_table(family, iface, default_route_table)
734
-
735
- routes = parse_routes(family, iface)
736
-
737
- default_route = choose_default_route(routes)
738
-
739
- if default_route.nil? || default_route.empty?
740
- attribute_name = if family[:name] == "inet"
741
- "default_interface"
742
- else
743
- "default_#{family[:name]}_interface"
744
- end
745
- logger.trace("Plugin Network: Unable to determine '#{attribute_name}' as no default routes were found for that interface family")
746
- else
747
- network["#{default_prefix}_interface"] = default_route[:dev]
748
- logger.trace("Plugin Network: #{default_prefix}_interface set to #{default_route[:dev]}")
749
-
750
- # setting gateway to 0.0.0.0 or :: if the default route is a link level one
751
- network["#{default_prefix}_gateway"] = default_route[:via] || family[:default_route].chomp("/0")
752
- logger.trace("Plugin Network: #{default_prefix}_gateway set to #{network["#{default_prefix}_gateway"]}")
753
-
754
- # deduce the default route the user most likely cares about to pick {ip,mac,ip6}address below
755
- favored_route = favored_default_route_linux(routes, iface, default_route, family)
756
-
757
- # FIXME: This entire block should go away, and the network plugin should be the sole source of {ip,ip6,mac}address
758
-
759
- # since we're at it, let's populate {ip,mac,ip6}address with the best values
760
- # if we don't set these, the network plugin may set them afterwards
761
- if favored_route && !favored_route.empty?
762
- if family[:name] == "inet"
763
- ipaddress favored_route[:src]
764
- m = get_mac_for_interface(iface, favored_route[:dev])
765
- logger.trace("Plugin Network: Overwriting macaddress #{macaddress} with #{m} from interface #{favored_route[:dev]}") if macaddress
766
- macaddress m
767
- elsif family[:name] == "inet6"
768
- # this rarely does anything since we rarely have src for ipv6, so this usually falls back on the network plugin
769
- ip6address favored_route[:src]
770
- if macaddress
771
- logger.trace("Plugin Network: Not setting macaddress from ipv6 interface #{favored_route[:dev]} because macaddress is already set")
772
- else
773
- macaddress get_mac_for_interface(iface, favored_route[:dev])
774
- end
775
- end
776
- else
777
- logger.trace("Plugin Network: Unable to deduce the favored default route for family '#{family[:name]}' despite finding a default route, and is not setting ipaddress/ip6address/macaddress. the network plugin may provide fallbacks.")
778
- logger.trace("Plugin Network: This potential default route was excluded: #{default_route}")
779
- end
780
- end
781
- end # end families.each
782
- else # ip binary not available, falling back to net-tools, e.g. route, ifconfig
783
- begin
784
- so = shell_out("route -n")
785
- route_result = so.stdout.split($/).grep( /^0.0.0.0/ )[0].split( /[ \t]+/ )
786
- network[:default_gateway], network[:default_interface] = route_result.values_at(1, 7)
787
- rescue Ohai::Exceptions::Exec
788
- logger.trace("Plugin Network: Unable to determine default interface")
789
- end
790
-
791
- so = shell_out("ifconfig -a")
792
- cint = nil
793
- so.stdout.lines do |line|
794
- tmp_addr = nil
795
- # dev_valid_name in the kernel only excludes slashes, nulls, spaces
796
- # http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=net/core/dev.c#l851
797
- if line =~ /^([0-9a-zA-Z@\.\:\-_]+)\s+/
798
- cint = $1
799
- iface[cint] = Mash.new
800
- if cint =~ /^(\w+?)(\d+.*)/
801
- iface[cint][:type] = $1
802
- iface[cint][:number] = $2
803
- end
804
- end
805
- if line =~ /Link encap:(Local Loopback)/ || line =~ /Link encap:(.+?)\s/
806
- iface[cint][:encapsulation] = linux_encaps_lookup($1)
807
- end
808
- if line =~ /HWaddr (.+?)\s/
809
- iface[cint][:addresses] ||= Mash.new
810
- iface[cint][:addresses][$1] = { "family" => "lladdr" }
811
- end
812
- if line =~ /inet addr:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
813
- iface[cint][:addresses] ||= Mash.new
814
- iface[cint][:addresses][$1] = { "family" => "inet" }
815
- tmp_addr = $1
816
- end
817
- if line =~ %r{inet6 addr: ([a-f0-9\:]+)/(\d+) Scope:(\w+)}
818
- iface[cint][:addresses] ||= Mash.new
819
- iface[cint][:addresses][$1] = { "family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("Host") ? "Node" : $3) }
820
- end
821
- if line =~ /Bcast:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
822
- iface[cint][:addresses][tmp_addr]["broadcast"] = $1
823
- end
824
- if line =~ /Mask:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
825
- iface[cint][:addresses][tmp_addr]["netmask"] = $1
826
- end
827
- flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|RUNNING|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)\s/)
828
- if flags.length > 1
829
- iface[cint][:flags] = flags.flatten
830
- end
831
- if line =~ /MTU:(\d+)/
832
- iface[cint][:mtu] = $1
833
- end
834
- if line =~ /P-t-P:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
835
- iface[cint][:peer] = $1
836
- end
837
- if line =~ /RX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) frame:(\d+)/
838
- net_counters[cint] ||= Mash.new
839
- net_counters[cint][:rx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "frame" => $5 }
840
- end
841
- if line =~ /TX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) carrier:(\d+)/
842
- net_counters[cint][:tx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "carrier" => $5 }
843
- end
844
- if line =~ /collisions:(\d+)/
845
- net_counters[cint][:tx]["collisions"] = $1
846
- end
847
- if line =~ /txqueuelen:(\d+)/
848
- net_counters[cint][:tx]["queuelen"] = $1
849
- end
850
- if line =~ /RX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
851
- net_counters[cint][:rx]["bytes"] = $1
852
- end
853
- if line =~ /TX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
854
- net_counters[cint][:tx]["bytes"] = $1
855
- end
856
- end
857
-
858
- so = shell_out("arp -an")
859
- so.stdout.lines do |line|
860
- if line =~ /^\S+ \((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\) at ([a-fA-F0-9\:]+) \[(\w+)\] on ([0-9a-zA-Z\.\:\-]+)/
861
- next unless iface[$4] # this should never happen
862
-
863
- iface[$4][:arp] ||= Mash.new
864
- iface[$4][:arp][$1] = $2.downcase
865
- end
866
- end
867
- end # end "ip else net-tools" block
868
-
869
- iface = ethernet_layer_one(iface)
870
- iface = ethernet_ring_parameters(iface)
871
- iface = ethernet_channel_parameters(iface)
872
- iface = ethernet_coalesce_parameters(iface)
873
- iface = ethernet_offload_parameters(iface)
874
- iface = ethernet_driver_info(iface)
875
- iface = ethernet_pause_parameters(iface)
876
- counters[:network][:interfaces] = net_counters
877
- network["interfaces"] = iface
878
- end
879
- end
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Adam Jacob (<adam@chef.io>)
4
+ # Author:: Chris Read <chris.read@gmail.com>
5
+ # Copyright:: Copyright (c) Chef Software Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ Ohai.plugin(:Network) do
22
+ provides "network", "network/interfaces"
23
+ provides "counters/network", "counters/network/interfaces"
24
+ provides "ipaddress", "ip6address", "macaddress"
25
+
26
+ def linux_encaps_lookup(encap)
27
+ return "Loopback" if encap.eql?("Local Loopback") || encap.eql?("loopback")
28
+ return "PPP" if encap.eql?("Point-to-Point Protocol")
29
+ return "SLIP" if encap.eql?("Serial Line IP")
30
+ return "VJSLIP" if encap.eql?("VJ Serial Line IP")
31
+ return "IPIP" if encap.eql?("IPIP Tunnel")
32
+ return "6to4" if encap.eql?("IPv6-in-IPv4")
33
+ return "Ethernet" if encap.eql?("ether")
34
+
35
+ encap
36
+ end
37
+
38
+ def ipv6_enabled?
39
+ file_exist? "/proc/net/if_inet6"
40
+ end
41
+
42
+ def ethtool_binary_path
43
+ @ethtool ||= which("ethtool")
44
+ end
45
+
46
+ def is_openvz?
47
+ @openvz ||= file_directory?("/proc/vz")
48
+ end
49
+
50
+ def is_openvz_host?
51
+ is_openvz? && file_directory?("/proc/bc")
52
+ end
53
+
54
+ def extract_neighbors(family, iface, neigh_attr)
55
+ so = shell_out("ip -f #{family[:name]} neigh show")
56
+ so.stdout.lines do |line|
57
+ if line =~ /^([a-f0-9\:\.]+)\s+dev\s+([^\s]+)\s+lladdr\s+([a-fA-F0-9\:]+)/
58
+ interface = iface[$2]
59
+ unless interface
60
+ logger.warn("neighbor list has entries for unknown interface #{interface}")
61
+ next
62
+ end
63
+ interface[neigh_attr] ||= Mash.new
64
+ interface[neigh_attr][$1] = $3.downcase
65
+ end
66
+ end
67
+ iface
68
+ end
69
+
70
+ # checking the routing tables
71
+ # why ?
72
+ # 1) to set the default gateway and default interfaces attributes
73
+ # 2) on some occasions, the best way to select node[:ipaddress] is to look at
74
+ # the routing table source field.
75
+ # 3) and since we're at it, let's populate some :routes attributes
76
+ # (going to do that for both inet and inet6 addresses)
77
+ def check_routing_table(family, iface, default_route_table)
78
+ so = shell_out("ip -o -f #{family[:name]} route show table #{default_route_table}")
79
+ so.stdout.lines do |line|
80
+ line.strip!
81
+ logger.trace("Plugin Network: Parsing #{line}")
82
+ if line.include?("\\")
83
+ # If we have multipath routing, then the first part will be a normal
84
+ # looking route:
85
+ # default proto ra metric 1024 <other options>
86
+ # Each successive part after that is a hop without those options.
87
+ # So the first thing we do is grab that first part, and split it into
88
+ # the route destination ("default"), and the route options.
89
+ parts = line.split("\\")
90
+ route_dest, dest_opts = parts.first.split(nil, 2)
91
+ # Then all the route endings, generally just nexthops.
92
+ route_endings = parts[1..]
93
+ if dest_opts && !dest_opts.empty?
94
+ # Route options like proto, metric, etc. only appear once for each
95
+ # multipath configuration. Prepend this information to the route
96
+ # endings so the code below will assign the fields properly.
97
+ route_endings.map! { |e| e.include?("nexthop") ? "#{dest_opts} #{e}" : e }
98
+ end
99
+ elsif line =~ /^([^\s]+)\s(.*)$/
100
+ route_dest = $1
101
+ route_endings = [$2]
102
+ else
103
+ next
104
+ end
105
+ route_endings.each do |route_ending|
106
+ route_entry = Mash.new(destination: route_dest,
107
+ family: family[:name])
108
+ route_int = nil
109
+ if route_ending =~ /\bdev\s+([^\s]+)\b/
110
+ route_int = $1
111
+ end
112
+ # does any known interface own the src address?
113
+ # we try to infer the interface/device from its address if it isn't specified
114
+ # we want to override the interface set via nexthop but only if possible
115
+ if line =~ /\bsrc\s+([^\s]+)\b/ && (!route_int || line.include?("nexthop"))
116
+ # only clobber previously set route_int if we find a match
117
+ if (match = iface.select { |name, intf| intf.fetch("addresses", {}).any? { |addr, _| addr == $1 } }.keys.first)
118
+ route_int = match
119
+ route_entry[:inferred] = true
120
+ end
121
+ end
122
+
123
+ unless route_int
124
+ logger.trace("Plugin Network: Skipping route entry without a device: '#{line}'")
125
+ next
126
+ end
127
+ route_int = "venet0:0" if is_openvz? && !is_openvz_host? && route_int == "venet0" && iface["venet0:0"]
128
+
129
+ unless iface[route_int]
130
+ logger.trace("Plugin Network: Skipping previously unseen interface from 'ip route show': #{route_int}")
131
+ next
132
+ end
133
+
134
+ %w{via scope metric proto src}.each do |k|
135
+ # http://rubular.com/r/pwTNp65VFf
136
+ route_entry[k] = $1 if route_ending =~ /\b#{k}\s+([^\s]+)/
137
+ end
138
+ # https://rubular.com/r/k1sMrRn5yLjgVi
139
+ route_entry["via"] = $1 if route_ending =~ /\bvia\s+inet6\s+([^\s]+)/
140
+
141
+ # a sanity check, especially for Linux-VServer, OpenVZ and LXC:
142
+ # don't report the route entry if the src address isn't set on the node
143
+ # unless the interface has no addresses of this type at all
144
+ if route_entry[:src]
145
+ addr = iface[route_int][:addresses]
146
+ unless addr.nil? || addr.key?(route_entry[:src]) ||
147
+ addr.values.all? { |a| a["family"] != family[:name] }
148
+ logger.trace("Plugin Network: Skipping route entry whose src does not match the interface IP")
149
+ next
150
+ end
151
+ end
152
+
153
+ iface[route_int][:routes] = [] unless iface[route_int][:routes]
154
+ iface[route_int][:routes] << route_entry
155
+ end
156
+ end
157
+ iface
158
+ end
159
+
160
+ # now looking at the routes to set the default attributes
161
+ # for information, default routes can be of this form :
162
+ # - default via 10.0.2.4 dev br0
163
+ # - default dev br0 scope link
164
+ # - default dev eth0 scope link src 1.1.1.1
165
+ # - default via 10.0.3.1 dev eth1 src 10.0.3.2 metric 10
166
+ # - default via 10.0.4.1 dev eth2 src 10.0.4.2 metric 20
167
+
168
+ # using a temporary var to hold routes and their interface name
169
+ def parse_routes(family, iface)
170
+ iface.filter_map do |i, iv|
171
+ next unless iv[:routes]
172
+
173
+ iv[:routes].filter_map do |r|
174
+ r.merge(dev: i) if r[:family] == family[:name]
175
+ end
176
+ end.flatten
177
+ end
178
+
179
+ # determine layer 1 details for the interface using ethtool
180
+ def ethernet_layer_one(iface)
181
+ return iface unless ethtool_binary_path
182
+
183
+ keys = %w{Speed Duplex Port Transceiver Auto-negotiation MDI-X}
184
+ iface.each_key do |tmp_int|
185
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
186
+
187
+ so = shell_out("#{ethtool_binary_path} #{tmp_int}")
188
+ so.stdout.lines do |line|
189
+ line.chomp!
190
+ logger.trace("Plugin Network: Parsing ethtool output: #{line}")
191
+ line.lstrip!
192
+ k, v = line.split(": ")
193
+ next unless keys.include? k
194
+
195
+ k.downcase!.tr!("-", "_")
196
+ if k == "speed"
197
+ k = "link_speed" # This is not necessarily the maximum speed the NIC supports
198
+ v = v[/\d+/].to_i
199
+ end
200
+ iface[tmp_int][k] = v
201
+ end
202
+ end
203
+ iface
204
+ end
205
+
206
+ # determine ring parameters for the interface using ethtool
207
+ def ethernet_ring_parameters(iface)
208
+ return iface unless ethtool_binary_path
209
+
210
+ iface.each_key do |tmp_int|
211
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
212
+
213
+ so = shell_out("#{ethtool_binary_path} -g #{tmp_int}")
214
+ logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
215
+ type = nil
216
+ iface[tmp_int]["ring_params"] = {}
217
+ so.stdout.lines.each do |line|
218
+ next if line.start_with?("Ring parameters for")
219
+ next if line.strip.nil?
220
+
221
+ if line.include?("Pre-set maximums")
222
+ type = "max"
223
+ next
224
+ end
225
+ if line.include?("Current hardware settings")
226
+ type = "current"
227
+ next
228
+ end
229
+ key, val = line.split(/:\s+/)
230
+ if type && val
231
+ ring_key = "#{type}_#{key.downcase.tr(" ", "_")}"
232
+ iface[tmp_int]["ring_params"][ring_key] = val.to_i
233
+ end
234
+ end
235
+ end
236
+ iface
237
+ end
238
+
239
+ # determine channel parameters for the interface using ethtool
240
+ def ethernet_channel_parameters(iface)
241
+ return iface unless ethtool_binary_path
242
+
243
+ iface.each_key do |tmp_int|
244
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
245
+
246
+ so = shell_out("#{ethtool_binary_path} -l #{tmp_int}")
247
+ logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
248
+ type = nil
249
+ iface[tmp_int]["channel_params"] = {}
250
+ so.stdout.lines.each do |line|
251
+ next if line.start_with?("Channel parameters for")
252
+ next if line.strip.nil?
253
+
254
+ if line.include?("Pre-set maximums")
255
+ type = "max"
256
+ next
257
+ end
258
+ if line.include?("Current hardware settings")
259
+ type = "current"
260
+ next
261
+ end
262
+ key, val = line.split(/:\s+/)
263
+ if type && val
264
+ channel_key = "#{type}_#{key.downcase.tr(" ", "_")}"
265
+ iface[tmp_int]["channel_params"][channel_key] = val.to_i
266
+ end
267
+ end
268
+ end
269
+ iface
270
+ end
271
+
272
+ # determine coalesce parameters for the interface using ethtool
273
+ def ethernet_coalesce_parameters(iface)
274
+ return iface unless ethtool_binary_path
275
+
276
+ iface.each_key do |tmp_int|
277
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
278
+
279
+ so = shell_out("#{ethtool_binary_path} -c #{tmp_int}")
280
+ logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
281
+ iface[tmp_int]["coalesce_params"] = {}
282
+ so.stdout.lines.each do |line|
283
+ next if line.start_with?("Coalesce parameters for")
284
+ next if line.strip.nil?
285
+
286
+ if line.start_with?("Adaptive")
287
+ _, adaptive_rx, _, adaptive_tx = line.split(/:\s+|\s+TX|\n/)
288
+ iface[tmp_int]["coalesce_params"]["adaptive_rx"] = adaptive_rx
289
+ iface[tmp_int]["coalesce_params"]["adaptive_tx"] = adaptive_tx
290
+ next
291
+ end
292
+ key, val = line.split(/:\s+/)
293
+ if val
294
+ coalesce_key = key.downcase.tr(" ", "_").to_s
295
+ iface[tmp_int]["coalesce_params"][coalesce_key] = val.to_i
296
+ end
297
+ end
298
+ end
299
+ iface
300
+ end
301
+
302
+ # determine offload features for the interface using ethtool
303
+ def ethernet_offload_parameters(iface)
304
+ return iface unless ethtool_binary_path
305
+
306
+ iface.each_key do |tmp_int|
307
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
308
+
309
+ so = shell_out("#{ethtool_binary_path} -k #{tmp_int}")
310
+ Ohai::Log.debug("Plugin Network: Parsing ethtool output: #{so.stdout}")
311
+ iface[tmp_int]["offload_params"] = {}
312
+ so.stdout.lines.each do |line|
313
+ next if line.start_with?("Features for")
314
+ next if line.strip.nil?
315
+
316
+ key, val = line.split(/:\s+/)
317
+ if val
318
+ offload_key = key.downcase.strip.tr(" ", "_").to_s
319
+ iface[tmp_int]["offload_params"][offload_key] = val.downcase.gsub(/\[.*\]/, "").strip.to_s
320
+ end
321
+ end
322
+ end
323
+ iface
324
+ end
325
+
326
+ # determine pause parameters for the interface using ethtool
327
+ def ethernet_pause_parameters(iface)
328
+ return iface unless ethtool_binary_path
329
+
330
+ iface.each_key do |tmp_int|
331
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
332
+
333
+ so = shell_out("#{ethtool_binary_path} -a #{tmp_int}")
334
+ logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
335
+ iface[tmp_int]["pause_params"] = {}
336
+ so.stdout.lines.each do |line|
337
+ next if line.start_with?("Pause parameters for")
338
+ next if line.strip.nil?
339
+
340
+ key, val = line.split(/:\s+/)
341
+ if val
342
+ pause_key = "#{key.downcase.tr(" ", "_")}"
343
+ iface[tmp_int]["pause_params"][pause_key] = val.strip.eql? "on"
344
+ end
345
+ end
346
+ end
347
+ iface
348
+ end
349
+
350
+ # determine driver info for the interface using ethtool
351
+ def ethernet_driver_info(iface)
352
+ return iface unless ethtool_binary_path
353
+
354
+ iface.each_key do |tmp_int|
355
+ next unless iface[tmp_int][:encapsulation] == "Ethernet"
356
+
357
+ so = shell_out("#{ethtool_binary_path} -i #{tmp_int}")
358
+ logger.trace("Plugin Network: Parsing ethtool output: #{so.stdout}")
359
+ iface[tmp_int]["driver_info"] = {}
360
+ so.stdout.lines.each do |line|
361
+ next if line.strip.nil?
362
+
363
+ key, val = line.split(/:\s+/)
364
+ if val.nil?
365
+ val = ""
366
+ end
367
+ driver_key = key.downcase.tr(" ", "_").to_s
368
+ iface[tmp_int]["driver_info"][driver_key] = val.chomp
369
+ end
370
+ end
371
+ iface
372
+ end
373
+
374
+ # determine link stats, vlans, queue length, and state for an interface using ip
375
+ def link_statistics(iface, net_counters)
376
+ so = shell_out("ip -d -s link")
377
+ tmp_int = nil
378
+ on_rx = true
379
+ xdp_mode = nil
380
+ so.stdout.lines do |line|
381
+ if line =~ IPROUTE_INT_REGEX
382
+ tmp_int = $2
383
+ iface[tmp_int] ||= Mash.new
384
+ net_counters[tmp_int] ||= Mash.new
385
+ end
386
+
387
+ if /^\s+(ip6tnl|ipip)/.match?(line)
388
+ iface[tmp_int][:tunnel_info] = {}
389
+ words = line.split
390
+ words.each_with_index do |word, index|
391
+ case word
392
+ when "external"
393
+ iface[tmp_int][:tunnel_info][word] = true
394
+ when "any", "ipip6", "ip6ip6"
395
+ iface[tmp_int][:tunnel_info][:proto] = word
396
+ when "remote",
397
+ "local",
398
+ "encaplimit",
399
+ "hoplimit",
400
+ "tclass",
401
+ "flowlabel",
402
+ "addrgenmode",
403
+ "numtxqueues",
404
+ "numrxqueues",
405
+ "gso_max_size",
406
+ "gso_max_segs"
407
+ iface[tmp_int][:tunnel_info][word] = words[index + 1]
408
+ end
409
+ end
410
+ end
411
+
412
+ if line =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
413
+ int = on_rx ? :rx : :tx
414
+ net_counters[tmp_int][int] ||= Mash.new
415
+ net_counters[tmp_int][int][:bytes] = $1
416
+ net_counters[tmp_int][int][:packets] = $2
417
+ net_counters[tmp_int][int][:errors] = $3
418
+ net_counters[tmp_int][int][:drop] = $4
419
+ if int == :rx
420
+ net_counters[tmp_int][int][:overrun] = $5
421
+ else
422
+ net_counters[tmp_int][int][:carrier] = $5
423
+ net_counters[tmp_int][int][:collisions] = $6
424
+ end
425
+
426
+ on_rx = !on_rx
427
+ end
428
+
429
+ if line =~ /qlen (\d+)/
430
+ net_counters[tmp_int][:tx] ||= Mash.new
431
+ net_counters[tmp_int][:tx][:queuelen] = $1
432
+ end
433
+
434
+ if line =~ /vlan id (\d+)/ || line =~ /vlan protocol ([\w\.]+) id (\d+)/
435
+ if $2
436
+ tmp_prot = $1
437
+ tmp_id = $2
438
+ else
439
+ tmp_id = $1
440
+ end
441
+ iface[tmp_int][:vlan] ||= Mash.new
442
+ iface[tmp_int][:vlan][:id] = tmp_id
443
+ iface[tmp_int][:vlan][:protocol] = tmp_prot if tmp_prot
444
+
445
+ vlan_flags = line.scan(/(REORDER_HDR|GVRP|LOOSE_BINDING)/)
446
+ if vlan_flags.length > 0
447
+ iface[tmp_int][:vlan][:flags] = vlan_flags.flatten.uniq
448
+ end
449
+ end
450
+
451
+ # https://rubular.com/r/JRp6lNANmpcLV5
452
+ if line =~ /\sstate (\w+)/
453
+ iface[tmp_int]["state"] = $1.downcase
454
+ end
455
+
456
+ if line.include?("xdp")
457
+ mode = line.scan(/\s(xdp|xdpgeneric|xdpoffload|xdpmulti)\s/).flatten[0]
458
+ # Fetches and sets the mode from the first line.
459
+ unless mode.nil?
460
+ iface[tmp_int][:xdp] = {}
461
+ if mode.eql?("xdp")
462
+ # In case of xdpdrv, mode is xdp,
463
+ # to keep it consistent, keeping mode as xdpdrv.
464
+ mode = "xdpdrv"
465
+ end
466
+ xdp_mode = mode
467
+ iface[tmp_int][:xdp][:attached] = []
468
+ end
469
+
470
+ if line =~ %r{prog/(\w+) id (\d+) tag (\w+)}
471
+ mode = $1.eql?("xdp") ? xdp_mode : $1
472
+ iface[tmp_int][:xdp][:attached] << {
473
+ mode: mode,
474
+ id: $2,
475
+ tag: $3,
476
+ }
477
+ end
478
+ end
479
+ end
480
+ iface
481
+ end
482
+
483
+ def match_iproute(iface, line, cint)
484
+ if line =~ IPROUTE_INT_REGEX
485
+ cint = $2
486
+ iface[cint] = Mash.new
487
+ if cint =~ /^(\w+?)(\d+.*)/
488
+ iface[cint][:type] = $1
489
+ iface[cint][:number] = $2
490
+ end
491
+
492
+ if line =~ /mtu (\d+)/
493
+ iface[cint][:mtu] = $1
494
+ end
495
+
496
+ flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|LOWER_UP|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)/)
497
+ if flags.length > 1
498
+ iface[cint][:flags] = flags.flatten.uniq
499
+ end
500
+ end
501
+ cint
502
+ end
503
+
504
+ def parse_ip_addr(iface)
505
+ so = shell_out("ip addr")
506
+ cint = nil
507
+ so.stdout.lines do |line|
508
+ cint = match_iproute(iface, line, cint)
509
+
510
+ parse_ip_addr_link_line(cint, iface, line)
511
+ cint = parse_ip_addr_inet_line(cint, iface, line)
512
+ parse_ip_addr_inet6_line(cint, iface, line)
513
+ end
514
+ end
515
+
516
+ def parse_ip_addr_link_line(cint, iface, line)
517
+ if line =~ %r{link/(\w+) ([\da-f\:]+) }
518
+ iface[cint][:encapsulation] = linux_encaps_lookup($1)
519
+ unless $2 == "00:00:00:00:00:00"
520
+ iface[cint][:addresses] ||= Mash.new
521
+ iface[cint][:addresses][$2.upcase] = { "family" => "lladdr" }
522
+ end
523
+ end
524
+ end
525
+
526
+ def parse_ip_addr_inet_line(cint, iface, line)
527
+ if line =~ %r{inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(/(\d{1,2}))?}
528
+ tmp_addr, tmp_prefix = $1, $3
529
+ tmp_prefix ||= "32"
530
+ original_int = nil
531
+
532
+ # Are we a formerly aliased interface?
533
+ if line =~ /#{cint}:(\d+)$/
534
+ sub_int = $1
535
+ alias_int = "#{cint}:#{sub_int}"
536
+ original_int = cint
537
+ cint = alias_int
538
+ end
539
+
540
+ iface[cint] ||= Mash.new # Create the fake alias interface if needed
541
+ iface[cint][:addresses] ||= Mash.new
542
+ iface[cint][:addresses][tmp_addr] = { "family" => "inet", "prefixlen" => tmp_prefix }
543
+ iface[cint][:addresses][tmp_addr][:netmask] = IPAddr.new("255.255.255.255").mask(tmp_prefix.to_i).to_s
544
+
545
+ if line =~ /peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
546
+ iface[cint][:addresses][tmp_addr][:peer] = $1
547
+ end
548
+
549
+ if line =~ /brd (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
550
+ iface[cint][:addresses][tmp_addr][:broadcast] = $1
551
+ end
552
+
553
+ if line =~ /scope (\w+)/
554
+ iface[cint][:addresses][tmp_addr][:scope] = ($1.eql?("host") ? "Node" : $1.capitalize)
555
+ end
556
+
557
+ # If we found we were an alias interface, restore cint to its original value
558
+ cint = original_int unless original_int.nil?
559
+ end
560
+ cint
561
+ end
562
+
563
+ def parse_ip_addr_inet6_line(cint, iface, line)
564
+ if line =~ %r{inet6 ([a-f0-9\:]+)/(\d+) scope (\w+)( .*)?}
565
+ iface[cint][:addresses] ||= Mash.new
566
+ tmp_addr = $1
567
+ tags = $4 || ""
568
+ tags = tags.split
569
+
570
+ iface[cint][:addresses][tmp_addr] = {
571
+ "family" => "inet6",
572
+ "prefixlen" => $2,
573
+ "scope" => ($3.eql?("host") ? "Node" : $3.capitalize),
574
+ "tags" => tags,
575
+ }
576
+ end
577
+ end
578
+
579
+ # returns the macaddress for interface from a hash of interfaces (iface elsewhere in this file)
580
+ def get_mac_for_interface(interfaces, interface)
581
+ interfaces[interface][:addresses].find { |k, v| v["family"] == "lladdr" }.first unless interfaces[interface][:addresses].nil? || interfaces[interface][:flags].include?("NOARP")
582
+ end
583
+
584
+ # returns the default route with the lowest metric (unspecified metric is 0)
585
+ def choose_default_route(routes)
586
+ routes.select do |r|
587
+ r[:destination] == "default"
588
+ end.min do |x, y|
589
+ (x[:metric].nil? ? 0 : x[:metric].to_i) <=> (y[:metric].nil? ? 0 : y[:metric].to_i)
590
+ end
591
+ end
592
+
593
+ def interface_has_no_addresses_in_family?(iface, family)
594
+ return true if iface[:addresses].nil?
595
+
596
+ iface[:addresses].values.all? { |addr| addr["family"] != family }
597
+ end
598
+
599
+ def interface_have_address?(iface, address)
600
+ return false if iface[:addresses].nil?
601
+
602
+ iface[:addresses].key?(address)
603
+ end
604
+
605
+ def interface_address_not_link_level?(iface, address)
606
+ !(iface[:addresses][address][:scope].casecmp("link") == 0)
607
+ end
608
+
609
+ def interface_valid_for_route?(iface, address, family)
610
+ return true if interface_has_no_addresses_in_family?(iface, family)
611
+
612
+ interface_have_address?(iface, address) && interface_address_not_link_level?(iface, address)
613
+ end
614
+
615
+ def route_is_valid_default_route?(route, default_route)
616
+ # if the route destination is a default route, it's good
617
+ return true if route[:destination] == "default"
618
+
619
+ return false if default_route[:via].nil?
620
+
621
+ dest_ipaddr = IPAddr.new(route[:destination])
622
+ default_route_via = IPAddr.new(default_route[:via])
623
+
624
+ # check if nexthop is the same address family
625
+ return false if dest_ipaddr.ipv4? != default_route_via.ipv4?
626
+
627
+ # the default route has a gateway and the route matches the gateway
628
+ dest_ipaddr.include?(default_route_via)
629
+ end
630
+
631
+ # ipv4/ipv6 routes are different enough that having a single algorithm to select the favored route for both creates unnecessary complexity
632
+ # this method attempts to deduce the route that is most important to the user, which is later used to deduce the favored values for {ip,mac,ip6}address
633
+ # we only consider routes that are default routes, or those routes that get us to the gateway for a default route
634
+ def favored_default_route_linux(routes, iface, default_route, family)
635
+ routes.select do |r|
636
+ if family[:name] == "inet"
637
+ # the route must have a source address
638
+ next if r[:src].nil? || r[:src].empty?
639
+
640
+ # the interface specified in the route must exist
641
+ route_interface = iface[r[:dev]]
642
+ next if route_interface.nil? # the interface specified in the route must exist
643
+
644
+ # the interface must have no addresses, or if it has the source address, the address must not
645
+ # be a link-level address
646
+ next unless interface_valid_for_route?(route_interface, r[:src], "inet")
647
+
648
+ # the route must either be a default route, or it must have a gateway which is accessible via the route
649
+ next unless route_is_valid_default_route?(r, default_route)
650
+
651
+ true
652
+ elsif family[:name] == "inet6"
653
+ iface[r[:dev]] &&
654
+ iface[r[:dev]][:state] == "up" &&
655
+ route_is_valid_default_route?(r, default_route)
656
+ end
657
+ end.min_by do |r|
658
+ # sorting the selected routes:
659
+ # - getting default routes first
660
+ # - then sort by metric
661
+ # - then sort by if the device was inferred or not (preferring explicit to inferred)
662
+ # - then by prefixlen
663
+ [
664
+ r[:destination] == "default" ? 0 : 1,
665
+ r[:metric].nil? ? 0 : r[:metric].to_i,
666
+ r[:inferred] ? 1 : 0,
667
+ # for some reason IPAddress doesn't accept "::/0", it doesn't like prefix==0
668
+ # just a quick workaround: use 0 if IPAddress fails
669
+ begin
670
+ IPAddress( r[:destination] == "default" ? family[:default_route] : r[:destination] ).prefix
671
+ rescue
672
+ 0
673
+ end,
674
+ ]
675
+ end
676
+ end
677
+
678
+ # Both the network plugin and this plugin (linux/network) are run on linux. This plugin runs first.
679
+ # If the 'ip' binary is available, this plugin may set {ip,mac,ip6}address. The network plugin should not overwrite these.
680
+ # The older code section below that relies on the deprecated net-tools, e.g. netstat and ifconfig, provides less functionality.
681
+ collect_data(:linux) do
682
+ require "ipaddr" unless defined?(IPAddr)
683
+
684
+ iface = Mash.new
685
+ net_counters = Mash.new
686
+
687
+ network Mash.new unless network
688
+ network[:interfaces] ||= Mash.new
689
+ counters Mash.new unless counters
690
+ counters[:network] ||= Mash.new
691
+
692
+ # ohai.plugin[:network][:default_route_table] = 'default'
693
+ if configuration(:default_route_table).nil? || configuration(:default_route_table).empty?
694
+ default_route_table = "main"
695
+ else
696
+ default_route_table = configuration(:default_route_table)
697
+ end
698
+ logger.trace("Plugin Network: default route table is '#{default_route_table}'")
699
+
700
+ # Match the lead line for an interface from iproute2
701
+ # 3: eth0.11@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
702
+ # The '@eth0:' portion doesn't exist on primary interfaces and thus is optional in the regex
703
+ IPROUTE_INT_REGEX ||= /^(\d+): ([0-9a-zA-Z@:\.\-_]*?)(@[0-9a-zA-Z\-_]+|):\s/.freeze
704
+
705
+ if which("ip")
706
+ # families to get default routes from
707
+ families = [{
708
+ name: "inet",
709
+ default_route: "0.0.0.0/0",
710
+ default_prefix: :default,
711
+ neighbour_attribute: :arp,
712
+ }]
713
+
714
+ if ipv6_enabled?
715
+ families << {
716
+ name: "inet6",
717
+ default_route: "::/0",
718
+ default_prefix: :default_inet6,
719
+ neighbour_attribute: :neighbour_inet6,
720
+ }
721
+ end
722
+
723
+ parse_ip_addr(iface)
724
+
725
+ iface = link_statistics(iface, net_counters)
726
+
727
+ families.each do |family|
728
+ neigh_attr = family[:neighbour_attribute]
729
+ default_prefix = family[:default_prefix]
730
+
731
+ iface = extract_neighbors(family, iface, neigh_attr)
732
+
733
+ iface = check_routing_table(family, iface, default_route_table)
734
+
735
+ routes = parse_routes(family, iface)
736
+
737
+ default_route = choose_default_route(routes)
738
+
739
+ if default_route.nil? || default_route.empty?
740
+ attribute_name = if family[:name] == "inet"
741
+ "default_interface"
742
+ else
743
+ "default_#{family[:name]}_interface"
744
+ end
745
+ logger.trace("Plugin Network: Unable to determine '#{attribute_name}' as no default routes were found for that interface family")
746
+ else
747
+ network["#{default_prefix}_interface"] = default_route[:dev]
748
+ logger.trace("Plugin Network: #{default_prefix}_interface set to #{default_route[:dev]}")
749
+
750
+ # setting gateway to 0.0.0.0 or :: if the default route is a link level one
751
+ network["#{default_prefix}_gateway"] = default_route[:via] || family[:default_route].chomp("/0")
752
+ logger.trace("Plugin Network: #{default_prefix}_gateway set to #{network["#{default_prefix}_gateway"]}")
753
+
754
+ # deduce the default route the user most likely cares about to pick {ip,mac,ip6}address below
755
+ favored_route = favored_default_route_linux(routes, iface, default_route, family)
756
+
757
+ # FIXME: This entire block should go away, and the network plugin should be the sole source of {ip,ip6,mac}address
758
+
759
+ # since we're at it, let's populate {ip,mac,ip6}address with the best values
760
+ # if we don't set these, the network plugin may set them afterwards
761
+ if favored_route && !favored_route.empty?
762
+ if family[:name] == "inet"
763
+ ipaddress favored_route[:src]
764
+ m = get_mac_for_interface(iface, favored_route[:dev])
765
+ logger.trace("Plugin Network: Overwriting macaddress #{macaddress} with #{m} from interface #{favored_route[:dev]}") if macaddress
766
+ macaddress m
767
+ elsif family[:name] == "inet6"
768
+ # this rarely does anything since we rarely have src for ipv6, so this usually falls back on the network plugin
769
+ ip6address favored_route[:src]
770
+ if macaddress
771
+ logger.trace("Plugin Network: Not setting macaddress from ipv6 interface #{favored_route[:dev]} because macaddress is already set")
772
+ else
773
+ macaddress get_mac_for_interface(iface, favored_route[:dev])
774
+ end
775
+ end
776
+ else
777
+ logger.trace("Plugin Network: Unable to deduce the favored default route for family '#{family[:name]}' despite finding a default route, and is not setting ipaddress/ip6address/macaddress. the network plugin may provide fallbacks.")
778
+ logger.trace("Plugin Network: This potential default route was excluded: #{default_route}")
779
+ end
780
+ end
781
+ end # end families.each
782
+ else # ip binary not available, falling back to net-tools, e.g. route, ifconfig
783
+ begin
784
+ so = shell_out("route -n")
785
+ route_result = so.stdout.split($/).grep( /^0.0.0.0/ )[0].split( /[ \t]+/ )
786
+ network[:default_gateway], network[:default_interface] = route_result.values_at(1, 7)
787
+ rescue Ohai::Exceptions::Exec
788
+ logger.trace("Plugin Network: Unable to determine default interface")
789
+ end
790
+
791
+ so = shell_out("ifconfig -a")
792
+ cint = nil
793
+ so.stdout.lines do |line|
794
+ tmp_addr = nil
795
+ # dev_valid_name in the kernel only excludes slashes, nulls, spaces
796
+ # http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=net/core/dev.c#l851
797
+ if line =~ /^([0-9a-zA-Z@\.\:\-_]+)\s+/
798
+ cint = $1
799
+ iface[cint] = Mash.new
800
+ if cint =~ /^(\w+?)(\d+.*)/
801
+ iface[cint][:type] = $1
802
+ iface[cint][:number] = $2
803
+ end
804
+ end
805
+ if line =~ /Link encap:(Local Loopback)/ || line =~ /Link encap:(.+?)\s/
806
+ iface[cint][:encapsulation] = linux_encaps_lookup($1)
807
+ end
808
+ if line =~ /HWaddr (.+?)\s/
809
+ iface[cint][:addresses] ||= Mash.new
810
+ iface[cint][:addresses][$1] = { "family" => "lladdr" }
811
+ end
812
+ if line =~ /inet addr:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
813
+ iface[cint][:addresses] ||= Mash.new
814
+ iface[cint][:addresses][$1] = { "family" => "inet" }
815
+ tmp_addr = $1
816
+ end
817
+ if line =~ %r{inet6 addr: ([a-f0-9\:]+)/(\d+) Scope:(\w+)}
818
+ iface[cint][:addresses] ||= Mash.new
819
+ iface[cint][:addresses][$1] = { "family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("Host") ? "Node" : $3) }
820
+ end
821
+ if line =~ /Bcast:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
822
+ iface[cint][:addresses][tmp_addr]["broadcast"] = $1
823
+ end
824
+ if line =~ /Mask:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
825
+ iface[cint][:addresses][tmp_addr]["netmask"] = $1
826
+ end
827
+ flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|RUNNING|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)\s/)
828
+ if flags.length > 1
829
+ iface[cint][:flags] = flags.flatten
830
+ end
831
+ if line =~ /MTU:(\d+)/
832
+ iface[cint][:mtu] = $1
833
+ end
834
+ if line =~ /P-t-P:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
835
+ iface[cint][:peer] = $1
836
+ end
837
+ if line =~ /RX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) frame:(\d+)/
838
+ net_counters[cint] ||= Mash.new
839
+ net_counters[cint][:rx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "frame" => $5 }
840
+ end
841
+ if line =~ /TX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) carrier:(\d+)/
842
+ net_counters[cint][:tx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "carrier" => $5 }
843
+ end
844
+ if line =~ /collisions:(\d+)/
845
+ net_counters[cint][:tx]["collisions"] = $1
846
+ end
847
+ if line =~ /txqueuelen:(\d+)/
848
+ net_counters[cint][:tx]["queuelen"] = $1
849
+ end
850
+ if line =~ /RX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
851
+ net_counters[cint][:rx]["bytes"] = $1
852
+ end
853
+ if line =~ /TX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
854
+ net_counters[cint][:tx]["bytes"] = $1
855
+ end
856
+ end
857
+
858
+ so = shell_out("arp -an")
859
+ so.stdout.lines do |line|
860
+ if line =~ /^\S+ \((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\) at ([a-fA-F0-9\:]+) \[(\w+)\] on ([0-9a-zA-Z\.\:\-]+)/
861
+ next unless iface[$4] # this should never happen
862
+
863
+ iface[$4][:arp] ||= Mash.new
864
+ iface[$4][:arp][$1] = $2.downcase
865
+ end
866
+ end
867
+ end # end "ip else net-tools" block
868
+
869
+ iface = ethernet_layer_one(iface)
870
+ iface = ethernet_ring_parameters(iface)
871
+ iface = ethernet_channel_parameters(iface)
872
+ iface = ethernet_coalesce_parameters(iface)
873
+ iface = ethernet_offload_parameters(iface)
874
+ iface = ethernet_driver_info(iface)
875
+ iface = ethernet_pause_parameters(iface)
876
+ counters[:network][:interfaces] = net_counters
877
+ network["interfaces"] = iface
878
+ end
879
+ end