[PR #475] feat(api): add POST /api/v1/sessions/resume to re-key a paused session #490

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

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/wolf/pull/475
Author: @darrengruber
Created: 8/10/2026
Status: 🔄 Open

Base: beta ← Head: api-session-resume


📝 Commits (1)

  • 02b472d feat(api): add POST /api/v1/sessions/resume to re-key a paused session

📊 Changes

3 files changed (+62 additions, -0 deletions)

View changed files

📝 src/moonlight-server/api/api.hpp (+8 -0)
📝 src/moonlight-server/api/endpoints.cpp (+38 -0)
📝 src/moonlight-server/api/unix_socket_server.cpp (+16 -0)

📄 Description

Problem

A Moonlight client generates a new rikey every time it reconnects, but a StreamSession keeps the AES key it was created with and decrypts the ENet control stream with it. A client that reconnects through the socket API therefore fails every control message with EVP_DecryptFinal_ex, never completes a ping, and reports a dead network to the user — while the server logs a clean 200 and the app keeps running happily.

Observed against 1.26.7/stable: three reconnects, three different rikeys, and a wall of

EVP_DecryptFinal_ex failed
...
[ENET] disconnected client: 10.0.4.168:24033
[GSTREAMER] Pausing pipeline: 6142509188972423790

Why the existing endpoints can't express it

The HTTPS server doesn't have this problem — resume() in rest/endpoints.hpp rebuilds the session from the new rikey and carries the Wayland display and virtual input devices across. There is no socket equivalent, and it can't be assembled from what's there:

  • /sessions/add appends a new session and can't carry over the display, so it rebuilds the compositor and restarts the app — the opposite of resuming.
  • /sessions/start only fires the VideoSession and AudioSession events; it never touches the session key.
  • get_session_by_id takes the first match, so adding a duplicate session id just resolves back to the stale one.

The change

Adds the missing primitive: copy the StreamSession, replace aes_key/aes_iv, swap it into running_sessions. The display, input devices and audio sink are all shared_ptr members, so the copy keeps the same desktop and the same devices already plugged into the container — which is what separates a resume from a launch.

rtsp_fake_ip and the RTP/ENet secret payloads are deliberately preserved: Wolf generates those rather than the client, and rtsp_fake_ip is how the RTSP thread identifies the session.

Verified

On a live session, pause → resume:

before after
session aes_key 3ACACAEE…0675 ADA28680…1FC3
pod / container restarts 0 0
sway / steam pid 186 / 417 same pids, uptime never reset
Wayland socket inode 1092100619 1092100619

The key rotates while the desktop and the running app stay exactly as they were.

Found while adding resume support to fenrir, a Kubernetes operator that drives Wolf entirely through the socket API.


🔄 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/475 **Author:** [@darrengruber](https://github.com/darrengruber) **Created:** 8/10/2026 **Status:** 🔄 Open **Base:** `beta` ← **Head:** `api-session-resume` --- ### 📝 Commits (1) - [`02b472d`](https://github.com/games-on-whales/wolf/commit/02b472de5d483ce4c19f80c766e1027020d6b17d) feat(api): add POST /api/v1/sessions/resume to re-key a paused session ### 📊 Changes **3 files changed** (+62 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/moonlight-server/api/api.hpp` (+8 -0) 📝 `src/moonlight-server/api/endpoints.cpp` (+38 -0) 📝 `src/moonlight-server/api/unix_socket_server.cpp` (+16 -0) </details> ### 📄 Description ## Problem A Moonlight client generates a **new rikey every time it reconnects**, but a `StreamSession` keeps the AES key it was created with and decrypts the ENet control stream with it. A client that reconnects through the socket API therefore fails every control message with `EVP_DecryptFinal_ex`, never completes a ping, and reports a dead network to the user — while the server logs a clean 200 and the app keeps running happily. Observed against 1.26.7/`stable`: three reconnects, three different rikeys, and a wall of ``` EVP_DecryptFinal_ex failed ... [ENET] disconnected client: 10.0.4.168:24033 [GSTREAMER] Pausing pipeline: 6142509188972423790 ``` ## Why the existing endpoints can't express it The HTTPS server doesn't have this problem — `resume()` in `rest/endpoints.hpp` rebuilds the session from the new rikey and carries the Wayland display and virtual input devices across. There is no socket equivalent, and it can't be assembled from what's there: - `/sessions/add` appends a new session and can't carry over the display, so it rebuilds the compositor and restarts the app — the opposite of resuming. - `/sessions/start` only fires the `VideoSession` and `AudioSession` events; it never touches the session key. - `get_session_by_id` takes the first match, so adding a duplicate session id just resolves back to the stale one. ## The change Adds the missing primitive: copy the `StreamSession`, replace `aes_key`/`aes_iv`, swap it into `running_sessions`. The display, input devices and audio sink are all `shared_ptr` members, so the copy keeps the **same** desktop and the same devices already plugged into the container — which is what separates a resume from a launch. `rtsp_fake_ip` and the RTP/ENet secret payloads are deliberately preserved: Wolf generates those rather than the client, and `rtsp_fake_ip` is how the RTSP thread identifies the session. ## Verified On a live session, pause → resume: | | before | after | |---|---|---| | session `aes_key` | `3ACACAEE…0675` | `ADA28680…1FC3` | | pod / container restarts | 0 | 0 | | `sway` / `steam` | pid 186 / 417 | same pids, uptime never reset | | Wayland socket inode | `1092100619` | `1092100619` | The key rotates while the desktop and the running app stay exactly as they were. Found while adding resume support to [fenrir](https://github.com/darren-iac/fenrir), a Kubernetes operator that drives Wolf entirely through the socket API. --- <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#490
No description provided.