[PR #441] fix: serialize the RTSP handshake per client IP (same-NAT clients) #469

Open
opened 2026-10-15 13:00:48 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/wolf/pull/441
Author: @kmreisi
Created: 6/22/2026
Status: 🔄 Open

Base: beta ← Head: fix/multi-port-rtsp-udp


📝 Commits (1)

  • 91c55f8 fix: serialize the RTSP handshake per client IP

📊 Changes

3 files changed (+161 additions, -3 deletions)

View changed files

📝 src/moonlight-server/rest/endpoints.hpp (+9 -0)
📝 src/moonlight-server/rtsp/net.hpp (+40 -3)
➕ src/moonlight-server/rtsp/rtsp_handshake.hpp (+112 -0)

📄 Description

Wolf gives each session a random fake IP in the launch response and expects the client to echo it back as the RTSP Host/URI — that's how it tells concurrent sessions apart. But below the high-quality-audio threshold moonlight-common-c sends Host: 0.0.0.0 and drops it, and there is no other per-session identifier in the RTSP handshake (the Session header is a constant). So two clients behind the same NAT both arrive as 0.0.0.0 from the same source IP, Wolf routes both to the first session, and their AES keys and ENET secrets get swapped — video corrupts and one client's input lands in the other's session.

Since the client can't reliably tell us which session it is, this couples each HTTPS launch/resume with the RTSP handshake that follows it: the launching client owns an exclusive RTSP window for its source IP, held until the handshake finishes (RTSP PLAY). While the window is open, ambiguous 0.0.0.0 packets from that IP resolve to that session; a concurrent same-IP launch waits until the window closes, so two handshakes never overlap.

client                                    Wolf
  |  HTTPS /launch (or /resume)             |
  |----------------------------------------→|  create session S
  |                                         |  open RTSP window:  srcIP → S
  |  200 (rtsp setup port)                  |
  |←----------------------------------------|
  |                                         |
  |  RTSP OPTIONS→DESCRIBE→SETUP(a/v/ctrl)   |
  |      →ANNOUNCE       (Host: 0.0.0.0)     |
  |----------------------------------------→|  window open → every packet → S
  |←----------------------------------------|  (SETUP control hands back S's ENET secret)
  |                                         |
  |  RTSP PLAY                              |
  |----------------------------------------→|  resolve → S, then close S's window
  |←----------------------------------------|
  |                                         |
  | === video/audio (UDP) + input (ENET), demuxed per session by that secret ===

A second distinct client on the same NAT that launches mid-handshake blocks at its own /launch until the first window closes, so the two handshakes are serialized and never resolve to each other.

Clients that do echo the fake IP (above the threshold) are still matched directly by it — the window only covers the 0.0.0.0 case.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/wolf/pull/441 **Author:** [@kmreisi](https://github.com/kmreisi) **Created:** 6/22/2026 **Status:** 🔄 Open **Base:** `beta` ← **Head:** `fix/multi-port-rtsp-udp` --- ### 📝 Commits (1) - [`91c55f8`](https://github.com/games-on-whales/wolf/commit/91c55f8d404be730c40515972331b7e1a20a97eb) fix: serialize the RTSP handshake per client IP ### 📊 Changes **3 files changed** (+161 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `src/moonlight-server/rest/endpoints.hpp` (+9 -0) 📝 `src/moonlight-server/rtsp/net.hpp` (+40 -3) ➕ `src/moonlight-server/rtsp/rtsp_handshake.hpp` (+112 -0) </details> ### 📄 Description Wolf gives each session a random **fake IP** in the launch response and expects the client to echo it back as the RTSP `Host`/URI — that's how it tells concurrent sessions apart. But below the high-quality-audio threshold `moonlight-common-c` sends `Host: 0.0.0.0` and drops it, and there is no other per-session identifier in the RTSP handshake (the `Session` header is a constant). So two clients behind the same NAT both arrive as `0.0.0.0` from the same source IP, Wolf routes both to the first session, and their AES keys and ENET secrets get swapped — video corrupts and one client's input lands in the other's session. Since the client can't reliably tell us which session it is, this couples each HTTPS launch/resume with the RTSP handshake that follows it: the launching client owns an exclusive RTSP window for its source IP, held until the handshake finishes (RTSP `PLAY`). While the window is open, ambiguous `0.0.0.0` packets from that IP resolve to that session; a concurrent same-IP launch waits until the window closes, so two handshakes never overlap. ``` client Wolf | HTTPS /launch (or /resume) | |----------------------------------------→| create session S | | open RTSP window: srcIP → S | 200 (rtsp setup port) | |←----------------------------------------| | | | RTSP OPTIONS→DESCRIBE→SETUP(a/v/ctrl) | | →ANNOUNCE (Host: 0.0.0.0) | |----------------------------------------→| window open → every packet → S |←----------------------------------------| (SETUP control hands back S's ENET secret) | | | RTSP PLAY | |----------------------------------------→| resolve → S, then close S's window |←----------------------------------------| | | | === video/audio (UDP) + input (ENET), demuxed per session by that secret === ``` A second **distinct** client on the same NAT that launches mid-handshake blocks at its own `/launch` until the first window closes, so the two handshakes are serialized and never resolve to each other. Clients that *do* echo the fake IP (above the threshold) are still matched directly by it — the window only covers the `0.0.0.0` case. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/wolf#469
No description provided.