From 353b920d754e3d02f387f20fc05edffce2fb5742 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 31 Oct 2019 09:13:23 -0700 Subject: [PATCH] Support zone identifiers in IPAddr Implements [Feature #10911] --- lib/ipaddr.rb | 42 +++++++++++++++++++++++++++++++++++++++--- test/test_ipaddr.rb | 26 +++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 7fff54b9d0..400e8d2d91 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -226,6 +226,10 @@ def to_s str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256) end + if @family == Socket::AF_INET6 + str << zone_id.to_s + end + str end @@ -404,7 +408,7 @@ def eql?(other) # 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. @@ -460,11 +464,37 @@ def inspect 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 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 @@ -572,6 +602,11 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC) 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) @@ -585,6 +620,7 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC) if !@addr && (family == Socket::AF_UNSPEC || family == Socket::AF_INET6) @addr = in6_addr(prefix) @family = Socket::AF_INET6 + @zone_id = zone_id end if family != Socket::AF_UNSPEC && @family != family raise AddressFamilyError, "address family mismatch" diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 7b3a199bf3..ef5ebb60df 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -43,6 +43,17 @@ def test_s_new assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) assert_equal(Socket::AF_INET6, a.family) 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", a.to_string) + assert_equal(Socket::AF_INET6, a.family) + assert_equal(false, a.ipv4?) + assert_equal(true, a.ipv6?) + assert_equal("#", a.inspect) + assert_equal(128, a.prefix) + assert_equal('%ab0', a.zone_id) a = IPAddr.new("0.0.0.0") assert_equal("0.0.0.0", a.to_s) @@ -87,7 +98,8 @@ def test_s_new 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("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("[2001:200:300::]\nINVALID") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32\nINVALID") } @@ -200,6 +212,18 @@ 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 + + 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 class TC_Operator < Test::Unit::TestCase -- 2.23.0