Compare commits

...

16 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
41 changed files with 2091 additions and 294 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",
]
+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 -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" }
+2 -2
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;
@@ -120,7 +120,7 @@ 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();
let first_hop_address =
+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.
+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");
}
+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
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() {
}
}
}
})
}
}
}
}
@@ -121,6 +140,85 @@ class MainActivity : ComponentActivity() {
}
}
@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)
@Composable
fun S5ClientSwitch(
@@ -203,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() {
@@ -225,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>()
@@ -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
+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
}
@@ -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,
}
}