-
Notifications
You must be signed in to change notification settings - Fork 264
Wireguard private metadata #5915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // Copyright 2025 - Nym Technologies SA <[email protected]> | ||
| // 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(), | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright 2025 - Nym Technologies SA <[email protected]> | ||
| // 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<()>>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Copyright 2025 - Nym Technologies SA <[email protected]> | ||
| // 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // Copyright 2025 - Nym Technologies SA <[email protected]> | ||
| // 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 { | ||
neacsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Copyright 2025 - Nym Technologies SA <[email protected]> | ||
| // 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(()) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.