Calculating EUI-64 addresses.

[ Posted by James Harton Thu, 13 Aug 2009 22:54:42 GMT ]

Another instalment in my series of IPAddr extensions for Ruby, today I show you how to calculate the EUI-64 address for a given network and MAC address:

class IPAddr
  def self.eui_64(network, mac)
    if !network.is_a? IPAddr
      raise ArgumentError, "Network must be an IPAddr type."
    elsif network.ipv4?
      raise ArgumentError, "Network must be an IPv6 network."
    elsif network.length != 64
      raise ArgumentError, "Network must have a 64 bit mask."
    end
    if mac.is_a? Integer
      mac = "%:012x" % mac
    end
    if mac.is_a? String
      mac = mac.split(":").join.downcase
      if mac.match(/^[0-9a-f]{12}/).nil?
        raise ArgumentError, "Second argument must be a valid MAC address."
      end
      e64 = (mac[0..5] + "fffe" + mac[6..11]).to_i(16) ^ 0x0200000000000000
      IPAddr.from_i(network.first.to_i + e64, Socket::AF_INET6)
    end
  end
end

Tags , , , , , , ,  | no comments

More missing pieces for Ruby's IPAddr

[ Posted by James Harton Wed, 12 Aug 2009 23:50:17 GMT ]

WRT yesterday's blog entry, Ruby's IPAddr class is missing a lot of useful methods. Today I'll show you how to add methods to return the network address and the last address (the broadcast address in IPv4 parlance).

class IPAddr

 def length
   # nasty hack, but works well enough.
   @mask_addr.to_s(2).count("1")
 end

 def first
   IPAddr.from_i(@addr & @mask_addr, @family)
 end

 def last
   if @family == Socket::AF_INET
     IPAddr.from_i(first.to_i | (@mask_addr ^ IN4MASK), @family)
   elsif @family == Socket::AF_INET6
     IPAddr.from_i(first.to_i | (@mask_addr ^ IN6MASK), @family)
   else
     raise "unsupported address family."
   end
 end

end

I hope you find these useful.

Tags , , , , , , ,  | no comments

Adding range and from_i support to Ruby's IPAddr class

[ Posted by James Harton Wed, 12 Aug 2009 01:00:38 GMT ]

There are a number of really dumb things wrong with Ruby's IPAddr class, but the two most annoying is no way to convert an integer to an IPv4 or IPv6 address and you can't use IPAddr's in ranges.

I've added a from_i() Class method that clones a new IPAddr instance by converting the integer into the string representation of an IP address. You can optionally also pass in an address family (Socket::AF_INET or Socket::AF_INET6) otherwise it will try and work it out from the size of the integer. I was lucky because I was able to take the bulk of the code from Culvert and convert it to rubyisms.

I've also added the succ() and <=>() methods to IPAddr so that you can use it in ranges, eg:

(IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.1") => false (IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.17") => true (IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.43") => false

I need this because I'm writing some code for work which abstracts DHCP ranges (which don't always match subnet boundaries).

Here's the code:

require 'ipaddr'

class IPAddr include Comparable

 def self.from_i(i = 0, family = Socket::AF_UNSPEC)
   if i < 0
     raise ArgumentError, "Cannot convert negative integer to an IP address."
   elsif family == Socket::AF_INET && i > IN4MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffff to an IPv4 address."
   elsif family == Socket::AF_INET6 && i > IN6MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to an IPv6 address."
   elsif i > IN6MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to IP address."
   end

   if family == Socket::AF_UNSPEC
     if (0..4) === i.size
       family = Socket::AF_INET
     elsif (5..16) === i.size
       family = Socket::AF_INET6
     end
   end

   if family == Socket::AF_INET
     r = []
     (0..3).each do |byte|
       x = (3 - byte) * 8
       y = (4 - byte) * 8
       r << ((i >> x) - ((i >> y) * 256 )).to_s
     end
     IPAddr.new(r * ".")
   elsif family == Socket::AF_INET6
     r = []
     (0..7).each do |byte|
       x = (7 - byte) * 16
       y = (8 - byte) * 16
       r << ((i >> x) - ((i >> y) * 65536)).to_s(16)
     end
     IPAddr.new(r * ":")
   end
 end

 def succ
   i = to_i + 1
   if ipv4? and (i <= IN4MASK)
     IPAddr.from_i(i)
   elsif ipv6? and (i <= IN6MASK)
     IPAddr.from_i(i)
   end
 end

 def <=>(other)
   to_i <=> other.to_i
 end

end

I hope you find this code useful.

Tags , , , , , , ,  | no comments