From 8aaa8dc8d35f871997ab8ff52468303457137d61 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Fri, 13 Feb 2026 10:31:55 +0100 Subject: [PATCH] testing port checks --- nym-gateway-probe/netstack_ping/lib.go | 2 +- nym-gateway-probe/src/common/wireguard.rs | 1 + nym-gateway-probe/src/config/netstack.rs | 7 +- nym-gateway-probe/src/lib.rs | 125 +++------------------- nym-gateway-probe/src/run.rs | 52 +++------ 5 files changed, 34 insertions(+), 153 deletions(-) diff --git a/nym-gateway-probe/netstack_ping/lib.go b/nym-gateway-probe/netstack_ping/lib.go index 9beb635d0c..8ab6b371ce 100644 --- a/nym-gateway-probe/netstack_ping/lib.go +++ b/nym-gateway-probe/netstack_ping/lib.go @@ -618,7 +618,7 @@ func checkPorts(target string, ports []uint16, timeoutSec uint64, tnet *netstack // testing all 114 ports in one burst trips it after ~29 connections. // small batches with a cooldown in between keep us under the limit. const batchSize = 20 - const batchDelay = 25 * time.Second + const batchDelay = 45 * time.Second const dialGap = 200 * time.Millisecond results := make(map[string]bool) diff --git a/nym-gateway-probe/src/common/wireguard.rs b/nym-gateway-probe/src/common/wireguard.rs index a2cb783433..740f32700a 100644 --- a/nym-gateway-probe/src/common/wireguard.rs +++ b/nym-gateway-probe/src/common/wireguard.rs @@ -12,6 +12,7 @@ use tracing::{error, info}; use crate::NetstackArgs; use crate::common::netstack::{NetstackRequest, NetstackRequestGo, NetstackResult}; use crate::common::types::WgProbeResults; +use crate::config::NetstackArgs; /// Safe division that returns 0.0 when divisor is 0 (instead of NaN/Inf) fn safe_ratio(received: u16, sent: u16) -> f32 { diff --git a/nym-gateway-probe/src/config/netstack.rs b/nym-gateway-probe/src/config/netstack.rs index 3479b42274..d07ba86558 100644 --- a/nym-gateway-probe/src/config/netstack.rs +++ b/nym-gateway-probe/src/config/netstack.rs @@ -47,12 +47,13 @@ pub struct NetstackArgs { #[arg(long = "use-target", default_value = "portquiz.net")] pub port_check_target: String, - /// List ports to check, separated by a comma. + /// TCP ports to check through the WireGuard tunnel for exit policy verification. + /// Only used with the `run-ports` subcommand. For all exit policy ports, use --check-all-ports instead. #[arg(long = "check-ports", value_delimiter = ',', default_values_t = Vec::::new())] pub port_check_ports: Vec, - /// Timeout in seconds for each individual port check attempt - #[arg(long, default_value_t = NetstackArgs::default().port_check_timeout_sec)] + /// Timeout in seconds for each individual TCP port check + #[arg(long, default_value_t = 5)] pub port_check_timeout_sec: u64, } diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 0d8e003b42..fdaea8a1bb 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -8,7 +8,7 @@ use crate::common::probe_tests::{ do_ping, do_socks5_connectivity_test, lp_registration_probe, wg_probe, }; use crate::common::types::{Entry, LpProbeResults}; -use crate::config::{CredentialArgs, CredentialMode, NetstackArgs, ProbeConfig}; +use crate::config::{CredentialArgs, CredentialMode, ProbeConfig}; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_client_core::config::ForgetMe; @@ -21,7 +21,6 @@ use nym_sdk::mixnet::{ use nym_topology::{HardcodedTopologyProvider, NymTopology}; use rand::rngs::OsRng; use std::collections::HashMap; -use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use tokio_util::sync::CancellationToken; @@ -299,15 +298,12 @@ impl Probe { } /// Run a port-check probe against the exit gateway's WG exit policy - pub async fn run_ports_bonded( - entry_node: TestedNodeDetails, - exit_node: Option, - network: NymNetworkDetails, - config: &RunPortsConfig, + pub async fn probe_run_ports( + mut self, config_dir: &PathBuf, credential: CredentialMode, ) -> anyhow::Result { - let exit_node = exit_node.unwrap_or(entry_node.clone()); + let exit_node = self.exit_node.take().unwrap_or(self.entry_node.clone()); let exit_identity = exit_node.identity.to_string(); // need authenticator + IP to be a functional exit @@ -322,8 +318,8 @@ impl Probe { } }; - let ports = config.netstack_args.port_check_ports.clone(); - let port_check_target = config.netstack_args.port_check_target.clone(); + let ports = self.config.netstack_args.port_check_ports.clone(); + let port_check_target = self.config.netstack_args.port_check_target.clone(); if ports.is_empty() { anyhow::bail!( @@ -345,23 +341,23 @@ impl Probe { .await?; let mixnet_debug_config = helpers::mixnet_debug_config( - config.min_gateway_mixnet_performance, - config.ignore_egress_epoch_role, + self.config.min_gateway_mixnet_performance, + self.config.ignore_egress_epoch_role, ); - let topology = helpers::fetch_topology(&network, &mixnet_debug_config) + self.topology = helpers::fetch_topology(&self.network, &mixnet_debug_config) .await .inspect_err(|e| warn!("Failed to fetch topology: {e}")) .ok(); let mut mixnet_client_builder = MixnetClientBuilder::new_with_storage(storage.clone()) - .request_gateway(entry_node.identity.to_string()) - .network_details(network.clone()) + .request_gateway(self.entry_node.identity.to_string()) + .network_details(self.network.clone()) .debug_config(mixnet_debug_config) .with_forget_me(ForgetMe::new_stats()) .credentials_mode(!credential.use_mock_ecash); - if let Some(topology) = &topology { + if let Some(topology) = &self.topology { mixnet_client_builder = mixnet_client_builder.custom_topology_provider(Box::new( HardcodedTopologyProvider::new(topology.clone()), )); @@ -383,7 +379,7 @@ impl Probe { .await?; let bandwidth_provider = build_bandwidth_controller( - &network, + &self.network, storage.credential_store().clone(), credential.use_mock_ecash, )?; @@ -392,7 +388,7 @@ impl Probe { Ok(client) => { info!( "Connected to mixnet via entry gateway: {}", - entry_node.identity + self.entry_node.identity ); info!("Our nym address: {}", *client.nym_address()); client @@ -455,7 +451,7 @@ impl Probe { } }; - let netstack_args = config.netstack_args.clone(); + let netstack_args = self.config.netstack_args.clone(); let mut rng = rand::thread_rng(); let auth_client = AuthenticatorClient::new( mixnet_listener_task.subscribe(), @@ -471,7 +467,7 @@ impl Probe { auth_client, ip_address, exit_node.authenticator_version, - None, + self.config.amnezia_args.clone(), netstack_args, true, // port_check_only credential, @@ -513,95 +509,6 @@ impl Probe { }) } - /// Run a direct TCP port check against an IP-only target gateway. - /// This bypasses mixnet and auth registration. It is intended for unannounced/local gateways - pub async fn probe_run_ports_direct_ip( - gateway_address: &str, - target_ip: IpAddr, - config: &RunPortsConfig, - protocol: DirectPortCheckProtocol, - ) -> anyhow::Result { - let ports = config.netstack_args.port_check_ports.clone(); - if ports.is_empty() { - anyhow::bail!( - "No ports specified. Use --check-ports 80,443,22021 or --check-all-ports" - ); - } - - // Preferred truth source: gateway-declared exit policy. - if let Ok(used_policy) = - crate::common::nodes::query_exit_policy_by_ip(gateway_address).await - { - if used_policy.enabled { - if let Some(policy) = used_policy.policy { - let mut policy_results = HashMap::with_capacity(ports.len()); - let v4_probe = std::net::IpAddr::V4(std::net::Ipv4Addr::new(1, 1, 1, 1)); - let v6_probe = std::net::IpAddr::V6(std::net::Ipv6Addr::new( - 0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111, - )); - - for port in &ports { - let allowed_v4 = policy.allows(&v4_probe, *port).unwrap_or(false); - let allowed_v6 = policy.allows(&v6_probe, *port).unwrap_or(false); - policy_results.insert(port.to_string(), allowed_v4 || allowed_v6); - } - - return Ok(PortCheckResult { - gateway: gateway_address.to_string(), - can_register: true, - port_check_target: gateway_address.to_string(), - ports: policy_results, - error: None, - }); - } - } - } - - let timeout_duration = - std::time::Duration::from_secs(config.netstack_args.port_check_timeout_sec); - let mut port_results = HashMap::with_capacity(ports.len()); - - info!( - "Direct {:?} port check: testing {} ports on {} (timeout {}s per port)", - protocol, - ports.len(), - target_ip, - config.netstack_args.port_check_timeout_sec - ); - - for port in ports { - let socket = SocketAddr::new(target_ip, port); - let is_open = match protocol { - DirectPortCheckProtocol::Auto => { - Probe::check_tcp_socket(socket, timeout_duration).await - || Probe::check_udp_socket(socket, timeout_duration).await - } - DirectPortCheckProtocol::Tcp => { - Probe::check_tcp_socket(socket, timeout_duration).await - } - DirectPortCheckProtocol::Udp => { - Probe::check_udp_socket(socket, timeout_duration).await - } - }; - port_results.insert(port.to_string(), is_open); - } - - let open = port_results.values().filter(|&&is_open| is_open).count(); - info!( - "Direct port check complete: {}/{} ports reachable", - open, - port_results.len() - ); - - Ok(PortCheckResult { - gateway: gateway_address.to_string(), - can_register: true, - port_check_target: gateway_address.to_string(), - ports: port_results, - error: None, - }) - } - #[allow(clippy::too_many_arguments)] pub async fn do_probe_test( self, diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index ffd5691277..00eac13661 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -4,10 +4,8 @@ use clap::{Args, Parser, Subcommand}; use nym_bin_common::bin_info; use nym_config::defaults::setup_env; -use nym_gateway_probe::config::{CredentialArgs, CredentialMode, NetstackArgs, ProbeConfig}; -use nym_gateway_probe::{ - NymApiDirectory, PortCheckResult, ProbeResult, RunPortsConfig, query_gateway_by_ip, -}; +use nym_gateway_probe::config::{CredentialArgs, CredentialMode, ProbeConfig}; +use nym_gateway_probe::{NymApiDirectory, PortCheckResult, ProbeResult, query_gateway_by_ip}; use nym_sdk::mixnet::NodeIdentity; use serde::Serialize; use std::path::Path; @@ -92,7 +90,7 @@ enum Commands { #[arg(long)] config_dir: Option, - /// Bonded gateway identity. + /// Gateway to test (used as both entry and exit unless --exit-gateway is set) #[arg(long, short = 'g', alias = "gateway")] entry_gateway: NodeIdentity, @@ -105,12 +103,12 @@ enum Commands { #[arg(long)] check_all_ports: bool, - /// Credential arguments (required). + /// Arguments to manage credentials #[command(flatten)] credential_mode: CredentialMode, #[command(flatten)] - probe_config: RunPortsProbeConfig, + probe_config: ProbeConfig, }, /// Run the probe by NS agents @@ -128,21 +126,6 @@ enum Commands { }, } -#[derive(Debug, Args, Clone)] -struct RunPortsProbeConfig { - /// Only choose gateway with that minimum performance - #[arg(long)] - min_gateway_mixnet_performance: Option, - - /// Ignore egress epoch role constraints - #[arg(long, global = true)] - ignore_egress_epoch_role: bool, - - /// Arguments to manage netstack downloads and port checks - #[command(flatten)] - netstack_args: NetstackArgs, -} - /// CLI output wrapper — either a standard probe result or a port-check result #[derive(Serialize)] #[serde(untagged)] @@ -276,18 +259,12 @@ pub(crate) async fn run() -> anyhow::Result { config_dir, check_all_ports, credential_mode, - probe_config, + mut probe_config, } => { - let mut run_ports_config = RunPortsConfig { - min_gateway_mixnet_performance: probe_config.min_gateway_mixnet_performance, - ignore_egress_epoch_role: probe_config.ignore_egress_epoch_role, - netstack_args: probe_config.netstack_args, - }; - // --check-all-ports overrides --check-ports with the full exit policy list if check_all_ports { use nym_gateway_probe::config::EXIT_POLICY_PORTS; - run_ports_config.netstack_args.port_check_ports = EXIT_POLICY_PORTS.to_vec(); + probe_config.netstack_args.port_check_ports = EXIT_POLICY_PORTS.to_vec(); info!( "Using full exit policy port list ({} ports)", EXIT_POLICY_PORTS.len() @@ -329,16 +306,11 @@ pub(crate) async fn run() -> anyhow::Result { config_dir.display() ); - Box::pin(nym_gateway_probe::Probe::run_ports_bonded( - entry_details, - exit_details, - network, - &run_ports_config, - &config_dir, - credential_mode, - )) - .await - .map(ProbeOutput::PortCheck) + let trial = + nym_gateway_probe::Probe::new(entry_details, exit_details, network, probe_config); + Box::pin(trial.probe_run_ports(&config_dir, credential_mode)) + .await + .map(ProbeOutput::PortCheck) } Commands::RunAgent { entry_gateway,