#
# Copyright (c) 2006-2011 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Support.
#
# Ronin Support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ronin Support. If not, see .
#
require 'ipaddr'
require 'resolv'
require 'strscan'
require 'combinatorics/list_comprehension'
class IPAddr
include Enumerable
# A regular expression for matching IPv4 Addresses.
IPV4_REGEXP = /[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}/
# A regular expression for matching IPv6 Addresses.
IPV6_REGEXP = /:(:[0-9a-f]{1,4}){1,7}|([0-9a-f]{1,4}::?){1,7}[0-9a-f]{1,4}(:#{IPV4_REGEXP})?/
# A regular expression for matching IP Addresses.
REGEXP = /#{IPV4_REGEXP}|#{IPV6_REGEXP}/
#
# Extracts IP Addresses from text.
#
# @param [String] text
# The text to scan for IP Addresses.
#
# @param [Symbol] version
# The version of IP Address to scan for (`:ipv4` or `:ipv6`).
#
# @yield [ip]
# The given block will be passed each extracted IP Address.
#
# @yieldparam [String] ip
# An IP Address from the text.
#
# @return [Array]
# The IP Addresses found in the text.
#
# @api public
#
def IPAddr.extract(text,version=nil,&block)
regexp = case version
when :ipv4
IPV4_REGEXP
when :ipv6
IPV6_REGEXP
else
REGEXP
end
parser = StringScanner.new(text)
if block_given?
yield parser.matched while parser.skip_until(regexp)
return nil
else
ips = []
ips << parser.matched while parser.skip_until(regexp)
return ips
end
end
#
# Iterates over each IP address within the IP Address range. Supports
# both IPv4 and IPv6 address ranges.
#
# @param [String] cidr_or_glob
# The IP address range to iterate over.
# May be in standard CIDR notation or globbed format.
#
# @yield [ip]
# The block which will be passed each IP address contained within the
# IP address range.
#
# @yieldparam [String] ip
# An IP address within the IP address range.
#
# @return [nil]
#
# @example Enumerate through a CIDR range
# IPAddr.each('10.1.1.1/24') do |ip|
# puts ip
# end
#
# @example Enumerate through a globbed IP range
# IPAddr.each('10.1.1-5.*') do |ip|
# puts ip
# end
#
# @example Enumerate through a globbed IPv6 range
# IPAddr.each('::ff::02-0a::c3') do |ip|
# puts ip
# end
#
# @api public
#
def IPAddr.each(cidr_or_glob,&block)
unless (cidr_or_glob.include?('*') || cidr_or_glob.include?('-'))
return IPAddr.new(cidr_or_glob).each(&block)
end
return enum_for(:each,cidr_or_glob) unless block
if cidr_or_glob.include?('::')
prefix = if cidr_or_glob[0,2] == '::'
'::'
else
''
end
separator = '::'
base = 16
format = lambda { |address|
prefix + address.map { |i| '%.2x' % i }.join('::')
}
else
separator = '.'
base = 10
format = lambda { |address| address.join('.') }
end
# split the address
segments = cidr_or_glob.split(separator)
ranges = []
# map the components of the address to numeric ranges
segments.each do |segment|
next if segment.empty?
ranges << if segment == '*'
(1..254)
elsif segment.include?('-')
start, stop = segment.split('-',2)
(start.to_i(base)..stop.to_i(base))
else
segment.to_i(base)
end
end
# cycle through the address ranges
ranges.comprehension do |address|
yield format[address]
end
return nil
end
#
# Resolves the host-names for the IP address.
#
# @return [Array]
# The host-names for the IP address.
#
# @api public
#
def lookup
Resolv.getnames(self.to_s)
end
#
# Iterates over each IP address that is included in the addresses
# netmask. Supports both IPv4 and IPv6 addresses.
#
# @yield [ip]
# The block which will be passed every IP address covered be the
# netmask of the IPAddr object.
#
# @yieldparam [String] ip
# An IP address.
#
# @example
# netblock = IPAddr.new('10.1.1.1/24')
#
# netblock.each do |ip|
# puts ip
# end
#
# @api public
#
def each
return enum_for(:each) unless block_given?
case @family
when Socket::AF_INET
family_mask = IN4MASK
when Socket::AF_INET6
family_mask = IN6MASK
end
(0..((~@mask_addr) & family_mask)).each do |i|
yield _to_string(@addr | i)
end
return self
end
end