summaryrefslogtreecommitdiff
path: root/lib/ipaddr.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ipaddr.rb')
-rw-r--r--lib/ipaddr.rb119
1 files changed, 83 insertions, 36 deletions
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb
index 5df798f5d7..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,8 +110,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
@@ -175,9 +181,7 @@ class IPAddr
def include?(other)
other = coerce_other(other)
return false unless other.family == family
- range = to_range
- other = other.to_range
- range.begin <= other.begin && range.end >= other.end
+ begin_addr <= other.begin_addr && end_addr >= other.end_addr
end
alias === include?
@@ -223,6 +227,12 @@ class IPAddr
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.
def hton
case @family
@@ -248,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
@@ -262,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
@@ -270,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
@@ -399,18 +424,7 @@ class IPAddr
# 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.
@@ -438,7 +452,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
@@ -463,6 +477,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
@@ -490,6 +518,21 @@ class IPAddr
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+.
@@ -497,11 +540,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"
@@ -509,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
@@ -525,12 +571,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
@@ -541,13 +587,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)
@@ -579,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
@@ -642,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: #{@addr}"
- s.match(/\A0./) and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}"
+ (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}"
i << 8 | n
}
end
@@ -731,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