diff --git a/Cargo.lock b/Cargo.lock index a2e038a0dac..c460d2c957b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4325,6 +4325,7 @@ dependencies = [ "bs58 0.5.1", "bytes", "clap 4.5.7", + "defguard_wireguard_rs", "fastrand 2.1.0", "futures", "ipnetwork 0.16.0", @@ -5012,6 +5013,7 @@ dependencies = [ "nym-ecash-contract-common", "nym-ecash-double-spending", "nym-gateway-requests", + "nym-gateway-storage", "nym-ip-packet-router", "nym-mixnet-client", "nym-mixnode-common", @@ -5101,6 +5103,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-gateway-storage" +version = "0.1.0" +dependencies = [ + "async-trait", + "bincode", + "defguard_wireguard_rs", + "log", + "nym-credentials-interface", + "nym-gateway-requests", + "nym-sphinx", + "sqlx", + "thiserror", + "time", + "tokio", + "tracing", +] + [[package]] name = "nym-group-contract-common" version = "0.1.0" @@ -6191,12 +6211,14 @@ name = "nym-wireguard" version = "0.1.0" dependencies = [ "base64 0.21.7", + "bincode", "chrono", "dashmap", "defguard_wireguard_rs", "ip_network", "log", "nym-crypto", + "nym-gateway-storage", "nym-network-defaults", "nym-task", "nym-wireguard-types", diff --git a/Cargo.toml b/Cargo.toml index d520a16185d..237f005d328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ members = [ "common/ecash-time", "common/execute", "common/exit-policy", + "common/gateway-requests", + "common/gateway-storage", "common/http-api-client", "common/http-api-common", "common/inclusion-probability", @@ -96,7 +98,6 @@ members = [ "explorer-api/explorer-api-requests", "explorer-api/explorer-client", "gateway", - "gateway/gateway-requests", "integrations/bity", "mixnode", "sdk/lib/socks5-listener", @@ -139,7 +140,7 @@ default-members = [ "tools/nymvisor", "explorer-api", "nym-validator-rewarder", - "nym-node" + "nym-node", ] exclude = [ @@ -346,8 +347,8 @@ bip32 = { version = "0.5.1", default-features = false } # plus response message parsing (which is, as of the time of writing this message, waiting to get merged) #cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" } cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support -tendermint = "0.37.0" # same version as used by cosmrs -tendermint-rpc = "0.37.0" # same version as used by cosmrs +tendermint = "0.37.0" # same version as used by cosmrs +tendermint-rpc = "0.37.0" # same version as used by cosmrs prost = { version = "0.12", default-features = false } # wasm-related dependencies diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 9680601e6e1..76f81c2b082 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "nym-client" version = "1.1.38" -authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] +authors = [ + "Dave Hrycyszyn ", + "Jędrzej Stuczyński ", +] description = "Implementation of the Nym Client" edition = "2021" rust-version = "1.70" @@ -26,30 +29,46 @@ clap = { workspace = true, features = ["cargo", "derive"] } dirs = { workspace = true } log = { workspace = true } # self explanatory rand = { workspace = true } -serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization +serde = { workspace = true, features = [ + "derive", +] } # for config serialization/deserialization serde_json = { workspace = true } thiserror = { workspace = true } tap = { workspace = true } time = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime +tokio = { workspace = true, features = [ + "rt-multi-thread", + "net", + "signal", +] } # async runtime tokio-tungstenite = { workspace = true } zeroize = { workspace = true } ## internal nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "clap"] } -nym-client-core = { path = "../../common/client-core", features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli"] } +nym-bin-common = { path = "../../common/bin-common", features = [ + "output_format", + "clap", +] } +nym-client-core = { path = "../../common/client-core", features = [ + "fs-credentials-storage", + "fs-surb-storage", + "fs-gateways-storage", + "cli", +] } nym-config = { path = "../../common/config" } nym-credential-storage = { path = "../../common/credential-storage" } nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../../common/gateway-requests" } nym-network-defaults = { path = "../../common/network-defaults" } nym-sphinx = { path = "../../common/nymsphinx" } nym-pemstore = { path = "../../common/pemstore" } nym-task = { path = "../../common/task" } nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] } +nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ + "http-client", +] } nym-client-websocket-requests = { path = "websocket-requests" } nym-id = { path = "../../common/nym-id" } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index a76b65cf8d1..098bbacfbed 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -11,7 +11,9 @@ license.workspace = true bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } log = { workspace = true } -serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization +serde = { workspace = true, features = [ + "derive", +] } # for config serialization/deserialization serde_json = { workspace = true } tap = { workspace = true } thiserror = { workspace = true } @@ -22,13 +24,21 @@ url = { workspace = true } zeroize = { workspace = true } # internal -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "clap"] } -nym-client-core = { path = "../../common/client-core", features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli"] } +nym-bin-common = { path = "../../common/bin-common", features = [ + "output_format", + "clap", +] } +nym-client-core = { path = "../../common/client-core", features = [ + "fs-credentials-storage", + "fs-surb-storage", + "fs-gateways-storage", + "cli", +] } nym-config = { path = "../../common/config" } nym-credential-storage = { path = "../../common/credential-storage" } nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../../common/gateway-requests" } nym-id = { path = "../../common/nym-id" } nym-network-defaults = { path = "../../common/network-defaults" } nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } @@ -36,7 +46,9 @@ nym-pemstore = { path = "../../common/pemstore" } nym-socks5-client-core = { path = "../../common/socks5-client-core" } nym-sphinx = { path = "../../common/nymsphinx" } nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] } +nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ + "http-client", +] } [features] default = [] diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 1378d2f77ff..763f0d89674 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -38,7 +38,7 @@ nym-country-group = { path = "../country-group" } nym-crypto = { path = "../crypto" } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-gateway-client = { path = "../client-libs/gateway-client" } -nym-gateway-requests = { path = "../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../gateway-requests" } nym-metrics = { path = "../nym-metrics" } nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } nym-sphinx = { path = "../nymsphinx" } @@ -49,7 +49,9 @@ nym-task = { path = "../task" } nym-credentials-interface = { path = "../credentials-interface" } nym-credential-storage = { path = "../credential-storage" } nym-network-defaults = { path = "../network-defaults" } -nym-client-core-config-types = { path = "./config-types", features = ["disk-persistence"] } +nym-client-core-config-types = { path = "./config-types", features = [ + "disk-persistence", +] } nym-client-core-surb-storage = { path = "./surb-storage" } nym-client-core-gateways-storage = { path = "./gateways-storage" } nym-ecash-time = { path = "../ecash-time" } diff --git a/common/client-core/gateways-storage/Cargo.toml b/common/client-core/gateways-storage/Cargo.toml index 4392798c0bd..e91c889ed69 100644 --- a/common/client-core/gateways-storage/Cargo.toml +++ b/common/client-core/gateways-storage/Cargo.toml @@ -18,7 +18,7 @@ url.workspace = true zeroize = { workspace = true, features = ["zeroize_derive"] } nym-crypto = { path = "../../crypto", features = ["asymmetric"] } -nym-gateway-requests = { path = "../../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../../gateway-requests" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true @@ -27,7 +27,12 @@ optional = true [build-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } +sqlx = { workspace = true, features = [ + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", +] } [features] -fs-gateways-storage = ["sqlx"] \ No newline at end of file +fs-gateways-storage = ["sqlx"] diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 9eb2e964022..640d9694df4 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -24,7 +24,7 @@ nym-bandwidth-controller = { path = "../../bandwidth-controller" } nym-credentials = { path = "../../credentials" } nym-credential-storage = { path = "../../credential-storage" } nym-crypto = { path = "../../crypto" } -nym-gateway-requests = { path = "../../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../../gateway-requests" } nym-network-defaults = { path = "../../network-defaults" } nym-sphinx = { path = "../../nymsphinx" } nym-pemstore = { path = "../../pemstore" } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 0d4f995e3c9..85938c3c61a 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -28,7 +28,7 @@ pub use nym_compact_ecash::{ withdrawal_request, Base58, BlindedSignature, Bytable, EncodedDate, EncodedTicketType, PartialWallet, PayInfo, PublicKeyUser, SecretKeyUser, VerificationKeyAuth, WithdrawalRequest, }; -use nym_ecash_time::EcashTime; +use nym_ecash_time::{ecash_today, EcashTime}; #[derive(Debug, Clone)] pub struct CredentialSigningData { @@ -292,3 +292,39 @@ impl From for TicketType { } } } + +#[derive(Clone)] +pub struct ClientTicket { + pub spending_data: CredentialSpendingData, + pub ticket_id: i64, +} + +impl ClientTicket { + pub fn new(spending_data: CredentialSpendingData, ticket_id: i64) -> Self { + ClientTicket { + spending_data, + ticket_id, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct AvailableBandwidth { + pub bytes: i64, + pub expiration: OffsetDateTime, +} + +impl AvailableBandwidth { + pub fn expired(&self) -> bool { + self.expiration < ecash_today() + } +} + +impl Default for AvailableBandwidth { + fn default() -> Self { + Self { + bytes: 0, + expiration: OffsetDateTime::UNIX_EPOCH, + } + } +} diff --git a/gateway/gateway-requests/Cargo.toml b/common/gateway-requests/Cargo.toml similarity index 72% rename from gateway/gateway-requests/Cargo.toml rename to common/gateway-requests/Cargo.toml index 365edc2a5c9..c1dadc47321 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/common/gateway-requests/Cargo.toml @@ -21,12 +21,12 @@ thiserror = { workspace = true } tracing = { workspace = true, features = ["log"] } zeroize = { workspace = true } -nym-crypto = { path = "../../common/crypto" } -nym-pemstore = { path = "../../common/pemstore" } -nym-sphinx = { path = "../../common/nymsphinx" } +nym-crypto = { path = "../crypto" } +nym-pemstore = { path = "../pemstore" } +nym-sphinx = { path = "../nymsphinx" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } +nym-credentials = { path = "../credentials" } +nym-credentials-interface = { path = "../credentials-interface" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -41,6 +41,4 @@ workspace = true default-features = false [dev-dependencies] -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } # we need specific imports in tests - - +nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests diff --git a/gateway/gateway-requests/src/authentication/encrypted_address.rs b/common/gateway-requests/src/authentication/encrypted_address.rs similarity index 100% rename from gateway/gateway-requests/src/authentication/encrypted_address.rs rename to common/gateway-requests/src/authentication/encrypted_address.rs diff --git a/gateway/gateway-requests/src/authentication/mod.rs b/common/gateway-requests/src/authentication/mod.rs similarity index 100% rename from gateway/gateway-requests/src/authentication/mod.rs rename to common/gateway-requests/src/authentication/mod.rs diff --git a/gateway/gateway-requests/src/iv.rs b/common/gateway-requests/src/iv.rs similarity index 100% rename from gateway/gateway-requests/src/iv.rs rename to common/gateway-requests/src/iv.rs diff --git a/gateway/gateway-requests/src/lib.rs b/common/gateway-requests/src/lib.rs similarity index 100% rename from gateway/gateway-requests/src/lib.rs rename to common/gateway-requests/src/lib.rs diff --git a/gateway/gateway-requests/src/models.rs b/common/gateway-requests/src/models.rs similarity index 100% rename from gateway/gateway-requests/src/models.rs rename to common/gateway-requests/src/models.rs diff --git a/gateway/gateway-requests/src/registration/handshake/client.rs b/common/gateway-requests/src/registration/handshake/client.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/client.rs rename to common/gateway-requests/src/registration/handshake/client.rs diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/common/gateway-requests/src/registration/handshake/error.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/error.rs rename to common/gateway-requests/src/registration/handshake/error.rs diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/common/gateway-requests/src/registration/handshake/gateway.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/gateway.rs rename to common/gateway-requests/src/registration/handshake/gateway.rs diff --git a/gateway/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/mod.rs rename to common/gateway-requests/src/registration/handshake/mod.rs diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/common/gateway-requests/src/registration/handshake/shared_key.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/shared_key.rs rename to common/gateway-requests/src/registration/handshake/shared_key.rs diff --git a/gateway/gateway-requests/src/registration/handshake/state.rs b/common/gateway-requests/src/registration/handshake/state.rs similarity index 100% rename from gateway/gateway-requests/src/registration/handshake/state.rs rename to common/gateway-requests/src/registration/handshake/state.rs diff --git a/gateway/gateway-requests/src/registration/mod.rs b/common/gateway-requests/src/registration/mod.rs similarity index 100% rename from gateway/gateway-requests/src/registration/mod.rs rename to common/gateway-requests/src/registration/mod.rs diff --git a/gateway/gateway-requests/src/types.rs b/common/gateway-requests/src/types.rs similarity index 100% rename from gateway/gateway-requests/src/types.rs rename to common/gateway-requests/src/types.rs diff --git a/common/gateway-storage/Cargo.toml b/common/gateway-storage/Cargo.toml new file mode 100644 index 00000000000..eb18f1444e1 --- /dev/null +++ b/common/gateway-storage/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "nym-gateway-storage" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +async-trait = { workspace = true } +bincode = { workspace = true, optional = true } +defguard_wireguard_rs = { workspace = true, optional = true } +log = { workspace = true } +sqlx = { workspace = true, features = [ + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", + "time", +] } +time = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +nym-credentials-interface = { path = "../credentials-interface" } +nym-gateway-requests = { path = "../gateway-requests" } +nym-sphinx = { path = "../nymsphinx" } + +[build-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +sqlx = { workspace = true, features = [ + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", +] } + +[features] +wireguard = ["defguard_wireguard_rs", "bincode"] diff --git a/gateway/build.rs b/common/gateway-storage/build.rs similarity index 100% rename from gateway/build.rs rename to common/gateway-storage/build.rs diff --git a/gateway/migrations/20210921120000_create_initial_tables.sql b/common/gateway-storage/migrations/20210921120000_create_initial_tables.sql similarity index 100% rename from gateway/migrations/20210921120000_create_initial_tables.sql rename to common/gateway-storage/migrations/20210921120000_create_initial_tables.sql diff --git a/gateway/migrations/20240126120000_store_serial_numbers.sql b/common/gateway-storage/migrations/20240126120000_store_serial_numbers.sql similarity index 100% rename from gateway/migrations/20240126120000_store_serial_numbers.sql rename to common/gateway-storage/migrations/20240126120000_store_serial_numbers.sql diff --git a/gateway/migrations/20240301120000_freepass_expiration.sql b/common/gateway-storage/migrations/20240301120000_freepass_expiration.sql similarity index 100% rename from gateway/migrations/20240301120000_freepass_expiration.sql rename to common/gateway-storage/migrations/20240301120000_freepass_expiration.sql diff --git a/gateway/migrations/20240624120000_ecash_changes.sql b/common/gateway-storage/migrations/20240624120000_ecash_changes.sql similarity index 100% rename from gateway/migrations/20240624120000_ecash_changes.sql rename to common/gateway-storage/migrations/20240624120000_ecash_changes.sql diff --git a/common/gateway-storage/migrations/20240724120000_wireguard_peers.sql b/common/gateway-storage/migrations/20240724120000_wireguard_peers.sql new file mode 100644 index 00000000000..083c9f8a79f --- /dev/null +++ b/common/gateway-storage/migrations/20240724120000_wireguard_peers.sql @@ -0,0 +1,18 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE wireguard_peer +( + public_key TEXT NOT NULL PRIMARY KEY UNIQUE, + preshared_key TEXT, + protocol_version INTEGER, + endpoint TEXT, + last_handshake TIMESTAMP, + tx_bytes BIGINT NOT NULL, + rx_bytes BIGINT NOT NULL, + persistent_keepalive_interval INTEGER, + allowed_ips BLOB NOT NULL, + suspended BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/gateway/src/node/storage/bandwidth.rs b/common/gateway-storage/src/bandwidth.rs similarity index 89% rename from gateway/src/node/storage/bandwidth.rs rename to common/gateway-storage/src/bandwidth.rs index 991c77d82ee..3554213ffd9 100644 --- a/gateway/src/node/storage/bandwidth.rs +++ b/common/gateway-storage/src/bandwidth.rs @@ -1,11 +1,28 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::PersistedBandwidth; +use crate::models::PersistedBandwidth; use time::OffsetDateTime; +#[derive(Debug, Clone, Copy, Default)] +pub struct AvailableBandwidth { + pub bytes: i64, + pub freepass_expiration: Option, +} + +impl AvailableBandwidth { + pub fn freepass_expired(&self) -> bool { + if let Some(expiration) = self.freepass_expiration { + if expiration < OffsetDateTime::now_utc() { + return true; + } + } + false + } +} + #[derive(Clone)] -pub(crate) struct BandwidthManager { +pub struct BandwidthManager { connection_pool: sqlx::SqlitePool, } @@ -15,7 +32,7 @@ impl BandwidthManager { /// # Arguments /// /// * `connection_pool`: database connection pool to use. - pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self { + pub fn new(connection_pool: sqlx::SqlitePool) -> Self { BandwidthManager { connection_pool } } diff --git a/gateway/src/node/storage/error.rs b/common/gateway-storage/src/error.rs similarity index 87% rename from gateway/src/node/storage/error.rs rename to common/gateway-storage/src/error.rs index 4cf292ea57a..178d630c109 100644 --- a/gateway/src/node/storage/error.rs +++ b/common/gateway-storage/src/error.rs @@ -16,4 +16,7 @@ pub enum StorageError { #[error("the stored data associated with ticket {ticket_id} is malformed!")] MalformedStoredTicketData { ticket_id: i64 }, + + #[error("Failed to convert from type of database: {0}")] + TypeConversion(String), } diff --git a/gateway/src/node/storage/inboxes.rs b/common/gateway-storage/src/inboxes.rs similarity index 98% rename from gateway/src/node/storage/inboxes.rs rename to common/gateway-storage/src/inboxes.rs index c94459fb884..6c1ac23c47c 100644 --- a/gateway/src/node/storage/inboxes.rs +++ b/common/gateway-storage/src/inboxes.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::StoredMessage; +use crate::models::StoredMessage; #[derive(Clone)] pub(crate) struct InboxManager { diff --git a/gateway/src/node/storage/mod.rs b/common/gateway-storage/src/lib.rs similarity index 86% rename from gateway/src/node/storage/mod.rs rename to common/gateway-storage/src/lib.rs index 6d756422460..7d3611fccff 100644 --- a/gateway/src/node/storage/mod.rs +++ b/common/gateway-storage/src/lib.rs @@ -1,29 +1,32 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::websocket::connection_handler::ecash::ClientTicket; -use crate::node::storage::bandwidth::BandwidthManager; -use crate::node::storage::error::StorageError; -use crate::node::storage::inboxes::InboxManager; -use crate::node::storage::models::{ +use async_trait::async_trait; +use bandwidth::BandwidthManager; +use error::StorageError; +use inboxes::InboxManager; +use models::{ PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage, VerifiedTicket, + WireguardPeer, }; -use crate::node::storage::shared_keys::SharedKeysManager; -use crate::node::storage::tickets::TicketStorageManager; -use async_trait::async_trait; +use nym_credentials_interface::ClientTicket; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_sphinx::DestinationAddressBytes; +use shared_keys::SharedKeysManager; use sqlx::ConnectOptions; use std::path::Path; +use tickets::TicketStorageManager; use time::OffsetDateTime; use tracing::{debug, error}; -mod bandwidth; -pub(crate) mod error; +pub mod bandwidth; +pub mod error; mod inboxes; pub(crate) mod models; mod shared_keys; mod tickets; +#[cfg(feature = "wireguard")] +mod wireguard_peers; #[async_trait] pub trait Storage: Send + Sync { @@ -207,6 +210,42 @@ pub trait Storage: Send + Sync { async fn get_votes(&self, ticket_id: i64) -> Result, StorageError>; async fn get_signers(&self, epoch_id: i64) -> Result, StorageError>; + + /// Insert a wireguard peer in the storage. + /// + /// # Arguments + /// + /// * `peer`: wireguard peer data to be stored + /// * `suspended`: if peer exists, but it's currently suspended + #[cfg(feature = "wireguard")] + async fn insert_wireguard_peer( + &self, + peer: &defguard_wireguard_rs::host::Peer, + suspended: bool, + ) -> Result<(), StorageError>; + + /// Tries to retrieve available bandwidth for the particular peer. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be retrieved. + #[cfg(feature = "wireguard")] + async fn get_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result, StorageError>; + + /// Retrieves all wireguard peers. + #[cfg(feature = "wireguard")] + async fn get_all_wireguard_peers(&self) -> Result, StorageError>; + + /// Remove a wireguard peer from the storage. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be removed. + #[cfg(feature = "wireguard")] + async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError>; } // note that clone here is fine as upon cloning the same underlying pool will be used @@ -216,6 +255,8 @@ pub struct PersistentStorage { inbox_manager: InboxManager, bandwidth_manager: BandwidthManager, ticket_manager: TicketStorageManager, + #[cfg(feature = "wireguard")] + wireguard_peer_manager: wireguard_peers::WgPeerManager, } impl PersistentStorage { @@ -259,6 +300,8 @@ impl PersistentStorage { // the cloning here are cheap as connection pool is stored behind an Arc Ok(PersistentStorage { + #[cfg(feature = "wireguard")] + wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()), shared_key_manager: SharedKeysManager::new(connection_pool.clone()), inbox_manager: InboxManager::new(connection_pool.clone(), message_retrieval_limit), bandwidth_manager: BandwidthManager::new(connection_pool.clone()), @@ -576,4 +619,42 @@ impl Storage for PersistentStorage { async fn get_signers(&self, epoch_id: i64) -> Result, StorageError> { Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?) } + + #[cfg(feature = "wireguard")] + async fn insert_wireguard_peer( + &self, + peer: &defguard_wireguard_rs::host::Peer, + suspended: bool, + ) -> Result<(), StorageError> { + let mut peer = WireguardPeer::from(peer.clone()); + peer.suspended = suspended; + self.wireguard_peer_manager.insert_peer(&peer).await?; + Ok(()) + } + + #[cfg(feature = "wireguard")] + async fn get_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result, StorageError> { + let peer = self + .wireguard_peer_manager + .retrieve_peer(peer_public_key) + .await?; + Ok(peer) + } + + #[cfg(feature = "wireguard")] + async fn get_all_wireguard_peers(&self) -> Result, StorageError> { + let ret = self.wireguard_peer_manager.retrieve_all_peers().await?; + Ok(ret) + } + + #[cfg(feature = "wireguard")] + async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError> { + self.wireguard_peer_manager + .remove_peer(peer_public_key) + .await?; + Ok(()) + } } diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs new file mode 100644 index 00000000000..e1973ac1e38 --- /dev/null +++ b/common/gateway-storage/src/models.rs @@ -0,0 +1,182 @@ +// Copyright 2021-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::StorageError; +use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData}; +use sqlx::FromRow; +use time::OffsetDateTime; + +pub struct PersistedSharedKeys { + #[allow(dead_code)] + pub id: i64, + + #[allow(dead_code)] + pub client_address_bs58: String, + pub derived_aes128_ctr_blake3_hmac_keys_bs58: String, +} + +pub struct StoredMessage { + pub id: i64, + #[allow(dead_code)] + pub client_address_bs58: String, + pub content: Vec, +} + +#[derive(Debug, Clone, FromRow)] +pub struct PersistedBandwidth { + #[allow(dead_code)] + pub client_id: i64, + pub available: i64, + pub expiration: Option, +} + +impl From for AvailableBandwidth { + fn from(value: PersistedBandwidth) -> Self { + AvailableBandwidth { + bytes: value.available, + expiration: value.expiration.unwrap_or(OffsetDateTime::UNIX_EPOCH), + } + } +} + +#[derive(FromRow)] +pub struct VerifiedTicket { + pub serial_number: Vec, + pub ticket_id: i64, +} + +#[derive(FromRow)] +pub struct RedemptionProposal { + pub proposal_id: i64, + pub created_at: OffsetDateTime, +} + +#[derive(FromRow)] +pub struct UnverifiedTicketData { + pub data: Vec, + pub ticket_id: i64, +} + +impl TryFrom for ClientTicket { + type Error = StorageError; + + fn try_from(value: UnverifiedTicketData) -> Result { + Ok(ClientTicket { + spending_data: CredentialSpendingData::try_from_bytes(&value.data).map_err(|_| { + StorageError::MalformedStoredTicketData { + ticket_id: value.ticket_id, + } + })?, + ticket_id: value.ticket_id, + }) + } +} + +#[cfg(feature = "wireguard")] +#[derive(Debug, Clone, FromRow)] +pub struct WireguardPeer { + pub public_key: String, + pub preshared_key: Option, + pub protocol_version: Option, + pub endpoint: Option, + pub last_handshake: Option, + pub tx_bytes: i64, + pub rx_bytes: i64, + pub persistent_keepalive_interval: Option, + pub allowed_ips: Vec, + pub suspended: bool, +} + +#[cfg(feature = "wireguard")] +impl From for WireguardPeer { + fn from(value: defguard_wireguard_rs::host::Peer) -> Self { + WireguardPeer { + public_key: value.public_key.to_string(), + preshared_key: value.preshared_key.as_ref().map(|k| k.to_string()), + protocol_version: value.protocol_version.map(|v| v as i64), + endpoint: value.endpoint.map(|e| e.to_string()), + last_handshake: value.last_handshake.and_then(|t| { + if let Ok(d) = t.duration_since(std::time::UNIX_EPOCH) { + if let Ok(millis) = d.as_millis().try_into() { + sqlx::types::chrono::DateTime::from_timestamp_millis(millis) + .map(|d| d.naive_utc()) + } else { + None + } + } else { + None + } + }), + tx_bytes: value.tx_bytes as i64, + rx_bytes: value.rx_bytes as i64, + persistent_keepalive_interval: value.persistent_keepalive_interval.map(|v| v as i64), + allowed_ips: bincode::Options::serialize( + bincode::DefaultOptions::new(), + &value.allowed_ips, + ) + .unwrap_or_default(), + suspended: false, + } + } +} + +#[cfg(feature = "wireguard")] +impl TryFrom for defguard_wireguard_rs::host::Peer { + type Error = crate::error::StorageError; + + fn try_from(value: WireguardPeer) -> Result { + Ok(Self { + public_key: value + .public_key + .as_str() + .try_into() + .map_err(|e| Self::Error::TypeConversion(format!("public key {e}")))?, + preshared_key: value + .preshared_key + .as_deref() + .map(TryFrom::try_from) + .transpose() + .map_err(|e| Self::Error::TypeConversion(format!("preshared key {e}")))?, + protocol_version: value + .protocol_version + .map(TryFrom::try_from) + .transpose() + .map_err(|e| Self::Error::TypeConversion(format!("protocol version {e}")))?, + endpoint: value + .endpoint + .as_deref() + .map(|e| e.parse()) + .transpose() + .map_err(|e| Self::Error::TypeConversion(format!("endpoint {e}")))?, + last_handshake: value.last_handshake.and_then(|t| { + let unix_time = std::time::UNIX_EPOCH; + if let Ok(millis) = t.and_utc().timestamp_millis().try_into() { + let duration = std::time::Duration::from_millis(millis); + unix_time.checked_add(duration) + } else { + None + } + }), + tx_bytes: value + .tx_bytes + .try_into() + .map_err(|e| Self::Error::TypeConversion(format!("tx bytes {e}")))?, + rx_bytes: value + .rx_bytes + .try_into() + .map_err(|e| Self::Error::TypeConversion(format!("rx bytes {e}")))?, + persistent_keepalive_interval: value + .persistent_keepalive_interval + .map(TryFrom::try_from) + .transpose() + .map_err(|e| { + Self::Error::TypeConversion(format!("persistent keepalive interval {e}")) + })?, + allowed_ips: bincode::Options::deserialize( + bincode::DefaultOptions::new(), + &value.allowed_ips, + ) + .map_err(|e| Self::Error::TypeConversion(format!("allowed ips {e}")))?, + }) + } +} diff --git a/gateway/src/node/storage/shared_keys.rs b/common/gateway-storage/src/shared_keys.rs similarity index 98% rename from gateway/src/node/storage/shared_keys.rs rename to common/gateway-storage/src/shared_keys.rs index de1ce0abf75..8d16ca5ba5a 100644 --- a/gateway/src/node/storage/shared_keys.rs +++ b/common/gateway-storage/src/shared_keys.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::PersistedSharedKeys; +use crate::models::PersistedSharedKeys; #[derive(Clone)] pub(crate) struct SharedKeysManager { diff --git a/gateway/src/node/storage/tickets.rs b/common/gateway-storage/src/tickets.rs similarity index 99% rename from gateway/src/node/storage/tickets.rs rename to common/gateway-storage/src/tickets.rs index 19a42878019..ce68d44514c 100644 --- a/gateway/src/node/storage/tickets.rs +++ b/common/gateway-storage/src/tickets.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::{RedemptionProposal, UnverifiedTicketData, VerifiedTicket}; +use crate::models::{RedemptionProposal, UnverifiedTicketData, VerifiedTicket}; use time::OffsetDateTime; #[derive(Clone)] diff --git a/common/gateway-storage/src/wireguard_peers.rs b/common/gateway-storage/src/wireguard_peers.rs new file mode 100644 index 00000000000..436cdc34756 --- /dev/null +++ b/common/gateway-storage/src/wireguard_peers.rs @@ -0,0 +1,89 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::models::WireguardPeer; + +#[derive(Clone)] +pub(crate) struct WgPeerManager { + connection_pool: sqlx::SqlitePool, +} + +impl WgPeerManager { + /// Creates new instance of the `WgPeersManager` with the provided sqlite connection pool. + /// + /// # Arguments + /// + /// * `connection_pool`: database connection pool to use. + pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self { + WgPeerManager { connection_pool } + } + + /// Creates a new wireguard peer entry for its particular public key or + /// overwrittes the peer entry data if it already existed. + /// + /// # Arguments + /// + /// * `peer`: peer information needed by wireguard interface. + pub(crate) async fn insert_peer(&self, peer: &WireguardPeer) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT OR REPLACE INTO wireguard_peer(public_key, preshared_key, protocol_version, endpoint, last_handshake, tx_bytes, rx_bytes, persistent_keepalive_interval, allowed_ips, suspended) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + peer.public_key, peer.preshared_key, peer.protocol_version, peer.endpoint, peer.last_handshake, peer.tx_bytes, peer.rx_bytes, peer.persistent_keepalive_interval, peer.allowed_ips, peer.suspended + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + /// Retrieve the wireguard peer with the provided public key from the storage. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + pub(crate) async fn retrieve_peer( + &self, + public_key: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + WireguardPeer, + r#" + SELECT * FROM wireguard_peer + WHERE public_key = ? + LIMIT 1 + "#, + public_key, + ) + .fetch_optional(&self.connection_pool) + .await + } + + /// Retrieve all wireguard peers. + pub(crate) async fn retrieve_all_peers(&self) -> Result, sqlx::Error> { + sqlx::query_as!( + WireguardPeer, + r#" + SELECT * + FROM wireguard_peer; + "#, + ) + .fetch_all(&self.connection_pool) + .await + } + + /// Retrieve the wireguard peer with the provided public key from the storage. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + pub(crate) async fn remove_peer(&self, public_key: &str) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + DELETE FROM wireguard_peer + WHERE public_key = ? + "#, + public_key, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } +} diff --git a/common/wireguard-types/src/error.rs b/common/wireguard-types/src/error.rs index 3584a056a68..0bd4a8d3f61 100644 --- a/common/wireguard-types/src/error.rs +++ b/common/wireguard-types/src/error.rs @@ -32,7 +32,4 @@ pub enum Error { #[source] source: hmac::digest::MacError, }, - - #[error("peers can't be modified anymore")] - PeerModifyStopped, } diff --git a/common/wireguard-types/src/lib.rs b/common/wireguard-types/src/lib.rs index 3c2cc66951e..18a01cd9e76 100644 --- a/common/wireguard-types/src/lib.rs +++ b/common/wireguard-types/src/lib.rs @@ -9,9 +9,7 @@ pub mod registration; pub use config::Config; pub use error::Error; pub use public_key::PeerPublicKey; -pub use registration::{ - ClientMac, ClientMessage, GatewayClient, GatewayClientRegistry, InitMessage, Nonce, -}; +pub use registration::{ClientMac, ClientMessage, GatewayClient, InitMessage, Nonce}; #[cfg(feature = "verify")] pub use registration::HmacSha256; diff --git a/common/wireguard-types/src/registration.rs b/common/wireguard-types/src/registration.rs index ae5a758d905..9d2d182f6d3 100644 --- a/common/wireguard-types/src/registration.rs +++ b/common/wireguard-types/src/registration.rs @@ -17,7 +17,6 @@ use nym_crypto::asymmetric::encryption::PrivateKey; #[cfg(feature = "verify")] use sha2::Sha256; -pub type GatewayClientRegistry = DashMap; pub type PendingRegistrations = DashMap; pub type PrivateIPs = DashMap; diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index 51a0b115a1e..18759fa6410 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true [dependencies] base64 = { workspace = true } +bincode = { workspace = true } chrono = { workspace = true } dashmap = { workspace = true } defguard_wireguard_rs = { workspace = true } @@ -22,6 +23,7 @@ x25519-dalek = { workspace = true } ip_network = { workspace = true } log.workspace = true nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-gateway-storage = { path = "../gateway-storage", features = ["wireguard"] } nym-network-defaults = { path = "../network-defaults" } nym-task = { path = "../task" } nym-wireguard-types = { path = "../wireguard-types" } diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index da7452614fc..bd5fa063990 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -8,4 +8,7 @@ pub enum Error { #[error("{0}")] Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError), + + #[error("{0}")] + GatewayStorage(#[from] nym_gateway_storage::error::StorageError), } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 6244320e540..fa1c22b7cb1 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -6,10 +6,11 @@ // #![warn(clippy::expect_used)] // #![warn(clippy::unwrap_used)] -use dashmap::DashMap; -use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi}; +use defguard_wireguard_rs::WGApi; +#[cfg(target_os = "linux")] +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; use nym_crypto::asymmetric::encryption::KeyPair; -use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey}; +use nym_wireguard_types::Config; use peer_controller::PeerControlRequest; use std::sync::Arc; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -42,7 +43,6 @@ impl Drop for WgApiWrapper { pub struct WireguardGatewayData { config: Config, keypair: Arc, - client_registry: Arc, peer_tx: UnboundedSender, } @@ -56,7 +56,6 @@ impl WireguardGatewayData { WireguardGatewayData { config, keypair, - client_registry: Arc::new(DashMap::default()), peer_tx, }, peer_rx, @@ -71,28 +70,8 @@ impl WireguardGatewayData { &self.keypair } - pub fn client_registry(&self) -> &Arc { - &self.client_registry - } - - pub fn add_peer(&self, client: &GatewayClient) -> Result<(), Error> { - let mut peer = Peer::new(Key::new(client.pub_key.to_bytes())); - peer.allowed_ips - .push(IpAddrMask::new(client.private_ip, 32)); - let msg = PeerControlRequest::AddPeer(peer); - self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped) - } - - pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> { - let key = Key::new(client.pub_key().to_bytes()); - let msg = PeerControlRequest::RemovePeer(key); - self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped) - } - - pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> { - let key = Key::new(peer_public_key.to_bytes()); - let msg = PeerControlRequest::QueryBandwidth(key); - self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped) + pub fn peer_tx(&self) -> &UnboundedSender { + &self.peer_tx } } @@ -103,7 +82,8 @@ pub struct WireguardData { /// Start wireguard device #[cfg(target_os = "linux")] -pub async fn start_wireguard( +pub async fn start_wireguard( + storage: St, task_client: nym_task::TaskClient, wireguard_data: WireguardData, control_tx: UnboundedSender, @@ -114,11 +94,15 @@ pub async fn start_wireguard( use peer_controller::PeerController; let mut peers = vec![]; - for peer_client in wireguard_data.inner.client_registry().iter() { - let mut peer = Peer::new(Key::new(peer_client.pub_key.to_bytes())); - let peer_ip_mask = IpAddrMask::new(peer_client.private_ip, 32); - peer.set_allowed_ips(vec![peer_ip_mask]); - peers.push(peer); + let mut suspended_peers = vec![]; + for storage_peer in storage.get_all_wireguard_peers().await? { + let suspended = storage_peer.suspended; + let peer = Peer::try_from(storage_peer)?; + if suspended { + suspended_peers.push(peer); + } else { + peers.push(peer); + } } let ifname = String::from(WG_TUN_NAME); @@ -147,8 +131,10 @@ pub async fn start_wireguard( let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); let mut controller = PeerController::new( + storage, wg_api.clone(), interface_config.peers, + suspended_peers, wireguard_data.peer_rx, control_tx, ); diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index fa11aa8a71d..7a48a20857d 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -3,6 +3,7 @@ use chrono::{Timelike, Utc}; use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi}; +use nym_gateway_storage::Storage; use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY}; use std::time::SystemTime; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -20,6 +21,7 @@ const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minut pub enum PeerControlRequest { AddPeer(Peer), RemovePeer(Key), + QueryPeer(Key), QueryBandwidth(Key), } @@ -30,12 +32,17 @@ pub enum PeerControlResponse { RemovePeer { success: bool, }, + QueryPeer { + success: bool, + peer: Option, + }, QueryBandwidth { bandwidth_data: Option, }, } -pub struct PeerController { +pub struct PeerController { + storage: St, request_rx: mpsc::UnboundedReceiver, response_tx: mpsc::UnboundedSender, wg_api: Arc, @@ -45,10 +52,12 @@ pub struct PeerController { last_seen_bandwidth: HashMap, } -impl PeerController { +impl PeerController { pub fn new( + storage: St, wg_api: Arc, peers: Vec, + suspended_peers: Vec, request_rx: mpsc::UnboundedReceiver, response_tx: mpsc::UnboundedSender, ) -> Self { @@ -59,22 +68,34 @@ impl PeerController { .into_iter() .map(|peer| (peer.public_key.clone(), peer)) .collect(); + let suspended_peers = suspended_peers + .into_iter() + .map(|peer| (peer.public_key.clone(), peer)) + .collect(); PeerController { + storage, wg_api, request_rx, response_tx, timeout_check_interval, active_peers, - suspended_peers: HashMap::new(), + suspended_peers, last_seen_bandwidth: HashMap::new(), } } - fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result { + async fn check_stale_peer( + &self, + peer: &Peer, + current_timestamp: SystemTime, + ) -> Result { if let Some(timestamp) = peer.last_handshake { if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) { if duration_since_handshake > DEFAULT_PEER_TIMEOUT { + self.storage + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; self.wg_api.inner.remove_peer(&peer.public_key)?; return Ok(true); } @@ -84,7 +105,7 @@ impl PeerController { Ok(false) } - fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> { + async fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> { let prev_peer = self .active_peers .get(&peer.public_key) @@ -92,17 +113,21 @@ impl PeerController { let data_usage = (peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes); if data_usage > BANDWIDTH_CAP_PER_DAY { + self.storage.insert_wireguard_peer(peer, true).await?; self.wg_api.inner.remove_peer(&peer.public_key)?; - let (moved_key, moved_peer) = self - .active_peers + self.active_peers .remove_entry(&peer.public_key) .ok_or(Error::PeerMismatch)?; - self.suspended_peers.insert(moved_key, moved_peer); + self.suspended_peers + .insert(peer.public_key.clone(), peer.clone()); + } else { + // Update peer stored data + self.storage.insert_wireguard_peer(peer, false).await?; } Ok(()) } - fn check_peers(&mut self) -> Result<(), Error> { + async fn check_peers(&mut self) -> Result<(), Error> { // Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth // by resetting the bandwidth twice let reset = Utc::now().num_seconds_from_midnight() as u64 @@ -121,11 +146,21 @@ impl PeerController { .collect(); if reset { self.active_peers = host.peers; + for peer in self.active_peers.values() { + self.storage.insert_wireguard_peer(peer, false).await?; + } } else { + let peers = self + .storage + .get_all_wireguard_peers() + .await? + .into_iter() + .map(Peer::try_from) + .collect::, _>>()?; let current_timestamp = SystemTime::now(); - for peer in host.peers.values() { - if !self.check_stale_peer(peer, current_timestamp)? { - self.check_suspend_peer(peer)?; + for peer in peers { + if !self.check_stale_peer(&peer, current_timestamp).await? { + self.check_suspend_peer(&peer).await?; } } } @@ -137,7 +172,7 @@ impl PeerController { loop { tokio::select! { _ = self.timeout_check_interval.next() => { - if let Err(e) = self.check_peers() { + if let Err(e) = self.check_peers().await { log::error!("Error while periodically checking peers: {:?}", e); } } @@ -148,6 +183,11 @@ impl PeerController { msg = self.request_rx.recv() => { match msg { Some(PeerControlRequest::AddPeer(peer)) => { + if let Err(e) = self.storage.insert_wireguard_peer(&peer, false).await { + log::error!("Could not insert peer into storage: {:?}", e); + self.response_tx.send(PeerControlResponse::AddPeer { success: false }).ok(); + continue; + } let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) { log::error!("Could not configure peer: {:?}", e); false @@ -158,6 +198,11 @@ impl PeerController { self.response_tx.send(PeerControlResponse::AddPeer { success }).ok(); } Some(PeerControlRequest::RemovePeer(peer_pubkey)) => { + if let Err(e) = self.storage.remove_wireguard_peer(&peer_pubkey.to_string()).await { + log::error!("Could not remove peer from storage: {:?}", e); + self.response_tx.send(PeerControlResponse::RemovePeer { success: false }).ok(); + continue; + } let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) { log::error!("Could not remove peer: {:?}", e); false @@ -168,6 +213,25 @@ impl PeerController { }; self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok(); } + Some(PeerControlRequest::QueryPeer(peer_pubkey)) => { + let (success, peer) = match self.storage.get_wireguard_peer(&peer_pubkey.to_string()).await { + Err(e) => { + log::error!("Could not query peer storage {e}"); + (false, None) + }, + Ok(None) => (true, None), + Ok(Some(storage_peer)) => { + match Peer::try_from(storage_peer) { + Ok(peer) => (true, Some(peer)), + Err(e) => { + log::error!("Could not parse storage peer {e}"); + (false, None) + } + } + }, + }; + self.response_tx.send(PeerControlResponse::QueryPeer { success, peer }).ok(); + } Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => { let msg = if self.suspended_peers.contains_key(&peer_pubkey) { PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 330f0a98244..e5f43bc54db 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -48,7 +48,7 @@ sqlx = { workspace = true, features = [ "sqlite", "macros", "migrate", - "time" + "time", ] } subtle-encoding = { workspace = true, features = ["bech32-preview"] } thiserror = { workspace = true } @@ -79,7 +79,8 @@ nym-credentials-interface = { path = "../common/credentials-interface" } nym-crypto = { path = "../common/crypto" } nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" } nym-ecash-double-spending = { path = "../common/ecash-double-spending" } -nym-gateway-requests = { path = "gateway-requests" } +nym-gateway-storage = { path = "../common/gateway-storage" } +nym-gateway-requests = { path = "../common/gateway-requests" } nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } nym-mixnode-common = { path = "../common/mixnode-common" } nym-network-defaults = { path = "../common/network-defaults" } @@ -108,7 +109,11 @@ sqlx = { workspace = true, features = [ ] } [features] -wireguard = ["nym-wireguard", "defguard_wireguard_rs"] +wireguard = [ + "nym-wireguard", + "defguard_wireguard_rs", + "nym-gateway-storage/wireguard", +] bin-deps = ["clap", 'nym-bin-common/output_format'] [package.metadata.deb] diff --git a/gateway/src/error.rs b/gateway/src/error.rs index b666a1e4b51..8dc005a0a1f 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::error::StorageError; use nym_authenticator::error::AuthenticatorError; +use nym_gateway_storage::error::StorageError; use nym_ip_packet_router::error::IpPacketRouterError; use nym_network_requester::error::{ClientCoreError, NetworkRequesterError}; use nym_validator_client::nyxd::error::NyxdError; diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index 1a7c8f29b57..72d0aa499d4 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -272,19 +272,3 @@ impl<'a> HttpApiBuilder<'a> { Ok(()) } } - -// pub(crate) fn start_http_api( -// gateway_config: &Config, -// network_requester_config: Option<&nym_network_requester::Config>, -// client_registry: Arc, -// identity_keypair: &identity::KeyPair, -// // TODO: this should be a wg specific key and not re-used sphinx -// sphinx_keypair: Arc, -// -// task_client: TaskClient, -// ) -> Result<(), GatewayError> { -// HttpApiBuilder::new(gateway_config, identity_keypair, sphinx_keypair) -// .with_wireguard_client_registry(client_registry) -// .with_network_requester(network_requester_config) -// .start(task_client) -// } 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 46f71b0e174..51117b2879a 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -1,33 +1,30 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::bandwidth::{Bandwidth, BandwidthError}; -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; -use crate::node::client_handling::websocket::connection_handler::ecash::ClientTicket; -use crate::node::client_handling::websocket::connection_handler::ClientBandwidth; -use crate::node::{ - client_handling::{ - websocket::{ - connection_handler::{ClientDetails, FreshHandler}, - message_receiver::{ - IsActive, IsActiveRequestReceiver, IsActiveResultSender, MixMessageReceiver, - }, +use crate::node::client_handling::{ + bandwidth::{Bandwidth, BandwidthError}, + websocket::{ + connection_handler::{ + ecash::error::EcashTicketError, ClientBandwidth, ClientDetails, FreshHandler, + }, + message_receiver::{ + IsActive, IsActiveRequestReceiver, IsActiveResultSender, MixMessageReceiver, }, - FREE_TESTNET_BANDWIDTH_VALUE, }, - storage::{error::StorageError, Storage}, + FREE_TESTNET_BANDWIDTH_VALUE, }; use futures::{ future::{FusedFuture, OptionFuture}, FutureExt, StreamExt, }; use nym_credentials::ecash::utils::{ecash_today, EcashTime}; -use nym_credentials_interface::CredentialSpendingData; +use nym_credentials_interface::{ClientTicket, CredentialSpendingData}; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_requests::{ types::{BinaryRequest, ServerResponse}, ClientControlRequest, GatewayRequestsError, SimpleGatewayRequestsError, }; +use nym_gateway_storage::{error::StorageError, Storage}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; use nym_validator_client::coconut::EcashApiError; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs b/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs index 0c7705b6ba4..16670fe1324 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs @@ -5,7 +5,6 @@ use crate::node::client_handling::bandwidth::Bandwidth; use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; use crate::node::client_handling::websocket::connection_handler::ecash::helpers::for_each_api_concurrent; use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState; -use crate::node::storage::Storage; use crate::GatewayError; use cosmwasm_std::Fraction; use cw_utils::ThresholdResponse; @@ -13,7 +12,8 @@ use futures::channel::mpsc::UnboundedReceiver; use futures::{Stream, StreamExt}; use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody}; -use nym_credentials_interface::CredentialSpendingData; +use nym_credentials_interface::ClientTicket; +use nym_gateway_storage::Storage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{ EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient, @@ -47,21 +47,6 @@ impl ProposalResult { } } -#[derive(Clone)] -pub struct ClientTicket { - pub spending_data: CredentialSpendingData, - pub ticket_id: i64, -} - -impl ClientTicket { - pub fn new(spending_data: CredentialSpendingData, ticket_id: i64) -> Self { - ClientTicket { - spending_data, - ticket_id, - } - } -} - struct PendingVerification { ticket: ClientTicket, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs b/gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs index 1494598a55e..c39ad897208 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::node::storage::error::StorageError; +use nym_gateway_storage::error::StorageError; use nym_validator_client::coconut::EcashApiError; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::error::NyxdError; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs index 40d82abd919..69c3d468004 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState; -use crate::node::storage::Storage; use crate::GatewayError; use credential_sender::CredentialHandler; use double_spending::DoubleSpendingDetector; use futures::channel::mpsc::{self, UnboundedSender}; use nym_credentials::CredentialSpendingData; -use nym_credentials_interface::{CompactEcashError, NymPayInfo, VerificationKeyAuth}; +use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth}; +use nym_gateway_storage::Storage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::DirectSigningHttpRpcNyxdClient; use time::OffsetDateTime; @@ -17,7 +17,6 @@ use tracing::error; use crate::node::client_handling::websocket::connection_handler::ecash::credential_sender::CredentialHandlerConfig; use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; -pub use credential_sender::ClientTicket; pub(crate) mod credential_sender; pub(crate) mod double_spending; 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 611f25c22ae..09865328964 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -6,6 +6,7 @@ use futures::{ channel::{mpsc, oneshot}, SinkExt, StreamExt, }; +use nym_credentials_interface::AvailableBandwidth; use nym_crypto::asymmetric::identity; use nym_gateway_requests::authentication::encrypted_address::{ EncryptedAddressBytes, EncryptedAddressConversionError, @@ -28,19 +29,16 @@ use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; use tracing::*; use crate::node::client_handling::websocket::common_state::CommonHandlerState; -use crate::node::client_handling::websocket::connection_handler::AvailableBandwidth; -use crate::node::{ - client_handling::{ - active_clients::ActiveClientsStore, - websocket::{ - connection_handler::{ - AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, - }, - message_receiver::{IsActive, IsActiveRequestSender}, +use crate::node::client_handling::{ + active_clients::ActiveClientsStore, + websocket::{ + connection_handler::{ + AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, }, + message_receiver::{IsActive, IsActiveRequestSender}, }, - storage::{error::StorageError, Storage}, }; +use nym_gateway_storage::{error::StorageError, Storage}; #[derive(Debug, Error)] pub(crate) enum InitialAuthenticationError { @@ -564,7 +562,8 @@ where .storage .get_available_bandwidth(client_id) .await? - .into(); + .map(From::from) + .unwrap_or_default(); let bandwidth_remaining = if available_bandwidth.expired() { self.expire_bandwidth(client_id).await?; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index 94ff8398f7a..ecb0aa48d7b 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; -use crate::node::storage::Storage; -use nym_credentials::ecash::utils::ecash_today; +use nym_credentials_interface::AvailableBandwidth; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_gateway_requests::ServerResponse; +use nym_gateway_storage::Storage; use nym_sphinx::DestinationAddressBytes; use nym_task::TaskClient; use rand::{CryptoRng, Rng}; @@ -152,27 +152,6 @@ impl<'a> From<&'a Config> for BandwidthFlushingBehaviourConfig { } } -#[derive(Debug, Clone, Copy)] -pub(crate) struct AvailableBandwidth { - pub(crate) bytes: i64, - pub(crate) expiration: OffsetDateTime, -} - -impl AvailableBandwidth { - pub(crate) fn expired(&self) -> bool { - self.expiration < ecash_today() - } -} - -impl Default for AvailableBandwidth { - fn default() -> Self { - Self { - bytes: 0, - expiration: OffsetDateTime::UNIX_EPOCH, - } - } -} - pub(crate) struct ClientBandwidth { pub(crate) bandwidth: AvailableBandwidth, pub(crate) last_flushed: OffsetDateTime, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 383e1b4b7d2..8c0b98a4401 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -4,7 +4,7 @@ use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket::common_state::CommonHandlerState; use crate::node::client_handling::websocket::connection_handler::FreshHandler; -use crate::node::storage::Storage; +use nym_gateway_storage::Storage; use nym_mixnet_client::forwarder::MixForwardingSender; use rand::rngs::OsRng; use std::net::SocketAddr; diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index 0c920181e03..854254e215e 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -4,8 +4,8 @@ use crate::config::Config; use crate::error::GatewayError; -use crate::node::storage::PersistentStorage; use nym_crypto::asymmetric::encryption; +use nym_gateway_storage::PersistentStorage; use nym_pemstore::traits::PemStorableKeyPair; use nym_pemstore::KeyPairPath; diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 4cd1f201602..7d6649530f6 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -4,10 +4,9 @@ use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket::message_receiver::MixMessageSender; use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor; -use crate::node::storage::error::StorageError; -use crate::node::storage::Storage; use futures::channel::mpsc::SendError; use futures::StreamExt; +use nym_gateway_storage::{error::StorageError, Storage}; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_mixnode_common::packet_processor::processor::ProcessedFinalHop; use nym_sphinx::forwarding::packet::MixPacket; diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 39d21cd8146..8f815f6068c 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; -use crate::node::storage::Storage; +use nym_gateway_storage::Storage; use nym_task::TaskClient; use std::net::SocketAddr; use std::process; diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index a306a82779a..5de29f806ad 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -34,10 +34,9 @@ use tracing::*; pub(crate) mod client_handling; pub(crate) mod helpers; pub(crate) mod mixnet_handling; -pub(crate) mod storage; use crate::node::client_handling::websocket::connection_handler::ecash::credential_sender::CredentialHandlerConfig; -pub use storage::{PersistentStorage, Storage}; +pub use nym_gateway_storage::{PersistentStorage, Storage}; // TODO: should this struct live here? struct StartedNetworkRequester { @@ -251,7 +250,10 @@ impl Gateway { &mut self, forwarding_channel: MixForwardingSender, shutdown: TaskClient, - ) -> Result> { + ) -> Result> + where + St: Storage + Clone + 'static, + { let opts = self .authenticator_opts .as_ref() @@ -302,8 +304,13 @@ impl Gateway { MessageRouter::new(auth_mix_receiver, packet_router) .start_with_shutdown(router_shutdown); - let wg_api = - nym_wireguard::start_wireguard(shutdown, wireguard_data, peer_response_tx).await?; + let wg_api = nym_wireguard::start_wireguard( + self.storage.clone(), + shutdown, + wireguard_data, + peer_response_tx, + ) + .await?; Ok(StartedAuthenticator { wg_api, diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs deleted file mode 100644 index 5927f09a427..00000000000 --- a/gateway/src/node/storage/models.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::node::client_handling::websocket::connection_handler::ecash::ClientTicket; -use crate::node::client_handling::websocket::connection_handler::AvailableBandwidth; -use crate::node::storage::error::StorageError; -use nym_credentials_interface::CredentialSpendingData; -use sqlx::FromRow; -use time::OffsetDateTime; - -pub struct PersistedSharedKeys { - #[allow(dead_code)] - pub(crate) id: i64, - - #[allow(dead_code)] - pub(crate) client_address_bs58: String, - pub(crate) derived_aes128_ctr_blake3_hmac_keys_bs58: String, -} - -pub struct StoredMessage { - pub(crate) id: i64, - #[allow(dead_code)] - pub(crate) client_address_bs58: String, - pub(crate) content: Vec, -} - -#[derive(Debug, Clone, FromRow)] -pub struct PersistedBandwidth { - #[allow(dead_code)] - pub(crate) client_id: i64, - pub(crate) available: i64, - pub(crate) expiration: Option, -} - -impl From for AvailableBandwidth { - fn from(value: PersistedBandwidth) -> Self { - AvailableBandwidth { - bytes: value.available, - expiration: value.expiration.unwrap_or(OffsetDateTime::UNIX_EPOCH), - } - } -} - -impl From> for AvailableBandwidth { - fn from(value: Option) -> Self { - match value { - None => AvailableBandwidth::default(), - Some(b) => b.into(), - } - } -} - -#[derive(FromRow)] -pub struct VerifiedTicket { - pub(crate) serial_number: Vec, - pub(crate) ticket_id: i64, -} - -#[derive(FromRow)] -pub struct RedemptionProposal { - pub(crate) proposal_id: i64, - pub(crate) created_at: OffsetDateTime, -} - -#[derive(FromRow)] -pub struct UnverifiedTicketData { - pub(crate) data: Vec, - pub(crate) ticket_id: i64, -} - -impl TryFrom for ClientTicket { - type Error = StorageError; - - fn try_from(value: UnverifiedTicketData) -> Result { - Ok(ClientTicket { - spending_data: CredentialSpendingData::try_from_bytes(&value.data).map_err(|_| { - StorageError::MalformedStoredTicketData { - ticket_id: value.ticket_id, - } - })?, - ticket_id: value.ticket_id, - }) - } -} diff --git a/nym-node/src/cli/commands/migrate.rs b/nym-node/src/cli/commands/migrate.rs index bf9e88d370f..08d9d0b2e0e 100644 --- a/nym-node/src/cli/commands/migrate.rs +++ b/nym-node/src/cli/commands/migrate.rs @@ -465,6 +465,7 @@ async fn migrate_gateway(mut args: Args) -> Result<(), NymNodeError> { .unwrap_or_default(), }, }, + debug: Default::default(), }), ) .build(); diff --git a/nym-node/src/config/exit_gateway.rs b/nym-node/src/config/exit_gateway.rs index 1ad3e42f022..4c3c55fad2d 100644 --- a/nym-node/src/config/exit_gateway.rs +++ b/nym-node/src/config/exit_gateway.rs @@ -34,6 +34,28 @@ pub struct ExitGatewayConfig { pub network_requester: NetworkRequester, pub ip_packet_router: IpPacketRouter, + + #[serde(default)] + pub debug: Debug, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl Debug { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for Debug { + fn default() -> Self { + Debug { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } } impl ExitGatewayConfig { @@ -49,6 +71,7 @@ impl ExitGatewayConfig { .expect("invalid default exit policy URL"), network_requester: Default::default(), ip_packet_router: Default::default(), + debug: Default::default(), } } } diff --git a/nym-node/src/config/old_configs/mod.rs b/nym-node/src/config/old_configs/mod.rs index d85e5839530..295cf46ed3c 100644 --- a/nym-node/src/config/old_configs/mod.rs +++ b/nym-node/src/config/old_configs/mod.rs @@ -3,6 +3,8 @@ mod old_config_v1; mod old_config_v2; +mod old_config_v3; pub use old_config_v1::try_upgrade_config_v1; pub use old_config_v2::try_upgrade_config_v2; +pub use old_config_v3::try_upgrade_config_v3; diff --git a/nym-node/src/config/old_configs/old_config_v2.rs b/nym-node/src/config/old_configs/old_config_v2.rs index d9c2254407f..28e35e66bcb 100644 --- a/nym-node/src/config/old_configs/old_config_v2.rs +++ b/nym-node/src/config/old_configs/old_config_v2.rs @@ -4,9 +4,6 @@ #![allow(dead_code)] use crate::{config::*, error::KeyIOFailure}; -use entry_gateway::Debug as EntryGatewayConfigDebug; -use exit_gateway::{IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug}; -use mixnode::{Verloc, VerlocDebug}; use nym_client_core_config_types::DebugConfig as ClientDebugConfig; use nym_config::serde_helpers::de_maybe_port; use nym_crypto::asymmetric::{ed25519, x25519}; @@ -16,10 +13,7 @@ use nym_network_requester::{ }; use nym_pemstore::{load_key, store_key, store_keypair}; use nym_sphinx_acknowledgements::AckKey; -use persistence::{ - AuthenticatorPaths, EntryGatewayPaths, ExitGatewayPaths, IpPacketRouterPaths, KeysPaths, - MixnodePaths, NetworkRequesterPaths, WireguardPaths, -}; +use old_configs::old_config_v3::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; @@ -90,12 +84,12 @@ pub enum NodeModeV2 { ExitGateway, } -impl From for NodeMode { +impl From for NodeModeV3 { fn from(config: NodeModeV2) -> Self { match config { - NodeModeV2::Mixnode => NodeMode::Mixnode, - NodeModeV2::EntryGateway => NodeMode::EntryGateway, - NodeModeV2::ExitGateway => NodeMode::ExitGateway, + NodeModeV2::Mixnode => NodeModeV3::Mixnode, + NodeModeV2::EntryGateway => NodeModeV3::EntryGateway, + NodeModeV2::ExitGateway => NodeModeV3::ExitGateway, } } } @@ -754,7 +748,7 @@ impl ConfigV2 { } pub async fn initialise( - paths: &AuthenticatorPaths, + paths: &AuthenticatorPathsV3, public_key: nym_crypto::asymmetric::identity::PublicKey, ) -> Result<(), NymNodeError> { let mut rng = OsRng; @@ -795,7 +789,7 @@ pub async fn initialise( pub async fn try_upgrade_config_v2>( path: P, prev_config: Option, -) -> Result { +) -> Result { tracing::debug!("Updating from 1.1.3"); let old_cfg = if let Some(prev_config) = prev_config { prev_config @@ -803,7 +797,7 @@ pub async fn try_upgrade_config_v2>( ConfigV2::read_from_path(&path)? }; - let authenticator_paths = AuthenticatorPaths::new( + let authenticator_paths = AuthenticatorPathsV3::new( old_cfg .exit_gateway .storage_paths @@ -813,20 +807,20 @@ pub async fn try_upgrade_config_v2>( .ok_or(NymNodeError::DataDirDerivationFailure)?, ); - let cfg = Config { + let cfg = ConfigV3 { save_path: old_cfg.save_path, id: old_cfg.id, mode: old_cfg.mode.into(), - host: Host { + host: HostV3 { public_ips: old_cfg.host.public_ips, hostname: old_cfg.host.hostname, location: old_cfg.host.location, }, - mixnet: Mixnet { + mixnet: MixnetV3 { bind_address: old_cfg.mixnet.bind_address, nym_api_urls: old_cfg.mixnet.nym_api_urls, nyxd_urls: old_cfg.mixnet.nyxd_urls, - debug: MixnetDebug { + debug: MixnetDebugV3 { packet_forwarding_initial_backoff: old_cfg .mixnet .debug @@ -840,8 +834,8 @@ pub async fn try_upgrade_config_v2>( unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, }, }, - storage_paths: NymNodePaths { - keys: KeysPaths { + storage_paths: NymNodePathsV3 { + keys: KeysPathsV3 { private_ed25519_identity_key_file: old_cfg .storage_paths .keys @@ -869,7 +863,7 @@ pub async fn try_upgrade_config_v2>( }, description: old_cfg.storage_paths.description, }, - http: Http { + http: HttpV3 { bind_address: old_cfg.http.bind_address, landing_page_assets_path: old_cfg.http.landing_page_assets_path, access_token: old_cfg.http.access_token, @@ -877,13 +871,13 @@ pub async fn try_upgrade_config_v2>( expose_system_hardware: old_cfg.http.expose_system_hardware, expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, }, - wireguard: Wireguard { + wireguard: WireguardV3 { enabled: old_cfg.wireguard.enabled, bind_address: old_cfg.wireguard.bind_address, private_ip: old_cfg.wireguard.private_ip, announced_port: old_cfg.wireguard.announced_port, private_network_prefix: old_cfg.wireguard.private_network_prefix, - storage_paths: WireguardPaths { + storage_paths: WireguardPathsV3 { private_diffie_hellman_key_file: old_cfg .wireguard .storage_paths @@ -894,11 +888,11 @@ pub async fn try_upgrade_config_v2>( .public_diffie_hellman_key_file, }, }, - mixnode: MixnodeConfig { - storage_paths: MixnodePaths {}, - verloc: Verloc { + mixnode: MixnodeConfigV3 { + storage_paths: MixnodePathsV3 {}, + verloc: VerlocV3 { bind_address: old_cfg.mixnode.verloc.bind_address, - debug: VerlocDebug { + debug: VerlocDebugV3 { packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, @@ -908,13 +902,13 @@ pub async fn try_upgrade_config_v2>( retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout, }, }, - debug: mixnode::Debug { + debug: DebugV3 { node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay, node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay, }, }, - entry_gateway: EntryGatewayConfig { - storage_paths: EntryGatewayPaths { + entry_gateway: EntryGatewayConfigV3 { + storage_paths: EntryGatewayPathsV3 { clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage, cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, authenticator: authenticator_paths.clone(), @@ -923,15 +917,13 @@ pub async fn try_upgrade_config_v2>( bind_address: old_cfg.entry_gateway.bind_address, announce_ws_port: old_cfg.entry_gateway.announce_ws_port, announce_wss_port: old_cfg.entry_gateway.announce_wss_port, - debug: EntryGatewayConfigDebug { + debug: EntryGatewayConfigDebugV3 { message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, - // \/ ADDED - zk_nym_tickets: Default::default(), }, }, - exit_gateway: ExitGatewayConfig { - storage_paths: ExitGatewayPaths { - network_requester: NetworkRequesterPaths { + exit_gateway: ExitGatewayConfigV3 { + storage_paths: ExitGatewayPathsV3 { + network_requester: NetworkRequesterPathsV3 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -968,7 +960,7 @@ pub async fn try_upgrade_config_v2>( .network_requester .gateway_registrations, }, - ip_packet_router: IpPacketRouterPaths { + ip_packet_router: IpPacketRouterPathsV3 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1009,8 +1001,8 @@ pub async fn try_upgrade_config_v2>( }, open_proxy: old_cfg.exit_gateway.open_proxy, upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, - network_requester: NetworkRequester { - debug: NetworkRequesterDebug { + network_requester: NetworkRequesterV3 { + debug: NetworkRequesterDebugV3 { enabled: old_cfg.exit_gateway.network_requester.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1020,8 +1012,8 @@ pub async fn try_upgrade_config_v2>( client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, }, }, - ip_packet_router: IpPacketRouter { - debug: IpPacketRouterDebug { + ip_packet_router: IpPacketRouterV3 { + debug: IpPacketRouterDebugV3 { enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1033,7 +1025,7 @@ pub async fn try_upgrade_config_v2>( }, }, authenticator: Default::default(), - logging: LoggingSettings {}, + logging: LoggingSettingsV3 {}, }; let public_key = load_key( diff --git a/nym-node/src/config/old_configs/old_config_v3.rs b/nym-node/src/config/old_configs/old_config_v3.rs new file mode 100644 index 00000000000..809955e5ee0 --- /dev/null +++ b/nym-node/src/config/old_configs/old_config_v3.rs @@ -0,0 +1,1282 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![allow(dead_code)] + +use crate::{config::*, error::KeyIOFailure}; +use entry_gateway::Debug as EntryGatewayConfigDebug; +use exit_gateway::{IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug}; +use mixnode::{Verloc, VerlocDebug}; +use nym_client_core_config_types::{ + disk_persistence::{ClientKeysPaths, CommonClientPaths}, + DebugConfig as ClientDebugConfig, +}; +use nym_config::serde_helpers::de_maybe_port; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_network_requester::{ + set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, + GatewayDetails, +}; +use nym_pemstore::{store_key, store_keypair}; +use nym_sphinx_acknowledgements::AckKey; +use persistence::*; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPathsV3 { + pub private_diffie_hellman_key_file: PathBuf, + pub public_diffie_hellman_key_file: PathBuf, +} + +impl WireguardPathsV3 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + WireguardPathsV3 { + private_diffie_hellman_key_file: data_dir + .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), + public_diffie_hellman_key_file: data_dir + .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), + } + } + + pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_diffie_hellman_key_file, + &self.public_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardV3 { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51822` + pub bind_address: SocketAddr, + + /// Ip address of the private wireguard network. + /// default: `10.1.0.0` + pub private_ip: IpAddr, + + /// Port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub announced_port: u16, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. + /// The maximum value for IPv4 is 32 and for IPv6 is 128 + pub private_network_prefix: u8, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: WireguardPathsV3, +} + +// a temporary solution until all "types" are run at the same time +#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum NodeModeV3 { + #[default] + #[clap(alias = "mix")] + Mixnode, + + #[clap(alias = "entry", alias = "gateway")] + EntryGateway, + + #[clap(alias = "exit")] + ExitGateway, +} + +impl From for NodeMode { + fn from(config: NodeModeV3) -> Self { + match config { + NodeModeV3::Mixnode => NodeMode::Mixnode, + NodeModeV3::EntryGateway => NodeMode::EntryGateway, + NodeModeV3::ExitGateway => NodeMode::ExitGateway, + } + } +} + +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HostV3 { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + /// If no values are provided, when this node gets included in the network, + /// its ip addresses will be populated by whatever value is resolved by associated nym-api. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_stringified")] + pub hostname: Option, + + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[serde(deserialize_with = "de_maybe_stringified")] + pub location: Option, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetDebugV3 { + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_initial_backoff: Duration, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_maximum_backoff: Duration, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + #[serde(with = "humantime_serde")] + pub initial_connection_timeout: Duration, + + /// Maximum number of packets that can be stored waiting to get sent to a particular connection. + pub maximum_connection_buffer_size: usize, + + /// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented) + pub unsafe_disable_noise: bool, +} + +impl MixnetDebugV3 { + const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); + const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); + const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); + const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; +} + +impl Default for MixnetDebugV3 { + fn default() -> Self { + MixnetDebugV3 { + packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + // to be changed by @SW once the implementation is there + unsafe_disable_noise: true, + } + } +} + +impl Default for MixnetV3 { + fn default() -> Self { + // SAFETY: + // our hardcoded values should always be valid + #[allow(clippy::expect_used)] + // is if there's anything set in the environment, otherwise fallback to mainnet + let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) { + parse_urls(&env_value) + } else { + vec![mainnet::NYM_API.parse().expect("Invalid default API URL")] + }; + + #[allow(clippy::expect_used)] + let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) { + parse_urls(&env_value) + } else { + vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")] + }; + + MixnetV3 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT), + nym_api_urls, + nyxd_urls, + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetV3 { + /// Address this node will bind to for listening for mixnet packets + /// default: `0.0.0.0:1789` + pub bind_address: SocketAddr, + + /// Addresses to nym APIs from which the node gets the view of the network. + pub nym_api_urls: Vec, + + /// Addresses to nyxd which the node uses to interact with the nyx chain. + pub nyxd_urls: Vec, + + #[serde(default)] + pub debug: MixnetDebugV3, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPathsV3 { + /// Path to file containing ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing x25519 sphinx private key. + pub private_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 sphinx public key. + pub public_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 noise private key. + pub private_x25519_noise_key_file: PathBuf, + + /// Path to file containing x25519 noise public key. + pub public_x25519_noise_key_file: PathBuf, +} + +impl KeysPathsV3 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + + KeysPathsV3 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_sphinx_key_file: data_dir + .join(DEFAULT_X25519_PRIVATE_SPHINX_KEY_FILENAME), + public_x25519_sphinx_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_SPHINX_KEY_FILENAME), + private_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME), + public_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_sphinx_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_sphinx_key_file, + &self.public_x25519_sphinx_key_file, + ) + } + + pub fn x25519_noise_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_noise_key_file, + &self.public_x25519_noise_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NymNodePathsV3 { + pub keys: KeysPathsV3, + + /// Path to a file containing basic node description: human-readable name, website, details, etc. + pub description: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HttpV3 { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8080` + pub bind_address: SocketAddr, + + /// Path to assets directory of custom landing page of this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub landing_page_assets_path: Option, + + /// An optional bearer token for accessing certain http endpoints. + /// Currently only used for obtaining mixnode's stats. + #[serde(default)] + pub access_token: Option, + + /// Specify whether basic system information should be exposed. + /// default: true + pub expose_system_info: bool, + + /// Specify whether basic system hardware information should be exposed. + /// This option is superseded by `expose_system_info` + /// default: true + pub expose_system_hardware: bool, + + /// Specify whether detailed system crypto hardware information should be exposed. + /// This option is superseded by `expose_system_hardware` + /// default: true + pub expose_crypto_hardware: bool, +} + +impl Default for HttpV3 { + fn default() -> Self { + HttpV3 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT), + landing_page_assets_path: None, + access_token: None, + expose_system_info: true, + expose_system_hardware: true, + expose_crypto_hardware: true, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodePathsV3 {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DebugV3 { + /// Delay between each subsequent node statistics being logged to the console + #[serde(with = "humantime_serde")] + pub node_stats_logging_delay: Duration, + + /// Delay between each subsequent node statistics being updated + #[serde(with = "humantime_serde")] + pub node_stats_updating_delay: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocDebugV3 { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocV3 { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + pub bind_address: SocketAddr, + + #[serde(default)] + pub debug: VerlocDebugV3, +} + +impl VerlocDebugV3 { + const DEFAULT_PACKETS_PER_NODE: usize = 100; + const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); + const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); + const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); + const DEFAULT_BATCH_SIZE: usize = 50; + const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); + const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); +} + +impl Default for VerlocDebugV3 { + fn default() -> Self { + VerlocDebugV3 { + packets_per_node: Self::DEFAULT_PACKETS_PER_NODE, + connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: Self::DEFAULT_PACKET_TIMEOUT, + delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE, + testing_interval: Self::DEFAULT_TESTING_INTERVAL, + retry_timeout: Self::DEFAULT_RETRY_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodeConfigV3 { + pub storage_paths: MixnodePathsV3, + + pub verloc: VerlocV3, + + #[serde(default)] + pub debug: DebugV3, +} + +impl DebugV3 { + const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); + const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); +} + +impl Default for DebugV3 { + fn default() -> Self { + DebugV3 { + node_stats_logging_delay: Self::DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: Self::DEFAULT_NODE_STATS_UPDATING_DELAY, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayPathsV3 { + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + pub clients_storage: PathBuf, + + /// Path to file containing cosmos account mnemonic used for zk-nym redemption. + pub cosmos_mnemonic: PathBuf, + + pub authenticator: AuthenticatorPathsV3, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigDebugV3 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl EntryGatewayConfigDebugV3 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for EntryGatewayConfigDebugV3 { + fn default() -> Self { + EntryGatewayConfigDebugV3 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigV3 { + pub storage_paths: EntryGatewayPathsV3, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, + + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + pub bind_address: SocketAddr, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + /// default: None + #[serde(deserialize_with = "de_maybe_port")] + pub announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_wss_port: Option, + + #[serde(default)] + pub debug: EntryGatewayConfigDebugV3, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkRequesterPathsV3 { + /// Path to file containing network requester ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct IpPacketRouterPathsV3 { + /// Path to file containing ip packet router ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct AuthenticatorPathsV3 { + /// Path to file containing authenticator ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +impl AuthenticatorPathsV3 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + AuthenticatorPathsV3 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PRIVATE_DH_KEY_FILENAME), + public_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PUBLIC_DH_KEY_FILENAME), + ack_key_file: data_dir.join(DEFAULT_AUTH_ACK_KEY_FILENAME), + reply_surb_database: data_dir.join(DEFAULT_AUTH_REPLY_SURB_DB_FILENAME), + gateway_registrations: data_dir.join(DEFAULT_AUTH_GATEWAYS_DB_FILENAME), + } + } + + pub fn to_common_client_paths(&self) -> CommonClientPaths { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: self.private_ed25519_identity_key_file.clone(), + public_identity_key_file: self.public_ed25519_identity_key_file.clone(), + private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(), + public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(), + ack_key_file: self.ack_key_file.clone(), + }, + gateway_registrations: self.gateway_registrations.clone(), + + // not needed for embedded providers + credentials_database: Default::default(), + reply_surb_database: self.reply_surb_database.clone(), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_diffie_hellman_key_file, + &self.public_x25519_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayPathsV3 { + pub network_requester: NetworkRequesterPathsV3, + + pub ip_packet_router: IpPacketRouterPathsV3, + + pub authenticator: AuthenticatorPathsV3, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct AuthenticatorV3 { + #[serde(default)] + pub debug: AuthenticatorDebugV3, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct AuthenticatorDebugV3 { + /// Specifies whether authenticator service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run + /// the authenticator. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for AuthenticatorDebugV3 { + fn default() -> Self { + AuthenticatorDebugV3 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for AuthenticatorV3 { + fn default() -> Self { + AuthenticatorV3 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebugV3 { + /// Specifies whether ip packet routing service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for IpPacketRouterDebugV3 { + fn default() -> Self { + IpPacketRouterDebugV3 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouterV3 { + #[serde(default)] + pub debug: IpPacketRouterDebugV3, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouterV3 { + fn default() -> Self { + IpPacketRouterV3 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebugV3 { + /// Specifies whether network requester service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for NetworkRequesterDebugV3 { + fn default() -> Self { + NetworkRequesterDebugV3 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterV3 { + #[serde(default)] + pub debug: NetworkRequesterDebugV3, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV3 { + fn default() -> Self { + NetworkRequesterV3 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayConfigV3 { + pub storage_paths: ExitGatewayPathsV3, + + /// specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + pub open_proxy: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + pub upstream_exit_policy_url: Url, + + pub network_requester: NetworkRequesterV3, + + pub ip_packet_router: IpPacketRouterV3, +} + +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV3 { + // well, we need to implement something here at some point... +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV3 { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + /// Human-readable ID of this particular node. + pub id: String, + + /// Current mode of this nym-node. + /// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) + pub mode: NodeModeV3, + + pub host: HostV3, + + pub mixnet: MixnetV3, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePathsV3, + + #[serde(default)] + pub http: HttpV3, + + pub wireguard: WireguardV3, + + pub mixnode: MixnodeConfigV3, + + pub entry_gateway: EntryGatewayConfigV3, + + pub exit_gateway: ExitGatewayConfigV3, + + pub authenticator: AuthenticatorV3, + + #[serde(default)] + pub logging: LoggingSettingsV3, +} + +impl NymConfigTemplate for ConfigV3 { + fn template(&self) -> &'static str { + CONFIG_TEMPLATE + } +} + +impl ConfigV3 { + pub fn save(&self) -> Result<(), NymNodeError> { + let save_location = self.save_location(); + debug!( + "attempting to save config file to '{}'", + save_location.display() + ); + save_formatted_config_to_file(self, &save_location).map_err(|source| { + NymNodeError::ConfigSaveFailure { + id: self.id.clone(), + path: save_location, + source, + } + }) + } + + pub fn save_location(&self) -> PathBuf { + self.save_path + .clone() + .unwrap_or(self.default_save_location()) + } + + pub fn default_save_location(&self) -> PathBuf { + default_config_filepath(&self.id) + } + + pub fn default_data_directory>(config_path: P) -> Result { + let config_path = config_path.as_ref(); + + // we got a proper path to the .toml file + let Some(config_dir) = config_path.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + let Some(config_dir_name) = config_dir.file_name() else { + error!( + "could not obtain parent directory name of '{}'. Have you used relative paths?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + if config_dir_name != DEFAULT_CONFIG_DIR { + error!( + "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", + config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") + ); + return Err(NymNodeError::DataDirDerivationFailure); + } + + let Some(node_dir) = config_dir.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_dir.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + Ok(node_dir.join(DEFAULT_DATA_DIR)) + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> Result { + let path = path.as_ref(); + let mut loaded: ConfigV3 = + read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure { + path: path.to_path_buf(), + source, + })?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + pub fn read_from_toml_file>(path: P) -> Result { + Self::read_from_path(path) + } +} + +pub async fn initialise( + paths: &AuthenticatorPaths, + public_key: nym_crypto::asymmetric::identity::PublicKey, +) -> Result<(), NymNodeError> { + let mut rng = OsRng; + let ed25519_keys = ed25519::KeyPair::new(&mut rng); + let x25519_keys = x25519::KeyPair::new(&mut rng); + let aes128ctr_key = AckKey::new(&mut rng); + let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); + + store_keypair(&ed25519_keys, &paths.ed25519_identity_storage_paths()).map_err(|e| { + KeyIOFailure::KeyPairStoreFailure { + keys: "ed25519-identity".to_string(), + paths: paths.ed25519_identity_storage_paths(), + err: e, + } + })?; + store_keypair(&x25519_keys, &paths.x25519_diffie_hellman_storage_paths()).map_err(|e| { + KeyIOFailure::KeyPairStoreFailure { + keys: "x25519-dh".to_string(), + paths: paths.x25519_diffie_hellman_storage_paths(), + err: e, + } + })?; + store_key(&aes128ctr_key, &paths.ack_key_file).map_err(|e| KeyIOFailure::KeyStoreFailure { + key: "ack".to_string(), + path: paths.ack_key_file.clone(), + err: e, + })?; + + // insert all required information into the gateways store + // (I hate that we have to do it, but that's currently the simplest thing to do) + let storage = setup_fs_gateways_storage(&paths.gateway_registrations).await?; + store_gateway_details(&storage, &gateway_details).await?; + set_active_gateway(&storage, &gateway_details.gateway_id().to_base58_string()).await?; + + Ok(()) +} + +pub async fn try_upgrade_config_v3>( + path: P, + prev_config: Option, +) -> Result { + tracing::debug!("Updating from 1.1.4"); + let old_cfg = if let Some(prev_config) = prev_config { + prev_config + } else { + ConfigV3::read_from_path(&path)? + }; + + let exit_gateway_paths = ExitGatewayPaths::new( + old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_ed25519_identity_key_file + .parent() + .ok_or(NymNodeError::DataDirDerivationFailure)?, + ); + + let cfg = Config { + save_path: old_cfg.save_path, + id: old_cfg.id, + mode: old_cfg.mode.into(), + host: Host { + public_ips: old_cfg.host.public_ips, + hostname: old_cfg.host.hostname, + location: old_cfg.host.location, + }, + mixnet: Mixnet { + bind_address: old_cfg.mixnet.bind_address, + nym_api_urls: old_cfg.mixnet.nym_api_urls, + nyxd_urls: old_cfg.mixnet.nyxd_urls, + debug: MixnetDebug { + packet_forwarding_initial_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size, + unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, + }, + }, + storage_paths: NymNodePaths { + keys: KeysPaths { + private_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .public_ed25519_identity_key_file, + private_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .private_x25519_sphinx_key_file, + public_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .public_x25519_sphinx_key_file, + private_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .private_x25519_noise_key_file, + public_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .public_x25519_noise_key_file, + }, + description: old_cfg.storage_paths.description, + }, + http: Http { + bind_address: old_cfg.http.bind_address, + landing_page_assets_path: old_cfg.http.landing_page_assets_path, + access_token: old_cfg.http.access_token, + expose_system_info: old_cfg.http.expose_system_info, + expose_system_hardware: old_cfg.http.expose_system_hardware, + expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, + }, + wireguard: Wireguard { + enabled: old_cfg.wireguard.enabled, + bind_address: old_cfg.wireguard.bind_address, + private_ip: old_cfg.wireguard.private_ip, + announced_port: old_cfg.wireguard.announced_port, + private_network_prefix: old_cfg.wireguard.private_network_prefix, + storage_paths: WireguardPaths { + private_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .private_diffie_hellman_key_file, + public_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .public_diffie_hellman_key_file, + }, + }, + mixnode: MixnodeConfig { + storage_paths: MixnodePaths {}, + verloc: Verloc { + bind_address: old_cfg.mixnode.verloc.bind_address, + debug: VerlocDebug { + packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, + connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, + packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, + delay_between_packets: old_cfg.mixnode.verloc.debug.delay_between_packets, + tested_nodes_batch_size: old_cfg.mixnode.verloc.debug.tested_nodes_batch_size, + testing_interval: old_cfg.mixnode.verloc.debug.testing_interval, + retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout, + }, + }, + debug: mixnode::Debug { + node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay, + node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay, + }, + }, + entry_gateway: EntryGatewayConfig { + storage_paths: EntryGatewayPaths { + clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage, + cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .entry_gateway + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .entry_gateway + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + enforce_zk_nyms: old_cfg.entry_gateway.enforce_zk_nyms, + bind_address: old_cfg.entry_gateway.bind_address, + announce_ws_port: old_cfg.entry_gateway.announce_ws_port, + announce_wss_port: old_cfg.entry_gateway.announce_wss_port, + debug: EntryGatewayConfigDebug { + message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, + // \/ ADDED + zk_nym_tickets: Default::default(), + }, + }, + exit_gateway: ExitGatewayConfig { + storage_paths: ExitGatewayPaths { + clients_storage: exit_gateway_paths.clients_storage, + network_requester: NetworkRequesterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .network_requester + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .network_requester + .gateway_registrations, + }, + ip_packet_router: IpPacketRouterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .gateway_registrations, + }, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + open_proxy: old_cfg.exit_gateway.open_proxy, + upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, + network_requester: NetworkRequester { + debug: NetworkRequesterDebug { + enabled: old_cfg.exit_gateway.network_requester.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .network_requester + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, + }, + }, + ip_packet_router: IpPacketRouter { + debug: IpPacketRouterDebug { + enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .ip_packet_router + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug, + }, + }, + debug: Default::default(), + }, + authenticator: Default::default(), + logging: LoggingSettings {}, + }; + + Ok(cfg) +} diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs index 3861f0e7acb..3ae8595fe83 100644 --- a/nym-node/src/config/persistence.rs +++ b/nym-node/src/config/persistence.rs @@ -144,7 +144,7 @@ pub struct MixnodePaths {} #[serde(deny_unknown_fields)] pub struct EntryGatewayPaths { /// Path to sqlite database containing all persistent data: messages for offline clients, - /// derived shared keys and available client bandwidths. + /// derived shared keys, available client bandwidths and wireguard peers. pub clients_storage: PathBuf, /// Path to file containing cosmos account mnemonic used for zk-nym redemption. @@ -203,6 +203,10 @@ impl EntryGatewayPaths { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct ExitGatewayPaths { + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys, available client bandwidths and wireguard peers. + pub clients_storage: PathBuf, + pub network_requester: NetworkRequesterPaths, pub ip_packet_router: IpPacketRouterPaths, @@ -454,6 +458,7 @@ impl ExitGatewayPaths { pub fn new>(data_dir: P) -> Self { let data_dir = data_dir.as_ref(); ExitGatewayPaths { + clients_storage: data_dir.join(DEFAULT_CLIENTS_STORAGE_FILENAME), network_requester: NetworkRequesterPaths::new(data_dir), ip_packet_router: IpPacketRouterPaths::new(data_dir), authenticator: AuthenticatorPaths::new(data_dir), diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs index 0c2cf47f515..f1576f2fe29 100644 --- a/nym-node/src/config/template.rs +++ b/nym-node/src/config/template.rs @@ -170,7 +170,7 @@ announce_wss_port = {{#if entry_gateway.announce_wss_port }} {{ entry_gateway.an [entry_gateway.storage_paths] # Path to sqlite database containing all persistent data: messages for offline clients, -# derived shared keys and available client bandwidths. +# derived shared keys, available client bandwidths and wireguard peers. clients_storage = '{{ entry_gateway.storage_paths.clients_storage }}' # Path to file containing cosmos account mnemonic used for zk-nym redemption. @@ -221,6 +221,10 @@ upstream_exit_policy_url = '{{ exit_gateway.upstream_exit_policy_url }}' [exit_gateway.storage_paths] +# Path to sqlite database containing all persistent data: messages for offline clients, +# derived shared keys, available client bandwidths and wireguard peers. +clients_storage = '{{ exit_gateway.storage_paths.clients_storage }}' + [exit_gateway.storage_paths.network_requester] # Path to file containing network requester ed25519 identity private key. private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.network_requester.private_ed25519_identity_key_file }}' diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index 1eb51154143..4645b1a2856 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -9,7 +9,8 @@ use std::path::Path; // currently there are no upgrades async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { let cfg = try_upgrade_config_v1(path, None).await.ok(); - match try_upgrade_config_v2(path, cfg).await { + let cfg = try_upgrade_config_v2(path, cfg).await.ok(); + match try_upgrade_config_v3(path, cfg).await { Ok(cfg) => cfg.save(), Err(e) => { tracing::error!("Failed to finish upgrade - {e}"); diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index c851decf239..19242eda042 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -110,6 +110,8 @@ pub struct ExitGatewayData { auth_ed25519: ed25519::PublicKey, auth_x25519: x25519::PublicKey, + + client_storage: nym_gateway::node::PersistentStorage, } impl ExitGatewayData { @@ -217,7 +219,7 @@ impl ExitGatewayData { Ok(()) } - fn new(config: &ExitGatewayConfig) -> Result { + async fn new(config: &ExitGatewayConfig) -> Result { let nr_paths = &config.storage_paths.network_requester; let nr_ed25519 = load_key( &nr_paths.public_ed25519_identity_key_file, @@ -251,6 +253,13 @@ impl ExitGatewayData { "authenticator x25519", )?; + let client_storage = nym_gateway::node::PersistentStorage::init( + &config.storage_paths.clients_storage, + config.debug.message_retrieval_limit, + ) + .await + .map_err(nym_gateway::GatewayError::from)?; + Ok(ExitGatewayData { nr_ed25519, nr_x25519, @@ -258,6 +267,7 @@ impl ExitGatewayData { ipr_x25519, auth_ed25519, auth_x25519, + client_storage, }) } } @@ -459,7 +469,7 @@ impl NymNode { verloc_stats: Default::default(), mixnode: MixnodeData::new(&config.mixnode)?, entry_gateway: EntryGatewayData::new(&config.entry_gateway).await?, - exit_gateway: ExitGatewayData::new(&config.exit_gateway)?, + exit_gateway: ExitGatewayData::new(&config.exit_gateway).await?, wireguard: wireguard_data, config, accepted_operator_terms_and_conditions: false, @@ -594,7 +604,7 @@ impl NymNode { Some(config.auth_opts), self.ed25519_identity_keys.clone(), self.x25519_sphinx_keys.clone(), - self.entry_gateway.client_storage.clone(), + self.exit_gateway.client_storage.clone(), ); exit_gateway.disable_http_server(); exit_gateway.set_task_client(task_client); diff --git a/nym-node/src/wireguard/mod.rs b/nym-node/src/wireguard/mod.rs index 8c2b1fd10f2..03d1b32e4bb 100644 --- a/nym-node/src/wireguard/mod.rs +++ b/nym-node/src/wireguard/mod.rs @@ -5,4 +5,3 @@ // but let's start putting everything in here pub mod error; -pub mod types; diff --git a/nym-node/src/wireguard/types.rs b/nym-node/src/wireguard/types.rs deleted file mode 100644 index 00ddb7a663f..00000000000 --- a/nym-node/src/wireguard/types.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -// pub use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::*; -// pub use nym_wireguard_types::registration::{GatewayClientRegistry, PendingRegistrations}; diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index b9ed55e096a..d1170d3973c 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -9,9 +9,13 @@ license.workspace = true [dependencies] async-trait = { workspace = true } bip39 = { workspace = true } -nym-client-core = { path = "../../../common/client-core", features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage"] } +nym-client-core = { path = "../../../common/client-core", features = [ + "fs-credentials-storage", + "fs-surb-storage", + "fs-gateways-storage", +] } nym-crypto = { path = "../../../common/crypto" } -nym-gateway-requests = { path = "../../../gateway/gateway-requests" } +nym-gateway-requests = { path = "../../../common/gateway-requests" } nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } nym-credentials = { path = "../../../common/credentials" } nym-credentials-interface = { path = "../../../common/credentials-interface" } @@ -22,7 +26,9 @@ nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } nym-topology = { path = "../../../common/topology" } nym-socks5-client-core = { path = "../../../common/socks5-client-core" } -nym-validator-client = { path = "../../../common/client-libs/validator-client", features = ["http-client"] } +nym-validator-client = { path = "../../../common/client-libs/validator-client", features = [ + "http-client", +] } nym-socks5-requests = { path = "../../../common/socks5/requests" } nym-ordered-buffer = { path = "../../../common/socks5/ordered-buffer" } nym-service-providers-common = { path = "../../../service-providers/common" } diff --git a/service-providers/authenticator/Cargo.toml b/service-providers/authenticator/Cargo.toml index 2b8ca52e8eb..c25a5b2d47f 100644 --- a/service-providers/authenticator/Cargo.toml +++ b/service-providers/authenticator/Cargo.toml @@ -14,6 +14,7 @@ bincode = { workspace = true } bs58 = { workspace = true } bytes = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } +defguard_wireguard_rs = { workspace = true } fastrand = { workspace = true } futures = { workspace = true } ipnetwork = { workspace = true } @@ -28,7 +29,10 @@ tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true } nym-authenticator-requests = { path = "../../common/authenticator-requests" } -nym-bin-common = { path = "../../common/bin-common", features = ["clap", "output_format"] } +nym-bin-common = { path = "../../common/bin-common", features = [ + "clap", + "output_format", +] } nym-client-core = { path = "../../common/client-core", features = ["cli"] } nym-config = { path = "../../common/config" } nym-crypto = { path = "../../common/crypto" } diff --git a/service-providers/authenticator/src/cli/peer_handler.rs b/service-providers/authenticator/src/cli/peer_handler.rs index b4d6e1faea6..8c644f7ef71 100644 --- a/service-providers/authenticator/src/cli/peer_handler.rs +++ b/service-providers/authenticator/src/cli/peer_handler.rs @@ -38,6 +38,10 @@ impl DummyHandler { log::info!("[DUMMY] Removing peer {:?}", key); self.response_tx.send(PeerControlResponse::RemovePeer { success: true }).ok(); } + PeerControlRequest::QueryPeer(key) => { + log::info!("[DUMMY] Querying peer {:?}", key); + self.response_tx.send(PeerControlResponse::QueryPeer { success: false, peer: None }).ok(); + } PeerControlRequest::QueryBandwidth(key) => { log::info!("[DUMMY] Querying bandwidth for peer {:?}", key); self.response_tx.send(PeerControlResponse::QueryBandwidth { bandwidth_data: None }).ok(); diff --git a/service-providers/authenticator/src/error.rs b/service-providers/authenticator/src/error.rs index addb1cf56cc..add82fd7786 100644 --- a/service-providers/authenticator/src/error.rs +++ b/service-providers/authenticator/src/error.rs @@ -67,6 +67,9 @@ pub enum AuthenticatorError { #[error("internal data corruption: {0}")] InternalDataCorruption(String), + + #[error("peers can't be interacted with anymore")] + PeerInteractionStopped, } pub type Result = std::result::Result; diff --git a/service-providers/authenticator/src/lib.rs b/service-providers/authenticator/src/lib.rs index bcdfd3c3416..ec7ca579f98 100644 --- a/service-providers/authenticator/src/lib.rs +++ b/service-providers/authenticator/src/lib.rs @@ -9,3 +9,4 @@ pub mod config; pub mod error; pub mod mixnet_client; pub mod mixnet_listener; +mod peer_manager; diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index a93b2185ef2..b08b5054a56 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -6,7 +6,7 @@ use std::{ time::{Duration, SystemTime}, }; -use crate::error::AuthenticatorError; +use crate::{error::AuthenticatorError, peer_manager::PeerManager}; use futures::StreamExt; use ipnetwork::IpNetwork; use nym_authenticator_requests::v1::{ @@ -14,6 +14,7 @@ use nym_authenticator_requests::v1::{ request::{AuthenticatorRequest, AuthenticatorRequestData}, response::AuthenticatorResponse, }; +use nym_crypto::asymmetric::x25519::KeyPair; use nym_sdk::mixnet::{InputMessage, MixnetMessageSender, Recipient, TransmissionLane}; use nym_sphinx::receiver::ReconstructedMessage; use nym_task::TaskHandle; @@ -44,9 +45,7 @@ pub(crate) struct MixnetListener { // Registrations awaiting confirmation pub(crate) registration_in_progres: Arc, - pub(crate) wireguard_gateway_data: WireguardGatewayData, - - pub(crate) response_rx: UnboundedReceiver, + pub(crate) peer_manager: PeerManager, pub(crate) free_private_network_ips: Arc, @@ -69,8 +68,7 @@ impl MixnetListener { mixnet_client, task_handle, registration_in_progres: Default::default(), - wireguard_gateway_data, - response_rx, + peer_manager: PeerManager::new(wireguard_gateway_data, response_rx), free_private_network_ips: Arc::new( private_ip_network.iter().map(|ip| (ip, None)).collect(), ), @@ -78,6 +76,10 @@ impl MixnetListener { } } + fn keypair(&self) -> &Arc { + self.peer_manager.wireguard_gateway_data.keypair() + } + fn remove_stale_registrations(&self) -> Result<()> { for reg in self.registration_in_progres.iter().map(|reg| reg.clone()) { let mut ip = self @@ -125,21 +127,18 @@ impl MixnetListener { reply_to, )); } - if let Some(gateway_client) = self - .wireguard_gateway_data - .client_registry() - .get(&remote_public) - { + + let peer = self.peer_manager.query_peer(remote_public).await?; + if let Some(peer) = peer { + let Some(allowed_ip) = peer.allowed_ips.first() else { + return Err(AuthenticatorError::InternalError( + "private ip list should not be empty".to_string(), + )); + }; return Ok(AuthenticatorResponse::new_registered( RegistredData { - pub_key: PeerPublicKey::new( - self.wireguard_gateway_data - .keypair() - .public_key() - .to_bytes() - .into(), - ), - private_ip: gateway_client.private_ip, + pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), + private_ip: allowed_ip.ip, wg_port: self.config.authenticator.announced_port, }, reply_to, @@ -155,7 +154,7 @@ impl MixnetListener { // mark it as used, even though it's not final *private_ip_ref = Some(SystemTime::now()); let gateway_data = GatewayClient::new( - self.wireguard_gateway_data.keypair().private_key(), + self.keypair().private_key(), remote_public.inner(), *private_ip_ref.key(), nonce, @@ -189,40 +188,12 @@ impl MixnetListener { .clone(); if gateway_client - .verify( - self.wireguard_gateway_data.keypair().private_key(), - registration_data.nonce, - ) + .verify(self.keypair().private_key(), registration_data.nonce) .is_ok() { - self.wireguard_gateway_data - .add_peer(&gateway_client) - .map_err(|err| { - AuthenticatorError::InternalError(format!("could not add peer: {:?}", err)) - })?; - - let PeerControlResponse::AddPeer { success } = - self.response_rx - .recv() - .await - .ok_or(AuthenticatorError::InternalError( - "no response for add peer".to_string(), - ))? - else { - return Err(AuthenticatorError::InternalError( - "unexpected response type".to_string(), - )); - }; - if !success { - return Err(AuthenticatorError::InternalError( - "adding peer could not be performed".to_string(), - )); - } + self.peer_manager.add_peer(&gateway_client).await?; self.registration_in_progres .remove(&gateway_client.pub_key()); - self.wireguard_gateway_data - .client_registry() - .insert(gateway_client.pub_key(), gateway_client); Ok(AuthenticatorResponse::new_registered( RegistredData { @@ -244,26 +215,7 @@ impl MixnetListener { request_id: u64, reply_to: Recipient, ) -> AuthenticatorHandleResult { - self.wireguard_gateway_data - .query_bandwidth(peer_public_key) - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "could not query peer bandwidth: {:?}", - err - )) - })?; - let PeerControlResponse::QueryBandwidth { bandwidth_data } = self - .response_rx - .recv() - .await - .ok_or(AuthenticatorError::InternalError( - "no response for query".to_string(), - ))? - else { - return Err(AuthenticatorError::InternalError( - "unexpected response type".to_string(), - )); - }; + let bandwidth_data = self.peer_manager.query_bandwidth(peer_public_key).await?; Ok(AuthenticatorResponse::new_remaining_bandwidth( bandwidth_data, reply_to, diff --git a/service-providers/authenticator/src/peer_manager.rs b/service-providers/authenticator/src/peer_manager.rs new file mode 100644 index 00000000000..cdc847a2ca2 --- /dev/null +++ b/service-providers/authenticator/src/peer_manager.rs @@ -0,0 +1,140 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::*; +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; +use nym_wireguard::{ + peer_controller::{PeerControlRequest, PeerControlResponse}, + WireguardGatewayData, +}; +use nym_wireguard_types::{registration::RemainingBandwidthData, GatewayClient, PeerPublicKey}; +use tokio::sync::mpsc::UnboundedReceiver; + +pub struct PeerManager { + pub(crate) wireguard_gateway_data: WireguardGatewayData, + + pub(crate) response_rx: UnboundedReceiver, +} + +impl PeerManager { + pub fn new( + wireguard_gateway_data: WireguardGatewayData, + response_rx: UnboundedReceiver, + ) -> Self { + PeerManager { + wireguard_gateway_data, + response_rx, + } + } + pub async fn add_peer(&mut self, client: &GatewayClient) -> Result<()> { + let mut peer = Peer::new(Key::new(client.pub_key.to_bytes())); + peer.allowed_ips + .push(IpAddrMask::new(client.private_ip, 32)); + let msg = PeerControlRequest::AddPeer(peer); + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + let PeerControlResponse::AddPeer { success } = + self.response_rx + .recv() + .await + .ok_or(AuthenticatorError::InternalError( + "no response for add peer".to_string(), + ))? + else { + return Err(AuthenticatorError::InternalError( + "unexpected response type".to_string(), + )); + }; + if !success { + return Err(AuthenticatorError::InternalError( + "adding peer could not be performed".to_string(), + )); + } + Ok(()) + } + + pub async fn _remove_peer(&mut self, client: &GatewayClient) -> Result<()> { + let key = Key::new(client.pub_key().to_bytes()); + let msg = PeerControlRequest::RemovePeer(key); + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + let PeerControlResponse::RemovePeer { success } = + self.response_rx + .recv() + .await + .ok_or(AuthenticatorError::InternalError( + "no response for add peer".to_string(), + ))? + else { + return Err(AuthenticatorError::InternalError( + "unexpected response type".to_string(), + )); + }; + if !success { + return Err(AuthenticatorError::InternalError( + "adding peer could not be performed".to_string(), + )); + } + Ok(()) + } + + pub async fn query_peer(&mut self, public_key: PeerPublicKey) -> Result> { + let key = Key::new(public_key.to_bytes()); + let msg = PeerControlRequest::QueryPeer(key); + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + let PeerControlResponse::QueryPeer { success, peer } = self + .response_rx + .recv() + .await + .ok_or(AuthenticatorError::InternalError( + "no response for query peer".to_string(), + ))? + else { + return Err(AuthenticatorError::InternalError( + "unexpected response type".to_string(), + )); + }; + if !success { + return Err(AuthenticatorError::InternalError( + "querying peer could not be performed".to_string(), + )); + } + Ok(peer) + } + + pub async fn query_bandwidth( + &mut self, + peer_public_key: PeerPublicKey, + ) -> Result> { + let key = Key::new(peer_public_key.to_bytes()); + let msg = PeerControlRequest::QueryBandwidth(key); + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + let PeerControlResponse::QueryBandwidth { bandwidth_data } = self + .response_rx + .recv() + .await + .ok_or(AuthenticatorError::InternalError( + "no response for query".to_string(), + ))? + else { + return Err(AuthenticatorError::InternalError( + "unexpected response type".to_string(), + )); + }; + Ok(bandwidth_data) + } +}