Please review pull request #107: Add `defaultroute' fact, improve `ipaddress' fact opened by (syskill)

Description:

Parse netstat output to determine the default route and its associated network
interface. Using this information, we can determine the "real" IP address of a
multi-homed host more accurately.

  • Opened: Wed Dec 07 22:42:11 UTC 2011
  • Based on: puppetlabs:master (0f9a595c7224a0b5566262647914f52c5c879447)
  • Requested merge: syskill:master (7abe5be5d628c3eb249d41df38f6fa8cd8196c0e)

Diff follows:

diff --git a/lib/facter/defaultroute.rb b/lib/facter/defaultroute.rb
new file mode 100644
index 0000000..6a7c691
--- /dev/null
+++ b/lib/facter/defaultroute.rb
@@ -0,0 +1,61 @@
+require 'facter/util/ip'
+require 'facter/util/netstat'
+require 'ipaddr'
+
+# Fact: defaultroute
+#
+# Purpose: Return the default route for a host.
+#
+# Resolution:
+#   Runs netstat, and returns the gateway associated with the destination
+#   "default" or "0.0.0.0".
+#
+# Caveats:
+#
+
+Facter.add(:defaultroute) do
+  confine :kernel => Facter::Util::NetStat.supported_platforms
+  setcode do
+    Facter::Util::NetStat.get_route_value('default', 'gw') ||
+    Facter::Util::NetStat.get_route_value('0.0.0.0', 'gw')
+  end
+end
+
+# Fact: defaultroute_interface
+#
+# Purpose: Return the interface uses for the host's default route.
+#
+# Resolution:
+#   Runs netstat, and returns the interface associated with the route for the
+#   destination "default" or "0.0.0.0".
+#
+#   If the default route listing only includes the gateway and not the
+#   interface (as is the case on Solaris), return the first interface whose
+#   network range includes the default gateway.
+#
+# Caveats:
+#
+
+Facter.add(:defaultroute_interface) do
+  confine :kernel => Facter::Util::NetStat.supported_platforms
+  setcode do
+    Facter::Util::NetStat.get_route_value('default', 'iface') ||
+    Facter::Util::NetStat.get_route_value('0.0.0.0', 'iface')
+  end
+end
+
+Facter.add(:defaultroute_interface) do
+  confine :kernel => Facter::Util::IP.supported_platforms
+  setcode do
+    return nil unless defaultroute = Facter.value(:defaultroute)
+    gw = IPAddr.new(defaultroute)
+
+    Facter::Util::IP.get_interfaces.collect { |i| Facter::Util::IP.alphafy(i) }.
+    detect do |i| 
+      range = Facter.value('network_' + i) +
+              '/' +
+              Facter.value('netmask_' + i)
+      IPAddr.new(range).include?(gw)
+    end
+  end
+end
diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb
index 360becd..a50d3ca 100644
--- a/lib/facter/ipaddress.rb
+++ b/lib/facter/ipaddress.rb
@@ -3,6 +3,9 @@
 # Purpose: Return the main IP address for a host.
 #
 # Resolution:
+#   As a first resort, if the interface for the default route could be
+#   determined (cf. defaultroute.rb), return its IP address.
+#
 #   On the Unixes does an ifconfig, and returns the first non 127.0.0.0/8
 #   subnetted IP it finds.
 #   On Windows, it attempts to use the socket library and resolve the machine's
@@ -23,6 +26,17 @@
 #
 
 Facter.add(:ipaddress) do
+  setcode do
+    interface = Facter.value(:defaultroute_interface)
+    if interface.nil?
+      nil
+    else
+      Facter.value('ipaddress_' + interface)
+    end
+  end
+end
+
+Facter.add(:ipaddress) do
   confine :kernel => :linux
   setcode do
     ip = nil
diff --git a/lib/facter/util/netstat.rb b/lib/facter/util/netstat.rb
new file mode 100644
index 0000000..7a9741a
--- /dev/null
+++ b/lib/facter/util/netstat.rb
@@ -0,0 +1,69 @@
+module Facter::Util::NetStat
+  COLUMN_MAP = {
+    :bsd     => {
+      :aliases => [:sunos, :freebsd, :netbsd, :darwin],
+      :dest    => 0,
+      :gw      => 1,
+      :iface   => 5
+    },
+    :linux   => {
+      :dest   => 0,
+      :gw     => 1,
+      :iface  => 7
+    },
+    :openbsd => {
+      :dest   => 0,
+      :gw     => 1,
+      :iface  => 6
+    }
+  }
+
+  def self.supported_platforms
+    COLUMN_MAP.inject([]) do |result, tmp|
+      key, map = tmp
+      if map[:aliases]
+        result += map[:aliases]
+      else
+        result << key
+      end
+      result
+    end
+  end
+
+  def self.get_ipv4_output
+    output = ""
+    case Facter.value(:kernel)
+    when 'SunOS', 'FreeBSD', 'NetBSD', 'OpenBSD'
+      output = %x{/usr/bin/netstat -rn -f inet}
+    when 'Darwin' 
+      output = %x{/usr/sbin/netstat -rn -f inet}
+    when 'Linux'
+      output = %x{/bin/netstat -rn -A inet}
+    end
+    output
+  end
+
+  def self.get_route_value(route, label)
+    tmp1 = []
+
+    kernel = Facter.value(:kernel).downcase.to_sym
+
+    # If it's not directly in the map or aliased in the map, then we don't know how to deal with it.
+    unless map = COLUMN_MAP[kernel] || COLUMN_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) }
+      return nil
+    end
+
+    c1 = map[:dest]
+    c2 = map[label.to_sym]
+
+    get_ipv4_output.to_a.collect { |s| s.split}.each { |a|
+      if a[c1] == route
+        tmp1 << a[c2]
+      end
+    }
+
+    if tmp1
+      return tmp1.shift
+    end
+  end
+end

    

--
You received this message because you are subscribed to the Google Groups "Puppet Developers" group.
To post to this group, send email to puppet-dev@googlegroups.com.
To unsubscribe from this group, send email to puppet-dev+unsubscr...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to