Project

General

Profile

Bug #17112

Resolv.getaddress fails with IPv6 link-local addresses

Added by daniel-rikowski (Daniel Rikowski) about 2 months ago. Updated about 1 month ago.

Status:
Open
Priority:
Normal
Target version:
-
ruby -v:
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x64-mingw32]
[ruby-core:99539]

Description

I noticed that I cannot resolve any link-local IPv6 address using Resolv.getaddress. For example calling Resolv.getaddress('fe80::eca4:7b00:ecc5:206c%8') fails with Resolv::ResolvError
Resolving any IPv4 address succeedes, as well as resolving the loopback address ::1.

After some code digging I noticed that Resolv::DNS doesn't even attempt to resolve that IP address. It checks if there is any local IPv6 interface with an IP address which is not a loopback or a link-local address.

def each_address(name)
  each_resource(name, Resource::IN::A) {|resource| yield resource.address}
  if use_ipv6? # <===== false on my system
    each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
  end
end

use_ipv6? ultimately boils down to this:

Socket.ip_address_list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }

In other words if the system doesn't have a "real" IPv6 address, Resolv.getaddress cannot resolve link-local addresses. (::1 is resolved using Resolv::Hosts which doesn't perform the same check)

One could argue that IP addresses don't have to be resolved, and I could just perform a regex check and skip Resolv.getaddress if I already have an IP address.
Unfortunately that is not possible when using resolv-replace. In that case most (all?) of Ruby's address resolving is piped through Resolv.getaddress.
(That is how I noticed this bug: The RubyMine test runner uses drb which connects to a process on the same machine using a link-local IPv6 address. Since I'm using resolv-replace and I don't have a network interface with a non-loopback non-local-link address I encountered this bug.)

Platform:
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x64-mingw32]
Microsoft Windows [Version 10.0.18363.959]

Updated by daniel-rikowski (Daniel Rikowski) about 2 months ago

After some experiments I noticed that changing use_ipv6? isn't the whole story.

In Resolv#each_address there seems to be a check for IP addresses. In case an IP address is given no actual resolving is performed.

if AddressRegex =~ name
  yield name
  return
end

with

AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/

Unfortunately the regex check doesn't catch link-local addresses. It looks like the IPv6 regexes cannot process the zone ID suffix. (i.e. the interface number/name after the percentage sign, e.g. 'fe80::...%8' or 'fe80::...%eth0')

As a result a regular resolving is attempted which fails (at least on my system)

As a workaround I modified AddressRegex to detect link-local addresses and everything worked as expected:

require 'resolv'
Regex_CompressedHex_LinkLocal = /\A
      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) %[[:alnum:]]+
      \z/x
Resolv::AddressRegex = /(?:#{Resolv::IPv4::Regex})|(?:#{Resolv::IPv6::Regex})|(?:#{Regex_CompressedHex_LinkLocal})/

(Notice that the regexp is just a quick hack and not a proper link-local pattern. For example the fe80:: prefix isn't taken into account)

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

  • Assignee set to akr (Akira Tanaka)

I've added a pull request with a fix for this: https://github.com/ruby/ruby/pull/3452

Also available in: Atom PDF