testing port checks
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user