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

Reply via email to