From 96d22abe99b64d87b71901cf989358351f35aa5d Mon Sep 17 00:00:00 2001 From: zijiren <84728412+zijiren233@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:42:19 +0800 Subject: [PATCH] fix(webrtc): use public STUN by default (#427) ## Summary - disable the built-in SyncTV STUN server by default across application, Compose, image, environment, and Helm configuration - default `webrtc.externalIceServers` to Cloudflare and Google public STUN endpoints while preserving persisted overrides and explicit `[]` - update deployment validation, tests, examples, and English/Chinese documentation Tailscale is not included because it publishes STUN endpoints through its dynamic DERP map and does not provide a stable general-purpose hostname suitable for this default. ## Testing - `cargo fmt --all -- --check` - `cargo test -p synctv-core --test global_settings_registry_tests` - `cargo test -p synctv-core test_runtime_settings_default_to_public_stun_servers` - `cargo test -p synctv default_webrtc_config_disables_builtin_stun` - `cargo check --workspace --all-targets` - `bash scripts/validate-helm.sh` - Compose configuration rendering - `npm run validate:content` - `npm run check` - `git diff --check` --- .env.synctv.example | 4 +++- Dockerfile | 3 +-- docker-compose.yml | 1 - .../docs/configuration/full-example.mdx | 2 +- .../src/content/docs/configuration/webrtc.mdx | 20 +++++++++++----- .../docs/en/configuration/full-example.mdx | 2 +- .../content/docs/en/configuration/webrtc.mdx | 16 +++++++++---- .../docs/en/install/docker-compose.mdx | 5 ++-- .../docs/en/reference/configuration-index.mdx | 2 +- .../docs/en/reference/runtime-settings.mdx | 4 ++-- .../content/docs/install/docker-compose.mdx | 5 ++-- .../docs/reference/configuration-index.mdx | 2 +- .../docs/reference/runtime-settings.mdx | 4 ++-- helm/synctv/README.md | 2 +- helm/synctv/values.yaml | 2 +- scripts/validate-helm.sh | 2 ++ synctv-api/tests/webrtc_integration_test.rs | 2 +- synctv-core/src/service/global_settings.rs | 23 ++++++++++++++++++- .../src/service/global_settings/types.rs | 8 +++++++ .../tests/global_settings_registry_tests.rs | 7 +++++- synctv.example.yaml | 10 ++++---- synctv/src/app.rs | 5 +--- synctv/src/app_config/mod.rs | 12 +++++++++- synctv/tests/cluster_startup_failure_tests.rs | 5 +--- 24 files changed, 104 insertions(+), 44 deletions(-) diff --git a/.env.synctv.example b/.env.synctv.example index 1f94c0fa..3a3ff0f8 100644 --- a/.env.synctv.example +++ b/.env.synctv.example @@ -236,7 +236,9 @@ SYNCTV_WEBRTC_LOGGING_COLOR=auto SYNCTV_WEBRTC_MODE=peer_to_peer SYNCTV_WEBRTC_STUN_PORT=3478 -SYNCTV_WEBRTC_ENABLE_BUILTIN_STUN=true +SYNCTV_WEBRTC_ENABLE_BUILTIN_STUN=false +# Clients use Cloudflare and Google public STUN servers by default. Enable the +# built-in server only when this deployment should provide STUN. # Built-in STUN needs a client-reachable public ip:port or DNS name:port. # Do not set this to a Pod IP or ClusterIP Service IP. # SYNCTV_WEBRTC_STUN_EXTERNAL_ADDR=stun.example.com:3478 diff --git a/Dockerfile b/Dockerfile index 8bcdba1e..66a002b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -110,8 +110,7 @@ RUN command -v synctv && synctv --version # 50051: dedicated cluster gRPC # 9090: internal Prometheus metrics listener # 1935: RTMP (livestream) -# 3478/udp: STUN (WebRTC) -EXPOSE 8080 8081 50051 9090 1935 3478/udp +EXPOSE 8080 8081 50051 9090 1935 # Health check against the HTTP health endpoint HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ diff --git a/docker-compose.yml b/docker-compose.yml index c536f68f..4da17341 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,6 @@ services: ports: - "8080:8080" - "1935:1935" - - "3478:3478/udp" depends_on: postgres: condition: service_healthy diff --git a/docs/src/content/docs/configuration/full-example.mdx b/docs/src/content/docs/configuration/full-example.mdx index 76370863..3a9765ba 100644 --- a/docs/src/content/docs/configuration/full-example.mdx +++ b/docs/src/content/docs/configuration/full-example.mdx @@ -395,7 +395,7 @@ media_providers: webrtc: mode: "peer_to_peer" - enable_builtin_stun: true + enable_builtin_stun: false stun_port: 3478 stun_host: "0.0.0.0" stun_external_addr: "" diff --git a/docs/src/content/docs/configuration/webrtc.mdx b/docs/src/content/docs/configuration/webrtc.mdx index 36cdfd7d..382f1da5 100644 --- a/docs/src/content/docs/configuration/webrtc.mdx +++ b/docs/src/content/docs/configuration/webrtc.mdx @@ -42,7 +42,7 @@ webrtc: STUN 帮助客户端发现自己的公网映射地址,是 NAT 穿透的一部分。 -关闭场景: +仅当此 SyncTV 部署需要提供 STUN,且客户端能够访问其 UDP 端口时开启。以下场景保持关闭: - 你使用外部 STUN/TURN。 - 当前部署环境不能暴露 UDP 端口。 @@ -52,9 +52,9 @@ STUN 帮助客户端发现自己的公网映射地址,是 NAT 穿透的一部 默认值:`3478`。 -这是 STUN 常用端口。 +这是 STUN 常用端口,仅在启用内置 STUN 时监听。 -Kubernetes 或云服务器上要确保 UDP 3478 能被客户端访问。 +启用内置 STUN 后,Kubernetes 或云服务器上要确保 UDP 3478 能被客户端访问。 ## `webrtc.stun_host` @@ -103,11 +103,19 @@ webrtc: webrtc.external_ice_servers ``` -值是 ICE server 对象数组,例如: +默认使用 Cloudflare 和 Google 的公开 STUN 服务: + +```json +[ + { "urls": ["stun:stun.cloudflare.com:3478"] }, + { "urls": ["stun:stun.l.google.com:19302"] } +] +``` + +修改此运行时设置会替换默认列表,设置为 `[]` 可禁用全部外部 ICE server。TURN server 使用相同的数据格式: ```json [ - { "urls": ["stun:stun.l.google.com:19302"] }, { "urls": ["turn:turn.example.com:3478"], "username": "user", @@ -139,7 +147,7 @@ webrtc.external_ice_servers 如果 WebRTC 连接失败,按顺序检查: 1. 客户端是否能访问 SyncTV HTTP/gRPC API。 -2. UDP 3478 是否开放。 +2. 启用内置 STUN 时,UDP 3478 是否开放。 3. `stun_external_addr` 是否是客户端可达地址。 这个地址不能是 Pod IP、ClusterIP、回环地址或私网地址,除非你完全使用外部 ICE/TURN 并关闭内置 STUN。 4. 反向代理是否只代理了 HTTP,而没有开放 UDP。 diff --git a/docs/src/content/docs/en/configuration/full-example.mdx b/docs/src/content/docs/en/configuration/full-example.mdx index ad4a7cef..df019515 100644 --- a/docs/src/content/docs/en/configuration/full-example.mdx +++ b/docs/src/content/docs/en/configuration/full-example.mdx @@ -395,7 +395,7 @@ media_providers: webrtc: mode: "peer_to_peer" - enable_builtin_stun: true + enable_builtin_stun: false stun_port: 3478 stun_host: "0.0.0.0" stun_external_addr: "" diff --git a/docs/src/content/docs/en/configuration/webrtc.mdx b/docs/src/content/docs/en/configuration/webrtc.mdx index f40b7b3c..68d195fe 100644 --- a/docs/src/content/docs/en/configuration/webrtc.mdx +++ b/docs/src/content/docs/en/configuration/webrtc.mdx @@ -33,11 +33,11 @@ webrtc: ## Built-In STUN -`webrtc.enable_builtin_stun` default: `true`. +`webrtc.enable_builtin_stun` default: `false`. STUN helps clients discover their public mapped address for NAT traversal. -Disable it when: +Enable the built-in server only when this SyncTV deployment should provide STUN and clients can reach its UDP port. Keep it disabled when: - You use external STUN/TURN infrastructure. - The deployment cannot expose UDP. @@ -92,11 +92,19 @@ External ICE servers are managed through runtime settings, not static YAML: webrtc.external_ice_servers ``` -Value format: +The default uses public STUN services from Cloudflare and Google: + +```json +[ + { "urls": ["stun:stun.cloudflare.com:3478"] }, + { "urls": ["stun:stun.l.google.com:19302"] } +] +``` + +Set this runtime setting to replace the defaults. Use `[]` to disable all external ICE servers. TURN servers use the same value format: ```json [ - { "urls": ["stun:stun.l.google.com:19302"] }, { "urls": ["turn:turn.example.com:3478"], "username": "user", diff --git a/docs/src/content/docs/en/install/docker-compose.mdx b/docs/src/content/docs/en/install/docker-compose.mdx index 790bdeea..c9c7ed9b 100644 --- a/docs/src/content/docs/en/install/docker-compose.mdx +++ b/docs/src/content/docs/en/install/docker-compose.mdx @@ -79,7 +79,7 @@ The HTTP API is available at `http://localhost:8080`. On the first startup with Docker Compose loads the required `.env.postgres`, `.env.redis`, and `.env.synctv` files through `env_file`: PostgreSQL reads database variables, Redis reads `REDIS_PASSWORD`, and SyncTV reads `SYNCTV_*` application settings. The Compose file also fixes `SYNCTV_DATA_DIR` to `/data` so runtime data stays on the named volume. Reuse the same files across restarts, new shells, host reboots, and upgrades. A missing file causes `docker compose config` to fail immediately. -The production Compose file uses the `latest` image and fixed host ports for HTTP 8080, RTMP 1935, and STUN 3478/udp. Container-only health port `8081` and metrics port `9090` are not published by the default Compose file. +The production Compose file uses the `latest` image and fixed host ports for HTTP 8080 and RTMP 1935. The built-in STUN server is disabled by default. Container-only health port `8081` and metrics port `9090` are not published by the default Compose file. ## Volumes @@ -106,10 +106,11 @@ Host mappings and container listeners are managed separately: | --- | --- | --- | | `8080` | `8080` | HTTP REST + public gRPC | | `1935` | `1935` | RTMP | -| `3478/udp` | `3478/udp` | STUN | | No host mapping | `8081` | liveness/readiness for the container healthcheck | | No host mapping | `9090` | Prometheus metrics for a collector on the container network | +To host the built-in STUN server, set `SYNCTV_WEBRTC_ENABLE_BUILTIN_STUN=true`, configure a client-reachable `SYNCTV_WEBRTC_STUN_EXTERNAL_ADDR`, and add `3478:3478/udp` to the SyncTV service's `ports` list. + When a host port is already in use, change the host mapping in the Compose file and run `docker compose config` again. ## Backups diff --git a/docs/src/content/docs/en/reference/configuration-index.mdx b/docs/src/content/docs/en/reference/configuration-index.mdx index a8174d59..75876f38 100644 --- a/docs/src/content/docs/en/reference/configuration-index.mdx +++ b/docs/src/content/docs/en/reference/configuration-index.mdx @@ -344,7 +344,7 @@ file_storage: | `media_providers.emby.request_timeout_seconds` | seconds | `30` | Local Emby/Jellyfin provider request timeout | | `media_providers.emby.connect_timeout_seconds` | seconds | `10` | Local Emby/Jellyfin provider connection timeout | | `webrtc.mode` | `signaling_only` or `peer_to_peer` | `peer_to_peer` | WebRTC operating mode | -| `webrtc.enable_builtin_stun` | boolean | `true` | Enable built-in STUN | +| `webrtc.enable_builtin_stun` | boolean | `false` | Enable built-in STUN | | `webrtc.stun_port` | port | `3478` | STUN port | | `webrtc.stun_host` | IP/listen address | `0.0.0.0` | STUN listen address | | `webrtc.stun_external_addr` | string | `""` | STUN external address announced to clients | diff --git a/docs/src/content/docs/en/reference/runtime-settings.mdx b/docs/src/content/docs/en/reference/runtime-settings.mdx index 3d38b85e..c7a9d732 100644 --- a/docs/src/content/docs/en/reference/runtime-settings.mdx +++ b/docs/src/content/docs/en/reference/runtime-settings.mdx @@ -189,10 +189,10 @@ Optional configuration is updated through its FieldMask path, for example `{"set | Key | Type | Default | Validation | Meaning | | --- | --- | --- | --- | --- | -| `webrtc.externalIceServers` | JSON/string structure | `[]` | Valid ICE server array | External ICE servers returned to native clients | +| `webrtc.externalIceServers` | JSON/string structure | Cloudflare and Google public STUN | Valid ICE server array | External ICE servers returned to native clients | | `webrtc.maxVoiceParticipantsPerRoom` | u32 | `8` | `2..=32` | Maximum simultaneous voice Mesh participants per room | -The external list is empty by default. When built-in STUN is enabled and has a client-reachable advertised address, the server adds it to the ICE servers returned to clients. Built-in STUN listener configuration is startup configuration. See [WebRTC Configuration](../../configuration/webrtc/). +The default value is `[{"urls":["stun:stun.cloudflare.com:3478"]},{"urls":["stun:stun.l.google.com:19302"]}]`. Updating the setting replaces this list; use `[]` to disable all external ICE servers. When built-in STUN is enabled and has a client-reachable advertised address, the server also adds it to the ICE servers returned to clients. Built-in STUN listener configuration is startup configuration. See [WebRTC Configuration](../../configuration/webrtc/). The voice limit is read dynamically for every join. Lowering it blocks subsequent joins while preserving existing calls; leaving or disconnecting releases a slot immediately. Multi-replica deployments coordinate room slots atomically through Redis. diff --git a/docs/src/content/docs/install/docker-compose.mdx b/docs/src/content/docs/install/docker-compose.mdx index 125a9b06..1b9c8336 100644 --- a/docs/src/content/docs/install/docker-compose.mdx +++ b/docs/src/content/docs/install/docker-compose.mdx @@ -79,7 +79,7 @@ HTTP API 使用 `http://localhost:8080`。首次启动数据库为空时,root Docker Compose 通过 `env_file` 分别加载必需的 `.env.postgres`、`.env.redis` 和 `.env.synctv`:PostgreSQL 读取数据库变量,Redis 读取 `REDIS_PASSWORD`,SyncTV 读取 `SYNCTV_*` 应用配置。Compose 文件另外固定将 `SYNCTV_DATA_DIR` 设置为 `/data`,让运行数据写入命名卷。重启、换 shell、服务器重启或升级时都应复用同一组环境文件。缺少任一文件时,`docker compose config` 会直接失败。 -生产 Compose 文件固定使用 `latest` 镜像以及 HTTP 8080、RTMP 1935、STUN 3478/udp 宿主机端口。容器内的健康端口 `8081`、metrics 端口 `9090` 不会被默认 Compose 文件映射到宿主机。 +生产 Compose 文件固定使用 `latest` 镜像以及 HTTP 8080、RTMP 1935 宿主机端口。内置 STUN 默认关闭。容器内的健康端口 `8081`、metrics 端口 `9090` 不会被默认 Compose 文件映射到宿主机。 ## 数据卷 @@ -106,10 +106,11 @@ environment: | --- | --- | --- | | `8080` | `8080` | HTTP REST + public gRPC | | `1935` | `1935` | RTMP | -| `3478/udp` | `3478/udp` | STUN | | 无宿主机映射 | `8081` | liveness/readiness,供容器 healthcheck 使用 | | 无宿主机映射 | `9090` | Prometheus metrics,供容器网络内的 Prometheus 抓取 | +需要自行提供内置 STUN 时,在 `.env.synctv` 中设置 `SYNCTV_WEBRTC_ENABLE_BUILTIN_STUN=true` 和客户端可达的 `SYNCTV_WEBRTC_STUN_EXTERNAL_ADDR`,并在 SyncTV 服务的 `ports` 列表中添加 `3478:3478/udp`。 + 宿主机端口冲突时,修改 Compose 文件中的宿主机端口映射并重新执行 `docker compose config`。 ## 备份 diff --git a/docs/src/content/docs/reference/configuration-index.mdx b/docs/src/content/docs/reference/configuration-index.mdx index 9fad730d..5d314bea 100644 --- a/docs/src/content/docs/reference/configuration-index.mdx +++ b/docs/src/content/docs/reference/configuration-index.mdx @@ -344,7 +344,7 @@ file_storage: | `media_providers.emby.request_timeout_seconds` | 秒数 | `30` | 本地 Emby/Jellyfin provider 请求总超时 | | `media_providers.emby.connect_timeout_seconds` | 秒数 | `10` | 本地 Emby/Jellyfin provider 建连超时 | | `webrtc.mode` | `signaling_only` 或 `peer_to_peer` | `peer_to_peer` | WebRTC 工作模式 | -| `webrtc.enable_builtin_stun` | 布尔值 | `true` | 是否启用内置 STUN | +| `webrtc.enable_builtin_stun` | 布尔值 | `false` | 是否启用内置 STUN | | `webrtc.stun_port` | 端口 | `3478` | STUN 端口 | | `webrtc.stun_host` | IP/监听地址 | `0.0.0.0` | STUN 监听地址 | | `webrtc.stun_external_addr` | 字符串 | `""` | 对客户端公布的 STUN 外部地址 | diff --git a/docs/src/content/docs/reference/runtime-settings.mdx b/docs/src/content/docs/reference/runtime-settings.mdx index 1bbede2f..d6f50170 100644 --- a/docs/src/content/docs/reference/runtime-settings.mdx +++ b/docs/src/content/docs/reference/runtime-settings.mdx @@ -189,10 +189,10 @@ SOCKS5 代理 URL 只包含 scheme、host 和 port。代理认证信息位于 `s | Key | 类型 | 默认值 | 校验 | 说明 | | --- | --- | --- | --- | --- | -| `webrtc.externalIceServers` | JSON/字符串结构 | `[]` | 有效 ICE server 数组 | 返回给原生客户端的外部 ICE servers | +| `webrtc.externalIceServers` | JSON/字符串结构 | Cloudflare 和 Google 公开 STUN | 有效 ICE server 数组 | 返回给原生客户端的外部 ICE servers | | `webrtc.maxVoiceParticipantsPerRoom` | u32 | `8` | `2..=32` | 每个房间同时加入语音通话的 Mesh 参与者上限 | -外部列表默认值为空。内置 STUN 启用且具有客户端可达的公布地址时,服务端会将它加入返回给客户端的 ICE servers。内置 STUN 监听端口、host、candidate 过滤等启动配置见 [WebRTC 配置](../../configuration/webrtc/)。 +默认值为 `[{"urls":["stun:stun.cloudflare.com:3478"]},{"urls":["stun:stun.l.google.com:19302"]}]`。更新此设置会替换该列表;设置为 `[]` 可禁用全部外部 ICE server。内置 STUN 启用且具有客户端可达的公布地址时,服务端也会将它加入返回给客户端的 ICE servers。内置 STUN 监听端口、host、candidate 过滤等启动配置见 [WebRTC 配置](../../configuration/webrtc/)。 语音人数上限在每次加入时动态读取。降低上限会阻止后续参与者加入,当前通话连接保持;离开或断开连接会立即释放席位。多副本部署通过 Redis 原子协调房间席位。 diff --git a/helm/synctv/README.md b/helm/synctv/README.md index a8501a30..e370f3a9 100644 --- a/helm/synctv/README.md +++ b/helm/synctv/README.md @@ -568,7 +568,7 @@ alerting: | HTTP API: 8080 | | gRPC: 8080 | | RTMP: 1935 | - | STUN: 3478/udp | + | STUN: 3478/udp | (optional) +----+----------+------+ ^ ^ | | diff --git a/helm/synctv/values.yaml b/helm/synctv/values.yaml index 8bcbff45..8eb30d5d 100644 --- a/helm/synctv/values.yaml +++ b/helm/synctv/values.yaml @@ -565,7 +565,7 @@ config: webrtc: ## Allowed values: signaling_only, peer_to_peer mode: "peer_to_peer" - enableBuiltinStun: true + enableBuiltinStun: false stunPort: 3478 stunHost: "0.0.0.0" ## Public client-reachable STUN address, e.g. "stun.example.com:3478" diff --git a/scripts/validate-helm.sh b/scripts/validate-helm.sh index 1d92cc88..b6485b1b 100755 --- a/scripts/validate-helm.sh +++ b/scripts/validate-helm.sh @@ -570,6 +570,7 @@ assert_pdb_field_absent "$tmp_dir/pdb-legacy-min-available.yaml" maxUnavailable if helm template synctv "$chart_dir" \ --namespace "$namespace" \ + --set config.webrtc.enableBuiltinStun=true \ --set stunService.enabled=true \ --set config.webrtc.stunExternalAddr=203.0.113.10:3478 \ >"$tmp_dir/clusterip-stun.yaml" 2>"$tmp_dir/clusterip-stun.err"; then @@ -578,6 +579,7 @@ fi helm template synctv "$chart_dir" \ --namespace "$namespace" \ + --set config.webrtc.enableBuiltinStun=true \ --set stunService.enabled=true \ --set stunService.type=LoadBalancer \ --set config.webrtc.stunExternalAddr=203.0.113.10:3478 \ diff --git a/synctv-api/tests/webrtc_integration_test.rs b/synctv-api/tests/webrtc_integration_test.rs index fb8e4df5..95a09a59 100644 --- a/synctv-api/tests/webrtc_integration_test.rs +++ b/synctv-api/tests/webrtc_integration_test.rs @@ -50,7 +50,7 @@ impl Default for Config { server: ServerConfig::default(), webrtc: WebRTCConfig { mode: WebRTCMode::PeerToPeer, - enable_builtin_stun: true, + enable_builtin_stun: false, stun_port: 3478, stun_host: "0.0.0.0".to_string(), stun_external_addr: String::new(), diff --git a/synctv-core/src/service/global_settings.rs b/synctv-core/src/service/global_settings.rs index 12de70ad..aace737a 100644 --- a/synctv-core/src/service/global_settings.rs +++ b/synctv-core/src/service/global_settings.rs @@ -321,7 +321,7 @@ setting!( ExternalIceServersSetting, IceServerList, "webrtc.external_ice_servers", - IceServerList::new() + IceServerList::public_stun_defaults() ); setting!( MaxVoiceParticipantsPerRoomSetting, @@ -1468,6 +1468,27 @@ mod tests { setting } + #[test] + fn test_runtime_settings_default_to_public_stun_servers() { + let store = RuntimeSettingsStore::new_for_tests(); + let settings = ok(store.runtime_settings(), "runtime settings should load"); + + assert_eq!( + settings.webrtc.external_ice_servers, + IceServerList::public_stun_defaults() + ); + + ok( + store + .webrtc + .external_ice_servers + .set_for_test(&IceServerList::new()), + "external ICE servers should update", + ); + let settings = ok(store.runtime_settings(), "runtime settings should reload"); + assert!(settings.webrtc.external_ice_servers.0.is_empty()); + } + #[test] fn test_runtime_settings_reads_complete_storage_generations() { let store = Arc::new(RuntimeSettingsStore::new_for_tests()); diff --git a/synctv-core/src/service/global_settings/types.rs b/synctv-core/src/service/global_settings/types.rs index 0ce5bb14..a220293e 100644 --- a/synctv-core/src/service/global_settings/types.rs +++ b/synctv-core/src/service/global_settings/types.rs @@ -320,6 +320,14 @@ impl IceServerList { pub fn new() -> Self { Self(Vec::new()) } + + #[must_use] + pub fn public_stun_defaults() -> Self { + Self(vec![ + ConfiguredIceServer::new(vec!["stun:stun.cloudflare.com:3478".to_string()]), + ConfiguredIceServer::new(vec!["stun:stun.l.google.com:19302".to_string()]), + ]) + } } impl Default for IceServerList { diff --git a/synctv-core/tests/global_settings_registry_tests.rs b/synctv-core/tests/global_settings_registry_tests.rs index 5c116fc9..1d39945c 100644 --- a/synctv-core/tests/global_settings_registry_tests.rs +++ b/synctv-core/tests/global_settings_registry_tests.rs @@ -35,10 +35,15 @@ fn test_room_password_policy_parse_and_display() { } #[test] -fn test_external_ice_server_list_defaults_empty_and_accepts_custom_servers() { +fn test_external_ice_server_list_supports_public_defaults_and_custom_servers() { let list = IceServerList::new(); assert!(list.0.is_empty()); + let defaults = IceServerList::public_stun_defaults(); + assert_eq!(defaults.0.len(), 2); + assert_eq!(defaults.0[0].urls, ["stun:stun.cloudflare.com:3478"]); + assert_eq!(defaults.0[1].urls, ["stun:stun.l.google.com:19302"]); + let custom = IceServerList(vec![ ConfiguredIceServer::new(vec!["stun:custom1.example.com:19302".to_string()]), ConfiguredIceServer::new(vec![ diff --git a/synctv.example.yaml b/synctv.example.yaml index 7d3f8a64..6c75a364 100644 --- a/synctv.example.yaml +++ b/synctv.example.yaml @@ -775,13 +775,14 @@ webauthn: # # WebRTC supports 2 operation modes: # - signaling_only: Pure signaling relay without ICE bootstrap -# - peer_to_peer: P2P with built-in STUN plus optional external ICE servers +# - peer_to_peer: P2P with public ICE servers and optional built-in STUN webrtc: mode: "peer_to_peer" # --- Built-in STUN Server --- - enable_builtin_stun: true + # Disabled by default. Enable only when this deployment should host STUN. + enable_builtin_stun: false stun_port: 3478 stun_host: "0.0.0.0" # External address for STUN reflexive candidates. In K8s/NAT, set this to @@ -801,7 +802,7 @@ webrtc: # --- External ICE Servers --- # Configured dynamically via the settings API (no restart required): # "webrtc.external_ice_servers" - JSON array of ICE server objects, e.g.: - # [{"urls":["stun:stun.example.com:3478"]},{"urls":["turn:turn.example.com:3478"],"username":"user","credential":"pass"}] + # [{"urls":["stun:stun.cloudflare.com:3478"]},{"urls":["stun:stun.l.google.com:19302"]}] # ============================================================================ # Dynamic ICE Servers Configuration @@ -811,7 +812,8 @@ webrtc: # # Setting key: "webrtc.external_ice_servers" # Value: JSON array of ICE server objects - # Default: [] (the built-in STUN server is used) + # Default: Cloudflare and Google public STUN servers shown above. + # Set [] to disable all external ICE servers. # # Changes take effect immediately without restarting the server. diff --git a/synctv/src/app.rs b/synctv/src/app.rs index 52a639cd..4311616f 100644 --- a/synctv/src/app.rs +++ b/synctv/src/app.rs @@ -2101,10 +2101,7 @@ mod tests { chat: crate::app_config::ChatConfig::default(), webauthn: WebAuthnConfig::default(), media_providers: MediaProvidersConfig::default(), - webrtc: WebRTCConfig { - stun_external_addr: "203.0.113.1:3478".to_string(), - ..WebRTCConfig::default() - }, + webrtc: WebRTCConfig::default(), connection_limits: ConnectionLimitsConfig::default(), bootstrap: BootstrapConfig { create_root_user: false, diff --git a/synctv/src/app_config/mod.rs b/synctv/src/app_config/mod.rs index 281a3be6..1d6a0e22 100644 --- a/synctv/src/app_config/mod.rs +++ b/synctv/src/app_config/mod.rs @@ -1316,7 +1316,7 @@ impl Default for WebRTCConfig { fn default() -> Self { Self { mode: WebRTCMode::PeerToPeer, - enable_builtin_stun: true, + enable_builtin_stun: false, stun_port: 3478, stun_host: "0.0.0.0".to_string(), stun_external_addr: String::new(), @@ -1791,3 +1791,13 @@ impl Default for AppConfig { } } } + +#[cfg(test)] +mod tests { + use super::WebRTCConfig; + + #[test] + fn default_webrtc_config_disables_builtin_stun() { + assert!(!WebRTCConfig::default().enable_builtin_stun); + } +} diff --git a/synctv/tests/cluster_startup_failure_tests.rs b/synctv/tests/cluster_startup_failure_tests.rs index 02a10c5a..852f1660 100644 --- a/synctv/tests/cluster_startup_failure_tests.rs +++ b/synctv/tests/cluster_startup_failure_tests.rs @@ -155,10 +155,7 @@ fn cluster_test_config() -> Config { chat: ChatConfig::default(), webauthn: WebAuthnConfig::default(), media_providers: MediaProvidersConfig::default(), - webrtc: WebRTCConfig { - stun_external_addr: "203.0.113.1:3478".to_string(), - ..WebRTCConfig::default() - }, + webrtc: WebRTCConfig::default(), connection_limits: ConnectionLimitsConfig::default(), bootstrap: BootstrapConfig { create_root_user: true,