[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