| Internet-Draft | HEv3 qlog | July 2026 |
| Kühlewind | Expires 7 January 2027 | [Page] |
This document specifies a qlog extension for Happy Eyeballs v3 (HEv3), enabling logging of dual-stack and multi-protocol connection racing behavior. It defines a dedicated event schema, event names, and data structures that capture DNS resolution timing, SVCB/HTTPS service discovery, candidate sorting and grouping, connection attempt scheduling and racing, NAT64 prefix discovery, success/failure outcomes, and summary metrics.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://mirjak.github.io/draft-happy-glog/draft-kuehlewind-happy-glog.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-kuehlewind-happy-qlog/.¶
Discussion of this document takes place on the Heuristics and Algorithms to Prioritize Protocol deploYment Working Group mailing list (mailto:happy@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/happy/. Subscribe at https://www.ietf.org/mailman/listinfo/happy/.¶
Source for this draft and an issue tracker can be found at https://github.com/mirjak/draft-happy-glog.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 7 January 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Happy Eyeballs helps applications reduce connection latency on dual-stack networks. Happy Eyeballs v3 (HEv3) extends racing from IPv4/IPv6 to include TCP+TLS and QUIC/HTTP3, and incorporates SVCB/HTTPS service discovery. Detailed logging of HEv3 behavior enables operators and developers to diagnose connection establishment failures and identify network issues that would otherwise be hidden by the racing algorithm. This document defines a qlog event schema that provides logging and visibility into HEv3 decision-making and timing.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document proposes a qlog schema for HEv3 using the newly defined event schema urn:ietf:params:qlog:events:hev3.¶
This section is to be removed before publishing as an RFC.¶
Only implementations of the final, published RFC can use the events belonging to the event schema with the URI urn:ietf:params:qlog:events:hev3. Until such an RFC exists, implementations MUST NOT identify themselves using this URI.¶
Implementations of draft versions of the event schema MUST append the string "-" and the corresponding draft number to the URI. For example, draft 01 of this document is identified using the URI urn:ietf:params:qlog:events:hev3-01.¶
The namespace identifier itself is not affected by this requirement.¶
HEAttemptTarget = {
address: text
port: uint16
family: "ipv4" / "ipv6"
interface: text ?
path_id: text ?
alpn: [+ text] ?
service_name: text ?
service_priority: uint32 ?
ech_offered: bool ?
* $he-attempttarget-extension
}
¶
The following fields are defined:¶
address: The IP address of the target endpoint.¶
port: The destination port number.¶
family: The address family, either "ipv4" or "ipv6".¶
interface: The local network interface used for this attempt.¶
path_id: An implementation-defined identifier for the network path.¶
alpn: The negotiated ALPN set for this target derived from SVCB records.¶
service_name: The SVCB ServiceMode TargetName this target originates from.¶
service_priority: The SVCB SvcPriority value for this target's service.¶
ech_offered: Whether ECH will be attempted on this connection.¶
HEPolicy = {
resolution_delay_ms: uint32 ?
preferred_address_family_count: uint32 ?
connection_attempt_delay_ms: uint32 ?
min_connection_attempt_delay_ms: uint32 ?
max_connection_attempt_delay_ms: uint32 ?
max_parallel_attempts: uint32 ?
last_resort_local_synthesis_delay_ms: uint32 ?
preferred_family: "ipv4" / "ipv6" / "auto" ?
success_definition:
"tcp_connected" /
"tls_handshake_complete" /
"quic_1rtt_ready" ?
use_historical_rtt: bool ?
* $he-policy-extension
}
¶
The fields correspond to the configurable values defined in Section 9 of HEv3:¶
resolution_delay_ms: Time to wait for preferred-family and SVCB/HTTPS
records after receiving initial answers (recommended 50ms).¶
preferred_address_family_count: Number of addresses of the preferred
family attempted before trying the other family (recommended 1).¶
connection_attempt_delay_ms: Delay between connection attempts in the
absence of RTT data (recommended 250ms).¶
min_connection_attempt_delay_ms: Floor for the connection attempt delay
(recommended 100ms, must not be less than 10ms).¶
max_connection_attempt_delay_ms: Ceiling for the connection attempt delay
(recommended 2s).¶
max_parallel_attempts: Maximum number of connection attempts allowed
in parallel.¶
last_resort_local_synthesis_delay_ms: Time to wait before querying A
records for local NAT64 synthesis when AAAA attempts are failing on
IPv6-only networks (recommended 2s).¶
preferred_family: The address family assumed to have better connectivity.¶
success_definition: What constitutes a successful connection establishment.¶
use_historical_rtt: Whether historical RTT data is used to order
destinations and adjust attempt delays.¶
If omitted, success_definition defaults to:¶
DNS results can represent either address records (A/AAAA) or service records (SVCB/HTTPS).¶
HEDNSResult = HEDNSAddressResult / HEDNSServiceResult
HEDNSAddressResult = {
type: "address"
address: text
family: "ipv4" / "ipv6"
ttl_s: uint32 ?
* $he-dnsaddressresult-extension
}
HEDNSServiceResult = {
type: "service"
mode: "alias" / "servicemode"
target_name: text
priority: uint32 ?
alpn: [+ text] ?
no_default_alpn: bool ?
ech_config: text ?
ipv4hint: [+ text] ?
ipv6hint: [+ text] ?
ttl_s: uint32 ?
* $he-dnsserviceresult-extension
}
¶
The mode field distinguishes AliasMode records (which require a follow-up
query) from ServiceMode records that carry usable service parameters.¶
The following fields are defined for HEDNSAddressResult:¶
type: Always "address" for this variant.¶
address: The resolved IP address.¶
family: The address family, either "ipv4" or "ipv6".¶
ttl_s: The DNS TTL in seconds.¶
The following fields are defined for HEDNSServiceResult:¶
type: Always "service" for this variant.¶
mode: Either "alias" (AliasMode, requires follow-up query) or
"servicemode" (carries usable parameters).¶
target_name: The SVCB TargetName (use "." for the owner name).¶
priority: The SVCB SvcPriority value.¶
alpn: The list of supported application protocols from the alpn key.¶
no_default_alpn: Whether the no-default-alpn key is present.¶
ech_config: Base64-encoded ECHConfigList from the ech key.¶
ipv4hint: IPv4 address hints from the ipv4hint key.¶
ipv6hint: IPv6 address hints from the ipv6hint key.¶
ttl_s: The DNS TTL in seconds.¶
Each event uses:
name: "hev3:<event>"
with the <event> type identifier defined below in the section headings.¶
All events include a he_session_id field that uniquely identifies the
Happy Eyeballs session. This allows correlation of all events belonging
to a single connection establishment attempt.¶
Logged when the HE policy is configured or updated during a session.¶
HEConfigSet = {
he_session_id: text
policy: HEPolicy
reason:
"startup" /
"app_config" /
"network_change"
* $$he-configset-extension
}
¶
The policy field contains the policy configuration. The reason field
indicates what triggered the configuration:¶
"startup": Default policy applied at application start.¶
"app_config": Policy provided by the application.¶
"network_change": Policy updated in response to a network transition.¶
The first occurrence of this event in a session represents the initial
configuration; typical reasons are "startup" or "app_config".
Subsequent occurrences represent policy updates.¶
Logged when a DNS query is initiated as part of hostname resolution.¶
HEDNSQueryStarted = {
he_session_id: text
dns_id: text
hostname: text
qtypes: [+ "A" / "AAAA" / "SVCB" / "HTTPS"]
bootstrap_hint: text ?
* $$he-dnsquerystarted-extension
}
¶
The dns_id uniquely identifies this query within the session and is used
to correlate with the corresponding dns_query_finished event. The
hostname is the name being resolved. The qtypes array lists the DNS
record types being queried. The bootstrap_hint field optionally contains
an address hint used to reach the DNS resolver itself.¶
Logged when a DNS query completes with results, an error, or a negative response.¶
HEDNSQueryFinished = {
he_session_id: text
dns_id: text
hostname: text
results: [* HEDNSResult]
answer_type: "positive" / "negative" / "error" ?
error_code: text ?
error_message: text ?
duration_ms: uint32
dnssec_validated: bool ?
is_alias_follow: bool ?
* $$he-dnsqueryfinished-extension
}
¶
The answer_type field distinguishes positive (non-empty) from negative
(empty, no error) and error responses, which is significant for HEv3's
async resolution logic (Section 4.2). The dnssec_validated field indicates
whether the response was cryptographically validated, relevant for
determining whether SVCB-dependent handshakes must be pended (Section 6.3).
The is_alias_follow field is set to true when this query was triggered
by an AliasMode SVCB/HTTPS record requiring a follow-up resolution.¶
Logged when the client discovers a NAT64 prefix for use on an IPv6-only network (Section 8 of HEv3).¶
HENat64PrefixDiscovered = {
he_session_id: text
prefix: text
prefix_length: uint32
source: "pref64_ra" / "rfc7050_discovery" / "dns64_inferred"
* $$he-nat64prefixdiscovered-extension
}
¶
The source field indicates how the prefix was obtained:¶
HECandidateDiscovered = {
he_session_id: text
source: "dns" / "svcb_hint" / "nat64_synthesis"
target: HEAttemptTarget
group_id: text ?
supersedes: text ?
* $$he-candidatediscovered-extension
}
¶
The source value "dns" indicates the address came from A or AAAA
records. The "svcb_hint" value indicates the address came from ipv4hint
or ipv6hint parameters in a SVCB/HTTPS record. The "nat64_synthesis"
value indicates a locally synthesized IPv6 address for NAT64 traversal.¶
The group_id field identifies which protocol/priority group this candidate
belongs to (per Section 5.1 and 5.2 of HEv3). The supersedes field
contains the address of an SVCB hint that this candidate replaces once
authoritative A/AAAA records arrive (per Section 7 of HEv3).¶
Logged once sufficient DNS answers have been received (per Section 4.2 of HEv3) and the implementation has applied the three-level sorting algorithm (Section 5). This event captures the full grouped and ordered candidate list before racing begins.¶
HECandidatesSorted = {
he_session_id: text
groups: [+ HECandidateGroup]
* $$he-candidatessorted-extension
}
HECandidateGroup = {
group_id: text
alpn: [+ text] ?
ech_available: bool ?
service_priority: uint32 ?
candidates: [+ HEAttemptTarget]
}
¶
The groups array is ordered by priority. Within each group:¶
alpn captures the application protocol set that defines this group
(Section 5.1 of HEv3).¶
ech_available indicates whether ECH configuration is available for
endpoints in this group (Section 5.1 of HEv3).¶
service_priority reflects the SVCB SvcPriority value for this group
(Section 5.2 of HEv3). Groups with equal priority are shuffled
randomly.¶
candidates is ordered per Section 5.3 of HEv3: RFC 6724 destination
address selection, historical RTT preferences, and address family
interleaving applied.¶
For simple cases without SVCB records, a single group with all candidates is sufficient.¶
Logged when a candidate address is removed from the list during connection setup (per Section 7 of HEv3).¶
HECandidateRemoved = {
he_session_id: text
target: HEAttemptTarget
reason: "ttl_expired" / "svcb_hint_replaced" / "dns_negative" / "alpn_mismatch"
had_active_attempt: bool ?
* $$he-candidateremoved-extension
}
¶
The reason field indicates why the candidate was removed:¶
"ttl_expired": The DNS TTL for this address expired before an attempt
was started.¶
"svcb_hint_replaced": Authoritative A/AAAA records arrived and this
hint address was absent from them (Section 7.3 of SVCB).¶
"dns_negative": A previously positive record was replaced by a negative
response (e.g., via DNS push notification).¶
"alpn_mismatch": The SVCB ALPN set does not contain protocols the
client supports (Section 6.2 of HEv3).¶
The had_active_attempt field indicates whether an in-progress attempt
exists for this target; per HEv3 Section 7, such attempts are not
canceled.¶
Logged when the candidate list is re-sorted due to new addresses arriving mid-race (per Section 7 of HEv3). Re-sorting ensures that address family interleaving and priority rules are maintained correctly regardless of when addresses arrive.¶
HECandidatesResorted = {
he_session_id: text
reason: "new_addresses" / "new_svcb" / "dns_push"
new_order: [+ HEAttemptTarget]
* $$he-candidatesresorted-extension
}
¶
The reason field captures why re-sorting occurred:¶
"new_addresses": New A/AAAA records arrived (e.g., delayed IPv4 after
an IPv6-only start).¶
"new_svcb": New SVCB/HTTPS ServiceMode records changed grouping or
priorities.¶
"dns_push": A DNS push notification added addresses.¶
The new_order array contains the full re-sorted candidate list as
HEAttemptTarget elements. Position in the array implies the new rank.
Only pending candidates (those not yet attempted or currently in progress)
are included.¶
Logged when a connection attempt is scheduled to start after a delay.¶
HEAttemptScheduled = {
he_session_id: text
attempt_id: text
target: HEAttemptTarget
scheduled_after_ms: uint32
reason:
"policy_timer" /
"dns_completed" /
"resolution_delay_expired" /
"last_resort_synthesis"
* $$he-attemptscheduled-extension
}
¶
The attempt_id uniquely identifies this attempt within the session. The
scheduled_after_ms field indicates the delay before the attempt will
start. The reason field indicates what triggered scheduling:¶
"policy_timer": The Next Connection Attempt Timer fired.¶
"dns_completed": DNS resolution completed, enabling the first attempt.¶
"resolution_delay_expired": The Resolution Delay timer expired
(Section 4.2 of HEv3).¶
"last_resort_synthesis": The Last Resort Local Synthesis Delay expired,
triggering a fallback A query and NAT64 synthesis (Section 8.4 of HEv3).¶
Logged when a connection attempt begins (i.e., the first packet is sent).¶
HEAttemptStarted = {
he_session_id: text
attempt_id: text
target: HEAttemptTarget
transport: "tcp" / "quic"
ref_event_id: text ?
* $$he-attemptstarted-extension
}
¶
The transport field indicates the transport protocol used for this
attempt. The ref_event_id field may reference a related event in another
qlog event schema; QUIC implementations should set it to the relevant
connectivity:connection_started event.¶
Logged when a connection attempt's TLS handshake must be paused until SVCB/HTTPS responses are received, as required by Section 6.3 of HEv3 (e.g., when DNS is cryptographically protected and ECH configuration is expected from SVCB records).¶
HEAttemptPended = {
he_session_id: text
attempt_id: text
reason: "awaiting_svcb" / "awaiting_ech_config" / "dnssec_validation"
waiting_for: text ?
* $$he-attemptpended-extension
}
¶
The waiting_for field may reference the dns_id of the outstanding
SVCB/HTTPS query.¶
Logged when a previously pended attempt resumes its handshake.¶
HEAttemptResumed = {
he_session_id: text
attempt_id: text
reason: "svcb_received" / "timeout" / "policy_override"
* $$he-attemptresumed-extension
}
¶
The reason field indicates what unblocked the attempt:¶
Logged when a connection attempt reaches a terminal state.¶
HEAttemptOutcome = {
he_session_id: text
attempt_id: text
result: "success" / "failure" / "timeout" / "canceled"
error_code: text ?
connect_duration_ms: uint32 ?
* $$he-attemptoutcome-extension
}
¶
The result field indicates the outcome:¶
"success": The connection was established per the success_definition
in the policy.¶
"failure": The attempt failed (e.g., connection refused, TLS error).¶
"timeout": The attempt timed out without completing.¶
"canceled": The attempt was canceled because another attempt succeeded.¶
The error_code field contains a protocol-specific error code on failure.
The connect_duration_ms field records the time from attempt start to
outcome.¶
Logged when the Next Connection Attempt Timer is set or adjusted. This timer controls when the next connection attempt begins (Section 6 of HEv3).¶
HENextAttemptTimerSet = {
he_session_id: text
delay_ms: uint32
reason: "initial" / "rtt_based" / "handshake_progress" ?
* $$he-nextattemptimerset-extension
}
¶
The reason field captures why the delay was chosen:¶
Logged when the timer fires and the next connection attempt is triggered.¶
HENextAttemptTimerFired = {
he_session_id: text
* $$he-nextattemptimerfired-extension
}
¶
Logged when the timer is canceled before firing.¶
HENextAttemptTimerCanceled = {
he_session_id: text
reason: "success" / "abort" / "list_exhausted"
* $$he-nextattemptimercanceled-extension
}
¶
The reason field indicates why the timer was canceled:¶
Logged when a successful connection attempt is chosen as the winning connection for this HE session.¶
HEConnectionSelected = {
he_session_id: text
attempt_id: text
ref_event_id: text ?
* $$he-connectionselected-extension
}
¶
The attempt_id identifies which attempt won. The ref_event_id field
may reference the corresponding transport-level event (e.g., a QUIC
connectivity:connection_started or TLS handshake_complete).¶
Logged when the entire HE session fails without establishing any connection.¶
HEConnectionAborted = {
he_session_id: text
reason: text
* $$he-connectionaborted-extension
}
¶
The reason field contains a human-readable or implementation-defined
description of why the session was aborted (e.g., "all attempts failed",
"no addresses resolved", "user canceled").¶
Logged at the end of an HE session to provide summary statistics.¶
HEMetrics = {
he_session_id: text
outcome: "success" / "aborted"
total_duration_ms: uint32
tt_first_success_ms: uint32 ?
first_success_family: "ipv4" / "ipv6" ?
first_success_transport: "tcp" / "quic" ?
attempts_total: uint32
attempts_success: uint32
attempts_failure: uint32
* $$he-metrics-extension
}
¶
The fields capture end-to-end session statistics:¶
outcome: Whether the session established a connection ("success") or
failed entirely ("aborted").¶
total_duration_ms: Total time from session start to final outcome
(connection selected or session aborted).¶
tt_first_success_ms: Time from session start to the first successful
connection attempt (i.e., time-to-first-byte readiness).¶
first_success_family: The address family of the first successful attempt.¶
first_success_transport: The transport protocol of the first successful
attempt.¶
attempts_total: Total number of connection attempts initiated.¶
attempts_success: Number of attempts that completed successfully.¶
attempts_failure: Number of attempts that failed, timed out, or were
canceled.¶
When outcome is "aborted", the fields tt_first_success_ms,
first_success_family, and first_success_transport are not present.¶
TCP/TLS implementation can correlate HE events with:¶
TBD¶
This document registers a new entry in the "qlog event schema URIs" registry (created in {Section 15 of QLOG-MAIN}):¶
urn:ietf:params:qlog:events:hev3¶
hev3¶
config_set, dns_query_started, dns_query_finished, nat64_prefix_discovered, candidate_discovered, candidates_sorted, candidate_removed, candidates_resorted, attempt_scheduled, attempt_started, attempt_pended, attempt_resumed, attempt_outcome, next_attempt_timer_set, next_attempt_timer_fired, next_attempt_timer_canceled, connection_selected, connection_aborted, metrics¶
Event definitions for logging HEv3 events¶
This document¶