diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index c21c0cbf12..48141198ef 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -231,7 +231,13 @@ 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 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 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. @@ -459,11 +465,12 @@ class IPAddr 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 netmask in string format e.g. 255.255.0.0 @@ -471,6 +478,31 @@ class IPAddr _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 # Set +@addr+, the internal stored ip address, to given +addr+. The @@ -579,6 +611,11 @@ class IPAddr 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) @@ -593,6 +630,7 @@ class IPAddr @addr = in6_addr(prefix) @family = Socket::AF_INET6 end + @zone_id = zone_id if family != Socket::AF_UNSPEC && @family != family raise AddressFamilyError, "address family mismatch" end diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index c055f4b2c4..029ad06642 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -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(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%ab0", 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 @@ 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.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") } @@ -231,6 +243,18 @@ class TC_IPAddr < Test::Unit::TestCase a = IPAddr.new("192.168.1.2/24") assert_equal(a.netmask, "255.255.255.0") 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