NAME
IO::Socket::HappyEyeballs - RFC 8305 Happy Eyeballs v2 connection algorithm
VERSION
version 0.002
SYNOPSIS
# Direct usage:
use IO::Socket::HappyEyeballs;
my $sock = IO::Socket::HappyEyeballs->new(
PeerHost => 'www.example.com',
PeerPort => 80,
);
die "Cannot connect: $@" unless $sock;
print $sock "GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n";
# Global override — makes ALL IO::Socket::IP connections use Happy Eyeballs:
use IO::Socket::HappyEyeballs -override;
# Now any code using IO::Socket::IP gets Happy Eyeballs automatically,
# including LWP::UserAgent, HTTP::Tiny, Net::Async::HTTP, etc.
use HTTP::Tiny;
my $response = HTTP::Tiny->new->get('http://www.example.com');
DESCRIPTION
This module implements the Happy Eyeballs algorithm for establishing TCP connections to dual-stack hosts (hosts reachable via both IPv4 and IPv6).
This module was created because David Leadbeater (DGL) needed it.
The problem
As the internet transitions from IPv4 to IPv6, many hosts are reachable via both protocols ("dual-stack"). A naive client that tries IPv6 first will experience long timeouts (typically 30-75 seconds) when IPv6 connectivity is broken — even though IPv4 would work instantly. This is a common situation: a host publishes AAAA records but the user's network path to that host over IPv6 is broken somewhere along the way.
The solution: Happy Eyeballs
The Happy Eyeballs algorithm (originally specified in RFC 6555, updated in RFC 8305) solves this by racing connection attempts in parallel:
- 1. Resolve the hostname to all available addresses (both AAAA and A records)
- 2. Sort the addresses with interleaving — IPv6 first, then alternate between families (e.g. IPv6, IPv4, IPv6, IPv4, ...)
- 3. Start connecting to the first address (typically IPv6)
- 4. Wait 250ms — if not connected yet, start connecting to the next address (typically IPv4) in parallel
- 5. Continue starting new attempts every 250ms while previous ones are still pending
- 6. Return the first socket that successfully connects, close all others
- 7. Cache the winning address family so future connections try it first
The 250ms delay is called the Connection Attempt Delay. It is short enough to avoid noticeable lag, but long enough to give the preferred address family (IPv6) a fair chance to connect first.
RFC compliance
This module implements RFC 8305 ("Happy Eyeballs Version 2: Better Connectivity Using Concurrency"), which supersedes the original RFC 6555 ("Happy Eyeballs: Success with Dual-Stack Hosts").
Key RFC 8305 features implemented:
Address interleaving (Section 4) — alternating address families
Connection attempt delay (Section 5) — 250ms default, configurable
First-wins connection racing — parallel non-blocking connects via
select()Address family caching (Section 5.2) — successful family is remembered
Last Resort Local Synthesis (Section 7.2) — handles broken AAAA records via NAT64 synthesis fallback
Using the -override import flag
The most powerful way to use this module is with the -override flag:
use IO::Socket::HappyEyeballs -override;
This transparently replaces IO::Socket::IP->new() with the Happy Eyeballs algorithm for all outgoing TCP connections in the entire process. Any library that uses IO::Socket::IP internally — including HTTP::Tiny, LWP::UserAgent, Net::Async::HTTP, IO::Async, and many others — will automatically benefit.
Only outgoing TCP connections (those with PeerHost/PeerAddr and PeerPort) are intercepted. Listening sockets, UDP sockets, and Unix domain sockets are passed through to the original IO::Socket::IP unchanged.
ConnectionAttemptDelay
Time in seconds to wait before starting the next connection attempt. Defaults to 0.250 (250ms) per RFC 8305 Section 5. Can be passed to new().
Timeout
Overall connection timeout in seconds. Defaults to 30.
new
my $sock = IO::Socket::HappyEyeballs->new(%args);
Creates a new socket connection using the Happy Eyeballs v2 algorithm (RFC 8305). Accepts the same arguments as IO::Socket::IP plus:
- ConnectionAttemptDelay
-
Delay in seconds between connection attempts (default: 0.250).
Returns the connected socket on success, or undef on failure with $@ set to an error message.
clear_cache
IO::Socket::HappyEyeballs->clear_cache;
Clears the internal address family preference cache.
connection_attempt_delay
IO::Socket::HappyEyeballs->connection_attempt_delay(0.300);
my $delay = IO::Socket::HappyEyeballs->connection_attempt_delay;
Get/set the default connection attempt delay in seconds. The default is 0.250 (250ms) as recommended by RFC 8305 Section 5.
cache_ttl
IO::Socket::HappyEyeballs->cache_ttl(300);
my $ttl = IO::Socket::HappyEyeballs->cache_ttl;
Get/set the address family cache TTL in seconds. The default is 600 (10 minutes). When a successful connection is made, the winning address family (IPv4 or IPv6) is cached for this duration. Subsequent connections to the same host:port will try the cached family first.
last_resort_delay
IO::Socket::HappyEyeballs->last_resort_delay(3);
my $delay = IO::Socket::HappyEyeballs->last_resort_delay;
Get/set the Last Resort Local Synthesis Delay in seconds per RFC 8305 Section 7.2. The default is 2 seconds. This is the time to wait after the last connection attempt before falling back to A-record-only resolution with NAT64 address synthesis. This handles the case of hostnames with broken AAAA records on IPv6-only networks with NAT64/DNS64.
SEE ALSO
RFC 8305 — Happy Eyeballs Version 2: Better Connectivity Using Concurrency
IO::Socket::IP — the parent class
IO::Socket::INET — basic IPv4 socket class (no dual-stack support)
IO::Socket::Happpy::EyeBalls — earlier Happy Eyeballs implementation that this module builds upon (not uploaded to CPAN)
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-io-socket-happyeyeballs/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudss.us>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.