Skip to content

Commit a49b115

Browse files
committed
Fix bugs
- force route construction - use same entry = exit
1 parent 4694c6f commit a49b115

File tree

7 files changed

+149
-35
lines changed

7 files changed

+149
-35
lines changed

nym-gateway-probe/bla.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# curl https://docs-demo.quiknode.pro/ \
5+
# -X POST \
6+
# -H "Content-Type: application/json" \
7+
# --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}'
8+
9+
10+
cargo run -- \
11+
--netstack-download-timeout-sec 60 \
12+
--netstack-num-ping 10 \
13+
--netstack-send-timeout-sec 3 \
14+
--netstack-recv-timeout-sec 3 \
15+
--min-gateway-mixnet-performance 0 \
16+
run-local --mnemonic "${MAINNET_MNEMONIC}" --only-wireguard

nym-gateway-probe/src/lib.rs

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ impl CredentialArgs {
128128
}
129129
}
130130

131+
#[derive(Args)]
132+
pub struct Socks5Args {
133+
#[arg(long, default_value_t = 45)]
134+
mixnet_client_timeout_sec: u64,
135+
136+
#[arg(long, default_value_t = 10)]
137+
test_count: u64,
138+
}
139+
131140
#[derive(Default, Debug)]
132141
pub enum TestedNode {
133142
#[default]
@@ -167,6 +176,7 @@ pub struct Probe {
167176
amnezia_args: String,
168177
netstack_args: NetstackArgs,
169178
credentials_args: CredentialArgs,
179+
socks5_args: Socks5Args,
170180
}
171181

172182
impl Probe {
@@ -175,13 +185,15 @@ impl Probe {
175185
tested_node: TestedNode,
176186
netstack_args: NetstackArgs,
177187
credentials_args: CredentialArgs,
188+
socks5_args: Socks5Args,
178189
) -> Self {
179190
Self {
180191
entrypoint,
181192
tested_node,
182193
amnezia_args: "".into(),
183194
netstack_args,
184195
credentials_args,
196+
socks5_args,
185197
}
186198
}
187199
pub fn with_amnezia(&mut self, args: &str) -> &Self {
@@ -197,7 +209,7 @@ impl Probe {
197209
) -> anyhow::Result<ProbeResult> {
198210
let exit_gateway = match gateway_key {
199211
Some(gateway_key) => NodeIdentity::from_base58_string(gateway_key)?,
200-
None => directory.random_exit_with_ipr()?,
212+
None => directory.random_exit_with_nr()?,
201213
};
202214
info!("Testing SOCKS5 only on exit gateway {}", exit_gateway);
203215
let node_info = directory
@@ -206,7 +218,15 @@ impl Probe {
206218

207219
let socks5_outcome = {
208220
if let Some(ref nr_details) = node_info.network_requester_details {
209-
match do_socks5_connectivity_test(&nr_details.address, network_details).await {
221+
match do_socks5_connectivity_test(
222+
&nr_details.address,
223+
network_details,
224+
directory,
225+
self.socks5_args.mixnet_client_timeout_sec,
226+
self.socks5_args.test_count,
227+
)
228+
.await
229+
{
210230
Ok(results) => Some(results),
211231
Err(e) => {
212232
error!("SOCKS5 test failed: {}", e);
@@ -274,6 +294,7 @@ impl Probe {
274294
nyxd_url,
275295
tested_entry,
276296
only_wireguard,
297+
directory,
277298
)
278299
.await
279300
}
@@ -354,6 +375,7 @@ impl Probe {
354375
nyxd_url,
355376
tested_entry,
356377
only_wireguard,
378+
directory,
357379
)
358380
.await
359381
}
@@ -410,6 +432,7 @@ impl Probe {
410432
nyxd_url: Url,
411433
tested_entry: bool,
412434
only_wireguard: bool,
435+
directory: NymApiDirectory,
413436
) -> anyhow::Result<ProbeResult>
414437
where
415438
T: MixnetClientStorage + Clone + 'static,
@@ -532,6 +555,9 @@ impl Probe {
532555
match do_socks5_connectivity_test(
533556
&nr_details.address,
534557
NymNetworkDetails::new_from_env(),
558+
directory,
559+
self.socks5_args.mixnet_client_timeout_sec,
560+
self.socks5_args.test_count,
535561
)
536562
.await
537563
{
@@ -849,14 +875,15 @@ async fn do_ping_exit(
849875
listen_for_icmp_ping_replies(mixnet_client, our_ips).await
850876
}
851877

852-
const TEST_REPEAT_COUNT: usize = 10;
853-
854878
/// Creates a SOCKS5 proxy connection through the mixnet to the exit GW
855879
/// and performs necessary tests.
856880
#[instrument(level = "info", name = "socks5_test", skip_all)]
857881
async fn do_socks5_connectivity_test(
858882
network_requester_address: &str,
859883
network_details: NymNetworkDetails,
884+
directory: NymApiDirectory,
885+
mixnet_client_timeout: u64,
886+
test_run_count: u64,
860887
) -> anyhow::Result<Socks5ProbeResults> {
861888
info!(
862889
"Starting SOCKS5 test through Network Requester: {}",
@@ -866,7 +893,7 @@ async fn do_socks5_connectivity_test(
866893
let mut results = Socks5ProbeResults::default();
867894

868895
// parse the network requester address
869-
let _nr_recipient = match network_requester_address.parse::<Recipient>() {
896+
let nr_recipient = match network_requester_address.parse::<Recipient>() {
870897
Ok(addr) => addr,
871898
Err(e) => {
872899
error!("Invalid Network Requester address: {}", e);
@@ -876,19 +903,58 @@ async fn do_socks5_connectivity_test(
876903
}
877904
};
878905

906+
info!(
907+
"Network Requester gateway: {}",
908+
nr_recipient.gateway().to_base58_string()
909+
);
910+
info!(
911+
"Network Requester identity: {}",
912+
nr_recipient.identity().to_base58_string()
913+
);
914+
879915
// create ephemeral SOCKS5 client
880916
let socks5_config = Socks5::new(network_requester_address.to_string());
881917

882-
// mainnet
918+
// Create debug config similar to main probe
919+
let mut debug_config = nym_client_core::config::DebugConfig::default();
920+
debug_config
921+
.traffic
922+
.disable_main_poisson_packet_distribution = true;
923+
debug_config.cover_traffic.disable_loop_cover_traffic_stream = true;
924+
debug_config.topology.ignore_egress_epoch_role = true;
925+
// since we define both entry & exit gateways to be the same tested GW,
926+
// this shouldn't negatively affect mixnet layers but it will force route
927+
// construction in case GW would get filtered out on topology refresh
928+
debug_config.topology.minimum_gateway_performance = 0;
929+
930+
// Verify the NR gateway exists in the directory with exit_nr role
931+
let nr_gateway_id = nr_recipient.gateway();
932+
if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) {
933+
results.https_connectivity = HttpsConnectivityResult::with_error(e.to_string());
934+
return Ok(results);
935+
} else {
936+
info!("✔️ Network Requester gateway found in directory with exit_nr role");
937+
}
938+
939+
// use intended exit as entry as well
940+
let entry_gateway = nr_gateway_id;
941+
883942
let socks5_client_builder = MixnetClientBuilder::new_ephemeral()
943+
// Specify entry gateway explicitly
944+
.request_gateway(entry_gateway.to_base58_string())
884945
.socks5_config(socks5_config)
885946
.network_details(network_details)
947+
.debug_config(debug_config)
886948
.build()?;
887949

888950
// connect to mixnet via SOCKS5
889951
let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await {
890952
Ok(client) => {
891953
info!("Successfully established SOCKS5 proxy connection");
954+
info!(
955+
"Connected via entry gateway: {}",
956+
client.nym_address().gateway().to_base58_string()
957+
);
892958
results.can_connect_socks5 = true;
893959
client
894960
}
@@ -900,7 +966,10 @@ async fn do_socks5_connectivity_test(
900966
}
901967
};
902968

903-
let test = HttpsConnectivityTest::new(TEST_REPEAT_COUNT);
969+
info!("Waiting for network topology to be ready...");
970+
tokio::time::sleep(Duration::from_secs(10)).await;
971+
972+
let test = HttpsConnectivityTest::new(test_run_count, mixnet_client_timeout);
904973
results.https_connectivity = test.run_tests(socks5_client.socks5_url()).await;
905974

906975
// cleanup

nym-gateway-probe/src/nodes.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ impl NymApiDirectory {
202202
.map(|(id, _)| *id)
203203
}
204204

205+
pub fn random_exit_with_nr(&self) -> anyhow::Result<NodeIdentity> {
206+
info!("Selecting random gateway with NR enabled");
207+
self.nodes
208+
.iter()
209+
.filter(|(_, n)| n.described.description.ip_packet_router.is_some())
210+
.choose(&mut rand::thread_rng())
211+
.ok_or(anyhow!("no gateways running NR available"))
212+
.map(|(id, _)| *id)
213+
}
214+
205215
pub fn random_entry_gateway(&self) -> anyhow::Result<NodeIdentity> {
206216
info!("Selecting random entry gateway");
207217
self.nodes
@@ -231,7 +241,7 @@ impl NymApiDirectory {
231241

232242
pub fn exit_gateway_nr(&self, identity: &NodeIdentity) -> anyhow::Result<DirectoryNode> {
233243
let Some(maybe_entry) = self.nodes.get(identity).cloned() else {
234-
bail!("{identity} does not exist")
244+
bail!("{identity} not found in directory")
235245
};
236246
if !maybe_entry.described.description.declared_role.exit_nr {
237247
bail!("{identity} doesn't support exit NR mode")

nym-gateway-probe/src/run.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clap::{Parser, Subcommand};
55
use nym_bin_common::bin_info;
66
use nym_config::defaults::setup_env;
77
use nym_gateway_probe::nodes::NymApiDirectory;
8-
use nym_gateway_probe::{CredentialArgs, NetstackArgs, ProbeResult, TestedNode};
8+
use nym_gateway_probe::{CredentialArgs, NetstackArgs, ProbeResult, Socks5Args, TestedNode};
99
use nym_sdk::NymNetworkDetails;
1010
use nym_sdk::mixnet::NodeIdentity;
1111
use std::path::Path;
@@ -69,6 +69,10 @@ struct CliArgs {
6969
/// Arguments to manage credentials
7070
#[command(flatten)]
7171
credential_args: CredentialArgs,
72+
73+
/// Arguments to configure socks5 probe
74+
#[command(flatten)]
75+
socks5_args: Socks5Args,
7276
}
7377

7478
const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/";
@@ -157,8 +161,13 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
157161
(None, _) => TestedNode::SameAsEntry,
158162
};
159163

160-
let mut trial =
161-
nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args);
164+
let mut trial = nym_gateway_probe::Probe::new(
165+
entry,
166+
test_point,
167+
args.netstack_args,
168+
args.credential_args,
169+
args.socks5_args,
170+
);
162171
if let Some(awg_args) = args.amnezia_args {
163172
trial.with_amnezia(&awg_args);
164173
}

nym-gateway-probe/src/types.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,20 @@ impl HttpsConnectivityResult {
162162
}
163163

164164
pub struct HttpsConnectivityTest {
165-
test_count: usize,
165+
test_count: u64,
166+
mixnet_client_timeout: Duration,
166167
}
167168

168-
/// currently we test against this endpoint
169+
/// endpoint to test against
169170
/// https://www.quicknode.com/docs/ethereum/web3_clientVersion
170171
const TARGET_URL: &str = "https://docs-demo.quiknode.pro";
171172
const POST_BODY: &str = r#"{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}"#;
172-
const MIXNET_TIMEOUT: Duration = Duration::from_secs(60);
173173

174174
impl HttpsConnectivityTest {
175-
pub fn new(test_count: usize) -> Self {
175+
pub fn new(test_count: u64, mixnet_client_timeout: u64) -> Self {
176176
Self {
177177
test_count: std::cmp::max(test_count, 1),
178+
mixnet_client_timeout: Duration::from_secs(mixnet_client_timeout),
178179
}
179180
}
180181

@@ -191,8 +192,7 @@ impl HttpsConnectivityTest {
191192

192193
let client = match reqwest::Client::builder()
193194
.proxy(proxy)
194-
// longer timeout for mixnet
195-
.timeout(MIXNET_TIMEOUT)
195+
.timeout(self.mixnet_client_timeout)
196196
.build()
197197
{
198198
Ok(c) => c,
@@ -202,9 +202,9 @@ impl HttpsConnectivityTest {
202202
}
203203
};
204204

205-
let mut successful_runs = 0;
206-
for i in 0..self.test_count {
207-
info!("Running test {}/{}", i + 1, self.test_count);
205+
let mut successful_runs = 0u64;
206+
for i in 1..self.test_count + 1 {
207+
info!("Running test {}/{}", i, self.test_count);
208208
let interim_res = self.perform_https_request(&client).await;
209209
if interim_res.https_success
210210
&& let Some(latency_ms) = interim_res.https_latency_ms
@@ -217,11 +217,17 @@ impl HttpsConnectivityTest {
217217
);
218218
result.https_success = true;
219219
result.https_status_code = interim_res.https_status_code;
220-
info!("{}/{} latency: {}ms", i + 1, self.test_count, latency_ms);
220+
info!("{}/{} latency: {}ms", i, self.test_count, latency_ms);
221221
} else if let Some(new_error) = interim_res.error {
222-
result.error = result
223-
.error
224-
.map(|existing| format!("{},{}", existing, new_error));
222+
result.error = Some(result.error.map_or(new_error.clone(), |existing| {
223+
format!("{},{}", existing, new_error)
224+
}))
225+
}
226+
227+
// too many failed runs: return early
228+
if successful_runs < 2 && i - successful_runs > 2 {
229+
// if < 2 runs, we don't have to calculate average before returning
230+
return result;
225231
}
226232
}
227233
result.https_latency_ms = result
@@ -241,7 +247,7 @@ impl HttpsConnectivityTest {
241247
let mut result = HttpsConnectivityResult::default();
242248
let start = Instant::now();
243249
match tokio::time::timeout(
244-
MIXNET_TIMEOUT,
250+
self.mixnet_client_timeout,
245251
client
246252
.post(TARGET_URL)
247253
.header(reqwest::header::CONTENT_TYPE, "application/json")
@@ -271,7 +277,7 @@ impl HttpsConnectivityTest {
271277
Err(_) => {
272278
warn!(
273279
"HTTPS request timed out after {}s",
274-
MIXNET_TIMEOUT.as_secs()
280+
self.mixnet_client_timeout.as_secs()
275281
);
276282
if result.error.is_none() {
277283
result.error = Some("HTTPS request timed out".to_string());

0 commit comments

Comments
 (0)