feat(remote): add --local-proxy to send server requests through the client

A reverse tunnel over the remote connection: the server's service traffic leaves through the client's own network. Only the client dials. Adds the tunnel-test command.
This commit is contained in:
imSp4rky
2026-10-21 17:09:37 -06:00
parent afaff8e070
commit 23614fd938
13 changed files with 1242 additions and 13 deletions
+12 -1
View File
@@ -146,10 +146,14 @@ hood the client walks a remote session through its lifecycle.
`controld://<resolver>@dns.controld.com`. The server counts it as a full proxy
URI and runs its own forwarder for it. A bare region skips Control D, because its
proxy is a forwarder on the client. See
[Control D](../../guide/proxies-and-vpn.md#control-d)
[Control D](../../guide/proxies-and-vpn.md#control-d).
The client also downloads playlists and segments through the same proxy,
as a local run does. `--no-proxy-download` and `--proxy-download` change
that for downloads only.
- A `--local-proxy` goes as `tunnel:<id>`. The client opens
`GET /api/tunnel` first, and the server replaces the ID with a loopback SOCKS5
proxy that relays to the client's own connection. See
[Send the server's requests through your machine](../../guide/proxies-and-vpn.md#send-the-servers-requests-through-your-machine).
- Track-selection hints (`range_`, `vcodec`, `quality`, `best_available`,
`generate_dv`) so the server fetches the right manifests
- Your language and audio codec selection (`-l`, `-vl`, `-al`, `-a`, `-fs`)
@@ -400,6 +404,11 @@ serve:
gets a persistent per-account cache instead, so the account's tokens outlive the
remote session. The server and the client both refuse a forwarded cache file that is not
JSON or that names a type other than a date or time, and the service then authenticates again.
- **A tunnel belongs to its API key.** The server accepts `tunnel:<id>` only from the API key
that opened the tunnel. Its SOCKS5 proxy listens on loopback only, and it needs a username and
password for that tunnel, so a raw `socks5h://127.0.0.1:<port>` proxy from a client fails.
- **Only the client dials.** The server ignores a request from the client to open a
connection, so a tunnel never gives a client access to the server's network.
- **The server redacts secrets in logs.** It redacts remote session IDs, service tags,
and other user values before it writes them to the log.
@@ -427,6 +436,8 @@ The server mounts all these routes, even in `--remote-only` mode. Paths use the
| Method | Path | Purpose |
|---|---|---|
| `POST` | `/api/session/create` | Build + authenticate a service instance; returns a session ID immediately |
| `GET` | `/api/tunnel` | WebSocket that relays the connections of a `tunnel:<id>` proxy to the client; `?id=` reconnects |
| `GET` | `/api/tunnel/check?id=` | Exit IP address and lookup time through the caller's tunnel, for `unshackle tunnel-test` |
| `GET` | `/api/session/{id}/titles` | Fetch and cache the title list |
| `POST` | `/api/session/{id}/tracks` | Resolve tracks, manifests, chapters, headers/cookies for a title |
| `POST` | `/api/session/{id}/segments` | Resolve per-segment/track download descriptors |
+20
View File
@@ -207,6 +207,7 @@ order the service used, then renumbers the episodes into the order you asked for
| Flag | Description |
|---|---|
| `--proxy` | Proxy URI, a country or location code resolved from configured providers, or `provider:region` (e.g. `nordvpn:ca`, `gluetun:us`, `protonvpn:de:berlin`, `controld:yul`). |
| `--local-proxy` | With `--remote`, send the server's service requests through your machine's own connection. Works with no other proxy option. Check it first with `unshackle tunnel-test`. See [Proxies & VPN](proxies-and-vpn.md#send-the-servers-requests-through-your-machine). |
| `--no-proxy` | Force-disable all proxy use. |
| `--no-proxy-download` | Bypass the proxy for **all downloads** (manifest, licence, and auth stay proxied). |
| `--proxy-download` | Use a different proxy for **all downloads**, in the same forms as `--proxy` (manifest, licence, and auth stay on `--proxy`). |
@@ -250,6 +251,7 @@ unshackle logs in to the service before the search. A service that declares a se
|---|---|
| `-p`, `--profile` | Profile for credentials and cookies. |
| `--proxy` | Proxy URI, country or location code, or `provider:region` (e.g. `controld:ca`). |
| `--local-proxy` | With `--remote`, send the server's search requests through your machine's own connection. Works with no other proxy option. |
| `--no-proxy` | Force-disable all proxy use. |
| `--remote` | Search on a remote unshackle server. The server logs in to the service. unshackle sends it your credentials, cookies and cache for the profile, and saves the cache that the server sends back. A service that searches with no login gets none of them. |
| `--local` | Search with the local service code, also when the config sets `remote: true` for the service. |
@@ -285,6 +287,24 @@ The export file can be a `mediaexport` file from `dl --export`, an export from a
---
## `tunnel-test`
Check `--local-proxy` before a real `--remote` run. The command finds the IP address that this
machine exits from, opens a tunnel to the server, and makes the server look up its exit IP address
through the tunnel. It stops at the first step that fails.
```
unshackle tunnel-test [--server NAME]
```
| Flag | Description |
|---|---|
| `--server` | Name of the remote server from `remote_services` config, if you configure more than one. |
See [Proxies & VPN](proxies-and-vpn.md#send-the-servers-requests-through-your-machine).
---
## `setup`
Install the external tools and make a first config.
+71 -1
View File
@@ -22,11 +22,12 @@ a query to a real proxy URL: [basic static proxies](#basic-static-proxies),
## The `--proxy` flag
Four options on the `dl` command control proxying at download time:
Five options on the `dl` command control proxying at download time:
| Option | Effect |
|---|---|
| `--proxy` | The proxy to use. Either an explicit URI, or a query that unshackle resolves against your configured providers. |
| `--local-proxy` | With `--remote`, send the server's service requests through your machine's own connection. Works with no other proxy option. See [Send the server's requests through your machine](#send-the-servers-requests-through-your-machine). |
| `--no-proxy` | Force **all** proxy use off. No providers are initialised and no proxy query is resolved. |
| `--no-proxy-download` | Use the proxy for the manifest, licence, and authentication, but bypass it for the **downloads** themselves. |
| `--proxy-download` | Use `--proxy` for the manifest, licence, and authentication, and a different proxy for the **downloads**. Takes the same forms as `--proxy`. |
@@ -204,6 +205,75 @@ sees your own IP, but the service does not.
A `socks5://` proxy makes the DNS lookup of each host on your machine and gives no warning.
Use `socks5h://` to make the proxy do the DNS lookups too.
### Send the server's requests through your machine
With `--remote`, the server sends the service requests, and they leave from the server's own
connection. Add `--local-proxy` to send them through your machine's connection instead:
```shell
unshackle dl --remote --local-proxy EXAMPLE 81234567
```
A VPN app that covers your whole machine then also covers the server's requests, so the
service sees your VPN's IP address. `--local-proxy` needs `--remote` (or a service with
`remote: true`), and it works with no other proxy option: unshackle stops if you also give
`--proxy`, `--no-proxy`, `--proxy-download` or `--no-proxy-download`. A proxy option from the `dl`
config defaults does not count: `--local-proxy` turns it off for the run.
A `--proxy` on `localhost` or a loopback address, such as the local port of a VPN app, stops a
`--remote` run with `That proxy is on your machine, so --remote cannot use it`. Connect the VPN
app for the whole machine, then use `--local-proxy` without `--proxy`.
#### Test it first
`unshackle tunnel-test` checks the route before a real run. Add `--server <name>` to test a
server other than the default one:
```shell
unshackle tunnel-test
```
It does three steps, and it stops at the first one that fails:
1. It looks up the IP address that your machine exits from.
2. It opens a tunnel to the server.
3. It makes the server look up its exit IP address through the tunnel. That address must be
the same as in step 1, or in the same country when one lookup used IPv4 and the other IPv6.
It fails when the lookup left from the server's own address. It also shows how long one
lookup takes.
#### How the tunnel works
1. The client opens a WebSocket on the server, on the same host and port as the other
remote session requests. Only a client opens a tunnel. The server never connects to
a host for a client, so a tunnel gives no access to the server's network.
2. The server gives the service a SOCKS5 proxy on its loopback address, with a username and
password for that tunnel only. Another API key cannot use it.
3. The server sends each connection that the service opens through the tunnel. The client
connects to the host and relays the bytes.
The client logs `The server sends its requests through your connection on this machine`. You
do not configure ports: the server picks a free one, and your
machine opens none, because the client opens the connection to the server. A NAT router, a
firewall, or a Cloudflare tunnel in front of the server does not stop it.
The service traffic stays encrypted from the server to the service, so the client relays only
encrypted bytes. The client refuses to connect to `localhost`, and to a host name or IP address
that resolves only to a loopback, private or link-local address, so the server cannot reach
your local network.
!!! note "Each request crosses the tunnel twice"
A request from the server goes to your machine and then to the service, so it takes
longer than a request that the server sends directly. The segments do not go through the
tunnel: the client downloads them as before.
A service that uses a server account does not work with `--local-proxy`, because the server
logs in with its own account. If the tunnel drops, the client connects again, and a new request
from the server waits up to 10 seconds for it. A request that was in progress when the tunnel
dropped fails. The server closes a tunnel when its client has not connected for 60 seconds, and
the client then stops with `The tunnel to the server closed`. An API key can have 4 tunnels
open at once.
## Basic (static proxies)
The `Basic` proxy provider is pure static configuration, with no accounts and no network