diff options
Diffstat (limited to 'lib/ipaddr.rb')
| -rw-r--r-- | lib/ipaddr.rb | 853 |
1 files changed, 448 insertions, 405 deletions
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 23d74af451..6b67d7eec6 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true # # ipaddr.rb - A class to manipulate an IP address # # Copyright (c) 2002 Hajimu UMEMOTO <ume@mahoroba.org>. -# Copyright (c) 2007 Akinori MUSHA <knu@iDaemons.org>. +# Copyright (c) 2007, 2009, 2012 Akinori MUSHA <knu@iDaemons.org>. # All rights reserved. # # You can redistribute and/or modify it under the same terms as Ruby. @@ -17,97 +18,113 @@ # require 'socket' -unless Socket.const_defined? "AF_INET6" - class Socket - AF_INET6 = Object.new - end - - class << IPSocket - def valid_v4?(addr) - if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr - return $~.captures.all? {|i| i.to_i < 256} - end - return false - end - - def valid_v6?(addr) - # IPv6 (normal) - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr - return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr - # IPv6 (IPv4 compat) - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_v4?($') - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_v4?($') - return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_v4?($') - - false - end - - def valid?(addr) - valid_v4?(addr) || valid_v6?(addr) - end - - alias getaddress_orig getaddress - def getaddress(s) - if valid?(s) - s - elsif /\A[-A-Za-z\d.]+\Z/ =~ s - getaddress_orig(s) - else - raise ArgumentError, "invalid address" - end - end - end -end - # IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and # IPv6 are supported. # # == Example # # require 'ipaddr' -# +# # ipaddr1 = IPAddr.new "3ffe:505:2::1" -# -# p ipaddr1 #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0001/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff> -# -# p ipaddr1.to_s #=> "3ffe:505:2::1" -# -# ipaddr2 = ipaddr1.mask(48) #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000> -# -# p ipaddr2.to_s #=> "3ffe:505:2::" -# +# +# p ipaddr1 #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0001/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff> +# +# p ipaddr1.to_s #=> "3ffe:505:2::1" +# +# ipaddr2 = ipaddr1.mask(48) #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000> +# +# p ipaddr2.to_s #=> "3ffe:505:2::" +# # ipaddr3 = IPAddr.new "192.168.2.0/24" -# -# p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> +# +# p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr + # The version string + VERSION = "1.2.8" + # 32 bit mask for IPv4 IN4MASK = 0xffffffff + # 128 bit mask for IPv6 IN6MASK = 0xffffffffffffffffffffffffffffffff - IN6FORMAT = (["%.4x"] * 8).join(':') + # Format string for IPv6 + IN6FORMAT = (["%.4x"] * 8).join(':').freeze + + # Regexp _internally_ used for parsing IPv4 address. + RE_IPV4ADDRLIKE = %r{ + \A + \d+ \. \d+ \. \d+ \. \d+ + \z + }x + + # Regexp _internally_ used for parsing IPv6 address. + RE_IPV6ADDRLIKE_FULL = %r{ + \A + (?: + (?: [\da-f]{1,4} : ){7} [\da-f]{1,4} + | + ( (?: [\da-f]{1,4} : ){6} ) + (\d+) \. (\d+) \. (\d+) \. (\d+) + ) + \z + }xi + + # Regexp _internally_ used for parsing IPv6 address. + RE_IPV6ADDRLIKE_COMPRESSED = %r{ + \A + ( (?: (?: [\da-f]{1,4} : )* [\da-f]{1,4} )? ) + :: + ( (?: + ( (?: [\da-f]{1,4} : )* ) + (?: + [\da-f]{1,4} + | + (\d+) \. (\d+) \. (\d+) \. (\d+) + ) + )? ) + \z + }xi + + # Generic IPAddr related error. Exceptions raised in this class should + # inherit from Error. + class Error < ArgumentError; end + + # Raised when the provided IP address is an invalid address. + class InvalidAddressError < Error; end + + # Raised when the address family is invalid such as an address with an + # unsupported family, an address with an inconsistent family, or an address + # who's family cannot be determined. + class AddressFamilyError < Error; end + + # Raised when the address is an invalid length. + class InvalidPrefixError < InvalidAddressError; end # Returns the address family of this IP address. - attr :family + attr_reader :family # Creates a new ipaddr containing the given network byte ordered # string form of an IP address. - def IPAddr::new_ntoh(addr) - return IPAddr.new(IPAddr::ntop(addr)) + def self.new_ntoh(addr) + return new(ntop(addr)) end # Convert a network byte ordered string form of an IP address into # human readable form. - def IPAddr::ntop(addr) - case addr.size + # It expects the string to be encoded in Encoding::ASCII_8BIT (BINARY). + def self.ntop(addr) + 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 ArgumentError, "unsupported address family" + raise AddressFamilyError, "unsupported address family" end - return s end # Returns a new ipaddr built by bitwise AND. @@ -135,10 +152,23 @@ class IPAddr return self.clone.set(addr_mask(~@addr)) end + # Returns a new ipaddr greater than the original address by offset + def +(offset) + self.clone.set(@addr + offset, @family) + end + + # Returns a new ipaddr less than the original address by offset + def -(offset) + self.clone.set(@addr - offset, @family) + end + # Returns true if two ipaddrs are equal. def ==(other) other = coerce_other(other) - return @family == other.family && @addr == other.to_i + rescue + false + else + @family == other.family && @addr == other.to_i end # Returns a new ipaddr built by masking IP address with the given @@ -154,34 +184,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") - # p net1.include?(net2) #=> true - # p net1.include?(net3) #=> false + # 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? @@ -197,7 +208,7 @@ class IPAddr str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1') loop do - break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::') + break if str.sub!(/\A0:0:0:0:0:0:0:0\z/, '::') break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') break if str.sub!(/\b0:0:0:0:0:0\b/, ':') break if str.sub!(/\b0:0:0:0:0\b/, ':') @@ -208,7 +219,7 @@ class IPAddr end str.sub!(/:{3,}/, '::') - if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\Z/i =~ str + if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\z/i =~ str str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256) end @@ -218,7 +229,35 @@ 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 with prefix. + def as_json(*) + if ipv4? && prefix == 32 + to_s + elsif ipv6? && prefix == 128 + to_s + else + cidr + end + end + + # Returns a json string containing the IP address representation. + def to_json(*a) + %Q{"#{as_json(*a)}"} + end + + # Returns a string containing the IP address representation in + # cidr notation + def cidr + "#{to_s}/#{prefix}" end # Returns a network byte ordered string form of the IP address. @@ -228,10 +267,10 @@ class IPAddr return [@addr].pack('N') when Socket::AF_INET6 return (0..7).map { |i| - (@addr >> (112 - 16 * i)) & 0xffff + (@addr >> (112 - 16 * i)) & 0xffff }.pack('n8') else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end end @@ -245,6 +284,65 @@ class IPAddr return @family == Socket::AF_INET6 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 # 127.0.0.1/8 + when Socket::AF_INET6 + @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 + end + + # 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. Private IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered private. + def private? + case @family + when Socket::AF_INET + @addr & 0xff000000 == 0x0a000000 || # 10.0.0.0/8 + @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 & 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 + # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are + # 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 || # 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 + end + # Returns true if the ipaddr is an IPv4-mapped IPv6 address. def ipv4_mapped? return ipv6? && (@addr >> 32) == 0xffff @@ -252,6 +350,11 @@ class IPAddr # Returns true if the ipaddr is an IPv4-compatible IPv6 address. def ipv4_compat? + warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE + _ipv4_compat? + end + + def _ipv4_compat? # :nodoc: if !ipv6? || (@addr >> 32) != 0 return false end @@ -259,29 +362,36 @@ class IPAddr return a != 0 && a != 1 end + private :_ipv4_compat? + # Returns a new ipaddr built by converting the native IPv4 address # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise ArgumentError, "not an IPv4 address" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" 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 # into an IPv4-compatible IPv6 address. def ipv4_compat + warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise ArgumentError, "not an IPv4 address" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end - return self.clone.set(@addr, Socket::AF_INET6) + clone = self.clone.set(@addr, Socket::AF_INET6) + clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) + clone end # Returns a new ipaddr built by converting the IPv6 address into a # native IPv4 address. If the IP address is not an IPv4-mapped or # IPv4-compatible IPv6 address, returns self. def native - if !ipv4_mapped? && !ipv4_compat? + if !ipv4_mapped? && !_ipv4_compat? return self end return self.clone.set(@addr & IN4MASK, Socket::AF_INET) @@ -296,14 +406,14 @@ class IPAddr when Socket::AF_INET6 return ip6_arpa else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end end # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise ArgumentError, "not an IPv6 address" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.arpa" end @@ -311,7 +421,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise ArgumentError, "not an IPv6 address" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.int" end @@ -324,27 +434,55 @@ class IPAddr # Compares the ipaddr with another. def <=>(other) other = coerce_other(other) + rescue + nil + else + @addr <=> other.to_i if other.family == @family + end + include Comparable - return nil if other.family != @family + # Checks equality used by Hash. + def eql?(other) + return self.class == other.class && self.hash == other.hash && self == other + end - return @addr <=> other.to_i + # Returns a hash value used by Hash, Set, and Array classes + def hash + return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1) end - include Comparable # Creates a Range object for the network address. def to_range - begin_addr = (@addr & @mask_addr) + self.class.new(begin_addr, @family)..self.class.new(end_addr, @family) + end + # Returns the prefix length in bits for the ipaddr. + def prefix case @family when Socket::AF_INET - end_addr = (@addr | (IN4MASK ^ @mask_addr)) + n = IN4MASK ^ @mask_addr + i = 32 when Socket::AF_INET6 - end_addr = (@addr | (IN6MASK ^ @mask_addr)) + n = IN6MASK ^ @mask_addr + i = 128 else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" + end + while n.positive? + n >>= 1 + i -= 1 end + i + end - return clone.set(begin_addr, @family)..clone.set(end_addr, @family) + # Sets the prefix length in bits + def prefix=(prefix) + case prefix + when Integer + mask!(prefix) + else + raise InvalidPrefixError, "prefix must be an integer" + end end # Returns a string containing a human-readable representation of the @@ -355,47 +493,124 @@ class IPAddr af = "IPv4" when Socket::AF_INET6 af = "IPv6" + zone_id = @zone_id.to_s else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" + end + 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 - return sprintf("#<%s: %s:%s/%s>", self.class.name, - af, _to_string(@addr), _to_string(@mask_addr)) end protected + # :stopdoc: + 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 + #:startdoc: + + # 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+. def set(addr, *family) case family[0] ? family[0] : @family when Socket::AF_INET if addr < 0 || addr > IN4MASK - raise ArgumentError, "invalid address" + raise InvalidAddressError, "invalid address: #{addr}" end when Socket::AF_INET6 if addr < 0 || addr > IN6MASK - raise ArgumentError, "invalid address" + raise InvalidAddressError, "invalid address: #{addr}" end else - raise ArgumentError, "unsupported address family" + raise AddressFamilyError, "unsupported address family" end @addr = addr if family[0] @family = family[0] + if @family == Socket::AF_INET + @mask_addr &= IN4MASK + end end return self end + # Set current netmask to given mask. def mask!(mask) - if mask.kind_of?(String) - if mask =~ /^\d+$/ - prefixlen = mask.to_i + case mask + when String + 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 - raise ArgumentError, "address family is not same" - end - @mask_addr = m.to_i - @addr &= @mask_addr - return self + m = IPAddr.new(mask) + if m.family != @family + raise InvalidPrefixError, "address family is not same" + end + @mask_addr = m.to_i + n = @mask_addr ^ m.instance_variable_get(:@mask_addr) + unless ((n + 1) & n).zero? + raise InvalidPrefixError, "invalid mask #{mask}" + end + @addr &= @mask_addr + return self end else prefixlen = mask @@ -403,18 +618,18 @@ class IPAddr case @family when Socket::AF_INET if prefixlen < 0 || prefixlen > 32 - raise ArgumentError, "invalid length" + raise InvalidPrefixError, "invalid length" end masklen = 32 - prefixlen @mask_addr = ((IN4MASK >> masklen) << masklen) when Socket::AF_INET6 if prefixlen < 0 || prefixlen > 128 - raise ArgumentError, "invalid length" + raise InvalidPrefixError, "invalid length" end masklen = 128 - prefixlen @mask_addr = ((IN6MASK >> masklen) << masklen) else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end @addr = ((@addr >> masklen) << masklen) return self @@ -425,7 +640,7 @@ class IPAddr # Creates a new ipaddr object either from a human readable IP # address representation in string, or from a packed in_addr value # followed by an address family. - # + # # In the former case, the following are the valid formats that will # be recognized: "address", "address/prefixlen" and "address/mask", # where IPv6 address may be enclosed in square brackets (`[' and @@ -433,14 +648,15 @@ class IPAddr # IP address. Although the address family is determined # automatically from a specified string, you can specify one # explicitly by the optional second argument. - # - # Otherwise an IP addess is generated from a packed in_addr value + # + # Otherwise an IP address is generated from a packed in_addr value # and an address family. # # The IPAddr class defines many methods and operators, and some of # 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 @@ -448,45 +664,47 @@ class IPAddr @mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK return when Socket::AF_UNSPEC - raise ArgumentError, "address family must be specified" + raise AddressFamilyError, "address family must be specified" else - raise ArgumentError, "unsupported address family: #{family}" + raise AddressFamilyError, "unsupported address family: #{family}" end end - prefix, prefixlen = addr.split('/') - if prefix =~ /^\[(.*)\]$/i + 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) - begin - IPSocket.getaddress(prefix) # test if address is vaild - rescue - raise ArgumentError, "invalid address" - end + # Socket::AI_NUMERICHOST) @addr = @family = nil if family == Socket::AF_UNSPEC || family == Socket::AF_INET @addr = in_addr(prefix) if @addr - @family = Socket::AF_INET + @family = Socket::AF_INET end end if !@addr && (family == Socket::AF_UNSPEC || family == Socket::AF_INET6) @addr = in6_addr(prefix) @family = Socket::AF_INET6 end + @zone_id = zone_id if family != Socket::AF_UNSPEC && @family != family - raise ArgumentError, "address family mismatch" + raise AddressFamilyError, "address family mismatch" end if prefixlen mask!(prefixlen) else - @mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK + @mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK end end + # :stopdoc: def coerce_other(other) case other when IPAddr @@ -499,26 +717,45 @@ class IPAddr end def in_addr(addr) - if addr =~ /^\d+\.\d+\.\d+\.\d+$/ - return addr.split('.').inject(0) { |i, s| - i << 8 | s.to_i - } + case addr + when Array + octets = addr + else + RE_IPV4ADDRLIKE.match?(addr) or return nil + octets = addr.split('.') end - return nil + octets.inject(0) { |i, s| + (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 def in6_addr(left) case left - when /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i - return in_addr($1) + 0xffff00000000 - when /^::(\d+\.\d+\.\d+\.\d+)$/i - return in_addr($1) - when /[^0-9a-f:]/i - raise ArgumentError, "invalid address" - when /^(.*)::(.*)$/ - left, right = $1, $2 - else + when RE_IPV6ADDRLIKE_FULL + if $2 + addr = in_addr($~[2,4]) + left = $1 + ':' + else + addr = 0 + end right = '' + when RE_IPV6ADDRLIKE_COMPRESSED + if $4 + left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{left}" + 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: #{left}" + left = $1 + right = $2 + addr = 0 + end + else + raise InvalidAddressError, "invalid address: #{left}" end l = left.split(':') r = right.split(':') @@ -526,9 +763,9 @@ class IPAddr if rest < 0 return nil end - return (l + Array.new(rest, '0') + r).inject(0) { |i, s| + (l + Array.new(rest, '0') + r).inject(0) { |i, s| i << 16 | s.hex - } + } | addr end def addr_mask(addr) @@ -538,7 +775,7 @@ class IPAddr when Socket::AF_INET6 return addr & IN6MASK else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end end @@ -546,12 +783,12 @@ class IPAddr case @family when Socket::AF_INET return (0..3).map { |i| - (@addr >> (8 * i)) & 0xff + (@addr >> (8 * i)) & 0xff }.join('.') when Socket::AF_INET6 return ("%.32x" % @addr).reverse!.gsub!(/.(?!$)/, '\&.') else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end end @@ -559,256 +796,62 @@ class IPAddr case @family when Socket::AF_INET return (0..3).map { |i| - (addr >> (24 - 8 * i)) & 0xff + (addr >> (24 - 8 * i)) & 0xff }.join('.') when Socket::AF_INET6 return (("%.32x" % addr).gsub!(/.{4}(?!$)/, '\&:')) else - raise "unsupported address family" + raise AddressFamilyError, "unsupported address family" end end end -if $0 == __FILE__ - eval DATA.read, nil, $0, __LINE__+4 -end - -__END__ - -require 'test/unit' -require 'test/unit/ui/console/testrunner' - -class TC_IPAddr < Test::Unit::TestCase - def test_s_new - assert_nothing_raised { - IPAddr.new("3FFE:505:ffff::/48") - IPAddr.new("0:0:0:1::") - IPAddr.new("2001:200:300::/48") - } - - a = IPAddr.new - assert_equal("::", a.to_s) - assert_equal("0000:0000:0000:0000:0000:0000:0000:0000", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - - a = IPAddr.new("0123:4567:89ab:cdef:0ABC:DEF0:1234:5678") - assert_equal("123:4567:89ab:cdef:abc:def0:1234:5678", a.to_s) - assert_equal("0123:4567:89ab:cdef:0abc:def0:1234:5678", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - - a = IPAddr.new("3ffe:505:2::/48") - assert_equal("3ffe:505:2::", a.to_s) - assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - assert_equal(false, a.ipv4?) - assert_equal(true, a.ipv6?) - assert_equal("#<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000>", a.inspect) - - a = IPAddr.new("3ffe:505:2::/ffff:ffff:ffff::") - assert_equal("3ffe:505:2::", a.to_s) - assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - - a = IPAddr.new("0.0.0.0") - assert_equal("0.0.0.0", a.to_s) - assert_equal("0.0.0.0", a.to_string) - assert_equal(Socket::AF_INET, a.family) - - a = IPAddr.new("192.168.1.2") - assert_equal("192.168.1.2", a.to_s) - assert_equal("192.168.1.2", a.to_string) - assert_equal(Socket::AF_INET, a.family) - assert_equal(true, a.ipv4?) - assert_equal(false, a.ipv6?) - - a = IPAddr.new("192.168.1.2/24") - assert_equal("192.168.1.0", a.to_s) - assert_equal("192.168.1.0", a.to_string) - assert_equal(Socket::AF_INET, a.family) - assert_equal("#<IPAddr: IPv4:192.168.1.0/255.255.255.0>", a.inspect) - - a = IPAddr.new("192.168.1.2/255.255.255.0") - assert_equal("192.168.1.0", a.to_s) - assert_equal("192.168.1.0", a.to_string) - assert_equal(Socket::AF_INET, a.family) - - assert_equal("0:0:0:1::", IPAddr.new("0:0:0:1::").to_s) - assert_equal("2001:200:300::", IPAddr.new("2001:200:300::/48").to_s) - - assert_equal("2001:200:300::", IPAddr.new("[2001:200:300::]/48").to_s) - - [ - ["fe80::1%fxp0"], - ["::1/255.255.255.0"], - ["::1:192.168.1.2/120"], - [IPAddr.new("::1").to_i], - ["::ffff:192.168.1.2/120", Socket::AF_INET], - ["[192.168.1.2]/120"], - ].each { |args| - assert_raises(ArgumentError) { - IPAddr.new(*args) - } - } - end - - def test_s_new_ntoh - addr = '' - IPAddr.new("1234:5678:9abc:def0:1234:5678:9abc:def0").hton.each_byte { |c| - addr += sprintf("%02x", c) - } - assert_equal("123456789abcdef0123456789abcdef0", addr) - addr = '' - IPAddr.new("123.45.67.89").hton.each_byte { |c| - addr += sprintf("%02x", c) - } - assert_equal(sprintf("%02x%02x%02x%02x", 123, 45, 67, 89), addr) - a = IPAddr.new("3ffe:505:2::") - assert_equal("3ffe:505:2::", IPAddr.new_ntoh(a.hton).to_s) - a = IPAddr.new("192.168.2.1") - assert_equal("192.168.2.1", IPAddr.new_ntoh(a.hton).to_s) - end - - def test_ipv4_compat - a = IPAddr.new("::192.168.1.2") - assert_equal("::192.168.1.2", a.to_s) - assert_equal("0000:0000:0000:0000:0000:0000:c0a8:0102", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - assert_equal(true, a.ipv4_compat?) - b = a.native - assert_equal("192.168.1.2", b.to_s) - assert_equal(Socket::AF_INET, b.family) - assert_equal(false, b.ipv4_compat?) - - a = IPAddr.new("192.168.1.2") - b = a.ipv4_compat - assert_equal("::192.168.1.2", b.to_s) - assert_equal(Socket::AF_INET6, b.family) - end - - def test_ipv4_mapped - a = IPAddr.new("::ffff:192.168.1.2") - assert_equal("::ffff:192.168.1.2", a.to_s) - assert_equal("0000:0000:0000:0000:0000:ffff:c0a8:0102", a.to_string) - assert_equal(Socket::AF_INET6, a.family) - assert_equal(true, a.ipv4_mapped?) - b = a.native - assert_equal("192.168.1.2", b.to_s) - assert_equal(Socket::AF_INET, b.family) - assert_equal(false, b.ipv4_mapped?) - - a = IPAddr.new("192.168.1.2") - b = a.ipv4_mapped - assert_equal("::ffff:192.168.1.2", b.to_s) - assert_equal(Socket::AF_INET6, b.family) - end - - def test_reverse - assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").reverse) - assert_equal("1.2.168.192.in-addr.arpa", IPAddr.new("192.168.2.1").reverse) - end - - def test_ip6_arpa - assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").ip6_arpa) - assert_raises(ArgumentError) { - IPAddr.new("192.168.2.1").ip6_arpa - } - end - - def test_ip6_int - assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int", IPAddr.new("3ffe:505:2::f").ip6_int) - assert_raises(ArgumentError) { - IPAddr.new("192.168.2.1").ip6_int - } - end - - def test_to_s - assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0001", IPAddr.new("3ffe:505:2::1").to_string) - assert_equal("3ffe:505:2::1", IPAddr.new("3ffe:505:2::1").to_s) - end -end - -class TC_Operator < Test::Unit::TestCase - - IN6MASK32 = "ffff:ffff::" - IN6MASK128 = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" - - def setup - @in6_addr_any = IPAddr.new() - @a = IPAddr.new("3ffe:505:2::/48") - @b = IPAddr.new("0:0:0:1::") - @c = IPAddr.new(IN6MASK32) +unless Socket.const_defined? :AF_INET6 + class Socket < BasicSocket + # IPv6 protocol family + AF_INET6 = Object.new.freeze end - alias set_up setup - def test_or - assert_equal("3ffe:505:2:1::", (@a | @b).to_s) - a = @a - a |= @b - assert_equal("3ffe:505:2:1::", a.to_s) - assert_equal("3ffe:505:2::", @a.to_s) - assert_equal("3ffe:505:2:1::", - (@a | 0x00000000000000010000000000000000).to_s) - end - - def test_and - assert_equal("3ffe:505::", (@a & @c).to_s) - a = @a - a &= @c - assert_equal("3ffe:505::", a.to_s) - assert_equal("3ffe:505:2::", @a.to_s) - assert_equal("3ffe:505::", (@a & 0xffffffff000000000000000000000000).to_s) - end - - def test_shift_right - assert_equal("0:3ffe:505:2::", (@a >> 16).to_s) - a = @a - a >>= 16 - assert_equal("0:3ffe:505:2::", a.to_s) - assert_equal("3ffe:505:2::", @a.to_s) - end - - def test_shift_left - assert_equal("505:2::", (@a << 16).to_s) - a = @a - a <<= 16 - assert_equal("505:2::", a.to_s) - assert_equal("3ffe:505:2::", @a.to_s) - end - - def test_carrot - a = ~@in6_addr_any - assert_equal(IN6MASK128, a.to_s) - assert_equal("::", @in6_addr_any.to_s) - end - - def test_equal - assert_equal(true, @a == IPAddr.new("3ffe:505:2::")) - assert_equal(false, @a == IPAddr.new("3ffe:505:3::")) - assert_equal(true, @a != IPAddr.new("3ffe:505:3::")) - assert_equal(false, @a != IPAddr.new("3ffe:505:2::")) - end - - def test_mask - a = @a.mask(32) - assert_equal("3ffe:505::", a.to_s) - assert_equal("3ffe:505:2::", @a.to_s) - end + class << IPSocket + private + + def valid_v6?(addr) # :nodoc: + case addr + when IPAddr::RE_IPV6ADDRLIKE_FULL + if $2 + $~[2,4].all? {|i| i.to_i < 256 } + else + true + end + when IPAddr::RE_IPV6ADDRLIKE_COMPRESSED + if $4 + addr.count(':') <= 6 && $~[4,4].all? {|i| i.to_i < 256} + else + addr.count(':') <= 7 + end + else + false + end + end - def test_include? - assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::"))) - assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::1"))) - assert_equal(false, @a.include?(IPAddr.new("3ffe:505:3::"))) - net1 = IPAddr.new("192.168.2.0/24") - assert_equal(true, net1.include?(IPAddr.new("192.168.2.0"))) - assert_equal(true, net1.include?(IPAddr.new("192.168.2.255"))) - assert_equal(false, net1.include?(IPAddr.new("192.168.3.0"))) - # test with integer parameter - int = (192 << 24) + (168 << 16) + (2 << 8) + 13 + alias getaddress_orig getaddress - assert_equal(true, net1.include?(int)) - assert_equal(false, net1.include?(int+255)) + public + # Returns a +String+ based representation of a valid DNS hostname, + # IPv4 or IPv6 address. + # + # IPSocket.getaddress 'localhost' #=> "::1" + # IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255" + # IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68" + # IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122" + def getaddress(s) + if valid_v6?(s) + s + else + getaddress_orig(s) + end + end end - end |
