diff options
Diffstat (limited to 'lib/ipaddr.rb')
| -rw-r--r-- | lib/ipaddr.rb | 134 |
1 files changed, 102 insertions, 32 deletions
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 03e1c18baa..70b804f642 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,7 +40,8 @@ require 'socket' # p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr - VERSION = "1.2.6" + # The version string + VERSION = "1.2.9" # 32 bit mask for IPv4 IN4MASK = 0xffffffff @@ -52,7 +53,7 @@ class IPAddr # Regexp _internally_ used for parsing IPv4 address. RE_IPV4ADDRLIKE = %r{ \A - (\d+) \. (\d+) \. (\d+) \. (\d+) + \d+ \. \d+ \. \d+ \. \d+ \z }x @@ -110,8 +111,13 @@ 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 addr.unpack('C4').join('.') when 16 @@ -146,8 +152,22 @@ 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) + if other.nil? + return false + end + other = coerce_other(other) rescue false @@ -222,6 +242,28 @@ class IPAddr 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. def hton case @family @@ -247,12 +289,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 >> 32 == 0xffff && ( + @addr & 0xff000000 == 0x7f000000 # ::ffff:127.0.0.1/8 + )) else raise AddressFamilyError, "unsupported address family" end @@ -271,10 +318,10 @@ class IPAddr @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 >> 32 == 0xffff && ( @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 + @addr & 0xfff00000 == 0xac100000 || # ::ffff:172.16.0.0/12 + @addr & 0xffff0000 == 0xc0a80000 # ::ffff:192.168.0.0/16 )) else raise AddressFamilyError, "unsupported address family" @@ -282,15 +329,19 @@ class IPAddr 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 >> 32 == 0xffff && ( + @addr & 0xffff0000 == 0xa9fe0000 # ::ffff:169.254.0.0/16 + )) else raise AddressFamilyError, "unsupported address family" end @@ -307,7 +358,7 @@ class IPAddr _ipv4_compat? end - def _ipv4_compat? + def _ipv4_compat? # :nodoc: if !ipv6? || (@addr >> 32) != 0 return false end @@ -321,7 +372,7 @@ class IPAddr # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) @@ -333,9 +384,11 @@ class IPAddr def ipv4_compat warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + 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 @@ -364,7 +417,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.arpa" end @@ -372,7 +425,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.int" end @@ -432,7 +485,7 @@ class IPAddr when Integer mask!(prefix) else - raise InvalidPrefixError, "prefix must be an integer: #{@addr}" + raise InvalidPrefixError, "prefix must be an integer" end end @@ -457,6 +510,20 @@ class IPAddr _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 @@ -483,6 +550,7 @@ class IPAddr end protected + # :stopdoc: def begin_addr @addr & @mask_addr @@ -498,6 +566,7 @@ class IPAddr 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, @@ -506,11 +575,11 @@ class IPAddr case family[0] ? family[0] : @family when Socket::AF_INET if addr < 0 || addr > IN4MASK - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{addr}" end when Socket::AF_INET6 if addr < 0 || addr > IN6MASK - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{addr}" end else raise AddressFamilyError, "unsupported address family" @@ -537,12 +606,12 @@ class IPAddr else m = IPAddr.new(mask) if m.family != @family - raise InvalidPrefixError, "address family is not same: #{@addr}" + 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}: #{@addr}" + raise InvalidPrefixError, "invalid mask #{mask}" end @addr &= @mask_addr return self @@ -553,13 +622,13 @@ class IPAddr case @family when Socket::AF_INET if prefixlen < 0 || prefixlen > 32 - raise InvalidPrefixError, "invalid length: #{@addr}" + raise InvalidPrefixError, "invalid length" end masklen = 32 - prefixlen @mask_addr = ((IN4MASK >> masklen) << masklen) when Socket::AF_INET6 if prefixlen < 0 || prefixlen > 128 - raise InvalidPrefixError, "invalid length: #{@addr}" + raise InvalidPrefixError, "invalid length" end masklen = 128 - prefixlen @mask_addr = ((IN6MASK >> masklen) << masklen) @@ -639,6 +708,7 @@ class IPAddr end end + # :stopdoc: def coerce_other(other) case other when IPAddr @@ -655,12 +725,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: #{@addr}" - s.match(/\A0./) and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" + (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 @@ -677,19 +747,19 @@ class IPAddr right = '' when RE_IPV6ADDRLIKE_COMPRESSED if $4 - left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" + 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: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" left = $1 right = $2 addr = 0 end else - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" end l = left.split(':') r = right.split(':') @@ -750,7 +820,7 @@ unless Socket.const_defined? :AF_INET6 class << IPSocket private - def valid_v6?(addr) + def valid_v6?(addr) # :nodoc: case addr when IPAddr::RE_IPV6ADDRLIKE_FULL if $2 |
