On Nov 14, 2006, at 11:23 AM, Aaron Johnson wrote:
Hello --
Is there an operating system independent way in ruby to determine the
IP address currently bound to any local ethernet interface? I would
like a way to find it that does not require DNS lookup or any network
query for that matter. The equivalent of 'ifconfig' for unix and
'ipconfig' for windows wrapped up into one line of ruby that doesn't
use system(). This would help with some cross platform server
deployment issues.
I have attached a script which for the most part does that. It's
intended for the "Puppet" project but still needs a lot of work (and
a clear integration plan) to fill in loose gaps.
It does not parse windows output, but if someone can send me a sample
of the output in text form, I would be happy to integrate it.
It won't waste it's time generating junk parsing "funky" interfaces
(funky: interfaces that do not have an IP address, or a single IP
address). It will handle aliases. Currently, it's known to work with
Solaris, OpenBSD, FreeBSD, Linux, and OS X.
There is also a "IFConfig" project on rubyforge.... I honestly can't
remember /why/ we didn't choose that project to use in Puppet, but I
remember it was pretty important. Either way, you'll have two options
this way.
Usage is simple: Call "IFConfig.ifconfig". It will produce a hash of
"Interface" structs (noted at top) with all the information it can
find. The hash is keyed by the interface name.
By all means, let me know if you have any trouble with it, I would
love the opportunity to have this tested more thoroughly. :)
require 'facter'
require 'stringio'
Interface = Struct.new(
:af_type, :address, :broadcast, :netmask, :mtu,
:status, :mac
)
module IFConfig
def ifconfig
interfaces = { }
output = ""
case Facter["Kernel"].value
when "Solaris", "Linux", "Darwin", /BSD/
output = %x(/sbin/ifconfig -a)
else
fail "Couldn't figure out your system ifconfig"
end
unless $?.exitstatus == 0
fail "Error getting data from ifconfig"
end
sio = StringIO.new(output)
case Facter["Kernel"].value
when "Linux"
interfaces = linux(sio)
when "Darwin", /BSD/
interfaces = bsd(sio, Facter["Kernel"].value)
when "Solaris"
interfaces = solaris(sio)
end
return interfaces
end
module_function :ifconfig
def convert_hex_to_octet(hexmask)
hexmask.scan(/.{2}/).collect { |x| x.hex }.join(".")
end
def parse_af_lines(sio)
loop do
ch = sio.getc
if ch and (ch.chr == " " or ch == ?\t)
words = sio.readline.sub(/^\s+/, "").split(/\s+/)
yield words
else
sio.ungetc(ch) if ch
break
end
end
end
def linux(sio)
interfaces = { }
while(!sio.eof?) do
header = sio.readline
m = header.match(/^([^ ]+).*?HWaddr ([^ ]+)/)
# downed and special interfaces have a different syntax
m = header.match(/^([^ ]+)/) unless m
interface,
mac = m[1,2]
mtu = ""
status = ""
interfaces[interface] = []
parse_af_lines(sio) do |words|
if words[0] =~ /^inet/
my_int = Interface.new
interfaces[interface].push(my_int)
case words[0]
when "inet"
my_int.af_type = :inet
# linux chooses to insert bcast in the middle instead of append it like
# freebsd/solaris.
my_int.address = words[1].sub(/^addr:/, "")
my_int.broadcast = words[2] =~ /^Bcast:/ ? words[2].sub(/^Bcast:/, "") : nil
my_int.netmask = words[2] =~ /^Bcast:/ ? words[3].sub(/^Mask:/, "") : words[2].sub(/^Mask:/, "")
when "inet6"
my_int.af_type = :inet6
my_int.address,
my_int.netmask = words[2].split(/\//)
my_int.broadcast = nil
end
elsif words[0] =~ /^UP/
status = :active
mtu = words.find { |x| x =~ /^MTU/ }
mtu.sub!(/^MTU:/, "")
end
end
interfaces[interface].each do |x|
x.status = status.to_s.length ? :active : :down
x.mac = mac
x.mtu = mtu
if interface == "lo"
x.status = :active
x.mac = nil
end
end
sio.readline
end
return interfaces
end
module_function :linux
def bsd(sio, kernel)
interfaces = { }
while(!sio.eof?) do
header = sio.readline
m = header.match(/^([^:]+): .*?mtu (\d+)/)
status = ""
mac = ""
interface,
mtu = m[1,2]
interfaces[interface] = []
parse_af_lines(sio) do |words|
if words[0] =~ /^inet/
my_int = Interface.new
interfaces[interface].push(my_int)
my_int.af_type = case words[0]
when "inet"
:inet
when "inet6"
:inet6
end
# ipv6 addresses in bsd have the interface
# appended after a % sign
my_int.address = words[1].sub(/%.*$/, "")
if words[0] == "inet"
my_int.netmask = convert_hex_to_octet(words[3].sub(/^0x/, ""))
my_int.broadcast = words[5]
elsif words[0] == "inet6"
my_int.netmask = words[3]
end
elsif words[0] =~ /^address/
# openbsd puts the mac on it's own line
mac = words[1]
elsif words[0] =~ /^media/
# darwin does not have "ethernet" in the media: line
status = words[5]
elsif words[0] =~ /^ether/
mac = words[1]
elsif words[0] =~ /^status/
# freebsd puts the status on it's own line.
# this only works because the status line is the last one.
status = words[1]
end # if words
end # parse_af_lines
# update the mac, media, and duplex in the interfaces structures.
interfaces[interface].each do |x|
x.mac = mac
x.status = case status
when "active"
:active
when "inactive"
:down
end
x.mtu = mtu
# force the loopback to have certain states
if interface == "lo0"
x.status = :active
x.mac = nil
x.mtu = nil
end
end # interfaces each
end # while
return interfaces
end
module_function :bsd
def solaris(sio)
interfaces = { }
while (!sio.eof?)
header = sio.readline
m = header.match(/([^ ]+) (.*?)mtu (\d+)/)
interface = m[1].sub(/:$/, "")
mtu = m[3]
status = m[2] =~ /RUNNING/ ? :active : :down
mac = ""
interfaces[interface] ||= []
parse_af_lines(sio) do |words|
if words[0] =~ /^inet/
my_int = Interface.new
interfaces[interface].push(my_int)
case words[0]
when "inet"
my_int.af_type = :inet
my_int.address = words[1]
my_int.netmask = words[3]
my_int.broadcast = words[5]
when "inet6"
my_int.af_type = :inet6
my_int.address = words[1].sub(/\/.*$/, "")
my_int.netmask = words[1].sub(/^[^\/]+\//, "")
end
elsif words[0] =~ /^ether/
mac = words[1]
end
end
interfaces[interface].each do |x|
x.status = status
x.mac = mac
x.mtu = mtu
if interface == "lo0"
x.status = :active
end
end
end
return interfaces
end
module_function :solaris
end
require 'pp'
include IFConfig
pp ifconfig
if File.exists? "openbsd_ifconfig.txt"
$stdout.puts
$stdout.puts "Running openbsd_ifconfig.txt"
$stdout.puts
pp bsd(File.open("openbsd_ifconfig.txt"), Facter["Kernel"].value)
end
if File.exists? "solaris_ifconfig.txt"
$stdout.puts
$stdout.puts "Running solaris_ifconfig.txt"
$stdout.puts
pp solaris(File.open("solaris_ifconfig.txt"))
end
--
Erik Hollensbe
[EMAIL PROTECTED]
_______________________________________________
PDXRuby mailing list
[email protected]
IRC: #pdx.rb on irc.freenode.net
http://lists.pdxruby.org/mailman/listinfo/pdxruby