Remove in-prove

remove in-probe test, it isn't needed.
This commit is contained in:
benedettadavico
2026-04-30 08:52:46 +02:00
parent 1dc3e94bdc
commit 2b237dec1f
10 changed files with 233 additions and 145 deletions
+14 -16
View File
@@ -9,40 +9,38 @@ pub use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortsCheckSummary {
pub all_pass: bool,
pub failed_ports: Vec<u16>,
pub error: Option<String>,
pub ports_tested: usize,
pub port_check_target: String,
pub failed_ports: Vec<String>,
}
impl PortsCheckSummary {
pub fn from_port_map(can_register: bool, ports: &BTreeMap<String, bool>) -> Self {
let failed_ports: Vec<u16> = ports
pub fn from_port_map(
can_register: bool,
port_check_target: impl Into<String>,
ports: &BTreeMap<String, bool>,
) -> Self {
let failed_ports: Vec<String> = ports
.iter()
.filter_map(|(port, open)| {
if *open {
None
} else {
port.parse::<u16>().ok()
}
})
.filter_map(|(port, open)| if *open { None } else { Some(port.clone()) })
.collect();
let all_pass = can_register && failed_ports.is_empty() && !ports.is_empty();
Self {
all_pass,
failed_ports,
error: None,
ports_tested: ports.len(),
port_check_target: port_check_target.into(),
failed_ports,
}
}
pub fn probe_error(_can_register: bool, message: impl Into<String>) -> Self {
pub fn probe_error(port_check_target: impl Into<String>, message: impl Into<String>) -> Self {
Self {
all_pass: false,
failed_ports: vec![],
error: Some(message.into()),
ports_tested: 0,
port_check_target: port_check_target.into(),
failed_ports: vec![],
}
}
}
+4 -112
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, EXIT_POLICY_PORTS, NetstackArgs, ProbeConfig};
use crate::config::{CredentialArgs, CredentialMode, NetstackArgs, ProbeConfig};
use nym_authenticator_client::{
AuthClientMixnetListener, AuthClientMixnetListenerHandle, AuthenticatorClient,
};
@@ -36,29 +36,6 @@ mod common;
pub use common::types;
pub mod config;
#[derive(Debug, Clone, Copy)]
pub enum AgentPortsSchedule {
NsAgent { last_ports_check_utc: Option<i64> },
}
// In-probe fallback TTL for the exit-policy port scan. It only fires
// when the regular probe runs and finds the gateway hasn't been scanned recently.
const PORTS_CHECK_FALLBACK_TTL_SECS: i64 = 14 * 24 * 60 * 60;
fn exit_policy_ports_check_due(last_ports_check_utc: Option<i64>, now: i64) -> bool {
match last_ports_check_utc {
None => true,
Some(ts) => now.saturating_sub(ts) >= PORTS_CHECK_FALLBACK_TTL_SECS,
}
}
fn unix_timestamp_secs() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
pub struct Probe {
/// Entry node
entry_node: TestedNodeDetails,
@@ -335,7 +312,6 @@ impl Probe {
pub async fn probe_run_agent(
mut self,
credential_args: CredentialArgs,
ports_schedule: Option<AgentPortsSchedule>,
) -> anyhow::Result<ProbeResult> {
let storage = Ephemeral::default();
@@ -389,8 +365,7 @@ impl Probe {
None
};
self.do_probe_test(mixnet_client, bandwidth_provider, ports_schedule)
.await
self.do_probe_test(mixnet_client, bandwidth_provider).await
}
/// Run a probe on unannounced gateway(s) some tests will not be available
@@ -428,7 +403,7 @@ impl Probe {
nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?;
}
self.do_probe_test(None, bandwidth_provider, None).await
self.do_probe_test(None, bandwidth_provider).await
}
pub async fn probe_run(
@@ -496,8 +471,7 @@ impl Probe {
None
};
self.do_probe_test(mixnet_client, bandwidth_provider, None)
.await
self.do_probe_test(mixnet_client, bandwidth_provider).await
}
pub async fn run_ports(
@@ -673,7 +647,6 @@ impl Probe {
self,
mixnet_client: Option<nym_sdk::Result<MixnetClient>>,
bandwith_provider: Box<dyn BandwidthTicketProvider>,
ports_schedule: Option<AgentPortsSchedule>,
) -> anyhow::Result<ProbeResult> {
// Setup exit node
let entry_under_test = self.exit_node.is_none();
@@ -808,87 +781,6 @@ impl Probe {
// Add wg results to probe result
probe_result.outcome.wg = Some(outcome);
if let Some(AgentPortsSchedule::NsAgent {
last_ports_check_utc,
}) = ports_schedule
{
let now = unix_timestamp_secs();
if exit_policy_ports_check_due(last_ports_check_utc, now) {
if let Some(ref wg1) = probe_result.outcome.wg {
if wg1.can_register {
info!(
"Running scheduled exit-policy port scan (stale or unset last_ports_check_utc)"
);
let mut netstack_ports = self.config.netstack_args.clone();
netstack_ports.port_check_ports = EXIT_POLICY_PORTS.to_vec();
let credential2 = bandwith_provider
.get_ecash_ticket(wg_ticket_type, credential_provider, 1)
.await?
.data;
let mut rng2 = rand::thread_rng();
let auth_client2 = AuthenticatorClient::new(
mixnet_listener_task.subscribe(),
mixnet_listener_task.mixnet_sender(),
nym_address,
authenticator,
exit_node.authenticator_version,
Arc::new(x25519::KeyPair::new(&mut rng2)),
ip_address,
);
match wg_probe(
auth_client2,
ip_address,
exit_node.authenticator_version,
self.config.amnezia_args.clone(),
netstack_ports,
true,
credential2,
)
.await
{
Ok(scan) => {
if let Some(ref mut wg) = probe_result.outcome.wg {
wg.port_check_results =
scan.port_check_results.clone();
}
probe_result.ports_check = Some(
match &scan.port_check_results {
Some(m) if !m.is_empty() => {
PortsCheckSummary::from_port_map(
scan.can_register,
m,
)
}
_ => PortsCheckSummary::probe_error(
scan.can_register,
"exit-policy port scan returned no per-port data",
),
},
);
}
Err(err) => {
warn!("Scheduled exit-policy port scan failed: {err}");
probe_result.ports_check =
Some(PortsCheckSummary::probe_error(
false,
format!(
"exit-policy port scan failed: {err:#}"
),
));
}
}
}
}
} else {
trace!(
"Skipping exit-policy port scan: checked within the last 14 days"
);
}
}
mixnet_listener_task.stop().await;
} else {
warn!("Not enough information to run WireGuard via mixnet registration tests");
+1 -1
View File
@@ -348,7 +348,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeOutput> {
let trial =
nym_gateway_probe::Probe::new_for_agent(entry_gateway, network, probe_config)
.await?;
Box::pin(trial.probe_run_agent(credential_args, None))
Box::pin(trial.probe_run_agent(credential_args))
.await
.map(ProbeOutput::Standard)
}
@@ -1,7 +1,6 @@
use crate::cli::ServerConfig;
use crate::cli::common;
use crate::log_capture::LogCapture;
use nym_gateway_probe::AgentPortsSchedule;
use tracing::instrument;
pub(crate) async fn run_probe(
@@ -49,10 +48,7 @@ pub(crate) async fn run_probe(
// Run the probe, capturing all tracing output
log_capture.start();
let ports_schedule = Some(AgentPortsSchedule::NsAgent {
last_ports_check_utc: testrun.assignment.last_ports_check_utc,
});
let probe_result = Box::pin(probe.probe_run_agent(credentials_args, ports_schedule)).await?;
let probe_result = Box::pin(probe.probe_run_agent(credentials_args)).await?;
let probe_log = log_capture.stop_and_drain();
// Inspect the probe output for socks5 field
@@ -177,12 +177,11 @@ pub(crate) struct TicketbookArgs {
pub(crate) quorum_check_interval: Duration,
/// Specify types of tickets to buffer
/// one V1WireguardEntry for wg test, additional for lp tests,
/// one V1WireguardExit for the wg exit test, additional for the in-probe exit-policy port scan
/// one V1WireguardEntry for wg test, additional for lp tests
#[clap(
long,
env = "NYM_NODE_STATUS_BUFFERED_TICKET_TYPES",
default_values_t = [TicketType::V1MixnetEntry, TicketType::V1WireguardEntry, TicketType::V1WireguardExit, TicketType::V1WireguardEntry, TicketType::V1WireguardExit]
default_values_t = [TicketType::V1MixnetEntry, TicketType::V1WireguardEntry, TicketType::V1WireguardExit, TicketType::V1WireguardEntry]
)]
#[arg(value_delimiter = ',')]
pub(crate) buffered_ticket_types: Vec<TicketType>,
@@ -38,6 +38,103 @@ fn strip_wg_port_check_results_from_last_probe(value: &mut serde_json::Value) {
wg.remove("port_check_results");
}
fn build_ports_check_summary_json(
can_register: bool,
port_check_target: Option<String>,
ports: Option<&serde_json::Map<String, serde_json::Value>>,
error: Option<String>,
) -> serde_json::Value {
let failed_ports = ports
.map(|ports| {
ports
.iter()
.filter_map(|(port, open)| open.as_bool().filter(|is_open| !is_open).map(|_| port))
.cloned()
.collect::<Vec<_>>()
})
.unwrap_or_default();
let has_ports = ports.is_some_and(|p| !p.is_empty());
let all_pass = can_register && failed_ports.is_empty() && has_ports;
serde_json::json!({
"all_pass": all_pass,
"error": error,
"port_check_target": port_check_target,
"failed_ports": failed_ports,
})
}
pub(crate) fn ports_check_summary_json_from_result(
result: &nym_gateway_probe::PortCheckResult,
) -> serde_json::Value {
let ports = result
.ports
.iter()
.map(|(k, v)| (k.clone(), serde_json::Value::Bool(*v)))
.collect::<serde_json::Map<_, _>>();
build_ports_check_summary_json(
result.can_register,
Some(result.port_check_target.clone()),
Some(&ports),
result.error.clone(),
)
}
pub(crate) fn normalize_ports_check_payload(value: serde_json::Value) -> Option<serde_json::Value> {
let serde_json::Value::Object(map) = value else {
return None;
};
// New shape is already in place; pass through untouched.
if map.contains_key("all_pass")
&& map.contains_key("error")
&& map.contains_key("port_check_target")
&& map.contains_key("failed_ports")
{
return Some(serde_json::Value::Object(map));
}
// Legacy dedicated shape: { gateway, can_register, port_check_target, ports, error }
if map.contains_key("can_register") && map.contains_key("ports") {
let can_register = map
.get("can_register")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let port_check_target = map
.get("port_check_target")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned);
let ports = map.get("ports").and_then(serde_json::Value::as_object);
let error = map
.get("error")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned);
return Some(build_ports_check_summary_json(
can_register,
port_check_target,
ports,
error,
));
}
if map.contains_key("all_pass") && map.contains_key("failed_ports") {
let mut normalized = map;
normalized.remove("ports_tested");
normalized
.entry("port_check_target".to_string())
.or_insert(serde_json::Value::Null);
normalized
.entry("error".to_string())
.or_insert(serde_json::Value::Null);
return Some(serde_json::Value::Object(normalized));
}
Some(serde_json::Value::Object(map))
}
macro_rules! serialize_opt_to_value {
($var:expr) => {{
match $var {
@@ -119,7 +216,8 @@ impl TryFrom<GatewayDto> for http::models::Gateway {
.ports_check
.clone()
.or(nested_ports)
.filter(|v| !v.is_null());
.filter(|v| !v.is_null())
.and_then(normalize_ports_check_payload);
let last_ports_check_utc = value
.last_ports_check_utc
.map(unix_timestamp_to_utc_rfc3339);
@@ -231,9 +231,10 @@ pub(crate) async fn update_gateway_ports_check_only(
gateway_pk: i32,
port_check_result: &nym_gateway_probe::PortCheckResult,
) -> anyhow::Result<()> {
use crate::db::models::ports_check_summary_json_from_result;
let now_ts = now_utc().unix_timestamp();
let value = serde_json::to_value(port_check_result)
.map_err(|e| anyhow::anyhow!("Invalid port_check_result JSON: {e}"))?;
let value = ports_check_summary_json_from_result(port_check_result);
sqlx::query(
r#"UPDATE gateways SET
@@ -292,8 +293,7 @@ pub(crate) async fn insert_external_ports_check_testrun(
pub(crate) async fn enqueue_due_ports_check_testruns(db: &DbPool) -> anyhow::Result<u64> {
let mut conn = db.acquire().await?;
let now = now_utc().unix_timestamp();
// 14 days soft TTL — must match the in-probe fallback TTL in
// `nym_gateway_probe::exit_policy_ports_check_due`.
// 14 days soft TTL for dedicated ports-check queueing.
let cutoff = now - time::Duration::days(14).whole_seconds();
let res = sqlx::query!(
@@ -76,6 +76,101 @@ mod db_tests {
assert_eq!(http_gateway.last_probe_result, Some(probe));
}
#[test]
fn test_gateway_dto_normalizes_legacy_dedicated_ports_check_shape() {
let legacy_ports = serde_json::json!({
"gateway": "gw1",
"can_register": true,
"port_check_target": "portquiz.net",
"ports": {
"20": false,
"21": true,
"43": false
},
"error": null
});
let gateway_dto = crate::db::models::GatewayDto {
gateway_identity_key: "id1".to_string(),
bonded: true,
performance: 50,
self_described: Some("{}".to_string()),
explorer_pretty_bond: Some("{}".to_string()),
last_probe_result: None,
last_probe_log: None,
ports_check: Some(legacy_ports),
last_ports_check_utc: Some(1672531200),
last_testrun_utc: None,
last_updated_utc: 1672531200,
moniker: "m".to_string(),
security_contact: "c".to_string(),
details: "d".to_string(),
website: "w".to_string(),
bridges: None,
};
let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap();
let normalized = http_gateway.ports_check.unwrap();
assert_eq!(
normalized.get("all_pass"),
Some(&serde_json::Value::Bool(false))
);
assert_eq!(
normalized.get("port_check_target"),
Some(&serde_json::Value::String("portquiz.net".to_string()))
);
assert_eq!(normalized.get("error"), Some(&serde_json::Value::Null));
assert_eq!(
normalized.get("failed_ports"),
Some(&serde_json::json!(["20", "43"]))
);
assert!(normalized.get("gateway").is_none());
assert!(normalized.get("ports").is_none());
}
#[test]
fn test_gateway_dto_normalizes_legacy_in_probe_summary_shape() {
let legacy_summary = serde_json::json!({
"all_pass": true,
"failed_ports": [],
"error": null,
"ports_tested": 32
});
let gateway_dto = crate::db::models::GatewayDto {
gateway_identity_key: "id2".to_string(),
bonded: true,
performance: 50,
self_described: Some("{}".to_string()),
explorer_pretty_bond: Some("{}".to_string()),
last_probe_result: None,
last_probe_log: None,
ports_check: Some(legacy_summary),
last_ports_check_utc: Some(1672531200),
last_testrun_utc: None,
last_updated_utc: 1672531200,
moniker: "m".to_string(),
security_contact: "c".to_string(),
details: "d".to_string(),
website: "w".to_string(),
bridges: None,
};
let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap();
let normalized = http_gateway.ports_check.unwrap();
assert_eq!(
normalized.get("all_pass"),
Some(&serde_json::Value::Bool(true))
);
assert_eq!(normalized.get("failed_ports"), Some(&serde_json::json!([])));
assert_eq!(normalized.get("error"), Some(&serde_json::Value::Null));
assert_eq!(
normalized.get("port_check_target"),
Some(&serde_json::Value::Null)
);
assert!(normalized.get("ports_tested").is_none());
}
#[test]
fn test_mixnode_dto_try_from() {
let mixnode_dto = crate::db::models::MixnodeDto {
@@ -1,5 +1,5 @@
use crate::db::DbConnection;
use crate::db::models::{TestRunDto, TestRunStatus};
use crate::db::models::{TestRunDto, TestRunKind, TestRunStatus};
use crate::db::queries;
use crate::utils::{now_utc, unix_timestamp_to_utc_rfc3339};
use crate::{
@@ -422,6 +422,18 @@ async fn submit_ports_check_testrun_v2(
match queries::testruns::get_testrun_by_id(&mut conn, submitted_testrun_id).await {
Ok(testrun) => {
if testrun.kind != TestRunKind::PortsCheck as i16 {
tracing::warn!(
"Testrun {} has wrong kind for ports-check submit: {}",
submitted_testrun_id,
testrun.kind
);
return Err(HttpError::invalid_input(format!(
"Testrun {} is not a ports-check run",
submitted_testrun_id
)));
}
let gw_identity = queries::select_gateway_identity(&mut conn, testrun.gateway_id)
.await
.map_err(HttpError::internal_with_logging)?;
@@ -53,8 +53,6 @@ impl Default for TicketbookManagerConfig {
V1WireguardExit,
// additional wg entry for lp tests
V1WireguardEntry,
// additional wg exit for the in-probe exit-policy port scan
V1WireguardExit,
],
}
}