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
6ad7d3f593
29 changed files with 2794 additions and 0 deletions
61
checker/stun.go
Normal file
61
checker/stun.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/turn/v4"
|
||||
)
|
||||
|
||||
// stunBindingResult holds the outcome of a STUN Binding test.
|
||||
type stunBindingResult struct {
|
||||
RTT time.Duration
|
||||
ReflexiveAddr net.Addr
|
||||
IsPrivateMapped bool
|
||||
Err error
|
||||
}
|
||||
|
||||
// runSTUNBinding sends a STUN Binding Request to the remote and returns the
|
||||
// reflexive (XOR-MAPPED) address along with the RTT. We construct a tiny
|
||||
// turn.Client with no credentials; its SendBindingRequestTo path drives a
|
||||
// vanilla STUN exchange (RFC 5389) and works on UDP/TCP/TLS/DTLS through
|
||||
// the dialed PacketConn we hand it.
|
||||
func runSTUNBinding(d *dialedConn, timeout time.Duration) stunBindingResult {
|
||||
cfg := &turn.ClientConfig{
|
||||
Conn: d.pc,
|
||||
STUNServerAddr: d.remoteAddr.String(),
|
||||
RTO: timeout,
|
||||
Software: "happyDomain-checker-stun-turn",
|
||||
}
|
||||
client, err := turn.NewClient(cfg)
|
||||
if err != nil {
|
||||
return stunBindingResult{Err: fmt.Errorf("turn.NewClient: %w", err)}
|
||||
}
|
||||
defer client.Close()
|
||||
if err := client.Listen(); err != nil {
|
||||
return stunBindingResult{Err: fmt.Errorf("client.Listen: %w", err)}
|
||||
}
|
||||
start := time.Now()
|
||||
addr, err := client.SendBindingRequestTo(d.remoteAddr)
|
||||
if err != nil {
|
||||
return stunBindingResult{Err: err}
|
||||
}
|
||||
res := stunBindingResult{
|
||||
RTT: time.Since(start),
|
||||
ReflexiveAddr: addr,
|
||||
}
|
||||
if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
||||
res.IsPrivateMapped = isPrivate(udpAddr.IP)
|
||||
} else if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||
res.IsPrivateMapped = isPrivate(tcpAddr.IP)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func isPrivate(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue