[ruby/resolv] Bind the random UDP source port exclusively on Windows

TestResolvDNS#test_query_ipv4_address occasionally fails on Windows CI:

  1) Failure:
  TestResolvDNS#test_query_ipv4_address [C:/a/ruby/ruby/src/test/resolv/test_dns.rb:236]:
  <1> expected but was
  <0>.

On Windows, Resolv picks a random source port by itself and binds
0.0.0.0 to it.  Windows allows this even when another socket has
already bound 127.0.0.1 to the same port, and then delivers datagrams
sent to 127.0.0.1 to that other socket.  When the chosen port collides
like this, the reply from the nameserver on 127.0.0.1 never reaches the
resolver, which waits out every timeout and returns no resources.

This commit sets SO_EXCLUSIVEADDRUSE before binding so that bind fails
on a port another socket already uses, and retries with another port.
Ruby does not define Socket::SO_EXCLUSIVEADDRUSE, so its value,
~SO_REUSEADDR, is used directly.

https://github.com/ruby/resolv/commit/ce593abc84

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yusuke EndohandClaude Opus 5.5 authored and git committed 2026-10-14 05:46:26 +00:00
1 parent 6095c59c7e
commit daa307aa87
1 file changed
+13
+13
View File
@@ -691,6 +691,19 @@ class Resolv
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
udpsock.bind(bind_host, 0)
end
when /mswin|mingw/
# Sequential port assignment
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
# SO_EXCLUSIVEADDRUSE: make bind fail if another socket already uses the port
udpsock.setsockopt(Socket::SOL_SOCKET, ~Socket::SO_REUSEADDR, 1)
begin
# Ephemeral port number range recommended by RFC 6056
port = random(1024..65535)
udpsock.bind(bind_host, port)
rescue Errno::EADDRINUSE, Errno::EACCES
retry
end
end
else
# Sequential port assignment
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: