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.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
c413e87f04
23 changed files with 1869 additions and 0 deletions
132
checker/types.go
Normal file
132
checker/types.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// 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 (
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// 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"
|
||||
}
|
||||
|
||||
// SubTestStatus mirrors sdk.Status as a string for JSON friendliness.
|
||||
type SubTestStatus string
|
||||
|
||||
const (
|
||||
SubTestOK SubTestStatus = "ok"
|
||||
SubTestInfo SubTestStatus = "info"
|
||||
SubTestWarn SubTestStatus = "warn"
|
||||
SubTestCrit SubTestStatus = "crit"
|
||||
SubTestSkipped SubTestStatus = "skipped"
|
||||
SubTestError SubTestStatus = "error"
|
||||
)
|
||||
|
||||
// SubTest is one fine-grained test executed against an endpoint.
|
||||
type SubTest struct {
|
||||
Name string `json:"name"`
|
||||
Status SubTestStatus `json:"status"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Fix string `json:"fix,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointReport gathers all sub-tests run against a single endpoint.
|
||||
type EndpointReport struct {
|
||||
Endpoint Endpoint `json:"endpoint"`
|
||||
SubTests []SubTest `json:"sub_tests"`
|
||||
}
|
||||
|
||||
// Worst returns the worst sub-test status of the endpoint.
|
||||
func (e EndpointReport) Worst() SubTestStatus {
|
||||
worst := SubTestOK
|
||||
for _, t := range e.SubTests {
|
||||
if statusRank(t.Status) > statusRank(worst) {
|
||||
worst = t.Status
|
||||
}
|
||||
}
|
||||
return worst
|
||||
}
|
||||
|
||||
// FirstFailure returns the first non-OK/Info/Skipped sub-test.
|
||||
func (e EndpointReport) FirstFailure() *SubTest {
|
||||
for i := range e.SubTests {
|
||||
s := e.SubTests[i].Status
|
||||
if s != SubTestOK && s != SubTestInfo && s != SubTestSkipped {
|
||||
return &e.SubTests[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StunTurnData is the JSON-serializable observation payload.
|
||||
type StunTurnData struct {
|
||||
Zone string `json:"zone,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
Endpoints []EndpointReport `json:"endpoints"`
|
||||
GlobalError string `json:"global_error,omitempty"`
|
||||
}
|
||||
|
||||
// statusRank converts a SubTestStatus to an orderable severity.
|
||||
func statusRank(s SubTestStatus) int {
|
||||
switch s {
|
||||
case SubTestOK:
|
||||
return 0
|
||||
case SubTestSkipped:
|
||||
return 1
|
||||
case SubTestInfo:
|
||||
return 2
|
||||
case SubTestWarn:
|
||||
return 3
|
||||
case SubTestCrit:
|
||||
return 4
|
||||
case SubTestError:
|
||||
return 5
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// toSDKStatus converts the worst SubTestStatus seen to an sdk.Status.
|
||||
func toSDKStatus(s SubTestStatus) sdk.Status {
|
||||
switch s {
|
||||
case SubTestOK:
|
||||
return sdk.StatusOK
|
||||
case SubTestInfo, SubTestSkipped:
|
||||
return sdk.StatusInfo
|
||||
case SubTestWarn:
|
||||
return sdk.StatusWarn
|
||||
case SubTestCrit:
|
||||
return sdk.StatusCrit
|
||||
case SubTestError:
|
||||
return sdk.StatusError
|
||||
}
|
||||
return sdk.StatusUnknown
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue