checker-stun-turn/README.md
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

90 lines
4 KiB
Markdown

# checker-stun-turn
happyDomain checker that probes **STUN** and **TURN** servers end-to-end:
DNS / SRV discovery, TCP/UDP reachability, TLS / DTLS handshake, STUN binding,
open-relay check, authenticated TURN Allocate (long-term creds *or*
REST API shared secret), relay address sanity, and a `CreatePermission + Send`
round-trip through the relay.
Backed by [`github.com/pion/stun`](https://github.com/pion/stun) +
[`github.com/pion/turn`](https://github.com/pion/turn).
## Tests performed per endpoint
| Test | What it proves |
|-----------------------------|----------------|
| `dial:<transport>` | DNS resolves, port is reachable, TLS/DTLS handshake succeeds. |
| `tls` / `dtls` | Records TLS version + cipher + peer cert CN. |
| `stun_binding` | Server answers RFC 5389 Binding Request; measures RTT. |
| `stun_reflexive_public` | Server returned a *public* XOR-MAPPED address (not RFC1918). |
| `turn_open_relay_check` | Unauthenticated Allocate is rejected with 401 + REALM/NONCE. |
| `turn_allocate_auth` | Authenticated Allocate succeeds; relay address returned. |
| `turn_relay_public` | Relay address is publicly routable. |
| `turn_relay_echo` | CreatePermission + Send to the probe peer succeed. |
| `turn_channel_bind` | (optional) ChannelBind exercised via the relay conn. |
## Most common failures surfaced with a fix
Each failing sub-test carries a `Fix` field that is rendered prominently in the
HTML report (yellow callout at the top of the card *and* inline with each row).
Mapping:
- UDP/TCP dial timeouts → firewall/NAT guidance
- TLS handshake errors → certificate reissue guidance (coturn `cert=`/`pkey=`)
- STUN binding returns RFC1918 → `external-ip=` for coturn
- Unauthenticated Allocate accepted → enable `lt-cred-mech`, close the open relay
- Allocate 401 loop → check NTP (TURN nonces are time-sensitive)
- Allocate 441 → wrong username/password or wrong REST shared secret
- Allocate 442 → try different transport or enable it server-side
- Allocate 486/508 → quota / port-range issues on the server
- Relay address is private → set `relay-ip=` to a public IP
- Relay echo fails → `min-port`/`max-port` range not publicly reachable
## Usage
Build and run:
```
make # standalone binary
make plugin # .so plugin for happyDomain
./checker-stun-turn -listen :8080
```
Trigger a check:
```
curl -sX POST localhost:8080/collect -H 'content-type: application/json' -d '{
"options": {
"zone": "example.com",
"serverURI": "turns:turn.example.com:5349?transport=tcp",
"mode": "turn",
"username": "alice",
"credential": "s3cret",
"transports": "udp,tcp,tls",
"probePeer": "1.1.1.1:53",
"timeout": 5
}
}' | jq .
```
## Options
| Scope | Option | Type | Default | Notes |
|---------|-------------------|--------|---------------|-------|
| run | `zone` | string | (auto-filled) | used for `_stun._udp` / `_turn._udp` / `_turns._tcp` SRV discovery |
| run | `serverURI` | string | | explicit URI, RFC 7064/7065 |
| run | `mode` | choice | `auto` | `stun`, `turn`, `auto` |
| user | `username` | string | | long-term credentials |
| user | `credential` | secret | | long-term credentials |
| user | `sharedSecret` | secret | | REST-API auth (draft-uberti), takes precedence |
| user | `realm` | string | | optional explicit realm |
| user | `transports` | string | `udp,tcp,tls` | comma-separated among `udp,tcp,tls,dtls` |
| user | `probePeer` | string | `1.1.1.1:53` | target for the relay echo test |
| user | `testChannelBind` | bool | `false` | |
| user | `warningRTT` | uint | `200` ms | |
| user | `criticalRTT` | uint | `1000` ms | |
| user | `timeout` | uint | `5` s | per-probe |
## License
MIT (see LICENSE).