checker-stun-turn/checker/discovery.go
Pierre-Olivier Mercier 7c7706fe3f 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.
2026-04-26 19:55:05 +07:00

51 lines
1.4 KiB
Go

package checker
import (
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
tlsct "git.happydns.org/checker-tls/contract"
)
// DiscoverEntries implements sdk.DiscoveryPublisher.
//
// stuns:/turns: (RFC 7064/7065) speak TLS immediately after the TCP
// handshake, so every secure TCP-based endpoint we observed is published
// under the tls.endpoint.v1 contract for checker-tls to pick up.
//
// DTLS is intentionally omitted: the current checker-tls consumer uses
// crypto/tls and would not probe a datagram-TLS endpoint correctly. Emitting
// a DTLS entry today would only produce orphan lineage.
//
// SNI is left empty (= Host); no STARTTLS upgrade applies; the scheme
// mandates direct TLS on the wire.
func (p *stunTurnProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) {
d, ok := data.(*StunTurnData)
if !ok {
return nil, fmt.Errorf("unexpected data type %T", data)
}
seen := make(map[string]struct{})
var out []sdk.DiscoveryEntry
for _, ep := range d.Endpoints {
if !ep.Dial.OK {
continue
}
if !ep.Endpoint.Secure || ep.Endpoint.Transport == TransportDTLS {
continue
}
key := fmt.Sprintf("%s|%d", ep.Endpoint.Host, ep.Endpoint.Port)
if _, dup := seen[key]; dup {
continue
}
seen[key] = struct{}{}
entry, err := tlsct.NewEntry(tlsct.TLSEndpoint{
Host: ep.Endpoint.Host,
Port: ep.Endpoint.Port,
})
if err != nil {
return nil, err
}
out = append(out, entry)
}
return out, nil
}