nym_sdk/mixnet/socks5_client.rs
1use std::net::SocketAddr;
2use std::time::Duration;
3
4use tokio::sync::RwLockReadGuard;
5
6use nym_client_core::client::base_client::ClientState;
7use nym_socks5_client_core::config::Socks5;
8use nym_sphinx::addressing::clients::Recipient;
9use nym_task::connections::LaneQueueLengths;
10use nym_task::ShutdownTracker;
11use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError};
12
13use crate::mixnet::client::MixnetClientBuilder;
14use crate::mixnet::NetworkRequesterSelector;
15use crate::Result;
16
17/// A SOCKS5 proxy client connected to the Nym mixnet.
18///
19/// `Socks5MixnetClient` provides a SOCKS5 proxy interface to the Nym mixnet,
20/// allowing HTTP(S) clients and other SOCKS5-compatible applications to route
21/// their traffic through the mixnet without having to modify their networking
22/// code.
23///
24/// Traffic leaves the mixnet through a network requester: a service running on
25/// an exit gateway that makes requests on the client's behalf and enforces the
26/// Nym exit policy. You can let the client discover one for you or name a specific
27/// one; see [`connect_with`](Self::connect_with) and [`NetworkRequesterSelector`].
28///
29/// ## Usage
30///
31/// 1. Connect, either by discovering a requester with
32/// [`connect_with`](Self::connect_with) or naming a known one with
33/// [`connect_new`](Self::connect_new)
34/// 2. Get the SOCKS5 URL via [`socks5_url`](Self::socks5_url)
35/// 3. Point your HTTP client at that SOCKS5 proxy
36///
37/// ## Example
38///
39/// ```rust,no_run
40/// use nym_sdk::mixnet::Socks5MixnetClient;
41///
42/// #[tokio::main]
43/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
44/// // Connect to a known network requester by address
45/// let client = Socks5MixnetClient::connect_new("provider_nym_address...").await?;
46///
47/// // Get the SOCKS5 proxy URL
48/// let socks5_url = client.socks5_url();
49/// println!("Configure your HTTP client to use: {}", socks5_url);
50///
51/// // Your HTTP client can now use the SOCKS5 proxy
52/// // let http_client = reqwest::Client::builder()
53/// // .proxy(reqwest::Proxy::all(&socks5_url)?)
54/// // .build()?;
55///
56/// client.disconnect().await;
57/// Ok(())
58/// }
59// ```
60pub struct Socks5MixnetClient {
61 /// The nym address of this connected client.
62 pub(crate) nym_address: Recipient,
63
64 /// The current state of the client that is exposed to the user. This includes things like
65 /// current message send queue length.
66 pub(crate) client_state: ClientState,
67
68 /// The task manager controlling all the spawned tasks the client uses to do its job.
69 pub(crate) task_handle: ShutdownTracker,
70
71 /// SOCKS5 configuration parameters.
72 pub(crate) socks5_config: Socks5,
73}
74
75impl Socks5MixnetClient {
76 /// Create a new client and connect to a network requester over the mixnet via SOCKS5 using
77 /// ephemeral in-memory keys that are discarded at application close.
78 ///
79 /// This is the zero-ceremony path when you already know the requester's
80 /// address; it is shorthand for [`connect_with`](Self::connect_with) with
81 /// [`NetworkRequesterSelector::exact`] and the default listener bind.
82 ///
83 /// Kept for backwards compatibility: it predates [`connect_with`] and overlaps
84 /// with the `exact` case, but existing callers pass an address string directly.
85 ///
86 /// # Examples
87 ///
88 /// ```no_run
89 /// use nym_sdk::mixnet;
90 ///
91 /// #[tokio::main]
92 /// async fn main() {
93 /// let receiving_client = mixnet::MixnetClient::connect_new().await.unwrap();
94 /// let mut client = mixnet::Socks5MixnetClient::connect_new(receiving_client.nym_address().to_string()).await;
95 /// }
96 ///
97 /// ```
98 pub async fn connect_new<S: Into<String>>(provider_mix_address: S) -> Result<Self> {
99 MixnetClientBuilder::new_ephemeral()
100 .socks5_config(Socks5::new(provider_mix_address))
101 .build()?
102 .connect_to_mixnet_via_socks5()
103 .await
104 }
105
106 /// Create a new client and connect to a network requester chosen per the
107 /// given [`NetworkRequesterSelector`]: auto-discovered ([`Any`](NetworkRequesterSelector::Any)),
108 /// country-restricted ([`InCountries`](NetworkRequesterSelector::InCountries)), or a
109 /// known address ([`Exact`](NetworkRequesterSelector::Exact)).
110 ///
111 /// The discovered requester enforces the Nym exit policy, so destinations
112 /// outside that policy are refused at the exit regardless of which
113 /// requester is selected.
114 ///
115 /// `bind` sets the local SOCKS5 listener address; pass `None` for the default
116 /// `127.0.0.1:1080`, or `Some(addr)` to move it (for example when 1080 is
117 /// already taken, or to run more than one client at once).
118 ///
119 /// # Examples
120 ///
121 /// ```no_run
122 /// use nym_sdk::mixnet::{NetworkRequesterSelector, Socks5MixnetClient};
123 ///
124 /// #[tokio::main]
125 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
126 /// // Any requester, weighted by performance, on the default port:
127 /// let any = Socks5MixnetClient::connect_with(NetworkRequesterSelector::any(), None).await?;
128 ///
129 /// // Pinned to Switzerland or Germany, listening on 127.0.0.1:1081:
130 /// let pinned = Socks5MixnetClient::connect_with(
131 /// NetworkRequesterSelector::in_countries(["CH", "DE"])?,
132 /// Some("127.0.0.1:1081".parse()?),
133 /// )
134 /// .await?;
135 /// Ok(())
136 /// }
137 /// ```
138 pub async fn connect_with(
139 requester: NetworkRequesterSelector,
140 bind: Option<SocketAddr>,
141 ) -> Result<Self> {
142 let provider = requester.resolve().await?;
143 let mut socks5_config = Socks5::new(provider.to_string());
144 if let Some(addr) = bind {
145 socks5_config.bind_address = addr;
146 }
147 MixnetClientBuilder::new_ephemeral()
148 .socks5_config(socks5_config)
149 .build()?
150 .connect_to_mixnet_via_socks5()
151 .await
152 }
153
154 /// Get the nym address of this client. The nym address is composed of the
155 /// client identity, the client encryption key, and the gateway identity.
156 pub fn nym_address(&self) -> &Recipient {
157 &self.nym_address
158 }
159
160 /// Get the SOCKS5 proxy URL that a HTTP(S) client can connect to.
161 pub fn socks5_url(&self) -> String {
162 format!("socks5h://{}", self.socks5_config.bind_address)
163 }
164
165 /// Get a shallow clone of [`LaneQueueLengths`]. This is useful to manually implement some form
166 /// of backpressure logic.
167 pub fn shared_lane_queue_lengths(&self) -> LaneQueueLengths {
168 self.client_state.shared_lane_queue_lengths.clone()
169 }
170
171 /// Change the network topology used by this client for constructing sphinx packets into the
172 /// provided one.
173 pub async fn manually_overwrite_topology(&self, new_topology: NymTopology) {
174 self.client_state
175 .topology_accessor
176 .manually_change_topology(new_topology)
177 .await
178 }
179
180 /// Restore default topology refreshing behaviour of this client.
181 pub fn restore_automatic_topology_refreshing(&self) {
182 self.client_state.topology_accessor.release_manual_control()
183 }
184
185 /// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected
186 /// client.
187 pub async fn disconnect(self) {
188 self.task_handle.shutdown().await;
189 }
190
191 /// Gets the current route provider if topology is available.
192 /// Returns `None` if topology is empty/not yet fetched.
193 async fn read_current_route_provider(&self) -> Option<RwLockReadGuard<'_, NymRouteProvider>> {
194 self.client_state
195 .topology_accessor
196 .current_route_provider()
197 .await
198 }
199
200 /// Wait for topology to become available, with a timeout.
201 /// Returns `Ok(())` when topology is ready, or `Err` if timeout is reached.
202 pub async fn wait_for_topology(&self, timeout: Duration) -> Result<(), NymTopologyError> {
203 let deadline = tokio::time::Instant::now() + timeout;
204 loop {
205 if self.read_current_route_provider().await.is_some() {
206 return Ok(());
207 }
208 if tokio::time::Instant::now() >= deadline {
209 return Err(NymTopologyError::EmptyNetworkTopology);
210 }
211 tokio::time::sleep(Duration::from_millis(100)).await;
212 }
213 }
214}