Remove in-prove
remove in-probe test, it isn't needed.
This commit is contained in:
@@ -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![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user