From 26ef2299b94c562f64c648af65671c5d7548283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 23 Apr 2024 22:11:43 +0200 Subject: [PATCH 1/8] Add entry to proto definition --- proto/nym/vpn.proto | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/proto/nym/vpn.proto b/proto/nym/vpn.proto index 6bbdefc92e..90cba59369 100644 --- a/proto/nym/vpn.proto +++ b/proto/nym/vpn.proto @@ -2,27 +2,27 @@ syntax = "proto3"; package nym.vpn; -// message Empty {} +message Empty {} -// message Gateway { -// string id = 1; -// Location location = 2; -// } -// -// message Location { -// string two_letter_country_code = 1; -// } -// -// message Node { -// oneof node { -// Location location = 1; -// Gateway gateway = 2; -// Empty fastest = 3; -// } -// } +message Gateway { + string id = 1; + // Location location = 2; +} + +message Location { + string two_letter_iso_country_code = 1; +} + +message Node { + oneof node { + Gateway gateway = 1; + Location location = 2; + Empty low_latency = 3; + } +} message ConnectRequest { - // Node entry = 1; + Node entry = 1; // Node exit = 2; } From 4a325a5e6a1ef0b4e5ccf90500ab3f6386b156e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 23 Apr 2024 22:48:27 +0200 Subject: [PATCH 2/8] Pass entry from client all the way to service --- nym-vpnc/src/main.rs | 15 +++++++++- .../command_interface/connection_handler.rs | 9 +++--- nym-vpnd/src/command_interface/listener.rs | 28 ++++++++++++++++++- nym-vpnd/src/service/mod.rs | 4 +-- nym-vpnd/src/service/vpn_service.rs | 19 ++++++++++--- proto/nym/vpn.proto | 2 +- 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index f436d688e8..d0027913dd 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -119,9 +119,22 @@ async fn get_client(args: &CliArgs) -> anyhow::Result nym_vpn_proto::Node { + nym_vpn_proto::Node { + node_enum: Some(nym_vpn_proto::node::NodeEnum::Location( + nym_vpn_proto::Location { + two_letter_iso_country_code: country_code.to_string(), + }, + )), + } +} + async fn connect(args: &CliArgs) -> anyhow::Result<()> { let mut client = get_client(args).await?; - let request = tonic::Request::new(ConnectRequest {}); + let country_code = "DE"; + let request = tonic::Request::new(ConnectRequest { + entry: Some(new_entry_node_location(country_code)), + }); let response = client.vpn_connect(request).await?.into_inner(); println!("{:?}", response); Ok(()) diff --git a/nym-vpnd/src/command_interface/connection_handler.rs b/nym-vpnd/src/command_interface/connection_handler.rs index 0d3b1443f2..a9b64b0cb3 100644 --- a/nym-vpnd/src/command_interface/connection_handler.rs +++ b/nym-vpnd/src/command_interface/connection_handler.rs @@ -5,8 +5,8 @@ use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tracing::{info, warn}; use crate::service::{ - ConnectArgs, VpnServiceCommand, VpnServiceConnectResult, VpnServiceDisconnectResult, - VpnServiceImportUserCredentialResult, VpnServiceStatusResult, + ConnectArgs, CustomConnectArgs, VpnServiceCommand, VpnServiceConnectResult, + VpnServiceDisconnectResult, VpnServiceImportUserCredentialResult, VpnServiceStatusResult, }; pub(super) struct CommandInterfaceConnectionHandler { @@ -18,10 +18,11 @@ impl CommandInterfaceConnectionHandler { Self { vpn_command_tx } } - pub(crate) async fn handle_connect(&self) -> VpnServiceConnectResult { + pub(crate) async fn handle_connect(&self, entry: String) -> VpnServiceConnectResult { info!("Starting VPN"); let (tx, rx) = oneshot::channel(); - let connect_args = ConnectArgs::Default; + // let connect_args = ConnectArgs::Default; + let connect_args = ConnectArgs::Custom(CustomConnectArgs { entry }); self.vpn_command_tx .send(VpnServiceCommand::Connect(tx, connect_args)) .unwrap(); diff --git a/nym-vpnd/src/command_interface/listener.rs b/nym-vpnd/src/command_interface/listener.rs index ea784c60b5..538b2e6634 100644 --- a/nym-vpnd/src/command_interface/listener.rs +++ b/nym-vpnd/src/command_interface/listener.rs @@ -82,8 +82,34 @@ impl NymVpnd for CommandInterface { ) -> Result, tonic::Status> { info!("Got connect request: {:?}", request); + let entry = if let Some(entry) = request.into_inner().entry { + if let Some(node_enum) = entry.node_enum { + match node_enum { + nym_vpn_proto::node::NodeEnum::Location(location) => { + info!( + "Connecting to entry node in country: {:?}", + location.two_letter_iso_country_code + ); + location.two_letter_iso_country_code.to_string() + } + nym_vpn_proto::node::NodeEnum::Gateway(gateway) => { + info!("Connecting to entry node with gateway id: {:?}", gateway.id); + todo!() + } + nym_vpn_proto::node::NodeEnum::LowLatency(_) => { + info!("Connecting to low latency entry node"); + todo!() + } + } + } else { + "random".to_string() + } + } else { + "random".to_string() + }; + let status = CommandInterfaceConnectionHandler::new(self.vpn_command_tx.clone()) - .handle_connect() + .handle_connect(entry) .await; info!("Returning connect response"); diff --git a/nym-vpnd/src/service/mod.rs b/nym-vpnd/src/service/mod.rs index 84c03aada4..2ba3cfeba5 100644 --- a/nym-vpnd/src/service/mod.rs +++ b/nym-vpnd/src/service/mod.rs @@ -9,6 +9,6 @@ mod vpn_service; pub(crate) use start::start_vpn_service; pub(crate) use vpn_service::{ - ConnectArgs, VpnServiceCommand, VpnServiceConnectResult, VpnServiceDisconnectResult, - VpnServiceImportUserCredentialResult, VpnServiceStatusResult, + ConnectArgs, CustomConnectArgs, VpnServiceCommand, VpnServiceConnectResult, + VpnServiceDisconnectResult, VpnServiceImportUserCredentialResult, VpnServiceStatusResult, }; diff --git a/nym-vpnd/src/service/vpn_service.rs b/nym-vpnd/src/service/vpn_service.rs index 9addc137c7..459e333ea5 100644 --- a/nym-vpnd/src/service/vpn_service.rs +++ b/nym-vpnd/src/service/vpn_service.rs @@ -44,9 +44,13 @@ pub enum VpnServiceCommand { pub enum ConnectArgs { // Read the entry and exit points from the config file. Default, + Custom(CustomConnectArgs), +} - #[allow(unused)] - Custom(String, String), +#[derive(Debug)] +pub struct CustomConnectArgs { + pub entry: String, + // pub exit: String, } #[derive(Debug)] @@ -132,11 +136,18 @@ impl NymVpnService { Ok(config) } - async fn handle_connect(&mut self, _connect_args: ConnectArgs) -> VpnServiceConnectResult { + async fn handle_connect(&mut self, connect_args: ConnectArgs) -> VpnServiceConnectResult { self.set_shared_state(VpnState::Connecting); - // TODO: use connect_args here + let entry = match connect_args { + ConnectArgs::Default => None, + ConnectArgs::Custom(CustomConnectArgs { entry }) => { + info!("Using custom entry point: {}", entry); + Some(entry) + } + }; + // TODO: pass in location to the config file let config = match self.try_setup_config() { Ok(config) => config, Err(err) => { diff --git a/proto/nym/vpn.proto b/proto/nym/vpn.proto index 90cba59369..c89d1bea7d 100644 --- a/proto/nym/vpn.proto +++ b/proto/nym/vpn.proto @@ -14,7 +14,7 @@ message Location { } message Node { - oneof node { + oneof node_enum { Gateway gateway = 1; Location location = 2; Empty low_latency = 3; From 899945a7e2f5cb8235ce29f53a17512623caccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 23 Apr 2024 23:36:56 +0200 Subject: [PATCH 3/8] Pass more connect data --- .../src/entries/entry_point.rs | 1 - nym-vpnc/src/main.rs | 7 +- .../command_interface/connection_handler.rs | 14 +-- nym-vpnd/src/command_interface/listener.rs | 86 ++++++++++++++++--- nym-vpnd/src/service/mod.rs | 4 +- nym-vpnd/src/service/vpn_service.rs | 45 +++++----- proto/nym/vpn.proto | 27 ++++-- 7 files changed, 132 insertions(+), 52 deletions(-) diff --git a/crates/nym-gateway-directory/src/entries/entry_point.rs b/crates/nym-gateway-directory/src/entries/entry_point.rs index f29dbf7cda..3bee19dc8a 100644 --- a/crates/nym-gateway-directory/src/entries/entry_point.rs +++ b/crates/nym-gateway-directory/src/entries/entry_point.rs @@ -19,7 +19,6 @@ pub enum EntryPoint { // An explicit entry gateway identity. Gateway { identity: NodeIdentity }, // Select a random entry gateway in a specific location. - // NOTE: Consider using a crate with strongly typed country codes instead of strings Location { location: String }, // Select a random entry gateway but increasey probability of selecting a low latency gateway // as determined by ping times. diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index d0027913dd..4cbfb088ce 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -119,9 +119,9 @@ async fn get_client(args: &CliArgs) -> anyhow::Result nym_vpn_proto::Node { - nym_vpn_proto::Node { - node_enum: Some(nym_vpn_proto::node::NodeEnum::Location( +fn new_entry_node_location(country_code: &str) -> nym_vpn_proto::EntryNode { + nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( nym_vpn_proto::Location { two_letter_iso_country_code: country_code.to_string(), }, @@ -134,6 +134,7 @@ async fn connect(args: &CliArgs) -> anyhow::Result<()> { let country_code = "DE"; let request = tonic::Request::new(ConnectRequest { entry: Some(new_entry_node_location(country_code)), + exit: None, }); let response = client.vpn_connect(request).await?.into_inner(); println!("{:?}", response); diff --git a/nym-vpnd/src/command_interface/connection_handler.rs b/nym-vpnd/src/command_interface/connection_handler.rs index a9b64b0cb3..c4bf3e38cf 100644 --- a/nym-vpnd/src/command_interface/connection_handler.rs +++ b/nym-vpnd/src/command_interface/connection_handler.rs @@ -1,12 +1,13 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_vpn_lib::gateway_directory::{EntryPoint, ExitPoint}; use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tracing::{info, warn}; use crate::service::{ - ConnectArgs, CustomConnectArgs, VpnServiceCommand, VpnServiceConnectResult, - VpnServiceDisconnectResult, VpnServiceImportUserCredentialResult, VpnServiceStatusResult, + ConnectArgs, VpnServiceCommand, VpnServiceConnectResult, VpnServiceDisconnectResult, + VpnServiceImportUserCredentialResult, VpnServiceStatusResult, }; pub(super) struct CommandInterfaceConnectionHandler { @@ -18,11 +19,14 @@ impl CommandInterfaceConnectionHandler { Self { vpn_command_tx } } - pub(crate) async fn handle_connect(&self, entry: String) -> VpnServiceConnectResult { + pub(crate) async fn handle_connect( + &self, + entry: Option, + exit: Option, + ) -> VpnServiceConnectResult { info!("Starting VPN"); let (tx, rx) = oneshot::channel(); - // let connect_args = ConnectArgs::Default; - let connect_args = ConnectArgs::Custom(CustomConnectArgs { entry }); + let connect_args = ConnectArgs { entry, exit }; self.vpn_command_tx .send(VpnServiceCommand::Connect(tx, connect_args)) .unwrap(); diff --git a/nym-vpnd/src/command_interface/listener.rs b/nym-vpnd/src/command_interface/listener.rs index 538b2e6634..7b1c0775ec 100644 --- a/nym-vpnd/src/command_interface/listener.rs +++ b/nym-vpnd/src/command_interface/listener.rs @@ -7,6 +7,10 @@ use std::{ path::{Path, PathBuf}, }; +use nym_vpn_lib::{ + gateway_directory::{EntryPoint, ExitPoint}, + NodeIdentity, Recipient, +}; use nym_vpn_proto::{ nym_vpnd_server::NymVpnd, ConnectRequest, ConnectResponse, ConnectionStatus, DisconnectRequest, DisconnectResponse, Error as ProtoError, ImportUserCredentialRequest, @@ -82,34 +86,92 @@ impl NymVpnd for CommandInterface { ) -> Result, tonic::Status> { info!("Got connect request: {:?}", request); - let entry = if let Some(entry) = request.into_inner().entry { - if let Some(node_enum) = entry.node_enum { - match node_enum { - nym_vpn_proto::node::NodeEnum::Location(location) => { + let connect_request = request.into_inner(); + + let entry = if let Some(entry) = connect_request.entry { + if let Some(entry_node_enum) = entry.entry_node_enum { + match entry_node_enum { + nym_vpn_proto::entry_node::EntryNodeEnum::Location(location) => { info!( "Connecting to entry node in country: {:?}", location.two_letter_iso_country_code ); - location.two_letter_iso_country_code.to_string() + Some(EntryPoint::Location { + location: location.two_letter_iso_country_code.to_string(), + }) } - nym_vpn_proto::node::NodeEnum::Gateway(gateway) => { + nym_vpn_proto::entry_node::EntryNodeEnum::Gateway(gateway) => { info!("Connecting to entry node with gateway id: {:?}", gateway.id); - todo!() + let identity = + NodeIdentity::from_base58_string(&gateway.id).map_err(|err| { + error!("Failed to parse gateway id: {:?}", err); + tonic::Status::invalid_argument("Invalid gateway id") + })?; + Some(EntryPoint::Gateway { identity }) } - nym_vpn_proto::node::NodeEnum::LowLatency(_) => { + nym_vpn_proto::entry_node::EntryNodeEnum::RandomLowLatency(_) => { info!("Connecting to low latency entry node"); - todo!() + Some(EntryPoint::RandomLowLatency) + } + nym_vpn_proto::entry_node::EntryNodeEnum::Random(_) => { + info!("Connecting to random entry node"); + Some(EntryPoint::Random) + } + } + } else { + None + } + } else { + None + }; + + let exit = if let Some(exit) = connect_request.exit { + if let Some(exit_node_enum) = exit.exit_node_enum { + match exit_node_enum { + nym_vpn_proto::exit_node::ExitNodeEnum::Address(address) => { + info!( + "Connecting to exit node at address: {:?}", + address.nym_address + ); + let address = Recipient::try_from_base58_string(address.nym_address) + .map_err(|err| { + error!("Failed to parse exit node address: {:?}", err); + tonic::Status::invalid_argument("Invalid exit node address") + })?; + Some(ExitPoint::Address { address }) + } + nym_vpn_proto::exit_node::ExitNodeEnum::Gateway(gateway) => { + info!("Connecting to exit node with gateway id: {:?}", gateway.id); + let identity = + NodeIdentity::from_base58_string(&gateway.id).map_err(|err| { + error!("Failed to parse gateway id: {:?}", err); + tonic::Status::invalid_argument("Invalid gateway id") + })?; + Some(ExitPoint::Gateway { identity }) + } + nym_vpn_proto::exit_node::ExitNodeEnum::Location(location) => { + info!( + "Connecting to exit node in country: {:?}", + location.two_letter_iso_country_code + ); + Some(ExitPoint::Location { + location: location.two_letter_iso_country_code.to_string(), + }) + } + nym_vpn_proto::exit_node::ExitNodeEnum::Random(_) => { + info!("Connecting to low latency exit node"); + Some(ExitPoint::Random) } } } else { - "random".to_string() + None } } else { - "random".to_string() + None }; let status = CommandInterfaceConnectionHandler::new(self.vpn_command_tx.clone()) - .handle_connect(entry) + .handle_connect(entry, exit) .await; info!("Returning connect response"); diff --git a/nym-vpnd/src/service/mod.rs b/nym-vpnd/src/service/mod.rs index 2ba3cfeba5..84c03aada4 100644 --- a/nym-vpnd/src/service/mod.rs +++ b/nym-vpnd/src/service/mod.rs @@ -9,6 +9,6 @@ mod vpn_service; pub(crate) use start::start_vpn_service; pub(crate) use vpn_service::{ - ConnectArgs, CustomConnectArgs, VpnServiceCommand, VpnServiceConnectResult, - VpnServiceDisconnectResult, VpnServiceImportUserCredentialResult, VpnServiceStatusResult, + ConnectArgs, VpnServiceCommand, VpnServiceConnectResult, VpnServiceDisconnectResult, + VpnServiceImportUserCredentialResult, VpnServiceStatusResult, }; diff --git a/nym-vpnd/src/service/vpn_service.rs b/nym-vpnd/src/service/vpn_service.rs index 459e333ea5..f566540e1c 100644 --- a/nym-vpnd/src/service/vpn_service.rs +++ b/nym-vpnd/src/service/vpn_service.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use futures::channel::mpsc::UnboundedSender; use futures::SinkExt; use nym_vpn_lib::credentials::import_credential; -use nym_vpn_lib::gateway_directory::{self}; +use nym_vpn_lib::gateway_directory::{self, EntryPoint}; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::oneshot; use tracing::info; @@ -41,16 +41,9 @@ pub enum VpnServiceCommand { } #[derive(Debug)] -pub enum ConnectArgs { - // Read the entry and exit points from the config file. - Default, - Custom(CustomConnectArgs), -} - -#[derive(Debug)] -pub struct CustomConnectArgs { - pub entry: String, - // pub exit: String, +pub struct ConnectArgs { + pub entry: Option, + pub exit: Option, } #[derive(Debug)] @@ -126,12 +119,23 @@ impl NymVpnService { } } - fn try_setup_config(&self) -> std::result::Result { + fn try_setup_config( + &self, + entry: Option, + exit: Option, + ) -> std::result::Result { // If the config file does not exit, create it let config = if self.config_file.exists() { - read_config_file(&self.config_file)? + let mut read_config = read_config_file(&self.config_file)?; + read_config.entry_point = entry.unwrap_or(read_config.entry_point); + read_config.exit_point = exit.unwrap_or(read_config.exit_point); + read_config } else { - create_config_file(&self.config_file, NymVpnServiceConfig::default())? + let config = NymVpnServiceConfig { + entry_point: entry.unwrap_or(EntryPoint::Random), + ..Default::default() + }; + create_config_file(&self.config_file, config)? }; Ok(config) } @@ -139,16 +143,11 @@ impl NymVpnService { async fn handle_connect(&mut self, connect_args: ConnectArgs) -> VpnServiceConnectResult { self.set_shared_state(VpnState::Connecting); - let entry = match connect_args { - ConnectArgs::Default => None, - ConnectArgs::Custom(CustomConnectArgs { entry }) => { - info!("Using custom entry point: {}", entry); - Some(entry) - } - }; + let ConnectArgs { entry, exit } = connect_args; + info!("Using entry point: {:?}", entry); + info!("Using exit point: {:?}", exit); - // TODO: pass in location to the config file - let config = match self.try_setup_config() { + let config = match self.try_setup_config(entry, exit) { Ok(config) => config, Err(err) => { self.set_shared_state(VpnState::NotConnected); diff --git a/proto/nym/vpn.proto b/proto/nym/vpn.proto index c89d1bea7d..9249cfc4c1 100644 --- a/proto/nym/vpn.proto +++ b/proto/nym/vpn.proto @@ -4,26 +4,41 @@ package nym.vpn; message Empty {} +// Represents the identity of a gateway message Gateway { string id = 1; - // Location location = 2; +} + +// Represents a nym-address of the form id.enc@gateway +message Address { + string nym_address = 1; } message Location { string two_letter_iso_country_code = 1; } -message Node { - oneof node_enum { +message EntryNode { + oneof entry_node_enum { Gateway gateway = 1; Location location = 2; - Empty low_latency = 3; + Empty random_low_latency = 3; + Empty random = 4; + } +} + +message ExitNode { + oneof exit_node_enum { + Address address = 1; + Gateway gateway = 2; + Location location = 3; + Empty random = 4; } } message ConnectRequest { - Node entry = 1; - // Node exit = 2; + EntryNode entry = 1; + ExitNode exit = 2; } message ConnectResponse { From eb3705dd0e1d4f8432b1e361db83f15fa010c7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 24 Apr 2024 06:10:41 +0200 Subject: [PATCH 4/8] Handle all connect arguments --- Cargo.lock | 1 + nym-vpn-cli/src/commands.rs | 3 +- nym-vpnc/Cargo.toml | 1 + nym-vpnc/src/main.rs | 195 ++++++++++++++++++++++++++-- nym-vpnd/src/service/config.rs | 13 ++ nym-vpnd/src/service/vpn_service.rs | 6 +- 6 files changed, 208 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c076c822d5..d9a33b2693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4704,6 +4704,7 @@ dependencies = [ "anyhow", "bs58 0.5.1", "clap", + "nym-gateway-directory", "nym-vpn-proto", "parity-tokio-ipc", "prost 0.12.4", diff --git a/nym-vpn-cli/src/commands.rs b/nym-vpn-cli/src/commands.rs index 8a90a6ca22..f3cd6c373f 100644 --- a/nym-vpn-cli/src/commands.rs +++ b/nym-vpn-cli/src/commands.rs @@ -129,10 +129,11 @@ pub(crate) struct CliExit { #[clap(long, alias = "exit-address")] pub(crate) exit_router_address: Option, + /// Mixnet public ID of the exit gateway. #[clap(long, alias = "exit-id")] pub(crate) exit_gateway_id: Option, - /// Mixnet recipient address. + /// Auto-select exit gateway by country ISO. #[clap(long, alias = "exit-country")] pub(crate) exit_gateway_country: Option, } diff --git a/nym-vpnc/Cargo.toml b/nym-vpnc/Cargo.toml index ec332c597f..e64d4e2e8c 100644 --- a/nym-vpnc/Cargo.toml +++ b/nym-vpnc/Cargo.toml @@ -20,6 +20,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"]} tonic.workspace = true tower.workspace = true +nym-gateway-directory = { path = "../crates/nym-gateway-directory" } nym-vpn-proto = { path = "../crates/nym-vpn-proto" } [build-dependencies] diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index 4cbfb088ce..5d0658f4b0 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -3,8 +3,9 @@ use std::path::{Path, PathBuf}; -use anyhow::Context; +use anyhow::{anyhow, Context}; use clap::{Args, Parser, Subcommand}; +use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; use nym_vpn_proto::{ nym_vpnd_client::NymVpndClient, ConnectRequest, DisconnectRequest, ImportUserCredentialRequest, StatusRequest, @@ -25,12 +26,84 @@ struct CliArgs { #[derive(Subcommand)] enum Command { - Connect, + Connect(ConnectArgs), Disconnect, Status, ImportCredential(ImportCredentialArgs), } +#[derive(Args)] +pub(crate) struct ConnectArgs { + #[command(flatten)] + pub(crate) entry: CliEntry, + + #[command(flatten)] + pub(crate) exit: CliExit, + + /// Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN + /// device will be created, but to route traffic through it you will need to do it manually, + /// e.g. ping -Itun0. + #[arg(long)] + pub(crate) disable_routing: bool, + + /// Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to + /// exit gateway. + #[arg(long)] + pub(crate) enable_two_hop: bool, + + /// Enable Poisson process rate limiting of outbound traffic. + #[arg(long)] + pub(crate) enable_poisson_rate: bool, + + /// Disable constant rate background loop cover traffic. + #[arg(long)] + pub(crate) disable_background_cover_traffic: bool, + + /// Enable credentials mode. + #[arg(long)] + pub(crate) enable_credentials_mode: bool, +} + +#[derive(Args)] +#[group(multiple = false)] +pub(crate) struct CliEntry { + /// Mixnet public ID of the entry gateway. + #[clap(long, alias = "entry-id")] + pub(crate) entry_gateway_id: Option, + + /// Auto-select entry gateway by country ISO. + #[clap(long, alias = "entry-country")] + pub(crate) entry_gateway_country: Option, + + /// Auto-select entry gateway by latency + #[clap(long, alias = "entry-fastest")] + pub(crate) entry_gateway_low_latency: bool, + + /// Auto-select entry gateway randomly. + #[clap(long, alias = "entry-random")] + pub(crate) entry_gateway_random: bool, +} + +#[derive(Args)] +#[group(multiple = false)] +pub(crate) struct CliExit { + /// Mixnet recipient address. + #[clap(long, alias = "exit-address")] + pub(crate) exit_router_address: Option, + + /// Mixnet public ID of the exit gateway. + #[clap(long, alias = "exit-id")] + pub(crate) exit_gateway_id: Option, + + /// Auto-select exit gateway by country ISO. + #[clap(long, alias = "exit-country")] + pub(crate) exit_gateway_country: Option, + + /// Auto-select exit gateway randomly. + #[clap(long, alias = "exit-random")] + pub(crate) exit_gateway_random: bool, +} + #[derive(Args)] pub(crate) struct ImportCredentialArgs { #[command(flatten)] @@ -53,6 +126,47 @@ pub(crate) struct ImportCredentialType { pub(crate) credential_path: Option, } +fn parse_entry_point(args: &ConnectArgs) -> anyhow::Result> { + if let Some(ref entry_gateway_id) = args.entry.entry_gateway_id { + Ok(Some(EntryPoint::Gateway { + identity: NodeIdentity::from_base58_string(entry_gateway_id.clone()) + .map_err(|_| anyhow!("Failed to parse gateway id"))?, + })) + } else if let Some(ref entry_gateway_country) = args.entry.entry_gateway_country { + Ok(Some(EntryPoint::Location { + location: entry_gateway_country.clone(), + })) + } else if args.entry.entry_gateway_low_latency { + Ok(Some(EntryPoint::RandomLowLatency)) + } else if args.entry.entry_gateway_random { + Ok(Some(EntryPoint::Random)) + } else { + Ok(None) + } +} + +fn parse_exit_point(args: &ConnectArgs) -> anyhow::Result> { + if let Some(ref exit_router_address) = args.exit.exit_router_address { + Ok(Some(ExitPoint::Address { + address: Recipient::try_from_base58_string(exit_router_address.clone()) + .map_err(|_| anyhow!("Failed to parse exit node address"))?, + })) + } else if let Some(ref exit_router_id) = args.exit.exit_gateway_id { + Ok(Some(ExitPoint::Gateway { + identity: NodeIdentity::from_base58_string(exit_router_id.clone()) + .map_err(|_| anyhow!("Failed to parse gateway id"))?, + })) + } else if let Some(ref exit_gateway_country) = args.exit.exit_gateway_country { + Ok(Some(ExitPoint::Location { + location: exit_gateway_country.clone(), + })) + } else if args.exit.exit_gateway_random { + Ok(Some(ExitPoint::Random)) + } else { + Ok(None) + } +} + fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { bs58::decode(raw).into_vec() } @@ -77,7 +191,7 @@ impl From for ImportCredentialTypeEnum { async fn main() -> anyhow::Result<()> { let args = CliArgs::parse(); match args.command { - Command::Connect => connect(&args).await?, + Command::Connect(ref connect_args) => connect(&args, connect_args).await?, Command::Disconnect => disconnect(&args).await?, Command::Status => status(&args).await?, Command::ImportCredential(ref import_args) => import_credential(&args, import_args).await?, @@ -119,6 +233,7 @@ async fn get_client(args: &CliArgs) -> anyhow::Result nym_vpn_proto::EntryNode { nym_vpn_proto::EntryNode { entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( @@ -129,13 +244,77 @@ fn new_entry_node_location(country_code: &str) -> nym_vpn_proto::EntryNode { } } -async fn connect(args: &CliArgs) -> anyhow::Result<()> { - let mut client = get_client(args).await?; - let country_code = "DE"; +fn into_entry_point(entry: EntryPoint) -> nym_vpn_proto::EntryNode { + match entry { + EntryPoint::Gateway { identity } => nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Gateway( + nym_vpn_proto::Gateway { + id: identity.to_base58_string(), + }, + )), + }, + EntryPoint::Location { location } => nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( + nym_vpn_proto::Location { + two_letter_iso_country_code: location, + }, + )), + }, + EntryPoint::RandomLowLatency => nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::RandomLowLatency( + nym_vpn_proto::Empty {}, + )), + }, + EntryPoint::Random => nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Random( + nym_vpn_proto::Empty {}, + )), + }, + } +} + +fn into_exit_point(exit: ExitPoint) -> nym_vpn_proto::ExitNode { + match exit { + ExitPoint::Address { address } => nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Address( + nym_vpn_proto::Address { + nym_address: address.to_string(), + }, + )), + }, + ExitPoint::Gateway { identity } => nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Gateway( + nym_vpn_proto::Gateway { + id: identity.to_base58_string(), + }, + )), + }, + ExitPoint::Location { location } => nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Location( + nym_vpn_proto::Location { + two_letter_iso_country_code: location, + }, + )), + }, + ExitPoint::Random => nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Random( + nym_vpn_proto::Empty {}, + )), + }, + } +} + +async fn connect(args: &CliArgs, connect_args: &ConnectArgs) -> anyhow::Result<()> { + // Setup connect arguments + let entry = parse_entry_point(connect_args)?; + let exit = parse_exit_point(connect_args)?; + let request = tonic::Request::new(ConnectRequest { - entry: Some(new_entry_node_location(country_code)), - exit: None, + entry: entry.map(into_entry_point), + exit: exit.map(into_exit_point), }); + + let mut client = get_client(args).await?; let response = client.vpn_connect(request).await?.into_inner(); println!("{:?}", response); Ok(()) diff --git a/nym-vpnd/src/service/config.rs b/nym-vpnd/src/service/config.rs index 79d217e2a3..27c15f8a29 100644 --- a/nym-vpnd/src/service/config.rs +++ b/nym-vpnd/src/service/config.rs @@ -91,6 +91,19 @@ pub(super) fn read_config_file( }) } +pub(super) fn write_config_file( + config_file: &PathBuf, + config: &NymVpnServiceConfig, +) -> Result<(), ConfigSetupError> { + let config_str = toml::to_string(config).unwrap(); + fs::write(config_file, config_str).map_err(|error| ConfigSetupError::WriteFile { + file: config_file.clone(), + error, + })?; + info!("Config file updated at {:?}", config_file); + Ok(()) +} + pub(super) fn create_data_dir(data_dir: &PathBuf) -> Result<(), ConfigSetupError> { fs::create_dir_all(data_dir).map_err(|error| ConfigSetupError::CreateDirectory { dir: data_dir.clone(), diff --git a/nym-vpnd/src/service/vpn_service.rs b/nym-vpnd/src/service/vpn_service.rs index f566540e1c..702094f103 100644 --- a/nym-vpnd/src/service/vpn_service.rs +++ b/nym-vpnd/src/service/vpn_service.rs @@ -13,8 +13,8 @@ use tokio::sync::oneshot; use tracing::info; use super::config::{ - create_config_file, create_data_dir, read_config_file, ConfigSetupError, NymVpnServiceConfig, - DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE, DEFAULT_DATA_DIR, + create_config_file, create_data_dir, read_config_file, write_config_file, ConfigSetupError, + NymVpnServiceConfig, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE, DEFAULT_DATA_DIR, }; use super::exit_listener::VpnServiceExitListener; use super::status_listener::VpnServiceStatusListener; @@ -29,6 +29,7 @@ pub enum VpnState { ConnectionFailed(String), } +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum VpnServiceCommand { Connect(oneshot::Sender, ConnectArgs), @@ -129,6 +130,7 @@ impl NymVpnService { let mut read_config = read_config_file(&self.config_file)?; read_config.entry_point = entry.unwrap_or(read_config.entry_point); read_config.exit_point = exit.unwrap_or(read_config.exit_point); + write_config_file(&self.config_file, &read_config)?; read_config } else { let config = NymVpnServiceConfig { From ae213ac80a332db7fd1af50c248847c1957341e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 24 Apr 2024 06:17:25 +0200 Subject: [PATCH 5/8] Extract out into cli.rs --- nym-vpnc/src/cli.rs | 134 ++++++++++++++++++++++++++++++++++++++ nym-vpnc/src/main.rs | 150 ++++--------------------------------------- 2 files changed, 147 insertions(+), 137 deletions(-) create mode 100644 nym-vpnc/src/cli.rs diff --git a/nym-vpnc/src/cli.rs b/nym-vpnc/src/cli.rs new file mode 100644 index 0000000000..357b791800 --- /dev/null +++ b/nym-vpnc/src/cli.rs @@ -0,0 +1,134 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Args, Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Parser)] +#[clap(author = "Nymtech", version, about)] +pub(crate) struct CliArgs { + /// Use HTTP instead of socket file for IPC with the daemon. + #[arg(long)] + pub(crate) http: bool, + + #[command(subcommand)] + pub(crate) command: Command, +} + +#[derive(Subcommand)] +pub(crate) enum Command { + Connect(ConnectArgs), + Disconnect, + Status, + ImportCredential(ImportCredentialArgs), +} + +#[derive(Args)] +pub(crate) struct ConnectArgs { + #[command(flatten)] + pub(crate) entry: CliEntry, + + #[command(flatten)] + pub(crate) exit: CliExit, + + /// Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN + /// device will be created, but to route traffic through it you will need to do it manually, + /// e.g. ping -Itun0. + #[arg(long)] + pub(crate) disable_routing: bool, + + /// Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to + /// exit gateway. + #[arg(long)] + pub(crate) enable_two_hop: bool, + + /// Enable Poisson process rate limiting of outbound traffic. + #[arg(long)] + pub(crate) enable_poisson_rate: bool, + + /// Disable constant rate background loop cover traffic. + #[arg(long)] + pub(crate) disable_background_cover_traffic: bool, + + /// Enable credentials mode. + #[arg(long)] + pub(crate) enable_credentials_mode: bool, +} + +#[derive(Args)] +#[group(multiple = false)] +pub(crate) struct CliEntry { + /// Mixnet public ID of the entry gateway. + #[clap(long, alias = "entry-id")] + pub(crate) entry_gateway_id: Option, + + /// Auto-select entry gateway by country ISO. + #[clap(long, alias = "entry-country")] + pub(crate) entry_gateway_country: Option, + + /// Auto-select entry gateway by latency + #[clap(long, alias = "entry-fastest")] + pub(crate) entry_gateway_low_latency: bool, + + /// Auto-select entry gateway randomly. + #[clap(long, alias = "entry-random")] + pub(crate) entry_gateway_random: bool, +} + +#[derive(Args)] +#[group(multiple = false)] +pub(crate) struct CliExit { + /// Mixnet recipient address. + #[clap(long, alias = "exit-address")] + pub(crate) exit_router_address: Option, + + /// Mixnet public ID of the exit gateway. + #[clap(long, alias = "exit-id")] + pub(crate) exit_gateway_id: Option, + + /// Auto-select exit gateway by country ISO. + #[clap(long, alias = "exit-country")] + pub(crate) exit_gateway_country: Option, + + /// Auto-select exit gateway randomly. + #[clap(long, alias = "exit-random")] + pub(crate) exit_gateway_random: bool, +} + +#[derive(Args)] +pub(crate) struct ImportCredentialArgs { + #[command(flatten)] + pub(crate) credential_type: ImportCredentialType, + + // currently hidden as there exists only a single serialization standard + #[arg(long, hide = true)] + pub(crate) version: Option, +} + +#[derive(Args, Clone)] +#[group(required = true, multiple = false)] +pub(crate) struct ImportCredentialType { + /// Credential encoded using base58. + #[arg(long)] + pub(crate) credential_data: Option, + + /// Path to the credential file. + #[arg(long)] + pub(crate) credential_path: Option, +} + +// Workaround until clap supports enums for ArgGroups +pub(crate) enum ImportCredentialTypeEnum { + Path(PathBuf), + Data(String), +} + +impl From for ImportCredentialTypeEnum { + fn from(ict: ImportCredentialType) -> Self { + match (ict.credential_data, ict.credential_path) { + (Some(data), None) => ImportCredentialTypeEnum::Data(data), + (None, Some(path)) => ImportCredentialTypeEnum::Path(path), + _ => unreachable!(), + } + } +} diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index 5d0658f4b0..a02c679941 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -4,7 +4,8 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context}; -use clap::{Args, Parser, Subcommand}; +use clap::Parser; +use cli::Command; use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; use nym_vpn_proto::{ nym_vpnd_client::NymVpndClient, ConnectRequest, DisconnectRequest, ImportUserCredentialRequest, @@ -13,120 +14,11 @@ use nym_vpn_proto::{ use parity_tokio_ipc::Endpoint as IpcEndpoint; use tonic::transport::{Channel as TonicChannel, Endpoint as TonicEndpoint}; -#[derive(Parser)] -#[clap(author = "Nymtech", version, about)] -struct CliArgs { - /// Use HTTP instead of socket file for IPC with the daemon. - #[arg(long)] - http: bool, +use crate::cli::ImportCredentialTypeEnum; - #[command(subcommand)] - command: Command, -} - -#[derive(Subcommand)] -enum Command { - Connect(ConnectArgs), - Disconnect, - Status, - ImportCredential(ImportCredentialArgs), -} - -#[derive(Args)] -pub(crate) struct ConnectArgs { - #[command(flatten)] - pub(crate) entry: CliEntry, - - #[command(flatten)] - pub(crate) exit: CliExit, - - /// Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN - /// device will be created, but to route traffic through it you will need to do it manually, - /// e.g. ping -Itun0. - #[arg(long)] - pub(crate) disable_routing: bool, - - /// Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to - /// exit gateway. - #[arg(long)] - pub(crate) enable_two_hop: bool, - - /// Enable Poisson process rate limiting of outbound traffic. - #[arg(long)] - pub(crate) enable_poisson_rate: bool, - - /// Disable constant rate background loop cover traffic. - #[arg(long)] - pub(crate) disable_background_cover_traffic: bool, - - /// Enable credentials mode. - #[arg(long)] - pub(crate) enable_credentials_mode: bool, -} - -#[derive(Args)] -#[group(multiple = false)] -pub(crate) struct CliEntry { - /// Mixnet public ID of the entry gateway. - #[clap(long, alias = "entry-id")] - pub(crate) entry_gateway_id: Option, - - /// Auto-select entry gateway by country ISO. - #[clap(long, alias = "entry-country")] - pub(crate) entry_gateway_country: Option, - - /// Auto-select entry gateway by latency - #[clap(long, alias = "entry-fastest")] - pub(crate) entry_gateway_low_latency: bool, - - /// Auto-select entry gateway randomly. - #[clap(long, alias = "entry-random")] - pub(crate) entry_gateway_random: bool, -} +mod cli; -#[derive(Args)] -#[group(multiple = false)] -pub(crate) struct CliExit { - /// Mixnet recipient address. - #[clap(long, alias = "exit-address")] - pub(crate) exit_router_address: Option, - - /// Mixnet public ID of the exit gateway. - #[clap(long, alias = "exit-id")] - pub(crate) exit_gateway_id: Option, - - /// Auto-select exit gateway by country ISO. - #[clap(long, alias = "exit-country")] - pub(crate) exit_gateway_country: Option, - - /// Auto-select exit gateway randomly. - #[clap(long, alias = "exit-random")] - pub(crate) exit_gateway_random: bool, -} - -#[derive(Args)] -pub(crate) struct ImportCredentialArgs { - #[command(flatten)] - pub(crate) credential_type: ImportCredentialType, - - // currently hidden as there exists only a single serialization standard - #[arg(long, hide = true)] - pub(crate) version: Option, -} - -#[derive(Args, Clone)] -#[group(required = true, multiple = false)] -pub(crate) struct ImportCredentialType { - /// Credential encoded using base58. - #[arg(long)] - pub(crate) credential_data: Option, - - /// Path to the credential file. - #[arg(long)] - pub(crate) credential_path: Option, -} - -fn parse_entry_point(args: &ConnectArgs) -> anyhow::Result> { +fn parse_entry_point(args: &cli::ConnectArgs) -> anyhow::Result> { if let Some(ref entry_gateway_id) = args.entry.entry_gateway_id { Ok(Some(EntryPoint::Gateway { identity: NodeIdentity::from_base58_string(entry_gateway_id.clone()) @@ -145,7 +37,7 @@ fn parse_entry_point(args: &ConnectArgs) -> anyhow::Result> { } } -fn parse_exit_point(args: &ConnectArgs) -> anyhow::Result> { +fn parse_exit_point(args: &cli::ConnectArgs) -> anyhow::Result> { if let Some(ref exit_router_address) = args.exit.exit_router_address { Ok(Some(ExitPoint::Address { address: Recipient::try_from_base58_string(exit_router_address.clone()) @@ -171,25 +63,9 @@ fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { bs58::decode(raw).into_vec() } -// Workaround until clap supports enums for ArgGroups -pub(crate) enum ImportCredentialTypeEnum { - Path(PathBuf), - Data(String), -} - -impl From for ImportCredentialTypeEnum { - fn from(ict: ImportCredentialType) -> Self { - match (ict.credential_data, ict.credential_path) { - (Some(data), None) => ImportCredentialTypeEnum::Data(data), - (None, Some(path)) => ImportCredentialTypeEnum::Path(path), - _ => unreachable!(), - } - } -} - #[tokio::main] async fn main() -> anyhow::Result<()> { - let args = CliArgs::parse(); + let args = cli::CliArgs::parse(); match args.command { Command::Connect(ref connect_args) => connect(&args, connect_args).await?, Command::Disconnect => disconnect(&args).await?, @@ -216,7 +92,7 @@ fn default_endpoint() -> String { "http://[::1]:53181".to_string() } -async fn get_client(args: &CliArgs) -> anyhow::Result> { +async fn get_client(args: &cli::CliArgs) -> anyhow::Result> { if args.http { let endpoint = default_endpoint(); let client = NymVpndClient::connect(endpoint.clone()) @@ -304,7 +180,7 @@ fn into_exit_point(exit: ExitPoint) -> nym_vpn_proto::ExitNode { } } -async fn connect(args: &CliArgs, connect_args: &ConnectArgs) -> anyhow::Result<()> { +async fn connect(args: &cli::CliArgs, connect_args: &cli::ConnectArgs) -> anyhow::Result<()> { // Setup connect arguments let entry = parse_entry_point(connect_args)?; let exit = parse_exit_point(connect_args)?; @@ -320,7 +196,7 @@ async fn connect(args: &CliArgs, connect_args: &ConnectArgs) -> anyhow::Result<( Ok(()) } -async fn disconnect(args: &CliArgs) -> anyhow::Result<()> { +async fn disconnect(args: &cli::CliArgs) -> anyhow::Result<()> { let mut client = get_client(args).await?; let request = tonic::Request::new(DisconnectRequest {}); let response = client.vpn_disconnect(request).await?.into_inner(); @@ -328,7 +204,7 @@ async fn disconnect(args: &CliArgs) -> anyhow::Result<()> { Ok(()) } -async fn status(args: &CliArgs) -> anyhow::Result<()> { +async fn status(args: &cli::CliArgs) -> anyhow::Result<()> { let mut client = get_client(args).await?; let request = tonic::Request::new(StatusRequest {}); let response = client.vpn_status(request).await?.into_inner(); @@ -337,8 +213,8 @@ async fn status(args: &CliArgs) -> anyhow::Result<()> { } async fn import_credential( - args: &CliArgs, - import_args: &ImportCredentialArgs, + args: &cli::CliArgs, + import_args: &cli::ImportCredentialArgs, ) -> anyhow::Result<()> { let import_type: ImportCredentialTypeEnum = import_args.credential_type.clone().into(); let raw_credential = match import_type { From 23032bce645a0d030c7550e3fcda83b98342f772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 24 Apr 2024 06:30:20 +0200 Subject: [PATCH 6/8] Extract out config.rs and protob_conversion.rs --- nym-vpnc/src/config.rs | 8 +++ nym-vpnc/src/main.rs | 112 ++++++------------------------ nym-vpnc/src/protob_conversion.rs | 96 +++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 nym-vpnc/src/config.rs create mode 100644 nym-vpnc/src/protob_conversion.rs diff --git a/nym-vpnc/src/config.rs b/nym-vpnc/src/config.rs new file mode 100644 index 0000000000..c7471b2cec --- /dev/null +++ b/nym-vpnc/src/config.rs @@ -0,0 +1,8 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::path::{Path, PathBuf}; + +pub(crate) fn get_socket_path() -> PathBuf { + Path::new("/var/run/nym-vpn.sock").to_path_buf() +} diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index a02c679941..edd5eb0766 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -1,11 +1,10 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::{anyhow, Context}; use clap::Parser; -use cli::Command; use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; use nym_vpn_proto::{ nym_vpnd_client::NymVpndClient, ConnectRequest, DisconnectRequest, ImportUserCredentialRequest, @@ -14,9 +13,26 @@ use nym_vpn_proto::{ use parity_tokio_ipc::Endpoint as IpcEndpoint; use tonic::transport::{Channel as TonicChannel, Endpoint as TonicEndpoint}; -use crate::cli::ImportCredentialTypeEnum; +use crate::{ + cli::{Command, ImportCredentialTypeEnum}, + protob_conversion::{into_entry_point, into_exit_point}, +}; mod cli; +mod config; +mod protob_conversion; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = cli::CliArgs::parse(); + match args.command { + Command::Connect(ref connect_args) => connect(&args, connect_args).await?, + Command::Disconnect => disconnect(&args).await?, + Command::Status => status(&args).await?, + Command::ImportCredential(ref import_args) => import_credential(&args, import_args).await?, + } + Ok(()) +} fn parse_entry_point(args: &cli::ConnectArgs) -> anyhow::Result> { if let Some(ref entry_gateway_id) = args.entry.entry_gateway_id { @@ -63,22 +79,6 @@ fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { bs58::decode(raw).into_vec() } -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let args = cli::CliArgs::parse(); - match args.command { - Command::Connect(ref connect_args) => connect(&args, connect_args).await?, - Command::Disconnect => disconnect(&args).await?, - Command::Status => status(&args).await?, - Command::ImportCredential(ref import_args) => import_credential(&args, import_args).await?, - } - Ok(()) -} - -fn get_socket_path() -> PathBuf { - Path::new("/var/run/nym-vpn.sock").to_path_buf() -} - async fn get_channel(socket_path: PathBuf) -> anyhow::Result { // NOTE: the uri here is ignored Ok(TonicEndpoint::from_static("http://[::1]:53181") @@ -100,7 +100,7 @@ async fn get_client(args: &cli::CliArgs) -> anyhow::Result anyhow::Result nym_vpn_proto::EntryNode { - nym_vpn_proto::EntryNode { - entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( - nym_vpn_proto::Location { - two_letter_iso_country_code: country_code.to_string(), - }, - )), - } -} - -fn into_entry_point(entry: EntryPoint) -> nym_vpn_proto::EntryNode { - match entry { - EntryPoint::Gateway { identity } => nym_vpn_proto::EntryNode { - entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Gateway( - nym_vpn_proto::Gateway { - id: identity.to_base58_string(), - }, - )), - }, - EntryPoint::Location { location } => nym_vpn_proto::EntryNode { - entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( - nym_vpn_proto::Location { - two_letter_iso_country_code: location, - }, - )), - }, - EntryPoint::RandomLowLatency => nym_vpn_proto::EntryNode { - entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::RandomLowLatency( - nym_vpn_proto::Empty {}, - )), - }, - EntryPoint::Random => nym_vpn_proto::EntryNode { - entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Random( - nym_vpn_proto::Empty {}, - )), - }, - } -} - -fn into_exit_point(exit: ExitPoint) -> nym_vpn_proto::ExitNode { - match exit { - ExitPoint::Address { address } => nym_vpn_proto::ExitNode { - exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Address( - nym_vpn_proto::Address { - nym_address: address.to_string(), - }, - )), - }, - ExitPoint::Gateway { identity } => nym_vpn_proto::ExitNode { - exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Gateway( - nym_vpn_proto::Gateway { - id: identity.to_base58_string(), - }, - )), - }, - ExitPoint::Location { location } => nym_vpn_proto::ExitNode { - exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Location( - nym_vpn_proto::Location { - two_letter_iso_country_code: location, - }, - )), - }, - ExitPoint::Random => nym_vpn_proto::ExitNode { - exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Random( - nym_vpn_proto::Empty {}, - )), - }, - } -} - async fn connect(args: &cli::CliArgs, connect_args: &cli::ConnectArgs) -> anyhow::Result<()> { - // Setup connect arguments let entry = parse_entry_point(connect_args)?; let exit = parse_exit_point(connect_args)?; diff --git a/nym-vpnc/src/protob_conversion.rs b/nym-vpnc/src/protob_conversion.rs new file mode 100644 index 0000000000..2e53626eda --- /dev/null +++ b/nym-vpnc/src/protob_conversion.rs @@ -0,0 +1,96 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; + +fn new_entry_node_gateway(identity: &NodeIdentity) -> nym_vpn_proto::EntryNode { + nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Gateway( + nym_vpn_proto::Gateway { + id: identity.to_base58_string(), + }, + )), + } +} + +fn new_entry_node_location(country_code: &str) -> nym_vpn_proto::EntryNode { + nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Location( + nym_vpn_proto::Location { + two_letter_iso_country_code: country_code.to_string(), + }, + )), + } +} + +fn new_entry_node_random_low_latency() -> nym_vpn_proto::EntryNode { + nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::RandomLowLatency( + nym_vpn_proto::Empty {}, + )), + } +} + +fn new_entry_node_random() -> nym_vpn_proto::EntryNode { + nym_vpn_proto::EntryNode { + entry_node_enum: Some(nym_vpn_proto::entry_node::EntryNodeEnum::Random( + nym_vpn_proto::Empty {}, + )), + } +} + +pub(crate) fn into_entry_point(entry: EntryPoint) -> nym_vpn_proto::EntryNode { + match entry { + EntryPoint::Gateway { identity } => new_entry_node_gateway(&identity), + EntryPoint::Location { location } => new_entry_node_location(&location), + EntryPoint::RandomLowLatency => new_entry_node_random_low_latency(), + EntryPoint::Random => new_entry_node_random(), + } +} + +fn new_exit_node_address(address: &Recipient) -> nym_vpn_proto::ExitNode { + nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Address( + nym_vpn_proto::Address { + nym_address: address.to_string(), + }, + )), + } +} + +fn new_exit_node_gateway(identity: &NodeIdentity) -> nym_vpn_proto::ExitNode { + nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Gateway( + nym_vpn_proto::Gateway { + id: identity.to_base58_string(), + }, + )), + } +} + +fn new_exit_node_location(country_code: &str) -> nym_vpn_proto::ExitNode { + nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Location( + nym_vpn_proto::Location { + two_letter_iso_country_code: country_code.to_string(), + }, + )), + } +} + +fn new_exit_node_random() -> nym_vpn_proto::ExitNode { + nym_vpn_proto::ExitNode { + exit_node_enum: Some(nym_vpn_proto::exit_node::ExitNodeEnum::Random( + nym_vpn_proto::Empty {}, + )), + } +} + +pub(crate) fn into_exit_point(exit: ExitPoint) -> nym_vpn_proto::ExitNode { + match exit { + ExitPoint::Address { address } => new_exit_node_address(&address), + ExitPoint::Gateway { identity } => new_exit_node_gateway(&identity), + ExitPoint::Location { location } => new_exit_node_location(&location), + ExitPoint::Random => new_exit_node_random(), + } +} From ce01cb42ed14298bc4b9cc6354ebe4aace998b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 24 Apr 2024 06:44:18 +0200 Subject: [PATCH 7/8] Extract out vpnd_client --- nym-vpnc/src/cli.rs | 43 ++++++ nym-vpnc/src/config.rs | 5 + nym-vpnc/src/main.rs | 135 +++++------------- ...b_conversion.rs => protobuf_conversion.rs} | 0 nym-vpnc/src/vpnd_client.rs | 49 +++++++ 5 files changed, 131 insertions(+), 101 deletions(-) rename nym-vpnc/src/{protob_conversion.rs => protobuf_conversion.rs} (100%) create mode 100644 nym-vpnc/src/vpnd_client.rs diff --git a/nym-vpnc/src/cli.rs b/nym-vpnc/src/cli.rs index 357b791800..788a1727c3 100644 --- a/nym-vpnc/src/cli.rs +++ b/nym-vpnc/src/cli.rs @@ -1,7 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use anyhow::{anyhow, Result}; use clap::{Args, Parser, Subcommand}; +use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; use std::path::PathBuf; #[derive(Parser)] @@ -132,3 +134,44 @@ impl From for ImportCredentialTypeEnum { } } } + +pub(crate) fn parse_entry_point(args: &ConnectArgs) -> Result> { + if let Some(ref entry_gateway_id) = args.entry.entry_gateway_id { + Ok(Some(EntryPoint::Gateway { + identity: NodeIdentity::from_base58_string(entry_gateway_id.clone()) + .map_err(|_| anyhow!("Failed to parse gateway id"))?, + })) + } else if let Some(ref entry_gateway_country) = args.entry.entry_gateway_country { + Ok(Some(EntryPoint::Location { + location: entry_gateway_country.clone(), + })) + } else if args.entry.entry_gateway_low_latency { + Ok(Some(EntryPoint::RandomLowLatency)) + } else if args.entry.entry_gateway_random { + Ok(Some(EntryPoint::Random)) + } else { + Ok(None) + } +} + +pub(crate) fn parse_exit_point(args: &ConnectArgs) -> Result> { + if let Some(ref exit_router_address) = args.exit.exit_router_address { + Ok(Some(ExitPoint::Address { + address: Recipient::try_from_base58_string(exit_router_address.clone()) + .map_err(|_| anyhow!("Failed to parse exit node address"))?, + })) + } else if let Some(ref exit_router_id) = args.exit.exit_gateway_id { + Ok(Some(ExitPoint::Gateway { + identity: NodeIdentity::from_base58_string(exit_router_id.clone()) + .map_err(|_| anyhow!("Failed to parse gateway id"))?, + })) + } else if let Some(ref exit_gateway_country) = args.exit.exit_gateway_country { + Ok(Some(ExitPoint::Location { + location: exit_gateway_country.clone(), + })) + } else if args.exit.exit_gateway_random { + Ok(Some(ExitPoint::Random)) + } else { + Ok(None) + } +} diff --git a/nym-vpnc/src/config.rs b/nym-vpnc/src/config.rs index c7471b2cec..10b410860c 100644 --- a/nym-vpnc/src/config.rs +++ b/nym-vpnc/src/config.rs @@ -6,3 +6,8 @@ use std::path::{Path, PathBuf}; pub(crate) fn get_socket_path() -> PathBuf { Path::new("/var/run/nym-vpn.sock").to_path_buf() } + +pub(crate) fn default_endpoint() -> String { + "http://[::1]:53181".to_string() +} + diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index edd5eb0766..9e199409fb 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -1,139 +1,67 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::path::PathBuf; - -use anyhow::{anyhow, Context}; +use anyhow::Result; use clap::Parser; -use nym_gateway_directory::{EntryPoint, ExitPoint, NodeIdentity, Recipient}; use nym_vpn_proto::{ - nym_vpnd_client::NymVpndClient, ConnectRequest, DisconnectRequest, ImportUserCredentialRequest, - StatusRequest, + ConnectRequest, DisconnectRequest, ImportUserCredentialRequest, StatusRequest, }; -use parity_tokio_ipc::Endpoint as IpcEndpoint; -use tonic::transport::{Channel as TonicChannel, Endpoint as TonicEndpoint}; +use vpnd_client::ClientType; use crate::{ cli::{Command, ImportCredentialTypeEnum}, - protob_conversion::{into_entry_point, into_exit_point}, + protobuf_conversion::{into_entry_point, into_exit_point}, }; mod cli; mod config; -mod protob_conversion; +mod protobuf_conversion; +mod vpnd_client; #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<()> { let args = cli::CliArgs::parse(); + let client_type = if args.http { + vpnd_client::ClientType::Http + } else { + vpnd_client::ClientType::Ipc + }; match args.command { - Command::Connect(ref connect_args) => connect(&args, connect_args).await?, - Command::Disconnect => disconnect(&args).await?, - Command::Status => status(&args).await?, - Command::ImportCredential(ref import_args) => import_credential(&args, import_args).await?, + Command::Connect(ref connect_args) => connect(client_type, connect_args).await?, + Command::Disconnect => disconnect(client_type).await?, + Command::Status => status(client_type).await?, + Command::ImportCredential(ref import_args) => { + import_credential(client_type, import_args).await? + } } Ok(()) } -fn parse_entry_point(args: &cli::ConnectArgs) -> anyhow::Result> { - if let Some(ref entry_gateway_id) = args.entry.entry_gateway_id { - Ok(Some(EntryPoint::Gateway { - identity: NodeIdentity::from_base58_string(entry_gateway_id.clone()) - .map_err(|_| anyhow!("Failed to parse gateway id"))?, - })) - } else if let Some(ref entry_gateway_country) = args.entry.entry_gateway_country { - Ok(Some(EntryPoint::Location { - location: entry_gateway_country.clone(), - })) - } else if args.entry.entry_gateway_low_latency { - Ok(Some(EntryPoint::RandomLowLatency)) - } else if args.entry.entry_gateway_random { - Ok(Some(EntryPoint::Random)) - } else { - Ok(None) - } -} - -fn parse_exit_point(args: &cli::ConnectArgs) -> anyhow::Result> { - if let Some(ref exit_router_address) = args.exit.exit_router_address { - Ok(Some(ExitPoint::Address { - address: Recipient::try_from_base58_string(exit_router_address.clone()) - .map_err(|_| anyhow!("Failed to parse exit node address"))?, - })) - } else if let Some(ref exit_router_id) = args.exit.exit_gateway_id { - Ok(Some(ExitPoint::Gateway { - identity: NodeIdentity::from_base58_string(exit_router_id.clone()) - .map_err(|_| anyhow!("Failed to parse gateway id"))?, - })) - } else if let Some(ref exit_gateway_country) = args.exit.exit_gateway_country { - Ok(Some(ExitPoint::Location { - location: exit_gateway_country.clone(), - })) - } else if args.exit.exit_gateway_random { - Ok(Some(ExitPoint::Random)) - } else { - Ok(None) - } -} - -fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { - bs58::decode(raw).into_vec() -} - -async fn get_channel(socket_path: PathBuf) -> anyhow::Result { - // NOTE: the uri here is ignored - Ok(TonicEndpoint::from_static("http://[::1]:53181") - .connect_with_connector(tower::service_fn(move |_| { - IpcEndpoint::connect(socket_path.clone()) - })) - .await?) -} - -fn default_endpoint() -> String { - "http://[::1]:53181".to_string() -} - -async fn get_client(args: &cli::CliArgs) -> anyhow::Result> { - if args.http { - let endpoint = default_endpoint(); - let client = NymVpndClient::connect(endpoint.clone()) - .await - .with_context(|| format!("Failed to connect to: {}", endpoint))?; - Ok(client) - } else { - let socket_path = config::get_socket_path(); - let channel = get_channel(socket_path.clone()) - .await - .with_context(|| format!("Failed to connect to: {:?}", socket_path))?; - let client = NymVpndClient::new(channel); - Ok(client) - } -} - -async fn connect(args: &cli::CliArgs, connect_args: &cli::ConnectArgs) -> anyhow::Result<()> { - let entry = parse_entry_point(connect_args)?; - let exit = parse_exit_point(connect_args)?; +async fn connect(client_type: ClientType, connect_args: &cli::ConnectArgs) -> Result<()> { + let entry = cli::parse_entry_point(connect_args)?; + let exit = cli::parse_exit_point(connect_args)?; let request = tonic::Request::new(ConnectRequest { entry: entry.map(into_entry_point), exit: exit.map(into_exit_point), }); - let mut client = get_client(args).await?; + let mut client = vpnd_client::get_client(client_type).await?; let response = client.vpn_connect(request).await?.into_inner(); println!("{:?}", response); Ok(()) } -async fn disconnect(args: &cli::CliArgs) -> anyhow::Result<()> { - let mut client = get_client(args).await?; +async fn disconnect(client_type: ClientType) -> Result<()> { + let mut client = vpnd_client::get_client(client_type).await?; let request = tonic::Request::new(DisconnectRequest {}); let response = client.vpn_disconnect(request).await?.into_inner(); println!("{:?}", response); Ok(()) } -async fn status(args: &cli::CliArgs) -> anyhow::Result<()> { - let mut client = get_client(args).await?; +async fn status(client_type: ClientType) -> Result<()> { + let mut client = vpnd_client::get_client(client_type).await?; let request = tonic::Request::new(StatusRequest {}); let response = client.vpn_status(request).await?.into_inner(); println!("{:?}", response); @@ -141,9 +69,9 @@ async fn status(args: &cli::CliArgs) -> anyhow::Result<()> { } async fn import_credential( - args: &cli::CliArgs, + client_type: ClientType, import_args: &cli::ImportCredentialArgs, -) -> anyhow::Result<()> { +) -> Result<()> { let import_type: ImportCredentialTypeEnum = import_args.credential_type.clone().into(); let raw_credential = match import_type { ImportCredentialTypeEnum::Path(path) => std::fs::read(path)?, @@ -152,8 +80,13 @@ async fn import_credential( let request = tonic::Request::new(ImportUserCredentialRequest { credential: raw_credential, }); - let mut client = get_client(args).await?; + let mut client = vpnd_client::get_client(client_type).await?; let response = client.import_user_credential(request).await?.into_inner(); println!("{:?}", response); Ok(()) } + +fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { + bs58::decode(raw).into_vec() +} + diff --git a/nym-vpnc/src/protob_conversion.rs b/nym-vpnc/src/protobuf_conversion.rs similarity index 100% rename from nym-vpnc/src/protob_conversion.rs rename to nym-vpnc/src/protobuf_conversion.rs diff --git a/nym-vpnc/src/vpnd_client.rs b/nym-vpnc/src/vpnd_client.rs new file mode 100644 index 0000000000..449442cadc --- /dev/null +++ b/nym-vpnc/src/vpnd_client.rs @@ -0,0 +1,49 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::path::PathBuf; + +use anyhow::Context; +use nym_vpn_proto::nym_vpnd_client::NymVpndClient; +use parity_tokio_ipc::Endpoint as IpcEndpoint; +use tonic::transport::{Channel as TonicChannel, Endpoint as TonicEndpoint}; + +use crate::config; + +pub(crate) enum ClientType { + Http, + Ipc, +} + +pub(crate) async fn get_client(client_type: ClientType) -> anyhow::Result> { + match client_type { + ClientType::Http => get_http_client().await, + ClientType::Ipc => get_ipc_client().await, + } +} + +async fn get_channel(socket_path: PathBuf) -> anyhow::Result { + // NOTE: the uri here is ignored + Ok(TonicEndpoint::from_static("http://[::1]:53181") + .connect_with_connector(tower::service_fn(move |_| { + IpcEndpoint::connect(socket_path.clone()) + })) + .await?) +} + +async fn get_http_client() -> anyhow::Result> { + let endpoint = config::default_endpoint(); + let client = NymVpndClient::connect(endpoint.clone()) + .await + .with_context(|| format!("Failed to connect to: {}", endpoint))?; + Ok(client) +} + +async fn get_ipc_client() -> anyhow::Result> { + let socket_path = config::get_socket_path(); + let channel = get_channel(socket_path.clone()) + .await + .with_context(|| format!("Failed to connect to: {:?}", socket_path))?; + let client = NymVpndClient::new(channel); + Ok(client) +} From 594e03a129ad27db1343ad31fe13b0fef527a8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 24 Apr 2024 08:59:16 +0200 Subject: [PATCH 8/8] rustfmt --- nym-vpnc/src/config.rs | 1 - nym-vpnc/src/main.rs | 1 - nym-vpnc/src/vpnd_client.rs | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nym-vpnc/src/config.rs b/nym-vpnc/src/config.rs index 10b410860c..9b9e5ed4eb 100644 --- a/nym-vpnc/src/config.rs +++ b/nym-vpnc/src/config.rs @@ -10,4 +10,3 @@ pub(crate) fn get_socket_path() -> PathBuf { pub(crate) fn default_endpoint() -> String { "http://[::1]:53181".to_string() } - diff --git a/nym-vpnc/src/main.rs b/nym-vpnc/src/main.rs index 9e199409fb..248399fc41 100644 --- a/nym-vpnc/src/main.rs +++ b/nym-vpnc/src/main.rs @@ -89,4 +89,3 @@ async fn import_credential( fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { bs58::decode(raw).into_vec() } - diff --git a/nym-vpnc/src/vpnd_client.rs b/nym-vpnc/src/vpnd_client.rs index 449442cadc..a360e22e3f 100644 --- a/nym-vpnc/src/vpnd_client.rs +++ b/nym-vpnc/src/vpnd_client.rs @@ -15,7 +15,9 @@ pub(crate) enum ClientType { Ipc, } -pub(crate) async fn get_client(client_type: ClientType) -> anyhow::Result> { +pub(crate) async fn get_client( + client_type: ClientType, +) -> anyhow::Result> { match client_type { ClientType::Http => get_http_client().await, ClientType::Ipc => get_ipc_client().await,