Skip to content

feat(marmot-app): optional SOCKS5 proxy for relay connections#783

Open
tsgx1990 wants to merge 3 commits into
marmot-protocol:masterfrom
tsgx1990:moyu/relay-proxy
Open

feat(marmot-app): optional SOCKS5 proxy for relay connections#783
tsgx1990 wants to merge 3 commits into
marmot-protocol:masterfrom
tsgx1990:moyu/relay-proxy

Conversation

@tsgx1990

@tsgx1990 tsgx1990 commented Jul 7, 2026

Copy link
Copy Markdown

What

Adds an optional SOCKS5 proxy for all relay connections, configured on MarmotAppConfig:

let config = MarmotAppConfig::default()
    .with_relay_connection(RelayConnectionMode::Socks5("127.0.0.1:9050".parse()?));

New public API:

  • RelayConnectionMode { Direct, Socks5(SocketAddr) } (in marmot_app::config, re-exported at the crate root), #[default] Direct.
  • MarmotAppConfig::relay_connection field + with_relay_connection(...) builder, mirroring the existing with_allow_loopback_relay_endpoints pattern.

Why

On a censored network, a direct relay WebSocket is torn down before it can publish, and today there's no way to route relay traffic through a proxy — MarmotAppConfig has no proxy knob and the relay client is built with NostrSdkClient::builder().build() (no opts). Pointing the client at a local Tor (or other SOCKS5) proxy is the standard way around this, and nostr-sdk already supports it via ClientOptions::connection(Connection::proxy(addr)).

How

The mode maps to nostr-sdk client options through a single shared helper, relay_plane::relay_client_options, applied at both places MDK builds a relay-facing client:

  1. relay_plane::from_sdk — the shared relay transport + user-directory fetcher.
  2. MarmotApp::relay_client_for_endpoints — the per-account publish client used by account setup / relay-list / KeyPackage publish.

That second one is easy to miss: with only the plane proxied, account setup still dials directly and fails on a censored network. Routing both through one helper keeps them from drifting.

Notes

  • Default is Direct — zero behavior change unless a host app opts in.
  • No new cargo featureConnection::proxy is SOCKS5 and tokio-socks is already in the graph via nostr-sdk. (Embedded arti-Tor, Connection::embedded_tor, is intentionally left for a follow-up behind a tor feature.)
  • Dial safety: the proxy socket is dialed by the nostr-sdk client and sits outside the relay-host safety chokepoint (relay_plane/safety.rs), which still validates the relay URL. The proxy is an explicit, user-configured egress (typically a loopback Tor/SOCKS port) analogous to a system VPN, so it is intentionally not run through reject_non_public_ip. Happy to gate it differently if you'd prefer.

Testing

  • cargo check -p marmot-app passes; cargo fmt clean; cargo clippy -p marmot-app clean for the added code.
  • Verified end-to-end in a downstream app (a CLI chat tool built on MDK): a full 1:1 MLS round-trip over a public relay (wss://nos.lol), both directions decrypting, with the client pointed at a local SOCKS5 proxy on a network that tears down direct WebSockets — where the same round-trip fails without the proxy.

Happy to adjust the API shape, naming, or split the commits differently.


Open in Stage

Summary by CodeRabbit

  • New Features
    • Added a new relay connection setting with support for direct connections or routing through a SOCKS5 proxy.
    • Relay and directory-related network traffic now follow the configured connection mode.
  • Changes
    • The app now exposes the relay connection setting for configuration and runtime setup.
    • Loopback-enabled relay plane modes now respect the selected connection type.

db and others added 3 commits July 8, 2026 00:24
…proxy

MarmotAppConfig had no way to make the relay plane's nostr-sdk Client dial
through a proxy: from_sdk built the Client with builder().build() and no opts,
so on a censored network the relay WebSocket is torn down with no way to route
around it. Add MarmotAppConfig::relay_connection (RelayConnectionMode: Direct |
Socks5(SocketAddr), default Direct) + with_relay_connection(...), and thread it
through the relay-plane constructors so from_sdk builds the Client with
ClientOptions::connection(Connection::proxy(addr)) when set. The same Client
backs the user-directory fetcher, so a proxy routes all relay traffic.

The proxy socket is dialed by the nostr-sdk Client and is deliberately outside
the relay-host safety chokepoint (relay_plane/safety.rs), which still validates
the relay URL; the proxy is an explicit, user-configured egress akin to a VPN.

cargo check -p marmot-app passes. SOCKS5 needs no cargo feature (tokio-socks is
already in the graph via nostr-sdk); embedded arti-Tor is left for a follow-up
behind the `tor` feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first commit proxied the shared relay plane but missed
MarmotApp::relay_client_for_endpoints -- the per-account publish client that
account setup / relay-list / KeyPackage publish uses. Left direct, `moyu init`
still dialed relays directly and failed on a censored network even with the
plane proxied. Route it through the same relay_plane::relay_client_options
helper (extracted here) so every relay-facing client honors
MarmotAppConfig::relay_connection.

cargo check -p marmot-app passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stage-review

stage-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 4 individual chapters for you:

Title
1 Define RelayConnectionMode and update app configuration
2 Implement proxy-aware Nostr client construction
3 Wire proxy configuration through the relay plane
4 Apply proxy settings to app and publish clients
Open in Stage

Chapters generated by Stage for commit 4c611ef on Jul 7, 2026 5:01pm UTC.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a02c308e-c728-458a-81b1-ffe85c7f001b

📥 Commits

Reviewing files that changed from the base of the PR and between 400d223 and 4c611ef.

📒 Files selected for processing (3)
  • crates/marmot-app/src/config.rs
  • crates/marmot-app/src/lib.rs
  • crates/marmot-app/src/relay_plane/mod.rs

Walkthrough

Adds a RelayConnectionMode enum (Direct or Socks5) to MarmotAppConfig, with a builder method and default. Relay plane constructors and SDK client-building helpers now accept and apply this connection mode, threading it through app-level relay setup and per-account relay client creation.

Changes

Relay Connection Mode Configuration

Layer / File(s) Summary
RelayConnectionMode config contract
crates/marmot-app/src/config.rs
Adds RelayConnectionMode enum (Direct default, Socks5(SocketAddr)), a relay_connection field on MarmotAppConfig, its default initialization, and a with_relay_connection builder method.
Relay plane client construction
crates/marmot-app/src/relay_plane/mod.rs
Adds relay_client_options/build_sdk_client helpers applying the connection mode to the SDK client builder, updates nostr_sdk/config imports, and updates loopback constructors and from_sdk to accept and use &RelayConnectionMode.
MarmotApp wiring
crates/marmot-app/src/lib.rs
Re-exports RelayConnectionMode, passes config.relay_connection into relay plane constructors in with_relays_and_config, with_relays_and_account_home_and_config, and client, and applies relay_client_options when building per-account relay clients in relay_client_for_endpoints.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MarmotApp
  participant MarmotRelayPlane
  participant build_sdk_client
  participant NostrSdkClient

  MarmotApp->>MarmotRelayPlane: runtime_default_with_loopback(lookback, allow_loopback, &config.relay_connection)
  MarmotRelayPlane->>build_sdk_client: build_sdk_client(connection)
  build_sdk_client->>NostrSdkClient: apply relay_client_options(connection)
  NostrSdkClient-->>MarmotRelayPlane: configured client
  MarmotApp->>NostrSdkClient: relay_client_for_endpoints builds client with relay_client_options(&self.config.relay_connection)
Loading

Related issues: None specified.

Related PRs: None specified.

Suggested labels: enhancement, relay-plane, configuration

Suggested reviewers: None specified.

🐰 A whisper through a SOCKS5 hole,
Relays now dial by mode's control,
Direct or proxied, config decides,
Through app and plane the setting glides,
A carrot-shaped tunnel, safe and whole.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main change: optional SOCKS5 proxy support for relay connections in marmot-app.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch moyu/relay-proxy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant