Fix bugs
- force route construction - use same entry = exit
This commit is contained in:
@@ -127,6 +127,15 @@ impl CredentialArgs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Socks5Args {
|
||||
#[arg(long, default_value_t = 45)]
|
||||
mixnet_client_timeout_sec: u64,
|
||||
|
||||
#[arg(long, default_value_t = 10)]
|
||||
test_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub enum TestedNode {
|
||||
#[default]
|
||||
@@ -202,6 +211,7 @@ pub struct Probe {
|
||||
localnet_entry: Option<TestedNodeDetails>,
|
||||
/// Localnet exit gateway info (used when --exit-gateway-identity is specified)
|
||||
localnet_exit: Option<TestedNodeDetails>,
|
||||
socks5_args: Socks5Args,
|
||||
}
|
||||
|
||||
impl Probe {
|
||||
@@ -210,6 +220,7 @@ impl Probe {
|
||||
tested_node: TestedNode,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -221,6 +232,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +243,7 @@ impl Probe {
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
gateway_node: DirectoryNode,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -242,6 +255,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +267,7 @@ impl Probe {
|
||||
credentials_args: CredentialArgs,
|
||||
entry_gateway_node: DirectoryNode,
|
||||
exit_gateway_node: DirectoryNode,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -264,6 +279,7 @@ impl Probe {
|
||||
exit_gateway_node: Some(exit_gateway_node),
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +290,7 @@ impl Probe {
|
||||
exit: Option<TestedNodeDetails>,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
let entrypoint = entry.identity;
|
||||
Self {
|
||||
@@ -286,6 +303,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: Some(entry),
|
||||
localnet_exit: exit,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +320,7 @@ impl Probe {
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
let exit_gateway = match gateway_key {
|
||||
Some(gateway_key) => NodeIdentity::from_base58_string(gateway_key)?,
|
||||
None => directory.random_exit_with_ipr()?,
|
||||
None => directory.random_exit_with_nr()?,
|
||||
};
|
||||
info!("Testing SOCKS5 only on exit gateway {}", exit_gateway);
|
||||
let node_info = directory
|
||||
@@ -311,7 +329,15 @@ impl Probe {
|
||||
|
||||
let socks5_outcome = {
|
||||
if let Some(ref nr_details) = node_info.network_requester_details {
|
||||
match do_socks5_connectivity_test(&nr_details.address, network_details).await {
|
||||
match do_socks5_connectivity_test(
|
||||
&nr_details.address,
|
||||
network_details,
|
||||
Some(&directory),
|
||||
self.socks5_args.mixnet_client_timeout_sec,
|
||||
self.socks5_args.test_count,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(results) => Some(results),
|
||||
Err(e) => {
|
||||
error!("SOCKS5 test failed: {}", e);
|
||||
@@ -1016,6 +1042,9 @@ impl Probe {
|
||||
match do_socks5_connectivity_test(
|
||||
&nr_details.address,
|
||||
NymNetworkDetails::new_from_env(),
|
||||
directory.as_deref(),
|
||||
self.socks5_args.mixnet_client_timeout_sec,
|
||||
self.socks5_args.test_count,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -1622,14 +1651,15 @@ async fn do_ping_exit(
|
||||
listen_for_icmp_ping_replies(mixnet_client, our_ips).await
|
||||
}
|
||||
|
||||
const TEST_REPEAT_COUNT: usize = 10;
|
||||
|
||||
/// Creates a SOCKS5 proxy connection through the mixnet to the exit GW
|
||||
/// and performs necessary tests.
|
||||
#[instrument(level = "info", name = "socks5_test", skip_all)]
|
||||
async fn do_socks5_connectivity_test(
|
||||
network_requester_address: &str,
|
||||
network_details: NymNetworkDetails,
|
||||
directory: Option<&NymApiDirectory>,
|
||||
mixnet_client_timeout: u64,
|
||||
test_run_count: u64,
|
||||
) -> anyhow::Result<Socks5ProbeResults> {
|
||||
info!(
|
||||
"Starting SOCKS5 test through Network Requester: {}",
|
||||
@@ -1639,7 +1669,7 @@ async fn do_socks5_connectivity_test(
|
||||
let mut results = Socks5ProbeResults::default();
|
||||
|
||||
// parse the network requester address
|
||||
let _nr_recipient = match network_requester_address.parse::<Recipient>() {
|
||||
let nr_recipient = match network_requester_address.parse::<Recipient>() {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
error!("Invalid Network Requester address: {}", e);
|
||||
@@ -1649,19 +1679,61 @@ async fn do_socks5_connectivity_test(
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
"Network Requester gateway: {}",
|
||||
nr_recipient.gateway().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Network Requester identity: {}",
|
||||
nr_recipient.identity().to_base58_string()
|
||||
);
|
||||
|
||||
// create ephemeral SOCKS5 client
|
||||
let socks5_config = Socks5::new(network_requester_address.to_string());
|
||||
|
||||
// mainnet
|
||||
// Create debug config similar to main probe
|
||||
let mut debug_config = nym_client_core::config::DebugConfig::default();
|
||||
debug_config
|
||||
.traffic
|
||||
.disable_main_poisson_packet_distribution = true;
|
||||
debug_config.cover_traffic.disable_loop_cover_traffic_stream = true;
|
||||
debug_config.topology.ignore_egress_epoch_role = true;
|
||||
// since we define both entry & exit gateways to be the same tested GW,
|
||||
// this shouldn't negatively affect mixnet layers but it will force route
|
||||
// construction in case GW would get filtered out on topology refresh
|
||||
debug_config.topology.minimum_gateway_performance = 0;
|
||||
|
||||
let Some(directory) = directory else {
|
||||
bail!("You need to provide Nym API directory through environment")
|
||||
};
|
||||
// Verify the NR gateway exists in the directory with exit_nr role
|
||||
let nr_gateway_id = nr_recipient.gateway();
|
||||
if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) {
|
||||
results.https_connectivity = HttpsConnectivityResult::with_error(e.to_string());
|
||||
return Ok(results);
|
||||
} else {
|
||||
info!("✔️ Network Requester gateway found in directory with exit_nr role");
|
||||
}
|
||||
|
||||
// use intended exit as entry as well
|
||||
let entry_gateway = nr_gateway_id;
|
||||
|
||||
let socks5_client_builder = MixnetClientBuilder::new_ephemeral()
|
||||
// Specify entry gateway explicitly
|
||||
.request_gateway(entry_gateway.to_base58_string())
|
||||
.socks5_config(socks5_config)
|
||||
.network_details(network_details)
|
||||
.debug_config(debug_config)
|
||||
.build()?;
|
||||
|
||||
// connect to mixnet via SOCKS5
|
||||
let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await {
|
||||
Ok(client) => {
|
||||
info!("Successfully established SOCKS5 proxy connection");
|
||||
info!(
|
||||
"Connected via entry gateway: {}",
|
||||
client.nym_address().gateway().to_base58_string()
|
||||
);
|
||||
results.can_connect_socks5 = true;
|
||||
client
|
||||
}
|
||||
@@ -1673,7 +1745,10 @@ async fn do_socks5_connectivity_test(
|
||||
}
|
||||
};
|
||||
|
||||
let test = HttpsConnectivityTest::new(TEST_REPEAT_COUNT);
|
||||
info!("Waiting for network topology to be ready...");
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
let test = HttpsConnectivityTest::new(test_run_count, mixnet_client_timeout);
|
||||
results.https_connectivity = test.run_tests(socks5_client.socks5_url()).await;
|
||||
|
||||
// cleanup
|
||||
|
||||
@@ -389,6 +389,16 @@ impl NymApiDirectory {
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
pub fn random_exit_with_nr(&self) -> anyhow::Result<NodeIdentity> {
|
||||
info!("Selecting random gateway with NR enabled");
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter(|(_, n)| n.described.description.ip_packet_router.is_some())
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(anyhow!("no gateways running NR available"))
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
pub fn random_entry_gateway(&self) -> anyhow::Result<NodeIdentity> {
|
||||
info!("Selecting random entry gateway");
|
||||
self.nodes
|
||||
@@ -418,7 +428,7 @@ impl NymApiDirectory {
|
||||
|
||||
pub fn exit_gateway_nr(&self, identity: &NodeIdentity) -> anyhow::Result<DirectoryNode> {
|
||||
let Some(maybe_entry) = self.nodes.get(identity).cloned() else {
|
||||
bail!("{identity} does not exist")
|
||||
bail!("{identity} not found in directory")
|
||||
};
|
||||
if !maybe_entry.described.description.declared_role.exit_nr {
|
||||
bail!("{identity} doesn't support exit NR mode")
|
||||
|
||||
@@ -5,10 +5,10 @@ use anyhow::bail;
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_config::defaults::setup_env;
|
||||
use nym_gateway_probe::nodes::{NymApiDirectory, query_gateway_by_ip};
|
||||
use nym_gateway_probe::{
|
||||
CredentialArgs, NetstackArgs, ProbeResult, TestMode, TestedNode, TestedNodeDetails,
|
||||
};
|
||||
use nym_gateway_probe::nodes::NymApiDirectory;
|
||||
use nym_gateway_probe::nodes::query_gateway_by_ip;
|
||||
use nym_gateway_probe::{CredentialArgs, NetstackArgs, ProbeResult, Socks5Args, TestedNode};
|
||||
use nym_gateway_probe::{TestMode, TestedNodeDetails};
|
||||
use nym_sdk::mixnet::NodeIdentity;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
@@ -131,6 +131,10 @@ struct CliArgs {
|
||||
/// Arguments to manage credentials
|
||||
#[command(flatten)]
|
||||
credential_args: CredentialArgs,
|
||||
|
||||
/// Arguments to configure socks5 probe
|
||||
#[command(flatten)]
|
||||
socks5_args: Socks5Args,
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/";
|
||||
@@ -295,6 +299,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
exit_details,
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
args.socks5_args,
|
||||
);
|
||||
|
||||
if let Some(awg_args) = args.amnezia_args {
|
||||
@@ -429,6 +434,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
args.credential_args,
|
||||
entry_node.clone(),
|
||||
exit_node.clone(),
|
||||
args.socks5_args,
|
||||
)
|
||||
} else if let Some(gw_node) = gateway_node {
|
||||
// Only entry gateway provided
|
||||
@@ -438,10 +444,17 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
gw_node,
|
||||
args.socks5_args,
|
||||
)
|
||||
} else {
|
||||
// No direct gateways, use directory lookup
|
||||
nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args)
|
||||
nym_gateway_probe::Probe::new(
|
||||
entry,
|
||||
test_point,
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
args.socks5_args,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(awg_args) = args.amnezia_args {
|
||||
|
||||
@@ -172,19 +172,20 @@ impl HttpsConnectivityResult {
|
||||
}
|
||||
|
||||
pub struct HttpsConnectivityTest {
|
||||
test_count: usize,
|
||||
test_count: u64,
|
||||
mixnet_client_timeout: Duration,
|
||||
}
|
||||
|
||||
/// currently we test against this endpoint
|
||||
/// endpoint to test against
|
||||
/// https://www.quicknode.com/docs/ethereum/web3_clientVersion
|
||||
const TARGET_URL: &str = "https://docs-demo.quiknode.pro";
|
||||
const POST_BODY: &str = r#"{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}"#;
|
||||
const MIXNET_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
impl HttpsConnectivityTest {
|
||||
pub fn new(test_count: usize) -> Self {
|
||||
pub fn new(test_count: u64, mixnet_client_timeout: u64) -> Self {
|
||||
Self {
|
||||
test_count: std::cmp::max(test_count, 1),
|
||||
mixnet_client_timeout: Duration::from_secs(mixnet_client_timeout),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +202,7 @@ impl HttpsConnectivityTest {
|
||||
|
||||
let client = match reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
// longer timeout for mixnet
|
||||
.timeout(MIXNET_TIMEOUT)
|
||||
.timeout(self.mixnet_client_timeout)
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
@@ -212,9 +212,9 @@ impl HttpsConnectivityTest {
|
||||
}
|
||||
};
|
||||
|
||||
let mut successful_runs = 0;
|
||||
for i in 0..self.test_count {
|
||||
info!("Running test {}/{}", i + 1, self.test_count);
|
||||
let mut successful_runs = 0u64;
|
||||
for i in 1..self.test_count + 1 {
|
||||
info!("Running test {}/{}", i, self.test_count);
|
||||
let interim_res = self.perform_https_request(&client).await;
|
||||
if interim_res.https_success
|
||||
&& let Some(latency_ms) = interim_res.https_latency_ms
|
||||
@@ -227,11 +227,17 @@ impl HttpsConnectivityTest {
|
||||
);
|
||||
result.https_success = true;
|
||||
result.https_status_code = interim_res.https_status_code;
|
||||
info!("{}/{} latency: {}ms", i + 1, self.test_count, latency_ms);
|
||||
info!("{}/{} latency: {}ms", i, self.test_count, latency_ms);
|
||||
} else if let Some(new_error) = interim_res.error {
|
||||
result.error = result
|
||||
.error
|
||||
.map(|existing| format!("{},{}", existing, new_error));
|
||||
result.error = Some(result.error.map_or(new_error.clone(), |existing| {
|
||||
format!("{},{}", existing, new_error)
|
||||
}))
|
||||
}
|
||||
|
||||
// too many failed runs: return early
|
||||
if successful_runs < 2 && i - successful_runs > 2 {
|
||||
// if < 2 runs, we don't have to calculate average before returning
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.https_latency_ms = result
|
||||
@@ -251,7 +257,7 @@ impl HttpsConnectivityTest {
|
||||
let mut result = HttpsConnectivityResult::default();
|
||||
let start = Instant::now();
|
||||
match tokio::time::timeout(
|
||||
MIXNET_TIMEOUT,
|
||||
self.mixnet_client_timeout,
|
||||
client
|
||||
.post(TARGET_URL)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
@@ -281,7 +287,7 @@ impl HttpsConnectivityTest {
|
||||
Err(_) => {
|
||||
warn!(
|
||||
"HTTPS request timed out after {}s",
|
||||
MIXNET_TIMEOUT.as_secs()
|
||||
self.mixnet_client_timeout.as_secs()
|
||||
);
|
||||
if result.error.is_none() {
|
||||
result.error = Some("HTTPS request timed out".to_string());
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# used primarily for local testing
|
||||
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.4.0"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
echo crate_root=${crate_root}
|
||||
monorepo_root=$(realpath "${crate_root}/../..")
|
||||
echo monorepo_root=${monorepo_root}
|
||||
|
||||
echo "Expecting nym-vpn-client repo at a sibling level of nym monorepo dir"
|
||||
gateway_probe_src=$(dirname "${monorepo_root}")/nym-vpn-client/nym-vpn-core
|
||||
gateway_probe_src="${monorepo_root}/nym-gateway-probe"
|
||||
echo "gateway_probe_src=$gateway_probe_src"
|
||||
|
||||
set -a
|
||||
@@ -25,7 +26,8 @@ export RUST_LOG="info"
|
||||
NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
|
||||
# hardcoded key used only for LOCAL TESTING
|
||||
export NODE_STATUS_AGENT_AUTH_KEY=${NODE_STATUS_AGENT_AUTH_KEY_STAGING:-"BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"}
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
|
||||
@@ -35,11 +37,9 @@ echo "Running $workers workers in parallel"
|
||||
# build & copy over GW probe
|
||||
function copy_gw_probe() {
|
||||
pushd $gateway_probe_src
|
||||
git fetch -a
|
||||
git checkout $probe_git_ref
|
||||
|
||||
cargo build --release --package nym-gateway-probe
|
||||
cp target/release/nym-gateway-probe "$crate_root"
|
||||
cp "${monorepo_root}/target/release/nym-gateway-probe" "$crate_root"
|
||||
$crate_root/nym-gateway-probe --version
|
||||
|
||||
popd
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::mixnet::{CredentialStorage, MixnetClient, Recipient};
|
||||
use crate::GatewayTransceiver;
|
||||
use crate::NymNetworkDetails;
|
||||
use crate::{Error, Result};
|
||||
use log::{debug, warn};
|
||||
use log::{debug, info, warn};
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::GatewayRegistration;
|
||||
use nym_client_core::client::base_client::storage::helpers::{
|
||||
get_active_gateway_identity, get_all_registered_identities, has_gateway_details,
|
||||
@@ -601,6 +601,10 @@ where
|
||||
);
|
||||
|
||||
let available_gateways = self.available_gateways().await?;
|
||||
info!("Listing all available gateways in topology:");
|
||||
for node in available_gateways.iter() {
|
||||
info!("{}", node.identity_key.to_base58_string());
|
||||
}
|
||||
|
||||
Ok(GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
|
||||
Reference in New Issue
Block a user