[Pkg-puppet-devel] [facter] 29/61: (#16668) Use WMI to collect IPv4 address
Stig Sandbeck Mathisen
ssm at debian.org
Mon Nov 4 15:01:54 UTC 2013
This is an automated email from the git hooks/post-receive script.
ssm pushed a commit to branch master
in repository facter.
commit 0a8c231b4269a838eecf58f7ec0b0103be2d456b
Author: Rob Reynolds <rob at puppetlabs.com>
Date: Wed Jul 24 16:59:22 2013 -0700
(#16668) Use WMI to collect IPv4 address
Previously, we were calling `IPSocket.getaddress(Socket.gethostname)` to
resolve the `ipaddress` fact on Windows, but using WMI-based methods
to resolve other IP related facts, e.g. interfaces. This led to inconsistent
information -- ipaddress from one interface, netmask from another.
This commit introduces a `get_preferred_network_adapters` method that
returns an ordered list of WIN32OLE objects that wrap
Win32_NetworkAdapterConfiguration objects. The list is ordered by the
IPConnectionMetric. If two interfaces have the same metric, e.g. host with
multiple NICs, then the order is based on the adapter bindings[1].
It is possible for a network adapter configuration to contain IPv4 and/or
IPv6 addresses, but only the former will be considered valid for the
ipaddress fact.
[1] http://support.microsoft.com/kb/894564
---
lib/facter/ipaddress.rb | 15 +++++-
lib/facter/util/ip/windows.rb | 107 +++++++++++++++++++++++++++++++++++++++++
spec/unit/ipaddress_spec.rb | 90 ++++++++++++++++++++++++++++++++++
3 files changed, 210 insertions(+), 2 deletions(-)
diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb
index b9475fd..97ed464 100644
--- a/lib/facter/ipaddress.rb
+++ b/lib/facter/ipaddress.rb
@@ -105,9 +105,20 @@ end
Facter.add(:ipaddress) do
confine :kernel => %w{windows}
+
setcode do
- require 'socket'
- IPSocket.getaddress(Socket.gethostname)
+ require 'facter/util/ip/windows'
+ ipaddr = nil
+
+ adapters = Facter::Util::IP::Windows.get_preferred_network_adapters
+ adapters.find do |nic|
+ nic.IPAddress.any? do |addr|
+ ipaddr = addr if Facter::Util::IP::Windows.valid_ipv4_address?(addr)
+ ipaddr
+ end
+ end
+
+ ipaddr
end
end
diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb
new file mode 100644
index 0000000..11ce6ec
--- /dev/null
+++ b/lib/facter/util/ip/windows.rb
@@ -0,0 +1,107 @@
+# encoding: UTF-8
+
+require 'facter/util/wmi'
+
+class Facter::Util::IP::Windows
+ # The path to netsh.exe.
+ #
+ # @return [String]
+ #
+ # @api private
+ NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe"
+
+ # The WMI query used to return ip information
+ #
+ # @return [String]
+ #
+ # @api private
+ WMI_IP_INFO_QUERY = 'SELECT Description, ServiceName, IPAddress, IPConnectionMetric, InterfaceIndex, Index, IPSubnet, MACAddress, MTU, SettingID FROM Win32_NetworkAdapterConfiguration WHERE IPConnectionMetric IS NOT NULL AND IPEnabled = TRUE'
+
+ def self.to_s
+ 'windows'
+ end
+
+ # Windows doesn't display netmask in hex.
+ #
+ # @return [Boolean] false by default
+ #
+ # @api private
+ def self.convert_netmask_from_hex?
+ false
+ end
+
+ # Uses netsh.exe to obtain a list of interfaces.
+ #
+ # @return [Array]
+ #
+ # @api private
+ def self.interfaces
+ cmd = "#{NETSH} interface %s show interface"
+ output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s
+
+ output.scan(/\s* connected\s*(\S.*)/).flatten.uniq
+ end
+
+ # Executes a wmi query that returns ip information and returns for each item in the results
+ #
+ # @return [Win32OLE] objects
+ #
+ # @api private
+ def self.exec_wmi_ip_query(&block)
+ Facter::Util::WMI.execquery(WMI_IP_INFO_QUERY).each do |nic|
+ yield nic
+ end
+ end
+
+ # Gets a list of active adapters and sorts by the lowest connection metric (aka best weight) and MACAddress to ensure order
+ #
+ # @return [Win32OLE]
+ #
+ # @api private
+ def self.get_preferred_network_adapters
+ network_adapters = []
+
+ self.exec_wmi_ip_query do |nic|
+ network_adapters << nic
+ end
+
+ require 'facter/util/registry'
+ bindings = {}
+
+ Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Linkage','Bind').each_with_index do |entry, index|
+ match_data = entry.match(/\\Device\\(\{.*\})/)
+ unless match_data.nil?
+ bindings[match_data[1]] = index
+ end
+ end
+
+ network_adapters.sort do |nic_left,nic_right|
+ cmp = nic_left.IPConnectionMetric <=> nic_right.IPConnectionMetric
+ if cmp == 0
+ bindings[nic_left.SettingID] <=> bindings[nic_right.SettingID]
+ else
+ cmp
+ end
+ end
+ end
+
+ # Determines if the value passed in is a valid ipv4 address.
+ #
+ # @param ip_address [String]
+ #
+ # @return [String] or [NilClass]
+ #
+ # @api private
+ def self.valid_ipv4_address?(ip_address)
+ String(ip_address).scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/).each do |match|
+ # excluding 169.254.x.x in Windows - this is the DHCP APIPA
+ # meaning that if the node cannot get an ip address from the dhcp server,
+ # it auto-assigns a private ip address
+ unless match == "127.0.0.1" or match =~ /^169.254.*/
+ return match
+ end
+ end
+
+ nil
+ end
+end
diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb
index 85f9aa6..62cbbc2 100755
--- a/spec/unit/ipaddress_spec.rb
+++ b/spec/unit/ipaddress_spec.rb
@@ -40,4 +40,94 @@ describe "ipaddress fact" do
end
end
end
+
+ context "on Windows" do
+ require 'facter/util/wmi'
+ require 'facter/util/registry'
+ require 'facter/util/ip/windows'
+
+ let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' }
+ let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' }
+ let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] }
+
+ before :each do
+ Facter.fact(:kernel).stubs(:value).returns(:windows)
+ Facter.fact(:kernelrelease).stubs(:value).returns('6.1.7601')
+ Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings)
+ end
+
+ it "should do what when VPN is turned on?"
+
+ context "when you have no active network adapter" do
+ it "should return nil if there are no active (or any) network adapters" do
+ Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY).returns([])
+ Facter::Util::Resolution.stubs(:exec)
+
+ Facter.value(:ipaddress).should == nil
+ end
+ end
+
+ context "when you have one network adapter" do
+ it "should return the ip address properly" do
+ network1 = mock('network1')
+ network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"])
+ Facter::Util::WMI.expects(:execquery).returns([network1])
+
+ Facter.value(:ipaddress).should == "12.123.12.12"
+ end
+ end
+
+ context "when you have more than one network adapter" do
+ it "should return the ip of the adapter with the lowest IP connection metric (best connection)" do
+ network1 = mock('network1')
+ network1.expects(:IPConnectionMetric).returns(10)
+ network2 = mock('network2')
+ network2.expects(:IPConnectionMetric).returns(5)
+ network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"])
+ Facter::Util::WMI.expects(:execquery).returns([network1, network2])
+
+ Facter.value(:ipaddress).should == "12.123.12.13"
+ end
+
+ it "should return the ip of the adapter with the lowest IP connection metric (best connection) that has ipv4 enabled" do
+ network1 = mock('network1')
+ network1.expects(:IPConnectionMetric).returns(10)
+ network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"])
+ network2 = mock('network2')
+ network2.expects(:IPConnectionMetric).returns(5)
+ network2.expects(:IPAddress).returns(["2013:0:4137:9e76:2087:77a:53ef:7527"])
+ Facter::Util::WMI.expects(:execquery).returns([network1, network2])
+
+ Facter.value(:ipaddress).should == "12.123.12.12"
+ end
+
+ context "when the IP connection metric is the same" do
+ it "should return the ip of the adapter with the lowest binding order" do
+ network1 = mock('network1')
+ network1.expects(:SettingID).returns(settingId0)
+ network1.expects(:IPConnectionMetric).returns(5)
+ network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"])
+ network2 = mock('network2')
+ network2.expects(:SettingID).returns(settingId1)
+ network2.expects(:IPConnectionMetric).returns(5)
+ Facter::Util::WMI.expects(:execquery).returns([network1, network2])
+
+ Facter.value(:ipaddress).should == "12.123.12.12"
+ end
+
+ it "should return the ip of the adapter with the lowest binding order even if the adapter is not first" do
+ network1 = mock('network1')
+ network1.expects(:IPConnectionMetric).returns(5)
+ network1.expects(:SettingID).returns(settingId1)
+ network2 = mock('network2')
+ network2.expects(:IPConnectionMetric).returns(5)
+ network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"])
+ network2.expects(:SettingID).returns(settingId0)
+ Facter::Util::WMI.expects(:execquery).returns([network1, network2])
+
+ Facter.value(:ipaddress).should == "12.123.12.13"
+ end
+ end
+ end
+ end
end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-puppet/facter.git
More information about the Pkg-puppet-devel
mailing list