Bug #22538
closedrequire 'socket' raises NameError for builds with --disable-ipv6, and IPv6 addresses leak from the resolver
Description
On a Ruby built with ./configure --disable-ipv6, require 'socket' fails:
$ ruby -rsocket -e0
.../socket.rb:622:in '<class:Socket>': uninitialized constant Socket::AF_INET6 (NameError)
from .../socket.rb:478:in '<top (required)>'
Reproduced with:
- ruby 3.4.10 (2026-06-30 revision 2b0b7728dc) +PRISM [arm64-darwin25]
- ruby 4.1.0dev (2026-11-12T01:39:13Z master 0738c9dfaa) +PRISM [arm64-darwin25]
Cause¶
With --disable-ipv6, INET6 is not defined, so mkconstants.rb omits
AF_INET6, PF_INET6 and IPV6_*. Since Happy Eyeballs v2 was added to
Socket.tcp (Ruby 3.4), socket.rb defines a class-level constant that
references Socket::AF_INET6 without a defined? check:
Further problems once the require is fixed¶
--disable-ipv6 only removes the Ruby-level IPv6 constants. The system
resolver still returns IPv6 addresses (with AF_UNSPEC hints), and
getifaddrs still reports IPv6 interface addresses. Code that skips IPv6
handling with defined?(Socket::AF_INET6) then gets IPv6 sockets anyway:
Socket.tcp_server_sockets(0)never returns.getaddrinfo(nil, 0, ..., AI_PASSIVE)
returns::and0.0.0.0;ipv6only!is a no-op because
Socket::IPV6_V6ONLYis not defined, so the[::]:Psocket is
dual-stack, binding0.0.0.0:Pfails withEADDRINUSE, and
ip_sockets_port0retries forever. This also hangs
TestSocket#test_accept_loop_with_unix.Socket.tcp(host, port, nil, 0)(local port only) resolves::1and
127.0.0.1locally;ADDRESS_FAMILIES.key(AF_INET6)isnil, so the
resolver thread runs with anilfamily.Addrinfo#connect_addressdoes not turn[::]into[::1](that branch
is guarded bydefined?(Socket::AF_INET6)), and connecting to it fails
withENOBUFSon macOS.Socket.ip_address_listandSocket.udp_server_socketsreturn IPv6
addresses.TCPSocket.new(C Happy Eyeballs) starts an IPv6 resolver thread and then
discards its results.
Proposed fix¶
Make --disable-ipv6 consistently IPv4-only:
socket.rb: only add:ipv6toADDRESS_FAMILIESwhenSocket::AF_INET6
is defined.raddrinfo.c(rsock_getaddrinfo, which every Ruby-level lookup goes
through): whenINET6is not defined, changePF_UNSPEChints toPF_INET
and rejectPF_INET6withEAI_FAMILY.ipsocket.c: resolve only IPv4 in theTCPSocket.newfast fallback path.socket.c: skipAF_INET6in the getifaddrs and ioctl implementations of
Socket.ip_address_list. The Windows implementation already does this.- Tests: skip tests that need IPv6 (
::1servers, IPv6 addresses); make
stubbedSocket.tcpandTCPSocket.newtests work whenSocket::AF_INET6
is undefined.
User-visible change, only in --disable-ipv6 builds: IPv6 literals such as
Addrinfo.ip("::1") or TCPSocket.new("::1", port) raise
Socket::ResolutionError instead of partly working.
I will open a pull request.
Note on backporting to 3.4¶
On 3.4, fixing ADDRESS_FAMILIES alone is not enough. With only one family
to resolve, Socket.tcp_with_fast_fallback passes the Symbol :ipv4 to
Addrinfo.getaddrinfo, which raises unknown socket domain: ipv4
(#21512). A 3.4 backport also needs both fixes for that issue:
b896f80598 and its follow-up f33766720c. The first commit alone passes
ADDRESS_FAMILIES[:family_name], i.e. nil, so it silently resolves all
families.