diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 9adb0df9d8..60617c55d6 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -8,10 +8,13 @@ on: - 'gateway/**' - 'integrations/**' - 'nym-api/**' + - 'nym-authenticator-client/**' - 'nym-credential-proxy/**' + - 'nym-ip-packet-client/**' - 'nym-network-monitor/**' - 'nym-node/**' - 'nym-node-status-api/**' + - 'nym-registration-client/**' - 'nym-statistics-api/**' - 'nym-outfox/**' - 'nym-validator-rewarder/**' diff --git a/nym-authenticator-client/src/error.rs b/nym-authenticator-client/src/error.rs index 39d991f886..43598acdd8 100644 --- a/nym-authenticator-client/src/error.rs +++ b/nym-authenticator-client/src/error.rs @@ -2,7 +2,7 @@ use nym_credentials_interface::TicketType; use nym_sdk::mixnet::InputMessage; #[derive(thiserror::Error, Debug)] -pub enum Error { +pub enum AuthenticationClientError { #[error("mixnet client stopped returning responses")] NoMixnetMessagesReceived, @@ -42,10 +42,19 @@ pub enum Error { #[error("unknown authenticator version number")] UnsupportedAuthenticatorVersion, +} - #[error("failed to wait on AuthenticatorClientListener")] - FailedToJoinOnTask(#[from] tokio::task::JoinError), +#[derive(thiserror::Error, Debug)] +pub enum RegistrationError { + #[error(transparent)] + NoCredentialSent(AuthenticationClientError), // This intentionnally doesn't use `from` to avoid random ? operator to land here when they shouldn't + + #[error("an error occured after a credential was sent : {source}")] + CredentialSent { + #[source] + source: AuthenticationClientError, + }, } // Result type based on our error type -pub type Result = std::result::Result; +pub(crate) type Result = std::result::Result; diff --git a/nym-authenticator-client/src/lib.rs b/nym-authenticator-client/src/lib.rs index 8fe7edb511..ab811ef79d 100644 --- a/nym-authenticator-client/src/lib.rs +++ b/nym-authenticator-client/src/lib.rs @@ -6,11 +6,11 @@ use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND use nym_crypto::asymmetric::x25519::KeyPair; use nym_registration_common::GatewayData; use std::net::{IpAddr, SocketAddr}; -use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tracing::{debug, error, trace}; +use crate::error::Result; use crate::mixnet_listener::{MixnetMessageBroadcastReceiver, MixnetMessageInputSender}; use nym_authenticator_requests::{ AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, @@ -25,7 +25,7 @@ mod error; mod helpers; mod mixnet_listener; -pub use crate::error::{Error, Result}; +pub use crate::error::{AuthenticationClientError, RegistrationError}; pub use crate::mixnet_listener::{AuthClientMixnetListener, AuthClientMixnetListenerHandle}; pub struct AuthenticatorClient { @@ -91,7 +91,7 @@ impl AuthenticatorClient { self.mixnet_sender .send(input_message) .await - .map_err(|e| Error::SendMixnetMessage(Box::new(e)))?; + .map_err(|e| AuthenticationClientError::SendMixnetMessage(Box::new(e)))?; Ok(request_id) } @@ -104,11 +104,11 @@ impl AuthenticatorClient { tokio::select! { _ = &mut timeout => { error!("Timed out waiting for reply to connect request"); - return Err(Error::TimeoutWaitingForConnectResponse); + return Err(AuthenticationClientError::TimeoutWaitingForConnectResponse); } msg = self.mixnet_listener.recv() => match msg { Err(_) => { - return Err(Error::NoMixnetMessagesReceived); + return Err(AuthenticationClientError::NoMixnetMessagesReceived); } Ok(msg) => { let Some(header) = msg.message.first_chunk::<2>() else { @@ -131,12 +131,12 @@ impl AuthenticatorClient { // Then we deserialize the message debug!("AuthClient: got message while waiting for connect response with version {version:?}"); let ret: Result = match version { - AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion), + AuthenticatorVersion::V1 => Err(AuthenticationClientError::UnsupportedVersion), AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into), AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into), AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into), AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into), - AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion), + AuthenticatorVersion::UNKNOWN => Err(AuthenticationClientError::UnknownVersion), }; let Ok(response) = ret else { // This is ok, it's likely just one of our self-pings @@ -158,10 +158,14 @@ impl AuthenticatorClient { &mut self, controller: &dyn BandwidthTicketProvider, ticketbook_type: TicketType, - ) -> Result { + ) -> std::result::Result { debug!("Registering with the wg gateway..."); let init_message = match self.auth_version { - AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => { + return Err(RegistrationError::NoCredentialSent( + AuthenticationClientError::UnsupportedAuthenticatorVersion, + )); + } AuthenticatorVersion::V2 => { ClientMessage::Initial(Box::new(v2::registration::InitMessage { pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()), @@ -182,16 +186,20 @@ impl AuthenticatorClient { pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()), })) } - AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion), }; trace!("sending init msg to {}: {:?}", &self.ip_addr, &init_message); - let response = self.send_and_wait_for_response(&init_message).await?; + let response = self + .send_and_wait_for_response(&init_message) + .await + .map_err(RegistrationError::NoCredentialSent)?; let registered_data = match response { AuthenticatorResponse::PendingRegistration(pending_registration_response) => { // Unwrap since we have already checked that we have the keypair. debug!("Verifying data"); if let Err(e) = pending_registration_response.verify(self.keypair.private_key()) { - return Err(Error::VerificationFailed(e)); + return Err(RegistrationError::NoCredentialSent( + AuthenticationClientError::VerificationFailed(e), + )); } trace!( @@ -199,6 +207,7 @@ impl AuthenticatorClient { &self.ip_addr, &pending_registration_response ); + // This call takes care of updating the credential count in storage, so failure of this must be counted as credential waste let credential = Some( controller .get_ecash_ticket( @@ -207,15 +216,21 @@ impl AuthenticatorClient { DEFAULT_TICKETS_TO_SPEND, ) .await - .map_err(|source| Error::GetTicket { - ticketbook_type, - source, + .map_err(|source| RegistrationError::CredentialSent { + source: AuthenticationClientError::GetTicket { + ticketbook_type, + source, + }, })? .data, ); let finalized_message = match self.auth_version { - AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => { + return Err(RegistrationError::CredentialSent { + source: AuthenticationClientError::UnsupportedAuthenticatorVersion, + }); + } AuthenticatorVersion::V2 => { ClientMessage::Final(Box::new(v2::registration::FinalMessage { gateway_client: v2::registration::GatewayClient::new( @@ -260,23 +275,29 @@ impl AuthenticatorClient { credential, })) } - AuthenticatorVersion::UNKNOWN => { - return Err(Error::UnsupportedAuthenticatorVersion); - } }; trace!( "sending final msg to {}: {:?}", &self.ip_addr, &finalized_message ); - let response = self.send_and_wait_for_response(&finalized_message).await?; + let response = self + .send_and_wait_for_response(&finalized_message) + .await + .map_err(|source| RegistrationError::CredentialSent { source })?; let AuthenticatorResponse::Registered(registered_response) = response else { - return Err(Error::InvalidGatewayAuthResponse); + return Err(RegistrationError::CredentialSent { + source: AuthenticationClientError::InvalidGatewayAuthResponse, + }); }; registered_response } AuthenticatorResponse::Registered(registered_response) => registered_response, - _ => return Err(Error::InvalidGatewayAuthResponse), + _ => { + return Err(RegistrationError::NoCredentialSent( + AuthenticationClientError::InvalidGatewayAuthResponse, + )); + } }; trace!( @@ -286,12 +307,7 @@ impl AuthenticatorClient { let gateway_data = GatewayData { public_key: registered_data.pub_key().inner().into(), - endpoint: SocketAddr::from_str(&format!( - "{}:{}", - self.ip_addr, - registered_data.wg_port() - )) - .map_err(Error::FailedToParseEntryGatewaySocketAddr)?, + endpoint: SocketAddr::new(self.ip_addr, registered_data.wg_port()), private_ipv4: registered_data.private_ips().ipv4, private_ipv6: registered_data.private_ips().ipv6, }; @@ -299,9 +315,12 @@ impl AuthenticatorClient { Ok(gateway_data) } + // This is up to the caller to know nothing is ever spent there pub async fn query_bandwidth(&mut self) -> Result> { let query_message = match self.auth_version { - AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::V1 => { + return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion); + } AuthenticatorVersion::V2 => ClientMessage::Query(Box::new(QueryMessageImpl { pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()), version: AuthenticatorVersion::V2, @@ -318,7 +337,9 @@ impl AuthenticatorClient { pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()), version: AuthenticatorVersion::V5, })), - AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::UNKNOWN => { + return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion); + } }; let response = self.send_and_wait_for_response(&query_message).await?; @@ -332,7 +353,7 @@ impl AuthenticatorClient { return Ok(None); } } - _ => return Err(Error::InvalidGatewayAuthResponse), + _ => return Err(AuthenticationClientError::InvalidGatewayAuthResponse), }; let remaining_pretty = if available_bandwidth > 1024 * 1024 { @@ -347,13 +368,13 @@ impl AuthenticatorClient { ); if available_bandwidth < 1024 * 1024 { tracing::warn!( - "Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon - " - ); + "Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon" + ); } Ok(Some(available_bandwidth)) } + // Since the caller provides the credential, it knows it is spent pub async fn top_up(&mut self, credential: CredentialSpendingData) -> Result { let top_up_message = match self.auth_version { AuthenticatorVersion::V3 => ClientMessage::TopUp(Box::new(v3::topup::TopUpMessage { @@ -371,7 +392,7 @@ impl AuthenticatorClient { credential, })), AuthenticatorVersion::V1 | AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => { - return Err(Error::UnsupportedAuthenticatorVersion); + return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion); } }; let response = self.send_and_wait_for_response(&top_up_message).await?; @@ -380,7 +401,7 @@ impl AuthenticatorClient { AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => { top_up_bandwidth_response.available_bandwidth() } - _ => return Err(Error::InvalidGatewayAuthResponse), + _ => return Err(AuthenticationClientError::InvalidGatewayAuthResponse), }; Ok(remaining_bandwidth) diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index ed3b09c220..822d44dcae 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -35,19 +35,85 @@ pub enum RegistrationClientError { #[error("timeout connecting the mixnet client")] Timeout(#[from] tokio::time::error::Elapsed), - #[error("failed to register wireguard with the gateway for {gateway_id}")] - EntryGatewayRegisterWireguard { + #[error( + "failed to register wireguard with the gateway for {gateway_id}, no credential was sent" + )] + WireguardEntryRegistration { gateway_id: String, authenticator_address: Box, #[source] - source: Box, + source: Box, }, - #[error("failed to register wireguard with the gateway for {gateway_id}")] - ExitGatewayRegisterWireguard { + #[error( + "failed to register wireguard with the gateway for {gateway_id}, no credential was sent" + )] + WireguardExitRegistration { gateway_id: String, authenticator_address: Box, #[source] - source: Box, + source: Box, + }, + + #[error( + "failed to register wireguard with the gateway for {gateway_id}, a credential was sent" + )] + WireguardEntryRegistrationCredentialSent { + gateway_id: String, + authenticator_address: Box, + #[source] + source: Box, + }, + + #[error( + "failed to register wireguard with the gateway for {gateway_id}, a credential was sent" + )] + WireguardExitRegistrationCredentialSent { + gateway_id: String, + authenticator_address: Box, + #[source] + source: Box, }, } + +impl RegistrationClientError { + pub fn from_authenticator_error( + error: nym_authenticator_client::RegistrationError, + gateway_id: String, + authenticator_address: nym_sdk::mixnet::Recipient, + entry: bool, + ) -> Self { + match error { + nym_authenticator_client::RegistrationError::NoCredentialSent(source) => { + if entry { + Self::WireguardEntryRegistration { + gateway_id, + authenticator_address: Box::new(authenticator_address), + source: Box::new(source), + } + } else { + Self::WireguardExitRegistration { + gateway_id, + authenticator_address: Box::new(authenticator_address), + source: Box::new(source), + } + } + } + nym_authenticator_client::RegistrationError::CredentialSent { source } => { + if entry { + Self::WireguardEntryRegistrationCredentialSent { + gateway_id, + authenticator_address: Box::new(authenticator_address), + source: Box::new(source), + } + } else { + Self::WireguardExitRegistrationCredentialSent { + gateway_id, + authenticator_address: Box::new(authenticator_address), + source: Box::new(source), + } + } + } + } + } +} diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index ece0e6fd64..4317787c1f 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -162,20 +162,22 @@ impl RegistrationClient { let entry = entry.map_err(|source| RegistrationError { mixnet_client: None, - source: RegistrationClientError::EntryGatewayRegisterWireguard { - gateway_id: self.config.entry.node.identity.to_base58_string(), - authenticator_address: Box::new(entry_auth_address), - source: Box::new(source), - }, + source: RegistrationClientError::from_authenticator_error( + source, + self.config.entry.node.identity.to_base58_string(), + entry_auth_address, + true, + ), })?; let exit = exit.map_err(|source| RegistrationError { mixnet_client: None, - source: RegistrationClientError::EntryGatewayRegisterWireguard { - gateway_id: self.config.exit.node.identity.to_base58_string(), - authenticator_address: Box::new(exit_auth_address), - source: Box::new(source), - }, + source: RegistrationClientError::from_authenticator_error( + source, + self.config.exit.node.identity.to_base58_string(), + exit_auth_address, + false, + ), })?; Ok(RegistrationResult::Wireguard(Box::new(