Restructure gateway probe with mode and common modules

- Extract TestMode enum to mode/mod.rs for cleaner organization
- Add common/wireguard.rs with shared WireGuard tunnel testing
- Deduplicate netstack code from wg_probe() and wg_probe_lp()
- Net reduction of 174 lines in lib.rs
This commit is contained in:
durch
2025-12-01 11:02:46 +01:00
parent 1b2f482ff1
commit c7e92c860b
4 changed files with 334 additions and 236 deletions
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
//! Common utilities shared across test modes.
//!
//! This module contains shared functionality used by multiple test modes:
//! - WireGuard tunnel testing via netstack
//! - LP registration helpers
//! - Credential handling
pub mod wireguard;
pub use wireguard::{WgTunnelConfig, run_tunnel_tests};
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
//! Shared WireGuard tunnel testing via netstack.
//!
//! This module provides common functionality for testing WireGuard tunnels
//! that is shared between different test modes (authenticator-based and LP-based).
use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4};
use tracing::{error, info};
use crate::netstack::{NetstackRequest, NetstackRequestGo, NetstackResult};
use crate::types::WgProbeResults;
use crate::NetstackArgs;
/// WireGuard tunnel configuration for netstack testing.
///
/// Contains all the parameters needed to establish and test a WireGuard tunnel.
pub struct WgTunnelConfig {
/// Client's private IPv4 address in the tunnel
pub private_ipv4: String,
/// Client's private IPv6 address in the tunnel
pub private_ipv6: String,
/// Client's WireGuard private key (hex encoded)
pub private_key_hex: String,
/// Gateway's WireGuard public key (hex encoded)
pub public_key_hex: String,
/// WireGuard endpoint address (gateway_ip:port)
pub endpoint: String,
}
impl WgTunnelConfig {
/// Create a new tunnel configuration.
pub fn new(
private_ipv4: impl Into<String>,
private_ipv6: impl Into<String>,
private_key_hex: impl Into<String>,
public_key_hex: impl Into<String>,
endpoint: impl Into<String>,
) -> Self {
Self {
private_ipv4: private_ipv4.into(),
private_ipv6: private_ipv6.into(),
private_key_hex: private_key_hex.into(),
public_key_hex: public_key_hex.into(),
endpoint: endpoint.into(),
}
}
}
/// Run WireGuard tunnel connectivity tests using netstack.
///
/// This function tests both IPv4 and IPv6 connectivity through the WireGuard tunnel:
/// - DNS resolution
/// - ICMP ping to specified hosts and IPs
/// - Optional download test
///
/// # Arguments
/// * `config` - WireGuard tunnel configuration
/// * `netstack_args` - Netstack test parameters (DNS, hosts to ping, timeouts, etc.)
/// * `awg_args` - Amnezia WireGuard arguments (empty string for standard WG)
///
/// # Returns
/// `WgProbeResults` with the test outcomes for both IPv4 and IPv6.
// AIDEV-NOTE: This function extracts the shared netstack testing logic from
// wg_probe() and wg_probe_lp() to eliminate code duplication.
pub fn run_tunnel_tests(
config: &WgTunnelConfig,
netstack_args: &NetstackArgs,
awg_args: &str,
) -> WgProbeResults {
let mut wg_outcome = WgProbeResults::default();
// Build the netstack request
let netstack_request = NetstackRequest::new(
&config.private_ipv4,
&config.private_ipv6,
&config.private_key_hex,
&config.public_key_hex,
&config.endpoint,
&format!("http://{WG_TUN_DEVICE_IP_ADDRESS_V4}:{WG_METADATA_PORT}"),
netstack_args.netstack_download_timeout_sec,
awg_args,
netstack_args.clone(),
);
// Perform IPv4 ping test
info!("Testing IPv4 tunnel connectivity...");
let ipv4_request = NetstackRequestGo::from_rust_v4(&netstack_request);
match crate::netstack::ping(&ipv4_request) {
Ok(NetstackResult::Response(netstack_response_v4)) => {
info!(
"WireGuard probe response for IPv4: {:#?}",
netstack_response_v4
);
wg_outcome.can_query_metadata_v4 = netstack_response_v4.can_query_metadata;
wg_outcome.can_handshake_v4 = netstack_response_v4.can_handshake;
wg_outcome.can_resolve_dns_v4 = netstack_response_v4.can_resolve_dns;
// AIDEV-NOTE: Division by zero is possible here if sent_hosts/sent_ips is 0.
// This matches existing behavior; consider adding guards in a follow-up.
wg_outcome.ping_hosts_performance_v4 =
netstack_response_v4.received_hosts as f32 / netstack_response_v4.sent_hosts as f32;
wg_outcome.ping_ips_performance_v4 =
netstack_response_v4.received_ips as f32 / netstack_response_v4.sent_ips as f32;
wg_outcome.download_duration_sec_v4 = netstack_response_v4.download_duration_sec;
wg_outcome.download_duration_milliseconds_v4 =
netstack_response_v4.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v4 =
netstack_response_v4.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v4 = netstack_response_v4.downloaded_file;
wg_outcome.download_error_v4 = netstack_response_v4.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error (IPv4): {error}")
}
Err(error) => {
error!("Internal error (IPv4): {error}")
}
}
// Perform IPv6 ping test
info!("Testing IPv6 tunnel connectivity...");
let ipv6_request = NetstackRequestGo::from_rust_v6(&netstack_request);
match crate::netstack::ping(&ipv6_request) {
Ok(NetstackResult::Response(netstack_response_v6)) => {
info!(
"WireGuard probe response for IPv6: {:#?}",
netstack_response_v6
);
wg_outcome.can_handshake_v6 = netstack_response_v6.can_handshake;
wg_outcome.can_resolve_dns_v6 = netstack_response_v6.can_resolve_dns;
// AIDEV-NOTE: Same division by zero concern as IPv4
wg_outcome.ping_hosts_performance_v6 =
netstack_response_v6.received_hosts as f32 / netstack_response_v6.sent_hosts as f32;
wg_outcome.ping_ips_performance_v6 =
netstack_response_v6.received_ips as f32 / netstack_response_v6.sent_ips as f32;
wg_outcome.download_duration_sec_v6 = netstack_response_v6.download_duration_sec;
wg_outcome.download_duration_milliseconds_v6 =
netstack_response_v6.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v6 =
netstack_response_v6.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v6 = netstack_response_v6.downloaded_file;
wg_outcome.download_error_v6 = netstack_response_v6.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error (IPv6): {error}")
}
Err(error) => {
error!("Internal error (IPv6): {error}")
}
}
wg_outcome
}
+62 -236
View File
@@ -7,7 +7,7 @@ use std::{
time::Duration,
};
use crate::{netstack::NetstackResult, types::Entry};
use crate::types::Entry;
use anyhow::bail;
use base64::{Engine as _, engine::general_purpose};
use bytes::BytesMut;
@@ -20,7 +20,7 @@ use nym_authenticator_requests::{
};
use nym_client_core::config::ForgetMe;
use nym_config::defaults::{
NymNetworkDetails, WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4,
NymNetworkDetails,
mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6},
};
use nym_connection_monitor::self_ping_and_wait;
@@ -51,10 +51,11 @@ use crate::{
types::Exit,
};
use netstack::{NetstackRequest, NetstackRequestGo};
mod bandwidth_helpers;
mod common;
mod icmp;
pub mod mode;
mod netstack;
pub mod nodes;
mod types;
@@ -62,6 +63,7 @@ mod types;
use crate::bandwidth_helpers::{acquire_bandwidth, import_bandwidth};
use crate::nodes::{DirectoryNode, NymApiDirectory};
use nym_node_status_client::models::AttachedTicketMaterials;
pub use mode::TestMode;
pub use types::{IpPingReplies, ProbeOutcome, ProbeResult};
#[derive(Args, Clone)]
@@ -124,89 +126,6 @@ impl CredentialArgs {
}
}
/// Test mode for the gateway probe.
///
/// Determines which tests are performed and how connections are established.
// AIDEV-NOTE: This enum replaces the scattered boolean flags (only_wireguard,
// only_lp_registration, test_lp_wg) with explicit, named modes for clarity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TestMode {
/// Traditional mixnet testing - connects via mixnet, tests entry/exit pings + WireGuard via authenticator
#[default]
Mixnet,
/// LP registration + WireGuard on single gateway (no mixnet, no forwarding)
SingleHop,
/// Entry LP + Exit LP (nested session forwarding) + WireGuard tunnel
TwoHop,
/// LP registration only - test handshake and registration, skip WireGuard
LpOnly,
}
impl TestMode {
/// Infer test mode from legacy boolean flags (backward compatibility)
pub fn from_flags(only_wireguard: bool, only_lp_registration: bool, test_lp_wg: bool, has_exit_gateway: bool) -> Self {
if only_lp_registration {
TestMode::LpOnly
} else if test_lp_wg {
if has_exit_gateway {
TestMode::TwoHop
} else {
TestMode::SingleHop
}
} else if only_wireguard {
// WireGuard via authenticator (still uses mixnet path)
TestMode::Mixnet
} else {
TestMode::Mixnet
}
}
/// Whether this mode requires a mixnet client
pub fn needs_mixnet(&self) -> bool {
matches!(self, TestMode::Mixnet)
}
/// Whether this mode uses LP registration
pub fn uses_lp(&self) -> bool {
matches!(self, TestMode::SingleHop | TestMode::TwoHop | TestMode::LpOnly)
}
/// Whether this mode tests WireGuard tunnels
pub fn tests_wireguard(&self) -> bool {
matches!(self, TestMode::Mixnet | TestMode::SingleHop | TestMode::TwoHop)
}
/// Whether this mode requires an exit gateway
pub fn needs_exit_gateway(&self) -> bool {
matches!(self, TestMode::TwoHop)
}
}
impl std::fmt::Display for TestMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestMode::Mixnet => write!(f, "mixnet"),
TestMode::SingleHop => write!(f, "single-hop"),
TestMode::TwoHop => write!(f, "two-hop"),
TestMode::LpOnly => write!(f, "lp-only"),
}
}
}
impl std::str::FromStr for TestMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"mixnet" => Ok(TestMode::Mixnet),
"single-hop" | "singlehop" | "single_hop" => Ok(TestMode::SingleHop),
"two-hop" | "twohop" | "two_hop" => Ok(TestMode::TwoHop),
"lp-only" | "lponly" | "lp_only" => Ok(TestMode::LpOnly),
_ => Err(format!("Unknown test mode: '{}'. Valid modes: mixnet, single-hop, two-hop, lp-only", s)),
}
}
}
#[derive(Default, Debug)]
pub enum TestedNode {
#[default]
@@ -1075,84 +994,38 @@ async fn wg_probe(
wg_outcome.can_register = true;
if wg_outcome.can_register {
let netstack_request = NetstackRequest::new(
&registered_data.private_ips().ipv4.to_string(),
&registered_data.private_ips().ipv6.to_string(),
&private_key_hex,
&public_key_hex,
&wg_endpoint,
&format!("http://{WG_TUN_DEVICE_IP_ADDRESS_V4}:{WG_METADATA_PORT}"),
netstack_args.netstack_download_timeout_sec,
&awg_args,
netstack_args,
);
// Run tunnel connectivity tests using shared helper
let tunnel_config = common::WgTunnelConfig::new(
registered_data.private_ips().ipv4.to_string(),
registered_data.private_ips().ipv6.to_string(),
private_key_hex,
public_key_hex,
wg_endpoint,
);
// Perform IPv4 ping test
let ipv4_request = NetstackRequestGo::from_rust_v4(&netstack_request);
let tunnel_results = common::run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args);
match netstack::ping(&ipv4_request) {
Ok(NetstackResult::Response(netstack_response_v4)) => {
info!(
"Wireguard probe response for IPv4: {:#?}",
netstack_response_v4
);
wg_outcome.can_query_metadata_v4 = netstack_response_v4.can_query_metadata;
wg_outcome.can_handshake_v4 = netstack_response_v4.can_handshake;
wg_outcome.can_resolve_dns_v4 = netstack_response_v4.can_resolve_dns;
wg_outcome.ping_hosts_performance_v4 = netstack_response_v4.received_hosts as f32
/ netstack_response_v4.sent_hosts as f32;
wg_outcome.ping_ips_performance_v4 =
netstack_response_v4.received_ips as f32 / netstack_response_v4.sent_ips as f32;
// Merge tunnel test results into outcome
wg_outcome.can_query_metadata_v4 = tunnel_results.can_query_metadata_v4;
wg_outcome.can_handshake_v4 = tunnel_results.can_handshake_v4;
wg_outcome.can_resolve_dns_v4 = tunnel_results.can_resolve_dns_v4;
wg_outcome.ping_hosts_performance_v4 = tunnel_results.ping_hosts_performance_v4;
wg_outcome.ping_ips_performance_v4 = tunnel_results.ping_ips_performance_v4;
wg_outcome.download_duration_sec_v4 = tunnel_results.download_duration_sec_v4;
wg_outcome.download_duration_milliseconds_v4 = tunnel_results.download_duration_milliseconds_v4;
wg_outcome.downloaded_file_size_bytes_v4 = tunnel_results.downloaded_file_size_bytes_v4;
wg_outcome.downloaded_file_v4 = tunnel_results.downloaded_file_v4.clone();
wg_outcome.download_error_v4 = tunnel_results.download_error_v4.clone();
wg_outcome.download_duration_sec_v4 = netstack_response_v4.download_duration_sec;
wg_outcome.download_duration_milliseconds_v4 =
netstack_response_v4.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v4 =
netstack_response_v4.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v4 = netstack_response_v4.downloaded_file;
wg_outcome.download_error_v4 = netstack_response_v4.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error: {error}")
}
Err(error) => {
error!("Internal error: {error}")
}
}
// Perform IPv6 ping test
let ipv6_request = NetstackRequestGo::from_rust_v6(&netstack_request);
match netstack::ping(&ipv6_request) {
Ok(NetstackResult::Response(netstack_response_v6)) => {
info!(
"Wireguard probe response for IPv6: {:#?}",
netstack_response_v6
);
wg_outcome.can_handshake_v6 = netstack_response_v6.can_handshake;
wg_outcome.can_resolve_dns_v6 = netstack_response_v6.can_resolve_dns;
wg_outcome.ping_hosts_performance_v6 = netstack_response_v6.received_hosts as f32
/ netstack_response_v6.sent_hosts as f32;
wg_outcome.ping_ips_performance_v6 =
netstack_response_v6.received_ips as f32 / netstack_response_v6.sent_ips as f32;
wg_outcome.download_duration_sec_v6 = netstack_response_v6.download_duration_sec;
wg_outcome.download_duration_milliseconds_v6 =
netstack_response_v6.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v6 =
netstack_response_v6.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v6 = netstack_response_v6.downloaded_file;
wg_outcome.download_error_v6 = netstack_response_v6.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error: {error}")
}
Err(error) => {
error!("Internal error: {error}")
}
}
}
wg_outcome.can_handshake_v6 = tunnel_results.can_handshake_v6;
wg_outcome.can_resolve_dns_v6 = tunnel_results.can_resolve_dns_v6;
wg_outcome.ping_hosts_performance_v6 = tunnel_results.ping_hosts_performance_v6;
wg_outcome.ping_ips_performance_v6 = tunnel_results.ping_ips_performance_v6;
wg_outcome.download_duration_sec_v6 = tunnel_results.download_duration_sec_v6;
wg_outcome.download_duration_milliseconds_v6 = tunnel_results.download_duration_milliseconds_v6;
wg_outcome.downloaded_file_size_bytes_v6 = tunnel_results.downloaded_file_size_bytes_v6;
wg_outcome.downloaded_file_v6 = tunnel_results.downloaded_file_v6.clone();
wg_outcome.download_error_v6 = tunnel_results.download_error_v6.clone();
Ok(wg_outcome)
}
@@ -1426,85 +1299,38 @@ where
info!(" Private IPv6: {}", exit_gateway_data.private_ipv6);
info!(" Endpoint: {}", wg_endpoint);
// Run tunnel tests (copied from wg_probe)
let netstack_request = crate::netstack::NetstackRequest::new(
&exit_gateway_data.private_ipv4.to_string(),
&exit_gateway_data.private_ipv6.to_string(),
&private_key_hex,
&public_key_hex,
&wg_endpoint,
&format!("http://{WG_TUN_DEVICE_IP_ADDRESS_V4}:{WG_METADATA_PORT}"),
netstack_args.netstack_download_timeout_sec,
&awg_args,
netstack_args,
// Run tunnel connectivity tests using shared helper
let tunnel_config = common::WgTunnelConfig::new(
exit_gateway_data.private_ipv4.to_string(),
exit_gateway_data.private_ipv6.to_string(),
private_key_hex,
public_key_hex,
wg_endpoint,
);
// Perform IPv4 ping test
info!("Testing IPv4 tunnel connectivity...");
let ipv4_request = crate::netstack::NetstackRequestGo::from_rust_v4(&netstack_request);
let tunnel_results = common::run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args);
match crate::netstack::ping(&ipv4_request) {
Ok(NetstackResult::Response(netstack_response_v4)) => {
info!(
"Wireguard probe response for IPv4: {:#?}",
netstack_response_v4
);
wg_outcome.can_query_metadata_v4 = netstack_response_v4.can_query_metadata;
wg_outcome.can_handshake_v4 = netstack_response_v4.can_handshake;
wg_outcome.can_resolve_dns_v4 = netstack_response_v4.can_resolve_dns;
wg_outcome.ping_hosts_performance_v4 =
netstack_response_v4.received_hosts as f32 / netstack_response_v4.sent_hosts as f32;
wg_outcome.ping_ips_performance_v4 =
netstack_response_v4.received_ips as f32 / netstack_response_v4.sent_ips as f32;
// Merge tunnel test results into outcome
wg_outcome.can_query_metadata_v4 = tunnel_results.can_query_metadata_v4;
wg_outcome.can_handshake_v4 = tunnel_results.can_handshake_v4;
wg_outcome.can_resolve_dns_v4 = tunnel_results.can_resolve_dns_v4;
wg_outcome.ping_hosts_performance_v4 = tunnel_results.ping_hosts_performance_v4;
wg_outcome.ping_ips_performance_v4 = tunnel_results.ping_ips_performance_v4;
wg_outcome.download_duration_sec_v4 = tunnel_results.download_duration_sec_v4;
wg_outcome.download_duration_milliseconds_v4 = tunnel_results.download_duration_milliseconds_v4;
wg_outcome.downloaded_file_size_bytes_v4 = tunnel_results.downloaded_file_size_bytes_v4;
wg_outcome.downloaded_file_v4 = tunnel_results.downloaded_file_v4.clone();
wg_outcome.download_error_v4 = tunnel_results.download_error_v4.clone();
wg_outcome.download_duration_sec_v4 = netstack_response_v4.download_duration_sec;
wg_outcome.download_duration_milliseconds_v4 =
netstack_response_v4.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v4 =
netstack_response_v4.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v4 = netstack_response_v4.downloaded_file;
wg_outcome.download_error_v4 = netstack_response_v4.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error (IPv4): {error}")
}
Err(error) => {
error!("Internal error (IPv4): {error}")
}
}
// Perform IPv6 ping test
info!("Testing IPv6 tunnel connectivity...");
let ipv6_request = crate::netstack::NetstackRequestGo::from_rust_v6(&netstack_request);
match crate::netstack::ping(&ipv6_request) {
Ok(NetstackResult::Response(netstack_response_v6)) => {
info!(
"Wireguard probe response for IPv6: {:#?}",
netstack_response_v6
);
wg_outcome.can_handshake_v6 = netstack_response_v6.can_handshake;
wg_outcome.can_resolve_dns_v6 = netstack_response_v6.can_resolve_dns;
wg_outcome.ping_hosts_performance_v6 =
netstack_response_v6.received_hosts as f32 / netstack_response_v6.sent_hosts as f32;
wg_outcome.ping_ips_performance_v6 =
netstack_response_v6.received_ips as f32 / netstack_response_v6.sent_ips as f32;
wg_outcome.download_duration_sec_v6 = netstack_response_v6.download_duration_sec;
wg_outcome.download_duration_milliseconds_v6 =
netstack_response_v6.download_duration_milliseconds;
wg_outcome.downloaded_file_size_bytes_v6 =
netstack_response_v6.downloaded_file_size_bytes;
wg_outcome.downloaded_file_v6 = netstack_response_v6.downloaded_file;
wg_outcome.download_error_v6 = netstack_response_v6.download_error;
}
Ok(NetstackResult::Error { error }) => {
error!("Netstack runtime error (IPv6): {error}")
}
Err(error) => {
error!("Internal error (IPv6): {error}")
}
}
wg_outcome.can_handshake_v6 = tunnel_results.can_handshake_v6;
wg_outcome.can_resolve_dns_v6 = tunnel_results.can_resolve_dns_v6;
wg_outcome.ping_hosts_performance_v6 = tunnel_results.ping_hosts_performance_v6;
wg_outcome.ping_ips_performance_v6 = tunnel_results.ping_ips_performance_v6;
wg_outcome.download_duration_sec_v6 = tunnel_results.download_duration_sec_v6;
wg_outcome.download_duration_milliseconds_v6 = tunnel_results.download_duration_milliseconds_v6;
wg_outcome.downloaded_file_size_bytes_v6 = tunnel_results.downloaded_file_size_bytes_v6;
wg_outcome.downloaded_file_v6 = tunnel_results.downloaded_file_v6.clone();
wg_outcome.download_error_v6 = tunnel_results.download_error_v6.clone();
info!("LP-based WireGuard probe completed");
Ok(wg_outcome)
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
//! Test mode definitions for gateway probe.
//!
//! This module defines the different test modes supported by the gateway probe:
//! - Mixnet: Traditional mixnet path testing
//! - SingleHop: LP registration + WireGuard on single gateway
//! - TwoHop: Entry LP + Exit LP (nested forwarding) + WireGuard
//! - LpOnly: LP registration only, no WireGuard
/// Test mode for the gateway probe.
///
/// Determines which tests are performed and how connections are established.
// AIDEV-NOTE: This enum replaces the scattered boolean flags (only_wireguard,
// only_lp_registration, test_lp_wg) with explicit, named modes for clarity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TestMode {
/// Traditional mixnet testing - connects via mixnet, tests entry/exit pings + WireGuard via authenticator
#[default]
Mixnet,
/// LP registration + WireGuard on single gateway (no mixnet, no forwarding)
SingleHop,
/// Entry LP + Exit LP (nested session forwarding) + WireGuard tunnel
TwoHop,
/// LP registration only - test handshake and registration, skip WireGuard
LpOnly,
}
impl TestMode {
/// Infer test mode from legacy boolean flags (backward compatibility)
pub fn from_flags(
only_wireguard: bool,
only_lp_registration: bool,
test_lp_wg: bool,
has_exit_gateway: bool,
) -> Self {
if only_lp_registration {
TestMode::LpOnly
} else if test_lp_wg {
if has_exit_gateway {
TestMode::TwoHop
} else {
TestMode::SingleHop
}
} else if only_wireguard {
// WireGuard via authenticator (still uses mixnet path)
TestMode::Mixnet
} else {
TestMode::Mixnet
}
}
/// Whether this mode requires a mixnet client
pub fn needs_mixnet(&self) -> bool {
matches!(self, TestMode::Mixnet)
}
/// Whether this mode uses LP registration
pub fn uses_lp(&self) -> bool {
matches!(self, TestMode::SingleHop | TestMode::TwoHop | TestMode::LpOnly)
}
/// Whether this mode tests WireGuard tunnels
pub fn tests_wireguard(&self) -> bool {
matches!(self, TestMode::Mixnet | TestMode::SingleHop | TestMode::TwoHop)
}
/// Whether this mode requires an exit gateway
pub fn needs_exit_gateway(&self) -> bool {
matches!(self, TestMode::TwoHop)
}
}
impl std::fmt::Display for TestMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestMode::Mixnet => write!(f, "mixnet"),
TestMode::SingleHop => write!(f, "single-hop"),
TestMode::TwoHop => write!(f, "two-hop"),
TestMode::LpOnly => write!(f, "lp-only"),
}
}
}
impl std::str::FromStr for TestMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"mixnet" => Ok(TestMode::Mixnet),
"single-hop" | "singlehop" | "single_hop" => Ok(TestMode::SingleHop),
"two-hop" | "twohop" | "two_hop" => Ok(TestMode::TwoHop),
"lp-only" | "lponly" | "lp_only" => Ok(TestMode::LpOnly),
_ => Err(format!(
"Unknown test mode: '{}'. Valid modes: mixnet, single-hop, two-hop, lp-only",
s
)),
}
}
}