testing port checks

This commit is contained in:
benedettadavico
2026-02-13 10:31:55 +01:00
parent 35515fbf8c
commit 8aaa8dc8d3
5 changed files with 34 additions and 153 deletions
+1 -1
View File
@@ -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)
@@ -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 {
+4 -3
View File
@@ -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::<u16>::new())]
pub port_check_ports: Vec<u16>,
/// 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,
}
+16 -109
View File
@@ -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<TestedNodeDetails>,
network: NymNetworkDetails,
config: &RunPortsConfig,
pub async fn probe_run_ports(
mut self,
config_dir: &PathBuf,
credential: CredentialMode,
) -> anyhow::Result<PortCheckResult> {
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<PortCheckResult> {
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,
+12 -40
View File
@@ -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<PathBuf>,
/// 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<u8>,
/// 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<ProbeOutput> {
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<ProbeOutput> {
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,