Skip to content

QoL fix: TCP bind/fail before creation of client in socks5 - #6929

Draft
mfahampshire wants to merge 14 commits into
developfrom
max/fix-socks5-listener-bind-error
Draft

QoL fix: TCP bind/fail before creation of client in socks5 #6929
mfahampshire wants to merge 14 commits into
developfrom
max/fix-socks5-listener-bind-error

Conversation

@mfahampshire

@mfahampshire mfahampshire commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Only review once #6920 is merged and this has been rebased against develop

This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Added flexible SOCKS5 connection setup, including choosing a network requester automatically, by country, or by exact address.
    • Improved the SOCKS5 example to show a complete end-to-end HTTPS request through the proxy.
  • Bug Fixes

    • SOCKS5 listeners now reserve their port earlier, helping surface port collisions sooner and avoid partial startup issues.
    • Added clearer errors for invalid country codes, missing countries, and invalid recipient addresses.

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nym-explorer-v2 Ready Ready Preview, Comment Jun 30, 2026 9:09pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs-nextra Ignored Ignored Jun 30, 2026 9:09pm
nym-node-status Ignored Ignored Jun 30, 2026 9:09pm

Request Review

@mfahampshire
mfahampshire marked this pull request as draft June 30, 2026 21:09
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The SOCKS5 client core now binds its TcpListener before starting the mixnet client and passes the listener explicitly into the server, removing internal binding logic. The nym-sdk introduces a NetworkRequester abstraction with country-based directory discovery, a new connect_with API, supporting error variants, and an updated example.

Changes

SOCKS5 listener binding and network requester selection

Layer / File(s) Summary
Core SOCKS5 server accepts pre-bound listener
common/socks5-client-core/src/lib.rs, common/socks5-client-core/src/socks/server.rs, clients/socks5/src/main.rs
NymSocksServer::new/serve and start_socks5_listener/start() no longer bind their own socket; a TcpListener is bound early and passed in explicitly.
nym-sdk mixnet client binds listener before connect
sdk/rust/nym-sdk/src/mixnet/client.rs
connect_to_mixnet_via_socks5 binds a TcpListener on bind_address before starting the mixnet client and forwards it to start_socks5_listener.
NetworkRequester enum, errors, and discovery resolution
sdk/rust/nym-sdk/src/error.rs, sdk/rust/nym-sdk/src/mixnet/socks5_client.rs
Adds Error variants for invalid countries/addresses, a NetworkRequester enum (Any, InCountries, Exact) with resolve(), and a discovery module that queries the directory and selects gateways weighted by performance with country filtering.
Socks5MixnetClient::connect_with and re-exports
sdk/rust/nym-sdk/src/mixnet/socks5_client.rs, sdk/rust/nym-sdk/src/mixnet.rs
Adds connect_with(requester, bind) to resolve a requester, build Socks5 config, optionally override bind address, and connect; updates struct fields/docs; re-exports NetworkRequester from mixnet.rs.
Updated socks5 example
sdk/rust/nym-sdk/examples/socks5.rs, sdk/rust/nym-sdk/Cargo.toml
Rewrites the example to use NetworkRequester::any() and connect_with, performs a real HTTPS GET through the proxy, and adds the celes dependency.

Sequence Diagram(s)

sequenceDiagram
  participant Example
  participant Socks5MixnetClient
  participant NetworkRequester
  participant Directory
  participant ReqwestClient

  Example->>NetworkRequester: any()
  Example->>Socks5MixnetClient: connect_with(requester, bind)
  Socks5MixnetClient->>NetworkRequester: resolve()
  NetworkRequester->>Directory: query exit gateways
  Directory-->>NetworkRequester: weighted candidates
  NetworkRequester-->>Socks5MixnetClient: Recipient
  Socks5MixnetClient->>Socks5MixnetClient: bind TcpListener, start mixnet client
  Example->>ReqwestClient: GET https://nymtech.net via SOCKS5 proxy
  ReqwestClient-->>Example: HTTP status
  Example->>Socks5MixnetClient: disconnect()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • simonwicky
  • neacsu

A rabbit binds its burrow tight,
before it hops into the night 🐰
No more late collisions feared—
the listener's bound, the path is cleared.
Now requesters chosen near and far,
by country code or weighted star.
Hop on, proxy, through the mix! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: binding the SOCKS5 TCP listener before client creation to fail early.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch max/fix-socks5-listener-bind-error

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
sdk/rust/nym-sdk/examples/socks5.rs (1)

17-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Quote the address literals in the doc-comment examples.

exact(...)/connect_new(...) take string arguments (impl AsRef<str>), but the doc comment shows the Nym address as a bare unquoted token, which reads as invalid pseudo-code.

✏️ Suggested fix
-    //   NetworkRequester::exact(Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf)? -> a specific known requester
-    // If you already have the address, Socks5MixnetClient::connect_new(Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf) is the one-line shorthand for the `exact` case.
+    //   NetworkRequester::exact("Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf")? -> a specific known requester
+    // If you already have the address, Socks5MixnetClient::connect_new("Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf") is the one-line shorthand for the `exact` case.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/nym-sdk/examples/socks5.rs` around lines 17 - 18, The doc-comment
examples in socks5.rs show Nym addresses as bare tokens, but the referenced APIs
expect string inputs. Update the examples in the comment near
NetworkRequester::exact and Socks5MixnetClient::connect_new so the address
literals are quoted like string arguments, keeping the examples aligned with the
actual AsRef<str> signatures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/rust/nym-sdk/src/mixnet/socks5_client.rs`:
- Around line 139-148: The `requester.resolve().await` call in
`socks5_client.rs` happens before the SOCKS5 bind is attempted, so discovery can
fail first and hide a deterministic bind error. Update the `connect` flow to
bind the selected or default address before calling `requester.resolve()` and
carry that bound listener/address through
`MixnetClientBuilder::new_ephemeral()`, `socks5_config(...)`, and
`connect_to_mixnet_via_socks5()`. Make sure the bind failure is returned
immediately when the port is occupied, while preserving the existing discovery
behavior afterward.
- Around line 275-280: Reject empty InCountries values inside
NetworkRequester::resolve so direct construction cannot bypass the guard and
fall back to Any. Update the match arm for Self::InCountries(countries) to
detect an empty list and return an error instead of calling discovery::discover,
while keeping the existing behavior for non-empty country lists and the
Exact/Any variants unchanged.

---

Nitpick comments:
In `@sdk/rust/nym-sdk/examples/socks5.rs`:
- Around line 17-18: The doc-comment examples in socks5.rs show Nym addresses as
bare tokens, but the referenced APIs expect string inputs. Update the examples
in the comment near NetworkRequester::exact and Socks5MixnetClient::connect_new
so the address literals are quoted like string arguments, keeping the examples
aligned with the actual AsRef<str> signatures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36014c8a-1552-4285-bdc7-24566c0e8b69

📥 Commits

Reviewing files that changed from the base of the PR and between 18ace32 and 99f6837.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • clients/socks5/src/main.rs
  • common/socks5-client-core/src/lib.rs
  • common/socks5-client-core/src/socks/server.rs
  • sdk/rust/nym-sdk/Cargo.toml
  • sdk/rust/nym-sdk/examples/socks5.rs
  • sdk/rust/nym-sdk/src/error.rs
  • sdk/rust/nym-sdk/src/mixnet.rs
  • sdk/rust/nym-sdk/src/mixnet/client.rs
  • sdk/rust/nym-sdk/src/mixnet/socks5_client.rs

Comment on lines +139 to +148
let provider = requester.resolve().await?;
let mut socks5_config = Socks5::new(provider.to_string());
if let Some(addr) = bind {
socks5_config.bind_address = addr;
}
MixnetClientBuilder::new_ephemeral()
.socks5_config(socks5_config)
.build()?
.connect_to_mixnet_via_socks5()
.await

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bind before requester discovery to keep bind failures deterministic.

requester.resolve().await can perform mainnet directory I/O before the SOCKS5 listener is bound later in connect_to_mixnet_via_socks5; if the port is occupied but discovery is slow or fails, callers get the wrong failure first. Bind the selected/default address before discovery and thread that listener into the SOCKS5 connect path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/nym-sdk/src/mixnet/socks5_client.rs` around lines 139 - 148, The
`requester.resolve().await` call in `socks5_client.rs` happens before the SOCKS5
bind is attempted, so discovery can fail first and hide a deterministic bind
error. Update the `connect` flow to bind the selected or default address before
calling `requester.resolve()` and carry that bound listener/address through
`MixnetClientBuilder::new_ephemeral()`, `socks5_config(...)`, and
`connect_to_mixnet_via_socks5()`. Make sure the bind failure is returned
immediately when the port is occupied, while preserving the existing discovery
behavior afterward.

Comment on lines +275 to +280
pub async fn resolve(&self) -> Result<Recipient, Error> {
match self {
Self::Exact(addr) => Ok(**addr),
Self::Any => discovery::discover(&[]).await,
Self::InCountries(countries) => discovery::discover(countries).await,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject empty InCountries during resolution too.

Because NetworkRequester is public, callers can construct NetworkRequester::InCountries(Vec::new()) directly and bypass in_countries(); resolve() then treats it as Any.

Proposed fix
         match self {
             Self::Exact(addr) => Ok(**addr),
             Self::Any => discovery::discover(&[]).await,
+            Self::InCountries(countries) if countries.is_empty() => {
+                Err(Error::NoCountriesSpecified)
+            }
             Self::InCountries(countries) => discovery::discover(countries).await,
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn resolve(&self) -> Result<Recipient, Error> {
match self {
Self::Exact(addr) => Ok(**addr),
Self::Any => discovery::discover(&[]).await,
Self::InCountries(countries) => discovery::discover(countries).await,
}
pub async fn resolve(&self) -> Result<Recipient, Error> {
match self {
Self::Exact(addr) => Ok(**addr),
Self::Any => discovery::discover(&[]).await,
Self::InCountries(countries) if countries.is_empty() => {
Err(Error::NoCountriesSpecified)
}
Self::InCountries(countries) => discovery::discover(countries).await,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/nym-sdk/src/mixnet/socks5_client.rs` around lines 275 - 280, Reject
empty InCountries values inside NetworkRequester::resolve so direct construction
cannot bypass the guard and fall back to Any. Update the match arm for
Self::InCountries(countries) to detect an empty list and return an error instead
of calling discovery::discover, while keeping the existing behavior for
non-empty country lists and the Exact/Any variants unchanged.

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.

2 participants