1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[ruby/ipaddr] Support zone identifiers in IPv6 addresses

These are supported by Ruby's socket library if the operating system
supports zone indentifiers, so they should be supported by ipaddr.
See RFCs 4007 and 6874 for additional information.

Implements Ruby Feature #10911

09a6408fb2
This commit is contained in:
Jeremy Evans 2020-07-13 09:36:06 -07:00 committed by Hiroshi SHIBATA
parent 74ed881e10
commit bd6e1a0f08
No known key found for this signature in database
GPG key ID: F9CF13417264FAC2
2 changed files with 67 additions and 5 deletions

View file

@ -231,7 +231,13 @@ class IPAddr
# Returns a string containing the IP address representation in # Returns a string containing the IP address representation in
# canonical form. # canonical form.
def to_string 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 end
# Returns a network byte ordered string form of the IP address. # Returns a network byte ordered string form of the IP address.
@ -403,7 +409,7 @@ class IPAddr
# Returns a hash value used by Hash, Set, and Array classes # Returns a hash value used by Hash, Set, and Array classes
def hash def hash
return ([@addr, @mask_addr].hash << 1) | (ipv4? ? 0 : 1) return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1)
end end
# Creates a Range object for the network address. # Creates a Range object for the network address.
@ -459,11 +465,12 @@ class IPAddr
af = "IPv4" af = "IPv4"
when Socket::AF_INET6 when Socket::AF_INET6
af = "IPv6" af = "IPv6"
zone_id = @zone_id.to_s
else else
raise AddressFamilyError, "unsupported address family" raise AddressFamilyError, "unsupported address family"
end end
return sprintf("#<%s: %s:%s/%s>", self.class.name, return sprintf("#<%s: %s:%s%s/%s>", self.class.name,
af, _to_string(@addr), _to_string(@mask_addr)) af, _to_string(@addr), zone_id, _to_string(@mask_addr))
end end
# Returns the netmask in string format e.g. 255.255.0.0 # Returns the netmask in string format e.g. 255.255.0.0
@ -471,6 +478,31 @@ class IPAddr
_to_string(@mask_addr) _to_string(@mask_addr)
end 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 protected
# Set +@addr+, the internal stored ip address, to given +addr+. The # Set +@addr+, the internal stored ip address, to given +addr+. The
@ -579,6 +611,11 @@ class IPAddr
prefix = $1 prefix = $1
family = Socket::AF_INET6 family = Socket::AF_INET6
end 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. # It seems AI_NUMERICHOST doesn't do the job.
#Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil,
# Socket::AI_NUMERICHOST) # Socket::AI_NUMERICHOST)
@ -593,6 +630,7 @@ class IPAddr
@addr = in6_addr(prefix) @addr = in6_addr(prefix)
@family = Socket::AF_INET6 @family = Socket::AF_INET6
end end
@zone_id = zone_id
if family != Socket::AF_UNSPEC && @family != family if family != Socket::AF_UNSPEC && @family != family
raise AddressFamilyError, "address family mismatch" raise AddressFamilyError, "address family mismatch"
end end

View file

@ -43,6 +43,17 @@ class TC_IPAddr < Test::Unit::TestCase
assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string)
assert_equal(Socket::AF_INET6, a.family) assert_equal(Socket::AF_INET6, a.family)
assert_equal(48, a.prefix) assert_equal(48, a.prefix)
assert_nil(a.zone_id)
a = IPAddr.new("fe80::1%ab0")
assert_equal("fe80::1%ab0", a.to_s)
assert_equal("fe80:0000:0000:0000:0000:0000:0000:0001%ab0", a.to_string)
assert_equal(Socket::AF_INET6, a.family)
assert_equal(false, a.ipv4?)
assert_equal(true, a.ipv6?)
assert_equal("#<IPAddr: IPv6:fe80:0000:0000:0000:0000:0000:0000:0001%ab0/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>", a.inspect)
assert_equal(128, a.prefix)
assert_equal('%ab0', a.zone_id)
a = IPAddr.new("0.0.0.0") a = IPAddr.new("0.0.0.0")
assert_equal("0.0.0.0", a.to_s) assert_equal("0.0.0.0", a.to_s)
@ -87,7 +98,8 @@ class TC_IPAddr < Test::Unit::TestCase
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.256") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.256") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.011") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.011") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%fxp0") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%]") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[2001:200:300::]\nINVALID") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[2001:200:300::]\nINVALID") }
assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32\nINVALID") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32\nINVALID") }
@ -231,6 +243,18 @@ class TC_IPAddr < Test::Unit::TestCase
a = IPAddr.new("192.168.1.2/24") a = IPAddr.new("192.168.1.2/24")
assert_equal(a.netmask, "255.255.255.0") assert_equal(a.netmask, "255.255.255.0")
end end
def test_zone_id
a = IPAddr.new("192.168.1.2")
assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%ab0' }
assert_raise(IPAddr::InvalidAddressError) { a.zone_id }
a = IPAddr.new("1:2:3:4:5:6:7:8")
a.zone_id = '%ab0'
assert_equal('%ab0', a.zone_id)
assert_equal("1:2:3:4:5:6:7:8%ab0", a.to_s)
assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%' }
end
end end
class TC_Operator < Test::Unit::TestCase class TC_Operator < Test::Unit::TestCase