ohai 18.0.26 → 18.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) 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 +86 -86
  17. data/lib/ohai/mixin/azure_metadata.rb +111 -111
  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 +256 -256
  24. data/lib/ohai/mixin/gce_metadata.rb +83 -83
  25. data/lib/ohai/mixin/http_helper.rb +64 -64
  26. data/lib/ohai/mixin/network_helper.rb +65 -65
  27. data/lib/ohai/mixin/oci_metadata.rb +69 -0
  28. data/lib/ohai/mixin/os.rb +128 -128
  29. data/lib/ohai/mixin/scaleway_metadata.rb +51 -51
  30. data/lib/ohai/mixin/seconds_to_human.rb +52 -52
  31. data/lib/ohai/mixin/shell_out.rb +51 -51
  32. data/lib/ohai/mixin/softlayer_metadata.rb +74 -74
  33. data/lib/ohai/mixin/string.rb +31 -31
  34. data/lib/ohai/mixin/train_helpers.rb +36 -36
  35. data/lib/ohai/mixin/which.rb +39 -39
  36. data/lib/ohai/plugin_config.rb +47 -47
  37. data/lib/ohai/plugins/aix/kernel.rb +50 -50
  38. data/lib/ohai/plugins/aix/memory.rb +37 -37
  39. data/lib/ohai/plugins/aix/network.rb +142 -142
  40. data/lib/ohai/plugins/aix/platform.rb +30 -30
  41. data/lib/ohai/plugins/aix/uptime.rb +54 -54
  42. data/lib/ohai/plugins/aix/virtualization.rb +154 -154
  43. data/lib/ohai/plugins/alibaba.rb +72 -72
  44. data/lib/ohai/plugins/azure.rb +154 -154
  45. data/lib/ohai/plugins/bsd/virtualization.rb +121 -121
  46. data/lib/ohai/plugins/c.rb +178 -178
  47. data/lib/ohai/plugins/chef.rb +50 -50
  48. data/lib/ohai/plugins/cloud.rb +379 -357
  49. data/lib/ohai/plugins/command.rb +26 -26
  50. data/lib/ohai/plugins/cpu.rb +635 -635
  51. data/lib/ohai/plugins/darwin/hardware.rb +99 -99
  52. data/lib/ohai/plugins/darwin/memory.rb +62 -62
  53. data/lib/ohai/plugins/darwin/network.rb +207 -207
  54. data/lib/ohai/plugins/darwin/platform.rb +38 -38
  55. data/lib/ohai/plugins/darwin/virtualization.rb +90 -93
  56. data/lib/ohai/plugins/digital_ocean.rb +67 -67
  57. data/lib/ohai/plugins/dmi.rb +134 -134
  58. data/lib/ohai/plugins/docker.rb +58 -58
  59. data/lib/ohai/plugins/dragonflybsd/memory.rb +60 -60
  60. data/lib/ohai/plugins/dragonflybsd/network.rb +128 -128
  61. data/lib/ohai/plugins/dragonflybsd/platform.rb +28 -28
  62. data/lib/ohai/plugins/ec2.rb +148 -148
  63. data/lib/ohai/plugins/elixir.rb +36 -36
  64. data/lib/ohai/plugins/erlang.rb +60 -60
  65. data/lib/ohai/plugins/eucalyptus.rb +86 -86
  66. data/lib/ohai/plugins/filesystem.rb +751 -751
  67. data/lib/ohai/plugins/fips.rb +36 -36
  68. data/lib/ohai/plugins/freebsd/memory.rb +60 -60
  69. data/lib/ohai/plugins/freebsd/network.rb +128 -128
  70. data/lib/ohai/plugins/freebsd/platform.rb +28 -28
  71. data/lib/ohai/plugins/gce.rb +89 -89
  72. data/lib/ohai/plugins/go.rb +34 -34
  73. data/lib/ohai/plugins/groovy.rb +38 -38
  74. data/lib/ohai/plugins/grub2.rb +40 -40
  75. data/lib/ohai/plugins/habitat.rb +73 -73
  76. data/lib/ohai/plugins/haskell.rb +96 -96
  77. data/lib/ohai/plugins/hostname.rb +133 -133
  78. data/lib/ohai/plugins/init_package.rb +26 -26
  79. data/lib/ohai/plugins/java.rb +78 -78
  80. data/lib/ohai/plugins/kernel.rb +292 -292
  81. data/lib/ohai/plugins/keys.rb +27 -27
  82. data/lib/ohai/plugins/languages.rb +26 -26
  83. data/lib/ohai/plugins/libvirt.rb +114 -114
  84. data/lib/ohai/plugins/linode.rb +73 -73
  85. data/lib/ohai/plugins/linux/block_device.rb +48 -48
  86. data/lib/ohai/plugins/linux/hostnamectl.rb +34 -34
  87. data/lib/ohai/plugins/linux/interrupts.rb +84 -84
  88. data/lib/ohai/plugins/linux/ipc.rb +52 -52
  89. data/lib/ohai/plugins/linux/livepatch.rb +38 -38
  90. data/lib/ohai/plugins/linux/lsb.rb +46 -46
  91. data/lib/ohai/plugins/linux/lspci.rb +76 -76
  92. data/lib/ohai/plugins/linux/machineid.rb +36 -36
  93. data/lib/ohai/plugins/linux/mdadm.rb +120 -120
  94. data/lib/ohai/plugins/linux/memory.rb +106 -106
  95. data/lib/ohai/plugins/linux/network.rb +879 -879
  96. data/lib/ohai/plugins/linux/os_release.rb +38 -38
  97. data/lib/ohai/plugins/linux/platform.rb +314 -314
  98. data/lib/ohai/plugins/linux/selinux.rb +69 -69
  99. data/lib/ohai/plugins/linux/sessions.rb +54 -54
  100. data/lib/ohai/plugins/linux/sysctl.rb +39 -39
  101. data/lib/ohai/plugins/linux/systemd_paths.rb +36 -36
  102. data/lib/ohai/plugins/linux/tc.rb +61 -61
  103. data/lib/ohai/plugins/linux/virtualization.rb +300 -300
  104. data/lib/ohai/plugins/lua.rb +39 -39
  105. data/lib/ohai/plugins/mono.rb +50 -50
  106. data/lib/ohai/plugins/netbsd/memory.rb +99 -99
  107. data/lib/ohai/plugins/netbsd/network.rb +122 -122
  108. data/lib/ohai/plugins/netbsd/platform.rb +28 -28
  109. data/lib/ohai/plugins/network.rb +186 -186
  110. data/lib/ohai/plugins/nodejs.rb +40 -40
  111. data/lib/ohai/plugins/oci.rb +94 -0
  112. data/lib/ohai/plugins/ohai.rb +29 -29
  113. data/lib/ohai/plugins/ohai_time.rb +26 -26
  114. data/lib/ohai/plugins/openbsd/memory.rb +99 -99
  115. data/lib/ohai/plugins/openbsd/network.rb +122 -122
  116. data/lib/ohai/plugins/openbsd/platform.rb +28 -28
  117. data/lib/ohai/plugins/openstack.rb +84 -84
  118. data/lib/ohai/plugins/os.rb +55 -55
  119. data/lib/ohai/plugins/packages.rb +234 -234
  120. data/lib/ohai/plugins/passwd.rb +104 -104
  121. data/lib/ohai/plugins/perl.rb +45 -45
  122. data/lib/ohai/plugins/php.rb +52 -52
  123. data/lib/ohai/plugins/platform.rb +29 -29
  124. data/lib/ohai/plugins/powershell.rb +82 -82
  125. data/lib/ohai/plugins/ps.rb +35 -35
  126. data/lib/ohai/plugins/python.rb +43 -43
  127. data/lib/ohai/plugins/rackspace.rb +177 -177
  128. data/lib/ohai/plugins/root_group.rb +41 -41
  129. data/lib/ohai/plugins/rpm.rb +121 -121
  130. data/lib/ohai/plugins/ruby.rb +66 -66
  131. data/lib/ohai/plugins/rust.rb +34 -34
  132. data/lib/ohai/plugins/scala.rb +38 -38
  133. data/lib/ohai/plugins/scaleway.rb +58 -58
  134. data/lib/ohai/plugins/scsi.rb +52 -52
  135. data/lib/ohai/plugins/shard.rb +142 -142
  136. data/lib/ohai/plugins/shells.rb +32 -32
  137. data/lib/ohai/plugins/softlayer.rb +48 -48
  138. data/lib/ohai/plugins/solaris2/dmi.rb +191 -191
  139. data/lib/ohai/plugins/solaris2/memory.rb +32 -32
  140. data/lib/ohai/plugins/solaris2/network.rb +192 -192
  141. data/lib/ohai/plugins/solaris2/platform.rb +58 -58
  142. data/lib/ohai/plugins/solaris2/virtualization.rb +90 -90
  143. data/lib/ohai/plugins/ssh_host_key.rb +84 -84
  144. data/lib/ohai/plugins/sysconf.rb +46 -46
  145. data/lib/ohai/plugins/timezone.rb +45 -25
  146. data/lib/ohai/plugins/train.rb +35 -35
  147. data/lib/ohai/plugins/uptime.rb +95 -95
  148. data/lib/ohai/plugins/virtualbox.rb +197 -197
  149. data/lib/ohai/plugins/vmware.rb +109 -94
  150. data/lib/ohai/plugins/windows/dmi.rb +95 -95
  151. data/lib/ohai/plugins/windows/drivers.rb +52 -52
  152. data/lib/ohai/plugins/windows/memory.rb +39 -39
  153. data/lib/ohai/plugins/windows/network.rb +222 -222
  154. data/lib/ohai/plugins/windows/platform.rb +34 -34
  155. data/lib/ohai/plugins/windows/system_enclosure.rb +29 -29
  156. data/lib/ohai/plugins/windows/virtualization.rb +45 -45
  157. data/lib/ohai/plugins/zpools.rb +94 -94
  158. data/lib/ohai/provides_map.rb +208 -208
  159. data/lib/ohai/runner.rb +128 -128
  160. data/lib/ohai/system.rb +258 -258
  161. data/lib/ohai/train_transport.rb +29 -29
  162. data/lib/ohai/util/file_helper.rb +6 -6
  163. data/lib/ohai/util/ip_helper.rb +56 -56
  164. data/lib/ohai/util/win32.rb +47 -47
  165. data/lib/ohai/version.rb +23 -23
  166. data/lib/ohai.rb +23 -23
  167. data/ohai.gemspec +35 -35
  168. metadata +5 -3
@@ -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 /\\/.match?(line)
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 /Pre-set maximums/.match?(line)
222
- type = "max"
223
- next
224
- end
225
- if /Current hardware settings/.match?(line)
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 /Pre-set maximums/.match?(line)
255
- type = "max"
256
- next
257
- end
258
- if /Current hardware settings/.match?(line)
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