Skip to content
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

Feature/reg over mix #633

Merged
merged 22 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 89 additions & 61 deletions nym-vpn-core/Cargo.lock

Large diffs are not rendered by default.

36 changes: 19 additions & 17 deletions nym-vpn-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"crates/nym-authenticator-client",
"crates/nym-connection-monitor",
"crates/nym-gateway-directory",
"crates/nym-gateway-probe",
Expand Down Expand Up @@ -91,20 +92,21 @@ uniffi = { version = "0.27.3", features = ["cli"] }
url = "2.5"
vergen = { version = "8.3.1", default-features = false }

nym-bandwidth-controller = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-client-core = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-config = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-credential-storage = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-credentials = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-crypto = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-explorer-client = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-http-api-client = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-id = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-ip-packet-requests = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-node-requests = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-task = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-topology = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-wireguard-types = { git = "https://github.com/nymtech/nym", rev = "70599b9" }
nym-authenticator-requests = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-bandwidth-controller = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-client-core = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-config = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-credential-storage = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-credentials = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-crypto = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-explorer-client = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-http-api-client = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-id = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-ip-packet-requests = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-node-requests = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-task = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-topology = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
nym-wireguard-types = { git = "https://github.com/nymtech/nym", rev = "1f144690da9692ad61c60b754f0333dabeaf29d8" }
19 changes: 19 additions & 0 deletions nym-vpn-core/crates/nym-authenticator-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[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

[dependencies]
nym-authenticator-requests.workspace = true
nym-sdk.workspace = true
nym-wireguard-types.workspace = true
tracing.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
futures.workspace = true
26 changes: 26 additions & 0 deletions nym-vpn-core/crates/nym-authenticator-client/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("got reply for connect request, but it appears intended for the wrong address?")]
GotReplyIntendedForWrongAddress,

#[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(transparent)]
SdkError(#[from] nym_sdk::Error),

#[error("timeout waiting for connect response from exit gateway (authenticator)")]
TimeoutWaitingForConnectResponse,
}

// Result type based on our error type
pub type Result<T> = std::result::Result<T, Error>;
186 changes: 186 additions & 0 deletions nym-vpn-core/crates/nym-authenticator-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use std::{cmp::Ordering, sync::Arc, time::Duration};

use nym_authenticator_requests::v1::{
request::AuthenticatorRequest, response::AuthenticatorResponse,
};
use nym_sdk::mixnet::{
MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient, ReconstructedMessage,
TransmissionLane,
};
use nym_wireguard_types::ClientMessage;
use tracing::{debug, error};

mod error;

pub use crate::error::{Error, Result};

#[derive(Clone)]
pub struct SharedMixnetClient(Arc<tokio::sync::Mutex<Option<MixnetClient>>>);

impl SharedMixnetClient {
pub fn new(mixnet_client: MixnetClient) -> Self {
Self(Arc::new(tokio::sync::Mutex::new(Some(mixnet_client))))
}

pub async fn lock(&self) -> tokio::sync::MutexGuard<'_, Option<MixnetClient>> {
self.0.lock().await
}

pub async fn nym_address(&self) -> Recipient {
*self.lock().await.as_ref().unwrap().nym_address()
}

pub async fn send(&self, msg: nym_sdk::mixnet::InputMessage) -> Result<()> {
self.lock().await.as_mut().unwrap().send(msg).await?;
Ok(())
}

pub fn inner(&self) -> Arc<tokio::sync::Mutex<Option<MixnetClient>>> {
self.0.clone()
}
}

pub struct AuthClient {
mixnet_client: SharedMixnetClient,
mixnet_sender: MixnetClientSender,
nym_address: Recipient,
}

impl AuthClient {
pub async fn new(mixnet_client: SharedMixnetClient) -> Self {
let mixnet_sender = mixnet_client.lock().await.as_ref().unwrap().split_sender();
let nym_address = *mixnet_client
.inner()
.lock()
.await
.as_ref()
.unwrap()
.nym_address();
Self {
mixnet_client,
mixnet_sender,
nym_address,
}
}

// A workaround until we can extract SharedMixnetClient to a common crate
pub async fn new_from_inner(
mixnet_client: Arc<tokio::sync::Mutex<Option<MixnetClient>>>,
) -> Self {
let mixnet_client = SharedMixnetClient(mixnet_client);
Self::new(mixnet_client).await
}

pub async fn send(
&mut self,
message: ClientMessage,
authenticator_address: Recipient,
) -> Result<AuthenticatorResponse> {
self.send_inner(message, authenticator_address).await
}

async fn send_inner(
&mut self,
message: ClientMessage,
authenticator_address: Recipient,
) -> Result<AuthenticatorResponse> {
let request_id = self
.send_connect_request(message, authenticator_address)
.await?;

debug!("Waiting for reply...");
self.listen_for_connect_response(request_id).await
}

async fn send_connect_request(
&self,
message: ClientMessage,
authenticator_address: Recipient,
) -> Result<u64> {
let (request, request_id) = match message {
ClientMessage::Initial(init_message) => {
AuthenticatorRequest::new_initial_request(init_message, self.nym_address)
}
ClientMessage::Final(gateway_client) => {
AuthenticatorRequest::new_final_request(gateway_client, self.nym_address)
}
};
debug!("Sent connect request with version v{}", request.version);

self.mixnet_sender
.send(nym_sdk::mixnet::InputMessage::new_regular(
authenticator_address,
request.to_bytes().unwrap(),
TransmissionLane::General,
None,
))
.await?;

Ok(request_id)
}

async fn listen_for_connect_response(&self, request_id: u64) -> Result<AuthenticatorResponse> {
// 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 = tokio::time::sleep(Duration::from_secs(5));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to make this configurable? Something to keep in mind for the future

tokio::pin!(timeout);

loop {
tokio::select! {
_ = &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
check_auth_message_version(&msg)?;

// Then we deserialize the message
debug!("AuthClient: got message while waiting for connect response");
let Ok(response) = AuthenticatorResponse::from_reconstructed_message(&msg) else {
// This is ok, it's likely just one of our self-pings
debug!("Failed to deserialize reconstructed message");
continue;
};

if response.id() == Some(request_id) {
debug!("Got response with matching id");
return Ok(response);
}
}
}
}
}
}
}
}

fn check_auth_message_version(message: &ReconstructedMessage) -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have this a proper struct (the whole message), but we can leave it for a future iteration

// Assuing it's an Authenticator message, it will have a version as its first byte
if let Some(version) = message.message.first() {
match version.cmp(&nym_authenticator_requests::CURRENT_VERSION) {
Ordering::Greater => Err(Error::ReceivedResponseWithNewVersion {
expected: nym_authenticator_requests::CURRENT_VERSION,
received: *version,
}),
Ordering::Less => Err(Error::ReceivedResponseWithOldVersion {
expected: nym_authenticator_requests::CURRENT_VERSION,
received: *version,
}),
Ordering::Equal => {
// We're good
Ok(())
}
}
} else {
Err(Error::NoVersionInMessage)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only

use std::{fmt::Display, str::FromStr};

use crate::{error::Result, DescribedGatewayWithLocation, Error};
use nym_sdk::mixnet::Recipient;

// optional, until we remove the wireguard feature flag
#[derive(Debug, Copy, Clone)]
pub struct AuthAddress(pub Option<Recipient>);

#[derive(Debug, Copy, Clone)]
pub struct AuthAddresses {
entry_addr: AuthAddress,
exit_addr: AuthAddress,
}

impl AuthAddresses {
pub fn new(entry_addr: AuthAddress, exit_addr: AuthAddress) -> Self {
AuthAddresses {
entry_addr,
exit_addr,
}
}

pub fn entry(&self) -> AuthAddress {
self.entry_addr
}

pub fn exit(&self) -> AuthAddress {
self.exit_addr
}
}

impl Display for AuthAddresses {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"entry: {:?} exit: {:?}",
self.entry_addr.0, self.exit_addr.0
)
}
}

pub fn extract_authenticator(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, love it

gateways: &[DescribedGatewayWithLocation],
identity: String,
) -> Result<AuthAddress> {
let auth_addr = gateways
.iter()
.find(|gw| *gw.gateway.bond.identity() == identity)
.ok_or(Error::RequestedGatewayIdNotFound(identity.clone()))?
.gateway
.self_described
.clone()
.ok_or(Error::NoGatewayDescriptionAvailable(identity))?
.authenticator
.map(|auth| Recipient::from_str(&auth.address))
.transpose()
.map_err(|_| Error::RecipientFormattingError)?;
Ok(AuthAddress(auth_addr))
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only

use std::fmt::{Display, Formatter};
use std::{
fmt::{Display, Formatter},
str::FromStr,
};

use crate::{
entries::described_gateway::{by_location_described, by_random_described},
Expand Down Expand Up @@ -145,3 +148,25 @@ impl LookupGateway for ExitPoint {
}
}
}

pub fn extract_router_address(
gateways: &[DescribedGatewayWithLocation],
identity_key: String,
) -> Result<IpPacketRouterAddress> {
Ok(IpPacketRouterAddress(
Recipient::from_str(
&gateways
.iter()
.find(|gw| *gw.gateway.bond.identity() == identity_key)
.ok_or(Error::NoMatchingGateway)?
.gateway
.self_described
.clone()
.ok_or(Error::NoGatewayDescriptionAvailable(identity_key))?
.ip_packet_router
.ok_or(Error::MissingIpPacketRouterAddress)?
.address,
)
.map_err(|_| Error::RecipientFormattingError)?,
))
}
3 changes: 2 additions & 1 deletion nym-vpn-core/crates/nym-gateway-directory/src/entries/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod auth_addresses;
pub(crate) mod described_gateway;
pub(crate) mod entry_point;
pub(crate) mod exit_point;
pub(crate) mod ipr_address;
pub(crate) mod ipr_addresses;
3 changes: 3 additions & 0 deletions nym-vpn-core/crates/nym-gateway-directory/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ pub enum Error {
#[error("tunnel in a tunnel works for gateway id or location")]
InvalidExitPointDescription,

#[error("gateway {0} doesn't have a description available")]
NoGatewayDescriptionAvailable(String),

#[error("the only available exit gateway is the entry gateway")]
OnlyAvailableExitGatewayIsTheEntryGateway {
requested_location: String,
Expand Down
Loading
Loading