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.
83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// Rules returns the list of CheckRules exposed by the STUN/TURN checker.
|
|
// Each concern is its own rule (SRV for STUN, SRV for TURN, STUN
|
|
// binding, TURN open-relay probe, TURN authenticated allocation, relay
|
|
// echo, TLS transport, IPv6 coverage, …) so the UI can show a granular
|
|
// status instead of a single aggregated one.
|
|
func Rules() []sdk.CheckRule {
|
|
return []sdk.CheckRule{
|
|
&discoveryRule{},
|
|
&srvStunRule{},
|
|
&srvTurnRule{},
|
|
&dialRule{},
|
|
&stunBindingRule{},
|
|
&stunReflexivePublicRule{},
|
|
&stunLatencyRule{},
|
|
&turnOpenRelayRule{},
|
|
&turnAuthRule{},
|
|
&turnRelayPublicRule{},
|
|
&turnRelayEchoRule{},
|
|
&turnTLSTransportRule{},
|
|
&ipv6CoverageRule{},
|
|
}
|
|
}
|
|
|
|
// loadData fetches the observation; on error returns a CheckState that
|
|
// callers should emit directly.
|
|
func loadData(ctx context.Context, obs sdk.ObservationGetter) (*StunTurnData, *sdk.CheckState) {
|
|
var data StunTurnData
|
|
if err := obs.Get(ctx, ObservationKeyStunTurn, &data); err != nil {
|
|
return nil, &sdk.CheckState{
|
|
Status: sdk.StatusError,
|
|
Message: fmt.Sprintf("failed to get STUN/TURN observation: %v", err),
|
|
Code: "stun_turn.observation_error",
|
|
}
|
|
}
|
|
return &data, nil
|
|
}
|
|
|
|
func passState(code, msg string) sdk.CheckState {
|
|
return sdk.CheckState{Status: sdk.StatusOK, Message: msg, Code: code}
|
|
}
|
|
|
|
func skippedState(code, msg string) sdk.CheckState {
|
|
return sdk.CheckState{Status: sdk.StatusUnknown, Message: msg, Code: code}
|
|
}
|
|
|
|
func epSubject(ep Endpoint) string {
|
|
if ep.URI != "" {
|
|
return ep.URI
|
|
}
|
|
return fmt.Sprintf("%s:%d/%s", ep.Host, ep.Port, ep.Transport)
|
|
}
|
|
|
|
// hasTURNEndpoint reports whether the observation contains at least one
|
|
// TURN endpoint (excluding STUN-only endpoints).
|
|
func hasTURNEndpoint(data *StunTurnData) bool {
|
|
for _, ep := range data.Endpoints {
|
|
if ep.Endpoint.IsTURN {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// joinMsg concatenates non-empty parts with ": " between them.
|
|
func joinMsg(parts ...string) string {
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
if strings.TrimSpace(p) != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return strings.Join(out, ": ")
|
|
}
|