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.
53 lines
1.7 KiB
Go
53 lines
1.7 KiB
Go
package checker
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/pion/stun/v3"
|
|
)
|
|
|
|
// TestStunErrorOf_PinPionFormat builds an error using pion's own
|
|
// ErrorCodeAttribute.String() so that any future change to the format
|
|
// pion/turn uses ("... (error <code>: <reason>)") makes this test fail
|
|
// loudly instead of silently breaking our diagnostic parsing.
|
|
func TestStunErrorOf_PinPionFormat(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
code stun.ErrorCode
|
|
reason string
|
|
wantCode int
|
|
wantReason string
|
|
}{
|
|
{"unauthorized", stun.CodeUnauthorized, "Unauthorized", 401, "Unauthorized"},
|
|
{"stale nonce", stun.CodeStaleNonce, "Stale Nonce", 438, "Stale Nonce"},
|
|
{"server error", stun.CodeServerError, "Server Error", 500, "Server Error"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
attr := stun.ErrorCodeAttribute{Code: tc.code, Reason: []byte(tc.reason)}
|
|
// Mirror pion/turn/v4@v4.0.0 client.go:296:
|
|
// fmt.Errorf("%s (error %s)", res.Type, code)
|
|
err := fmt.Errorf("error response (error %s)", attr)
|
|
gotCode, gotReason := stunErrorOf(err)
|
|
if gotCode != tc.wantCode || gotReason != tc.wantReason {
|
|
t.Errorf("stunErrorOf(%q) = (%d, %q); want (%d, %q): pion error format may have changed",
|
|
err.Error(), gotCode, gotReason, tc.wantCode, tc.wantReason)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStunErrorOf_NoMatch(t *testing.T) {
|
|
code, reason := stunErrorOf(errors.New("plain error"))
|
|
if code != 0 {
|
|
t.Errorf("expected code 0 for unparseable error, got %d", code)
|
|
}
|
|
if reason != "plain error" {
|
|
t.Errorf("expected reason to fall back to message, got %q", reason)
|
|
}
|
|
if c, r := stunErrorOf(nil); c != 0 || r != "" {
|
|
t.Errorf("nil error: got (%d, %q), want (0, \"\")", c, r)
|
|
}
|
|
}
|