Moving clients crate from vpn-client repo to here (#6015)
* moving crates as is * changes due to crate moving * cargo fmt
This commit is contained in:
Generated
+55
-12
@@ -5010,6 +5010,25 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"futures",
|
||||
"nym-authenticator-requests",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-sdk",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-wireguard-types",
|
||||
"semver 1.0.26",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator-requests"
|
||||
version = "0.1.0"
|
||||
@@ -5607,6 +5626,7 @@ dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes-gcm-siv",
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
"bs58",
|
||||
"cipher",
|
||||
@@ -6005,6 +6025,20 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ip-packet-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-sdk",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ip-packet-requests"
|
||||
version = "0.1.0"
|
||||
@@ -6409,7 +6443,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.3.1"
|
||||
version = "3.3.2"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -7375,6 +7409,26 @@ dependencies = [
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wg-gateway-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-authenticator-client",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-node-requests",
|
||||
"nym-pemstore",
|
||||
"nym-sdk",
|
||||
"nym-statistics-common",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
@@ -12002,17 +12056,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
||||
@@ -111,10 +111,12 @@ members = [
|
||||
"gateway",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-authenticator-client",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-ip-packet-client",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
@@ -125,6 +127,7 @@ members = [
|
||||
"nym-outfox", "nym-signers-monitor",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nym-wg-gateway-client",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
aes-gcm-siv = { workspace = true, optional = true }
|
||||
aes = { workspace = true, optional = true }
|
||||
aead = { workspace = true, optional = true }
|
||||
base64.workspace = true
|
||||
bs58 = { workspace = true }
|
||||
blake3 = { workspace = true, features = ["traits-preview"], optional = true }
|
||||
ctr = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use base64::Engine;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -158,6 +159,15 @@ impl PublicKey {
|
||||
.map_err(|source| KeyRecoveryError::MalformedPublicKeyString { source })?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Option<Self> {
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(s).ok()?;
|
||||
Self::from_bytes(&bytes).ok()
|
||||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::engine::general_purpose::STANDARD.encode(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKey {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "nym-authenticator-client"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode.workspace = true
|
||||
futures.workspace = true
|
||||
semver.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
nym-authenticator-requests = { path = "../common/authenticator-requests" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" }
|
||||
nym-wireguard-types = { path = "../common/wireguard-types" }
|
||||
@@ -0,0 +1,42 @@
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("mixnet client stopped returning responses")]
|
||||
NoMixnetMessagesReceived,
|
||||
|
||||
#[error("failed to get version from message")]
|
||||
NoVersionInMessage,
|
||||
|
||||
#[error(
|
||||
"received response with version v{received}, the client is too new and can only understand v{expected}"
|
||||
)]
|
||||
ReceivedResponseWithOldVersion { expected: u8, received: u8 },
|
||||
|
||||
#[error(
|
||||
"received response with version v{received}, the client is too old and can only understand v{expected}"
|
||||
)]
|
||||
ReceivedResponseWithNewVersion { expected: u8, received: u8 },
|
||||
|
||||
#[error("failed to send mixnet message")]
|
||||
SendMixnetMessage(#[source] Box<nym_sdk::Error>),
|
||||
|
||||
#[error("timeout waiting for connect response from exit gateway (authenticator)")]
|
||||
TimeoutWaitingForConnectResponse,
|
||||
|
||||
#[error("unable to get mixnet handle when sending authenticator message")]
|
||||
UnableToGetMixnetHandle,
|
||||
|
||||
#[error("unknown version number")]
|
||||
UnknownVersion,
|
||||
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
#[error("gateway doesn't support this type of message")]
|
||||
UnsupportedMessage,
|
||||
|
||||
#[error(transparent)]
|
||||
AuthenticatorRequests(#[from] nym_authenticator_requests::Error),
|
||||
}
|
||||
|
||||
// Result type based on our error type
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// To remove with the Registration Client PR
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_sdk::mixnet::{MixnetClient, ReconstructedMessage};
|
||||
use tokio::{sync::broadcast, task::JoinHandle};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::AuthenticatorMixnetClient;
|
||||
|
||||
pub type SharedMixnetClient = Arc<tokio::sync::Mutex<Option<MixnetClient>>>;
|
||||
pub type MixnetMessageBroadcastSender = broadcast::Sender<Arc<ReconstructedMessage>>;
|
||||
pub type MixnetMessageBroadcastReceiver = broadcast::Receiver<Arc<ReconstructedMessage>>;
|
||||
|
||||
// The AuthClientsMixnetListener listens to mixnet messages and rebroadcasts them to the
|
||||
// AuthClients, or whoever else is interested.
|
||||
// While it is running, it has a lock on the shared mixnet client. This is the reason it's
|
||||
// designed to be able to start and stop, so that the lock can be released when it's not needed.
|
||||
//
|
||||
// NOTE: this is potentially bit wasteful. Ideally we should have proper channels where the
|
||||
// recipient only gets messages they're interested in.
|
||||
pub struct AuthClientMixnetListener {
|
||||
// The shared mixnet client that we're listening to
|
||||
mixnet_client: SharedMixnetClient,
|
||||
|
||||
// Broadcast channel for the messages that we re-broadcast to the AuthClients
|
||||
message_broadcast: MixnetMessageBroadcastSender,
|
||||
|
||||
// Listen to cancel from the outside world
|
||||
shutdown_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl AuthClientMixnetListener {
|
||||
pub fn new(mixnet_client: SharedMixnetClient, shutdown_token: CancellationToken) -> Self {
|
||||
let (message_broadcast, _) = broadcast::channel(100);
|
||||
Self {
|
||||
mixnet_client,
|
||||
message_broadcast,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> MixnetMessageBroadcastReceiver {
|
||||
self.message_broadcast.subscribe()
|
||||
}
|
||||
|
||||
async fn run(self) {
|
||||
let mut mixnet_client = self.mixnet_client.lock().await.take().unwrap();
|
||||
self.shutdown_token
|
||||
.run_until_cancelled(async {
|
||||
while let Some(event) = mixnet_client.next().await {
|
||||
if let Err(err) = self.message_broadcast.send(Arc::new(event)) {
|
||||
tracing::error!("Failed to broadcast mixnet message: {err}");
|
||||
}
|
||||
}
|
||||
tracing::error!("Mixnet client stream ended unexpectedly");
|
||||
})
|
||||
.await;
|
||||
self.mixnet_client.lock().await.replace(mixnet_client);
|
||||
}
|
||||
|
||||
pub fn start(self) -> AuthClientMixnetListenerHandle {
|
||||
let mixnet_client = self.mixnet_client.clone();
|
||||
let message_broadcast = self.message_broadcast.clone();
|
||||
let handle = tokio::spawn(self.run());
|
||||
|
||||
AuthClientMixnetListenerHandle {
|
||||
mixnet_client,
|
||||
message_broadcast,
|
||||
handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthClientMixnetListenerHandle {
|
||||
mixnet_client: SharedMixnetClient,
|
||||
message_broadcast: MixnetMessageBroadcastSender,
|
||||
handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl AuthClientMixnetListenerHandle {
|
||||
/// Returns new `AuthClient` or `None` if `MixnetClient` is already moved from shared reference.
|
||||
pub async fn new_auth_client(&self) -> Option<AuthenticatorMixnetClient> {
|
||||
let mixnet_client_guard = self.mixnet_client.lock().await;
|
||||
let mixnet_client_ref = mixnet_client_guard.as_ref()?;
|
||||
let mixnet_sender = mixnet_client_ref.split_sender();
|
||||
let nym_address = *mixnet_client_ref.nym_address();
|
||||
|
||||
Some(
|
||||
AuthenticatorMixnetClient::new(
|
||||
mixnet_sender,
|
||||
self.message_broadcast.subscribe(),
|
||||
nym_address,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> MixnetMessageBroadcastReceiver {
|
||||
self.message_broadcast.subscribe()
|
||||
}
|
||||
|
||||
pub async fn wait(self) {
|
||||
if let Err(err) = self.handle.await {
|
||||
tracing::error!("Error waiting for auth clients mixnet listener to stop: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "nym-ip-packet-client"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bytes.workspace = true
|
||||
futures.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-ip-packet-requests = { path = "../common/ip-packet-requests" }
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// To remove with the Registration Client PR
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use nym_sdk::mixnet::{
|
||||
InputMessage, MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient,
|
||||
TransmissionLane,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::{
|
||||
current::{
|
||||
request::IpPacketRequest,
|
||||
response::{
|
||||
ConnectResponse, ConnectResponseReply, ControlResponse, IpPacketResponse,
|
||||
IpPacketResponseData,
|
||||
},
|
||||
},
|
||||
error::{Error, Result},
|
||||
helpers::check_ipr_message_version,
|
||||
};
|
||||
|
||||
pub type SharedMixnetClient = Arc<tokio::sync::Mutex<Option<MixnetClient>>>;
|
||||
|
||||
const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
enum ConnectionState {
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
#[allow(unused)]
|
||||
Disconnecting,
|
||||
}
|
||||
|
||||
pub struct IprClientConnect {
|
||||
// During connection we need the mixnet client, but once connected we expect to setup a channel
|
||||
// from the main mixnet listener at the top-level.
|
||||
// As such, we drop the shared mixnet client once we're connected.
|
||||
mixnet_client: SharedMixnetClient,
|
||||
mixnet_sender: MixnetClientSender,
|
||||
connected: ConnectionState,
|
||||
cancel_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl IprClientConnect {
|
||||
pub async fn new(mixnet_client: SharedMixnetClient, cancel_token: CancellationToken) -> Self {
|
||||
let mixnet_sender = mixnet_client.lock().await.as_ref().unwrap().split_sender();
|
||||
Self {
|
||||
mixnet_client,
|
||||
mixnet_sender,
|
||||
connected: ConnectionState::Disconnected,
|
||||
cancel_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&mut self, ip_packet_router_address: Recipient) -> Result<IpPair> {
|
||||
if self.connected != ConnectionState::Disconnected {
|
||||
return Err(Error::AlreadyConnected);
|
||||
}
|
||||
|
||||
tracing::info!("Connecting to exit gateway");
|
||||
self.connected = ConnectionState::Connecting;
|
||||
match self.connect_inner(ip_packet_router_address).await {
|
||||
Ok(ips) => {
|
||||
debug!("Successfully connected to the ip-packet-router");
|
||||
self.connected = ConnectionState::Connected;
|
||||
Ok(ips)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to connect to the ip-packet-router: {:?}", err);
|
||||
self.connected = ConnectionState::Disconnected;
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_inner(&mut self, ip_packet_router_address: Recipient) -> Result<IpPair> {
|
||||
let request_id = self.send_connect_request(ip_packet_router_address).await?;
|
||||
|
||||
debug!("Waiting for reply...");
|
||||
self.listen_for_connect_response(request_id).await
|
||||
}
|
||||
|
||||
async fn send_connect_request(&self, ip_packet_router_address: Recipient) -> Result<u64> {
|
||||
let (request, request_id) = IpPacketRequest::new_connect_request(None);
|
||||
|
||||
// We use 20 surbs for the connect request because typically the IPR is configured to have
|
||||
// a min threshold of 10 surbs that it reserves for itself to request additional surbs.
|
||||
let surbs = 20;
|
||||
self.mixnet_sender
|
||||
.send(create_input_message(
|
||||
ip_packet_router_address,
|
||||
request,
|
||||
surbs,
|
||||
))
|
||||
.await
|
||||
.map_err(|err| Error::SdkError(Box::new(err)))?;
|
||||
|
||||
Ok(request_id)
|
||||
}
|
||||
|
||||
async fn handle_connect_response(&self, response: ConnectResponse) -> Result<IpPair> {
|
||||
debug!("Handling dynamic connect response");
|
||||
match response.reply {
|
||||
ConnectResponseReply::Success(r) => Ok(r.ips),
|
||||
ConnectResponseReply::Failure(reason) => Err(Error::ConnectRequestDenied { reason }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_ip_packet_router_response(&self, response: IpPacketResponse) -> Result<IpPair> {
|
||||
let control_response = match response.data {
|
||||
IpPacketResponseData::Control(control_response) => control_response,
|
||||
_ => {
|
||||
error!("Received non-control response while waiting for connect response");
|
||||
return Err(Error::UnexpectedConnectResponse);
|
||||
}
|
||||
};
|
||||
|
||||
match *control_response {
|
||||
ControlResponse::Connect(resp) => self.handle_connect_response(resp).await,
|
||||
response => {
|
||||
error!("Unexpected response: {response:?}");
|
||||
Err(Error::UnexpectedConnectResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_connect_response(&self, request_id: u64) -> Result<IpPair> {
|
||||
// Connecting is basically synchronous from the perspective of the mixnet client, so it's safe
|
||||
// to just grab ahold of the mutex and keep it until we get the response.
|
||||
let mut mixnet_client_handle = self.mixnet_client.lock().await;
|
||||
let mixnet_client = mixnet_client_handle.as_mut().unwrap();
|
||||
|
||||
let timeout = sleep(IPR_CONNECT_TIMEOUT);
|
||||
tokio::pin!(timeout);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel_token.cancelled() => {
|
||||
error!("Cancelled while waiting for reply to connect request");
|
||||
return Err(Error::Cancelled);
|
||||
},
|
||||
_ = &mut timeout => {
|
||||
error!("Timed out waiting for reply to connect request");
|
||||
return Err(Error::TimeoutWaitingForConnectResponse);
|
||||
},
|
||||
msgs = mixnet_client.wait_for_messages() => match msgs {
|
||||
None => {
|
||||
return Err(Error::NoMixnetMessagesReceived);
|
||||
}
|
||||
Some(msgs) => {
|
||||
for msg in msgs {
|
||||
// Confirm that the version is correct
|
||||
if let Err(err) = check_ipr_message_version(&msg) {
|
||||
tracing::info!("Mixnet message version mismatch: {err}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Then we deserialize the message
|
||||
tracing::debug!("IprClient: got message while waiting for connect response");
|
||||
let Ok(response) = IpPacketResponse::from_reconstructed_message(&msg) else {
|
||||
// This is ok, it's likely just one of our self-pings
|
||||
tracing::debug!("Failed to deserialize mixnet message");
|
||||
continue;
|
||||
};
|
||||
|
||||
if response.id() == Some(request_id) {
|
||||
tracing::debug!("Got response with matching id");
|
||||
return self.handle_ip_packet_router_response(response).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_input_message(
|
||||
recipient: Recipient,
|
||||
request: IpPacketRequest,
|
||||
surbs: u32,
|
||||
) -> InputMessage {
|
||||
InputMessage::new_anonymous(
|
||||
recipient,
|
||||
request.to_bytes().unwrap(),
|
||||
surbs,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::current::response::ConnectFailureReason;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("nym sdk")]
|
||||
SdkError(#[source] Box<nym_sdk::Error>),
|
||||
|
||||
#[error(
|
||||
"received response with version v{received}, the client is too new and can only understand v{expected}"
|
||||
)]
|
||||
ReceivedResponseWithOldVersion { expected: u8, received: u8 },
|
||||
|
||||
#[error(
|
||||
"received response with version v{received}, the client is too old and can only understand v{expected}"
|
||||
)]
|
||||
ReceivedResponseWithNewVersion { expected: u8, received: u8 },
|
||||
|
||||
#[error("got reply for connect request, but it appears intended for the wrong address?")]
|
||||
GotReplyIntendedForWrongAddress,
|
||||
|
||||
#[error("unexpected connect response")]
|
||||
UnexpectedConnectResponse,
|
||||
|
||||
#[error("mixnet client stopped returning responses")]
|
||||
NoMixnetMessagesReceived,
|
||||
|
||||
#[error("timeout waiting for connect response from exit gateway (ipr)")]
|
||||
TimeoutWaitingForConnectResponse,
|
||||
|
||||
#[error("connection cancelled")]
|
||||
Cancelled,
|
||||
|
||||
#[error("connect request denied: {reason}")]
|
||||
ConnectRequestDenied { reason: ConnectFailureReason },
|
||||
|
||||
#[error("failed to get version from message")]
|
||||
NoVersionInMessage,
|
||||
|
||||
#[error("already connected to the mixnet")]
|
||||
AlreadyConnected,
|
||||
|
||||
#[error("failed to create connect request")]
|
||||
FailedToCreateConnectRequest {
|
||||
source: nym_ip_packet_requests::sign::SignatureError,
|
||||
},
|
||||
}
|
||||
|
||||
// Result type based on our error type
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
|
||||
use crate::{current::VERSION as CURRENT_VERSION, error::Result, Error};
|
||||
|
||||
pub(crate) fn check_ipr_message_version(message: &ReconstructedMessage) -> Result<()> {
|
||||
// Assuming it's a IPR message, it will have a version as its first byte
|
||||
if let Some(version) = message.message.first() {
|
||||
match version.cmp(&CURRENT_VERSION) {
|
||||
Ordering::Greater => Err(Error::ReceivedResponseWithNewVersion {
|
||||
expected: CURRENT_VERSION,
|
||||
received: *version,
|
||||
}),
|
||||
Ordering::Less => Err(Error::ReceivedResponseWithOldVersion {
|
||||
expected: CURRENT_VERSION,
|
||||
received: *version,
|
||||
}),
|
||||
Ordering::Equal => {
|
||||
// We're good
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoVersionInMessage)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod connect;
|
||||
mod error;
|
||||
mod helpers;
|
||||
mod listener;
|
||||
|
||||
pub use connect::IprClientConnect;
|
||||
pub use error::Error;
|
||||
pub use listener::{IprListener, MixnetMessageOutcome};
|
||||
|
||||
// Re-export the currently used version
|
||||
pub use nym_ip_packet_requests::v8 as current;
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use nym_ip_packet_requests::{codec::MultiIpPacketCodec, v8::response::ControlResponse};
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
use tokio_util::codec::FramedRead;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::{
|
||||
current::{
|
||||
request::{ControlRequest, IpPacketRequest, IpPacketRequestData},
|
||||
response::{InfoLevel, IpPacketResponse, IpPacketResponseData},
|
||||
},
|
||||
helpers::check_ipr_message_version,
|
||||
};
|
||||
|
||||
pub enum MixnetMessageOutcome {
|
||||
IpPackets(Vec<Bytes>),
|
||||
MixnetSelfPing,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
pub struct IprListener {}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IprListenerError {
|
||||
#[error(transparent)]
|
||||
IprClientError(#[from] crate::Error),
|
||||
}
|
||||
|
||||
impl IprListener {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn is_mix_ping(&self, request: &IpPacketRequest) -> bool {
|
||||
match request.data {
|
||||
IpPacketRequestData::Control(ref control) => {
|
||||
matches!(**control, ControlRequest::Ping(_))
|
||||
}
|
||||
_ => {
|
||||
debug!("Received unexpected request: {request:?}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_reconstructed_message(
|
||||
&mut self,
|
||||
message: ReconstructedMessage,
|
||||
) -> Result<Option<MixnetMessageOutcome>, IprListenerError> {
|
||||
check_ipr_message_version(&message)?;
|
||||
|
||||
match IpPacketResponse::from_reconstructed_message(&message) {
|
||||
Ok(response) => {
|
||||
match response.data {
|
||||
IpPacketResponseData::Data(data_response) => {
|
||||
// Un-bundle the mixnet message and send the individual IP packets
|
||||
// to the tun device
|
||||
let framed_reader = FramedRead::new(
|
||||
data_response.ip_packet.as_ref(),
|
||||
MultiIpPacketCodec::new(),
|
||||
);
|
||||
let responses: Vec<Bytes> = framed_reader
|
||||
.filter_map(|res| async { res.ok().map(|packet| packet.into_bytes()) })
|
||||
.collect()
|
||||
.await;
|
||||
return Ok(Some(MixnetMessageOutcome::IpPackets(responses)));
|
||||
}
|
||||
IpPacketResponseData::Control(control_response) => match *control_response {
|
||||
ControlResponse::Connect(_) => {
|
||||
info!("Received connect response when already connected - ignoring");
|
||||
}
|
||||
ControlResponse::Disconnect(_) => {
|
||||
info!("Received disconnect response");
|
||||
return Ok(Some(MixnetMessageOutcome::Disconnect));
|
||||
}
|
||||
ControlResponse::UnrequestedDisconnect(_) => {
|
||||
info!("Received unrequested disconnect response, ignoring for now");
|
||||
}
|
||||
ControlResponse::Pong(_) => {
|
||||
info!("Received pong response, ignoring for now");
|
||||
}
|
||||
ControlResponse::Health(_) => {
|
||||
info!("Received health response, ignoring for now");
|
||||
}
|
||||
ControlResponse::Info(info) => {
|
||||
let msg =
|
||||
format!("Received info response from the mixnet: {}", info.reply);
|
||||
match info.level {
|
||||
InfoLevel::Info => info!("{msg}"),
|
||||
InfoLevel::Warn => warn!("{msg}"),
|
||||
InfoLevel::Error => error!("{msg}"),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// The exception to when we are not expecting a response, is when we
|
||||
// are sending a ping to ourselves.
|
||||
if let Ok(request) = IpPacketRequest::from_reconstructed_message(&message) {
|
||||
if self.is_mix_ping(&request) {
|
||||
return Ok(Some(MixnetMessageOutcome::MixnetSelfPing));
|
||||
}
|
||||
} else {
|
||||
warn!("Failed to deserialize reconstructed message: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IprListener {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "nym-wg-gateway-client"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nym-authenticator-client = { path = "../nym-authenticator-client" }
|
||||
nym-authenticator-requests = { path = "../common/authenticator-requests" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
nym-pemstore = { path = "../common/pemstore" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
rand.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
url.workspace = true
|
||||
@@ -0,0 +1,154 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_authenticator_client::{
|
||||
AuthenticatorClient, AuthenticatorResponse, AuthenticatorVersion, ClientMessage,
|
||||
QueryMessageImpl,
|
||||
};
|
||||
use nym_authenticator_requests::{v3, v4, v5};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::PeerPublicKey;
|
||||
use nym_sdk::mixnet::Recipient;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
const RETRY_PERIOD: Duration = Duration::from_secs(30);
|
||||
|
||||
impl crate::WgGatewayClient {
|
||||
pub fn light_client(&self) -> WgGatewayLightClient {
|
||||
WgGatewayLightClient {
|
||||
public_key: *self.keypair.public_key(),
|
||||
auth_client: self.auth_client.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WgGatewayLightClient {
|
||||
public_key: encryption::PublicKey,
|
||||
auth_client: AuthenticatorClient,
|
||||
}
|
||||
impl WgGatewayLightClient {
|
||||
pub fn auth_recipient(&self) -> Recipient {
|
||||
self.auth_client.auth_recipient()
|
||||
}
|
||||
|
||||
pub fn auth_client(&self) -> &AuthenticatorClient {
|
||||
&self.auth_client
|
||||
}
|
||||
|
||||
pub fn set_auth_client(&mut self, auth_client: AuthenticatorClient) {
|
||||
self.auth_client = auth_client;
|
||||
}
|
||||
pub async fn query_bandwidth(&mut self) -> Result<Option<i64>> {
|
||||
let query_message = match self.auth_client.auth_version() {
|
||||
AuthenticatorVersion::V2 => ClientMessage::Query(Box::new(QueryMessageImpl {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
version: AuthenticatorVersion::V2,
|
||||
})),
|
||||
AuthenticatorVersion::V3 => ClientMessage::Query(Box::new(QueryMessageImpl {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
version: AuthenticatorVersion::V3,
|
||||
})),
|
||||
AuthenticatorVersion::V4 => ClientMessage::Query(Box::new(QueryMessageImpl {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
version: AuthenticatorVersion::V4,
|
||||
})),
|
||||
AuthenticatorVersion::V5 => ClientMessage::Query(Box::new(QueryMessageImpl {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
version: AuthenticatorVersion::V5,
|
||||
})),
|
||||
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
|
||||
};
|
||||
let response = self.auth_client.send(&query_message).await?;
|
||||
|
||||
let available_bandwidth = match response {
|
||||
nym_authenticator_client::AuthenticatorResponse::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => {
|
||||
if let Some(available_bandwidth) =
|
||||
remaining_bandwidth_response.available_bandwidth()
|
||||
{
|
||||
available_bandwidth
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::InvalidGatewayAuthResponse),
|
||||
};
|
||||
|
||||
let remaining_pretty = if available_bandwidth > 1024 * 1024 {
|
||||
format!("{:.2} MB", available_bandwidth as f64 / 1024.0 / 1024.0)
|
||||
} else {
|
||||
format!("{} KB", available_bandwidth / 1024)
|
||||
};
|
||||
tracing::debug!(
|
||||
"Remaining wireguard bandwidth with gateway {} for today: {}",
|
||||
self.auth_client.auth_recipient().gateway(),
|
||||
remaining_pretty
|
||||
);
|
||||
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"
|
||||
);
|
||||
}
|
||||
Ok(Some(available_bandwidth))
|
||||
}
|
||||
async fn send(&mut self, msg: ClientMessage) -> Result<AuthenticatorResponse> {
|
||||
let now = std::time::Instant::now();
|
||||
while now.elapsed() < RETRY_PERIOD {
|
||||
match self.auth_client.send(&msg).await {
|
||||
Ok(response) => return Ok(response),
|
||||
Err(nym_authenticator_client::Error::TimeoutWaitingForConnectResponse) => continue,
|
||||
Err(source) => {
|
||||
if msg.is_wasteful() {
|
||||
return Err(Error::NoRetry { source });
|
||||
} else {
|
||||
return Err(Error::AuthenticatorClientError(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if msg.is_wasteful() {
|
||||
Err(Error::NoRetry {
|
||||
source: nym_authenticator_client::Error::TimeoutWaitingForConnectResponse,
|
||||
})
|
||||
} else {
|
||||
Err(Error::AuthenticatorClientError(
|
||||
nym_authenticator_client::Error::TimeoutWaitingForConnectResponse,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn top_up(&mut self, credential: CredentialSpendingData) -> Result<i64> {
|
||||
let top_up_message = match self.auth_client.auth_version() {
|
||||
AuthenticatorVersion::V3 => ClientMessage::TopUp(Box::new(v3::topup::TopUpMessage {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
credential,
|
||||
})),
|
||||
// NOTE: looks like a bug here using v3. But we're leaving it as is since it's working
|
||||
// and V4 is deprecated in favour of V5
|
||||
AuthenticatorVersion::V4 => ClientMessage::TopUp(Box::new(v4::topup::TopUpMessage {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
credential,
|
||||
})),
|
||||
AuthenticatorVersion::V5 => ClientMessage::TopUp(Box::new(v5::topup::TopUpMessage {
|
||||
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
|
||||
credential,
|
||||
})),
|
||||
AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
|
||||
return Err(Error::UnsupportedAuthenticatorVersion);
|
||||
}
|
||||
};
|
||||
let response = self.send(top_up_message).await?;
|
||||
|
||||
let remaining_bandwidth = match response {
|
||||
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
top_up_bandwidth_response.available_bandwidth()
|
||||
}
|
||||
_ => return Err(Error::InvalidGatewayAuthResponse),
|
||||
};
|
||||
|
||||
Ok(remaining_bandwidth)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_sdk::mixnet::NodeIdentity;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("received invalid response from gateway authenticator")]
|
||||
InvalidGatewayAuthResponse,
|
||||
|
||||
#[error("unknown authenticator version number")]
|
||||
UnsupportedAuthenticatorVersion,
|
||||
|
||||
#[error(transparent)]
|
||||
AuthenticatorClientError(#[from] nym_authenticator_client::Error),
|
||||
|
||||
#[error("error that should stop auto retrying")]
|
||||
NoRetry {
|
||||
#[source]
|
||||
source: nym_authenticator_client::Error,
|
||||
},
|
||||
|
||||
#[error("verification failure")]
|
||||
VerificationFailed(#[source] nym_authenticator_requests::Error),
|
||||
|
||||
#[error("failed to parse entry gateway socket addr")]
|
||||
FailedToParseEntryGatewaySocketAddr(#[source] std::net::AddrParseError),
|
||||
|
||||
#[error("failed to get {ticketbook_type} ticket")]
|
||||
GetTicket {
|
||||
ticketbook_type: TicketType,
|
||||
#[source]
|
||||
source: nym_bandwidth_controller::error::BandwidthControllerError,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ErrorMessage {
|
||||
#[error("out of bandwidth for gateway: {gateway_id}")]
|
||||
OutOfBandwidth { gateway_id: Box<NodeIdentity> },
|
||||
|
||||
#[error("gateway {gateway_id} is erroring out")]
|
||||
ErrorsFromGateway { gateway_id: Box<NodeIdentity> },
|
||||
}
|
||||
|
||||
// Result type based on our error type
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -0,0 +1,302 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod deprecated;
|
||||
mod error;
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
pub use error::{Error, ErrorMessage};
|
||||
use nym_authenticator_client::{
|
||||
AuthenticatorClient, AuthenticatorMixnetClient, AuthenticatorResponse, AuthenticatorVersion,
|
||||
ClientMessage,
|
||||
};
|
||||
use nym_authenticator_requests::{v2, v3, v4, v5};
|
||||
use nym_bandwidth_controller::PreparedCredential;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::{encryption, x25519::KeyPair, x25519::PublicKey};
|
||||
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::PeerPublicKey;
|
||||
use nym_pemstore::KeyPairPath;
|
||||
use nym_sdk::mixnet::{CredentialStorage, NodeIdentity, Recipient};
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
pub const DEFAULT_PRIVATE_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_private_entry_wireguard.pem";
|
||||
pub const DEFAULT_PUBLIC_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_public_entry_wireguard.pem";
|
||||
pub const DEFAULT_PRIVATE_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_private_exit_wireguard.pem";
|
||||
pub const DEFAULT_PUBLIC_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_public_exit_wireguard.pem";
|
||||
|
||||
pub const TICKETS_TO_SPEND: u32 = 1;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GatewayData {
|
||||
pub public_key: PublicKey,
|
||||
pub endpoint: SocketAddr,
|
||||
pub private_ipv4: Ipv4Addr,
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
pub struct WgGatewayClient {
|
||||
keypair: encryption::KeyPair,
|
||||
auth_client: AuthenticatorClient,
|
||||
}
|
||||
|
||||
impl WgGatewayClient {
|
||||
fn new_type(
|
||||
data_path: &Option<PathBuf>,
|
||||
auth_mix_client: AuthenticatorMixnetClient,
|
||||
auth_recipient: Recipient,
|
||||
auth_version: AuthenticatorVersion,
|
||||
private_file_name: &str,
|
||||
public_file_name: &str,
|
||||
) -> Self {
|
||||
let mut rng = OsRng;
|
||||
let auth_client = AuthenticatorClient::new(auth_mix_client, auth_recipient, auth_version);
|
||||
if let Some(data_path) = data_path {
|
||||
let paths = KeyPairPath::new(
|
||||
data_path.join(private_file_name),
|
||||
data_path.join(public_file_name),
|
||||
);
|
||||
let keypair = load_or_generate_keypair(&mut rng, paths);
|
||||
WgGatewayClient {
|
||||
keypair,
|
||||
auth_client,
|
||||
}
|
||||
} else {
|
||||
WgGatewayClient {
|
||||
keypair: KeyPair::new(&mut rng),
|
||||
auth_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_entry(
|
||||
data_path: &Option<PathBuf>,
|
||||
auth_mix_client: AuthenticatorMixnetClient,
|
||||
auth_recipient: Recipient,
|
||||
auth_version: AuthenticatorVersion,
|
||||
) -> Self {
|
||||
Self::new_type(
|
||||
data_path,
|
||||
auth_mix_client,
|
||||
auth_recipient,
|
||||
auth_version,
|
||||
DEFAULT_PRIVATE_ENTRY_WIREGUARD_KEY_FILENAME,
|
||||
DEFAULT_PUBLIC_ENTRY_WIREGUARD_KEY_FILENAME,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_exit(
|
||||
data_path: &Option<PathBuf>,
|
||||
auth_mix_client: AuthenticatorMixnetClient,
|
||||
auth_recipient: Recipient,
|
||||
auth_version: AuthenticatorVersion,
|
||||
) -> Self {
|
||||
Self::new_type(
|
||||
data_path,
|
||||
auth_mix_client,
|
||||
auth_recipient,
|
||||
auth_version,
|
||||
DEFAULT_PRIVATE_EXIT_WIREGUARD_KEY_FILENAME,
|
||||
DEFAULT_PUBLIC_EXIT_WIREGUARD_KEY_FILENAME,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn keypair(&self) -> &encryption::KeyPair {
|
||||
&self.keypair
|
||||
}
|
||||
|
||||
pub fn auth_recipient(&self) -> Recipient {
|
||||
self.auth_client.auth_recipient()
|
||||
}
|
||||
|
||||
pub fn auth_version(&self) -> AuthenticatorVersion {
|
||||
self.auth_client.auth_version()
|
||||
}
|
||||
|
||||
pub async fn request_bandwidth<St: CredentialStorage>(
|
||||
gateway_id: NodeIdentity,
|
||||
controller: &nym_bandwidth_controller::BandwidthController<QueryHttpRpcNyxdClient, St>,
|
||||
ticketbook_type: TicketType,
|
||||
) -> Result<PreparedCredential>
|
||||
where
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let credential = controller
|
||||
.prepare_ecash_ticket(ticketbook_type, gateway_id.to_bytes(), TICKETS_TO_SPEND)
|
||||
.await
|
||||
.map_err(|source| Error::GetTicket {
|
||||
ticketbook_type,
|
||||
source,
|
||||
})?;
|
||||
Ok(credential)
|
||||
}
|
||||
|
||||
pub async fn register_wireguard<St: CredentialStorage>(
|
||||
&mut self,
|
||||
gateway_host: IpAddr,
|
||||
controller: &nym_bandwidth_controller::BandwidthController<QueryHttpRpcNyxdClient, St>,
|
||||
ticketbook_type: TicketType,
|
||||
) -> Result<GatewayData>
|
||||
where
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
debug!("Registering with the wg gateway...");
|
||||
let init_message = match self.auth_version() {
|
||||
AuthenticatorVersion::V2 => {
|
||||
ClientMessage::Initial(Box::new(v2::registration::InitMessage {
|
||||
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V3 => {
|
||||
ClientMessage::Initial(Box::new(v3::registration::InitMessage {
|
||||
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V4 => {
|
||||
ClientMessage::Initial(Box::new(v4::registration::InitMessage {
|
||||
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
ClientMessage::Initial(Box::new(v5::registration::InitMessage {
|
||||
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
|
||||
};
|
||||
trace!("sending init msg to {}: {:?}", &gateway_host, &init_message);
|
||||
let response = self.auth_client.send(&init_message).await?;
|
||||
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));
|
||||
}
|
||||
|
||||
trace!(
|
||||
"received \"pending-registration\" msg from {}: {:?}",
|
||||
&gateway_host,
|
||||
&pending_registration_response
|
||||
);
|
||||
|
||||
let credential = Some(
|
||||
Self::request_bandwidth(
|
||||
self.auth_recipient().gateway(),
|
||||
controller,
|
||||
ticketbook_type,
|
||||
)
|
||||
.await?
|
||||
.data,
|
||||
);
|
||||
|
||||
let finalized_message = match self.auth_version() {
|
||||
AuthenticatorVersion::V2 => {
|
||||
ClientMessage::Final(Box::new(v2::registration::FinalMessage {
|
||||
gateway_client: v2::registration::GatewayClient::new(
|
||||
self.keypair.private_key(),
|
||||
pending_registration_response.pub_key().inner(),
|
||||
pending_registration_response.private_ips().ipv4.into(),
|
||||
pending_registration_response.nonce(),
|
||||
),
|
||||
credential,
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V3 => {
|
||||
ClientMessage::Final(Box::new(v3::registration::FinalMessage {
|
||||
gateway_client: v3::registration::GatewayClient::new(
|
||||
self.keypair.private_key(),
|
||||
pending_registration_response.pub_key().inner(),
|
||||
pending_registration_response.private_ips().ipv4.into(),
|
||||
pending_registration_response.nonce(),
|
||||
),
|
||||
credential,
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V4 => {
|
||||
ClientMessage::Final(Box::new(v4::registration::FinalMessage {
|
||||
gateway_client: v4::registration::GatewayClient::new(
|
||||
self.keypair.private_key(),
|
||||
pending_registration_response.pub_key().inner(),
|
||||
pending_registration_response.private_ips().into(),
|
||||
pending_registration_response.nonce(),
|
||||
),
|
||||
credential,
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
ClientMessage::Final(Box::new(v5::registration::FinalMessage {
|
||||
gateway_client: v5::registration::GatewayClient::new(
|
||||
self.keypair.private_key(),
|
||||
pending_registration_response.pub_key().inner(),
|
||||
pending_registration_response.private_ips(),
|
||||
pending_registration_response.nonce(),
|
||||
),
|
||||
credential,
|
||||
}))
|
||||
}
|
||||
AuthenticatorVersion::UNKNOWN => {
|
||||
return Err(Error::UnsupportedAuthenticatorVersion);
|
||||
}
|
||||
};
|
||||
trace!(
|
||||
"sending final msg to {}: {:?}",
|
||||
&gateway_host,
|
||||
&finalized_message
|
||||
);
|
||||
|
||||
let response = self.auth_client.send(&finalized_message).await?;
|
||||
let AuthenticatorResponse::Registered(registered_response) = response else {
|
||||
return Err(Error::InvalidGatewayAuthResponse);
|
||||
};
|
||||
registered_response
|
||||
}
|
||||
AuthenticatorResponse::Registered(registered_response) => registered_response,
|
||||
_ => return Err(Error::InvalidGatewayAuthResponse),
|
||||
};
|
||||
|
||||
trace!(
|
||||
"received \"registered\" msg from {}: {:?}",
|
||||
&gateway_host,
|
||||
®istered_data
|
||||
);
|
||||
|
||||
let gateway_data = GatewayData {
|
||||
public_key: registered_data.pub_key().inner().into(),
|
||||
endpoint: SocketAddr::from_str(&format!(
|
||||
"{}:{}",
|
||||
gateway_host,
|
||||
registered_data.wg_port()
|
||||
))
|
||||
.map_err(Error::FailedToParseEntryGatewaySocketAddr)?,
|
||||
private_ipv4: registered_data.private_ips().ipv4,
|
||||
private_ipv6: registered_data.private_ips().ipv6,
|
||||
};
|
||||
|
||||
Ok(gateway_data)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_or_generate_keypair<R: RngCore + CryptoRng>(rng: &mut R, paths: KeyPairPath) -> KeyPair {
|
||||
match nym_pemstore::load_keypair(&paths) {
|
||||
Ok(keypair) => keypair,
|
||||
Err(_) => {
|
||||
let keypair = KeyPair::new(rng);
|
||||
if let Err(e) = nym_pemstore::store_keypair(&keypair, &paths) {
|
||||
error!(
|
||||
"could not store generated keypair at {:?} - {:?}; will use ephemeral keys",
|
||||
paths, e
|
||||
);
|
||||
}
|
||||
keypair
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user