Compare commits

...

26 Commits

Author SHA1 Message Date
durch 8f0c427734 Dummy WG implementation - cleaned up 2023-08-30 12:47:48 +02:00
durch 7dfc396f4f Each packet to its own thread 2023-07-25 17:18:06 +02:00
durch 2bf44db72f Tun arc and mutex 2023-07-25 16:46:24 +02:00
durch ebfecba933 Wireguard POC 2023-06-27 11:45:25 +02:00
pierre 0134030341 build(nc-android): fix release build (sentry) 2023-06-26 12:08:41 +02:00
pierre 97c775bc68 build(nc-android): fix release build (sentry) 2023-06-26 11:59:59 +02:00
pierre bbce67902b ci(nc-android): disable release apk (unused) 2023-06-22 15:36:30 +02:00
pierre e6930046c4 ci(nc-android): disable release apk (unused) 2023-06-22 15:18:36 +02:00
pierre 0c9402503a Merge branch 'feature/nyms5-android-sentry' into develop 2023-06-22 14:23:33 +02:00
pierre 81e133b789 feat(nc-android): sentry integration and topbar navigation 2023-06-22 14:21:24 +02:00
Jon Häggblad 31bc439f65 Cargo.lock 2023-06-22 10:59:08 +02:00
Jon Häggblad 6479480cf7 Merge remote-tracking branch 'origin/release/v1.1.23' into develop 2023-06-22 10:57:52 +02:00
Jon Häggblad 4af70ef255 nym-connect: medium speed setting (#3585)
* Lock files

* Add flag to disable cover traffic

* Add flag to disable per hop delays

* Add flag to enable mixed size packets

* Add meta flag to set medium speed

* Special case zero averge hop delay to be exactly zero

* Extract out generate_hop_delays function
2023-06-22 10:55:35 +02:00
Jędrzej Stuczyński eba58f6451 NC: load old gateway configuration if we're not registering (#3586) 2023-06-22 08:49:00 +01:00
Jon Häggblad 35206655e0 Lock files 2023-06-21 09:01:47 +02:00
Jon Häggblad e14db00fc2 nym-cli: client identity signing support (#3575)
* Add client identity key signing to nym-cli

* Only load private key

* rustfmt

* Rename to identity key since it's generic

* Rename client_key to identity_key
2023-06-20 14:48:53 +02:00
benedettadavico 72b92784cc Merge remote-tracking branch 'origin/develop' into develop 2023-06-20 13:45:29 +02:00
benedettadavico 38b95c2673 nym connect version fix 2023-06-20 13:45:04 +02:00
Mark Sinclair f0f9899f82 Update package.json to fix build:dev target 2023-06-20 12:12:03 +01:00
benedettadavico 09c46e3403 Merge remote-tracking branch 'origin/master' into develop 2023-06-20 10:36:06 +02:00
Jędrzej Stuczyński 8f57919571 optional id argument for NymNodeTester (#3555) 2023-06-16 11:19:40 +01:00
pierre 3cdca0ad8d copy change 2023-06-16 11:20:20 +02:00
pierre 4c13d91bfb fix(nyms5-android): add check for worker and proxy states desync 2023-06-16 10:08:57 +02:00
Pierre Dommerc 8355e6ce5e feat(nc-desktop): add error reporting and monitoring setting (#3553) 2023-06-15 19:23:23 +02:00
Jędrzej Stuczyński bbb1e5e15a Feature/node tester disconnect (#3552)
* Ability to disconnect and reconnect GatewayClient

* usage of ibid. inside NodeTester

* example

* wasm-compatible `wait_for_shutdown` (for the future)
2023-06-13 17:25:42 +01:00
Drazen Urch 6d30e7ea8e Adjustments to cover traffic and ack handling (#3548) 2023-06-13 13:07:02 +02:00
57 changed files with 2398 additions and 351 deletions
+2 -1
View File
@@ -68,8 +68,10 @@ jobs:
working-directory: nym-connect/native/android
env:
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
SENTRY_AUTH_TOKEN: ${{ secrets.NYMS5_ANDROID_SENTRY_AUTH_TOKEN }}
# build for arm64 and x86_64
run: |
echo "auth.token=$SENTRY_AUTH_TOKEN" | tee -a sentry.properties
./gradlew :app:assembleArch64Debug
./gradlew :app:assembleArch64Release
@@ -107,4 +109,3 @@ jobs:
files: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
+2 -1
View File
@@ -43,4 +43,5 @@ envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
foxyfox.env
gateway/deploy.sh
Generated
+1074 -255
View File
File diff suppressed because it is too large Load Diff
+17 -3
View File
@@ -2240,7 +2240,7 @@ dependencies = [
[[package]]
name = "nym-bin-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"atty",
"clap",
@@ -2255,7 +2255,7 @@ dependencies = [
[[package]]
name = "nym-client-core"
version = "1.1.14"
version = "1.1.15"
dependencies = [
"async-trait",
"base64 0.21.2",
@@ -2300,7 +2300,7 @@ dependencies = [
[[package]]
name = "nym-client-wasm"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"async-trait",
@@ -2697,6 +2697,7 @@ dependencies = [
"nym-pemstore",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -2723,6 +2724,7 @@ dependencies = [
"nym-crypto",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -2753,6 +2755,7 @@ dependencies = [
"nym-sphinx-chunking",
"nym-sphinx-forwarding",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -2884,6 +2887,7 @@ dependencies = [
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"openssl",
"prost",
"reqwest",
"serde",
@@ -2974,6 +2978,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.26.0+1.1.1u"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.87"
@@ -2982,6 +2995,7 @@ checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
+41 -4
View File
@@ -116,11 +116,11 @@ async function testWithTester() {
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
// const nodeTester = await new NymNodeTester(topology, undefined, preferredGateway);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
// const nodeTester = await NymNodeTester.new_with_api(validator, undefined, preferredGateway)
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
// const topology = dummyTopology()
@@ -139,6 +139,40 @@ async function testWithTester() {
}
}
};
}
async function testerReconnection() {
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const nodeTester = await NymNodeTester.new_with_api(validator);
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
console.log("starting node test...");
let result1 = await nodeTester.test_node(mixnodeIdentity);
console.log("sleeping for 5s");
await new Promise(r => setTimeout(r, 5000));
await nodeTester.disconnect_from_gateway();
console.log("sleeping for 5s");
await new Promise(r => setTimeout(r, 5000));
await nodeTester.reconnect_to_gateway();
let result2 = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result1)
printAndDisplayTestResult(result2)
}
}
}
};
}
async function testWithNymClient() {
@@ -330,14 +364,17 @@ async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// show reconnection capabilities
// await testerReconnection()
// run test on simplified and dedicated tester:
// await testWithTester()
await testWithTester()
// hook-up the whole client for testing
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
await normalNymClientUsage()
// await normalNymClientUsage()
}
// Let's get started!
+38
View File
@@ -4,6 +4,9 @@
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use crate::error::WasmClientError;
use crate::tester::LockedGatewayClient;
use js_sys::Promise;
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use serde::{Deserialize, Serialize};
@@ -12,6 +15,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
@@ -107,3 +111,37 @@ impl Drop for TestMarker {
self.value.store(false, Ordering::SeqCst)
}
}
pub(crate) trait GatewayReconnection {
fn disconnect_from_gateway(&self) -> Promise;
fn reconnect_to_gateway(&self) -> Promise;
}
impl GatewayReconnection for LockedGatewayClient {
fn disconnect_from_gateway(&self) -> Promise {
let this = self.clone();
future_to_promise(async move {
let mut guard = this.lock().await;
guard
.disconnect()
.await
.map_err(|err| JsValue::from(WasmClientError::from(err)))?;
Ok(JsValue::undefined())
})
}
fn reconnect_to_gateway(&self) -> Promise {
let this = self.clone();
future_to_promise(async move {
let mut guard = this.lock().await;
guard
.try_reconnect()
.await
.map_err(|err| JsValue::from(WasmClientError::from(err)))?;
Ok(JsValue::undefined())
})
}
}
+41 -10
View File
@@ -7,7 +7,7 @@ use crate::helpers::{current_network_topology_async, setup_from_topology};
use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
GatewayReconnection, NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
};
use crate::topology::WasmNymTopology;
use futures::channel::mpsc;
@@ -73,6 +73,7 @@ pub struct NymNodeTester {
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway: Option<IdentityKey>,
id: Option<String>,
base_topology: NymTopology,
@@ -94,9 +95,11 @@ impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
base_topology: WasmNymTopology,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
id,
gateway,
base_topology: base_topology.into(),
bandwidth_controller: None,
@@ -105,15 +108,20 @@ impl NymNodeTesterBuilder {
async fn _new_with_api(
api_url: String,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(topology, gateway))
Ok(NymNodeTesterBuilder::new(topology, id, gateway))
}
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
pub fn new_with_api(
api_url: String,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
Self::_new_with_api(api_url, id, gateway)
.await
.into_promise_result()
})
@@ -133,7 +141,13 @@ impl NymNodeTesterBuilder {
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let task_manager = TaskManager::default();
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let storage_id = if let Some(client_id) = &self.id {
format!("{NODE_TESTER_ID}-{client_id}")
} else {
NODE_TESTER_ID.to_owned()
};
let client_store = ClientStorage::new_async(&storage_id, None).await?;
let init_details = self.gateway_info(&client_store).await?;
let gateway_endpoint = init_details.gateway_details;
@@ -234,29 +248,46 @@ async fn test_mixnode(
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
pub fn new(
topology: WasmNymTopology,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(topology, gateway).setup_client()
NymNodeTesterBuilder::new(topology, id, gateway).setup_client()
}
async fn _new_with_api(
api_url: String,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
NymNodeTesterBuilder::_new_with_api(api_url, id, gateway)
.await?
._setup_client()
.await
}
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
pub fn new_with_api(
api_url: String,
id: Option<String>,
gateway: Option<IdentityKey>,
) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
Self::_new_with_api(api_url, id, gateway)
.await
.into_promise_result()
})
}
pub fn disconnect_from_gateway(&self) -> Promise {
self.gateway_client.disconnect_from_gateway()
}
pub fn reconnect_to_gateway(&self) -> Promise {
self.gateway_client.reconnect_to_gateway()
}
fn prepare_test_packets(
&self,
mixnode_identity: String,
@@ -10,7 +10,7 @@ use log::*;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::cover::generate_loop_cover_packet;
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
@@ -63,6 +63,8 @@ where
/// Optional secondary predefined packet size used for the loop cover messages.
secondary_packet_size: Option<PacketSize>,
packet_type: PacketType,
}
impl<R> Stream for LoopCoverTrafficStream<R>
@@ -135,6 +137,7 @@ impl LoopCoverTrafficStream<OsRng> {
topology_access,
primary_packet_size: traffic_config.primary_packet_size,
secondary_packet_size: traffic_config.secondary_packet_size,
packet_type: traffic_config.packet_type,
}
}
@@ -194,7 +197,7 @@ impl LoopCoverTrafficStream<OsRng> {
self.average_ack_delay,
self.cover_traffic.loop_cover_traffic_average_delay,
cover_traffic_packet_size,
nym_sphinx::params::PacketType::Mix,
self.packet_type,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
@@ -724,7 +724,7 @@ impl<C, St> GatewayClient<C, St> {
Ok(())
}
async fn try_reconnect(&mut self) -> Result<(), GatewayClientError> {
pub async fn try_reconnect(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
self.establish_connection().await?;
}
@@ -738,6 +738,12 @@ impl<C, St> GatewayClient<C, St> {
Ok(())
}
pub async fn disconnect(&mut self) -> Result<(), GatewayClientError> {
self.recover_socket_connection().await?;
self.connection = SocketState::NotConnected;
Ok(())
}
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError>
where
C: DkgQueryClient + Send + Sync,
@@ -48,17 +48,21 @@ impl PacketRouter {
// data he takes the SURB-ACK and first hop address.
// currently SURB-ACKs are attached in EVERY packet, even cover, so this is always true
let ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let outfox_ack_overhead =
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
for received_packet in unwrapped_packets {
if received_packet.len() == PacketSize::AckPacket.plaintext_size()
|| received_packet.len() == PacketSize::OutfoxAckPacket.plaintext_size()
// we don't know the real size of the payload, it could be anything <= 48 bytes
|| received_packet.len() <= PacketSize::OutfoxAckPacket.plaintext_size()
{
received_acks.push(received_packet);
} else if received_packet.len()
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
|| received_packet.len()
== PacketSize::OutfoxRegularPacket.plaintext_size() - ack_overhead
|| received_packet.len() == PacketSize::OutfoxRegularPacket.size() - 6
== PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead
|| received_packet.len()
== PacketSize::OutfoxRegularPacket.size() - outfox_ack_overhead
{
trace!("routing regular packet");
received_messages.push(received_packet);
+3
View File
@@ -41,3 +41,6 @@ nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-co
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
nym-types = { path = "../../common/types" }
@@ -0,0 +1,68 @@
use clap::{Args, Parser, Subcommand};
use nym_bin_common::output_format::OutputFormat;
use nym_crypto::asymmetric::identity;
use nym_types::helpers::ConsoleSigningOutput;
use nym_validator_client::nyxd::error::NyxdError;
use std::path::PathBuf;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsIdentityKey {
#[clap(subcommand)]
pub command: MixnetOperatorsIdentityKeyCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsIdentityKeyCommands {
/// Register a name alias for a nym address
Sign(SignArgs),
}
#[derive(Debug, Parser)]
pub struct SignArgs {
/// Path to private identity key (example: private_identity_key.pem)
#[clap(long)]
private_key: PathBuf,
/// Base58 encoded message to sign
#[clap(long)]
base58_msg: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub async fn sign(args: SignArgs) -> Result<(), NyxdError> {
eprintln!(">>> loading: {}", args.private_key.display());
let private_identity_key: identity::PrivateKey =
nym_pemstore::load_key(args.private_key).expect("failed to load key");
print_signed_msg(&private_identity_key, &args.base58_msg, args.output);
Ok(())
}
fn print_signed_msg(private_key: &identity::PrivateKey, raw_msg: &str, output: OutputFormat) {
let trimmed = raw_msg.trim();
eprintln!(">>> attempting to sign: {trimmed}");
let Ok(decoded) = bs58::decode(trimmed).into_vec() else {
println!("failed to base58 decode the message, did you copy it correctly?");
return;
};
eprintln!(">>> decoding the message...");
// we don't really care about what particular information is embedded inside of it,
// we just want to know if user correctly copied the string, i.e. whether it's a valid bs58 encoded json
if serde_json::from_slice::<serde_json::Value>(&decoded).is_err() {
println!("failed to parse the message after decoding, did you copy it correctly?");
return;
};
// if this is a valid json, it MUST be a valid string
let decoded_string = String::from_utf8(decoded.clone()).unwrap();
let signature = private_key.sign(&decoded).to_base58_string();
let sign_output = ConsoleSigningOutput::new(decoded_string, signature);
println!("{}", output.format(&sign_output));
}
@@ -4,6 +4,7 @@
use clap::{Args, Subcommand};
pub mod gateway;
pub mod identity_key;
pub mod mixnode;
pub mod name;
pub mod service;
@@ -26,4 +27,6 @@ pub enum MixnetOperatorsCommands {
ServiceProvider(service::MixnetOperatorsService),
/// Manage your registered name
Name(name::MixnetOperatorsName),
/// Sign messages using your private identity key
IdentityKey(identity_key::MixnetOperatorsIdentityKey),
}
@@ -3,7 +3,7 @@
use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx_types::{NymPacketError, SphinxError};
use nym_sphinx_types::{NymPacketError, OutfoxError, SphinxError};
use thiserror::Error;
#[derive(Error, Debug)]
@@ -25,4 +25,7 @@ pub enum MixProcessingError {
#[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")]
ReceivedOldTypeVpnPacket,
#[error("failed to process received outfox packet: {0}")]
OutfoxProcessingError(#[from] OutfoxError),
}
@@ -20,12 +20,14 @@ use tracing::instrument;
type ForwardAck = MixPacket;
#[derive(Debug)]
pub struct ProcessedFinalHop {
pub destination: DestinationAddressBytes,
pub forward_ack: Option<ForwardAck>,
pub message: Vec<u8>,
}
#[derive(Debug)]
pub enum MixProcessingResult {
/// Contains unwrapped data that should first get delayed before being sent to next hop.
ForwardHop(MixPacket, Option<SphinxDelay>),
@@ -141,7 +143,7 @@ impl SphinxPacketProcessor {
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
debug!("Failed to recover first hop from ack data: {err}");
info!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
@@ -205,7 +207,7 @@ impl SphinxPacketProcessor {
if packet.is_final_hop() {
self.process_final_hop(
DestinationAddressBytes::from_bytes(next_address),
packet.recover_plaintext().to_vec(),
packet.recover_plaintext()?.to_vec(),
packet_size,
packet_type,
)
@@ -239,7 +241,14 @@ impl SphinxPacketProcessor {
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
self.perform_final_processing(processed_packet, packet_size, packet_type)
let final_processing_result =
self.perform_final_processing(processed_packet, packet_size, packet_type);
if final_processing_result.is_err() {
error!("{:?}", final_processing_result)
}
final_processing_result
})
}
}
+3 -2
View File
@@ -15,11 +15,12 @@ thiserror = { workspace = true }
zeroize = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nym-pemstore = { path = "../../pemstore" }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-routing = { path = "../routing" }
nym-sphinx-types = { path = "../types" }
nym-pemstore = { path = "../../pemstore" }
nym-topology = { path = "../../topology" }
[features]
serde = ["serde_crate", "generic-array"]
serde = ["serde_crate", "generic-array"]
@@ -9,7 +9,7 @@ use nym_sphinx_addressing::nodes::{
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::delays::{self, Delay};
use nym_sphinx_types::delays::Delay;
use nym_sphinx_types::{NymPacket, NymPacketError, MIN_PACKET_SIZE};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
@@ -51,7 +51,7 @@ impl SurbAck {
{
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
let delays = delays::generate_from_average_duration(route.len(), average_delay);
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id);
@@ -16,6 +16,7 @@ thiserror = "1"
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-routing = { path = "../routing" }
nym-sphinx-types = { path = "../types" }
nym-topology = { path = "../../topology" }
@@ -7,7 +7,7 @@ use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, NymPacket, SURBMaterial, SphinxError, SURB};
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
@@ -96,7 +96,7 @@ impl ReplySurb {
{
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
let delays = delays::generate_from_average_duration(route.len(), average_delay);
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
let surb_material = SURBMaterial::new(route, delays, destination);
+2 -1
View File
@@ -15,7 +15,8 @@ nym-crypto = { path = "../../crypto" }
nym-sphinx-acknowledgements = { path = "../acknowledgements" }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-chunking = { path = "../chunking" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-forwarding = { path = "../forwarding" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-routing = { path = "../routing" }
nym-sphinx-types = { path = "../types" }
nym-topology = { path = "../../topology" }
+29 -12
View File
@@ -13,7 +13,7 @@ use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType, DEFAULT_NUM_MIX_HOPS,
};
use nym_sphinx_types::{delays, NymPacket};
use nym_sphinx_types::NymPacket;
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
@@ -91,6 +91,7 @@ where
>(rng, full_address.encryption_key());
let public_key_bytes = ephemeral_keypair.public_key().to_bytes();
let cover_size = packet_size.plaintext_size() - public_key_bytes.len() - ack_bytes.len();
let mut cover_content: Vec<_> = LOOP_COVER_MESSAGE_PAYLOAD
@@ -119,22 +120,38 @@ where
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, full_address.gateway())?;
let delays = delays::generate_from_average_duration(route.len(), average_packet_delay);
let delays = nym_sphinx_routing::generate_hop_delays(average_packet_delay, route.len());
let destination = full_address.as_sphinx_destination();
// once merged, that's an easy rng injection point for sphinx packets : )
let packet = NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?;
let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
Ok(MixPacket::new(first_hop_address, packet, PacketType::Mix))
// once merged, that's an easy rng injection point for sphinx packets : )
let packet = match packet_type {
PacketType::Mix => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
PacketType::Outfox => NymPacket::outfox_build(
packet_payload,
&route,
&destination,
Some(packet_size.plaintext_size()),
)?,
};
Ok(MixPacket::new(first_hop_address, packet, packet_type))
}
/// Helper function used to determine if given message represents a loop cover message.
+11 -1
View File
@@ -1,8 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_types::Node;
use nym_sphinx_types::{delays, Delay, Node};
use thiserror::Error;
pub trait SphinxRouteMaker {
@@ -41,3 +43,11 @@ impl SphinxRouteMaker for Vec<Node> {
}
}
}
pub fn generate_hop_delays(average_packet_delay: Duration, num_hops: usize) -> Vec<Delay> {
if average_packet_delay.is_zero() {
vec![nym_sphinx_types::Delay::new_from_millis(0); num_hops]
} else {
delays::generate_from_average_duration(num_hops, average_packet_delay)
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, Delay, NymPacket};
use nym_sphinx_types::{Delay, NymPacket};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng};
use std::convert::TryFrom;
@@ -233,7 +233,7 @@ pub trait FragmentPreparer {
// including set of delays
let delays =
delays::generate_from_average_duration(route.len(), self.average_packet_delay());
nym_sphinx_routing::generate_hop_delays(self.average_packet_delay(), route.len());
// create the actual sphinx packet here. With valid route and correct payload size,
// there's absolutely no reason for this call to fail.
+13 -4
View File
@@ -177,20 +177,29 @@ impl TaskManager {
drop(notify_rx);
}
#[cfg(not(target_arch = "wasm32"))]
let interrupt_future = tokio::signal::ctrl_c();
// in wasm we'll never get our shutdown anyway...
#[cfg(target_arch = "wasm32")]
futures::future::pending::<()>().await;
let interrupt_future = futures::future::pending::<()>();
#[cfg(not(target_arch = "wasm32"))]
let wait_future = tokio::time::sleep(Duration::from_secs(self.shutdown_timer_secs));
// TODO: we should be using a `Delay` here for wasm
#[cfg(target_arch = "wasm32")]
let wait_future = futures::future::pending::<()>();
tokio::select! {
_ = self.notify_tx.closed() => {
log::info!("All registered tasks succesfully shutdown");
},
_ = tokio::signal::ctrl_c() => {
_ = interrupt_future => {
log::info!("Forcing shutdown");
}
_ = tokio::time::sleep(Duration::from_secs(self.shutdown_timer_secs)) => {
log::info!("Timout reached, forcing shutdown");
_ = wait_future => {
log::info!("Timeout reached, forcing shutdown");
},
}
}
+30 -3
View File
@@ -34,16 +34,41 @@ pretty_env_logger = "0.4"
rand = "0.7"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] }
sqlx = { version = "0.5", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs", ] }
tokio = { version = "1.24.1", features = [
"rt-multi-thread",
"net",
"signal",
"fs",
] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tungstenite = "0.14"
tokio-util = { version = "0.7.4", features = ["codec"] }
url = { version = "2.2", features = ["serde"] }
zeroize = { workspace = true }
# wireguard
# Forked it to be able to bump x25519-dalek to rc.3
boringtun = { git = "https://github.com/durch/boringtun.git" }
base64 = "0.21"
x25519-dalek = { version = "=2.0.0-rc.3", features = [
"reusable_secrets",
"static_secrets",
] }
etherparse = "0.13.0"
pnet = "0.34.0"
bytes = "1.4.0"
async-recursion = "1.0.4"
smoltcp = "0.10.0"
tun-tap = "0.1.3"
# internal
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
@@ -60,7 +85,9 @@ nym-sphinx = { path = "../common/nymsphinx" }
nym-statistics-common = { path = "../common/statistics" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client", features = [ "nyxd-client" ] }
nym-validator-client = { path = "../common/client-libs/validator-client", features = [
"nyxd-client",
] }
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
+1
View File
@@ -0,0 +1 @@
gA3NCDl+xOorR3heFVB47FlGunsZgS4RDX2M0IY73lc=
+1
View File
@@ -0,0 +1 @@
mxV/mw7WZTe+0Msa0kvJHMHERDA/cSskiZWQce+TdEs=
+1
View File
@@ -0,0 +1 @@
AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=
+1
View File
@@ -0,0 +1 @@
WM8s8bYegwMa0TJ+xIwhk+dImk2IpDUKslDBCZPizlE=
+5
View File
@@ -10,6 +10,7 @@ use crate::node::client_handling::websocket::connection_handler::coconut::Coconu
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
use crate::node::wireguard::wireguard;
use log::*;
use nym_bin_common::output_format::OutputFormat;
use nym_crypto::asymmetric::{encryption, identity};
@@ -28,6 +29,8 @@ pub(crate) mod client_handling;
pub(crate) mod mixnet_handling;
pub(crate) mod statistics;
pub(crate) mod storage;
mod wg;
pub(crate) mod wireguard;
/// Wire up and create Gateway instance
pub(crate) async fn create_gateway(config: Config) -> Gateway<PersistentStorage> {
@@ -297,6 +300,8 @@ impl<St> Gateway<St> {
Arc::new(coconut_verifier),
);
tokio::spawn(wireguard());
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
self.wait_for_interrupt(shutdown).await
+31
View File
@@ -0,0 +1,31 @@
use bytes::Bytes;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone)]
pub enum Event {
/// Dumb event with no data.
Dumb,
/// IP packet received from the WireGuard tunnel that should be passed through to the corresponding virtual device/internet.
/// Original implementation also has protocol here since it understands it, but we'll have to infer it downstream
WgPacket(Bytes),
/// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device.
IpPacket(Bytes),
}
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Event::Dumb => {
write!(f, "Dumb{{}}")
}
Event::WgPacket(data) => {
let size = data.len();
write!(f, "WgPacket{{ size={} }}", size)
}
Event::IpPacket(data) => {
let size = data.len();
write!(f, "IpPacket{{ size={} }}", size)
}
}
}
}
+437
View File
@@ -0,0 +1,437 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use async_recursion::async_recursion;
use base64::engine::general_purpose;
use base64::Engine as _;
use boringtun::noise::errors::WireGuardError;
use boringtun::noise::{Tunn, TunnResult};
use etherparse::{InternetSlice, PacketBuilder, SlicedPacket, TransportSlice};
use log::{debug, info, warn};
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::ipv4::{Ipv4Packet, MutableIpv4Packet};
use pnet::packet::{MutablePacket, Packet, PacketSize};
use pnet::transport::{ipv4_packet_iter, transport_channel};
use tokio::net::UdpSocket;
use tokio::sync::{Mutex, RwLock};
use tokio::time::{sleep, timeout};
use x25519_dalek::StaticSecret;
use crate::error;
use self::events::Event;
pub mod events;
const MAX_PACKET: usize = 65536;
/// A WireGuard tunnel. Encapsulates and decapsulates IP packets,
/// recieves packets from the client on the udp_rx channel,
/// and events from the internet on the eth_rx channel,
/// sends data through udp socket or datalink sender directly.
/// For now all tunnels recieve all events and filter on the source_peer_addr
pub struct WireGuardTunnel {
source_peer_addr: Arc<RwLock<Option<(Ipv4Addr, u16)>>>,
/// `boringtun` peer/tunnel implementation, used for crypto & WG protocol.
peer: Arc<Mutex<Tunn>>,
udp: Arc<UdpSocket>,
peer_endpoint: SocketAddr,
bus_rx: tokio::sync::broadcast::Receiver<Event>,
bus_tx: tokio::sync::broadcast::Sender<Event>,
}
pub fn handle_l3_packet(data: &[u8], destination_addr: Ipv4Addr) -> Vec<u8> {
let (mut tx, mut rx) = transport_channel(
65535,
pnet::transport::TransportChannelType::Layer3(IpNextHeaderProtocols::Tcp),
)
.unwrap();
let mut rx_iterator = ipv4_packet_iter(&mut rx);
let mut must_send = true;
let mut cnt = 0;
while let Ok((packet, addr)) = rx_iterator.next() {
if must_send {
let data = data.to_vec();
let incoming_packet = Ipv4Packet::new(&data).unwrap();
let mut new_packet = vec![0; incoming_packet.packet_size()];
let mut outgoing_packet = MutableIpv4Packet::new(&mut new_packet).unwrap();
outgoing_packet.clone_from(&incoming_packet);
info!(
"Sending (ttl={}, proto={} from {} to {}({})",
outgoing_packet.get_ttl(),
outgoing_packet.get_next_level_protocol(),
outgoing_packet.get_source(),
outgoing_packet.get_destination(),
destination_addr
);
outgoing_packet.set_source("95.217.227.118".parse().unwrap());
let sent = tx
.send_to(outgoing_packet, IpAddr::V4(destination_addr))
.unwrap();
info!("Sent L3 packet ({sent})");
must_send = false;
continue;
}
cnt += 1;
let source = packet.get_source();
let destination = packet.get_destination();
info!("Ignoring packet from {source}");
if source == destination_addr {
info!("({addr}){source} -> {destination}");
return packet.payload().to_vec();
}
if cnt >= 10 {
break;
}
}
vec![]
}
impl WireGuardTunnel {
async fn set_source_peer_addr(&self, source_addr: Ipv4Addr, source_port: Option<u16>) {
{
if self.source_peer_addr.read().await.is_some() {
return;
}
}
let mut source_peer_addr = self.source_peer_addr.write().await;
*source_peer_addr = Some((source_addr, source_port.unwrap_or(0)))
}
pub async fn spin_off(mut self) {
info!("Spun off WG tunnel");
// We'll receive both inbound and outbound packages on the same channel, and filter on packet type
loop {
tokio::select! {
packet = self.bus_rx.recv() => {
match packet {
Ok(p) => {
info!("{p}");
match p {
Event::IpPacket(data) => self.consume_eth(&data).await,
Event::WgPacket(data) => self.consume_wg(&data).await,
_ => {}
}
},
Err(e) => error!("{e}")
}
},
_ = sleep(Duration::from_millis(5))=> {
let mut send_buf = [0u8; MAX_PACKET];
let tun_result = {
let mut tun = timeout(Duration::from_millis(100), self.peer()).await.unwrap();
tun.update_timers(&mut send_buf)
};
self.handle_routine_tun_result(tun_result).await;
}
}
}
}
pub async fn consume_eth(&self, data: &[u8]) {
let parsed_packet = SlicedPacket::from_ethernet(data).unwrap();
debug!("{parsed_packet:?}");
let (source_addr, destination_addr) = match parsed_packet.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
_ => unimplemented!(),
};
let (source_port, destination_port, icmp_type) = match parsed_packet.transport.as_ref() {
Some(TransportSlice::Tcp(tcp)) => {
(Some(tcp.source_port()), Some(tcp.destination_port()), None)
}
Some(TransportSlice::Udp(udp)) => {
(Some(udp.source_port()), Some(udp.destination_port()), None)
}
Some(TransportSlice::Icmpv4(icmp)) => (None, None, Some(icmp.icmp_type())),
Some(TransportSlice::Icmpv6(_)) => panic!("ICMPv6"),
Some(TransportSlice::Unknown(_)) => panic!("Unknown"),
None => panic!("No transport layer"),
};
debug!(
"{:?}:{:?} -> {:?}:{:?} - ({:?})",
source_addr, source_port, destination_addr, destination_port, icmp_type
);
if destination_addr == self.source_peer_addr.read().await.unwrap().0 {
info!("Sending {} to {}", data.len(), self.peer_endpoint);
} else {
return;
}
let response_packet_builder =
PacketBuilder::ipv4(source_addr.octets(), destination_addr.octets(), 64);
let mut response_packet =
Vec::<u8>::with_capacity(response_packet_builder.size(parsed_packet.payload.len()));
match parsed_packet.transport.as_ref() {
Some(TransportSlice::Udp(udp)) => {
debug!("UDP: {}, {}", udp.length(), udp.destination_port());
let response_packet_builder =
response_packet_builder.udp(source_port.unwrap(), destination_port.unwrap());
response_packet_builder
.write(&mut response_packet, parsed_packet.payload)
.unwrap();
}
Some(TransportSlice::Tcp(tcp)) => {
let response_packet_builder = response_packet_builder.tcp(
destination_port.unwrap(),
source_port.unwrap(),
tcp.sequence_number(),
tcp.window_size(),
);
response_packet_builder
.write(&mut response_packet, parsed_packet.payload)
.unwrap();
}
Some(TransportSlice::Icmpv4(icmp)) => {
info!("{:?}", icmp.icmp_type());
let response_packet_builder = response_packet_builder.icmpv4(icmp.icmp_type());
response_packet_builder
.write(&mut response_packet, parsed_packet.payload)
.unwrap();
}
None => {}
_ => unimplemented!(),
};
let encapsulated_response_packet = self.encapsulate_packet(&response_packet).await;
// let packet = Tunn::parse_incoming_packet(&response_packet).unwrap();
// info!("Sending {packet:?} to {addr}");
let sent = self
.udp
.send_to(&encapsulated_response_packet, self.peer_endpoint)
.await
.unwrap();
info!(
"[{}:{} ({sent})-> {}:{}] -> {}",
destination_addr,
destination_port.unwrap_or(0),
source_addr,
source_port.unwrap_or(0),
self.peer_endpoint
);
}
// TODO: extend to work with IPv6
pub async fn produce_eth(&self, packet_bytes: &[u8]) -> Vec<u8> {
let outgoing_packet = SlicedPacket::from_ip(packet_bytes).unwrap();
let (source_addr, destination_addr) = match outgoing_packet.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
_ => unimplemented!(),
};
let (source_port, destination_port, icmp_type) = match outgoing_packet.transport.as_ref() {
Some(TransportSlice::Tcp(tcp)) => {
(Some(tcp.source_port()), Some(tcp.destination_port()), None)
}
Some(TransportSlice::Udp(udp)) => {
(Some(udp.source_port()), Some(udp.destination_port()), None)
}
Some(TransportSlice::Icmpv4(icmp)) => (None, None, Some(icmp.icmp_type())),
Some(TransportSlice::Icmpv6(_)) => panic!("ICMPv6"),
Some(TransportSlice::Unknown(_)) => panic!("Unknown"),
None => panic!("No transport layer"),
};
info!(
"{:?}:{:?} -> {:?}:{:?} - ({:?})",
source_addr, source_port, destination_addr, destination_port, icmp_type
);
self.set_source_peer_addr(source_addr, source_port).await;
handle_l3_packet(packet_bytes, destination_addr)
}
/// WireGuard consumption task. Receives encrypted packets from the WireGuard peer,
/// decapsulates them, and dispatches newly received IP packets.
async fn consume_wg(&self, data: &[u8]) {
let mut send_buf = [0u8; MAX_PACKET];
let mut peer = self.peer().await;
match peer.decapsulate(None, data, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
match self.udp.send_to(packet, self.peer_endpoint).await {
Ok(_) => {}
Err(e) => {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {:?}", e);
}
};
loop {
let mut send_buf = [0u8; MAX_PACKET];
match peer.decapsulate(None, &[], &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
match self.udp.send_to(packet, self.peer_endpoint).await {
Ok(_) => {}
Err(e) => {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {:?}", e);
break;
}
};
}
_ => {
break;
}
}
}
}
TunnResult::WriteToTunnelV4(packet, _) | TunnResult::WriteToTunnelV6(packet, _) => {
info!(
"WireGuard endpoint sent IP packet of {} bytes",
packet.len()
);
let response = self.produce_eth(packet).await;
if !response.is_empty() {
self.bus_tx.send(Event::IpPacket(response.into())).unwrap();
}
}
x => warn!("{x:?}"),
}
}
async fn encapsulate_packet(&self, payload: &[u8]) -> Vec<u8> {
let len = 148.max(payload.len() + 32);
let mut dst = vec![0; len];
let mut t = self.peer().await;
let packet = t.encapsulate(payload, &mut dst);
match packet {
TunnResult::WriteToNetwork(p) => p.to_vec(),
unexpected => {
error!("{:?}", unexpected);
vec![]
}
}
}
pub async fn peer(&self) -> tokio::sync::MutexGuard<'_, Tunn> {
self.peer.lock().await
}
pub async fn new(
peer_static_public: x25519_dalek::PublicKey,
udp: Arc<UdpSocket>,
peer_endpoint: SocketAddr,
bus_tx: tokio::sync::broadcast::Sender<Event>,
) -> Self {
let peer = Arc::new(Mutex::new(Self::create_tunnel(peer_static_public)));
Self {
source_peer_addr: Arc::new(RwLock::new(None)),
peer,
udp,
peer_endpoint,
bus_rx: bus_tx.subscribe(),
bus_tx,
}
}
fn create_tunnel(peer_static_public: x25519_dalek::PublicKey) -> Tunn {
let secret_bytes: [u8; 32] = general_purpose::STANDARD
.decode("AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=")
.unwrap()
.try_into()
.unwrap();
let private_key = StaticSecret::try_from(secret_bytes).unwrap();
Tunn::new(private_key, peer_static_public, None, None, 0, None).unwrap()
}
/// Encapsulates and sends an IP packet back to the WireGuard client.
pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> {
let mut send_buf = [0u8; MAX_PACKET];
match self.peer().await.encapsulate(packet, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
self.udp.send_to(packet, self.peer_endpoint).await.unwrap();
debug!(
"Sent {} bytes to WireGuard endpoint (encrypted IP packet)",
packet.len()
);
}
TunnResult::Err(e) => {
error!("Failed to encapsulate IP packet: {:?}", e);
}
TunnResult::Done => {
// Ignored
}
other => {
error!(
"Unexpected WireGuard state during encapsulation: {:?}",
other
);
}
};
Ok(())
}
#[async_recursion]
async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) -> () {
match result {
TunnResult::WriteToNetwork(packet) => {
info!(
"Sending routine packet of {} bytes to WireGuard endpoint",
packet.len()
);
match self.udp.send_to(packet, self.peer_endpoint).await {
Ok(_) => {}
Err(e) => {
error!(
"Failed to send routine packet to WireGuard endpoint: {:?}",
e
);
}
};
}
TunnResult::Err(WireGuardError::ConnectionExpired) => {
warn!("Wireguard handshake has expired!");
let mut buf = vec![0u8; MAX_PACKET];
let result = self
.peer()
.await
.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await
}
TunnResult::Err(e) => {
error!(
"Failed to prepare routine packet for WireGuard endpoint: {:?}",
e
);
}
TunnResult::Done => {
// Sleep for a bit
// tokio::time::sleep(Duration::from_millis(1)).await;
}
other => {
warn!("Unexpected WireGuard routine task state: {:?}", other);
}
};
}
// fn route_protocol(&self, packet: &[u8]) -> Option<Protocol> {
// match IpVersion::of_packet(packet) {
// Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet)
// .ok()
// // Only care if the packet is destined for this tunnel
// .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip)
// .and_then(|packet| match packet.next_header() {
// IpProtocol::Tcp => Some(Protocol::Tcp),
// IpProtocol::Udp => Some(Protocol::Udp),
// // Unrecognized protocol, so we cannot determine where to route
// _ => None,
// }),
// Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet)
// .ok()
// // Only care if the packet is destined for this tunnel
// .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip)
// .and_then(|packet| match packet.next_header() {
// IpProtocol::Tcp => Some(Protocol::Tcp),
// IpProtocol::Udp => Some(Protocol::Udp),
// // Unrecognized protocol, so we cannot determine where to route
// _ => None,
// }),
// _ => None,
// }
// }
}
+77
View File
@@ -0,0 +1,77 @@
use base64::engine::general_purpose;
use base64::Engine as _;
use log::{error, info};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::net::UdpSocket;
use tokio::sync::broadcast;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::node::wg::events::Event;
use crate::node::wg::WireGuardTunnel;
pub async fn wireguard() {
let wg_address = "0.0.0.0:51820";
let sock = Arc::new(UdpSocket::bind(wg_address).await.unwrap());
info!("wg listening on {wg_address}");
// Secret key ofthe gateway, we'll need a way to generate this from the IdentityKey, might be enough to do some base58 -> base64 conversion
let secret_bytes: [u8; 32] = general_purpose::STANDARD
.decode("AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=")
.unwrap()
.try_into()
.unwrap();
// Hardcoded peer public key, we'll need a way to register those, private key for that one is `aMUcuAgTiFCHQ/fHqEQRvpLWBxh8sKA7f7lSyWymrGE=`
// Wireguard configuration that works with this setup is below, this needs to be put into the wireguard client of choice.
// Working in this case means that they go through the handshake, and client
// starts sending data packets to the gateway.
//
// [Interface]
// PrivateKey = aMUcuAgTiFCHQ/fHqEQRvpLWBxh8sKA7f7lSyWymrGE=
// Address = 10.8.0.0/24
// DNS = 1.1.1.1
//
// [Peer]
// PublicKey = y6/iGYraJjON6pw9fcBa5vLRbGsQqprFLfWKyJQnlWs=
// AllowedIPs = 0.0.0.0/0
// Endpoint = 127.0.0.1:51820
let peer_public_bytes: [u8; 32] = general_purpose::STANDARD
.decode("mxV/mw7WZTe+0Msa0kvJHMHERDA/cSskiZWQce+TdEs=")
.unwrap()
.try_into()
.unwrap();
let peer_public = PublicKey::from(peer_public_bytes);
let secret = StaticSecret::try_from(secret_bytes).unwrap();
let public = PublicKey::from(&secret);
info!(
"wg public key: {}",
general_purpose::STANDARD.encode(public)
);
let mut buf = [0; 1024];
let mut peers = HashSet::new();
let (bus_tx, _) = broadcast::channel(128);
while let Ok((len, addr)) = sock.recv_from(&mut buf).await {
info!("Received {} bytes from {}", len, addr);
if peers.contains(&addr) {
bus_tx
.send(Event::WgPacket(buf[..len].to_vec().into()))
.map_err(|e| error!("{e}"))
.unwrap();
} else {
info!("New peer with endpoint {addr}");
let tun =
WireGuardTunnel::new(peer_public, Arc::clone(&sock), addr, bus_tx.clone()).await;
peers.insert(addr);
tokio::spawn(tun.spin_off());
bus_tx
.send(Event::WgPacket(buf[..len].to_vec().into()))
.map_err(|e| error!("{e}"))
.unwrap();
}
}
panic!("Not OK");
}
+1 -1
View File
@@ -2,7 +2,7 @@
## [Unreleased]
## [v1.1.14] (2023-06-20)
## [v1.1.13] (2023-06-20)
- NymConnect - add sentry.io reporting ([#3421])
+17 -2
View File
@@ -3220,7 +3220,7 @@ dependencies = [
[[package]]
name = "nym-bin-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"atty",
"clap",
@@ -3235,7 +3235,7 @@ dependencies = [
[[package]]
name = "nym-client-core"
version = "1.1.14"
version = "1.1.15"
dependencies = [
"async-trait",
"base64 0.21.2",
@@ -3365,6 +3365,7 @@ dependencies = [
"nym-credential-storage",
"nym-crypto",
"nym-socks5-client-core",
"nym-sphinx",
"nym-task",
"pretty_env_logger",
"rand 0.8.5",
@@ -3735,6 +3736,7 @@ dependencies = [
"nym-pemstore",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -3760,6 +3762,7 @@ dependencies = [
"nym-crypto",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -3790,6 +3793,7 @@ dependencies = [
"nym-sphinx-chunking",
"nym-sphinx-forwarding",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -3906,6 +3910,7 @@ dependencies = [
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"openssl",
"prost",
"reqwest",
"serde",
@@ -4045,6 +4050,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.26.0+1.1.1u"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.82"
@@ -4054,6 +4068,7 @@ dependencies = [
"autocfg",
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/nym-connect",
"version": "1.1.14",
"version": "1.1.13",
"main": "index.js",
"license": "MIT",
"scripts": {
+1
View File
@@ -53,6 +53,7 @@ nym-crypto = { path = "../../../common/crypto" }
nym-credential-storage = { path = "../../../common/credential-storage" }
nym-bin-common = { path = "../../../common/bin-common"}
nym-socks5-client-core = { path = "../../../common/socks5-client-core" }
nym-sphinx = { path = "../../../common/nymsphinx" }
nym-task = { path = "../../../common/task" }
[dev-dependencies]
@@ -160,7 +160,11 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
config.core.base.client.nym_api_urls = nym_config_common::parse_urls(&raw_validators);
}
let gateway_setup = GatewaySetup::new_fresh(Some(chosen_gateway_id), None);
let gateway_setup = if register_gateway {
GatewaySetup::new_fresh(Some(chosen_gateway_id), None)
} else {
GatewaySetup::MustLoad
};
// Setup gateway by either registering a new one, or reusing exiting keys
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
@@ -25,6 +25,11 @@ mod tasks;
mod window;
fn main() {
if std::env::var("NYM_CONNECT_ENABLE_MEDIUM").is_ok() {
std::env::set_var("NYM_CONNECT_DISABLE_COVER", "1");
std::env::set_var("NYM_CONNECT_ENABLE_MIXED_SIZE_PACKETS", "1");
std::env::set_var("NYM_CONNECT_DISABLE_PER_HOP_DELAYS", "1");
}
setup_env(None);
println!("Starting up...");
+18 -1
View File
@@ -4,8 +4,10 @@ use nym_client_core::client::base_client::storage::{MixnetClientStorage, OnDiskP
use nym_client_core::{config::GatewayEndpointConfig, error::ClientCoreStatusMessage};
use nym_socks5_client_core::NymClient as Socks5NymClient;
use nym_socks5_client_core::Socks5ControlMessageSender;
use nym_sphinx::params::PacketSize;
use nym_task::manager::TaskStatus;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use tokio::sync::RwLock;
@@ -39,7 +41,7 @@ pub async fn start_nym_socks5_client(
GatewayEndpointConfig,
)> {
log::info!("Loading config from file: {id}");
let config = Config::read_from_default_path(id)
let mut config = Config::read_from_default_path(id)
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
let storage =
@@ -53,6 +55,21 @@ pub async fn start_nym_socks5_client(
.expect("failed to load gateway details")
.into();
// Disable both the loop cover traffic that runs in the background as well as the Poisson
// process that injects cover traffic into the traffic stream.
if std::env::var("NYM_CONNECT_DISABLE_COVER").is_ok() {
config.core.base.set_no_cover_traffic();
}
if std::env::var("NYM_CONNECT_ENABLE_MIXED_SIZE_PACKETS").is_ok() {
config.core.base.debug.traffic.secondary_packet_size = Some(PacketSize::ExtendedPacket16);
}
if std::env::var("NYM_CONNECT_DISABLE_PER_HOP_DELAY").is_ok() {
config.core.base.debug.traffic.average_packet_delay = Duration::ZERO;
config.core.base.debug.acknowledgements.average_ack_delay = Duration::ZERO;
}
log::info!("Starting socks5 client");
// Channel to send control messages to the socks5 client
+4 -1
View File
@@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21'
id "io.sentry.android.gradle" version "3.11.0"
}
android {
@@ -101,18 +102,20 @@ dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.5.1'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
implementation 'androidx.navigation:navigation-compose:2.6.0'
implementation 'androidx.compose.runtime:runtime-livedata'
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'androidx.datastore:datastore-preferences:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
@@ -17,6 +17,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Nyms5"
android:enableOnBackInvokedCallback="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
@@ -34,6 +35,20 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<!-- Sentry -->
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />
<!-- enable screenshot for crashes -->
<meta-data
android:name="io.sentry.attach-screenshot"
android:value="true" />
<!-- enable view hierarchy for crashes -->
<meta-data
android:name="io.sentry.attach-view-hierarchy"
android:value="true" />
</application>
</manifest>
@@ -1,9 +1,21 @@
package net.nymtech.nyms5
import android.app.Application
import android.content.Context
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.work.Configuration
import androidx.work.DelegatingWorkerFactory
import io.sentry.android.core.SentryAndroid
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val monitoringKey = booleanPreferencesKey("monitoring")
class App : Application(), Configuration.Provider {
companion object {
@@ -13,6 +25,30 @@ class App : Application(), Configuration.Provider {
private val tag = "App"
override fun onCreate() {
super.onCreate()
val app = this
runBlocking {
val monitoring = applicationContext.dataStore.data.map { preferences ->
preferences[monitoringKey] ?: false
}.first()
if (monitoring) {
Log.i(tag, "Performance monitoring and error reporting enabled")
SentryAndroid.init(app) { options ->
options.dsn =
"https://6872f5818bc147ef9c0fce114fcaac8a@o967446.ingest.sentry.io/4505306218102784"
options.enableAllAutoBreadcrumbs(true)
// TODO should be adjusted in production env
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
}
}
}
}
override fun getWorkManagerConfiguration(): Configuration {
val workerFactory = DelegatingWorkerFactory()
// pass in the NymProxy class instance
@@ -14,11 +14,21 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
@@ -34,6 +44,8 @@ import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import net.nymtech.nyms5.ui.theme.NymTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
@@ -42,8 +54,17 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
class MainActivity : ComponentActivity() {
private val tag = "MainActivity"
@@ -81,18 +102,16 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
Log.d(tag, "____uiState collect")
viewModel.uiState.collect {
setContent {
NymTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Log.d(tag, "____UI recompose")
applicationContext.dataStore.data.map { preferences ->
preferences[monitoringKey] ?: false
}.collect { monitoring ->
viewModel.uiState.collect {
setContent {
NymTheme {
val loading = it.loading
S5ClientSwitch(it.connected, loading, {
HomeScreen(it, monitoring, applicationContext.dataStore) {
if (!loading) {
when {
it -> {
@@ -106,7 +125,7 @@ class MainActivity : ComponentActivity() {
}
}
}
})
}
}
}
}
@@ -114,6 +133,90 @@ class MainActivity : ComponentActivity() {
}
}
}
override fun onStart() {
super.onStart()
viewModel.checkStateSync()
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(
proxyState: MainViewModel.ProxyState,
monitoring: Boolean,
dataStore: DataStore<Preferences>,
onSwitch: (value: Boolean) -> Unit,
) {
val navController = rememberNavController()
var expanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Scaffold(topBar = {
CenterAlignedTopAppBar(
title = {
Text(stringResource(R.string.app_name))
},
navigationIcon = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
if (currentRoute === "proxy") {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Main menu"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(onClick = {
navController.navigate("monitoring") {
popUpTo("proxy")
}
expanded = false
}, text = {
Text("Error reporting")
})
}
} else {
IconButton(onClick = {
navController.navigate("proxy") {
popUpTo("proxy")
}
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back home"
)
}
}
},
)
}) { contentPadding ->
NavHost(
navController = navController,
startDestination = "proxy",
modifier = Modifier.padding(contentPadding)
) {
composable("proxy") {
S5ClientSwitch(
connected = proxyState.connected,
loading = proxyState.loading,
onSwitch = onSwitch
)
}
composable("monitoring") {
Monitoring(initialValue = monitoring) {
scope.launch(Dispatchers.IO) {
dataStore.edit { settings -> settings[monitoringKey] = it }
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -198,6 +301,46 @@ fun S5ClientSwitch(
}
}
@Composable
fun Monitoring(
modifier: Modifier = Modifier,
initialValue: Boolean,
onSwitch: (value: Boolean) -> Unit,
) {
var monitoring by remember { mutableStateOf(initialValue) }
Column(
modifier = modifier
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text("Enable error reporting")
Spacer(modifier = modifier.width(16.dp))
Switch(checked = monitoring, onCheckedChange = {
monitoring = it
onSwitch(it)
})
}
Spacer(modifier = modifier.height(18.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(R.drawable.warning_24),
contentDescription = "copy to clipboard",
tint = Color.Yellow
)
Spacer(modifier = modifier.width(16.dp))
Text(stringResource(R.string.monitoring_desc_3), color = Color.Yellow)
}
Spacer(modifier = modifier.height(18.dp))
Text(stringResource(R.string.monitoring_desc_1))
Spacer(modifier = modifier.height(18.dp))
Text(stringResource(R.string.monitoring_desc_2))
}
}
@Preview
@Composable
fun PreviewSocks5Client() {
@@ -220,3 +363,18 @@ fun PreviewSocks5Client() {
}
}
}
@Preview
@Composable
fun PreviewMonitoring() {
NymTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Monitoring(initialValue = false) {
Log.d("Monitoring", "switch $it")
}
}
}
}
@@ -27,7 +27,7 @@ class MainViewModel(
private val workManager: WorkManager,
private val nymProxy: NymProxy
) : ViewModel() {
private val tag = "viewModel"
private val tag = "MainViewModel"
private val workRequest: OneTimeWorkRequest =
OneTimeWorkRequestBuilder<ProxyWorker>()
@@ -56,8 +56,7 @@ class MainViewModel(
// this viewModel instance is cleared
// use GlobalScope instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running ie. connected
// kill it
// if the proxy process is still running ie. connected kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.d(tag, "stopping proxy")
nymProxy.stop()
@@ -131,6 +130,29 @@ class MainViewModel(
setDisconnected()
}
}
fun checkStateSync() {
Log.d(tag, "check state sync")
viewModelScope.launch(Dispatchers.IO) {
val proxyState = nymProxy.getState()
val workInfo = workManager.getWorkInfoById(ProxyWorker.workId).get()
Log.d(tag, "proxy state $proxyState, work state ${workInfo?.state}")
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workInfo?.state != WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping proxy")
cancelProxyWork()
}
if (proxyState == NymProxy.Companion.State.DISCONNECTED &&
workInfo?.state == WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping worker")
workManager.cancelAllWorkByTag(ProxyWorker.workTag)
}
}
}
}
class MainViewModelFactory(
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>
@@ -11,4 +11,8 @@
<string name="sp_url">https://harbourmaster.nymtech.net/v1/services?size=100</string>
<string name="default_sp">DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh</string>
<string name="connected_text">Connected to the mixnet</string>
<string name="monitoring_desc_1">Help Nym developers to fix errors, crashes and improve the application by enabling this option. If errors occur or if the app crashes, it will automatically send a report. Also it tracks various performance metrics. We use sentry.io service to handle this.</string>
<string name="monitoring_desc_2">Note: A report can include your external IP, this can be useful to catch issues related to IP location.
All recorded data is used by Nym developers and for app development purposes only.</string>
<string name="monitoring_desc_3">You must restart the application for the change to take effect.</string>
</resources>
@@ -0,0 +1,3 @@
defaults.project=nym-connect-android
defaults.org=nymtech
# auth.token=xxx
+3 -1
View File
@@ -9,9 +9,11 @@ pub const DEFAULT_HOPS: usize = 4;
pub const ROUTING_INFORMATION_LENGTH_BY_STAGE: [u8; DEFAULT_HOPS] =
[DEFAULT_ROUTING_INFO_SIZE; DEFAULT_HOPS];
pub const MIN_PACKET_SIZE: usize = 48;
pub const MAGIC_SLICE: &[u8] = &[111, 102, 120];
pub const OUTFOX_PACKET_OVERHEAD: usize = MIX_PARAMS_LEN
+ (groupelementbytes() + tagbytes() + DEFAULT_ROUTING_INFO_SIZE as usize) * DEFAULT_HOPS;
+ (groupelementbytes() + tagbytes() + DEFAULT_ROUTING_INFO_SIZE as usize) * DEFAULT_HOPS
+ MAGIC_SLICE.len();
pub const fn groupelementbytes() -> usize {
GROUPELEMENTBYTES as usize
+3
View File
@@ -1,3 +1,4 @@
use crate::constants::MAGIC_SLICE;
use crate::constants::MIN_MESSAGE_LEN;
use crate::constants::MIX_PARAMS_LEN;
use chacha20::cipher::InvalidLength;
@@ -25,4 +26,6 @@ pub enum OutfoxError {
},
#[error("Header length must be {MIX_PARAMS_LEN}, got {0}")]
InvalidHeaderLength(usize),
#[error("Invalid magic bytes, expected: {:?}, got: {:?}", MAGIC_SLICE, 0)]
InvalidMagicBytes(Vec<u8>),
}
+15 -11
View File
@@ -7,7 +7,7 @@ use std::{
};
use crate::{
constants::{DEFAULT_HOPS, MIN_PACKET_SIZE, MIX_PARAMS_LEN},
constants::{DEFAULT_HOPS, MAGIC_SLICE, MIN_PACKET_SIZE, MIX_PARAMS_LEN},
error::OutfoxError,
format::{MixCreationParameters, MixStageParameters},
};
@@ -60,16 +60,19 @@ impl TryFrom<&[u8]> for OutfoxPacket {
}
impl OutfoxPacket {
pub fn recover_plaintext(&self) -> Vec<u8> {
pub fn recover_plaintext(&self) -> Result<Vec<u8>, OutfoxError> {
let plaintext = self.payload()[self.payload_range()].to_vec();
if plaintext.starts_with(&[0]) {
let mut plaintext = VecDeque::from_iter(plaintext);
while let Some(0) = plaintext.front() {
plaintext.pop_front();
}
return plaintext.make_contiguous().to_vec();
let mut plaintext = VecDeque::from_iter(plaintext);
while let Some(0) = plaintext.front() {
plaintext.pop_front();
}
let mut plaintext = plaintext.make_contiguous().to_vec();
let payload = plaintext.split_off(MAGIC_SLICE.len());
if plaintext != MAGIC_SLICE {
Err(OutfoxError::InvalidMagicBytes(plaintext))
} else {
Ok(payload)
}
plaintext
}
pub fn len(&self) -> usize {
@@ -100,11 +103,12 @@ impl OutfoxPacket {
MIN_PACKET_SIZE
} else {
packet_size
};
} + MAGIC_SLICE.len();
let mix_params = MixCreationParameters::new(packet_size as u16);
let padding = mix_params.total_packet_length() - payload.as_ref().len();
let padding = mix_params.total_packet_length() - payload.as_ref().len() - MAGIC_SLICE.len();
let mut buffer = vec![0; padding];
buffer.extend_from_slice(MAGIC_SLICE);
buffer.extend_from_slice(payload.as_ref());
// Last node in the route is a gateway, it will decrypt last, and get the final destination address
+59 -3
View File
@@ -88,7 +88,7 @@ mod tests {
}
#[test]
fn test_packet_params() {
fn test_packet_params_short() {
let (node1_pk, node1_pub) = sphinx_packet::crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
@@ -118,7 +118,7 @@ mod tests {
let route = [node1, node2.clone(), node3.clone(), gateway.clone()];
let payload = randombytes(21);
let payload = vec![0, 0, 1, 1, 1, 0, 0];
let packet =
OutfoxPacket::build(&payload, &route, &destination, Some(payload.len())).unwrap();
@@ -140,6 +140,62 @@ mod tests {
let destination_address = packet.decode_next_layer(&gateway_pk).unwrap();
assert_eq!(destination_address, destination.address.as_bytes());
assert_eq!(payload, packet.recover_plaintext());
assert_eq!(payload, packet.recover_plaintext().unwrap());
}
#[test]
fn test_packet_params_long() {
let (node1_pk, node1_pub) = sphinx_packet::crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = sphinx_packet::crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = sphinx_packet::crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let (gateway_pk, gateway_pub) = sphinx_packet::crypto::keygen();
let gateway = Node::new(
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
gateway_pub,
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let route = [node1, node2.clone(), node3.clone(), gateway.clone()];
let payload = randombytes(2048);
let packet =
OutfoxPacket::build(&payload, &route, &destination, Some(payload.len())).unwrap();
let packet_bytes = packet.to_bytes().unwrap();
println!(
"packet bytes length, {}, declared {}",
packet_bytes.len(),
packet.len()
);
let mut packet = OutfoxPacket::try_from(packet_bytes.as_slice()).unwrap();
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
assert_eq!(next_address, node2.address.as_bytes());
let next_address = packet.decode_next_layer(&node2_pk).unwrap();
assert_eq!(next_address, node3.address.as_bytes());
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
assert_eq!(next_address, gateway.address.as_bytes());
let destination_address = packet.decode_next_layer(&gateway_pk).unwrap();
assert_eq!(destination_address, destination.address.as_bytes());
assert_eq!(payload, packet.recover_plaintext().unwrap());
}
}
+13 -2
View File
@@ -2915,7 +2915,7 @@ dependencies = [
[[package]]
name = "nym-bin-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"atty",
"clap",
@@ -3220,6 +3220,7 @@ dependencies = [
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"openssl",
"prost",
"reqwest",
"serde",
@@ -3297,7 +3298,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.2.4"
version = "1.2.5"
dependencies = [
"async-trait",
"base64 0.13.1",
@@ -3447,6 +3448,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.26.0+1.1.1u"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.80"
@@ -3456,6 +3466,7 @@ dependencies = [
"autocfg",
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@@ -0,0 +1,5 @@
{
"name": "@nymproject/nym-client-wasm",
"version": "1.0.0",
"sideEffects": false
}
+1
View File
@@ -24,6 +24,7 @@
"prebuild": "yarn build:dependencies",
"build": "yarn build:only-this",
"build:only-this": "scripts/build-prod.sh",
"prebuild:dev": "yarn build:dependencies",
"build:dev": "yarn build:dev:only-this",
"build:dev:only-this": "scripts/build.sh"
},
@@ -0,0 +1,15 @@
use nym_cli_commands::context::ClientArgs;
use nym_network_defaults::NymNetworkDetails;
pub(crate) async fn execute(
_global_args: ClientArgs,
identity_key: nym_cli_commands::validator::mixnet::operators::identity_key::MixnetOperatorsIdentityKey,
_network_details: &NymNetworkDetails,
) -> anyhow::Result<()> {
let res = match identity_key.command {
nym_cli_commands::validator::mixnet::operators::identity_key::MixnetOperatorsIdentityKeyCommands::Sign(sign_args) => {
nym_cli_commands::validator::mixnet::operators::identity_key::sign(sign_args).await
}
};
Ok(res?)
}
@@ -5,6 +5,7 @@ use nym_cli_commands::context::ClientArgs;
use nym_network_defaults::NymNetworkDetails;
pub(crate) mod gateways;
pub(crate) mod identity_key;
pub(crate) mod mixnodes;
pub(crate) mod name;
pub(crate) mod services;
@@ -23,5 +24,6 @@ pub(crate) async fn execute(
) => mixnodes::execute(global_args, mixnode, network_details).await,
nym_cli_commands::validator::mixnet::operators::MixnetOperatorsCommands::ServiceProvider(service) => services::execute(global_args, service, network_details).await,
nym_cli_commands::validator::mixnet::operators::MixnetOperatorsCommands::Name(name) => name::execute(global_args, name, network_details).await,
nym_cli_commands::validator::mixnet::operators::MixnetOperatorsCommands::IdentityKey(identity_key) => identity_key::execute(global_args, identity_key, network_details).await,
}
}