-
Notifications
You must be signed in to change notification settings - Fork 262
Socks5 in GW probe #6298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Socks5 in GW probe #6298
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,10 @@ use std::{ | |
| time::Duration, | ||
| }; | ||
|
|
||
| use crate::{netstack::NetstackResult, types::Entry}; | ||
| use crate::{ | ||
| netstack::NetstackResult, | ||
| types::{Entry, HttpsConnectivityTest}, | ||
| }; | ||
| use anyhow::bail; | ||
| use base64::{Engine as _, engine::general_purpose}; | ||
| use bytes::BytesMut; | ||
|
|
@@ -36,8 +39,9 @@ use nym_ip_packet_requests::{ | |
| }; | ||
| use nym_sdk::mixnet::{ | ||
| CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, | ||
| NodeIdentity, Recipient, ReconstructedMessage, StoragePaths, | ||
| NodeIdentity, Recipient, ReconstructedMessage, Socks5, StoragePaths, | ||
| }; | ||
| use nym_validator_client::models::NetworkRequesterDetails; | ||
| use rand::rngs::OsRng; | ||
| use std::path::PathBuf; | ||
|
|
||
|
|
@@ -48,7 +52,7 @@ use url::Url; | |
|
|
||
| use crate::{ | ||
| icmp::{check_for_icmp_beacon_reply, icmp_identifier, send_ping_v4, send_ping_v6}, | ||
| types::Exit, | ||
| types::{Exit, HttpsConnectivityResult, Socks5ProbeResults}, | ||
| }; | ||
|
|
||
| use netstack::{NetstackRequest, NetstackRequestGo}; | ||
|
|
@@ -124,6 +128,15 @@ impl CredentialArgs { | |
| } | ||
| } | ||
|
|
||
| #[derive(Args)] | ||
| pub struct Socks5Args { | ||
| #[arg(long, default_value_t = 45)] | ||
| mixnet_client_timeout_sec: u64, | ||
|
|
||
| #[arg(long, default_value_t = 10)] | ||
| test_count: u64, | ||
| } | ||
|
|
||
| #[derive(Default, Debug)] | ||
| pub enum TestedNode { | ||
| #[default] | ||
|
|
@@ -151,6 +164,7 @@ impl TestedNode { | |
| pub struct TestedNodeDetails { | ||
| identity: NodeIdentity, | ||
| exit_router_address: Option<Recipient>, | ||
| network_requester_details: Option<NetworkRequesterDetails>, | ||
| authenticator_address: Option<Recipient>, | ||
| authenticator_version: AuthenticatorVersion, | ||
| ip_address: Option<IpAddr>, | ||
|
|
@@ -162,6 +176,7 @@ pub struct Probe { | |
| amnezia_args: String, | ||
| netstack_args: NetstackArgs, | ||
| credentials_args: CredentialArgs, | ||
| socks5_args: Socks5Args, | ||
| } | ||
|
|
||
| impl Probe { | ||
|
|
@@ -170,20 +185,74 @@ impl Probe { | |
| tested_node: TestedNode, | ||
| netstack_args: NetstackArgs, | ||
| credentials_args: CredentialArgs, | ||
| socks5_args: Socks5Args, | ||
| ) -> Self { | ||
| Self { | ||
| entrypoint, | ||
| tested_node, | ||
| amnezia_args: "".into(), | ||
| netstack_args, | ||
| credentials_args, | ||
| socks5_args, | ||
| } | ||
| } | ||
| pub fn with_amnezia(&mut self, args: &str) -> &Self { | ||
| self.amnezia_args = args.to_string(); | ||
| self | ||
| } | ||
|
|
||
| pub async fn test_socks5_only( | ||
| self, | ||
| directory: NymApiDirectory, | ||
| gateway_key: Option<String>, | ||
| network_details: NymNetworkDetails, | ||
| ) -> anyhow::Result<ProbeResult> { | ||
| let exit_gateway = match gateway_key { | ||
| Some(gateway_key) => NodeIdentity::from_base58_string(gateway_key)?, | ||
| None => directory.random_exit_with_nr()?, | ||
| }; | ||
| info!("Testing SOCKS5 only on exit gateway {}", exit_gateway); | ||
| let node_info = directory | ||
| .exit_gateway_nr(&exit_gateway)? | ||
| .to_testable_node()?; | ||
|
|
||
| let socks5_outcome = { | ||
| if let Some(ref nr_details) = node_info.network_requester_details { | ||
| match do_socks5_connectivity_test( | ||
| &nr_details.address, | ||
| network_details, | ||
| directory, | ||
| self.socks5_args.mixnet_client_timeout_sec, | ||
| self.socks5_args.test_count, | ||
| ) | ||
| .await | ||
| { | ||
| Ok(results) => Some(results), | ||
| Err(e) => { | ||
| error!("SOCKS5 test failed: {}", e); | ||
| None | ||
| } | ||
| } | ||
| } else { | ||
| info!("No NR available, skipping SOCKS5 tests"); | ||
| None | ||
| } | ||
| }; | ||
|
|
||
| let probe_result = ProbeResult { | ||
| node: exit_gateway.to_base58_string(), | ||
| used_entry: exit_gateway.to_base58_string(), | ||
| outcome: ProbeOutcome { | ||
| as_entry: Entry::NotTested, | ||
| as_exit: None, | ||
| socks5: socks5_outcome, | ||
| wg: None, | ||
| }, | ||
| }; | ||
|
|
||
| Ok(probe_result) | ||
| } | ||
|
|
||
| pub async fn probe( | ||
| self, | ||
| directory: NymApiDirectory, | ||
|
|
@@ -225,6 +294,7 @@ impl Probe { | |
| nyxd_url, | ||
| tested_entry, | ||
| only_wireguard, | ||
| directory, | ||
| ) | ||
| .await | ||
| } | ||
|
|
@@ -305,6 +375,7 @@ impl Probe { | |
| nyxd_url, | ||
| tested_entry, | ||
| only_wireguard, | ||
| directory, | ||
| ) | ||
| .await | ||
| } | ||
|
|
@@ -361,6 +432,7 @@ impl Probe { | |
| nyxd_url: Url, | ||
| tested_entry: bool, | ||
| only_wireguard: bool, | ||
| directory: NymApiDirectory, | ||
| ) -> anyhow::Result<ProbeResult> | ||
| where | ||
| T: MixnetClientStorage + Clone + 'static, | ||
|
|
@@ -381,6 +453,7 @@ impl Probe { | |
| Entry::EntryFailure | ||
| }, | ||
| as_exit: None, | ||
| socks5: None, | ||
| wg: None, | ||
| }, | ||
| }); | ||
|
|
@@ -403,6 +476,7 @@ impl Probe { | |
| Entry::NotTested | ||
| }, | ||
| as_exit: None, | ||
| socks5: None, | ||
| wg: None, | ||
| }), | ||
| mixnet_client, | ||
|
|
@@ -475,9 +549,34 @@ impl Probe { | |
| WgProbeResults::default() | ||
| }; | ||
|
|
||
| // test failure doesn't stop further tests | ||
| let socks5_outcome = { | ||
| if let Some(ref nr_details) = node_info.network_requester_details { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you have exactly the same match logic in multiple places |
||
| match do_socks5_connectivity_test( | ||
| &nr_details.address, | ||
| NymNetworkDetails::new_from_env(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if env is not set? |
||
| directory, | ||
| self.socks5_args.mixnet_client_timeout_sec, | ||
| self.socks5_args.test_count, | ||
| ) | ||
| .await | ||
| { | ||
| Ok(results) => Some(results), | ||
| Err(e) => { | ||
| error!("SOCKS5 test failed: {}", e); | ||
| None | ||
| } | ||
| } | ||
| } else { | ||
| info!("No NR available, skipping SOCKS5 tests"); | ||
| None | ||
| } | ||
| }; | ||
|
|
||
| // Disconnect the mixnet client gracefully | ||
| outcome.map(|mut outcome| { | ||
| outcome.wg = Some(wg_outcome); | ||
| outcome.socks5 = socks5_outcome; | ||
| ProbeResult { | ||
| node: node_info.identity.to_string(), | ||
| used_entry: mixnet_entry_gateway_id.to_string(), | ||
|
|
@@ -711,6 +810,7 @@ async fn do_ping( | |
| exit_result.map(|exit| ProbeOutcome { | ||
| as_entry: entry, | ||
| as_exit: exit, | ||
| socks5: None, | ||
| wg: None, | ||
| }), | ||
| mixnet_client, | ||
|
|
@@ -775,6 +875,109 @@ async fn do_ping_exit( | |
| listen_for_icmp_ping_replies(mixnet_client, our_ips).await | ||
| } | ||
|
|
||
| /// Creates a SOCKS5 proxy connection through the mixnet to the exit GW | ||
| /// and performs necessary tests. | ||
| #[instrument(level = "info", name = "socks5_test", skip_all)] | ||
| async fn do_socks5_connectivity_test( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should still investigate just creating a single client and sending NR packets to exit gateways rather than starting local socks5 listeners. |
||
| network_requester_address: &str, | ||
| network_details: NymNetworkDetails, | ||
| directory: NymApiDirectory, | ||
| mixnet_client_timeout: u64, | ||
| test_run_count: u64, | ||
| ) -> anyhow::Result<Socks5ProbeResults> { | ||
| info!( | ||
| "Starting SOCKS5 test through Network Requester: {}", | ||
| network_requester_address | ||
| ); | ||
|
|
||
| let mut results = Socks5ProbeResults::default(); | ||
|
|
||
| // parse the network requester address | ||
| let nr_recipient = match network_requester_address.parse::<Recipient>() { | ||
| Ok(addr) => addr, | ||
| Err(e) => { | ||
| error!("Invalid Network Requester address: {}", e); | ||
| results.https_connectivity = | ||
| HttpsConnectivityResult::with_error(format!("Invalid NR address: {}", e)); | ||
| return Ok(results); | ||
| } | ||
| }; | ||
|
|
||
| info!( | ||
| "Network Requester gateway: {}", | ||
| nr_recipient.gateway().to_base58_string() | ||
| ); | ||
| info!( | ||
| "Network Requester identity: {}", | ||
| nr_recipient.identity().to_base58_string() | ||
| ); | ||
|
|
||
| // create ephemeral SOCKS5 client | ||
| let socks5_config = Socks5::new(network_requester_address.to_string()); | ||
|
|
||
| // Create debug config similar to main probe | ||
| let mut debug_config = nym_client_core::config::DebugConfig::default(); | ||
| debug_config | ||
| .traffic | ||
| .disable_main_poisson_packet_distribution = true; | ||
| debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; | ||
| debug_config.topology.ignore_egress_epoch_role = true; | ||
| // since we define both entry & exit gateways to be the same tested GW, | ||
| // this shouldn't negatively affect mixnet layers but it will force route | ||
| // construction in case GW would get filtered out on topology refresh | ||
| debug_config.topology.minimum_gateway_performance = 0; | ||
|
|
||
| // Verify the NR gateway exists in the directory with exit_nr role | ||
| let nr_gateway_id = nr_recipient.gateway(); | ||
| if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) { | ||
| results.https_connectivity = HttpsConnectivityResult::with_error(e.to_string()); | ||
| return Ok(results); | ||
| } else { | ||
| info!("✔️ Network Requester gateway found in directory with exit_nr role"); | ||
| } | ||
|
|
||
| // use intended exit as entry as well | ||
| let entry_gateway = nr_gateway_id; | ||
|
|
||
| let socks5_client_builder = MixnetClientBuilder::new_ephemeral() | ||
| // Specify entry gateway explicitly | ||
| .request_gateway(entry_gateway.to_base58_string()) | ||
| .socks5_config(socks5_config) | ||
| .network_details(network_details) | ||
| .debug_config(debug_config) | ||
| .build()?; | ||
|
|
||
| // connect to mixnet via SOCKS5 | ||
| let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await { | ||
| Ok(client) => { | ||
| info!("Successfully established SOCKS5 proxy connection"); | ||
| info!( | ||
| "Connected via entry gateway: {}", | ||
| client.nym_address().gateway().to_base58_string() | ||
| ); | ||
| results.can_connect_socks5 = true; | ||
| client | ||
| } | ||
| Err(e) => { | ||
| error!("Failed to establish SOCKS5 connection: {}", e); | ||
| results.https_connectivity = | ||
| HttpsConnectivityResult::with_error(format!("SOCKS5 connection failed: {}", e)); | ||
| return Ok(results); | ||
| } | ||
| }; | ||
|
|
||
| info!("Waiting for network topology to be ready..."); | ||
| tokio::time::sleep(Duration::from_secs(10)).await; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. couldn't we wait for some condition rather than always wait 10s? what if topology takes longer than that? (or way less than that)? |
||
|
|
||
| let test = HttpsConnectivityTest::new(test_run_count, mixnet_client_timeout); | ||
| results.https_connectivity = test.run_tests(socks5_client.socks5_url()).await; | ||
|
|
||
| // cleanup | ||
| socks5_client.disconnect().await; | ||
|
|
||
| Ok(results) | ||
| } | ||
|
|
||
| async fn send_icmp_pings( | ||
| mixnet_client: &MixnetClient, | ||
| our_ips: IpPair, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the point of testing a random gateway for socks5?