Initial commit
Adds a happyDomain checker that probes STUN/TURN servers end-to-end:
DNS/SRV discovery, UDP/TCP/TLS/DTLS dial, STUN binding + reflexive-addr
sanity, open-relay detection, authenticated TURN Allocate (long-term
creds or REST-API HMAC), public-relay check, CreatePermission + Send
round-trip through the relay, and optional ChannelBind.
Failing sub-tests carry a remediation string (`Fix`) that the HTML
report surfaces as a yellow headline callout and inline next to each
row. Mapping covers the most common coturn misconfigurations
(external-ip, relay-ip, lt-cred-mech, min-port/max-port, cert issues,
401 nonce drift, 441/442/486/508 allocation errors).
Implements sdk.EndpointDiscoverer (checker/discovery.go): every
stuns:/turns:/DTLS endpoint observed during Collect is published as a
DiscoveredEndpoint{Type: "tls"|"dtls"} so a downstream TLS checker can
verify certificates without re-parsing the observation.
Backed by pion/stun/v3 + pion/turn/v4 + pion/dtls/v3; SDK pinned to a
local replace until the EndpointDiscoverer interface ships in a tagged
release.
This commit is contained in:
commit
7c7706fe3f
29 changed files with 2794 additions and 0 deletions
133
checker/types.go
Normal file
133
checker/types.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Package checker implements the STUN/TURN checker for happyDomain.
|
||||
//
|
||||
// The checker drives a target server through the STUN binding and TURN
|
||||
// allocation/relay protocols (RFC 5389, RFC 5766) using the Pion libraries,
|
||||
// then exposes a structured observation and a rich HTML report including
|
||||
// remediation guidance for the most common deployment mistakes.
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// isPrivateAddr returns whether s parses as a private/loopback/link-local
|
||||
// address. Accepts either a bare IP or a host:port form. Hostnames return
|
||||
// false; the caller should resolve first if needed.
|
||||
func isPrivateAddr(s string) bool {
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
host, _, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ip = net.ParseIP(host)
|
||||
}
|
||||
return isPrivate(ip)
|
||||
}
|
||||
|
||||
// ObservationKeyStunTurn is the observation key for STUN/TURN test data.
|
||||
const ObservationKeyStunTurn sdk.ObservationKey = "stun_turn"
|
||||
|
||||
// Transport identifies the L4/L4-secure transport used to reach an endpoint.
|
||||
type Transport string
|
||||
|
||||
const (
|
||||
TransportUDP Transport = "udp"
|
||||
TransportTCP Transport = "tcp"
|
||||
TransportTLS Transport = "tls"
|
||||
TransportDTLS Transport = "dtls"
|
||||
)
|
||||
|
||||
// Endpoint is a single resolved server target to probe.
|
||||
type Endpoint struct {
|
||||
URI string `json:"uri"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Transport Transport `json:"transport"`
|
||||
Secure bool `json:"secure"`
|
||||
IsTURN bool `json:"is_turn"` // false: STUN-only scheme, true: TURN scheme
|
||||
Source string `json:"source"` // "uri" or "srv:_turn._udp.example.com"
|
||||
}
|
||||
|
||||
// DialResult holds the raw outcome of establishing the L4(/secure)
|
||||
// connection to an endpoint. Collected without judgement.
|
||||
type DialResult struct {
|
||||
OK bool `json:"ok"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
RemoteAddr string `json:"remote_addr,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
// TLS metadata populated when the underlying transport used TLS.
|
||||
TLSVersion string `json:"tls_version,omitempty"`
|
||||
TLSCipher string `json:"tls_cipher,omitempty"`
|
||||
TLSPeerCN string `json:"tls_peer_cn,omitempty"`
|
||||
|
||||
// DTLSHandshake records whether a DTLS handshake was performed.
|
||||
DTLSHandshake bool `json:"dtls_handshake,omitempty"`
|
||||
}
|
||||
|
||||
// STUNBindingObservation holds the raw outcome of a STUN Binding probe.
|
||||
type STUNBindingObservation struct {
|
||||
Attempted bool `json:"attempted"`
|
||||
OK bool `json:"ok"`
|
||||
RTTMs int64 `json:"rtt_ms"`
|
||||
ReflexiveAddr string `json:"reflexive_addr,omitempty"`
|
||||
IsPrivateMapped bool `json:"is_private_mapped,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// TURNAllocateObservation holds the raw outcome of a TURN Allocate
|
||||
// attempt (either unauthenticated probe or authenticated attempt).
|
||||
type TURNAllocateObservation struct {
|
||||
Attempted bool `json:"attempted"`
|
||||
OK bool `json:"ok"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
RelayAddr string `json:"relay_addr,omitempty"`
|
||||
IsPrivateRelay bool `json:"is_private_relay,omitempty"`
|
||||
UnauthChallenge bool `json:"unauth_challenge,omitempty"`
|
||||
ErrorCode int `json:"error_code,omitempty"`
|
||||
ErrorReason string `json:"error_reason,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// RelayEchoObservation holds the raw outcome of the relay echo probe.
|
||||
type RelayEchoObservation struct {
|
||||
Attempted bool `json:"attempted"`
|
||||
OK bool `json:"ok"`
|
||||
PeerAddr string `json:"peer_addr,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointProbe holds the raw, unjudged observation for a single endpoint.
|
||||
type EndpointProbe struct {
|
||||
Endpoint Endpoint `json:"endpoint"`
|
||||
|
||||
// ResolvedIPs lists the A/AAAA addresses we observed for this host
|
||||
// (populated when the endpoint was reached). Used for IPv6 coverage.
|
||||
ResolvedIPs []string `json:"resolved_ips,omitempty"`
|
||||
|
||||
Dial DialResult `json:"dial"`
|
||||
STUNBinding STUNBindingObservation `json:"stun_binding"`
|
||||
TURNNoAuth TURNAllocateObservation `json:"turn_noauth"`
|
||||
TURNAuth TURNAllocateObservation `json:"turn_auth"`
|
||||
RelayEcho RelayEchoObservation `json:"relay_echo"`
|
||||
ChannelBindRun bool `json:"channel_bind_run,omitempty"`
|
||||
}
|
||||
|
||||
// StunTurnData is the JSON-serializable observation payload. It now
|
||||
// carries only raw per-endpoint probe outcomes; rules do the judging.
|
||||
type StunTurnData struct {
|
||||
Zone string `json:"zone,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
RequestedURI string `json:"requested_uri,omitempty"`
|
||||
HasCreds bool `json:"has_creds,omitempty"`
|
||||
ProbePeer string `json:"probe_peer,omitempty"`
|
||||
WarningRTTMs int64 `json:"warning_rtt_ms,omitempty"`
|
||||
CriticalRTT int64 `json:"critical_rtt_ms,omitempty"`
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
Endpoints []EndpointProbe `json:"endpoints"`
|
||||
GlobalError string `json:"global_error,omitempty"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue