0.01
No commit activity in last 3 years
No release in over 3 years
convenience class to create a set of possibly discontiguous IP address range segments, and check if an IP address is in the set.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

ipaddr_range_set

Build Status Gem Version

convenience class to create a set of possibly discontiguous IP address range segments, and check if an IP address is in the set. ruby 1.9.3+ only.

Ruby stdlib IPAddr does the heavy-lifting, this is relatively simple code wrapping it in a convenience class. But this can simplify your own code when used. Basing logic on IP address range checking can often be the sign of a bad design, but many of us have to do it anyway.

Usage

Creating

require 'ipaddr_range_set'

# Zero or more segment arguments, which can be input in a variety of
# of formats. 
range = IPAddrRangeSet.new(
  '220.1.10.3',    # a single IPv4 as a string
  '2001:db8::10',  # a single IPv6 as a string
  '8.0.0.0/24',    # IPv4 as CIDR, works for IPv6 CIDR too
  '8.*.*.*',       # informal splat notation, only for IPv4
  '8.8.0.0'..'8.8.2.255', # arbitrary range, works for IPv6 too. 
  IPAddr.new(whatever),   # arbitrary existing IPAddr object
  (ip_addr..ip_addr),     # range of arbitrary IPAddr objects.
  IPAddrRangeSet::LocalAddresses    # An existing IPAddrRangeSet
)

Ranges and splats are handled by this gem (in terms of underlying IPAddr), other strings passed directly to IPAddr constructor.

When ruby Range's are used, IPAddrRangeSet makes sure to use Range#cover? internally, not Range#include? (the latter being disastrous for anything that doesn't have #to_int). Triple dot ... exclusive endpoint ranges are supported, which can be convenient if you don't like writing lots of 255s in your range end points.

Checking

And then, once you have an IPAddrRangeSet, you can check if a particular ip is in the range set, using string (IPv4 or v6) or IPAddr instance:

range.include?  '220.1.10.5'
range.include?  IPAddr.new('220.1.10.5')

#include? is aliased as #=== so you can easily use it in case/when.

Immutable, but create new with union of existing

IPAddrRangeSets are immutable, but you can create new ones combining existing ranges:

new_range = IPAddrRangeSet('8.10.5.1') + IPAddrRangeSet('8.11.6.1')
new_range = IPAddrRangeSet('8.10.5.1').add('8.0.0.0/24', '10.0.0.1'..'10.1.4.255' )

The internal implementation just steps through all range segments and checks the argument for inclusion, there's no special optimization to detect overlapping ranges and simplify them. If you are doing a high enough volume of segment/arg checks that you need performance, you probably need a custom implementation involving a search tree of some kind anyway.

As above range 'union' is supported, but range intersection is not. It's a bit tricky to implement well, and I don't have a use case for it.

Built-in ranges for checking local addresses

Built-in constants are available for local (private, not publically routable) and loopback ranges in both IPv4 and IPv6. IPAddrRangeSet::IPv4Local, IPv4Loopback, IPv6Local, IPv6Loopback. The constant LocalAddresses is the union of all v4 and v6 local and loopback addresses.

IPAddrRangeSet::LocalAddresses.include? "127.0.0.1" # true
IPAddrRangeSet::LocalAddresses.include? "10.0.0.1" # true
IPAddrRangeSet::LocalAddresses.include? "192.168.0.1" # true
IPAddrRangeSet::LocalAddresses.include? "::1" # true, ipv6 loopback
IPAddrRangeSet::LocalAddresses.include? "fc00::1" # an ipv6 local

Note on ipv6

It supports ipv6 just because it was so easy to do so with the underlying IPAddr implementation. But I don't have much experience or use for IPv6, there could be oddities hiding in there.

You can create an IPAddrRangeSet that includes both IPv4 and IPv6 segments, no problem. But an individual include? argument will only match a segment of it's own type, no automatic conversion of IPv4-compatible IPv6 addresses is done (should it be? I have no idea, don't really understand ipv6 use cases).

thanks

to whoever wrote IPAddr in ruby stdlib, nice to have it, this is just a convenience wrapper over it, really.