# 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:` | 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 ## Rules | Code | Description | Severity | |-------------------------------|---------------------------------------------------------------------------------------------------|---------------------| | `stun_turn.discovery` | Verifies that at least one STUN/TURN endpoint could be discovered (explicit URI or SRV lookup). | CRITICAL | | `stun_turn.srv_stun` | Verifies that at least one STUN endpoint is reachable via SRV (_stun/_stuns) or an explicit URI. | WARNING | | `stun_turn.srv_turn` | Verifies that at least one TURN endpoint is reachable via SRV (_turn/_turns) or an explicit URI. | CRITICAL | | `stun_turn.dial` | Verifies that every discovered endpoint accepts a connection (TCP/TLS handshake or UDP socket). | CRITICAL | | `stun_turn.tls_transport` | Verifies that at least one TLS/DTLS transport (stuns/turns) succeeds when present. | CRITICAL | | `stun_turn.ipv6_coverage` | Verifies at least one STUN/TURN hostname resolves to an IPv6 address. | WARNING | | `stun_turn.stun_binding` | Verifies that the STUN Binding request receives a XOR-MAPPED-ADDRESS reply. | CRITICAL | | `stun_turn.reflexive_public` | Flags endpoints that return a private/loopback reflexive address (server unaware of its public IP). | CRITICAL | | `stun_turn.stun_latency` | Compares the STUN Binding RTT against the configured warning/critical thresholds. | CRITICAL | | `stun_turn.turn_open_relay` | Verifies the TURN server requires authentication (challenges unauthenticated Allocate with 401). | CRITICAL | | `stun_turn.turn_auth` | Verifies the supplied TURN credentials (or REST shared secret) yield a successful Allocate. | CRITICAL | | `stun_turn.relay_public` | Flags TURN servers whose allocated relay address is private/loopback (missing public relay-ip). | CRITICAL | | `stun_turn.relay_echo` | Verifies the TURN relay path can carry traffic to the configured probe peer (CreatePermission + Send). | WARNING | ## 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).