Project

General

Profile

Feature #10911

IPAddr.new should ignore zone identifiers

Added by postmodern (Hal Brodigan) over 5 years ago. Updated 5 days ago.

Status:
Assigned
Priority:
Normal
Target version:
-
[ruby-core:68331]

Description

Link local IPv6 addresses may have a zone identifier suffix:

fe80::1%lo0

IPAddr.new currently does not ignore the zone identifier and raises IPAddr::InvalidAddressError.


Files

ipaddr-ipv6-zone-id-10911.patch (5.17 KB) ipaddr-ipv6-zone-id-10911.patch jeremyevans0 (Jeremy Evans), 10/31/2019 04:16 PM

Updated by hsbt (Hiroshi SHIBATA) about 1 year ago

  • Assignee set to knu (Akinori MUSHA)
  • Status changed from Open to Assigned

Updated by jeremyevans0 (Jeremy Evans) 7 months ago

I don't think this is a bug. The decision to not support zone identifiers seems deliberate as there are tests that using a zone identifier raises an exception:

assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%fxp0") }

Still, I think this would be a useful feature to add. We should not ignore the zone identifier, as it a property of the address. Attached is a patch that implements support for it, so that to_s and inspect will show the zone identifier. It doesn't consider the zone identifier when determining equality, just as the mask_addr is not currently considered for that. That may be a bug (#11531), but at least it is consistent.

Updated by Dan0042 (Daniel DeLorme) 7 months ago

Looks like this testcase was in the original IPAddr commit from 2002:

commit 9ec0a96ad4235f2054976eab6c04efbe62b3c703
Author: knu <knu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date:   Mon Dec 23 17:07:49 2002 +0000

    * MANIFEST, lib/README, lib/ipaddr.rb: Add ipaddr.rb from rough.

Maybe knu (Akinori MUSHA) remembers why? I'm sure there must have been a reason.

But I think this could break the contract in other places that accept a IPv6 address but not a zone identifier. ex:

Socket.getaddrinfo("fe80::1%fxp0", nil) #=> SocketError (getaddrinfo: Name or service not known)
Socket.getaddrinfo("fe80::1", nil)      #=> [["AF_INET6", 0, "fe80::1", "fe80::1", 10, 1, 6], ["AF_INET6", 0, "fe80::1", "fe80::1", 10, 2, 17], ["AF_INET6", 0, "fe80::1", "fe80::1", 10, 3, 0]]

TCPSocket.new("fe80::1%fxp0", 42) #=> SocketError (getaddrinfo: Name or service not known)
TCPSocket.new("fe80::1", 42)      #=> Errno::EINVAL (Invalid argument - connect(2) for "fe80::1" port 42)

So "fe80::1%fxp0" is not even recognized as an IP address. This could result in unpleasantness in cases like this:

str = "fe80::1%fxp0"
ip = IPAddr.new(str) rescue nil   #validate IP address... ok!
TCPSocket.new(ip.to_s, 42) if ip  #and connect... fail!?

Updated by jeremyevans0 (Jeremy Evans) 7 months ago

Dan0042 (Daniel DeLorme) wrote:

So "fe80::1%fxp0" is not even recognized as an IP address. This could result in unpleasantness in cases like this:

str = "fe80::1%fxp0"
ip = IPAddr.new(str) rescue nil   #validate IP address
TCPSocket.new(ip.to_s, 42) if ip  #and connect

Good point. We would probably want to fix socket to support this before adding support to ipaddr. I think it is reasonable for socket to support it, as tools dealing with IPv6 should handle zone identifiers. I tried ping6 and traceroute6:

$ ping6 fe80::1%lo0
PING fe80::1%lo0 (fe80::1%lo0): 56 data bytes
64 bytes from fe80::1%lo0: icmp_seq=0 hlim=64 time=0.630 ms
$ traceroute6 fe80::1
traceroute6 to fe80::1%lo (fe80::1%lo0), 64 hops max, 60 byte packets
 1  fe80::1%lo0 (fe80::1%lo0)  0.162 ms  0.098 ms  0.091 ms

Ignoring the zone identifier is not valid:

$ ping6 fe80::1
PING fe80::1 (fe80::1): 56 data bytes
ping6: sendmsg: Network is unreachable
$ traceroute6 fe80::1
traceroute6 to fe80::1 (fe80::1), 64 hops max, 60 byte packets
traceroute6: sendto: Network is unreachable

Updated by jeremyevans0 (Jeremy Evans) 5 days ago

jeremyevans0 (Jeremy Evans) wrote in #note-4:

Dan0042 (Daniel DeLorme) wrote:

So "fe80::1%fxp0" is not even recognized as an IP address. This could result in unpleasantness in cases like this:

str = "fe80::1%fxp0"
ip = IPAddr.new(str) rescue nil   #validate IP address
TCPSocket.new(ip.to_s, 42) if ip  #and connect

Good point. We would probably want to fix socket to support this before adding support to ipaddr.

It turns out that the socket library already supports this, at least on my operating system (and I would guess other operating systems supporting IPv6 zone identifiers). If I get the IPv6 loopback address with the zone identifier, and use nc to create a listening socket:

$ ifconfig lo0 | fgrep fe80
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x3
$ nc -l fe80::1%lo0 1042

I can use that address with Ruby to write to it, as well as get the addrinfo:

require 'socket'
t = TCPSocket.new("fe80::1%lo0", 1042)
t.write("foo\n")
t.close

Socket.getaddrinfo("fe80::1%lo0", nil)
# => [["AF_INET6", 0, "fe80::1%lo0", "fe80::1%lo0", 24, 2, 17], ["AF_INET6", 0, "fe80::1%lo0", "fe80::1%lo0", 24, 1, 6]]

And foo gets printed to the stdout of the nc process. I've tested and this works back to Ruby 1.8.

So I don't think we need changes to socket, and I think it we should apply the patch to ipaddr (it still applies without conflicts).

Also available in: Atom PDF