diff options
Diffstat (limited to 'lib/ipaddr.rb')
-rw-r--r-- | lib/ipaddr.rb | 219 |
1 files changed, 147 insertions, 72 deletions
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 7fff54b9d0..dbb213c90a 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,18 +40,19 @@ require 'socket' # p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr + VERSION = "1.2.6" # 32 bit mask for IPv4 IN4MASK = 0xffffffff # 128 bit mask for IPv6 IN6MASK = 0xffffffffffffffffffffffffffffffff # Format string for IPv6 - IN6FORMAT = (["%.4x"] * 8).join(':') + IN6FORMAT = (["%.4x"] * 8).join(':').freeze # Regexp _internally_ used for parsing IPv4 address. RE_IPV4ADDRLIKE = %r{ \A - (\d+) \. (\d+) \. (\d+) \. (\d+) + \d+ \. \d+ \. \d+ \. \d+ \z }x @@ -109,16 +110,20 @@ class IPAddr # Convert a network byte ordered string form of an IP address into # human readable form. + # It expects the string to be encoded in Encoding::ASCII_8BIT (BINARY). def self.ntop(addr) - case addr.size + if addr.is_a?(String) && addr.encoding != Encoding::BINARY + raise InvalidAddressError, "invalid encoding (given #{addr.encoding}, expected BINARY)" + end + + case addr.bytesize when 4 - s = addr.unpack('C4').join('.') + addr.unpack('C4').join('.') when 16 - s = IN6FORMAT % addr.unpack('n8') + IN6FORMAT % addr.unpack('n8') else raise AddressFamilyError, "unsupported address family" end - return s end # Returns a new ipaddr built by bitwise AND. @@ -168,34 +173,15 @@ class IPAddr # net1 = IPAddr.new("192.168.2.0/24") # net2 = IPAddr.new("192.168.2.100") # net3 = IPAddr.new("192.168.3.0") + # net4 = IPAddr.new("192.168.2.0/16") # p net1.include?(net2) #=> true # p net1.include?(net3) #=> false + # p net1.include?(net4) #=> false + # p net4.include?(net1) #=> true def include?(other) other = coerce_other(other) - if ipv4_mapped? - if (@mask_addr >> 32) != 0xffffffffffffffffffffffff - return false - end - mask_addr = (@mask_addr & IN4MASK) - addr = (@addr & IN4MASK) - family = Socket::AF_INET - else - mask_addr = @mask_addr - addr = @addr - family = @family - end - if other.ipv4_mapped? - other_addr = (other.to_i & IN4MASK) - other_family = Socket::AF_INET - else - other_addr = other.to_i - other_family = other.family - end - - if family != other_family - return false - end - return ((addr & mask_addr) == (other_addr & mask_addr)) + return false unless other.family == family + begin_addr <= other.begin_addr && end_addr >= other.end_addr end alias === include? @@ -232,7 +218,19 @@ class IPAddr # Returns a string containing the IP address representation in # canonical form. def to_string - return _to_string(@addr) + str = _to_string(@addr) + + if @family == Socket::AF_INET6 + str << zone_id.to_s + end + + return str + end + + # Returns a string containing the IP address representation in + # cidr notation + def cidr + format("%s/%s", to_s, prefix) end # Returns a network byte ordered string form of the IP address. @@ -260,12 +258,17 @@ class IPAddr end # Returns true if the ipaddr is a loopback address. + # Loopback IPv4 addresses in the IPv4-mapped IPv6 + # address range are also considered as loopback addresses. def loopback? case @family when Socket::AF_INET - @addr & 0xff000000 == 0x7f000000 + @addr & 0xff000000 == 0x7f000000 # 127.0.0.1/8 when Socket::AF_INET6 - @addr == 1 + @addr == 1 || # ::1 + (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( + @addr & 0xff000000 == 0x7f000000 # ::ffff:127.0.0.1/8 + )) else raise AddressFamilyError, "unsupported address family" end @@ -274,7 +277,8 @@ class IPAddr # Returns true if the ipaddr is a private address. IPv4 addresses # in 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 as defined in RFC # 1918 and IPv6 Unique Local Addresses in fc00::/7 as defined in RFC - # 4193 are considered private. + # 4193 are considered private. Private IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered private. def private? case @family when Socket::AF_INET @@ -282,22 +286,31 @@ class IPAddr @addr & 0xfff00000 == 0xac100000 || # 172.16.0.0/12 @addr & 0xffff0000 == 0xc0a80000 # 192.168.0.0/16 when Socket::AF_INET6 - @addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000 + @addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000 || + (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( + @addr & 0xff000000 == 0x0a000000 || # ::ffff:10.0.0.0/8 + @addr & 0xfff00000 == 0xac100000 || # ::ffff::172.16.0.0/12 + @addr & 0xffff0000 == 0xc0a80000 # ::ffff::192.168.0.0/16 + )) else raise AddressFamilyError, "unsupported address family" end end # Returns true if the ipaddr is a link-local address. IPv4 - # addresses in 169.254.0.0/16 reserved by RFC 3927 and Link-Local + # addresses in 169.254.0.0/16 reserved by RFC 3927 and link-local # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are - # considered link-local. + # considered link-local. Link-local IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered link-local. def link_local? case @family when Socket::AF_INET @addr & 0xffff0000 == 0xa9fe0000 # 169.254.0.0/16 when Socket::AF_INET6 - @addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000 + @addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000 || # fe80::/10 + (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( + @addr & 0xffff0000 == 0xa9fe0000 # ::ffff:169.254.0.0/16 + )) else raise AddressFamilyError, "unsupported address family" end @@ -328,9 +341,11 @@ class IPAddr # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise InvalidAddressError, "not an IPv4 address" + raise InvalidAddressError, "not an IPv4 address: #{@addr}" end - return self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) + clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) + clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) + clone end # Returns a new ipaddr built by converting the native IPv4 address @@ -338,7 +353,7 @@ class IPAddr def ipv4_compat warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise InvalidAddressError, "not an IPv4 address" + raise InvalidAddressError, "not an IPv4 address: #{@addr}" end return self.clone.set(@addr, Socket::AF_INET6) end @@ -369,7 +384,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise InvalidAddressError, "not an IPv6 address" + raise InvalidAddressError, "not an IPv6 address: #{@addr}" end return _reverse + ".ip6.arpa" end @@ -377,7 +392,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise InvalidAddressError, "not an IPv6 address" + raise InvalidAddressError, "not an IPv6 address: #{@addr}" end return _reverse + ".ip6.int" end @@ -404,23 +419,12 @@ class IPAddr # Returns a hash value used by Hash, Set, and Array classes def hash - return ([@addr, @mask_addr].hash << 1) | (ipv4? ? 0 : 1) + return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1) end # Creates a Range object for the network address. def to_range - begin_addr = (@addr & @mask_addr) - - case @family - when Socket::AF_INET - end_addr = (@addr | (IN4MASK ^ @mask_addr)) - when Socket::AF_INET6 - end_addr = (@addr | (IN6MASK ^ @mask_addr)) - else - raise AddressFamilyError, "unsupported address family" - end - - return clone.set(begin_addr, @family)..clone.set(end_addr, @family) + self.class.new(begin_addr, @family)..self.class.new(end_addr, @family) end # Returns the prefix length in bits for the ipaddr. @@ -460,15 +464,75 @@ class IPAddr af = "IPv4" when Socket::AF_INET6 af = "IPv6" + zone_id = @zone_id.to_s else raise AddressFamilyError, "unsupported address family" end - return sprintf("#<%s: %s:%s/%s>", self.class.name, - af, _to_string(@addr), _to_string(@mask_addr)) + return sprintf("#<%s: %s:%s%s/%s>", self.class.name, + af, _to_string(@addr), zone_id, _to_string(@mask_addr)) + end + + # Returns the netmask in string format e.g. 255.255.0.0 + def netmask + _to_string(@mask_addr) + end + + # Returns the wildcard mask in string format e.g. 0.0.255.255 + def wildcard_mask + case @family + when Socket::AF_INET + mask = IN4MASK ^ @mask_addr + when Socket::AF_INET6 + mask = IN6MASK ^ @mask_addr + else + raise AddressFamilyError, "unsupported address family" + end + + _to_string(mask) + end + + # Returns the IPv6 zone identifier, if present. + # Raises InvalidAddressError if not an IPv6 address. + def zone_id + if @family == Socket::AF_INET6 + @zone_id + else + raise InvalidAddressError, "not an IPv6 address" + end + end + + # Returns the IPv6 zone identifier, if present. + # Raises InvalidAddressError if not an IPv6 address. + def zone_id=(zid) + if @family == Socket::AF_INET6 + case zid + when nil, /\A%(\w+)\z/ + @zone_id = zid + else + raise InvalidAddressError, "invalid zone identifier for address" + end + else + raise InvalidAddressError, "not an IPv6 address" + end end protected + def begin_addr + @addr & @mask_addr + end + + def end_addr + case @family + when Socket::AF_INET + @addr | (IN4MASK ^ @mask_addr) + when Socket::AF_INET6 + @addr | (IN6MASK ^ @mask_addr) + else + raise AddressFamilyError, "unsupported address family" + end + end + # Set +@addr+, the internal stored ip address, to given +addr+. The # parameter +addr+ is validated using the first +family+ member, # which is +Socket::AF_INET+ or +Socket::AF_INET6+. @@ -476,11 +540,11 @@ class IPAddr case family[0] ? family[0] : @family when Socket::AF_INET if addr < 0 || addr > IN4MASK - raise InvalidAddressError, "invalid address" + raise InvalidAddressError, "invalid address: #{addr}" end when Socket::AF_INET6 if addr < 0 || addr > IN6MASK - raise InvalidAddressError, "invalid address" + raise InvalidAddressError, "invalid address: #{addr}" end else raise AddressFamilyError, "unsupported address family" @@ -488,6 +552,9 @@ class IPAddr @addr = addr if family[0] @family = family[0] + if @family == Socket::AF_INET + @mask_addr &= IN4MASK + end end return self end @@ -496,8 +563,11 @@ class IPAddr def mask!(mask) case mask when String - if mask =~ /\A\d+\z/ + case mask + when /\A(0|[1-9]+\d*)\z/ prefixlen = mask.to_i + when /\A\d+\z/ + raise InvalidPrefixError, "leading zeros in prefix" else m = IPAddr.new(mask) if m.family != @family @@ -555,6 +625,7 @@ class IPAddr # those, such as &, |, include? and ==, accept a string, or a packed # in_addr value instead of an IPAddr object. def initialize(addr = '::', family = Socket::AF_UNSPEC) + @mask_addr = nil if !addr.kind_of?(String) case family when Socket::AF_INET, Socket::AF_INET6 @@ -567,11 +638,16 @@ class IPAddr raise AddressFamilyError, "unsupported address family: #{family}" end end - prefix, prefixlen = addr.split('/') + prefix, prefixlen = addr.split('/', 2) if prefix =~ /\A\[(.*)\]\z/i prefix = $1 family = Socket::AF_INET6 end + if prefix =~ /\A(.*)(%\w+)\z/ + prefix = $1 + zone_id = $2 + family = Socket::AF_INET6 + end # It seems AI_NUMERICHOST doesn't do the job. #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, # Socket::AI_NUMERICHOST) @@ -586,6 +662,7 @@ class IPAddr @addr = in6_addr(prefix) @family = Socket::AF_INET6 end + @zone_id = zone_id if family != Socket::AF_UNSPEC && @family != family raise AddressFamilyError, "address family mismatch" end @@ -594,8 +671,6 @@ class IPAddr else @mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK end - rescue InvalidAddressError => e - raise e.class, "#{e.message}: #{addr}" end def coerce_other(other) @@ -614,12 +689,12 @@ class IPAddr when Array octets = addr else - m = RE_IPV4ADDRLIKE.match(addr) or return nil - octets = m.captures + RE_IPV4ADDRLIKE.match?(addr) or return nil + octets = addr.split('.') end octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address" - s.match(/\A0./) and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous" + (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}" + (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" i << 8 | n } end @@ -636,19 +711,19 @@ class IPAddr right = '' when RE_IPV6ADDRLIKE_COMPRESSED if $4 - left.count(':') <= 6 or raise InvalidAddressError, "invalid address" + left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" addr = in_addr($~[4,4]) left = $1 right = $3 + '0:0' else left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or - raise InvalidAddressError, "invalid address" + raise InvalidAddressError, "invalid address: #{@addr}" left = $1 right = $2 addr = 0 end else - raise InvalidAddressError, "invalid address" + raise InvalidAddressError, "invalid address: #{@addr}" end l = left.split(':') r = right.split(':') @@ -703,7 +778,7 @@ end unless Socket.const_defined? :AF_INET6 class Socket < BasicSocket # IPv6 protocol family - AF_INET6 = Object.new + AF_INET6 = Object.new.freeze end class << IPSocket |