Wireguard private metadata

This commit is contained in:
Bogdan-Ștefan Neacşu
2025-07-24 13:01:23 +03:00
parent 33716f693b
commit 9cbf8e20b4
38 changed files with 3198 additions and 142 deletions
Generated
+25
View File
@@ -5723,6 +5723,7 @@ dependencies = [
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-private-metadata",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
@@ -7322,6 +7323,30 @@ dependencies = [
"x25519-dalek",
]
[[package]]
name = "nym-wireguard-private-metadata"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"axum 0.7.9",
"bincode",
"futures",
"nym-credential-verification",
"nym-credentials-interface",
"nym-http-api-common",
"nym-wireguard",
"schemars 0.8.22",
"serde",
"thiserror 2.0.12",
"tokio",
"tokio-util",
"tower-http 0.5.2",
"utoipa",
"utoipa-swagger-ui",
"utoipauto",
]
[[package]]
name = "nym-wireguard-types"
version = "0.1.0"
+1
View File
@@ -102,6 +102,7 @@ members = [
"common/wasm/storage",
"common/wasm/utils",
"common/wireguard",
"common/wireguard-private-metadata",
"common/wireguard-types",
"common/zulip-client",
"documentation/autodoc",
+16
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::ecash::traits::EcashManager;
use async_trait::async_trait;
use bandwidth_storage_manager::BandwidthStorageManager;
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
@@ -139,3 +140,18 @@ impl CredentialVerifier {
.await)
}
}
#[async_trait]
pub trait TicketVerifier {
/// Verify that the ticket is valid and cryptographically correct.
/// If the verification succeeds, also increase the bandwidth with the ticket's
/// amount and return the latest available bandwidth
async fn verify(&mut self) -> Result<i64>;
}
#[async_trait]
impl TicketVerifier for CredentialVerifier {
async fn verify(&mut self) -> Result<i64> {
self.verify().await
}
}
+2 -1
View File
@@ -47,7 +47,8 @@ pub mod nyx {
pub mod wireguard {
use std::net::{Ipv4Addr, Ipv6Addr};
pub const WG_PORT: u16 = 51822;
pub const WG_TUNNEL_PORT: u16 = 51822;
pub const WG_METADATA_PORT: u16 = 51830;
// The interface used to route traffic
pub const WG_TUN_BASE_NAME: &str = "nymwg";
@@ -0,0 +1,43 @@
[package]
name = "nym-wireguard-private-metadata"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
anyhow = { workspace = true }
axum = { workspace = true, features = ["tokio", "macros"] }
bincode = { workspace = true }
futures = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-util = { workspace = true }
tower-http = { workspace = true, features = [
"cors",
"trace",
"compression-br",
"compression-deflate",
"compression-gzip",
"compression-zstd",
] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipauto = { workspace = true }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
nym-credentials-interface = { path = "../credentials-interface" }
nym-credential-verification = { path = "../credential-verification" }
nym-http-api-common = { path = "../http-api-common", features = [
"middleware",
"utoipa",
"output",
] }
nym-wireguard = { path = "../wireguard" }
[dev-dependencies]
async-trait = { workspace = true }
@@ -0,0 +1,36 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum MetadataError {
#[error("peers can't be interacted with anymore")]
PeerInteractionStopped,
#[error("no response received")]
NoResponse,
#[error("query was not successful: {reason}")]
Unsuccessful { reason: String },
#[error("Models error: {message}")]
Models { message: String },
#[error("Credential verification error: {message}")]
CredentialVerification { message: String },
}
impl From<crate::models::error::Error> for MetadataError {
fn from(value: crate::models::error::Error) -> Self {
Self::Models {
message: value.to_string(),
}
}
}
impl From<nym_credential_verification::Error> for MetadataError {
fn from(value: nym_credential_verification::Error) -> Self {
Self::CredentialVerification {
message: value.to_string(),
}
}
}
@@ -0,0 +1,46 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::Arc;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use nym_wireguard::WgApiWrapper;
pub(crate) mod openapi;
pub(crate) mod router;
pub(crate) mod state;
/// Shutdown goes 2 directions:
/// 1. signal background tasks to gracefully finish
/// 2. signal server itself
///
/// These are done through separate shutdown handles. Of course, shut down server
/// AFTER you have shut down BG tasks (or past their grace period).
#[allow(unused)]
pub struct ShutdownHandles {
axum_shutdown_button: CancellationToken,
/// Tokio JoinHandle for axum server's task
axum_join_handle: AxumJoinHandle,
/// Wireguard API for kernel interactions
wg_api: Arc<WgApiWrapper>,
}
impl ShutdownHandles {
/// Cancellation token is given to Axum server constructor. When the token
/// receives a shutdown signal, Axum server will shut down gracefully.
pub fn new(
axum_join_handle: AxumJoinHandle,
wg_api: Arc<WgApiWrapper>,
axum_shutdown_button: CancellationToken,
) -> Self {
Self {
axum_shutdown_button,
axum_join_handle,
wg_api,
}
}
}
type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
@@ -0,0 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use utoipa::OpenApi;
use crate::models::{AvailableBandwidthResponse, TopUpRequest};
#[derive(OpenApi)]
#[openapi(
info(title = "Nym Wireguard Private Metadata"),
tags(),
components(schemas(AvailableBandwidthResponse, TopUpRequest))
)]
pub(crate) struct ApiDoc;
@@ -0,0 +1,101 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use anyhow::anyhow;
use axum::response::Redirect;
use axum::routing::get;
use axum::Router;
use core::net::SocketAddr;
use nym_http_api_common::middleware::logging::log_request_info;
use tokio::net::TcpListener;
use tokio_util::sync::WaitForCancellationFutureOwned;
use tower_http::cors::CorsLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::http::openapi::ApiDoc;
use crate::http::state::AppState;
use crate::network::bandwidth_routes;
/// Wrapper around `axum::Router` which ensures correct [order of layers][order].
/// Add new routes as if you were working directly with `axum`.
///
/// Why? Middleware like logger, CORS, TLS which need to handle request before other
/// layers should be added last. Using this builder pattern ensures that.
///
/// [order]: https://docs.rs/axum/latest/axum/middleware/index.html#ordering
pub struct RouterBuilder {
unfinished_router: Router<AppState>,
}
impl RouterBuilder {
/// All routes should be, if possible, added here. Exceptions are e.g.
/// routes which are added conditionally in other places based on some `if`.
pub fn with_default_routes() -> Self {
let default_routes = Router::new()
.merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", ApiDoc::openapi()))
.route("/", get(|| async { Redirect::to("/swagger") }))
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()));
Self {
unfinished_router: default_routes,
}
}
/// Invoke this as late as possible before constructing HTTP server
/// (after all routes were added).
pub fn with_state(self, state: AppState) -> RouterWithState {
RouterWithState {
router: self.finalize_routes().with_state(state),
}
}
/// Middleware added here intercepts the request before it gets to other routes.
fn finalize_routes(self) -> Router<AppState> {
self.unfinished_router
.layer(setup_cors())
.layer(axum::middleware::from_fn(log_request_info))
}
}
fn setup_cors() -> CorsLayer {
CorsLayer::new()
.allow_origin(tower_http::cors::Any)
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_headers(tower_http::cors::Any)
.allow_credentials(false)
}
pub struct RouterWithState {
router: Router,
}
impl RouterWithState {
pub async fn build_server(self, bind_address: &SocketAddr) -> anyhow::Result<ApiHttpServer> {
let listener = tokio::net::TcpListener::bind(bind_address)
.await
.map_err(|err| anyhow!("Couldn't bind to address {} due to {}", bind_address, err))?;
Ok(ApiHttpServer {
router: self.router,
listener,
})
}
}
pub struct ApiHttpServer {
router: Router,
listener: TcpListener,
}
impl ApiHttpServer {
pub async fn run(self, receiver: WaitForCancellationFutureOwned) -> Result<(), std::io::Error> {
// into_make_service_with_connect_info allows us to see client ip address
axum::serve(
self.listener,
self.router
.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(receiver)
.await
}
}
@@ -0,0 +1,43 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::IpAddr;
use nym_credentials_interface::CredentialSpendingData;
use crate::{
error::MetadataError,
models::{latest, AvailableBandwidthResponse},
transceiver::PeerControllerTransceiver,
};
#[derive(Clone, axum::extract::FromRef)]
pub struct AppState {
transceiver: PeerControllerTransceiver,
}
impl AppState {
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
Self { transceiver }
}
pub(crate) async fn available_bandwidth(
&self,
ip: IpAddr,
) -> Result<AvailableBandwidthResponse, MetadataError> {
let value = self.transceiver.query_bandwidth(ip).await?;
let res = latest::InnerAvailableBandwidthResponse::new(value).try_into()?;
Ok(res)
}
pub(crate) async fn topup_bandwidth(
&self,
ip: IpAddr,
credential: CredentialSpendingData,
) -> Result<(), MetadataError> {
self.transceiver
.topup_bandwidth(ip, Box::new(credential))
.await?;
Ok(())
}
}
@@ -0,0 +1,30 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_os = "linux")]
mod error;
#[cfg(target_os = "linux")]
mod http;
#[cfg(target_os = "linux")]
mod models;
#[cfg(target_os = "linux")]
mod network;
#[cfg(target_os = "linux")]
mod transceiver;
#[cfg(target_os = "linux")]
pub use http::{
router::{ApiHttpServer, RouterBuilder, RouterWithState},
state::AppState,
ShutdownHandles,
};
#[cfg(target_os = "linux")]
pub use transceiver::PeerControllerTransceiver;
#[cfg(target_os = "linux")]
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
@@ -0,0 +1,16 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::Version;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Bincode(#[from] bincode::Error),
#[error("trying to deserialize from version {source_version} into {target_version}")]
InvalidVersion {
source_version: Version,
target_version: Version,
},
}
@@ -0,0 +1,50 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Display;
use axum::http::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
pub(crate) mod error;
pub(crate) mod version_1;
pub(crate) use version_1 as latest;
pub type Version = usize;
#[derive(Clone, Serialize, Deserialize, ToSchema)]
pub struct AvailableBandwidthResponse {
pub(crate) version: Version,
pub(crate) inner: Vec<u8>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct TopUpRequest {
pub(crate) version: Version,
pub(crate) inner: Vec<u8>,
}
pub(crate) type AxumResult<T> = Result<T, AxumErrorResponse>;
pub(crate) struct AxumErrorResponse {
message: String,
status: StatusCode,
}
impl AxumErrorResponse {
pub(crate) fn bad_request(msg: impl Display) -> Self {
Self {
message: msg.to_string(),
status: StatusCode::BAD_REQUEST,
}
}
}
impl axum::response::IntoResponse for AxumErrorResponse {
fn into_response(self) -> axum::response::Response {
(self.status, self.message).into_response()
}
}
@@ -0,0 +1,163 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use super::error::Error;
use crate::{
make_bincode_serializer,
models::{AvailableBandwidthResponse, TopUpRequest},
};
use nym_credentials_interface::CredentialSpendingData;
pub const VERSION: usize = 1;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthResponse {
pub(crate) value: i64,
}
impl InnerAvailableBandwidthResponse {
pub(crate) fn new(value: i64) -> Self {
Self { value }
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for AvailableBandwidthResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(AvailableBandwidthResponse {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<AvailableBandwidthResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: AvailableBandwidthResponse) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InnerTopUpRequest {
/// Ecash credential
pub credential: CredentialSpendingData,
}
impl TryFrom<InnerTopUpRequest> for TopUpRequest {
type Error = Error;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
Ok(TopUpRequest {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<TopUpRequest> for InnerTopUpRequest {
type Error = Error;
fn try_from(value: TopUpRequest) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_available_bandwidth() {
let bw = InnerAvailableBandwidthResponse::new(42);
let ser = AvailableBandwidthResponse::try_from(bw).unwrap();
assert_eq!(VERSION, ser.version);
assert_eq!(ser.inner, vec![84]);
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
assert_eq!(bw, de);
}
#[test]
fn mismatched_version_available_bandwidth() {
let version = 4242;
let future_bw = AvailableBandwidthResponse {
version,
inner: vec![],
};
if let Err(Error::InvalidVersion {
source_version,
target_version,
}) = InnerAvailableBandwidthResponse::try_from(future_bw)
{
assert_eq!(source_version, version);
assert_eq!(target_version, VERSION);
} else {
panic!("failed");
};
}
#[test]
fn invalid_content_available_bandwidth() {
let future_bw = AvailableBandwidthResponse {
version: VERSION,
inner: vec![],
};
assert!(InnerAvailableBandwidthResponse::try_from(future_bw).is_err());
}
#[test]
fn serde_topup() {
let bw = InnerAvailableBandwidthResponse::new(42);
let ser = AvailableBandwidthResponse::try_from(bw).unwrap();
assert_eq!(VERSION, ser.version);
assert_eq!(ser.inner, vec![84]);
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
assert_eq!(bw, de);
}
#[test]
fn mismatched_version_topup() {
let version = 4242;
let future_bw = AvailableBandwidthResponse {
version,
inner: vec![],
};
if let Err(Error::InvalidVersion {
source_version,
target_version,
}) = InnerAvailableBandwidthResponse::try_from(future_bw)
{
assert_eq!(source_version, version);
assert_eq!(target_version, VERSION);
} else {
panic!("failed");
};
}
#[test]
fn invalid_content_topup() {
let future_bw = AvailableBandwidthResponse {
version: VERSION,
inner: vec![],
};
assert!(InnerAvailableBandwidthResponse::try_from(future_bw).is_err());
}
}
@@ -0,0 +1,77 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::SocketAddr;
use axum::{
extract::{ConnectInfo, Query, State},
Json, Router,
};
use nym_http_api_common::{FormattedResponse, OutputParams};
use tower_http::compression::CompressionLayer;
use crate::{
http::state::AppState,
models::{
latest::InnerTopUpRequest, AvailableBandwidthResponse, AxumErrorResponse, AxumResult,
TopUpRequest,
},
};
pub(crate) fn bandwidth_routes() -> Router<AppState> {
Router::new()
.route("/available", axum::routing::get(available_bandwidth))
.route("/topup", axum::routing::post(topup_bandwidth))
.layer(CompressionLayer::new())
}
#[utoipa::path(
tag = "bandwidth",
get,
path = "/v1/bandwidth/available",
responses(
(status = 200, content(
(AvailableBandwidthResponse = "application/bincode")
))
),
)]
async fn available_bandwidth(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> AxumResult<FormattedResponse<AvailableBandwidthResponse>> {
let output = output.output.unwrap_or_default();
Ok(output.to_response(
state
.available_bandwidth(addr.ip())
.await
.map_err(AxumErrorResponse::bad_request)?,
))
}
#[utoipa::path(
tag = "bandwidth",
post,
request_body = TopUpRequest,
path = "/v1/bandwidth/topup",
responses(
(status = 200),
),
)]
async fn topup_bandwidth(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
Json(request): Json<TopUpRequest>,
) -> AxumResult<FormattedResponse<()>> {
let output = output.output.unwrap_or_default();
let credential = InnerTopUpRequest::try_from(request)
.map_err(AxumErrorResponse::bad_request)?
.credential;
state
.topup_bandwidth(addr.ip(), credential)
.await
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(()))
}
@@ -0,0 +1,292 @@
use futures::channel::oneshot;
use nym_credential_verification::ClientBandwidth;
use nym_credentials_interface::CredentialSpendingData;
use tokio::sync::mpsc;
use std::net::IpAddr;
use nym_wireguard::peer_controller::PeerControlRequest;
use crate::error::MetadataError;
#[derive(Clone)]
pub struct PeerControllerTransceiver {
request_tx: mpsc::Sender<PeerControlRequest>,
}
impl PeerControllerTransceiver {
pub fn new(request_tx: mpsc::Sender<PeerControlRequest>) -> Self {
Self { request_tx }
}
async fn get_client_bandwidth(&self, ip: IpAddr) -> Result<ClientBandwidth, MetadataError> {
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetClientBandwidthByIp { ip, response_tx };
self.request_tx
.send(msg)
.await
.map_err(|_| MetadataError::PeerInteractionStopped)?;
response_rx
.await
.map_err(|_| MetadataError::NoResponse)?
.map_err(|err| MetadataError::Unsuccessful {
reason: err.to_string(),
})
}
pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
Ok(self.get_client_bandwidth(ip).await?.available().await)
}
pub(crate) async fn topup_bandwidth(
&self,
ip: IpAddr,
credential: Box<CredentialSpendingData>,
) -> Result<i64, MetadataError> {
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetVerifierByIp {
ip,
credential,
response_tx,
};
self.request_tx
.send(msg)
.await
.map_err(|_| MetadataError::PeerInteractionStopped)?;
let mut verifier = response_rx
.await
.map_err(|_| MetadataError::NoResponse)?
.map_err(|err| MetadataError::Unsuccessful {
reason: err.to_string(),
})?;
let available_bandwidth = verifier.verify().await?;
Ok(available_bandwidth)
}
}
#[cfg(test)]
mod tests {
use nym_credential_verification::TicketVerifier;
use nym_wireguard::CONTROL_CHANNEL_SIZE;
use super::*;
const CREDENTIAL_BYTES: [u8; 1245] = [
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
0, 0, 0, 0, 0, 1,
];
struct MockVerifier {
ret: i64,
}
#[async_trait::async_trait]
impl TicketVerifier for MockVerifier {
async fn verify(&mut self) -> nym_credential_verification::Result<i64> {
Ok(self.ret)
}
}
#[tokio::test]
async fn get_bandwidth() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
response_tx
.send(Ok(ClientBandwidth::new(Default::default())))
.ok();
}
_ => unimplemented!(),
}
});
let bw = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap();
assert_eq!(bw, 0);
}
#[tokio::test]
async fn stop_peer() {
let (request_tx, request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
drop(request_rx);
let err = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap_err();
assert_eq!(err, MetadataError::PeerInteractionStopped);
}
#[tokio::test]
async fn unresponsive_peer() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp {
ip: _,
response_tx: _,
} => {}
_ => unimplemented!(),
}
});
let err = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap_err();
assert_eq!(err, MetadataError::NoResponse);
}
#[tokio::test]
async fn unsuccessful_query_bandwidth() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
response_tx
.send(Err(nym_wireguard::error::Error::Internal(
"testing".to_owned(),
)))
.ok();
}
_ => unimplemented!(),
}
});
let ret = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await;
assert!(ret.is_err());
}
#[tokio::test]
async fn topup() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
let verifier_bw = 42;
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
} => {
response_tx
.send(Ok(Box::new(MockVerifier { ret: verifier_bw })))
.ok();
}
_ => unimplemented!(),
}
});
let bw = transceiver
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
.await
.unwrap();
assert_eq!(bw, verifier_bw);
}
#[tokio::test]
async fn unsuccessful_topup() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
} => {
response_tx
.send(Err(nym_wireguard::error::Error::Internal(
"testing".to_owned(),
)))
.ok();
}
_ => unimplemented!(),
}
});
let ret = transceiver
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
.await;
assert!(ret.is_err());
}
}
+6 -2
View File
@@ -17,9 +17,13 @@ pub struct Config {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel 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,
pub announced_tunnel_port: u16,
/// Metadata port announced to external clients wishing to connect to the endpoint.
/// Useful in the instances where the node is behind a proxy.
pub announced_metadata_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
+15 -6
View File
@@ -18,11 +18,13 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
#[cfg(target_os = "linux")]
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
pub(crate) mod error;
pub mod error;
pub mod peer_controller;
pub mod peer_handle;
pub mod peer_storage_manager;
pub const CONTROL_CHANNEL_SIZE: usize = 256;
pub struct WgApiWrapper {
inner: WGApi,
}
@@ -126,7 +128,7 @@ pub struct WireguardGatewayData {
impl WireguardGatewayData {
pub fn new(config: Config, keypair: Arc<KeyPair>) -> (Self, Receiver<PeerControlRequest>) {
let (peer_tx, peer_rx) = mpsc::channel(1);
let (peer_tx, peer_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
(
WireguardGatewayData {
config,
@@ -178,10 +180,16 @@ pub async fn start_wireguard(
let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len());
for peer in peers.iter() {
let bandwidth_manager = Arc::new(RwLock::new(
PeerController::generate_bandwidth_manager(ecash_manager.storage(), &peer.public_key)
let bandwidth_manager = peer_handle::SharedBandwidthStorageManager::new(
Arc::new(RwLock::new(
PeerController::generate_bandwidth_manager(
ecash_manager.storage(),
&peer.public_key,
)
.await?,
));
)),
peer.allowed_ips.clone(),
);
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
}
@@ -190,7 +198,7 @@ pub async fn start_wireguard(
name: ifname.clone(),
prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()),
address: wireguard_data.inner.config().private_ipv4.to_string(),
port: wireguard_data.inner.config().announced_port as u32,
port: wireguard_data.inner.config().announced_tunnel_port as u32,
peers,
mtu: None,
};
@@ -233,6 +241,7 @@ pub async fn start_wireguard(
let host = wg_api.read_interface_data()?;
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(
ecash_manager,
metrics,
+84 -23
View File
@@ -10,15 +10,18 @@ use futures::channel::oneshot;
use log::info;
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager,
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, TicketVerifier,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_gateway_requests::models::CredentialSpendingRequest;
use nym_gateway_storage::traits::BandwidthGatewayStorage;
use nym_node_metrics::NymNodeMetrics;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::time::{Duration, SystemTime};
use std::{collections::HashMap, sync::Arc};
use std::{
net::IpAddr,
time::{Duration, SystemTime},
};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
@@ -41,22 +44,31 @@ pub enum PeerControlRequest {
key: Key,
response_tx: oneshot::Sender<QueryPeerControlResponse>,
},
GetClientBandwidth {
GetClientBandwidthByKey {
key: Key,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifier {
GetClientBandwidthByIp {
ip: IpAddr,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifierByKey {
key: Key,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
GetVerifierByIp {
ip: IpAddr,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
}
pub type AddPeerControlResponse = Result<()>;
pub type RemovePeerControlResponse = Result<()>;
pub type QueryPeerControlResponse = Result<Option<Peer>>;
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
pub type QueryVerifierControlResponse = Result<CredentialVerifier>;
pub type QueryVerifierControlResponse = Result<Box<dyn TicketVerifier + Send + Sync>>;
pub struct PeerController {
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
@@ -77,7 +89,7 @@ pub struct PeerController {
impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new(
pub(crate) fn new(
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
metrics: NymNodeMetrics,
wg_api: Arc<dyn WireguardInterfaceApi + Send + Sync>,
@@ -165,10 +177,13 @@ impl PeerController {
async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> {
self.wg_api.configure_peer(peer)?;
let bandwidth_storage_manager = Arc::new(RwLock::new(
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
.await?,
));
let bandwidth_storage_manager = SharedBandwidthStorageManager::new(
Arc::new(RwLock::new(
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
.await?,
)),
peer.allowed_ips.clone(),
);
let cached_peer_manager = CachedPeerManager::new(peer);
let mut handle = PeerHandle::new(
peer.public_key.clone(),
@@ -192,7 +207,20 @@ impl PeerController {
Ok(())
}
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>> {
async fn ip_to_key(&self, ip: IpAddr) -> Result<Option<Key>> {
Ok(self
.bw_storage_managers
.iter()
.find_map(|(key, bw_manager)| {
bw_manager
.allowed_ips()
.iter()
.find(|ip_mask| ip_mask.ip == ip)
.and(Some(key.clone()))
}))
}
async fn handle_query_peer_by_key(&self, key: &Key) -> Result<Option<Peer>> {
Ok(self
.ecash_verifier
.storage()
@@ -202,20 +230,32 @@ impl PeerController {
.transpose()?)
}
async fn handle_get_client_bandwidth(&self, key: &Key) -> Result<ClientBandwidth> {
async fn handle_get_client_bandwidth_by_key(&self, key: &Key) -> Result<ClientBandwidth> {
let bandwidth_storage_manager = self
.bw_storage_managers
.get(key)
.ok_or(Error::MissingClientBandwidthEntry)?;
Ok(bandwidth_storage_manager.read().await.client_bandwidth())
Ok(bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth())
}
async fn handle_query_verifier(
async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result<ClientBandwidth> {
let Some(key) = self.ip_to_key(ip).await? else {
return Err(Error::MissingClientKernelEntry(ip.to_string()));
};
self.handle_get_client_bandwidth_by_key(&key).await
}
async fn handle_query_verifier_by_key(
&self,
key: &Key,
credential: CredentialSpendingData,
) -> Result<CredentialVerifier> {
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
let storage = self.ecash_verifier.storage();
let client_id = storage
.get_wireguard_peer(&key.to_string())
@@ -225,7 +265,11 @@ impl PeerController {
let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else {
return Err(Error::MissingClientBandwidthEntry);
};
let client_bandwidth = bandwidth_storage_manager.read().await.client_bandwidth();
let client_bandwidth = bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth();
let verifier = CredentialVerifier::new(
CredentialSpendingRequest::new(credential),
self.ecash_verifier.clone(),
@@ -237,7 +281,19 @@ impl PeerController {
true,
),
);
Ok(verifier)
Ok(Box::new(verifier))
}
async fn handle_query_verifier_by_ip(
&self,
ip: IpAddr,
credential: CredentialSpendingData,
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
let Some(key) = self.ip_to_key(ip).await? else {
return Err(Error::MissingClientKernelEntry(ip.to_string()));
};
self.handle_query_verifier_by_key(&key, credential).await
}
async fn update_metrics(&self, new_host: &Host) {
@@ -340,18 +396,23 @@ impl PeerController {
response_tx.send(self.remove_peer(&key).await).ok();
}
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
response_tx.send(self.handle_query_peer(&key).await).ok();
response_tx.send(self.handle_query_peer_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth(&key).await).ok();
Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetVerifier { key, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier(&key, *credential).await).ok();
Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok();
}
Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok();
}
Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok();
}
None => {
log::trace!("PeerController [main loop]: stopping since channel closed");
break;
}
}
}
+26 -4
View File
@@ -4,7 +4,7 @@
use crate::error::Error;
use crate::peer_controller::PeerControlRequest;
use crate::peer_storage_manager::{CachedPeerManager, PeerInformation};
use defguard_wireguard_rs::{host::Host, key::Key};
use defguard_wireguard_rs::{host::Host, key::Key, net::IpAddrMask};
use futures::channel::oneshot;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_task::TaskClient;
@@ -13,7 +13,28 @@ use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
#[derive(Clone)]
pub(crate) struct SharedBandwidthStorageManager {
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
}
impl SharedBandwidthStorageManager {
pub(crate) fn new(
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
) -> Self {
Self { inner, allowed_ips }
}
pub(crate) fn inner(&self) -> &RwLock<BandwidthStorageManager> {
&self.inner
}
pub(crate) fn allowed_ips(&self) -> &[IpAddrMask] {
&self.allowed_ips
}
}
pub struct PeerHandle {
public_key: Key,
@@ -26,7 +47,7 @@ pub struct PeerHandle {
}
impl PeerHandle {
pub fn new(
pub(crate) fn new(
public_key: Key,
host_information: Arc<RwLock<Host>>,
cached_peer: CachedPeerManager,
@@ -120,6 +141,7 @@ impl PeerHandle {
if spent_bandwidth > 0
&& self
.bandwidth_storage_manager
.inner()
.write()
.await
.try_use_bandwidth(spent_bandwidth)
@@ -182,7 +204,7 @@ impl PeerHandle {
_ = self.task_client.recv() => {
log::trace!("PeerHandle: Received shutdown");
if let Err(e) = self.bandwidth_storage_manager.write().await.sync_storage_bandwidth().await {
if let Err(e) = self.bandwidth_storage_manager.inner().write().await.sync_storage_bandwidth().await {
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
}
@@ -58,8 +58,8 @@ Options:
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
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 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
--verloc-bind-address <VERLOC_BIND_ADDRESS>
+1
View File
@@ -72,6 +72,7 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
nym-node-metrics = { path = "../nym-node/nym-node-metrics" }
nym-wireguard = { path = "../common/wireguard" }
nym-wireguard-private-metadata = { path = "../common/wireguard-private-metadata" }
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
nym-authenticator-requests = { path = "../common/authenticator-requests" }
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use nym_network_defaults::{
WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUN_DEVICE_NETMASK_V4,
WG_TUN_DEVICE_NETMASK_V6,
WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6,
WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6,
};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
@@ -46,9 +46,9 @@ pub struct Authenticator {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel 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,
pub tunnel_announced_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
@@ -62,10 +62,10 @@ pub struct Authenticator {
impl Default for Authenticator {
fn default() -> Self {
Self {
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
announced_port: WG_PORT,
tunnel_announced_port: WG_TUNNEL_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
}
@@ -78,7 +78,8 @@ impl From<Authenticator> for nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
announced_tunnel_port: value.tunnel_announced_port,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
@@ -291,7 +291,7 @@ impl MixnetListener {
v1::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -304,7 +304,7 @@ impl MixnetListener {
v2::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -317,7 +317,7 @@ impl MixnetListener {
v3::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -330,7 +330,7 @@ impl MixnetListener {
v4::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -343,7 +343,7 @@ impl MixnetListener {
v5::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
request_id,
)
@@ -374,7 +374,7 @@ impl MixnetListener {
let registration_data = RegistrationData {
nonce,
gateway_data: gateway_data.clone(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
};
registred_and_free
.registration_in_progres
@@ -691,7 +691,7 @@ impl MixnetListener {
} else {
let mut verifier = self
.peer_manager
.query_verifier(msg.pub_key(), msg.credential())
.query_verifier_by_key(msg.pub_key(), msg.credential())
.await?;
let available_bandwidth = verifier.verify().await?;
self.seen_credential_cache
@@ -4,7 +4,7 @@
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
use defguard_wireguard_rs::{host::Peer, key::Key};
use futures::channel::oneshot;
use nym_credential_verification::{ClientBandwidth, CredentialVerifier};
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
use nym_wireguard_types::PeerPublicKey;
@@ -99,7 +99,7 @@ impl PeerManager {
) -> Result<ClientBandwidth, AuthenticatorError> {
let key = Key::new(key.to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetClientBandwidth { key, response_tx };
let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx };
self.wireguard_gateway_data
.peer_tx()
.send(msg)
@@ -120,14 +120,14 @@ impl PeerManager {
})
}
pub async fn query_verifier(
pub async fn query_verifier_by_key(
&mut self,
key: PeerPublicKey,
credential: CredentialSpendingData,
) -> Result<CredentialVerifier, AuthenticatorError> {
) -> Result<Box<dyn TicketVerifier + Send + Sync>, AuthenticatorError> {
let key = Key::new(key.to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetVerifier {
let msg = PeerControlRequest::GetVerifierByKey {
key,
credential: Box::new(credential),
response_tx,
@@ -398,13 +398,13 @@ mod tests {
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
assert!(peer_manager
.query_verifier(public_key, credential.clone())
.query_verifier_by_key(public_key, credential.clone())
.await
.is_err());
helper_add_peer(&storage, &mut peer_manager).await;
peer_manager
.query_verifier(public_key, credential)
.query_verifier_by_key(public_key, credential)
.await
.unwrap();
+54 -17
View File
@@ -19,7 +19,7 @@ use nym_network_defaults::NymNetworkDetails;
use nym_network_requester::NRServiceProviderBuilder;
use nym_node_metrics::events::MetricEventsSender;
use nym_node_metrics::NymNodeMetrics;
use nym_task::TaskClient;
use nym_task::{ShutdownToken, TaskClient};
use nym_topology::TopologyProvider;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
@@ -91,7 +91,9 @@ pub struct GatewayTasksBuilder {
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
// populated and cached as necessary
ecash_manager: Option<Arc<EcashManager>>,
@@ -105,7 +107,7 @@ impl Drop for GatewayTasksBuilder {
fn drop(&mut self) {
// disarm the shutdown as it was already used to construct relevant tasks and we don't want the builder
// to cause shutdown
self.shutdown.disarm();
self.legacy_task_client.disarm();
}
}
@@ -119,7 +121,8 @@ impl GatewayTasksBuilder {
metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
) -> GatewayTasksBuilder {
GatewayTasksBuilder {
config,
@@ -133,7 +136,8 @@ impl GatewayTasksBuilder {
metrics_sender,
metrics,
mnemonic,
shutdown,
legacy_task_client,
shutdown_token,
ecash_manager: None,
wireguard_peers: None,
wireguard_networks: None,
@@ -228,7 +232,7 @@ impl GatewayTasksBuilder {
handler_config,
nyxd_client,
self.identity_keypair.public_key().to_bytes(),
self.shutdown.fork("ecash_manager"),
self.legacy_task_client.fork("ecash_manager"),
self.storage.clone(),
)
.await?,
@@ -270,7 +274,7 @@ impl GatewayTasksBuilder {
self.config.gateway.websocket_bind_address,
self.config.debug.maximum_open_connections,
shared_state,
self.shutdown.fork("websocket"),
self.legacy_task_client.fork("websocket"),
))
}
@@ -286,13 +290,14 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("network_requester_message_router"),
self.legacy_task_client
.fork("network_requester_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
let (on_start_tx, on_start_rx) = oneshot::channel();
let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone())
.with_shutdown(self.shutdown.fork("network_requester_sp"))
.with_shutdown(self.legacy_task_client.fork("network_requester_sp"))
.with_custom_gateway_transceiver(transceiver)
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
@@ -321,13 +326,13 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("ipr_message_router"),
self.legacy_task_client.fork("ipr_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
let (on_start_tx, on_start_rx) = oneshot::channel();
let mut ip_packet_router = IpPacketRouter::new(ip_opts.config.clone())
.with_shutdown(self.shutdown.fork("ipr_sp"))
.with_shutdown(self.legacy_task_client.fork("ipr_sp"))
.with_custom_gateway_transceiver(Box::new(transceiver))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
@@ -427,7 +432,7 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("authenticator_message_router"),
self.legacy_task_client.fork("authenticator_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
@@ -440,7 +445,7 @@ impl GatewayTasksBuilder {
ecash_manager,
)
.with_custom_gateway_transceiver(transceiver)
.with_shutdown(self.shutdown.fork("authenticator_sp"))
.with_shutdown(self.legacy_task_client.fork("authenticator_sp"))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
.with_custom_topology_provider(topology_provider)
@@ -460,7 +465,7 @@ impl GatewayTasksBuilder {
pub fn build_stale_messages_cleaner(&self) -> StaleMessagesCleaner {
StaleMessagesCleaner::new(
&self.storage,
self.shutdown.fork("stale_messages_cleaner"),
self.legacy_task_client.fork("stale_messages_cleaner"),
self.config.debug.stale_messages_max_age,
self.config.debug.stale_messages_cleaner_run_interval,
)
@@ -471,13 +476,17 @@ impl GatewayTasksBuilder {
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
let _ = self.metrics.clone();
let _ = self.shutdown_token.clone();
unimplemented!("wireguard is not supported on this platform")
}
#[cfg(target_os = "linux")]
pub async fn try_start_wireguard(
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<
nym_wireguard_private_metadata::ShutdownHandles,
Box<dyn std::error::Error + Send + Sync>,
> {
let all_peers = self.get_wireguard_peers().await?;
let Some(wireguard_data) = self.wireguard_data.take() else {
@@ -492,14 +501,42 @@ impl GatewayTasksBuilder {
);
};
let router = nym_wireguard_private_metadata::RouterBuilder::with_default_routes();
let router = router.with_state(nym_wireguard_private_metadata::AppState::new(
nym_wireguard_private_metadata::PeerControllerTransceiver::new(
wireguard_data.inner.peer_tx().clone(),
),
));
let bind_address = std::net::SocketAddr::new(
wireguard_data.inner.config().private_ipv4.into(),
wireguard_data.inner.config().announced_metadata_port,
);
let server = router.build_server(&bind_address).await?;
let cancel_token: tokio_util::sync::CancellationToken = (*self.shutdown_token).clone();
let axum_shutdown_receiver = cancel_token.clone().cancelled_owned();
let server_handle = tokio::spawn(async move {
{
info!("Started Wireguard Axum HTTP V2 server on {bind_address}");
server.run(axum_shutdown_receiver).await
}
});
let wg_handle = nym_wireguard::start_wireguard(
ecash_manager,
self.metrics.clone(),
all_peers,
self.shutdown.fork("wireguard"),
self.legacy_task_client.fork("wireguard"),
wireguard_data,
)
.await?;
Ok(wg_handle)
let shutdown_handles = nym_wireguard_private_metadata::ShutdownHandles::new(
server_handle,
wg_handle,
cancel_token,
);
Ok(shutdown_handles)
}
}
@@ -311,7 +311,10 @@ impl From<Authenticator> for AuthenticatorDetails {
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct WireguardDetails {
// NOTE: the port field is deprecated in favour of tunnel_port
pub port: u16,
pub tunnel_port: u16,
pub metadata_port: u16,
pub public_key: String,
}
@@ -320,6 +323,8 @@ impl From<Wireguard> for WireguardDetails {
fn from(value: Wireguard) -> Self {
WireguardDetails {
port: value.port,
tunnel_port: value.tunnel_port,
metadata_port: value.metadata_port,
public_key: value.public_key,
}
}
@@ -16,9 +16,16 @@ pub struct Gateway {
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Wireguard {
#[deprecated(note = "use specific port instead (tunnel or metadata service)")]
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
pub port: u16,
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
pub tunnel_port: u16,
#[cfg_attr(feature = "openapi", schema(example = 51830, default = 51830))]
pub metadata_port: u16,
pub public_key: String,
}
+4 -4
View File
@@ -276,13 +276,13 @@ pub(crate) struct WireguardArgs {
)]
pub(crate) wireguard_bind_address: Option<SocketAddr>,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
#[clap(
long,
env = NYMNODE_WG_ANNOUNCED_PORT_ARG
)]
pub(crate) wireguard_announced_port: Option<u16>,
pub(crate) wireguard_tunnel_announced_port: Option<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
@@ -311,8 +311,8 @@ impl WireguardArgs {
section.bind_address = bind_address
}
if let Some(announced_port) = self.wireguard_announced_port {
section.announced_port = announced_port
if let Some(announced_tunnel_port) = self.wireguard_tunnel_announced_port {
section.announced_tunnel_port = announced_tunnel_port
}
if let Some(private_network_prefix) = self.wireguard_private_network_prefix {
+2 -1
View File
@@ -202,7 +202,8 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig {
bind_address: config.wireguard.bind_address,
private_ipv4: config.wireguard.private_ipv4,
private_ipv6: config.wireguard.private_ipv6,
announced_port: config.wireguard.announced_port,
announced_tunnel_port: config.wireguard.announced_tunnel_port,
announced_metadata_port: config.wireguard.announced_metadata_port,
private_network_prefix_v4: config.wireguard.private_network_prefix_v4,
private_network_prefix_v6: config.wireguard.private_network_prefix_v6,
storage_paths: config.wireguard.storage_paths.clone(),
+13 -7
View File
@@ -10,7 +10,7 @@ use human_repr::HumanCount;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::{
mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT,
DEFAULT_VERLOC_LISTENING_PORT, WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4,
DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4,
WG_TUN_DEVICE_IP_ADDRESS_V6,
};
use nym_config::defaults::{WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6};
@@ -931,9 +931,13 @@ pub struct Wireguard {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel 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,
pub announced_tunnel_port: u16,
/// Metadata port announced to external clients wishing to connect to the metadata endpoint.
/// Useful in the instances where the node is behind a proxy.
pub announced_metadata_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
@@ -951,10 +955,11 @@ impl Wireguard {
pub fn new_default<P: AsRef<Path>>(data_dir: P) -> Self {
Wireguard {
enabled: false,
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
announced_port: WG_PORT,
announced_tunnel_port: WG_TUNNEL_PORT,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
storage_paths: persistence::WireguardPaths::new(data_dir),
@@ -968,7 +973,8 @@ impl From<Wireguard> for nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
announced_tunnel_port: value.announced_tunnel_port,
announced_metadata_port: value.announced_metadata_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
@@ -981,7 +987,7 @@ impl From<Wireguard> for nym_authenticator::config::Authenticator {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
tunnel_announced_port: value.announced_tunnel_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
+2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
mod old_config_v1;
mod old_config_v10;
mod old_config_v2;
mod old_config_v3;
mod old_config_v4;
@@ -12,6 +13,7 @@ mod old_config_v8;
mod old_config_v9;
pub use old_config_v1::try_upgrade_config_v1;
pub use old_config_v10::try_upgrade_config_v10;
pub use old_config_v2::try_upgrade_config_v2;
pub use old_config_v3::try_upgrade_config_v3;
pub use old_config_v4::try_upgrade_config_v4;
File diff suppressed because it is too large Load Diff
@@ -1,29 +1,26 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::authenticator::{Authenticator, AuthenticatorDebug};
use crate::config::gateway_tasks::{
ClientBandwidthDebug, StaleMessageDebug, ZkNymTicketHandlerDebug,
use crate::config::old_configs::old_config_v10::{
AuthenticatorDebugV10, AuthenticatorPathsV10, AuthenticatorV10, ClientBandwidthDebugV10,
ConfigV10, GatewayTasksConfigDebugV10, GatewayTasksConfigV10, GatewayTasksPathsV10, HostV10,
HttpV10, IpPacketRouterDebugV10, IpPacketRouterPathsV10, IpPacketRouterV10, KeysPathsV10,
LoggingSettingsV10, MixnetDebugV10, MixnetV10, NetworkRequesterDebugV10,
NetworkRequesterPathsV10, NetworkRequesterV10, NodeModesV10, NymNodePathsV10,
ReplayProtectionDebugV10, ReplayProtectionPathsV10, ReplayProtectionV10,
ServiceProvidersConfigDebugV10, ServiceProvidersConfigV10, ServiceProvidersPathsV10,
StaleMessageDebugV10, VerlocDebugV10, VerlocV10, WireguardPathsV10, WireguardV10,
ZkNymTicketHandlerDebugV10,
};
use crate::config::persistence::{
AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths,
NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths,
DEFAULT_PRIMARY_X25519_SPHINX_KEY_FILENAME, DEFAULT_SECONDARY_X25519_SPHINX_KEY_FILENAME,
};
use crate::config::service_providers::{
IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug,
};
use crate::config::{
gateway_tasks, service_providers, Config, GatewayTasksConfig, Host, Http, Mixnet, MixnetDebug,
NodeModes, ReplayProtection, ReplayProtectionDebug, ServiceProvidersConfig, Verloc,
VerlocDebug, Wireguard, DEFAULT_HTTP_PORT,
};
use crate::config::{NodeModes, DEFAULT_HTTP_PORT};
use crate::error::{KeyIOFailure, NymNodeError};
use crate::node::helpers::{get_current_rotation_id, load_key, store_key};
use crate::node::key_rotation::key::SphinxPrivateKey;
use celes::Country;
use clap::ValueEnum;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
use nym_config::defaults::DEFAULT_VERLOC_LISTENING_PORT;
use nym_config::helpers::{in6addr_any_init, inaddr_any};
@@ -1330,7 +1327,7 @@ async fn upgrade_sphinx_key(old_cfg: &ConfigV9) -> Result<(PathBuf, PathBuf), Ny
pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
path: P,
prev_config: Option<ConfigV9>,
) -> Result<Config, NymNodeError> {
) -> Result<ConfigV10, NymNodeError> {
debug!("attempting to load v9 config...");
let old_cfg = if let Some(prev_config) = prev_config {
@@ -1342,33 +1339,33 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
let (primary_x25519_sphinx_key_file, secondary_x25519_sphinx_key_file) =
upgrade_sphinx_key(&old_cfg).await?;
let cfg = Config {
let cfg = ConfigV10 {
save_path: old_cfg.save_path,
id: old_cfg.id,
modes: NodeModes {
modes: NodeModesV10 {
mixnode: old_cfg.modes.mixnode,
entry: old_cfg.modes.entry,
exit: old_cfg.modes.exit,
},
host: Host {
host: HostV10 {
public_ips: old_cfg.host.public_ips,
hostname: old_cfg.host.hostname,
location: old_cfg.host.location,
},
mixnet: Mixnet {
mixnet: MixnetV10 {
bind_address: old_cfg.mixnet.bind_address,
announce_port: old_cfg.mixnet.announce_port,
nym_api_urls: old_cfg.mixnet.nym_api_urls,
nyxd_urls: old_cfg.mixnet.nyxd_urls,
replay_protection: ReplayProtection {
storage_paths: ReplayProtectionPaths {
replay_protection: ReplayProtectionV10 {
storage_paths: ReplayProtectionPathsV10 {
current_bloomfilters_directory: old_cfg
.mixnet
.replay_protection
.storage_paths
.current_bloomfilters_directory,
},
debug: ReplayProtectionDebug {
debug: ReplayProtectionDebugV10 {
unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled,
maximum_replay_detection_deferral: old_cfg
.mixnet
@@ -1404,7 +1401,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
},
key_rotation: Default::default(),
debug: MixnetDebug {
debug: MixnetDebugV10 {
maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay,
packet_forwarding_initial_backoff: old_cfg
.mixnet
@@ -1420,8 +1417,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
..Default::default()
},
},
storage_paths: NymNodePaths {
keys: KeysPaths {
storage_paths: NymNodePathsV10 {
keys: KeysPathsV10 {
private_ed25519_identity_key_file: old_cfg
.storage_paths
.keys
@@ -1443,7 +1440,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
description: old_cfg.storage_paths.description,
},
http: Http {
http: HttpV10 {
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,
@@ -1452,10 +1449,10 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
node_load_cache_ttl: old_cfg.http.node_load_cache_ttl,
},
verloc: Verloc {
verloc: VerlocV10 {
bind_address: old_cfg.verloc.bind_address,
announce_port: old_cfg.verloc.announce_port,
debug: VerlocDebug {
debug: VerlocDebugV10 {
packets_per_node: old_cfg.verloc.debug.packets_per_node,
connection_timeout: old_cfg.verloc.debug.connection_timeout,
packet_timeout: old_cfg.verloc.debug.packet_timeout,
@@ -1465,7 +1462,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
retry_timeout: old_cfg.verloc.debug.retry_timeout,
},
},
wireguard: Wireguard {
wireguard: WireguardV10 {
enabled: old_cfg.wireguard.enabled,
bind_address: old_cfg.wireguard.bind_address,
private_ipv4: old_cfg.wireguard.private_ipv4,
@@ -1473,7 +1470,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
announced_port: old_cfg.wireguard.announced_port,
private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4,
private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6,
storage_paths: WireguardPaths {
storage_paths: WireguardPathsV10 {
private_diffie_hellman_key_file: old_cfg
.wireguard
.storage_paths
@@ -1484,8 +1481,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.public_diffie_hellman_key_file,
},
},
gateway_tasks: GatewayTasksConfig {
storage_paths: GatewayTasksPaths {
gateway_tasks: GatewayTasksConfigV10 {
storage_paths: GatewayTasksPathsV10 {
clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage,
stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage,
cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic,
@@ -1494,12 +1491,12 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
ws_bind_address: old_cfg.gateway_tasks.ws_bind_address,
announce_ws_port: old_cfg.gateway_tasks.announce_ws_port,
announce_wss_port: old_cfg.gateway_tasks.announce_wss_port,
debug: gateway_tasks::Debug {
debug: GatewayTasksConfigDebugV10 {
message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit,
maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections,
minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance,
max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew,
stale_messages: StaleMessageDebug {
stale_messages: StaleMessageDebugV10 {
cleaner_run_interval: old_cfg
.gateway_tasks
.debug
@@ -1507,7 +1504,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.cleaner_run_interval,
max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age,
},
client_bandwidth: ClientBandwidthDebug {
client_bandwidth: ClientBandwidthDebugV10 {
max_flushing_rate: old_cfg
.gateway_tasks
.debug
@@ -1519,7 +1516,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_bandwidth
.max_delta_flushing_amount,
},
zk_nym_tickets: ZkNymTicketHandlerDebug {
zk_nym_tickets: ZkNymTicketHandlerDebugV10 {
revocation_bandwidth_penalty: old_cfg
.gateway_tasks
.debug
@@ -1544,11 +1541,11 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
},
},
service_providers: ServiceProvidersConfig {
storage_paths: ServiceProvidersPaths {
service_providers: ServiceProvidersConfigV10 {
storage_paths: ServiceProvidersPathsV10 {
clients_storage: old_cfg.service_providers.storage_paths.clients_storage,
stats_storage: old_cfg.service_providers.storage_paths.stats_storage,
network_requester: NetworkRequesterPaths {
network_requester: NetworkRequesterPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1585,7 +1582,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.network_requester
.gateway_registrations,
},
ip_packet_router: IpPacketRouterPaths {
ip_packet_router: IpPacketRouterPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1622,7 +1619,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.ip_packet_router
.gateway_registrations,
},
authenticator: AuthenticatorPaths {
authenticator: AuthenticatorPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1662,8 +1659,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
open_proxy: old_cfg.service_providers.open_proxy,
upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url,
network_requester: NetworkRequester {
debug: NetworkRequesterDebug {
network_requester: NetworkRequesterV10 {
debug: NetworkRequesterDebugV10 {
enabled: old_cfg.service_providers.network_requester.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1677,8 +1674,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_debug,
},
},
ip_packet_router: IpPacketRouter {
debug: IpPacketRouterDebug {
ip_packet_router: IpPacketRouterV10 {
debug: IpPacketRouterDebugV10 {
enabled: old_cfg.service_providers.ip_packet_router.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1692,8 +1689,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_debug,
},
},
authenticator: Authenticator {
debug: AuthenticatorDebug {
authenticator: AuthenticatorV10 {
debug: AuthenticatorDebugV10 {
enabled: old_cfg.service_providers.authenticator.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1703,12 +1700,12 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
client_debug: old_cfg.service_providers.authenticator.debug.client_debug,
},
},
debug: service_providers::Debug {
debug: ServiceProvidersConfigDebugV10 {
message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit,
},
},
metrics: Default::default(),
logging: LoggingSettings {},
logging: LoggingSettingsV10 {},
debug: Default::default(),
};
Ok(cfg)
+5 -1
View File
@@ -145,7 +145,11 @@ private_ipv6 = '{{ wireguard.private_ipv6 }}'
# Port announced to external clients wishing to connect to the wireguard interface.
# Useful in the instances where the node is behind a proxy.
announced_port = {{ wireguard.announced_port }}
announced_tunnel_port = {{ wireguard.announced_tunnel_port }}
# Port announced to external clients wishing to connect to the metadata service.
# Useful in the instances where the node is behind a proxy.
announced_metadata_port = {{ wireguard.announced_metadata_port }}
# The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
# The maximum value for IPv4 is 32
+2 -1
View File
@@ -16,7 +16,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> {
let cfg = try_upgrade_config_v6(path, cfg).await.ok();
let cfg = try_upgrade_config_v7(path, cfg).await.ok();
let cfg = try_upgrade_config_v8(path, cfg).await.ok();
match try_upgrade_config_v9(path, cfg).await {
let cfg = try_upgrade_config_v9(path, cfg).await.ok();
match try_upgrade_config_v10(path, cfg).await {
Ok(cfg) => cfg.save(),
Err(e) => {
tracing::error!("Failed to finish upgrade: {e}");
+9 -3
View File
@@ -605,7 +605,8 @@ impl NymNode {
metrics_sender: MetricEventsSender,
active_clients_store: ActiveClientsStore,
mix_packet_sender: MixForwardingSender,
task_client: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
) -> Result<(), NymNodeError> {
let config = gateway_tasks_config(&self.config);
@@ -623,7 +624,8 @@ impl NymNode {
metrics_sender,
self.metrics.clone(),
self.entry_gateway.mnemonic.clone(),
task_client,
legacy_task_client,
shutdown_token,
);
// if we're running in entry mode, start the websocket
@@ -716,8 +718,11 @@ impl NymNode {
// entry gateway info
let wireguard = if self.config.wireguard.enabled {
#[allow(deprecated)]
Some(api_requests::v1::gateway::models::Wireguard {
port: self.config.wireguard.announced_port,
port: self.config.wireguard.announced_tunnel_port,
tunnel_port: self.config.wireguard.announced_tunnel_port,
metadata_port: self.config.wireguard.announced_metadata_port,
public_key: self.x25519_wireguard_key()?.to_string(),
})
} else {
@@ -1183,6 +1188,7 @@ impl NymNode {
active_clients_store,
mix_packet_sender,
self.shutdown_manager.subscribe_legacy("gateway-tasks"),
self.shutdown_manager.child_token("gateway-tasks"),
)
.await?;
@@ -0,0 +1,243 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_bin_common::logging::LoggingSettings;
pub use nym_client_core::config::Config as BaseClientConfig;
use nym_client_core::{cli_helpers::CliClientConfig, config::disk_persistence::CommonClientPaths};
use nym_config::{
must_get_home, save_formatted_config_to_file, NymConfigTemplate, OptionalSet,
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use nym_network_defaults::{
WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6,
WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6,
};
use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR;
pub use persistence::AuthenticatorPaths;
use serde::{Deserialize, Serialize};
use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::{Path, PathBuf},
str::FromStr,
};
use template::CONFIG_TEMPLATE;
pub mod helpers;
pub mod old_config_v1_1_54;
pub mod persistence;
pub mod template;
const DEFAULT_AUTHENTICATOR_DIR: &str = "authenticator";
/// Derive default path to authenticator's config directory.
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SERVICE_PROVIDERS_DIR)
.join(DEFAULT_AUTHENTICATOR_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to authenticator's config file.
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
/// Derive default path to authenticator's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SERVICE_PROVIDERS_DIR)
.join(DEFAULT_AUTHENTICATOR_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(flatten)]
pub base: BaseClientConfig,
#[serde(default)]
pub authenticator: Authenticator,
pub storage_paths: AuthenticatorPaths,
pub logging: LoggingSettings,
}
impl NymConfigTemplate for Config {
fn template(&self) -> &'static str {
CONFIG_TEMPLATE
}
}
impl CliClientConfig for Config {
fn common_paths(&self) -> &CommonClientPaths {
&self.storage_paths.common_paths
}
fn core_config(&self) -> &BaseClientConfig {
&self.base
}
fn default_store_location(&self) -> PathBuf {
self.default_location()
}
fn save_to<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
save_formatted_config_to_file(self, path)
}
}
impl Config {
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
authenticator: Default::default(),
storage_paths: AuthenticatorPaths::new_base(default_data_directory(id.as_ref())),
logging: Default::default(),
}
}
#[allow(unused)]
pub fn with_data_directory<P: AsRef<Path>>(mut self, data_directory: P) -> Self {
self.storage_paths = AuthenticatorPaths::new_base(data_directory);
self
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
nym_config::read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.base.client.id)
}
#[allow(unused)]
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.base.validate()
}
#[doc(hidden)]
pub fn set_no_poisson_process(&mut self) {
self.base.set_no_poisson_process()
}
// poor man's 'builder' method
#[allow(unused)]
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.base = f(self.base, val);
self
}
// helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config
// (plz, lets refactor it)
pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.base = self.base.with_optional(f, val);
self
}
#[allow(unused)]
pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr,
<T as FromStr>::Err: std::fmt::Debug,
{
self.base = self.base.with_optional_env(f, val, env_var);
self
}
pub fn with_optional_base_custom_env<F, T, G>(
mut self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T,
{
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
self
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Authenticator {
/// Socket address this node will use for binding its wireguard interface.
/// default: `0.0.0.0:51822`
pub bind_address: SocketAddr,
/// Private IP address of the wireguard gateway.
/// default: `10.1.0.1`
pub private_ipv4: Ipv4Addr,
/// Private IP address of the wireguard gateway.
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub tunnel_announced_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
pub private_network_prefix_v4: u8,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6.
/// The maximum value for IPv6 is 128
pub private_network_prefix_v6: u8,
}
impl Default for Authenticator {
fn default() -> Self {
Self {
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
tunnel_announced_port: WG_TUNNEL_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
}
}
}
impl From<Authenticator> for nym_wireguard_types::Config {
fn from(value: Authenticator) -> Self {
nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_tunnel_port: value.tunnel_announced_port,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
}
}