ruby-ip library

This is a library for manipulating IP addresses.

<img src=“travis-ci.org/G7M3/ruby-ip2.svg?branch=master” alt=“Build Status” /> <img src=“coveralls.io/repos/github/G7M3/ruby-ip2/badge.svg?branch=master” alt=“Coverage Status” />

Installation

Gem

gem install ruby-ip

Source

git clone git://github.com/G7M3/ruby-ip2.git

Feature overview

  • Create from string and to string

    require 'ip'
    ip = IP.new("192.0.2.53/24")
    ip.to_s       # "192.0.2.53/24"
    ip.to_i       # 3221226037
    ip.to_b       # 11000000000000000000001000110101
    ip.to_hex     # "c0000235"
    ip.to_addr    # "192.0.2.53"
    ip.to_arpa    # "53.2.0.192.in-addr.arpa."
    ip.pfxlen     # 24
    
  • Convert from IPAddr to IP and back to IPAddr

    # new IPAddr
    ipaddr = IPAddr.new('192.168.2.1')
    # => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>
    
    # convert it to IP
    ip_from_ipaddr = IP.new_ntoh(ipaddr.hton)
    # => <IP::V4 192.168.2.1>
    
    # and back to IPAddr
    ipaddr_from_ip = IPAddr.new_ntoh(ip_from_ipaddr.hton)
    # => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>
    
  • Qualify IP address with “routing context” (VRF)

    ip = IP.new("192.0.2.53/24@cust1")
    ip.to_s       # "192.0.2.53/24@cust1"
    ip.to_addrlen # "192.0.2.53/24"
    ip.ctx        # "cust1"
    
  • Clean implementation of IP::V4 and IP::V6 as subclasses of IP

    ip1 = IP.new("192.0.2.53/24")       #<IP::V4 192.0.2.53/24>
    ip2 = IP.new("2001:db8:be00::/48")  #<IP::V6 2001:db8:be00::/48>
    ip1.is_a?(IP::V4)                   # true
    
  • Create directly from integers or hex

    ip = IP::V4.new(3221226037, 24, "cust1")
    ip = IP::V4.new("c0000235", 24, "cust1")
    
  • Netmask manipulation

    ip = IP.new("192.0.2.53/24")
    ip.network         #<IP::V4 192.0.2.0/24>
    ip.network(1)      #<IP::V4 192.0.2.1/24>
    ip.broadcast       #<IP::V4 192.0.2.255/24>
    ip.broadcast(-1)   #<IP::V4 192.0.2.254/24>
    ip.mask            # 255
    ip.size            # 256
    ip.netmask.to_s    # "255.255.255.0"
    ip.wildmask.to_s   # "0.0.0.255"
    
  • Address masking

    ip = IP.new("192.0.2.53/24")
    ip.offset?         # true
    ip.offset          # 53
    ip.mask!
    ip.to_s            # "192.0.2.0/24"
    ip.offset?         # false
    
  • Simple IP arithmetic

    ip = IP.new("192.0.2.53/24")
    ip + 4             #<IP::V4 192.0.2.57/24>
    ip | 7             #<IP::V4 192.0.2.55/24>
    ip ^ 7             #<IP::V4 192.0.2.50/24>
    ~ip                #<IP::V4 63.255.253.202/24>
    
  • Advanced Subnet Operations

    sn = IP.new('192.168.0.0/24')
    ip = IP.new('192.168.0.48/32')
    sn.split                    [#<IP::V4 192.168.0.0/25>,
                                #<IP::V4 192.168.0.128/25>] (2 evenly divided subnets)
    sn.divide_by_subnets(3)     [#<IP::V4 192.168.0.0/26>,
                                #<IP::V4 192.168.0.64/26>,
                                #<IP::V4 192.168.0.128/26>,
                                #<IP::V4 192.168.0.192/26>] (4 evenly divided subnets)
    #keep in mind this always takes into account a network and broadcast address
    sn.divide_by_hosts(100)     [#<IP::V4 192.168.0.0/25>,
                                #<IP::V4 192.168.0.128/25>] (128 hosts each)
    ip = IP.new('192.168.0.48/32')
    ip.is_in?(sn)
    => true
    
  • Deaggregate subnets from range

    IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255'))
      => [#<IP::V4 1.2.0.0/15>]
    IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255'))
      => [#<IP::V4 1.2.0.0/15>, #<IP::V4 1.4.0.0/16>]
    IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff'))
      => [#<IP::V6 2001:db8:85a3:8d3::/64>]
    IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::'))
      => [#<IP::V6 2001:db8:85a3:8d3::/80>, #<IP::V6 2001:db8:85a3:8d3:1::>]
    
  • Convert to and from a compact Array representation

    ip1 = IP.new("192.0.2.53/24@cust1")
    ip1.to_a     # ["v4", 3221226037, 24, "cust1"]
    
    ip2 = IP.new(["v4", 3221226037, 24, "cust1"])
    ip1 == ip2   # true
    
  • Hex array representation, useful when talking to languages which don’t have Bignum support

    ip1 = IP.new("2001:db8:be00::/48@cust1")
    ip1.to_ah    # ["v6", "20010db8be0000000000000000000000", 48, "cust1"]
    
    ip2 = IP.new(["v6", "20010db8be0000000000000000000000", 48, "cust1"])
    ip1 == ip2   # true
    
  • Addresses are Comparable, sortable, and can be used as Hash keys

  • Addresses can be stored in a VARBINARY(16) database column as string of bytes

    # == Schema Information
    #
    # Table name: ipaddresses
    #
    #  id         :integer          not null, primary key
    #  address    :binary(16)
    #  pfxlen     :integer
    #  created_at :datetime         not null
    #  updated_at :datetime         not null
    #
    # Indexes
    #
    #  index_ipaddresses_on_address  (address)
    #  index_ipaddresses_on_pfxlen   (pfxlen)
    #
    
    require 'ip'
    class IpAddress < ActiveRecord::Base
      validates_uniqueness_of :address, scope: [:pfxlen]
      validates_presence_of :address, :pfxlen
    
      # IpAddress.last.address returns an IP object
      def address
        IP.new("#{IP.new_ntoh(super).to_s}/#{pfxlen}")
      end
    
      # Address can be set with string:
      # ipaddress = IpAddress.new({address: '2001:db8:be00::/128'})
      # Or IP object:
      # ip = IP.new('2001:db8:be00::/128')
      # ipaddress = IpAddress.new({address: ip})
      def address=(arg)
        ip = IP.new(arg)
        self.pfxlen = ip.pfxlen
        super(ip.hton)
      end
    end
    

Why not IPAddr?

Ruby bundles an IPAddr class (ipaddr.rb). However there are a number of serious problems with this library.

  1. Given an IP address with a netmask or prefix (e.g. 192.0.2.0/24) it’s very hard to get access to the netmask part. It involves digging around instance variables.

  2. It’s impossible to deal with an off-base address with prefix, because IPAddr forcibly masks it to the base. e.g. 192.0.2.53/24 is stored as 192.0.2.0/24

  3. IPAddr uses calls to the socket library to validate IP addresses, and this can trigger spurious DNS lookups when given an invalid IP address. ruby-ip does not depend on the socket library at all, unless you require ‘ip/socket’ to have access to the Socket::AF_INET and Socket::AF_INET6 constants.

Copying

You may use, distribute and modify this software under the same terms as ruby itself (see LICENSE.txt and COPYING.txt)