Search code examples
rubyipv6

Converting mac address to IPv6 link local address in Ruby


How can I convert a mac address like 00:16:3e:15:d3:a9 into an IPv6 link local address (as modified EUI-64, like fe80::216:3eff:fe15:d3a9) in Ruby?

So far, I have the following steps:

mac = "00:16:3e:15:d3:a9"
mac.delete!(':')        # Delete colons
mac.insert(6, 'fffe')   # Insert ff:ee in the middle
mac = mac.scan(/.{4}/)  # Split into octets of 4

The next step would be to flip the sixth bit of the first octet, which I'm having trouble with.


Solution

  • Here is your main problem: Ruby is an object-oriented language. You create programs by manipulating rich, structured objects, more precisely by telling rich, structured objects to manipulate themselves.

    You, however, are manipulating Strings. Now, of course, Strings are also objects in Ruby, but they are objects which represent text, they are not objects which represent IP addresses or EUIs.

    You should at least treat IP addresses or EUIs as numbers, not as text, but really, you should treat them as rich, structured IP address objects or EUI objects.

    Ruby actually comes with a library for manipulating IP addresses as part of its standard library.

    Here's an example of what it would look like to treat those addresses as numbers and/or objects:

    require 'ipaddr'
    
    eui48 = '00-16-3E-15-D3-A9'
    # eliminate all delimiters, note that only ':' is not enough; the standard is '-', but '.' is also sometimes used
    eui48 = eui48.gsub(/[^0-9a-zA-Z]/, '')
    # parse the EUI-48 as a number
    eui48 = eui48.to_i(16)
    
    # split the EUI-48 into the OUI and the manufacturer-assigned parts
    oui, assigned = eui48.divmod(1 << 24)
    
    # append 16 zero bits to the OUI
    left = oui << 16
    # add the standard bit sequence
    left += 0xfffe
    # append 24 zero bits
    left <<= 24
    
    # now we have an EUI-64
    eui64 = left + assigned
    
    # flip bit index 57 to create a modified EUI-64
    modified_eui64 = eui64 ^ 1 << 57
    
    # the prefix for link-local IPv6 addresses is fe80::/10, link-local addresses are in the fe80::/64 network
    prefix = IPAddr.new('fe80::/64')
    
    # the suffix is based on our modified EUI-64
    suffix = IPAddr.new(modified_eui64, Socket::AF_INET6)
    
    # the final result is the bitwise logical OR of the prefix and suffix
    link_local_ipv6 = prefix | suffix
    
    # the IP address library knows how to correctly format an address according to RFC 5952 Section 4
    link_local_ipv6.to_s
    #=> 'fe80::216:3eff:fe15:d3a9'