PageRenderTime 63ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/ohai/plugins/linux/network.rb

https://github.com/jmanero/ohai
Ruby | 412 lines | 307 code | 47 blank | 58 comment | 65 complexity | 3c9f72c07400d408129ba32292099692 MD5 | raw file
Possible License(s): Apache-2.0
  1. #
  2. # Author:: Adam Jacob (<adam@opscode.com>)
  3. # Copyright:: Copyright (c) 2008 Opscode, Inc.
  4. # License:: Apache License, Version 2.0
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. require 'ipaddr'
  19. provides "network", "counters/network"
  20. def encaps_lookup(encap)
  21. return "Loopback" if encap.eql?("Local Loopback") || encap.eql?("loopback")
  22. return "PPP" if encap.eql?("Point-to-Point Protocol")
  23. return "SLIP" if encap.eql?("Serial Line IP")
  24. return "VJSLIP" if encap.eql?("VJ Serial Line IP")
  25. return "IPIP" if encap.eql?("IPIP Tunnel")
  26. return "6to4" if encap.eql?("IPv6-in-IPv4")
  27. return "Ethernet" if encap.eql?("ether")
  28. encap
  29. end
  30. iface = Mash.new
  31. net_counters = Mash.new
  32. # Match the lead line for an interface from iproute2
  33. # 3: eth0.11@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
  34. # The '@eth0:' portion doesn't exist on primary interfaces and thus is optional in the regex
  35. IPROUTE_INT_REGEX = /^(\d+): ([0-9a-zA-Z@:\.\-_]*?)(@[0-9a-zA-Z]+|):\s/
  36. if File.exist?("/sbin/ip")
  37. # families to get default routes from
  38. families = [
  39. {
  40. :name => "inet",
  41. :default_route => "0.0.0.0/0",
  42. :default_prefix => :default,
  43. :neighbour_attribute => :arp
  44. },
  45. {
  46. :name => "inet6",
  47. :default_route => "::/0",
  48. :default_prefix => :default_inet6,
  49. :neighbour_attribute => :neighbour_inet6
  50. }
  51. ]
  52. popen4("ip addr") do |pid, stdin, stdout, stderr|
  53. stdin.close
  54. cint = nil
  55. stdout.each do |line|
  56. if line =~ IPROUTE_INT_REGEX
  57. cint = $2
  58. iface[cint] = Mash.new
  59. if cint =~ /^(\w+)(\d+.*)/
  60. iface[cint][:type] = $1
  61. iface[cint][:number] = $2
  62. end
  63. if line =~ /mtu (\d+)/
  64. iface[cint][:mtu] = $1
  65. end
  66. flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|LOWER_UP|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)/)
  67. if flags.length > 1
  68. iface[cint][:flags] = flags.flatten.uniq
  69. end
  70. end
  71. if line =~ /link\/(\w+) ([\da-f\:]+) /
  72. iface[cint][:encapsulation] = encaps_lookup($1)
  73. unless $2 == "00:00:00:00:00:00"
  74. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  75. iface[cint][:addresses][$2.upcase] = { "family" => "lladdr" }
  76. end
  77. end
  78. if line =~ /inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/(\d{1,2}))?/
  79. tmp_addr, tmp_prefix = $1, $3
  80. tmp_prefix ||= "32"
  81. original_int = nil
  82. # Are we a formerly aliased interface?
  83. if line =~ /#{cint}:(\d+)$/
  84. sub_int = $1
  85. alias_int = "#{cint}:#{sub_int}"
  86. original_int = cint
  87. cint = alias_int
  88. end
  89. iface[cint] = Mash.new unless iface[cint] # Create the fake alias interface if needed
  90. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  91. iface[cint][:addresses][tmp_addr] = { "family" => "inet", "prefixlen" => tmp_prefix }
  92. iface[cint][:addresses][tmp_addr][:netmask] = IPAddr.new("255.255.255.255").mask(tmp_prefix.to_i).to_s
  93. if line =~ /peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  94. iface[cint][:addresses][tmp_addr][:peer] = $1
  95. end
  96. if line =~ /brd (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  97. iface[cint][:addresses][tmp_addr][:broadcast] = $1
  98. end
  99. if line =~ /scope (\w+)/
  100. iface[cint][:addresses][tmp_addr][:scope] = ($1.eql?("host") ? "Node" : $1.capitalize)
  101. end
  102. # If we found we were an an alias interface, restore cint to its original value
  103. cint = original_int unless original_int.nil?
  104. end
  105. if line =~ /inet6 ([a-f0-9\:]+)\/(\d+) scope (\w+)/
  106. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  107. tmp_addr = $1
  108. iface[cint][:addresses][tmp_addr] = { "family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("host") ? "Node" : $3.capitalize) }
  109. end
  110. end
  111. end
  112. popen4("ip -d -s link") do |pid, stdin, stdout, stderr|
  113. stdin.close
  114. tmp_int = nil
  115. on_rx = true
  116. stdout.each do |line|
  117. if line =~ IPROUTE_INT_REGEX
  118. tmp_int = $2
  119. net_counters[tmp_int] = Mash.new unless net_counters[tmp_int]
  120. end
  121. if line =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
  122. int = on_rx ? :rx : :tx
  123. net_counters[tmp_int][int] = Mash.new unless net_counters[tmp_int][int]
  124. net_counters[tmp_int][int][:bytes] = $1
  125. net_counters[tmp_int][int][:packets] = $2
  126. net_counters[tmp_int][int][:errors] = $3
  127. net_counters[tmp_int][int][:drop] = $4
  128. if(int == :rx)
  129. net_counters[tmp_int][int][:overrun] = $5
  130. else
  131. net_counters[tmp_int][int][:carrier] = $5
  132. net_counters[tmp_int][int][:collisions] = $6
  133. end
  134. on_rx = !on_rx
  135. end
  136. if line =~ /qlen (\d+)/
  137. net_counters[tmp_int][:tx] = Mash.new unless net_counters[tmp_int][:tx]
  138. net_counters[tmp_int][:tx][:queuelen] = $1
  139. end
  140. if line =~ /vlan id (\d+)/
  141. tmp_id = $1
  142. iface[tmp_int][:vlan] = Mash.new unless iface[tmp_int][:vlan]
  143. iface[tmp_int][:vlan][:id] = tmp_id
  144. vlan_flags = line.scan(/(REORDER_HDR|GVRP|LOOSE_BINDING)/)
  145. if vlan_flags.length > 0
  146. iface[tmp_int][:vlan][:flags] = vlan_flags.flatten.uniq
  147. end
  148. end
  149. if line =~ /state (\w+)/
  150. iface[tmp_int]['state'] = $1.downcase
  151. end
  152. end
  153. end
  154. families.each do |family|
  155. neigh_attr = family[:neighbour_attribute]
  156. default_prefix = family[:default_prefix]
  157. popen4("ip -f #{family[:name]} neigh show") do |pid, stdin, stdout, stderr|
  158. stdin.close
  159. stdout.each do |line|
  160. if line =~ /^([a-f0-9\:\.]+)\s+dev\s+([^\s]+)\s+lladdr\s+([a-fA-F0-9\:]+)/
  161. unless iface[$2]
  162. Ohai::Log.warn("neighbour list has entries for unknown interface #{iface[$2]}")
  163. next
  164. end
  165. iface[$2][neigh_attr] = Mash.new unless iface[$2][neigh_attr]
  166. iface[$2][neigh_attr][$1] = $3.downcase
  167. end
  168. end
  169. end
  170. # checking the routing tables
  171. # why ?
  172. # 1) to set the default gateway and default interfaces attributes
  173. # 2) on some occasions, the best way to select node[:ipaddress] is to look at
  174. # the routing table source field.
  175. # 3) and since we're at it, let's populate some :routes attributes
  176. # (going to do that for both inet and inet6 addresses)
  177. popen4("ip -f #{family[:name]} route show") do |pid, stdin, stdout, stderr|
  178. stdin.close
  179. stdout.each do |line|
  180. if line =~ /^([^\s]+)\s(.*)$/
  181. route_dest = $1
  182. route_ending = $2
  183. #
  184. if route_ending =~ /\bdev\s+([^\s]+)\b/
  185. route_int = $1
  186. else
  187. Ohai::Log.debug("Skipping route entry without a device: '#{line}'")
  188. next
  189. end
  190. unless iface[route_int]
  191. Ohai::Log.debug("Skipping previously unseen interface from 'ip route show': #{route_int}")
  192. next
  193. end
  194. route_entry = Mash.new( :destination => route_dest,
  195. :family => family[:name] )
  196. %w[via scope metric proto src].each do |k|
  197. route_entry[k] = $1 if route_ending =~ /\b#{k}\s+([^\s]+)\b/
  198. end
  199. # a sanity check, especially for Linux-VServer, OpenVZ and LXC:
  200. # don't report the route entry if the src address isn't set on the node
  201. next if route_entry[:src] and not iface[route_int][:addresses].has_key? route_entry[:src]
  202. iface[route_int][:routes] = Array.new unless iface[route_int][:routes]
  203. iface[route_int][:routes] << route_entry
  204. end
  205. end
  206. end
  207. # now looking at the routes to set the default attributes
  208. # for information, default routes can be of this form :
  209. # - default via 10.0.2.4 dev br0
  210. # - default dev br0 scope link
  211. # - default via 10.0.3.1 dev eth1 src 10.0.3.2 metric 10
  212. # - default via 10.0.4.1 dev eth2 src 10.0.4.2 metric 20
  213. # using a temporary var to hold routes and their interface name
  214. routes = iface.collect do |i,iv|
  215. iv[:routes].collect do |r|
  216. r.merge(:dev=>i) if r[:family] == family[:name]
  217. end.compact if iv[:routes]
  218. end.compact.flatten
  219. # using a temporary var to hold the default route
  220. # in case there are more than 1 default route, sort it by its metric
  221. # and return the first one
  222. # (metric value when unspecified is 0)
  223. default_route = routes.select do |r|
  224. r[:destination] == "default"
  225. end.sort do |x,y|
  226. (x[:metric].nil? ? 0 : x[:metric].to_i) <=> (y[:metric].nil? ? 0 : y[:metric].to_i)
  227. end.first
  228. if default_route.nil? or default_route.empty?
  229. Ohai::Log.debug("Unable to determine default #{family[:name]} interface")
  230. else
  231. network["#{default_prefix}_interface"] = default_route[:dev]
  232. Ohai::Log.debug("#{default_prefix}_interface set to #{default_route[:dev]}")
  233. # setting gateway to 0.0.0.0 or :: if the default route is a link level one
  234. network["#{default_prefix}_gateway"] = default_route[:via] ? default_route[:via] : family[:default_route].chomp("/0")
  235. Ohai::Log.debug("#{default_prefix}_gateway set to #{network["#{default_prefix}_gateway"]}")
  236. # since we're at it, let's populate {ip,mac,ip6}address with the best values
  237. # using the source field when it's specified :
  238. # 1) in the default route
  239. # 2) in the route entry used to reach the default gateway
  240. route = routes.select do |r|
  241. # selecting routes
  242. r[:src] and # it has a src field
  243. iface[r[:dev]] and # the iface exists
  244. iface[r[:dev]][:addresses].has_key? r[:src] and # the src ip is set on the node
  245. iface[r[:dev]][:addresses][r[:src]][:scope].downcase != "link" and # this isn't a link level addresse
  246. ( r[:destination] == "default" or
  247. ( default_route[:via] and # the default route has a gateway
  248. IPAddress(r[:destination]).include? IPAddress(default_route[:via]) # the route matches the gateway
  249. )
  250. )
  251. end.sort_by do |r|
  252. # sorting the selected routes:
  253. # - getting default routes first
  254. # - then sort by metric
  255. # - then by prefixlen
  256. [
  257. r[:destination] == "default" ? 0 : 1,
  258. r[:metric].nil? ? 0 : r[:metric].to_i,
  259. # for some reason IPAddress doesn't accept "::/0", it doesn't like prefix==0
  260. # just a quick workaround: use 0 if IPAddress fails
  261. begin
  262. IPAddress( r[:destination] == "default" ? family[:default_route] : r[:destination] ).prefix
  263. rescue
  264. 0
  265. end
  266. ]
  267. end.first
  268. unless route.nil? or route.empty?
  269. if family[:name] == "inet"
  270. ipaddress route[:src]
  271. macaddress iface[route[:dev]][:addresses].select{|k,v| v["family"]=="lladdr"}.first.first unless iface[route[:dev]][:flags].include? "NOARP"
  272. else
  273. ip6address route[:src]
  274. end
  275. end
  276. end
  277. end
  278. else
  279. begin
  280. route_result = from("route -n \| grep -m 1 ^0.0.0.0").split(/[ \t]+/)
  281. network[:default_gateway], network[:default_interface] = route_result.values_at(1,7)
  282. rescue Ohai::Exceptions::Exec
  283. Ohai::Log.debug("Unable to determine default interface")
  284. end
  285. popen4("ifconfig -a") do |pid, stdin, stdout, stderr|
  286. stdin.close
  287. cint = nil
  288. stdout.each do |line|
  289. tmp_addr = nil
  290. # dev_valid_name in the kernel only excludes slashes, nulls, spaces
  291. # http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=net/core/dev.c#l851
  292. if line =~ /^([0-9a-zA-Z@\.\:\-_]+)\s+/
  293. cint = $1
  294. iface[cint] = Mash.new
  295. if cint =~ /^(\w+)(\d+.*)/
  296. iface[cint][:type] = $1
  297. iface[cint][:number] = $2
  298. end
  299. end
  300. if line =~ /Link encap:(Local Loopback)/ || line =~ /Link encap:(.+?)\s/
  301. iface[cint][:encapsulation] = encaps_lookup($1)
  302. end
  303. if line =~ /HWaddr (.+?)\s/
  304. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  305. iface[cint][:addresses][$1] = { "family" => "lladdr" }
  306. end
  307. if line =~ /inet addr:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  308. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  309. iface[cint][:addresses][$1] = { "family" => "inet" }
  310. tmp_addr = $1
  311. end
  312. if line =~ /inet6 addr: ([a-f0-9\:]+)\/(\d+) Scope:(\w+)/
  313. iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
  314. iface[cint][:addresses][$1] = { "family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("Host") ? "Node" : $3) }
  315. end
  316. if line =~ /Bcast:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  317. iface[cint][:addresses][tmp_addr]["broadcast"] = $1
  318. end
  319. if line =~ /Mask:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  320. iface[cint][:addresses][tmp_addr]["netmask"] = $1
  321. end
  322. flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|RUNNING|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)\s/)
  323. if flags.length > 1
  324. iface[cint][:flags] = flags.flatten
  325. end
  326. if line =~ /MTU:(\d+)/
  327. iface[cint][:mtu] = $1
  328. end
  329. if line =~ /P-t-P:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
  330. iface[cint][:peer] = $1
  331. end
  332. if line =~ /RX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) frame:(\d+)/
  333. net_counters[cint] = Mash.new unless net_counters[cint]
  334. net_counters[cint][:rx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "frame" => $5 }
  335. end
  336. if line =~ /TX packets:(\d+) errors:(\d+) dropped:(\d+) overruns:(\d+) carrier:(\d+)/
  337. net_counters[cint][:tx] = { "packets" => $1, "errors" => $2, "drop" => $3, "overrun" => $4, "carrier" => $5 }
  338. end
  339. if line =~ /collisions:(\d+)/
  340. net_counters[cint][:tx]["collisions"] = $1
  341. end
  342. if line =~ /txqueuelen:(\d+)/
  343. net_counters[cint][:tx]["queuelen"] = $1
  344. end
  345. if line =~ /RX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
  346. net_counters[cint][:rx]["bytes"] = $1
  347. end
  348. if line =~ /TX bytes:(\d+) \((\d+?\.\d+ .+?)\)/
  349. net_counters[cint][:tx]["bytes"] = $1
  350. end
  351. end
  352. end
  353. popen4("arp -an") do |pid, stdin, stdout, stderr|
  354. stdin.close
  355. stdout.each do |line|
  356. 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\.\:\-]+)/
  357. next unless iface[$4] # this should never happen
  358. iface[$4][:arp] = Mash.new unless iface[$4][:arp]
  359. iface[$4][:arp][$1] = $2.downcase
  360. end
  361. end
  362. end
  363. end
  364. counters[:network][:interfaces] = net_counters
  365. network["interfaces"] = iface