Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions clients/socks5/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}

setup_tracing_logger();

if let Err(err) = commands::execute(args).await {
Expand Down
10 changes: 9 additions & 1 deletion common/socks5-client-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use nym_task::{ShutdownManager, ShutdownTracker};
use nym_validator_client::UserAgent;
use std::error::Error;
use std::path::PathBuf;
use tokio::net::TcpListener;

pub mod config;
pub mod error;
Expand Down Expand Up @@ -95,6 +96,7 @@ where
#[allow(clippy::too_many_arguments)]
pub fn start_socks5_listener(
socks5_config: &config::Socks5,
listener: TcpListener,
base_debug: DebugConfig,
client_input: ClientInput,
client_output: ClientOutput,
Expand All @@ -104,6 +106,7 @@ where
packet_type: PacketType,
) {
info!("Starting socks5 listener...");
info!("Listening on {}", socks5_config.bind_address);
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
let allowed_users: Vec<User> = Vec::new();

Expand All @@ -129,7 +132,6 @@ where

let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = NymSocksServer::new(
socks5_config.bind_address,
authenticator,
socks5_config.get_provider_mix_address(),
self_address,
Expand All @@ -147,6 +149,7 @@ where
nym_task::spawn_future(async move {
sphinx_socks
.serve(
listener,
input_sender,
received_buffer_request_sender,
connection_command_sender,
Expand Down Expand Up @@ -208,6 +211,10 @@ where
}

pub async fn start(self) -> Result<StartedSocks5Client, Socks5ClientCoreError> {
// Bind the listener before starting the base client so a port collision
// fails here, before we spin up (and would have to tear down) the mixnet.
let listener = TcpListener::bind(self.config.socks5.bind_address).await?;

// don't create dkg client for the bandwidth controller if credentials are disabled
let dkg_query_client = if self.config.base.client.disabled_credentials_mode {
None
Expand Down Expand Up @@ -236,6 +243,7 @@ where

Self::start_socks5_listener(
&self.config.socks5,
listener,
self.config.base().debug,
client_input,
client_output,
Expand Down
11 changes: 1 addition & 10 deletions common/socks5-client-core/src/socks/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths};
use nym_task::ShutdownTracker;
use std::net::SocketAddr;
use tap::TapFallible;
use tokio::net::TcpListener;

/// A Socks5 server that listens for connections.
pub struct NymSocksServer {
authenticator: Authenticator,
listening_address: SocketAddr,
service_provider: Recipient,
self_address: Recipient,
client_config: client::Config,
Expand All @@ -31,9 +28,7 @@ pub struct NymSocksServer {

impl NymSocksServer {
/// Create a new SphinxSocks instance
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
bind_address: SocketAddr,
authenticator: Authenticator,
service_provider: Recipient,
self_address: Recipient,
Expand All @@ -42,10 +37,8 @@ impl NymSocksServer {
shutdown: ShutdownTracker,
packet_type: PacketType,
) -> Self {
info!("Listening on {bind_address}");
NymSocksServer {
authenticator,
listening_address: bind_address,
service_provider,
self_address,
client_config,
Expand All @@ -59,13 +52,11 @@ impl NymSocksServer {
/// connects to the server.
pub(crate) async fn serve(
&mut self,
listener: TcpListener,
input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender,
client_connection_tx: ConnectionCommandSender,
) -> Result<(), Socks5ClientCoreError> {
let listener = TcpListener::bind(self.listening_address)
.await
.tap_err(|err| log::error!("Failed to bind to address: {err}"))?;
info!("Serving Connections...");

// controller for managing all active connections
Expand Down
1 change: 1 addition & 0 deletions sdk/rust/nym-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ nym-bin-common = { workspace = true, features = [
bytecodec = { workspace = true }
httpcodec = { workspace = true }
bytes = { workspace = true }
celes = { workspace = true }
http = { workspace = true }
zeroize = { workspace = true }

Expand Down
77 changes: 32 additions & 45 deletions sdk/rust/nym-sdk/examples/socks5.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,41 @@
//! SOCKS5 proxy client that routes HTTP requests through the mixnet.
//! SOCKS5 proxy client that routes an HTTPS request through the mixnet.
//!
//! Connects a `Socks5MixnetClient` to a receiving `MixnetClient` acting
//! as the service provider, then sends an HTTP GET via the SOCKS5 proxy.
//! Picks a network requester (the mixnet exit), starts a local SOCKS5 listener,
//! points an HTTP client at it, and makes a real request end to end.
//!
//! Run with: cargo run --example socks5

use nym_sdk::mixnet;
use nym_sdk::mixnet::{NetworkRequester, Socks5MixnetClient};

#[tokio::main]
async fn main() {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
nym_bin_common::logging::setup_tracing_logger();

// Connect a receiving client (acts as the "network requester").
println!("Connecting receiver");
let mut receiving_client = mixnet::MixnetClient::connect_new().await.unwrap();

// Build and connect a SOCKS5 sending client pointed at the receiver.
let socks5_config = mixnet::Socks5::new(receiving_client.nym_address().to_string());
let sending_client = mixnet::MixnetClientBuilder::new_ephemeral()
.socks5_config(socks5_config)
.build()
.unwrap();

println!("Connecting sender");
let sending_client = sending_client.connect_to_mixnet_via_socks5().await.unwrap();

// Configure an HTTP client to use the SOCKS5 proxy.
let proxy = reqwest::Proxy::all(sending_client.socks5_url()).unwrap();
let reqwest_client = reqwest::Client::builder().proxy(proxy).build().unwrap();

// Send an HTTP request through the mixnet via SOCKS5.
// No network requester is running on the other end, so we won't
// get a real HTTP response — but the receiver sees the raw bytes.
tokio::spawn(async move {
println!("Sending socks5-wrapped http request");
reqwest_client.get("https://nymtech.net").send().await.ok()
});

// The receiver sees the raw SOCKS5/HTTP bytes arrive.
println!("Waiting for message");
if let Some(received) = receiving_client.wait_for_messages().await {
for r in received {
println!(
"Received socks5 message requesting for endpoint: {}",
String::from_utf8_lossy(&r.message[10..27])
);
}
}

// Disconnect both clients.
receiving_client.disconnect().await;
sending_client.disconnect().await;
// How to choose the requester. `any()` auto-discovers one from the directory,
// weighted by performance, and is the most robust default. The alternatives:
// NetworkRequester::in_countries(["CH", "DE"])? -> discovery pinned to those countries
// 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.
let requester = NetworkRequester::any();

// Passing `None` binds the SOCKS5 listener to the default 127.0.0.1:1080. Pass Some(addr)
// to move it, for example when that port is taken or to run more than one client.
println!("Selecting a network requester and connecting");
let client =
Socks5MixnetClient::connect_with(requester, Some("127.0.0.1:1081".parse()?)).await?;
println!("SOCKS5 proxy listening at {}", client.socks5_url());

// Point an HTTP client at the proxy and make a request through the mixnet.
let proxy = reqwest::Proxy::all(client.socks5_url())?;
let http = reqwest::Client::builder().proxy(proxy).build()?;

// nymtech.net is on the default Nym exit policy. If you change this URL to a
// destination the exit policy does not allow, the request fails at the exit,
// which is not a discovery failure.
println!("Sending request through the mixnet");
let status = http.get("https://nymtech.net").send().await?.status();
println!("Got response status: {status}");

client.disconnect().await;
Ok(())
}
12 changes: 12 additions & 0 deletions sdk/rust/nym-sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ pub enum Error {
#[error("no available gateway")]
NoGatewayAvailable,

#[error("invalid ISO 3166 alpha-2 country code: {0}")]
InvalidCountryCode(String),

#[error("no available gateway in the requested countries")]
NoGatewayInCountries,

#[error("no countries specified; use NetworkRequester::any() to accept any country")]
NoCountriesSpecified,

#[error("invalid network requester address: {0}")]
InvalidRecipientAddress(String),

#[error("tunnel disconnected by IPR")]
IprTunnelDisconnected,

Expand Down
2 changes: 1 addition & 1 deletion sdk/rust/nym-sdk/src/mixnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub use native_client::MixnetClient;
pub use native_client::MixnetClientSender;
pub use paths::StoragePaths;
pub use sink::{MixnetMessageSink, MixnetMessageSinkTranslator};
pub use socks5_client::Socks5MixnetClient;
pub use socks5_client::{NetworkRequester, Socks5MixnetClient};
pub use stream::{MixnetListener, MixnetStream, StreamId};
pub use traits::MixnetMessageSender;

Expand Down
6 changes: 6 additions & 0 deletions sdk/rust/nym-sdk/src/mixnet/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
use std::sync::Arc;
use tokio::net::TcpListener;
use url::Url;
use zeroize::Zeroizing;

Expand Down Expand Up @@ -905,6 +906,10 @@ where
.socks5_config
.clone()
.ok_or(Error::Socks5Config { set: false })?;
// Bind the local listener before starting the mixnet client: a port
// collision then fails here, loudly, without spinning up (and leaking) a
// mixnet client we would immediately discard.
let listener = TcpListener::bind(socks5_config.bind_address).await?;
let debug_config = self.config.debug_config;
let packet_type = self.config.debug_config.traffic.packet_type;
let (mut started_client, nym_address) = self.connect_to_mixnet_common().await?;
Expand All @@ -915,6 +920,7 @@ where

nym_socks5_client_core::NymClient::<S>::start_socks5_listener(
&socks5_config,
listener,
debug_config,
client_input,
client_output,
Expand Down
Loading
Loading