Skip to content

Network and Remote Access

In this page

How Tuvima separates desired network configuration from observed connectivity and chooses remote playback delivery.

Where this lives in the code

  • src/MediaEngine.Api/Services/Networking
  • src/MediaEngine.Api/DependencyInjection/TuvimaNetworkServiceCollectionExtensions.cs
  • deploy

Tuvima treats networking as an appliance-style workflow. Administrators use Settings → Server → Network and Remote Access for a health overview, LAN settings, remote access, network-aware streaming, and advanced diagnostics. A new install uses the same panels in /setup/network; there is no second onboarding model. Tuvima is open from the start: any device that can reach the server can use it, and Tuvima sign-in is always required. The default needs no router, DNS, TLS, or tunnel knowledge, and a secure outside address is an optional extra.

Source of truth

Desired state lives in config/network.json and is validated against config/schemas/network.schema.json. There is no rule about how far away a visitor may be: the Dashboard answers every request whatever its origin, and Tuvima sign-in decides who gets in. The only exception is the no-password “use Tuvima on this computer” account, which any device that reaches the server can use, with its administrator rights, until a password or passkey is added (see Account security). Closed router ports are the gatekeeper. The Dashboard serves plain http, so if its port is forwarded to the internet, sign-ins and pairing codes cross the internet unencrypted; for access from outside, use Tailscale or an HTTPS reverse proxy. Schema 2.0 includes the Dashboard port, binding, local discovery, remote mode, advanced router automation, custom HTTPS address, exact/CIDR trusted proxies, and remote-streaming policy. Supported remote modes are tailscale, custom, and the advanced direct-only mode. The removed secure-provider placeholder is rejected. Keys from the retired reach and app-switch settings left in a user’s file are ignored.

Phone and TV apps are always allowed; there is no app on/off switch. The paired-app door (/.well-known/tuvima, /pair, /api/v1/** and /application-events/**) and the cast relay always answer. native_app_access.allow_browser_apps (default false, config file only) is a separate switch for packaged TV web apps, whose page origin is null or an app scheme. Only with it on does the Dashboard give those origins cross-origin permission for /api/v1/** and /.well-known/tuvima. Because any web page can claim the null origin, it also lets a website reach the anonymous pairing surface; pairing still needs a person to approve the code on /pair. See Configuration.

Addresses apps use

The discovery answer GET /.well-known/tuvima (Dashboard, anonymous) always answers and carries an optional outside_address (an origin) when a secure outside address is set up: the Tailscale address or the public hostname. The Engine supplies it through GET /network/outside-address (Dashboard service credential only), which returns { outside_address }. Settings → Network and Remote Access shows both: the home address (the Engine’s preferred address on the adapter chosen in Settings → Network, with the Dashboard port) and, when set, the outside address.

The Android app saves the address typed at pairing as its home address and learns the outside address from discovery, at pairing and at app start only. It tries home first (about 1.5 seconds), falls back to the outside address, rechecks when the phone’s network changes, rebases every request, stream and download to the current address, and sends its sign-in token only to those two addresses. Removing the outside address on the server reaches the phone after re-pairing. Apple, Roku and TV web apps do not do this yet.

Observed state stays in memory: usable interfaces, topology, current Tuvima-owned router mapping, public address, latest tests, measured bandwidth, and provider state. The API never writes those observations into configuration.

Supported secure edge

The Dashboard is the only supported user-facing service. The Engine stays on loopback or an internal application network. TLS terminates at one of these external edges:

  • Tailscale Serve for private tailnet HTTPS.
  • An administrator-managed HTTPS reverse proxy such as Caddy, nginx, or Traefik.

Tuvima authentication remains mandatory. Tailnet membership and forwarded identity headers do not replace the Dashboard sign-in boundary. Settings → Network → Remote Access (How the internet reaches Tuvima) sets up that optional secure outside address, and Test remote access runs a Tuvima challenge probe against the selected HTTPS/tunnel route. Passing the test does not unlock anything; the page only chooses and checks the outside address.

Connectivity lifecycle

  • Local discovery announces _tuvima._tcp.local when enabled.
  • Router setup is off by default and appears only under Advanced settings.
  • Advanced automatic setup tries PCP, NAT-PMP, then UPnP IGD, targeting an explicitly configured local TLS reverse-proxy listener rather than the Dashboard HTTP port.
  • Lease mappings are renewed every 15 minutes and after a safe port change.
  • Disabling router automation or a clean Engine shutdown removes only the mapping that Tuvima created.
  • A failed automatic setup produces a dynamic manual TCP forwarding rule.
  • CGNAT evidence changes the guidance instead of repeatedly recommending a router rule that is unlikely to work.
  • Docker bridge mode is classified as unsupported-topology. Inside the container, the visible gateway belongs to Docker’s bridge (commonly associated with docker0) rather than the physical LAN router. No PCP, NAT-PMP, or SSDP traffic is sent in that topology.

Mapping observations use explicit states: not-attempted, unsupported-topology, protocol-unavailable, router-refused, active, and expired. Protocol timeouts and malformed responses remain reason codes rather than being presented as proof that a router refused the request.

External reachability and upload measurements remain unknown when no trusted external measurement target exists. The UI does not substitute sample success, public-IP, or bandwidth values.

Tailscale deployment boundary

Tailscale is an external deployment provider, not an embedded VPN. Native hosts are observed with tailscale status --json and tailscale serve status --json. The Docker preset uses the official Tailscale sidecar, private Serve HTTPS, and a separate persistent state volume. Enrollment keys are deployment secrets outside Tuvima configuration and backups. The generic IRemoteConnectivityProvider boundary remains available for Headscale or a different tunnel provider.

Playback context

Playback manifests and heartbeats carry connection path, provider, approximate bandwidth, latency, device, profile, and optional room identity. These facts are used only for delivery selection and diagnostics—not authentication.

For remote playback, the existing playback manifest can prefer its existing HLS delivery path when source resolution or calculated bitrate exceeds the configured quality/bandwidth budget. Local compatible clients retain direct delivery. This does not introduce a second transcoding engine.

Security boundary

Every network settings and diagnostics endpoint requires the Administrator role. Reachability never grants authentication. Forwarded host, scheme, and client-address headers are accepted only from exact proxy IP addresses in trusted_proxies or explicit CIDR entries in trusted_proxy_networks, with one forwarding hop allowed. The middleware runs before HSTS, redirection, authentication, and URL generation. Forwarded headers are honoured only on the optional proxy port (remote.proxy_port); on the main Dashboard port they are ignored. Where a request comes from never decides whether the Dashboard answers it, and plain http is not refused. A Host allow-list (local.allowed_hostnames plus the machine, server and public names) refuses unknown host names to block DNS rebinding.