diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ed1bb017..e21f10f884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,52 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2026.5-raclette] (2026-03-10) + +- bugfix: correctly populate gateway probe LP data ([#6533]) +- chore: introduce additional prometheus metrics for registration times ([#6532]) +- bugfix: lp information to have proper snake_case on API endpoints ([#6531]) +- removed redundant LP states ([#6509]) +- chore: removed all matrix notifications from github actions ([#6495]) +- feat: Lewes Protocol with PSQv2 ([#6491]) +- build(deps): bump minimatch from 3.1.2 to 3.1.4 in /documentation/docs ([#6486]) +- build(deps): bump bn.js from 4.12.2 to 4.12.3 in /documentation/docs ([#6484]) +- build(deps): bump bn.js from 4.12.2 to 4.12.3 ([#6483]) +- build(deps): bump ajv from 8.17.1 to 8.18.0 in /clients/native/examples/js-examples/websocket ([#6478]) +- build(deps): bump ajv from 6.12.6 to 6.14.0 in /documentation/docs ([#6477]) +- build(deps): bump minimatch and glob in /documentation/scripts/post-process ([#6476]) +- build(deps): bump hono from 4.11.9 to 4.12.0 ([#6475]) +- build(deps): bump keccak from 0.1.5 to 0.1.6 ([#6472]) +- build(deps-dev): bump qs from 6.14.1 to 6.14.2 in /clients/native/examples/js-examples/websocket ([#6466]) +- build(deps): bump mikefarah/yq from 4.52.2 to 4.52.4 ([#6465]) +- Otel minimal v2 ([#6464]) +- build(deps): bump qs and express in /wasm/client/internal-dev ([#6461]) +- bugfix: restore 'latest_measurement' field for nym-node /verloc endpoint ([#6452]) +- build(deps-dev): bump webpack from 5.77.0 to 5.104.1 in /wasm/node-tester/internal-dev ([#6451]) +- Max/mixfetch concurrent test ([#6417]) + +[#6533]: https://github.com/nymtech/nym/pull/6533 +[#6532]: https://github.com/nymtech/nym/pull/6532 +[#6531]: https://github.com/nymtech/nym/pull/6531 +[#6509]: https://github.com/nymtech/nym/pull/6509 +[#6495]: https://github.com/nymtech/nym/pull/6495 +[#6491]: https://github.com/nymtech/nym/pull/6491 +[#6486]: https://github.com/nymtech/nym/pull/6486 +[#6484]: https://github.com/nymtech/nym/pull/6484 +[#6483]: https://github.com/nymtech/nym/pull/6483 +[#6478]: https://github.com/nymtech/nym/pull/6478 +[#6477]: https://github.com/nymtech/nym/pull/6477 +[#6476]: https://github.com/nymtech/nym/pull/6476 +[#6475]: https://github.com/nymtech/nym/pull/6475 +[#6472]: https://github.com/nymtech/nym/pull/6472 +[#6466]: https://github.com/nymtech/nym/pull/6466 +[#6465]: https://github.com/nymtech/nym/pull/6465 +[#6464]: https://github.com/nymtech/nym/pull/6464 +[#6461]: https://github.com/nymtech/nym/pull/6461 +[#6452]: https://github.com/nymtech/nym/pull/6452 +[#6451]: https://github.com/nymtech/nym/pull/6451 +[#6417]: https://github.com/nymtech/nym/pull/6417 + ## [2026.4-quark] (2026-02-24) - Enhance CI workflow with feature inputs ([#6462]) diff --git a/Cargo.lock b/Cargo.lock index e7bdcdd1a4..22e7b11d6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6520,6 +6520,7 @@ dependencies = [ "rand 0.8.5", "rand 0.9.2", "reqwest 0.13.1", + "semver 1.0.26", "serde", "serde_json", "time", diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 9aae772a8f..19f936b93b 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub use crate::ip_pool::IpPair; use crate::ip_pool::allocated_ip_pair; use crate::{ IpPool, @@ -23,6 +24,7 @@ use nym_credentials_interface::CredentialSpendingData; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_node_metrics::NymNodeMetrics; +use nym_node_metrics::prometheus_wrapper::{PROMETHEUS_METRICS, PrometheusMetric}; use nym_wireguard_types::{ DEFAULT_IP_CLEANUP_INTERVAL, DEFAULT_IP_STALE_AGE, DEFAULT_PEER_TIMEOUT_CHECK, }; @@ -35,8 +37,6 @@ use tokio::sync::{RwLock, mpsc}; use tokio_stream::{StreamExt, wrappers::IntervalStream}; use tracing::{debug, error, info, trace}; -pub use crate::ip_pool::IpPair; - #[cfg(feature = "mock")] pub mod mock; @@ -261,6 +261,10 @@ impl PeerController { } async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> { + // observation will get automatically added once dropped + let _metric_timer = + PROMETHEUS_METRICS.start_timer(PrometheusMetric::WireguardDefguardPeerCreation); + nym_metrics::inc!("wg_peer_addition_attempts"); // confirm ip allocation so that it wouldn't be released for as long as the peer exists diff --git a/gateway/src/node/wireguard/new_peer_registration/mod.rs b/gateway/src/node/wireguard/new_peer_registration/mod.rs index b2f89accf7..ddb53b5517 100644 --- a/gateway/src/node/wireguard/new_peer_registration/mod.rs +++ b/gateway/src/node/wireguard/new_peer_registration/mod.rs @@ -32,6 +32,7 @@ use nym_crypto::asymmetric::x25519; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_storage::models::PersistedBandwidth; use nym_lp::peer_config::LpReceiverIndex; +use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS}; use nym_registration_common::dvpn::{ LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, }; @@ -126,6 +127,9 @@ impl PeerRegistrator { credential: CredentialSpendingData, client_id: i64, ) -> Result { + let _metric_timer = PROMETHEUS_METRICS + .start_timer(PrometheusMetric::PeerRegistrationCredentialVerification); + let bandwidth = self.credential_storage_preparation(client_id).await?; let client_bandwidth = ClientBandwidth::new(bandwidth.into()); let mut verifier = CredentialVerifier::new( @@ -232,6 +236,9 @@ impl PeerRegistrator { request_id: u64, reply_to: Option, ) -> Result { + let _metric_timer = PROMETHEUS_METRICS + .start_timer(PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg1); + let remote_public = init_message.pub_key(); // 1. check if there's any pending registration already in progress, @@ -269,6 +276,9 @@ impl PeerRegistrator { request_id: u64, reply_to: Option, ) -> Result { + let _metric_timer = PROMETHEUS_METRICS + .start_timer(PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg2); + let peer = final_message.gateway_client_pub_key(); // 1. check if there's any pending registration associated with this peer let pending_data = self @@ -312,6 +322,9 @@ impl PeerRegistrator { init_msg: LpDvpnRegistrationInitialRequest, receiver_index: LpReceiverIndex, ) -> Result { + let _metric_timer = + PROMETHEUS_METRICS.start_timer(PrometheusMetric::DvpnLpClientRegistrationMsg1); + let remote_public = init_msg.wg_public_key; let psk = Key::new(init_msg.psk); @@ -343,6 +356,9 @@ impl PeerRegistrator { final_msg: LpDvpnRegistrationFinalisation, receiver_index: LpReceiverIndex, ) -> Result { + let _metric_timer = + PROMETHEUS_METRICS.start_timer(PrometheusMetric::DvpnLpClientRegistrationMsg2); + // 1. check if there's any pending registration associated with this peer let pending_data = self .pending_registrations diff --git a/nym-api/nym-api-requests/src/models/described/type_translation.rs b/nym-api/nym-api-requests/src/models/described/type_translation.rs index 37653a2d14..2cbc660aa2 100644 --- a/nym-api/nym-api-requests/src/models/described/type_translation.rs +++ b/nym-api/nym-api-requests/src/models/described/type_translation.rs @@ -296,6 +296,7 @@ fn translate_digests( ToSchema, Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[non_exhaustive] pub enum LPKEM { @@ -319,6 +320,7 @@ pub enum LPKEM { ToSchema, Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[non_exhaustive] pub enum LPHashFunction { @@ -343,6 +345,7 @@ pub enum LPHashFunction { EnumString, ToSchema, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[non_exhaustive] pub enum LPSignatureScheme { diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index b18927be15..7cebd4f98b 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -26,6 +26,7 @@ rand09.workspace = true reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true +semver.workspace = true time = { workspace = true } tokio = { workspace = true, features = [ "process", diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 04b2b10cac..c36daff81b 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -12,6 +12,7 @@ use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_http_api_client::UserAgent; use nym_kkt_ciphersuite::Ciphersuite; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use nym_lp::packet::version; use nym_lp::peer::{DHPublicKey, LpRemotePeer}; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; @@ -129,16 +130,32 @@ impl DirectoryNode { .copied() .ok_or_else(|| anyhow!("no ip address known"))?; - // let lp_data = description.lewes_protocol.as_ref().and_then(|lp_data| { - // Some(TestedNodeLpDetails { - // address: SocketAddr::new(ip_address, lp_data.control_port), - // expected_kem_key_hashes: lp_data.kem_keys().ok()?, - // expected_signing_key_hashes: lp_data.signing_keys().ok()?, - // x25519: lp_data.x25519, - // // \/ TODO: proper derivation from build version - // lp_version: version::CURRENT, - // }) - // }); + let identity = self.identity(); + + let lp_data = match description.lewes_protocol.as_ref() { + Some(lp_data) => { + // 1. verify the signature, if it's invalid, completely bail on the node + if !lp_data.verify(&identity) { + warn!("{identity} provided malformed lp data"); + bail!("{identity} provided malformed lp data"); + } + + let version: semver::Version = + description.build_information.build_version.parse()?; + let Some(ciphersuite) = Ciphersuite::from_node_version(version) else { + bail!("failed to identify valid ciphersuite for {identity}"); + }; + + Some(TestedNodeLpDetails { + address: SocketAddr::new(ip_address, lp_data.content.control_port), + expected_kem_key_hashes: lp_data.content.kem_keys()?, + x25519: lp_data.content.x25519, + lp_version: version::CURRENT, + ciphersuite, + }) + } + None => None, + }; Ok(TestedNodeDetails { identity: self.identity(), @@ -147,7 +164,7 @@ impl DirectoryNode { authenticator_address, authenticator_version, ip_address: Some(ip_address), - lp_data: None, + lp_data, }) } } diff --git a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs index 4102e82c98..ffdb66e5c0 100644 --- a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs +++ b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs @@ -24,6 +24,18 @@ const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[ 259200., // 72h+ (implicitly) ]; +const REG_LATENCY_BUCKETS: &[f64] = &[ + 0.001, // 1ms + 0.005, // 5ms + 0.01, // 10ms + 0.05, // 50ms + 0.1, // 100ms + 0.25, // 250ms + 0.5, // 500ms + 1.0, // 1s + 2.0, // 2s +]; + #[derive(Clone, Debug, EnumIter, Display, EnumProperty, EnumCount, Eq, Hash, PartialEq)] #[strum(serialize_all = "snake_case", prefix = "nym_node_")] pub enum PrometheusMetric { @@ -139,6 +151,34 @@ pub enum PrometheusMetric { #[strum(props(help = "The current receiving rate of wireguard"))] WireguardBytesRxRate, + #[strum(props(help = "The distribution of defguard peer creation time"))] + WireguardDefguardPeerCreation, + + #[strum(props( + help = "The distribution of time it takes to verify a credential during peer registration" + ))] + PeerRegistrationCredentialVerification, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the authenticator for msg1" + ))] + DvpnAuthenticatorClientRegistrationMsg1, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the authenticator for msg2" + ))] + DvpnAuthenticatorClientRegistrationMsg2, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the lp for msg1" + ))] + DvpnLpClientRegistrationMsg1, + + #[strum(props( + help = "The distribution of time for client dvpn registration through the lp for msg2" + ))] + DvpnLpClientRegistrationMsg2, + // # NETWORK #[strum(props(help = "The number of active ingress mixnet connections"))] NetworkActiveIngressMixnetConnections, @@ -271,6 +311,24 @@ impl PrometheusMetric { PrometheusMetric::WireguardActivePeers => Metric::new_int_gauge(&name, help), PrometheusMetric::WireguardBytesTxRate => Metric::new_float_gauge(&name, help), PrometheusMetric::WireguardBytesRxRate => Metric::new_float_gauge(&name, help), + PrometheusMetric::WireguardDefguardPeerCreation => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg1 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg2 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnLpClientRegistrationMsg1 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::DvpnLpClientRegistrationMsg2 => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } + PrometheusMetric::PeerRegistrationCredentialVerification => { + Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS)) + } PrometheusMetric::NetworkActiveIngressMixnetConnections => { Metric::new_int_gauge(&name, help) } @@ -388,7 +446,7 @@ mod tests { // a sanity check for anyone adding new metrics. if this test fails, // make sure any methods on `PrometheusMetric` enum don't need updating // or require custom Display impl - assert_eq!(39, PrometheusMetric::COUNT) + assert_eq!(45, PrometheusMetric::COUNT) } #[test] diff --git a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs index fe1ea68bd5..9c8104e494 100644 --- a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs @@ -71,6 +71,7 @@ impl LewesProtocol { EnumString, Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPKEM { @@ -112,6 +113,7 @@ impl From for LPKEM { EnumIter, Ord, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPHashFunction { @@ -158,6 +160,7 @@ impl From for LPHashFunction { EnumString, EnumIter, )] +#[serde(rename_all = "snake_case")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub enum LPSignatureScheme {