package netreport
Import Path
github.com/tmc/go-iroh/internal/netreport (on go.dev)
Dependency Relation
imports 15 packages, and imported by one package
Involved Source Files
client.go
defaults.go
Package netreport probes the local network environment to build a [Report]
describing relay latencies and (where available) the public reflexive address
of the host.
It is a port of iroh's net_report module (iroh/src/net_report). A [Client]
runs three kinds of probes against the configured relays:
- HTTPS probes fetch the relay's "/ping" path and time the round trip.
- QAD probes open a QUIC Address Discovery connection (ALPN "/iroh-qad/0",
QUIC port 7842) and read the connection RTT.
- A captive-portal check fetches "/generate_204" with an X-Iroh-Challenge
header and verifies the X-Iroh-Response echo, but only on full reports.
From those probes the client picks a preferred relay, applying hysteresis so a
responsive relay is not abandoned unless a new one is meaningfully faster.
# Reflexive address discovery
A QAD connection also learns the host's public address from the relay via the
QUIC Address Discovery observed-address extension
(draft-seemann-quic-address-discovery), implemented in the forked quic-go
[github.com/tmc/go-iroh/internal/qng] (slice X3). The QAD client advertises
the receive-only address-discovery role, and a QAD relay reports the client's
reflexive address with OBSERVED_ADDRESS frames; the latest report is surfaced
as [Report.GlobalV4]/[Report.GlobalV6] (and the matching UDP fields).
Reports arrive after the handshake, so [qadConn.observedAddr] waits briefly
for the first one; an immediate read would always miss. Without a report
(not negotiated, or timed out) the probe stays latency-only.
Which socket a probe rides decides what the observed address means: a
private per-probe socket learns a mapping that dies with it.
[Client.WithQADDialer] routes probes through the caller's transport instead,
so the observed address is the caller's real public mapping.
probe.go
probes.go
qad.go
report.go
Code Examples
package main
import (
"context"
"fmt"
"github.com/tmc/go-iroh/internal/netreport"
"github.com/tmc/go-iroh/relay"
)
func main() {
client := netreport.NewClient(relay.NewMap())
report, err := client.GetReport(context.Background(), netreport.IfStateDetails{HaveV4: true}, true)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("has udp:", report.HasUDP())
fmt.Println("preferred relay set:", !report.PreferredRelay.IsZero())
}
package main
import (
"fmt"
"github.com/tmc/go-iroh/internal/netreport"
)
func main() {
fmt.Println(netreport.ProbeHTTPS)
fmt.Println(netreport.ProbeQADv4)
fmt.Println(netreport.ProbeQADv6)
}
Package-Level Type Names (total 6)
Client runs net_report probes against a set of relays and tracks report
history so it can apply preferred-relay hysteresis across runs. It is a port
of net_report::Client (iroh/src/net_report.rs:90).
A Client is safe for concurrent use; GetReport serializes internally.
The zero value is not usable; construct a Client with [NewClient].
GetReport runs a single net_report. doFull forces a full report (captive
portal check and reset probe history); otherwise the report is full only if
no report has been generated within [fullReportInterval]. ifState informs the
sufficiency check.
The whole call is bounded by [overallReportTimeout]; the probes within it are
bounded by [probesTimeout].
If the relay map is empty the report is empty: no probes run and
PreferredRelay is the zero value.
WithDNSResolver sets the resolver used to look up relay hostnames.
WithQADDialer routes QAD probes through d instead of a private per-probe
UDP socket, so the observed address is the mapping of the dialer's own
socket. A per-probe socket's mapping dies with it and its port is nobody's
dial candidate; per-probe mappings also differ between relays, which makes
MappingVariesByDest misreport symmetric NAT.
WithQADTLSConfig sets the TLS verification policy for QAD QUIC connections.
It is used in tests to trust a self-signed relay certificate.
WithQUICConfig sets the QAD QUIC transport configuration.
WithTLSConfig sets the TLS configuration used for HTTPS probes. It is used in
tests to trust self-signed relay certificates.
func NewClient(relayMap *relay.Map) *Client
func (*Client).WithDNSResolver(r *net.Resolver) *Client
func (*Client).WithQADDialer(d QADDialer) *Client
func (*Client).WithQADTLSConfig(cfg *itls.Config) *Client
func (*Client).WithQUICConfig(cfg *quic.Config) *Client
func (*Client).WithTLSConfig(cfg *tls.Config) *Client
IfStateDetails describes the host's interface capabilities, used to decide
when enough probes have completed. It is a port of net_report's
IfStateDetails (iroh/src/net_report/reportgen.rs:74).
HaveV4 reports whether the host has IPv4 connectivity.
HaveV6 reports whether the host has IPv6 connectivity.
func (*Client).GetReport(ctx context.Context, ifState IfStateDetails, doFull bool) (*Report, error)
Probe identifies how a relay's latency was measured. The order matches the
Rust enum (iroh/src/net_report/probes.rs:22) so a [Probe] sorts and prints
consistently across the port.
String returns the probe name, matching the Rust Display impl.
Probe : expvar.Var
Probe : fmt.Stringer
const ProbeHTTPS
const ProbeQADv4
const ProbeQADv6
QADDialer opens the QUIC connection for one QAD probe to addr; tlsConf
already carries the QAD ALPN and server name.
func (*Client).WithQADDialer(d QADDialer) *Client
RelayLatencies records the lowest latency seen per relay for each probe type.
It is a port of net_report::RelayLatencies (report.rs:130). The zero value is
empty and ready to use.
Snapshot returns the lowest latency recorded for each relay.
Report describes the network environment as measured by a single
[Client.GetReport] run. It is a port of iroh's net_report::Report
(iroh/src/net_report/report.rs:14).
The zero Report is a valid empty report.
CaptivePortal reports whether a captive portal is intercepting HTTP, when
the check ran (full reports only).
Full reports whether this run used the full probe plan.
GlobalV4 is the host's public IPv4 address as seen by a relay. It is
absent when no QAD IPv4 observed-address report arrived.
GlobalV6 is the host's public IPv6 address as seen by a relay. It is
absent when no QAD IPv6 observed-address report arrived.
MappingVariesByDestV4 reports whether the observed public IPv4 address
differs across relays, when known.
MappingVariesByDestV6 reports whether the observed public IPv6 address
differs across relays, when known.
PreferredRelay is the relay with the best recent latency, chosen with
hysteresis. It is the zero RelayURL when no relay responded.
RelayLatency holds per-relay, per-probe latencies.
UDPv4 reports whether a QAD IPv4 round trip completed and reported an
observed IPv4 address.
UDPv6 reports whether a QAD IPv6 round trip completed and reported an
observed IPv6 address.
HasUDP reports whether any QAD round trip succeeded.
func (*Client).GetReport(ctx context.Context, ifState IfStateDetails, doFull bool) (*Report, error)
Package-Level Functions (only one)
NewClient returns a Client that probes the relays in relayMap.
Package-Level Variables (only one)
ErrExtensionNotNegotiated is returned by [qadConn.observedAddr] when no
reflexive address is available: either QUIC Address Discovery was not
negotiated on the connection, or no OBSERVED_ADDRESS report arrived within
[qadObservedAddrWait]. The forked quic-go implements the extension (slice
X3); see the package doc.
Package-Level Constants (total 3)
ProbeHTTPS times an HTTPS GET of the relay's probe path.
ProbeQADv4 times a QUIC Address Discovery connection over IPv4.
ProbeQADv6 times a QUIC Address Discovery connection over IPv6.
![]() |
The pages are generated with Golds v0.8.4. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |