Skip to content

Commit

Permalink
Added regexs for matching IP-glob ranges (ex: 1.1-10.1,2,4.*) (closes
Browse files Browse the repository at this point in the history
#473).

* Added `Network::IPRange::Glob::IPV4_REGEX`.
* Added `Network::IPRange::Glob::IPV6_REGEX`.
* Raise an `ArgumentError` if `IPRange::Glob#initialize` is given an
  invalid IP-glob string.
  • Loading branch information
postmodern committed Feb 8, 2024
1 parent 85c8385 commit a6eb60a
Show file tree
Hide file tree
Showing 2 changed files with 339 additions and 5 deletions.
57 changes: 52 additions & 5 deletions lib/ronin/support/network/ip_range/glob.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,48 @@ class Glob

include Enumerable

ipv4_octet = /(?:\d{1,2}|1\d{2}|2[1-4]\d|25[0-5])/
ipv4_octet_range = /#{ipv4_octet}(?:-#{ipv4_octet})?/
ipv4_octet_list = /(?:#{ipv4_octet_range}(?:,#{ipv4_octet_range})*|\*)/
ipv4_addr = /#{ipv4_octet_list}\.#{ipv4_octet_list}\.#{ipv4_octet_list}\.#{ipv4_octet_list}/

# Regex that matches IPv4 globbed addresses.
#
# @api private
#
# @since 1.1.0
IPV4_REGEX = /\A#{ipv4_addr}\z/

ipv6_octet = /[0-9a-fA-F]{1,4}/
ipv6_octet_range = /#{ipv6_octet}(?:-#{ipv6_octet})?/
ipv6_octet_list = /(?:#{ipv6_octet_range}(?:,#{ipv6_octet_range})*|\*)/

# Regex that matches IPv6 globbed addresses.
#
# @api private
#
# @since 1.1.0
IPV6_REGEX = /\A(?:
(?:#{ipv6_octet_list}:){6}#{ipv4_addr}|
(?:#{ipv6_octet_list}:){5}#{ipv6_octet_list}:#{ipv4_addr}|
(?:#{ipv6_octet_list}:){5}:#{ipv6_octet_list}:#{ipv4_addr}|
(?:#{ipv6_octet_list}:){1,1}(?::#{ipv6_octet_list}){1,4}:#{ipv4_addr}|
(?:#{ipv6_octet_list}:){1,2}(?::#{ipv6_octet_list}){1,3}:#{ipv4_addr}|
(?:#{ipv6_octet_list}:){1,3}(?::#{ipv6_octet_list}){1,2}:#{ipv4_addr}|
(?:#{ipv6_octet_list}:){1,4}(?::#{ipv6_octet_list}){1,1}:#{ipv4_addr}|
:(?::#{ipv6_octet_list}){1,5}:#{ipv4_addr}|
(?:(?:#{ipv6_octet_list}:){1,5}|:):#{ipv4_addr}|
(?:#{ipv6_octet_list}:){1,1}(?::#{ipv6_octet_list}){1,6}|
(?:#{ipv6_octet_list}:){1,2}(?::#{ipv6_octet_list}){1,5}|
(?:#{ipv6_octet_list}:){1,3}(?::#{ipv6_octet_list}){1,4}|
(?:#{ipv6_octet_list}:){1,4}(?::#{ipv6_octet_list}){1,3}|
(?:#{ipv6_octet_list}:){1,5}(?::#{ipv6_octet_list}){1,2}|
(?:#{ipv6_octet_list}:){1,6}(?::#{ipv6_octet_list}){1,1}|
#{ipv6_octet_list}(?::#{ipv6_octet_list}){7}|
:(?::#{ipv6_octet_list}){1,7}|
(?:(?:#{ipv6_octet_list}:){1,7}|:):
)\z/x

# The IP glob string.
#
# @return [String]
Expand All @@ -66,22 +108,27 @@ class Glob
# @param [String] string
# The IP-glob string to parse.
#
# @raise [ArgumentError]
# The IP-glob string was neither a valid IPv4 or IPv6 glob address.
#
def initialize(string)
@string = string

if @string.include?(':') # IPv6
case string
when IPV6_REGEX
@version = 6
@separator = ':'
@base = 16
@formatter = method(:format_ipv6_address)
else # IPv4
when IPV4_REGEX
@version = 4
@separator = '.'
@base = 10
@formatter = method(:format_ipv4_address)
else
raise(ArgumentError,"invalid IP-glob address: #{string.inspect}")
end

@ranges = @string.split(@separator).map do |segment|
@string = string
@ranges = string.split(@separator).map do |segment|
case segment
when '*' then (1..254)
when /,/ then parse_list(segment)
Expand Down
287 changes: 287 additions & 0 deletions spec/network/ip_range/glob_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,296 @@
require 'spec_helper'
require 'matchers/fully_match'
require 'ronin/support/network/ip_range/glob'

describe Ronin::Support::Network::IPRange::Glob do
let(:glob) { "10.1.1.*" }

describe "IPV4_REGEX" do
subject { described_class::IPV4_REGEX }

it "must match the full string" do
expect(' 1.2.3.4 ').to_not match(subject)
end

it "must match '0.0.0.0'" do
expect('0.0.0.0').to fully_match(subject)
end

it "must match '255.255.255.255'" do
expect('255.255.255.255').to fully_match(subject)
end

it "must not match octets greater than 255" do
expect('256.255.255.255').to_not match(subject)
expect('255.256.255.255').to_not match(subject)
expect('255.255.256.255').to_not match(subject)
expect('255.255.256.256').to_not match(subject)
end

it "must match a range of octets within the IPv4 address" do
expect('1-255.1.1.1').to fully_match(subject)
expect('1.1-255.1.1').to fully_match(subject)
expect('1.1.1-255.1').to fully_match(subject)
expect('1.1.1.1-255').to fully_match(subject)
end

it "must match a list of octets within the IPv4 address" do
expect('1,10,255.1.1.1').to fully_match(subject)
expect('1.1,10,255.1.1').to fully_match(subject)
expect('1.1.1,10,255.1').to fully_match(subject)
expect('1.1.1.1,10,255').to fully_match(subject)
end

it "must match a list of octets and octet ranges within the IPv4 address" do
expect('1,10-20,255.1.1.1').to fully_match(subject)
expect('1.1,10-20,255.1.1').to fully_match(subject)
expect('1.1.1,10-20,255.1').to fully_match(subject)
expect('1.1.1.1,10-20,255').to fully_match(subject)
end

it "must match a wildcard '*' octet within the IPv4 address" do
expect('*.1.1.1').to fully_match(subject)
expect('1.*.1.1').to fully_match(subject)
expect('1.1.*.1').to fully_match(subject)
expect('1.1.1.*').to fully_match(subject)
end

it "must not allow a wildcard '*' in octet ranges" do
expect('1-*.1.1.1').to_not match(subject)
expect('1.1-*.1.1').to_not match(subject)
expect('1.1.1-*.1').to_not match(subject)
expect('1.1.1.1-*').to_not match(subject)
expect('*-2.1.1.1').to_not match(subject)
expect('1.*-2.1.1').to_not match(subject)
expect('1.1.*-2.1').to_not match(subject)
expect('1.1.1.*-2').to_not match(subject)
end

it "must not allow a wildcard '*' in octet lists" do
expect('1,*,3.1.1.1').to_not match(subject)
expect('1.1,*,3.1.1').to_not match(subject)
expect('1.1.1,*,3.1').to_not match(subject)
expect('1.1.1.1,*,3').to_not match(subject)
end
end

describe "IPV6_REGEX" do
subject { described_class::IPV6_REGEX }

it "must match the full string" do
expect(' 1111:2222:3333:4444:5555:6666:7777:8888 ').to_not match(subject)
end

it "must match a fully qualified IPv6 address" do
expect('1111:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
end

it "must match lowercase hex IPv6 octets" do
expect('1111:2222:aaaa:bbbb:cccc:dddd:eeee:ffff').to fully_match(subject)
end

it "must match uppercase hex IPv6 octets" do
expect('1111:2222:AAAA:BBBB:CCCC:DDDD:EEEE:FFFF').to fully_match(subject)
end

it "must not match octects longer than four characters" do
expect('11111:2222:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:22222:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:33333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:44444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:55555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:66666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:77777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:88888').to_not match(subject)
end

it "must match truncated IPv6 octets" do
expect('1:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:8').to fully_match(subject)

expect('11:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:22:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:33:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:44:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:55:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:66:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:77:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:88').to fully_match(subject)

expect('111:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:888').to fully_match(subject)
end

it "must match truncated IPv6 address containing '::'" do
expect('::1111').to fully_match(subject)
expect('::1111:2222').to fully_match(subject)
expect('::1111:2222:3333').to fully_match(subject)
expect('::1111:2222:3333:4444').to fully_match(subject)
expect('::1111:2222:3333:4444:5555').to fully_match(subject)
expect('::1111:2222:3333:4444:5555:6666').to fully_match(subject)
expect('::1111:2222:3333:4444:5555:6666:7777').to fully_match(subject)
expect('1111::').to fully_match(subject)
expect('1111:2222::').to fully_match(subject)
expect('1111:2222:3333::').to fully_match(subject)
expect('1111:2222:3333:4444::').to fully_match(subject)
expect('1111:2222:3333:4444:5555::').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666::').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777::').to fully_match(subject)
expect('1111::8888').to fully_match(subject)
expect('1111:2222::8888').to fully_match(subject)
expect('1111:2222:3333::8888').to fully_match(subject)
expect('1111:2222:3333:4444::8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555::8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666::8888').to fully_match(subject)
expect('1111::7777:8888').to fully_match(subject)
expect('1111::6666:7777:8888').to fully_match(subject)
expect('1111::5555:6666:7777:8888').to fully_match(subject)
expect('1111::4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111::3333:4444:5555:6666:7777:8888').to fully_match(subject)
end

it "must match a range of octets within the IPv6 address" do
expect('1-1000:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2-2000:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3-3000:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4-4000:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5-5000:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6-6000:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7-7000:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:8-8000').to fully_match(subject)
end

it "must match a list of octets within the IPv6 address" do
expect('1,10,100,1000:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2,20,200,2000:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3,30,300,3000:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4,40,400,4000:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5,50,500,5000:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6,60,600,6000:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7,70,700,7000:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:8,80,800,8000').to fully_match(subject)
end

it "must match a list of octets and octet ranges within the IPv6 address" do
expect('1,10-100,1000:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2,20-200,2000:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3,30-300,3000:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4,40-400,4000:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5,50-500,5000:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6,60-600,6000:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7,70-700,7000:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:8,80-800,8000').to fully_match(subject)
end

it "must match a wildcard '*' octet within the IPv6 address" do
expect('*:2222:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:*:3333:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:*:4444:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:*:5555:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:*:6666:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:*:7777:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:*:8888').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:*').to fully_match(subject)
end

it "must not allow a wildcard '*' in octet ranges" do
expect('*-10:2222:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:*-20:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:*-30:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:*-40:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:*-50:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:*-60:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:*-70:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:*-80').to_not match(subject)
end

it "must not allow a wildcard '*' in octet lists" do
expect('1,*,10:2222:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2,*,20:3333:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3,*,30:4444:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4,*,40:5555:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5,*,50:6666:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6,*,60:7777:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:7,*,70:8888').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:7777:8,*,80').to_not match(subject)
end

it "must match IPv4 mapped IPv6 addresses" do
expect('::1.2.3.4').to fully_match(subject)
expect('::1111:1.2.3.4').to fully_match(subject)
expect('::1111:2222:1.2.3.4').to fully_match(subject)
expect('::1111:2222:3333:1.2.3.4').to fully_match(subject)
expect('::1111:2222:3333:4444:1.2.3.4').to fully_match(subject)
expect('::1111:2222:3333:4444:5555:1.2.3.4').to fully_match(subject)
expect('1111::1.2.3.4').to fully_match(subject)
expect('1111:2222::1.2.3.4').to fully_match(subject)
expect('1111:2222:3333::1.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444::1.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555::1.2.3.4').to fully_match(subject)
expect('1111::6666:1.2.3.4').to fully_match(subject)
expect('1111:2222::6666:1.2.3.4').to fully_match(subject)
expect('1111:2222:3333::6666:1.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444::6666:1.2.3.4').to fully_match(subject)
expect('1111::5555:6666:1.2.3.4').to fully_match(subject)
expect('1111::4444:5555:6666:1.2.3.4').to fully_match(subject)
expect('1111::3333:4444:5555:6666:1.2.3.4').to fully_match(subject)
end

it "must match a range of octets within the IPv4 mapped IPv6 address" do
expect('1111:2222:3333:4444:5555:6666:1-10.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2-20.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3-30.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.4-40').to fully_match(subject)
end

it "must match a list of octets within the IPv6 address" do
expect('1111:2222:3333:4444:5555:6666:1,2,3.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2,3,4.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3,4,5.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.4,5,6').to fully_match(subject)
end

it "must match a list of octets and octet ranges within the IPv4 mapped IPv6 address" do
expect('1111:2222:3333:4444:5555:6666:1,2-20,3.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2,3-30,4.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3,4-40,5.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.4,5-50,6').to fully_match(subject)
end

it "must match a wildcard '*' octet within the IPv4 mapped IPv6 address" do
expect('1111:2222:3333:4444:5555:6666:*.2.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.*.3.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.*.4').to fully_match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.*').to fully_match(subject)
end

it "must not allow a wildcard '*' in octet ranges of IPv4 mapped IPv6 addresses" do
expect('1111:2222:3333:4444:5555:6666:1-*.2.3.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2-*.3.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3-*.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.4-*').to_not match(subject)
end

it "must not allow a wildcard '*' in octet lists of IPv4 mapped IPv6 addresses" do
expect('1111:2222:3333:4444:5555:6666:1,*,3.2.3.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2,*,3.3.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3,*,4.4').to_not match(subject)
expect('1111:2222:3333:4444:5555:6666:1.2.3.4,*,5').to_not match(subject)
end
end

describe "#initialize" do
subject { described_class.new(glob) }

Expand Down

0 comments on commit a6eb60a

Please sign in to comment.