Search code examples
ruby-on-railsrubyipip-addresscidr

IP Range to CIDR in Ruby/Rails?


I want to do two things: Convert IP Address inputs into CIDR Here are some example inputs:

1.1.1.1    
192.168.*.* #=> 192.168.0-255.0-255
192.168.1.2-20
1.1.1-10.1-100

Check if a given IP Address falls into any CIDR. This must be a very fast query, as it's a very common lookup in my web app. I'm thinking of doing something like this:

def matches?(request)
  valid = @ips.select {|cidr| cidr.contains?(request.remote_ip) }
  !valid.empty?
end

I think converting IP ranges into CIDR will let lookups be faster than what we're doing now, which is breaking the IP's into integer octets. We then index the first two sets of octets to partially match against IP's. Another option might be converting everything to ints and doing comparisons that way. I'd convert to ints with something like this IPAddr.new("1.1.1.1").to_i but then I'd need to store an upper and lower IP for each range instead of just a single CIDR.

Please let me know if I am overlooking any mainstream approaches, popular gems or repo's. Thanks!


Solution

  • Well, to get the CIDR notation of a range, you need an IP and the number of network bits (calculated from the netmask).

    To enumerate the addresses of a given range, you can use the NetAddr (< 2.x) gem.

    p NetAddr::CIDR.create('192.168.1.0/24').enumerate
      => ['192.168.1.0', '192.168.1.1', '192.168.1.2'... '192.168.1.255']
    

    You can also calculate the bits from the netmask on the fly:

    mask_int = NetAddr.netmask_to_i('255.255.255.0')
    p NetAddr.mask_to_bits(mask_int)
      => 24
    

    And to create a range based on two IPs:

    lower = NetAddr::CIDR.create('192.168.1.1')
    upper = NetAddr::CIDR.create('192.168.1.10')
    p NetAddr.range(lower, upper)
      => ['192.168.1.2', '192.168.1.3'... '192.168.1.9']
    

    So now that you can create a CIDR range, you can check to see if an IP is a part of it:

    cidr = NetAddr::CIDR.create('192.168.1.0/24')
    p cidr.contains?('192.168.1.10')
      => true