diff --git a/.gitignore b/.gitignore index 597794c0e3..8953d2a2da 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ ppa-private-key.b64 ppa-private-key.asc nym-network-monitor/topology.json nym-network-monitor/__pycache__ -nym-network-monitor/*.key \ No newline at end of file +nym-network-monitor/*.key +nym-network-monitor/.envrc diff --git a/Cargo.lock b/Cargo.lock index c7c85e0d9e..2fd2fbf19c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5782,6 +5782,7 @@ dependencies = [ "nym-bin-common", "nym-client-core", "nym-crypto", + "nym-gateway-requests", "nym-network-defaults", "nym-sdk", "nym-sphinx", diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 839df3ec90..bb0ffd5d03 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -32,7 +32,7 @@ use crate::init::{ setup_gateway, types::{GatewaySetup, InitialisationResult}, }; -use crate::{config, spawn_future}; +use crate::{config, spawn_future, ForgetMe}; use futures::channel::mpsc; use log::*; use nym_bandwidth_controller::BandwidthController; @@ -191,6 +191,8 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> { #[cfg(unix)] connection_fd_callback: Option>, + + forget_me: ForgetMe, } impl<'a, C, S> BaseClientBuilder<'a, C, S> @@ -215,9 +217,16 @@ where setup_method: GatewaySetup::MustLoad { gateway_id: None }, #[cfg(unix)] connection_fd_callback: None, + forget_me: Default::default(), } } + #[must_use] + pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self { + self.forget_me = forget_me.clone(); + self + } + #[must_use] pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self { self.setup_method = setup; @@ -636,9 +645,11 @@ where fn start_mix_traffic_controller( gateway_transceiver: Box, shutdown: TaskClient, + forget_me: ForgetMe, ) -> BatchMixMessageSender { info!("Starting mix traffic controller..."); - let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver); + let (mix_traffic_controller, mix_tx) = + MixTrafficController::new(gateway_transceiver, forget_me); mix_traffic_controller.start_with_shutdown(shutdown); mix_tx } @@ -820,9 +831,11 @@ where // that are to be sent to the mixnet. They are used by cover traffic stream and real // traffic stream. // The MixTrafficController then sends the actual traffic + let message_sender = Self::start_mix_traffic_controller( gateway_transceiver, shutdown.fork("mix_traffic_controller"), + self.forget_me, ); // Channels that the websocket listener can use to signal downstream to the real traffic diff --git a/common/client-core/src/client/mix_traffic/mod.rs b/common/client-core/src/client/mix_traffic/mod.rs index 91c652efba..fa73cc82a8 100644 --- a/common/client-core/src/client/mix_traffic/mod.rs +++ b/common/client-core/src/client/mix_traffic/mod.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::mix_traffic::transceiver::GatewayTransceiver; -use crate::spawn_future; +use crate::{spawn_future, ForgetMe}; use log::*; +use nym_gateway_requests::ClientRequest; use nym_sphinx::forwarding::packet::MixPacket; pub type BatchMixMessageSender = tokio::sync::mpsc::Sender>; @@ -26,10 +27,14 @@ pub struct MixTrafficController { // TODO: this is temporary work-around. // in long run `gateway_client` will be moved away from `MixTrafficController` anyway. consecutive_gateway_failure_count: usize, + forget_me: ForgetMe, } impl MixTrafficController { - pub fn new(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender) + pub fn new( + gateway_transceiver: T, + forget_me: ForgetMe, + ) -> (MixTrafficController, BatchMixMessageSender) where T: GatewayTransceiver + Send + 'static, { @@ -40,6 +45,7 @@ impl MixTrafficController { gateway_transceiver: Box::new(gateway_transceiver), mix_rx: message_receiver, consecutive_gateway_failure_count: 0, + forget_me, }, message_sender, ) @@ -47,6 +53,7 @@ impl MixTrafficController { pub fn new_dynamic( gateway_transceiver: Box, + forget_me: ForgetMe, ) -> (MixTrafficController, BatchMixMessageSender) { let (message_sender, message_receiver) = tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); @@ -55,6 +62,7 @@ impl MixTrafficController { gateway_transceiver, mix_rx: message_receiver, consecutive_gateway_failure_count: 0, + forget_me, }, message_sender, ) @@ -111,7 +119,27 @@ impl MixTrafficController { } } shutdown.recv_timeout().await; + + if self.forget_me.any() { + log::info!("Sending forget me request to the gateway"); + match self + .gateway_transceiver + .send_client_request(ClientRequest::ForgetMe { + client: self.forget_me.client(), + stats: self.forget_me.stats(), + }) + .await + { + Ok(_) => { + log::info!("Successfully sent forget me request to the gateway"); + } + Err(err) => { + log::error!("Failed to send forget me request to the gateway: {err}"); + } + } + } + log::debug!("MixTrafficController: Exiting"); - }) + }); } } diff --git a/common/client-core/src/client/mix_traffic/transceiver.rs b/common/client-core/src/client/mix_traffic/transceiver.rs index 6d9b4fa6de..77c226d245 100644 --- a/common/client-core/src/client/mix_traffic/transceiver.rs +++ b/common/client-core/src/client/mix_traffic/transceiver.rs @@ -5,8 +5,10 @@ use async_trait::async_trait; use log::{debug, error}; use nym_credential_storage::storage::Storage as CredentialStorage; use nym_crypto::asymmetric::identity; +use nym_gateway_client::error::GatewayClientError; use nym_gateway_client::GatewayClient; pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter}; +use nym_gateway_requests::ClientRequest; use nym_sphinx::forwarding::packet::MixPacket; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use std::fmt::Debug; @@ -26,9 +28,14 @@ fn erase_err(err: E) -> ErasedGate } /// This combines combines the functionalities of being able to send and receive mix packets. +#[async_trait] pub trait GatewayTransceiver: GatewaySender + GatewayReceiver { fn gateway_identity(&self) -> identity::PublicKey; fn ws_fd(&self) -> Option; + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError>; } /// This trait defines the functionality of sending `MixPacket` into the mixnet, @@ -65,6 +72,7 @@ pub trait GatewayReceiver { } // to allow for dynamic dispatch +#[async_trait] impl GatewayTransceiver for Box { #[inline] fn gateway_identity(&self) -> identity::PublicKey { @@ -73,6 +81,13 @@ impl GatewayTransceiver for Box { fn ws_fd(&self) -> Option { (**self).ws_fd() } + + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError> { + (**self).send_client_request(message).await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -91,7 +106,6 @@ impl GatewaySender for Box { (**self).batch_send_mix_packets(packets).await } } - impl GatewayReceiver for Box { #[inline] fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> { @@ -111,6 +125,7 @@ impl RemoteGateway { } } +#[async_trait] impl GatewayTransceiver for RemoteGateway where C: DkgQueryClient + Send + Sync, @@ -123,6 +138,20 @@ where fn ws_fd(&self) -> Option { self.gateway_client.ws_fd() } + + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError> { + if let Some(shared_key) = self.gateway_client.shared_key() { + self.gateway_client + .send_websocket_message(message.encrypt(&*shared_key)?) + .await?; + Ok(()) + } else { + Err(GatewayClientError::ConnectionInInvalidState) + } + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -195,6 +224,7 @@ impl LocalGateway { mod nonwasm_sealed { use super::*; + #[async_trait] impl GatewayTransceiver for LocalGateway { fn gateway_identity(&self) -> identity::PublicKey { self.local_identity @@ -202,6 +232,13 @@ mod nonwasm_sealed { fn ws_fd(&self) -> Option { None } + + async fn send_client_request( + &mut self, + _message: ClientRequest, + ) -> Result<(), GatewayClientError> { + Ok(()) + } } #[async_trait] @@ -269,6 +306,7 @@ impl GatewaySender for MockGateway { } } +#[async_trait] impl GatewayTransceiver for MockGateway { fn gateway_identity(&self) -> identity::PublicKey { self.dummy_identity @@ -276,4 +314,11 @@ impl GatewayTransceiver for MockGateway { fn ws_fd(&self) -> Option { None } + + async fn send_client_request( + &mut self, + _message: ClientRequest, + ) -> Result<(), GatewayClientError> { + Ok(()) + } } diff --git a/common/client-core/src/lib.rs b/common/client-core/src/lib.rs index ffa1402859..12ea3f7d5c 100644 --- a/common/client-core/src/lib.rs +++ b/common/client-core/src/lib.rs @@ -34,3 +34,48 @@ where { tokio::spawn(future); } + +#[derive(Clone, Default, Debug)] +pub struct ForgetMe { + client: bool, + stats: bool, +} + +impl ForgetMe { + pub fn new_all() -> Self { + Self { + client: true, + stats: true, + } + } + + pub fn new_client() -> Self { + Self { + client: true, + stats: false, + } + } + + pub fn new_stats() -> Self { + Self { + client: false, + stats: true, + } + } + + pub fn new(client: bool, stats: bool) -> Self { + Self { client, stats } + } + + pub fn any(&self) -> bool { + self.client || self.stats + } + + pub fn client(&self) -> bool { + self.client + } + + pub fn stats(&self) -> bool { + self.stats + } +} diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 481e57dd6f..26986e3df8 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -324,7 +324,7 @@ impl GatewayClient { // If we want to send a message (with response), we need to have a full control over the socket, // as we need to be able to write the request and read the subsequent response - async fn send_websocket_message( + pub async fn send_websocket_message( &mut self, msg: impl Into, ) -> Result { diff --git a/common/gateway-requests/src/types/text_request.rs b/common/gateway-requests/src/types/text_request.rs index 8be56bcfb3..72eebc3e17 100644 --- a/common/gateway-requests/src/types/text_request.rs +++ b/common/gateway-requests/src/types/text_request.rs @@ -20,6 +20,10 @@ pub enum ClientRequest { hkdf_salt: Vec, derived_key_digest: Vec, }, + ForgetMe { + client: bool, + stats: bool, + }, } impl ClientRequest { diff --git a/common/gateway-requests/src/types/text_response.rs b/common/gateway-requests/src/types/text_response.rs index b0c8250f1e..5c6ce668b5 100644 --- a/common/gateway-requests/src/types/text_response.rs +++ b/common/gateway-requests/src/types/text_response.rs @@ -11,6 +11,7 @@ use tungstenite::Message; #[non_exhaustive] pub enum SensitiveServerResponse { KeyUpgradeAck {}, + ForgetMeAck {}, } impl SensitiveServerResponse { diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index 74b45e9e7a..e57f7452cd 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -116,6 +116,16 @@ impl PersistentStatsStorage { .await?) } + pub async fn delete_unique_user( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), StatsStorageError> { + Ok(self + .session_manager + .delete_unique_user(client_address.as_base58_string()) + .await?) + } + pub async fn insert_active_session( &self, client_address: DestinationAddressBytes, diff --git a/common/gateway-stats-storage/src/sessions.rs b/common/gateway-stats-storage/src/sessions.rs index 920ee4d1d5..a919696967 100644 --- a/common/gateway-stats-storage/src/sessions.rs +++ b/common/gateway-stats-storage/src/sessions.rs @@ -71,6 +71,16 @@ impl SessionManager { Ok(()) } + pub(crate) async fn delete_unique_user(&self, client_address_b58: String) -> Result<()> { + sqlx::query!( + "DELETE FROM sessions_unique_users WHERE client_address = ?", + client_address_b58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + pub(crate) async fn get_unique_users(&self, date: Date) -> Result> { sqlx::query_scalar!( "SELECT client_address as count FROM sessions_unique_users WHERE day = ?", diff --git a/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json b/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json new file mode 100644 index 0000000000..8c8994d4ca --- /dev/null +++ b/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM message_store WHERE client_address_bs58 = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734" +} diff --git a/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json b/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json new file mode 100644 index 0000000000..bc58f0aa1b --- /dev/null +++ b/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM available_bandwidth WHERE client_id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118" +} diff --git a/common/gateway-storage/src/bandwidth.rs b/common/gateway-storage/src/bandwidth.rs index 1f1cdce1c8..be64357350 100644 --- a/common/gateway-storage/src/bandwidth.rs +++ b/common/gateway-storage/src/bandwidth.rs @@ -49,6 +49,16 @@ impl BandwidthManager { Ok(()) } + pub(crate) async fn remove_client(&self, client_id: i64) -> Result<(), sqlx::Error> { + sqlx::query!( + "DELETE FROM available_bandwidth WHERE client_id = ?", + client_id + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + /// Set the expiration date of the particular client to the provided date. pub(crate) async fn set_expiration( &self, diff --git a/common/gateway-storage/src/inboxes.rs b/common/gateway-storage/src/inboxes.rs index 6c1ac23c47..bdf798aa02 100644 --- a/common/gateway-storage/src/inboxes.rs +++ b/common/gateway-storage/src/inboxes.rs @@ -133,4 +133,17 @@ impl InboxManager { .await?; Ok(()) } + + pub(crate) async fn remove_messages_for_client( + &self, + client_address_bs58: &str, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "DELETE FROM message_store WHERE client_address_bs58 = ?", + client_address_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 7b0f70a5b3..9d574dbe91 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -41,6 +41,33 @@ pub struct GatewayStorage { } impl GatewayStorage { + #[allow(dead_code)] + pub(crate) fn client_manager(&self) -> &ClientManager { + &self.client_manager + } + + pub(crate) fn shared_key_manager(&self) -> &SharedKeysManager { + &self.shared_key_manager + } + + pub(crate) fn inbox_manager(&self) -> &InboxManager { + &self.inbox_manager + } + + pub(crate) fn bandwidth_manager(&self) -> &BandwidthManager { + &self.bandwidth_manager + } + + #[allow(dead_code)] + pub(crate) fn ticket_manager(&self) -> &TicketStorageManager { + &self.ticket_manager + } + + #[allow(dead_code)] + pub(crate) fn wireguard_peer_manager(&self) -> &wireguard_peers::WgPeerManager { + &self.wireguard_peer_manager + } + /// Initialises `PersistentStorage` using the provided path. /// /// # Arguments @@ -101,6 +128,21 @@ impl GatewayStorage { .await?) } + pub async fn handle_forget_me( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), GatewayStorageError> { + let client_id = self.get_mixnet_client_id(client_address).await?; + self.inbox_manager() + .remove_messages_for_client(&client_address.as_base58_string()) + .await?; + self.bandwidth_manager().remove_client(client_id).await?; + self.shared_key_manager() + .remove_shared_keys(&client_address.as_base58_string()) + .await?; + Ok(()) + } + pub async fn insert_shared_keys( &self, client_address: DestinationAddressBytes, diff --git a/common/statistics/src/gateways.rs b/common/statistics/src/gateways.rs index cc617ae027..4e8e701de1 100644 --- a/common/statistics/src/gateways.rs +++ b/common/statistics/src/gateways.rs @@ -58,6 +58,10 @@ pub enum GatewaySessionEvent { /// Address of the remote client opening the connection client: DestinationAddressBytes, }, + SessionDelete { + /// Address of the remote client opening the connection + client: DestinationAddressBytes, + }, } impl GatewaySessionEvent { @@ -87,4 +91,8 @@ impl GatewaySessionEvent { client, } } + + pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent { + GatewaySessionEvent::SessionDelete { client } + } } diff --git a/gateway/src/node/client_handling/websocket/common_state.rs b/gateway/src/node/client_handling/websocket/common_state.rs index 4ff8b8991a..19a5943c53 100644 --- a/gateway/src/node/client_handling/websocket/common_state.rs +++ b/gateway/src/node/client_handling/websocket/common_state.rs @@ -21,3 +21,9 @@ pub(crate) struct CommonHandlerState { pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) active_clients_store: ActiveClientsStore, } + +impl CommonHandlerState { + pub(crate) fn storage(&self) -> &GatewayStorage { + &self.storage + } +} diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index d7bc9dd32e..6c39b613ce 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -157,6 +157,10 @@ impl Drop for AuthenticatedHandler { } impl AuthenticatedHandler { + pub(crate) fn inner(&self) -> &FreshHandler { + &self.inner + } + /// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated /// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or /// sending sphinx packets. @@ -327,6 +331,24 @@ impl AuthenticatedHandler { } } + async fn handle_forget_me( + &mut self, + client: bool, + stats: bool, + ) -> Result { + if client { + self.inner() + .shared_state() + .storage() + .handle_forget_me(self.client.address) + .await?; + } + if stats { + self.send_metrics(GatewaySessionEvent::new_session_delete(self.client.address)); + } + Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?) + } + async fn handle_key_upgrade( &mut self, hkdf_salt: Vec, @@ -370,6 +392,7 @@ impl AuthenticatedHandler { hkdf_salt, derived_key_digest, } => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await, + ClientRequest::ForgetMe { client, stats } => self.handle_forget_me(client, stats).await, _ => Err(RequestHandlingError::UnknownEncryptedTextRequest), } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 188699c64c..818aea8fdb 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -114,6 +114,10 @@ pub(crate) struct FreshHandler { } impl FreshHandler { + pub(crate) fn shared_state(&self) -> &CommonHandlerState { + &self.shared_state + } + // for time being we assume handle is always constructed from raw socket. // if we decide we want to change it, that's not too difficult // also at this point I'm not entirely sure how to deal with this warning without diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index 3d80fed9a0..bd537d5483 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -33,6 +33,7 @@ nym-bin-common = { path = "../common/bin-common" } nym-client-core = { path = "../common/client-core" } nym-crypto = { path = "../common/crypto" } nym-network-defaults = { path = "../common/network-defaults" } +nym-gateway-requests = { path = "../common/gateway-requests" } nym-sdk = { path = "../sdk/rust/nym-sdk" } nym-sphinx = { path = "../common/nymsphinx" } nym-topology = { path = "../common/topology" } diff --git a/nym-network-monitor/src/main.rs b/nym-network-monitor/src/main.rs index 141944eb8a..76ec69440d 100644 --- a/nym-network-monitor/src/main.rs +++ b/nym-network-monitor/src/main.rs @@ -3,6 +3,7 @@ use accounting::submit_metrics; use anyhow::Result; use clap::Parser; use log::{info, warn}; +use nym_client_core::ForgetMe; use nym_crypto::asymmetric::ed25519::PrivateKey; use nym_network_defaults::setup_env; use nym_network_defaults::var_names::NYM_API; @@ -56,7 +57,11 @@ async fn make_clients( loop { if Arc::strong_count(&dropped_client) == 1 { if let Some(client) = Arc::into_inner(dropped_client) { - client.into_inner().disconnect().await; + // let forget_me = ClientRequest::ForgetMe { + // also_from_stats: true, + // }; + let client_handle = client.into_inner(); + client_handle.disconnect().await; } else { warn!("Failed to drop client, client had more then one strong ref") } @@ -89,6 +94,7 @@ async fn make_client(topology: NymTopology) -> Result { .network_details(net) .custom_topology_provider(topology_provider) .debug_config(mixnet_debug_config(0)) + .with_forget_me(ForgetMe::new_all()) // .enable_credentials_mode() .build()?; diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs index 369acee1a7..8841d3f505 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs @@ -154,31 +154,39 @@ mod api_regression { use super::*; use std::{env::var, sync::LazyLock}; - static IPINFO_TOKEN: LazyLock = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap()); + static IPINFO_TOKEN: LazyLock> = LazyLock::new(|| var("IPINFO_API_TOKEN").ok()); + static CI: LazyLock> = LazyLock::new(|| var("CI").ok()); #[tokio::test] async fn should_parse_response() { - let client = IpInfoClient::new(&(*IPINFO_TOKEN)); - let my_ip = reqwest::get("https://api.ipify.org") - .await - .expect("Couldn't get own IP") - .text() - .await - .unwrap(); + if CI.is_none() { + return; + } + if let Some(token) = &*IPINFO_TOKEN { + let client = IpInfoClient::new(token); + let my_ip = reqwest::get("https://api.ipify.org") + .await + .expect("Couldn't get own IP") + .text() + .await + .unwrap(); - let location_result = client.locate_ip(my_ip).await; - assert!(location_result.is_ok(), "Did ipinfo response change?"); + let location_result = client.locate_ip(my_ip).await; + assert!(location_result.is_ok(), "Did ipinfo response change?"); - assert!( - client.check_remaining_bandwidth().await.is_ok(), - "Failed to check remaining bandwidth?" - ); + assert!( + client.check_remaining_bandwidth().await.is_ok(), + "Failed to check remaining bandwidth?" + ); - // when serialized, these fields should be present because they're exposed over API - let location_result = location_result.unwrap(); - let json = serde_json::to_value(&location_result).unwrap(); - assert!(json.get("two_letter_iso_country_code").is_some()); - assert!(json.get("latitude").is_some()); - assert!(json.get("longitude").is_some()); + // when serialized, these fields should be present because they're exposed over API + let location_result = location_result.unwrap(); + let json = serde_json::to_value(&location_result).unwrap(); + assert!(json.get("two_letter_iso_country_code").is_some()); + assert!(json.get("latitude").is_some()); + assert!(json.get("longitude").is_some()); + } else { + panic!("IPINFO_API_TOKEN not set"); + } } } diff --git a/nym-node/src/node/metrics/handler/client_sessions.rs b/nym-node/src/node/metrics/handler/client_sessions.rs index 5f9e870a2a..8394eb1d89 100644 --- a/nym-node/src/node/metrics/handler/client_sessions.rs +++ b/nym-node/src/node/metrics/handler/client_sessions.rs @@ -73,6 +73,15 @@ impl GatewaySessionStatsHandler { Ok(()) } + async fn handle_session_delete( + &mut self, + client: DestinationAddressBytes, + ) -> Result<(), StatsStorageError> { + self.storage.delete_active_session(client).await?; + self.storage.delete_unique_user(client).await?; + Ok(()) + } + async fn handle_session_event( &mut self, event: GatewaySessionEvent, @@ -90,6 +99,11 @@ impl GatewaySessionStatsHandler { ticket_type, client, } => self.handle_ecash_ticket(ticket_type, client).await, + + // As long as delete is sent before stop, everything should work as expected + GatewaySessionEvent::SessionDelete { client } => { + self.handle_session_delete(client).await + } } } diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index f223037572..598bbd4808 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -28,6 +28,7 @@ use nym_client_core::error::ClientCoreError; use nym_client_core::init::helpers::current_gateways; use nym_client_core::init::setup_gateway; use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup}; +use nym_client_core::ForgetMe; use nym_credentials_interface::TicketType; use nym_socks5_client_core::config::Socks5; use nym_task::{TaskClient, TaskHandle, TaskStatus}; @@ -61,6 +62,7 @@ pub struct MixnetClientBuilder { gateway_endpoint_config_path: Option, storage: S, + forget_me: ForgetMe, } impl MixnetClientBuilder { @@ -97,6 +99,7 @@ impl MixnetClientBuilder { user_agent: None, #[cfg(unix)] connection_fd_callback: None, + forget_me: Default::default(), }) } } @@ -128,6 +131,7 @@ where connection_fd_callback: None, gateway_endpoint_config_path: None, storage, + forget_me: Default::default(), } } @@ -148,6 +152,7 @@ where connection_fd_callback: self.connection_fd_callback, gateway_endpoint_config_path: self.gateway_endpoint_config_path, storage, + forget_me: self.forget_me, } } @@ -160,6 +165,12 @@ where self.set_storage(storage) } + #[must_use] + pub fn with_forget_me(mut self, forget_me: ForgetMe) -> Self { + self.forget_me = forget_me; + self + } + /// Request a specific gateway instead of a random one. #[must_use] pub fn request_gateway(mut self, user_chosen_gateway: String) -> Self { @@ -283,7 +294,7 @@ where client.force_tls = self.force_tls; client.user_agent = self.user_agent; client.connection_fd_callback = self.connection_fd_callback; - + client.forget_me = self.forget_me; Ok(client) } } @@ -335,6 +346,8 @@ where /// Callback on the websocket fd as soon as the connection has been established connection_fd_callback: Option>, + + forget_me: ForgetMe, } impl DisconnectedMixnetClient @@ -385,6 +398,7 @@ where custom_shutdown: None, user_agent: None, connection_fd_callback: None, + forget_me: Default::default(), }) } @@ -608,7 +622,8 @@ where let mut base_builder: BaseClientBuilder<_, _> = BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) - .with_wait_for_gateway(self.wait_for_gateway); + .with_wait_for_gateway(self.wait_for_gateway) + .with_forget_me(&self.forget_me); if let Some(user_agent) = self.user_agent { base_builder = base_builder.with_user_agent(user_agent);