diff --git a/Cargo.lock b/Cargo.lock index 7ee5e64e6..bf3df3dbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,6 +339,7 @@ version = "0.1.11" name = "althea_types" version = "0.1.0" dependencies = [ + "althea_kernel_interface", "arrayvec", "babel_monitor", "base64 0.13.1", diff --git a/althea_types/Cargo.toml b/althea_types/Cargo.toml index 93d5226bf..25d191a47 100644 --- a/althea_types/Cargo.toml +++ b/althea_types/Cargo.toml @@ -24,3 +24,4 @@ deep_space = { workspace = true } [dev-dependencies] rand = "0.8" +althea_kernel_interface = { path = "../althea_kernel_interface" } diff --git a/althea_types/src/contact_info.rs b/althea_types/src/contact_info.rs index 48ef7d68d..14dcb29f1 100644 --- a/althea_types/src/contact_info.rs +++ b/althea_types/src/contact_info.rs @@ -3,7 +3,7 @@ //! we need to convert said enum between different types left and right. Both to migrate from the old storage, to handle the fact that TOML refuses //! to serialize enums with struct members. This file is all boilerplate conversion code for pretty small storage formats. -use crate::ExitRegistrationDetails; +use crate::exit_interop::ExitRegistrationDetails; use lettre::Address as EmailAddress; use phonenumber::PhoneNumber; diff --git a/althea_types/src/exit_interop.rs b/althea_types/src/exit_interop.rs new file mode 100644 index 000000000..e96efccfd --- /dev/null +++ b/althea_types/src/exit_interop.rs @@ -0,0 +1,428 @@ +use crate::interop::default_system_chain; +use crate::regions::Regions; +use crate::wg_key::WgKey; +use crate::{Identity, SystemChain}; +use clarity::Address; +use ipnetwork::IpNetwork; +use serde::Deserialize; +use sodiumoxide::crypto::box_::{self, Nonce, PublicKey}; +use std::collections::HashSet; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; +use std::net::IpAddr; +use std::string::FromUtf8Error; +use std::time::SystemTime; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExitIdentity { + pub mesh_ip: IpAddr, + pub wg_key: WgKey, + pub eth_addr: Address, + // The port the client uses to query exit endpoints + pub registration_port: u16, + // The port the clients uses for exit wg tunnel setup + pub wg_exit_listen_port: u16, + pub allowed_regions: HashSet, + pub payment_types: HashSet, +} + +// Custom hash implementation that also ignores nickname. There should be no collding exits with +// the same mesh, wgkey and ethaddr +impl Hash for ExitIdentity { + fn hash(&self, state: &mut H) { + self.mesh_ip.hash(state); + self.eth_addr.hash(state); + self.wg_key.hash(state); + } +} + +pub fn exit_identity_to_id(exit_id: ExitIdentity) -> Identity { + Identity { + mesh_ip: exit_id.mesh_ip, + eth_address: exit_id.eth_addr, + wg_public_key: exit_id.wg_key, + nickname: None, + } +} + +/// An exit's unix time stamp that can be queried by a downstream router +/// Many routers have no built in clock and need to set their time at boot +/// in order for wireguard tunnels to work correctly +#[derive(Debug, Serialize, Deserialize)] +pub struct ExitSystemTime { + pub system_time: SystemTime, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Default)] +pub struct ExitRegistrationDetails { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub email: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub email_code: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub phone: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub phone_code: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub sequence_number: Option, +} + +/// This is the state an exit can be in +#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] +#[serde(tag = "state")] +pub enum ExitState { + /// the default state of the struct in the config + #[default] + New, + /// we have successfully contacted the exit and gotten basic info. This is + /// kept around for backwards compatitbility, it should be removed once all clients are + /// updated + GotInfo { + general_details: ExitDetails, + message: String, + }, + /// We are awaiting user action to enter the phone or email code + Pending { + general_details: ExitDetails, + message: String, + #[serde(default)] + email_code: Option, + phone_code: Option, + }, + /// we are currently registered and operating, update this state + /// incase the exit for example wants to assign us a new ip + Registered { + general_details: ExitDetails, + our_details: ExitClientDetails, + message: String, + }, + /// we have been denied + Denied { message: String }, +} + +impl ExitState { + pub fn general_details(&self) -> Option<&ExitDetails> { + match *self { + ExitState::GotInfo { + ref general_details, + .. + } => Some(general_details), + ExitState::Pending { + ref general_details, + .. + } => Some(general_details), + ExitState::Registered { + ref general_details, + .. + } => Some(general_details), + _ => None, + } + } + + pub fn our_details(&self) -> Option<&ExitClientDetails> { + match *self { + ExitState::Registered { + ref our_details, .. + } => Some(our_details), + _ => None, + } + } + + pub fn message(&self) -> String { + match *self { + ExitState::New => "New exit".to_string(), + ExitState::GotInfo { ref message, .. } => message.clone(), + ExitState::Pending { ref message, .. } => message.clone(), + ExitState::Registered { ref message, .. } => message.clone(), + ExitState::Denied { ref message, .. } => message.clone(), + } + } +} + +/// This is all the data we need to send to an exit, for our registration or update +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct ExitRegistrationIdentity { + pub wg_port: u16, + pub global: Identity, + pub reg_details: ExitRegistrationDetails, +} + +/// Wrapper for secure box containing an exit client identity +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct EncryptedExitRegistrationIdentity { + pub pubkey: WgKey, + pub nonce: [u8; 24], + pub encrypted_exit_client_id: Vec, +} + +/// A simple mesasge wrapper for authentication only, wrapping a WgKey type +/// this is for requests to the exit where the responses should be authenticated +/// but that do not require any other request payload +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct EncryptedAuthMessage { + pub pubkey: WgKey, + pub nonce: [u8; 24], + pub encrypted_wg_key: Vec, +} + +/// Wrapper for secure box containing an exit state +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct EncryptedExitState { + pub nonce: [u8; 24], + pub encrypted_exit_state: Vec, +} + +/// Wrapper for secure box containing a list of ips +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct EncryptedExitList { + pub nonce: [u8; 24], + pub exit_list: Vec, +} + +/// Struct returned when hitting exit_list endpoint +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct ExitList { + pub exit_list: Vec, + // All exits in a cluster listen on same port + pub wg_exit_listen_port: u16, +} + +/// Struct returned when hitting exit_list_V2 endpoint +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct ExitListV2 { + pub exit_list: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ExitVerifMode { + Phone, + Email, + Off, +} + +fn default_verif_mode() -> ExitVerifMode { + ExitVerifMode::Off +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +pub struct ExitDetails { + pub server_mesh_ip: IpAddr, + pub server_wg_pubkey: WgKey, + pub server_internal_ip: IpAddr, + pub netmask: u8, + pub wg_exit_port: u16, + pub exit_price: u64, + #[serde(default = "default_system_chain")] + pub exit_currency: SystemChain, + pub description: String, + #[serde(default = "default_verif_mode")] + pub verif_mode: ExitVerifMode, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] +pub struct ExitClientDetails { + pub client_internal_ip: IpAddr, + pub internet_ipv6_subnet: Option, +} + +/// Operator update that we get from the operator server during our checkin +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct OperatorExitUpdateMessage { + /// List of routers for this exit to register + pub to_register: Vec, +} + +#[derive(Debug)] +pub enum ExitMessageEncryptionError { + FromUtf8Error(FromUtf8Error), + SerdeJsonError(serde_json::Error), + DecryptionError, +} + +impl Display for ExitMessageEncryptionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExitMessageEncryptionError::FromUtf8Error(e) => write!(f, "FromUtf8Error: {}", e), + ExitMessageEncryptionError::SerdeJsonError(e) => write!(f, "SerdeJsonError: {}", e), + ExitMessageEncryptionError::DecryptionError => write!(f, "DecryptionError"), + } + } +} + +impl From for ExitMessageEncryptionError { + fn from(error: FromUtf8Error) -> Self { + ExitMessageEncryptionError::FromUtf8Error(error) + } +} + +impl From for ExitMessageEncryptionError { + fn from(error: serde_json::Error) -> Self { + ExitMessageEncryptionError::SerdeJsonError(error) + } +} + +/// Decrypts an encrypted exit list +pub fn decrypt_exit_list( + exit_list: EncryptedExitList, + exit_pubkey: PublicKey, + our_secretkey: &box_::SecretKey, +) -> Result { + let ciphertext = exit_list.exit_list; + let nonce = Nonce(exit_list.nonce); + let ret: ExitListV2 = match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { + Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { + Ok(json_string) => match serde_json::from_str(&json_string) { + Ok(ip_list) => ip_list, + Err(e) => { + return Err(e.into()); + } + }, + Err(e) => { + return Err(e.into()); + } + }, + Err(_) => { + return Err(ExitMessageEncryptionError::DecryptionError); + } + }; + Ok(ret) +} +/// Decrypts an encrypted exit state +pub fn decrypt_exit_state( + exit_state: EncryptedExitState, + exit_pubkey: PublicKey, + our_secretkey: &box_::SecretKey, +) -> Result { + let ciphertext = exit_state.encrypted_exit_state; + let nonce = Nonce(exit_state.nonce); + let decrypted_exit_state: ExitState = + match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { + Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { + Ok(json_string) => match serde_json::from_str(&json_string) { + Ok(exit_state) => exit_state, + Err(e) => { + return Err(ExitMessageEncryptionError::from(e)); + } + }, + Err(e) => { + return Err(ExitMessageEncryptionError::from(e)); + } + }, + Err(_) => { + return Err(ExitMessageEncryptionError::DecryptionError); + } + }; + Ok(decrypted_exit_state) +} + +pub fn encrypt_exit_registration_id( + exit_pubkey: &PublicKey, + id: ExitRegistrationIdentity, + our_secretkey: &box_::SecretKey, + our_wg_pubkey: WgKey, +) -> EncryptedExitRegistrationIdentity { + let plaintext = serde_json::to_string(&id) + .expect("Failed to serialize ExitState!") + .into_bytes(); + let nonce = box_::gen_nonce(); + let ciphertext = box_::seal(&plaintext, &nonce, exit_pubkey, &our_secretkey); + + EncryptedExitRegistrationIdentity { + nonce: nonce.0, + pubkey: our_wg_pubkey, + encrypted_exit_client_id: ciphertext, + } +} + +pub fn decrypt_exit_registration_id( + val: EncryptedExitRegistrationIdentity, + our_secretkey: &box_::SecretKey, +) -> Result { + let their_nacl_pubkey = val.pubkey.into(); + let their_nonce = Nonce(val.nonce); + let ciphertext = val.encrypted_exit_client_id; + + let decrypted_bytes = + match box_::open(&ciphertext, &their_nonce, &their_nacl_pubkey, our_secretkey) { + Ok(value) => value, + Err(_) => { + return Err(ExitMessageEncryptionError::DecryptionError); + } + }; + + let decrypted_string = match String::from_utf8(decrypted_bytes) { + Ok(value) => value, + Err(_) => { + return Err(ExitMessageEncryptionError::DecryptionError); + } + }; + + let decrypted_id = match serde_json::from_str(&decrypted_string) { + Ok(value) => value, + Err(_) => { + return Err(ExitMessageEncryptionError::DecryptionError); + } + }; + + Ok(decrypted_id) +} +#[cfg(test)] +mod tests { + use super::*; + use althea_kernel_interface::KI; + use phonenumber::country::Id::KI; + + /// generates a random identity, never use in production, your money will be stolen + fn random_identity() -> Identity { + use clarity::PrivateKey; + + let secret: [u8; 32] = rand::random(); + let mut ip: [u8; 16] = [0; 16]; + ip.copy_from_slice(&secret[0..16]); + + // the starting location of the funds + let eth_key = PrivateKey::from_bytes(secret).unwrap(); + let eth_address = eth_key.to_address(); + + Identity { + mesh_ip: ip.into(), + eth_address, + wg_public_key: secret.into(), + nickname: None, + } + } + + #[test] + fn test_encrypt_decrypt_exit_registration_id() { + let our_identity = random_identity(); + let our_wg = KI.create_wg_keypair().unwrap(); + let their_wg = KI.create_wg_keypair().unwrap(); + + + + // Create a sample ExitRegistrationIdentity + let exit_registration_id = ExitRegistrationIdentity { + wg_port: 55, + global: random_identity(), + reg_details: todo!(), + }; + + // Generate key pair for encryption + let (our_public_key, our_secret_key) = box_::gen_keypair(); + + // Encrypt the ExitRegistrationIdentity + let encrypted_exit_registration_id = encrypt_exit_registration_id( + &our_public_key, + exit_registration_id.clone(), + &our_secret_key, + test, + ); + + // Decrypt the encrypted ExitRegistrationIdentity + let decrypted_exit_registration_id = + decrypt_exit_registration_id(encrypted_exit_registration_id, &our_secret_key).unwrap(); + + // Check if the decrypted ExitRegistrationIdentity matches the original one + assert_eq!(exit_registration_id, decrypted_exit_registration_id); + } +} diff --git a/althea_types/src/interop.rs b/althea_types/src/interop.rs index 4c8ee148b..2f44ef2b1 100644 --- a/althea_types/src/interop.rs +++ b/althea_types/src/interop.rs @@ -1,4 +1,3 @@ -use crate::regions::Regions; use crate::{contact_info::ContactType, wg_key::WgKey, BillingDetails, InstallationDetails}; use crate::{ClientExtender, UsageTrackerFlat, UsageTrackerTransfer, WifiDevice}; use arrayvec::ArrayString; @@ -11,14 +10,12 @@ use num256::Uint256; use serde::de::Error; use serde::{Deserialize, Deserializer, Serializer}; use std::collections::hash_map::DefaultHasher; -use std::collections::HashSet; use std::fmt; use std::fmt::Display; use std::hash::{Hash, Hasher}; use std::net::IpAddr; -use std::net::Ipv4Addr; use std::str::FromStr; -use std::time::{Duration, SystemTime}; +use std::time::Duration; /// This is how nodes are identified. #[derive(Debug, Serialize, Deserialize, Clone, Copy)] @@ -112,38 +109,6 @@ impl Hash for Identity { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct ExitIdentity { - pub mesh_ip: IpAddr, - pub wg_key: WgKey, - pub eth_addr: Address, - // The port the client uses to query exit endpoints - pub registration_port: u16, - // The port the clients uses for exit wg tunnel setup - pub wg_exit_listen_port: u16, - pub allowed_regions: HashSet, - pub payment_types: HashSet, -} - -// Custom hash implementation that also ignores nickname. There should be no collding exits with -// the same mesh, wgkey and ethaddr -impl Hash for ExitIdentity { - fn hash(&self, state: &mut H) { - self.mesh_ip.hash(state); - self.eth_addr.hash(state); - self.wg_key.hash(state); - } -} - -pub fn exit_identity_to_id(exit_id: ExitIdentity) -> Identity { - Identity { - mesh_ip: exit_id.mesh_ip, - eth_address: exit_id.eth_addr, - wg_public_key: exit_id.wg_key, - nickname: None, - } -} - #[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)] pub struct Denom { /// String representation of token, ex, ualthea, wei, from athea chain will be some unpredictable ibc/ @@ -198,7 +163,7 @@ impl Display for SystemChain { } } -fn default_system_chain() -> SystemChain { +pub fn default_system_chain() -> SystemChain { SystemChain::default() } @@ -220,166 +185,6 @@ impl FromStr for SystemChain { } } -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Default)] -pub struct ExitRegistrationDetails { - #[serde(skip_serializing_if = "Option::is_none", default)] - pub email: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub email_code: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub phone: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub phone_code: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub sequence_number: Option, -} - -/// This is the state an exit can be in -#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] -#[serde(tag = "state")] -pub enum ExitState { - /// the default state of the struct in the config - #[default] - New, - /// we have successfully contacted the exit and gotten basic info. This is - /// kept around for backwards compatitbility, it should be removed once all clients are - /// updated - GotInfo { - general_details: ExitDetails, - message: String, - }, - /// We are awaiting user action to enter the phone or email code - Pending { - general_details: ExitDetails, - message: String, - #[serde(default)] - email_code: Option, - phone_code: Option, - }, - /// we are currently registered and operating, update this state - /// incase the exit for example wants to assign us a new ip - Registered { - general_details: ExitDetails, - our_details: ExitClientDetails, - message: String, - }, - /// we have been denied - Denied { message: String }, -} - -impl ExitState { - pub fn general_details(&self) -> Option<&ExitDetails> { - match *self { - ExitState::GotInfo { - ref general_details, - .. - } => Some(general_details), - ExitState::Pending { - ref general_details, - .. - } => Some(general_details), - ExitState::Registered { - ref general_details, - .. - } => Some(general_details), - _ => None, - } - } - - pub fn our_details(&self) -> Option<&ExitClientDetails> { - match *self { - ExitState::Registered { - ref our_details, .. - } => Some(our_details), - _ => None, - } - } - - pub fn message(&self) -> String { - match *self { - ExitState::New => "New exit".to_string(), - ExitState::GotInfo { ref message, .. } => message.clone(), - ExitState::Pending { ref message, .. } => message.clone(), - ExitState::Registered { ref message, .. } => message.clone(), - ExitState::Denied { ref message, .. } => message.clone(), - } - } -} - -/// This is all the data we need to send to an exit -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct ExitClientIdentity { - pub wg_port: u16, - pub global: Identity, - pub reg_details: ExitRegistrationDetails, -} - -/// Wrapper for secure box containing an exit client identity -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct EncryptedExitClientIdentity { - pub pubkey: WgKey, - pub nonce: [u8; 24], - pub encrypted_exit_client_id: Vec, -} - -/// Wrapper for secure box containing an exit state -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct EncryptedExitState { - pub nonce: [u8; 24], - pub encrypted_exit_state: Vec, -} - -/// Wrapper for secure box containing a list of ips -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct EncryptedExitList { - pub nonce: [u8; 24], - pub exit_list: Vec, -} - -/// Struct returned when hitting exit_list endpoint -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct ExitList { - pub exit_list: Vec, - // All exits in a cluster listen on same port - pub wg_exit_listen_port: u16, -} - -/// Struct returned when hitting exit_list_V2 endpoint -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct ExitListV2 { - pub exit_list: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] -pub enum ExitVerifMode { - Phone, - Email, - Off, -} - -fn default_verif_mode() -> ExitVerifMode { - ExitVerifMode::Off -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct ExitDetails { - pub server_internal_ip: IpAddr, - pub netmask: u8, - pub wg_exit_port: u16, - pub exit_price: u64, - #[serde(default = "default_system_chain")] - pub exit_currency: SystemChain, - pub description: String, - #[serde(default = "default_verif_mode")] - pub verif_mode: ExitVerifMode, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] -pub struct ExitClientDetails { - pub client_internal_ip: IpAddr, - pub internet_ipv6_subnet: Option, -} - /// This is all the data we need to give a neighbor to open a wg connection /// this is also known as a "hello" packet or message #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] @@ -389,21 +194,6 @@ pub struct LocalIdentity { pub global: Identity, } -/// This is all the data a light client needs to open a light client tunnel -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy)] -pub struct LightClientLocalIdentity { - pub wg_port: u16, - /// If we have an existing tunnel, None if we don't know - pub have_tunnel: Option, - pub global: Identity, - /// we have to replicate dhcp ourselves due to the android vpn api - pub tunnel_address: Ipv4Addr, - /// the local_fee of the node passing light client traffic, much bigger - /// than the actual babel price field for ergonomics around downcasting - /// the number after upcasting when we compute it. - pub price: u128, -} - /// This represents a generic payment that may be to or from us /// it contains a txid from a published transaction /// that should be validated against the blockchain @@ -847,13 +637,6 @@ pub struct OperatorExitCheckinMessage { pub users_online: Option, } -/// Operator update that we get from the operator server during our checkin -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct OperatorExitUpdateMessage { - /// List of routers for this exit to register - pub to_register: Vec, -} - #[derive(Debug, Clone, Serialize, Deserialize)] /// A set of info derived from /proc/ and /sys/ about the recent /// load on the system @@ -1017,14 +800,6 @@ pub struct HeartbeatMessage { pub version: String, } -/// An exit's unix time stamp that can be queried by a downstream router -/// Many routers have no built in clock and need to set their time at boot -/// in order for wireguard tunnels to work correctly -#[derive(Debug, Serialize, Deserialize)] -pub struct ExitSystemTime { - pub system_time: SystemTime, -} - #[derive(Hash, Eq, PartialEq, Debug)] pub struct AuthorizedKeys { // public ssh key diff --git a/althea_types/src/lib.rs b/althea_types/src/lib.rs index b6f6f643e..d15770c3f 100644 --- a/althea_types/src/lib.rs +++ b/althea_types/src/lib.rs @@ -4,6 +4,7 @@ extern crate serde_derive; pub mod contact_info; pub mod error; pub mod interop; +pub mod exit_interop; pub mod monitoring; pub mod regions; pub mod user_info; diff --git a/babel_monitor/src/lib.rs b/babel_monitor/src/lib.rs index 09ad72c61..e040bdeab 100644 --- a/babel_monitor/src/lib.rs +++ b/babel_monitor/src/lib.rs @@ -335,6 +335,31 @@ pub fn parse_routes(stream: &mut TcpStream) -> Result, BabelMonitorEr parse_routes_sync(babel_out) } +pub fn parse_routes_and_neighs(stream: &mut TcpStream) -> Result { + let result = run_command(stream, "dump")?; + + let babel_out = result; + let routes = parse_routes_sync(babel_out.clone())?; + let neighs = parse_neighs_sync(babel_out)?; + Ok(RoutesAndNeighbors { routes, neighs }) +} + +/// Return helper struct that contains both routes and neighbors +pub struct RoutesAndNeighbors { + pub routes: Vec, + pub neighs: Vec, +} + +/// Simple helper function that opens a babel stream to get all routes +pub fn get_babel_routes_and_neighbors( + babel_port: u16, + timeout: Duration, +) -> Result { + let mut stream = open_babel_stream(babel_port, timeout)?; + parse_routes_and_neighs(&mut stream) +} + + #[cfg(test)] mod tests { use super::*; diff --git a/rita_client/src/error.rs b/rita_client/src/error.rs index e840d90bc..457cdd6e2 100644 --- a/rita_client/src/error.rs +++ b/rita_client/src/error.rs @@ -36,6 +36,9 @@ pub enum RitaClientError { NoExitIPError(String), RitaCommonError(RitaCommonError), ParseIntError(ParseIntError), + /// This error should be impossible, as the mesh IP is generated at startup, the only reason it's an option + /// is to allow for serde to deserialize without it in the CLU module where it is generated + NoMeshIpError, } impl From for RitaClientError { @@ -142,6 +145,7 @@ impl Display for RitaClientError { } RitaClientError::RitaCommonError(e) => write!(f, "{e}"), RitaClientError::ParseIntError(e) => write!(f, "{e}"), + RitaClientError::NoMeshIpError => write!(f, "No mesh ip generated?"), } } } diff --git a/rita_client/src/exit_manager/exit_manager_loop.rs b/rita_client/src/exit_manager/exit_manager_loop.rs new file mode 100644 index 000000000..68fcbba70 --- /dev/null +++ b/rita_client/src/exit_manager/exit_manager_loop.rs @@ -0,0 +1,88 @@ +use super::time_sync::maybe_set_local_to_exit_time; +use super::utils::linux_setup_exit_tunnel; +use actix_async::System; +use althea_kernel_interface::KI; +use babel_monitor::get_babel_routes_and_neighbors; +use rita_common::FAST_LOOP_TIMEOUT; +use std::{ + thread, + time::{Duration, Instant}, +}; +use rita_common::utils::compute_next_loop_time; + +/// How often the exit manager loop runs, this is a target speed, if the loop takes longer than this +/// to actually execute it will end up running less often +const EXIT_LOOP_SPEED: Duration = Duration::from_secs(30); + +/// The exit manager loop performs 3 main functions, checking in with the exit servers +/// as provided by the Exit database. Using this info to select which of the available exits +/// this router would like to use. Setting up the exit tunnel to this selected exit and maintaining it +/// if the internal ip changes or the best exit changes. Finally a time sync function to keep the router +/// type as correct as possible. +pub fn start_exit_manager_loop() { + + // guard against impossible configuration + if settings::get_rita_client().exit_client.bootstrapping_exits.is_empty() { + panic!("No bootstrapping exits! Impossible to bootstrap to a successful connection!, provide at least one!") + } + + // start by setting up the exit tunnel, panic on failure, if we are not registered to + // an exit this will be a no-op. It's important to do this outside of the loop to minimize + // startup time in the common case where a router is already registered to an exit + linux_setup_exit_tunnel().expect("Failed to setup exit tunnel"); + + // get the babel port from the settings, this won't change at runtime so no reason to get it multiple times + let babel_port = settings::get_rita_client().network.babel_port; + + let mut last_restart = Instant::now(); + // outer thread is a watchdog inner thread is the runner + thread::spawn(move || { + // this will always be an error, so it's really just a loop statement + // with some fancy destructuring + while let Err(e) = { + thread::spawn(move || { + loop { + let start = Instant::now(); + let runner = System::new(); + + // get babel routes once per instance of this loop to check for the best exit, and if our selected is the best + let babel_routes_and_neighbors = + get_babel_routes_and_neighbors(babel_port, FAST_LOOP_TIMEOUT); + + // async part of the loop, making requests to the exit server or servers + runner.block_on(async { + + // ok at this stage we need to get the exit list, compare to routes list and select the best exit + // or setup an exit in the first place if registration has completed since we first ran + + // problems, how do we handle which exit to request the list from and how to update the list + // in the future to ensure we eventually have a successful query. Do we just wan to round robin? + + // Potential downsides would be that the round robin might not include some exits, we assume they are all functoining + // properly and that might not always be the case + + // we need to pick an ip for this + // check the exit's time and update locally if it's very different + maybe_set_local_to_exit_time().await; + }); + + info!("Exit Manager loop elapsed in = {:?}", start.elapsed()); + thread::sleep(compute_next_loop_time(start, EXIT_LOOP_SPEED)); + } + }) + .join() + } { + error!( + "Rita client Exit Manager loop thread paniced! Respawning {:?}", + e + ); + if Instant::now() - last_restart < Duration::from_secs(60) { + error!("Restarting too quickly, rebooting instead!"); + let _res = KI.run_command("reboot", &[]); + } + last_restart = Instant::now(); + } + }); +} + + diff --git a/rita_client/src/exit_manager/mod.rs b/rita_client/src/exit_manager/mod.rs index 338bb1080..34954e4c1 100644 --- a/rita_client/src/exit_manager/mod.rs +++ b/rita_client/src/exit_manager/mod.rs @@ -1,824 +1,6 @@ -//! This module contains utility functions for dealing with the exit signup and connection procedure -//! the procedure goes as follows. -//! -//! Exit is preconfigured with wireguard, mesh ip, and eth address info, this removes the possibility -//! of an effective MITM attack. -//! -//! The exit is queried for info about it that might change, such as it's subnet settings and default -//! route. -//! -//! Once the 'general' settings are acquire we contact the exit with our email, after getting an email -//! we input the confirmation code. -//! -//! The exit then serves up our user specific settings (our own exit internal ip) which we configure -//! and open the wg_exit tunnel. The exit performs the other side of this operation after querying -//! the database and finding a new entry. -//! -//! Signup is complete and the user may use the connection -pub mod exit_loop; -pub mod exit_switcher; -pub mod time_sync; - -use crate::heartbeat::get_selected_exit_server; -use crate::rita_loop::CLIENT_LOOP_TIMEOUT; -use crate::RitaClientError; -use actix_web_async::Result; -use althea_kernel_interface::{ - exit_client_tunnel::ClientExitTunnelConfig, DefaultRoute, KernelInterfaceError, -}; -use althea_types::exit_identity_to_id; -use althea_types::ExitClientDetails; -use althea_types::ExitListV2; -use althea_types::Identity; -use althea_types::WgKey; -use althea_types::{EncryptedExitClientIdentity, EncryptedExitState}; -use althea_types::{EncryptedExitList, ExitDetails}; -use althea_types::{ExitClientIdentity, ExitRegistrationDetails, ExitState}; -use babel_monitor::structs::Route; -use ipnetwork::IpNetwork; -use rita_common::KI; -use settings::client::{ExitServer, SelectedExit}; -use settings::get_rita_client; -use settings::set_rita_client; -use sodiumoxide::crypto::box_; -use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::Nonce; -use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::PublicKey; -use std::collections::{HashMap, HashSet}; -use std::net::{IpAddr, SocketAddr}; -use std::sync::Arc; -use std::sync::RwLock; -use std::time::Instant; - -/// The number of times ExitSwitcher will try to connect to an unresponsive exit before blacklisting its ip -const MAX_BLACKLIST_STRIKES: u16 = 100; - -lazy_static! { - pub static ref SELECTED_EXIT_DETAILS: Arc> = - Arc::new(RwLock::new(SelectedExitDetails::default())); -} - -/// This enum has two types of warnings for misbehaving exits, a hard warning which blacklists this ip immediatly, and a -/// soft warning. When an exit receives MAX_BLACKLIST_STIKES soft warnings, this exit is blacklisted. -pub enum WarningType { - HardWarning, - SoftWarning, -} - -/// This struct holds all the blacklisted ip that are considered when exit switching as well as keeps track -/// of non reponsive exits with a penalty system. After 'n' strikes this ip is added to a the blacklist -#[derive(Default, Debug, Clone)] -pub struct ExitBlacklist { - blacklisted_exits: HashSet, - potential_blacklists: HashMap, -} - -#[derive(Default)] -pub struct SelectedExitDetails { - /// information of current exit we are connected to and tracking exit, if connected to one - /// It also holds information about metrics and degradation values. Look at doc comment on 'set_best_exit' for more - /// information on what these mean - pub selected_exit: SelectedExit, - /// This struct hold infomation about exits that have misbehaved and are blacklisted, or are being watched - /// to being blacklisted through bad responses. - pub exit_blacklist: ExitBlacklist, -} - -/// Data to use identity whether a clients wg exit tunnel needs to be setup up again across ticks -#[derive(Default, Clone)] -pub struct LastExitStates { - last_exit: Option, - last_exit_details: Option, -} - -/// An actor which pays the exit -#[derive(Clone, Default)] -pub struct ExitManager { - pub nat_setup: bool, - /// Every tick we query an exit endpoint to get a list of exits in that cluster. We use this list for exit switching - pub exit_list: ExitListV2, - /// Store last exit here, when we see an exit change, we reset wg tunnels - pub last_exit_state: LastExitStates, - pub last_status_request: Option, -} - -/// This functions sets the exit list ONLY IF the list arguments provived is not empty. This is need for the following edge case: -/// When an exit goes down, the endpoint wont repsond, so we have no exits to switch to. By setting only when we have a length > 1 -/// we assure that we switch when an exit goes down -pub fn set_exit_list(list: ExitListV2, em_state: &mut ExitManager) -> bool { - if !list.exit_list.is_empty() { - em_state.exit_list = list; - return true; - } - false -} - -pub fn get_current_exit() -> Option { - SELECTED_EXIT_DETAILS - .read() - .unwrap() - .selected_exit - .selected_id -} - -pub fn get_full_selected_exit() -> SelectedExit { - SELECTED_EXIT_DETAILS.read().unwrap().selected_exit.clone() -} - -pub fn set_selected_exit(exit_info: SelectedExit) { - SELECTED_EXIT_DETAILS.write().unwrap().selected_exit = exit_info; -} - -pub fn get_exit_blacklist() -> HashSet { - SELECTED_EXIT_DETAILS - .read() - .unwrap() - .exit_blacklist - .blacklisted_exits - .clone() -} - -fn linux_setup_exit_tunnel( - general_details: &ExitDetails, - our_details: &ExitClientDetails, -) -> Result<(), RitaClientError> { - let mut rita_client = settings::get_rita_client(); - let mut network = rita_client.network; - let local_mesh_ip = network.mesh_ip; - - // TODO this should be refactored to return a value - KI.update_settings_route(&mut network.last_default_route)?; - info!("Updated settings route"); - - if let Err(KernelInterfaceError::RuntimeError(v)) = KI.create_blank_wg_interface("wg_exit") { - return Err(RitaClientError::MiscStringError(v)); - } - - let selected_exit = get_selected_exit_server().expect("There should be a selected exit here"); - let args = ClientExitTunnelConfig { - endpoint: SocketAddr::new( - selected_exit.exit_id.mesh_ip, - selected_exit.wg_exit_listen_port, - ), - pubkey: selected_exit.exit_id.wg_public_key, - private_key_path: network.wg_private_key_path.clone(), - listen_port: rita_client.exit_client.wg_listen_port, - local_ip: our_details.client_internal_ip, - netmask: general_details.netmask, - rita_hello_port: network.rita_hello_port, - user_specified_speed: network.user_bandwidth_limit, - }; - - info!("Args while setting up wg_exit on client are: {:?}", args); - - rita_client.network = network; - settings::set_rita_client(rita_client); - - KI.set_client_exit_tunnel_config(args, local_mesh_ip)?; - KI.set_route_to_tunnel(&general_details.server_internal_ip)?; - KI.set_ipv6_route_to_tunnel()?; - - KI.create_client_nat_rules()?; - - Ok(()) -} - -fn restore_nat() { - if let Err(e) = KI.restore_client_nat() { - error!("Failed to restore client nat! {:?}", e); - } -} - -fn remove_nat() { - if let Err(e) = KI.block_client_nat() { - error!("Failed to block client nat! {:?}", e); - } -} - -fn encrypt_exit_client_id( - exit_pubkey: &PublicKey, - id: ExitClientIdentity, -) -> EncryptedExitClientIdentity { - let network_settings = settings::get_rita_client().network; - let our_publickey = network_settings.wg_public_key.expect("No public key?"); - let our_secretkey = network_settings - .wg_private_key - .expect("No private key?") - .into(); - - let plaintext = serde_json::to_string(&id) - .expect("Failed to serialize ExitState!") - .into_bytes(); - let nonce = box_::gen_nonce(); - let ciphertext = box_::seal(&plaintext, &nonce, exit_pubkey, &our_secretkey); - - EncryptedExitClientIdentity { - nonce: nonce.0, - pubkey: our_publickey, - encrypted_exit_client_id: ciphertext, - } -} - -/// Blacklist an exit ip from being selected. This prevents rogue ip within the selected subnet to cause -/// blackhole attacks. Exits that cant be decrypted are immediately blacklisted and those exits that fail to respond after -/// MAX_BLACKLIST_STRIKES warning strikes are blacklisted -fn blacklist_strike_ip(ip: IpAddr, warning: WarningType) { - let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; - - match warning { - WarningType::SoftWarning => { - if let Some(warning_count) = writer.potential_blacklists.get(&ip).cloned() { - if warning_count >= MAX_BLACKLIST_STRIKES { - writer.blacklisted_exits.insert(ip); - writer.potential_blacklists.remove(&ip); - } else { - writer.potential_blacklists.insert(ip, warning_count + 1); - } - } else { - writer.potential_blacklists.insert(ip, 1); - } - } - WarningType::HardWarning => { - writer.blacklisted_exits.insert(ip); - } - } -} - -/// Resets the the warnings from this ip in this blacklist. This function is called whenever we -fn reset_blacklist_warnings(ip: IpAddr) { - let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; - - // This condition should not be reached since if an exit is blacklisted, we should never sucessfully connect to it - if writer.blacklisted_exits.contains(&ip) { - error!("Was able to successfully connect to a blacklisted exit, error in blacklist logic"); - writer.blacklisted_exits.remove(&ip); - } - - if writer.potential_blacklists.contains_key(&ip) { - writer.potential_blacklists.remove(&ip); - } -} - -/// This function clears all blacklist information from the datastore. This function is only called in the situation -/// of false positives where exits that are not supposed to be blacklist have been blacklisted, perhaps for being unresposive for -/// long periods of time -fn reset_exit_blacklist() { - let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; - - writer.blacklisted_exits.clear(); - writer.potential_blacklists.clear(); -} - -fn decrypt_exit_state( - exit_state: EncryptedExitState, - exit_pubkey: PublicKey, -) -> Result { - let rita_client = settings::get_rita_client(); - let network_settings = rita_client.network; - let our_secretkey = network_settings - .wg_private_key - .expect("No private key?") - .into(); - let ciphertext = exit_state.encrypted_exit_state; - let nonce = Nonce(exit_state.nonce); - let decrypted_exit_state: ExitState = - match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { - Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { - Ok(json_string) => match serde_json::from_str(&json_string) { - Ok(exit_state) => exit_state, - Err(e) => { - return Err(e.into()); - } - }, - Err(e) => { - error!("Could not deserialize exit state with {:?}", e); - return Err(e.into()); - } - }, - Err(_) => { - error!("Could not decrypt exit state"); - return Err(RitaClientError::MiscStringError( - "Could not decrypt exit state".to_string(), - )); - } - }; - Ok(decrypted_exit_state) -} - -/// When we retrieve an exit list from an exit, add the compatible exits to the exit server list. -/// This allows these exits to move to GotInfo state, allowing us to switch or connect quickly -pub fn add_exits_to_exit_server_list(list: ExitListV2) { - let mut rita_client = settings::get_rita_client(); - let mut exits = rita_client.exit_client.exits; - - for e in list.exit_list { - exits.entry(e.mesh_ip).or_insert(ExitServer { - exit_id: exit_identity_to_id(e.clone()), - registration_port: e.registration_port, - wg_exit_listen_port: e.wg_exit_listen_port, - info: ExitState::New, - }); - } - - // Update settings with new exits - rita_client.exit_client.exits = exits; - set_rita_client(rita_client); -} - -async fn send_exit_setup_request( - exit_pubkey: WgKey, - to: SocketAddr, - ident: ExitClientIdentity, -) -> Result { - let endpoint = format!("http://[{}]:{}/secure_setup", to.ip(), to.port()); - - let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); - - let client = awc::Client::default(); - - let response = client - .post(&endpoint) - .timeout(CLIENT_LOOP_TIMEOUT) - .send_json(&ident) - .await; - let mut response = match response { - Ok(a) => { - reset_blacklist_warnings(to.ip()); - a - } - Err(awc::error::SendRequestError::Timeout) => { - // Did not get a response, is it a rogue exit or some netork error? - blacklist_strike_ip(to.ip(), WarningType::SoftWarning); - return Err(RitaClientError::SendRequestError( - awc::error::SendRequestError::Timeout.to_string(), - )); - } - Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), - }; - - let value = response.json().await?; - - match decrypt_exit_state(value, exit_pubkey.into()) { - Err(e) => { - blacklist_strike_ip(to.ip(), WarningType::HardWarning); - Err(e) - } - a => { - reset_blacklist_warnings(to.ip()); - a - } - } -} - -async fn send_exit_status_request( - exit_pubkey: WgKey, - to: &SocketAddr, - ident: ExitClientIdentity, -) -> Result { - let endpoint = format!("http://[{}]:{}/secure_status", to.ip(), to.port()); - let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); - - let client = awc::Client::default(); - let response = client - .post(&endpoint) - .timeout(CLIENT_LOOP_TIMEOUT) - .send_json(&ident) - .await; - - let mut response = match response { - Ok(a) => { - reset_blacklist_warnings(to.ip()); - a - } - Err(awc::error::SendRequestError::Timeout) => { - // Did not get a response, is it a rogue exit or some netork error? - blacklist_strike_ip(to.ip(), WarningType::SoftWarning); - return Err(RitaClientError::SendRequestError( - awc::error::SendRequestError::Timeout.to_string(), - )); - } - Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), - }; - let value = response.json().await?; - - match decrypt_exit_state(value, exit_pubkey.into()) { - Err(e) => { - blacklist_strike_ip(to.ip(), WarningType::HardWarning); - Err(e) - } - Ok(a) => { - reset_blacklist_warnings(to.ip()); - Ok(a) - } - } -} -/// Registration is simply one of the exits requesting an update to a global smart contract -/// with our information. -pub async fn exit_setup_request(code: Option) -> Result<(), RitaClientError> { - let exit_client = settings::get_rita_client().exit_client; - - for (_, exit) in exit_client.exits { - match &exit.info { - ExitState::New { .. } | ExitState::Pending { .. } => { - let exit_pubkey = exit.exit_id.wg_public_key; - - let mut reg_details: ExitRegistrationDetails = - match settings::get_rita_client().exit_client.contact_info { - Some(val) => val.into(), - None => { - return Err(RitaClientError::MiscStringError( - "No registration info set!".to_string(), - )) - } - }; - - // Send a verification code if we have one - reg_details.phone_code = code; - - let ident = ExitClientIdentity { - global: match settings::get_rita_client().get_identity() { - Some(id) => id, - None => { - return Err(RitaClientError::MiscStringError( - "Identity has no mesh IP ready yet".to_string(), - )); - } - }, - wg_port: exit_client.wg_listen_port, - reg_details, - }; - - let endpoint = SocketAddr::new(exit.exit_id.mesh_ip, exit.registration_port); - - info!( - "sending exit setup request {:?} to {:?}, using {:?}", - ident, exit, endpoint - ); - - let exit_response = send_exit_setup_request(exit_pubkey, endpoint, ident).await?; - - info!("Setting an exit setup response"); - let mut rita_client = get_rita_client(); - if let Some(exit_to_update) = - rita_client.exit_client.exits.get_mut(&exit.exit_id.mesh_ip) - { - exit_to_update.info = exit_response; - } else { - warn!("Could not find an exit we just queried?"); - } - - set_rita_client(rita_client); - return Ok(()); - } - ExitState::Denied { message } => { - warn!( - "Exit {} is in ExitState DENIED with {}, not able to be setup", - exit.exit_id.mesh_ip, message - ); - } - ExitState::Registered { .. } => { - warn!( - "Exit {} already reports us as registered", - exit.exit_id.mesh_ip - ) - } - ExitState::GotInfo { .. } => { - warn!("This state should be removed for new clients and is kept around for backward compatibilty, how did we reach it?"); - } - } - } - - Err(RitaClientError::MiscStringError( - "Could not find a valid exit to register to!".to_string(), - )) -} - -async fn exit_status_request(exit: IpAddr) -> Result<(), RitaClientError> { - let current_exit = match settings::get_rita_client().exit_client.exits.get(&exit) { - Some(current_exit) => current_exit.clone(), - None => { - return Err(RitaClientError::NoExitError(exit.to_string())); - } - }; - let reg_details = match settings::get_rita_client().exit_client.contact_info { - Some(val) => val.into(), - None => { - return Err(RitaClientError::MiscStringError( - "No valid details".to_string(), - )) - } - }; - - let exit_pubkey = current_exit.exit_id.wg_public_key; - let ident = ExitClientIdentity { - global: match settings::get_rita_client().get_identity() { - Some(id) => id, - None => { - return Err(RitaClientError::MiscStringError( - "Identity has no mesh IP ready yet".to_string(), - )); - } - }, - wg_port: settings::get_rita_client().exit_client.wg_listen_port, - reg_details, - }; - - let endpoint = SocketAddr::new(current_exit.exit_id.mesh_ip, current_exit.registration_port); - - trace!( - "sending exit status request to {} using {:?}", - exit, - endpoint - ); - - let exit_response = send_exit_status_request(exit_pubkey, &endpoint, ident).await?; - let mut rita_client = settings::get_rita_client(); - let current_exit = match rita_client.exit_client.exits.get_mut(&exit) { - Some(exit_struct) => exit_struct, - None => return Err(RitaClientError::ExitNotFound(exit.to_string())), - }; - current_exit.info = exit_response.clone(); - settings::set_rita_client(rita_client); - - trace!("Got exit status response {:?}", exit_response); - Ok(()) -} - -/// Hits the exit_list endpoint for a given exit. -async fn get_exit_list(exit: IpAddr) -> Result { - let current_exit = match settings::get_rita_client().exit_client.exits.get(&exit) { - Some(current_exit) => current_exit.clone(), - None => { - return Err(RitaClientError::NoExitError(exit.to_string())); - } - }; - - let exit_pubkey = current_exit.exit_id.wg_public_key; - let reg_details = match settings::get_rita_client().exit_client.contact_info { - Some(val) => val.into(), - None => { - return Err(RitaClientError::MiscStringError( - "No valid details".to_string(), - )) - } - }; - let ident = ExitClientIdentity { - global: match settings::get_rita_client().get_identity() { - Some(id) => id, - None => { - return Err(RitaClientError::MiscStringError( - "Identity has no mesh IP ready yet".to_string(), - )); - } - }, - wg_port: settings::get_rita_client().exit_client.wg_listen_port, - reg_details, - }; - - let exit_server = current_exit.exit_id.mesh_ip; - - let endpoint = format!( - "http://[{}]:{}/exit_list_v2", - exit_server, current_exit.registration_port - ); - let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); - - let client = awc::Client::default(); - let response = client - .post(&endpoint) - .timeout(CLIENT_LOOP_TIMEOUT) - .send_json(&ident) - .await; - let mut response = match response { - Ok(a) => { - reset_blacklist_warnings(exit_server); - a - } - Err(awc::error::SendRequestError::Timeout) => { - // Did not get a response, is it a rogue exit or some netork error? - blacklist_strike_ip(exit_server, WarningType::SoftWarning); - return Err(RitaClientError::SendRequestError( - awc::error::SendRequestError::Timeout.to_string(), - )); - } - Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), - }; - - let value = response.json().await?; - - match decrypt_exit_list(value, exit_pubkey.into()) { - Err(e) => { - blacklist_strike_ip(exit_server, WarningType::HardWarning); - Err(e) - } - Ok(a) => { - reset_blacklist_warnings(exit_server); - Ok(a) - } - } -} - -fn decrypt_exit_list( - exit_list: EncryptedExitList, - exit_pubkey: PublicKey, -) -> Result { - let rita_client = settings::get_rita_client(); - let network_settings = rita_client.network; - let our_secretkey = network_settings - .wg_private_key - .expect("No private key?") - .into(); - let ciphertext = exit_list.exit_list; - let nonce = Nonce(exit_list.nonce); - let ret: ExitListV2 = match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { - Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { - Ok(json_string) => match serde_json::from_str(&json_string) { - Ok(ip_list) => ip_list, - Err(e) => { - return Err(e.into()); - } - }, - Err(e) => { - error!("Could not deserialize exit state with {:?}", e); - return Err(e.into()); - } - }, - Err(_) => { - error!("Could not decrypt exit state"); - return Err(RitaClientError::MiscStringError( - "Could not decrypt exit state".to_string(), - )); - } - }; - Ok(ret) -} - -fn correct_default_route(input: Option) -> bool { - match input { - Some(v) => v.is_althea_default_route(), - None => false, - } -} - -/// This function takes a list of babel routes and uses this to insert ip -> route -/// instances in the hashmap. This is an optimization that allows us to reduce route lookups from O(n * m ) to O(m + n) -/// when trying to find exit ips in our cluster -fn get_routes_hashmap(routes: Vec) -> HashMap { - let mut ret = HashMap::new(); - for r in routes { - ret.insert(r.prefix.ip(), r); - } - - ret -} - -/// Exits are ready to switch to when they are in the Registered State, we return list of exits that are -pub fn get_ready_to_switch_exits(exit_list: ExitListV2) -> Vec { - let exits = get_rita_client().exit_client.exits; - - let mut ret = vec![]; - for exit in exit_list.exit_list { - match exits.get(&exit.mesh_ip) { - Some(server) => { - if let ExitState::Registered { .. } = server.info { - ret.push(exit_identity_to_id(exit)); - } - } - None => { - error!("Exit List logic error! All entries of exit list should be setup in config!") - } - } - } - ret -} - -pub fn get_client_pub_ipv6() -> Option { - let rita_settings = settings::get_rita_client(); - let current_exit = get_current_exit(); - if let Some(exit) = current_exit { - let exit_ser = rita_settings.exit_client.exits.get(&exit); - if let Some(exit_ser) = exit_ser { - let exit_info = exit_ser.info.clone(); - - if let ExitState::Registered { our_details, .. } = exit_info { - return our_details.internet_ipv6_subnet; - } - } - } - None -} - -/// Verfies if exit has changed to reestablish wg tunnels -/// 1.) When exit instance ip has changed -/// 2.) Exit reg details have chaged -pub fn has_exit_changed( - state: LastExitStates, - selected_exit: Option, - cluster: ExitServer, -) -> bool { - let last_exit = state.last_exit; - - let instance_has_changed = !(last_exit.is_some() - && selected_exit.is_some() - && last_exit.unwrap() == selected_exit.unwrap()); - - let last_exit_details = state.last_exit_details; - let exit_reg_has_changed = - !(last_exit_details.is_some() && last_exit_details.unwrap() == cluster.info); - - instance_has_changed | exit_reg_has_changed -} - -#[cfg(test)] -mod tests { - use althea_types::{ExitVerifMode, SystemChain}; - - use super::*; - - #[test] - fn test_exit_has_changed() { - let mut exit_server = ExitServer { - exit_id: Identity { - mesh_ip: "fd00::1337".parse().unwrap(), - eth_address: "0xd2C5b6dd6ca641BE4c90565b5d3DA34C14949A53" - .parse() - .unwrap(), - wg_public_key: "V9I9yrxAqFqLV+9GeT5pnXPwk4Cxgfvl30Fv8khVGsM=" - .parse() - .unwrap(), - nickname: None, - }, - - registration_port: 3452, - wg_exit_listen_port: 59998, - - info: ExitState::New, - }; - let dummy_exit_details = ExitDetails { - server_internal_ip: "172.0.0.1".parse().unwrap(), - netmask: 0, - wg_exit_port: 123, - exit_price: 123, - exit_currency: SystemChain::Xdai, - description: "".to_string(), - verif_mode: ExitVerifMode::Off, - }; - let mut last_states = LastExitStates::default(); - - // An ip is selected and setup in last_states - let selected_exit = Some("fd00::2602".parse().unwrap()); - - assert!(has_exit_changed( - last_states.clone(), - selected_exit, - exit_server.clone() - )); - - // Last states get updated next tick - last_states.last_exit = Some("fd00::2602".parse().unwrap()); - last_states.last_exit_details = Some(exit_server.info.clone()); - assert!(!has_exit_changed( - last_states.clone(), - selected_exit, - exit_server.clone() - )); - - // Registration Details change - exit_server.info = ExitState::Registered { - general_details: dummy_exit_details.clone(), - our_details: ExitClientDetails { - client_internal_ip: "172.1.1.1".parse().unwrap(), - internet_ipv6_subnet: None, - }, - message: "".to_string(), - }; - assert!(has_exit_changed( - last_states.clone(), - selected_exit, - exit_server.clone() - )); - - // next tick last stats get updated accordingly - last_states.last_exit_details = Some(exit_server.info.clone()); - - // Registration detail for client change - exit_server.info = ExitState::Registered { - general_details: dummy_exit_details, - our_details: ExitClientDetails { - client_internal_ip: "172.1.1.14".parse().unwrap(), - internet_ipv6_subnet: None, - }, - message: "".to_string(), - }; - assert!(has_exit_changed( - last_states.clone(), - selected_exit, - exit_server.clone() - )); - - // next tick its updated accordingly - last_states.last_exit_details = Some(exit_server.info.clone()); - assert!(!has_exit_changed(last_states, selected_exit, exit_server)); - } -} +pub mod encryption_utils; +pub mod exit_manager_loop; +pub mod time_sync; +pub mod utils; diff --git a/rita_client/src/exit_manager/time_sync.rs b/rita_client/src/exit_manager/time_sync.rs index 9b9a7784e..489666d1c 100644 --- a/rita_client/src/exit_manager/time_sync.rs +++ b/rita_client/src/exit_manager/time_sync.rs @@ -1,9 +1,6 @@ use althea_kernel_interface::KI; use althea_types::ExitSystemTime; -use settings::client::ExitServer; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use crate::exit_manager::get_current_exit; +use std::{net::Ipv6Addr, time::{Duration, SystemTime, UNIX_EPOCH}}; /// The max time difference between the local router's time and the exit's before resetting the local time to the exit's const MAX_DIFF_LOCAL_EXIT_TIME: Duration = Duration::from_secs(60); @@ -13,10 +10,8 @@ const MAX_DIFF_LOCAL_EXIT_TIME: Duration = Duration::from_secs(60); const MAX_EXIT_TUNNEL_HANDSHAKE: Duration = Duration::from_secs(60 * 3); /// Retrieve a unix timestamp from the exit's mesh IPv6 -pub async fn get_exit_time(exit: ExitServer) -> Option { +pub async fn get_exit_time(exit_ip: Ipv6Addr) -> Option { info!("Getting the exit time"); - let exit_ip = get_current_exit().expect("There should be an exit ip here"); - let exit_port = exit.registration_port; let url = format!("http://[{exit_ip}]:{exit_port}/time"); let client = awc::Client::default(); @@ -57,7 +52,7 @@ pub fn get_latest_exit_handshake() -> Option { /// Check for a handshake time for wg_exit. We might not get one if there's no tunnel /// if there's no handshake or the handshake is more than 10 mins old, then try to get the exit time /// if we do get the exit time and it's more than 60 secs different than local time, then set the local time to it -pub async fn maybe_set_local_to_exit_time(exit: ExitServer) { +pub async fn maybe_set_local_to_exit_time(exit_ip: Ipv6Addr) { let now = SystemTime::now(); if let Some(last_handshake) = get_latest_exit_handshake() { @@ -88,7 +83,7 @@ pub async fn maybe_set_local_to_exit_time(exit: ExitServer) { } // if we're here, then it means we didn't get a reasonable handshake - if let Some(exit_time) = get_exit_time(exit).await { + if let Some(exit_time) = get_exit_time(exit_ip).await { // if exit time is more than 60 secs later than our time, set ours to its if let Ok(diff) = exit_time.duration_since(now) { if diff > MAX_DIFF_LOCAL_EXIT_TIME { diff --git a/rita_client/src/exit_manager/utils.rs b/rita_client/src/exit_manager/utils.rs new file mode 100644 index 000000000..138f456cd --- /dev/null +++ b/rita_client/src/exit_manager/utils.rs @@ -0,0 +1,129 @@ +use crate::{rita_loop::CLIENT_LOOP_TIMEOUT, RitaClientError}; +use althea_kernel_interface::{exit_client_tunnel::ClientExitTunnelConfig, KI}; +use althea_types::{ExitClientIdentity, ExitListV2}; +use babel_monitor::{open_babel_stream, parse_routes, structs::Route}; +use rand::Rng; +use settings::client::{ExitServer, EXIT_CLIENT_LISTEN_PORT}; +use std::net::SocketAddr; + +/// Performs initial setup of the exit tunnel on the router, this function is called once on startup +/// and sets up the last exit tunnel used by the router by default +pub fn linux_setup_exit_tunnel() -> Result<(), RitaClientError> { + let mut rita_client = settings::get_rita_client(); + let mut network = rita_client.network; + let local_mesh_ip = network.mesh_ip; + let (general_details, our_details) = match rita_client.exit_client.registration_state { + _ => return Ok(()), + althea_types::ExitState::Registered { + general_details, + our_details, + message, + } => (general_details, our_details), + }; + + KI.update_settings_route(&mut network.last_default_route)?; + KI.create_blank_wg_interface("wg_exit")?; + + let args = ClientExitTunnelConfig { + endpoint: SocketAddr::new( + general_details.server_mesh_ip.into(), + general_details.wg_exit_port, + ), + pubkey: general_details.server_wg_pubkey, + private_key_path: network.wg_private_key_path.clone(), + listen_port: EXIT_CLIENT_LISTEN_PORT, + local_ip: our_details.client_internal_ip, + netmask: general_details.netmask, + rita_hello_port: network.rita_hello_port, + user_specified_speed: network.user_bandwidth_limit, + }; + + info!("Args while setting up wg_exit on client are: {:?}", args); + + rita_client.network = network; + settings::set_rita_client(rita_client); + + KI.set_client_exit_tunnel_config(args, local_mesh_ip)?; + KI.set_route_to_tunnel(&general_details.server_internal_ip)?; + KI.set_ipv6_route_to_tunnel()?; + + KI.create_client_nat_rules()?; + + Ok(()) +} + +/// Gets an exit from the bootstrapping list at random, this is used to round robin select an exit to query +pub fn pick_bootstrapping_exit_at_random() -> ExitServer { + let rita_client = settings::get_rita_client(); + let len = rita_client.exit_client.bootstrapping_exits.len(); + let rand = rand::thread_rng().gen_range(0..len); + rita_client + .exit_client + .bootstrapping_exits + .iter() + .nth(rand) + .unwrap() + .clone() +} + +/// Hits the exit_list endpoint for a given exit, returning the exit list as configured +/// for that particular exit. For Postgres DB exits this will return the list in the config +/// for Solidity DB exits this will return the exits in the registration contract +async fn get_exit_list(server: ExitServer) -> Result { + let exit_pubkey = server.exit_id.wg_public_key; + let ident = ExitClientIdentity { + global: match settings::get_rita_client().get_identity() { + Some(id) => id, + None => { + return Err(RitaClientError::NoMeshIpError) + } + }, + wg_port: server.registration_port, + reg_details, + }; + + // TODO exit server should accept a json object that doesn't include the reg details + // it's not needed for this request at all + + let exit_server = current_exit.exit_id.mesh_ip; + + let endpoint = format!( + "http://[{}]:{}/exit_list_v2", + exit_server, current_exit.registration_port + ); + let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); + + let client = awc::Client::default(); + let response = client + .post(&endpoint) + .timeout(CLIENT_LOOP_TIMEOUT) + .send_json(&ident) + .await; + let mut response = match response { + Ok(a) => { + reset_blacklist_warnings(exit_server); + a + } + Err(awc::error::SendRequestError::Timeout) => { + // Did not get a response, is it a rogue exit or some netork error? + blacklist_strike_ip(exit_server, WarningType::SoftWarning); + return Err(RitaClientError::SendRequestError( + awc::error::SendRequestError::Timeout.to_string(), + )); + } + Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), + }; + + let value = response.json().await?; + + match decrypt_exit_list(value, exit_pubkey.into()) { + Err(e) => { + blacklist_strike_ip(exit_server, WarningType::HardWarning); + Err(e) + } + Ok(a) => { + reset_blacklist_warnings(exit_server); + Ok(a) + } + } +} \ No newline at end of file diff --git a/rita_client/src/exit_manager/exit_loop.rs b/rita_client/src/exit_manager_legacy/exit_loop.rs similarity index 100% rename from rita_client/src/exit_manager/exit_loop.rs rename to rita_client/src/exit_manager_legacy/exit_loop.rs diff --git a/rita_client/src/exit_manager/exit_switcher.rs b/rita_client/src/exit_manager_legacy/exit_switcher.rs similarity index 100% rename from rita_client/src/exit_manager/exit_switcher.rs rename to rita_client/src/exit_manager_legacy/exit_switcher.rs diff --git a/rita_client/src/exit_manager_legacy/mod.rs b/rita_client/src/exit_manager_legacy/mod.rs new file mode 100644 index 000000000..338bb1080 --- /dev/null +++ b/rita_client/src/exit_manager_legacy/mod.rs @@ -0,0 +1,824 @@ +//! This module contains utility functions for dealing with the exit signup and connection procedure +//! the procedure goes as follows. +//! +//! Exit is preconfigured with wireguard, mesh ip, and eth address info, this removes the possibility +//! of an effective MITM attack. +//! +//! The exit is queried for info about it that might change, such as it's subnet settings and default +//! route. +//! +//! Once the 'general' settings are acquire we contact the exit with our email, after getting an email +//! we input the confirmation code. +//! +//! The exit then serves up our user specific settings (our own exit internal ip) which we configure +//! and open the wg_exit tunnel. The exit performs the other side of this operation after querying +//! the database and finding a new entry. +//! +//! Signup is complete and the user may use the connection + +pub mod exit_loop; +pub mod exit_switcher; +pub mod time_sync; + +use crate::heartbeat::get_selected_exit_server; +use crate::rita_loop::CLIENT_LOOP_TIMEOUT; +use crate::RitaClientError; +use actix_web_async::Result; +use althea_kernel_interface::{ + exit_client_tunnel::ClientExitTunnelConfig, DefaultRoute, KernelInterfaceError, +}; +use althea_types::exit_identity_to_id; +use althea_types::ExitClientDetails; +use althea_types::ExitListV2; +use althea_types::Identity; +use althea_types::WgKey; +use althea_types::{EncryptedExitClientIdentity, EncryptedExitState}; +use althea_types::{EncryptedExitList, ExitDetails}; +use althea_types::{ExitClientIdentity, ExitRegistrationDetails, ExitState}; +use babel_monitor::structs::Route; +use ipnetwork::IpNetwork; +use rita_common::KI; +use settings::client::{ExitServer, SelectedExit}; +use settings::get_rita_client; +use settings::set_rita_client; +use sodiumoxide::crypto::box_; +use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::Nonce; +use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::PublicKey; +use std::collections::{HashMap, HashSet}; +use std::net::{IpAddr, SocketAddr}; +use std::sync::Arc; +use std::sync::RwLock; +use std::time::Instant; + +/// The number of times ExitSwitcher will try to connect to an unresponsive exit before blacklisting its ip +const MAX_BLACKLIST_STRIKES: u16 = 100; + +lazy_static! { + pub static ref SELECTED_EXIT_DETAILS: Arc> = + Arc::new(RwLock::new(SelectedExitDetails::default())); +} + +/// This enum has two types of warnings for misbehaving exits, a hard warning which blacklists this ip immediatly, and a +/// soft warning. When an exit receives MAX_BLACKLIST_STIKES soft warnings, this exit is blacklisted. +pub enum WarningType { + HardWarning, + SoftWarning, +} + +/// This struct holds all the blacklisted ip that are considered when exit switching as well as keeps track +/// of non reponsive exits with a penalty system. After 'n' strikes this ip is added to a the blacklist +#[derive(Default, Debug, Clone)] +pub struct ExitBlacklist { + blacklisted_exits: HashSet, + potential_blacklists: HashMap, +} + +#[derive(Default)] +pub struct SelectedExitDetails { + /// information of current exit we are connected to and tracking exit, if connected to one + /// It also holds information about metrics and degradation values. Look at doc comment on 'set_best_exit' for more + /// information on what these mean + pub selected_exit: SelectedExit, + /// This struct hold infomation about exits that have misbehaved and are blacklisted, or are being watched + /// to being blacklisted through bad responses. + pub exit_blacklist: ExitBlacklist, +} + +/// Data to use identity whether a clients wg exit tunnel needs to be setup up again across ticks +#[derive(Default, Clone)] +pub struct LastExitStates { + last_exit: Option, + last_exit_details: Option, +} + +/// An actor which pays the exit +#[derive(Clone, Default)] +pub struct ExitManager { + pub nat_setup: bool, + /// Every tick we query an exit endpoint to get a list of exits in that cluster. We use this list for exit switching + pub exit_list: ExitListV2, + /// Store last exit here, when we see an exit change, we reset wg tunnels + pub last_exit_state: LastExitStates, + pub last_status_request: Option, +} + +/// This functions sets the exit list ONLY IF the list arguments provived is not empty. This is need for the following edge case: +/// When an exit goes down, the endpoint wont repsond, so we have no exits to switch to. By setting only when we have a length > 1 +/// we assure that we switch when an exit goes down +pub fn set_exit_list(list: ExitListV2, em_state: &mut ExitManager) -> bool { + if !list.exit_list.is_empty() { + em_state.exit_list = list; + return true; + } + false +} + +pub fn get_current_exit() -> Option { + SELECTED_EXIT_DETAILS + .read() + .unwrap() + .selected_exit + .selected_id +} + +pub fn get_full_selected_exit() -> SelectedExit { + SELECTED_EXIT_DETAILS.read().unwrap().selected_exit.clone() +} + +pub fn set_selected_exit(exit_info: SelectedExit) { + SELECTED_EXIT_DETAILS.write().unwrap().selected_exit = exit_info; +} + +pub fn get_exit_blacklist() -> HashSet { + SELECTED_EXIT_DETAILS + .read() + .unwrap() + .exit_blacklist + .blacklisted_exits + .clone() +} + +fn linux_setup_exit_tunnel( + general_details: &ExitDetails, + our_details: &ExitClientDetails, +) -> Result<(), RitaClientError> { + let mut rita_client = settings::get_rita_client(); + let mut network = rita_client.network; + let local_mesh_ip = network.mesh_ip; + + // TODO this should be refactored to return a value + KI.update_settings_route(&mut network.last_default_route)?; + info!("Updated settings route"); + + if let Err(KernelInterfaceError::RuntimeError(v)) = KI.create_blank_wg_interface("wg_exit") { + return Err(RitaClientError::MiscStringError(v)); + } + + let selected_exit = get_selected_exit_server().expect("There should be a selected exit here"); + let args = ClientExitTunnelConfig { + endpoint: SocketAddr::new( + selected_exit.exit_id.mesh_ip, + selected_exit.wg_exit_listen_port, + ), + pubkey: selected_exit.exit_id.wg_public_key, + private_key_path: network.wg_private_key_path.clone(), + listen_port: rita_client.exit_client.wg_listen_port, + local_ip: our_details.client_internal_ip, + netmask: general_details.netmask, + rita_hello_port: network.rita_hello_port, + user_specified_speed: network.user_bandwidth_limit, + }; + + info!("Args while setting up wg_exit on client are: {:?}", args); + + rita_client.network = network; + settings::set_rita_client(rita_client); + + KI.set_client_exit_tunnel_config(args, local_mesh_ip)?; + KI.set_route_to_tunnel(&general_details.server_internal_ip)?; + KI.set_ipv6_route_to_tunnel()?; + + KI.create_client_nat_rules()?; + + Ok(()) +} + +fn restore_nat() { + if let Err(e) = KI.restore_client_nat() { + error!("Failed to restore client nat! {:?}", e); + } +} + +fn remove_nat() { + if let Err(e) = KI.block_client_nat() { + error!("Failed to block client nat! {:?}", e); + } +} + +fn encrypt_exit_client_id( + exit_pubkey: &PublicKey, + id: ExitClientIdentity, +) -> EncryptedExitClientIdentity { + let network_settings = settings::get_rita_client().network; + let our_publickey = network_settings.wg_public_key.expect("No public key?"); + let our_secretkey = network_settings + .wg_private_key + .expect("No private key?") + .into(); + + let plaintext = serde_json::to_string(&id) + .expect("Failed to serialize ExitState!") + .into_bytes(); + let nonce = box_::gen_nonce(); + let ciphertext = box_::seal(&plaintext, &nonce, exit_pubkey, &our_secretkey); + + EncryptedExitClientIdentity { + nonce: nonce.0, + pubkey: our_publickey, + encrypted_exit_client_id: ciphertext, + } +} + +/// Blacklist an exit ip from being selected. This prevents rogue ip within the selected subnet to cause +/// blackhole attacks. Exits that cant be decrypted are immediately blacklisted and those exits that fail to respond after +/// MAX_BLACKLIST_STRIKES warning strikes are blacklisted +fn blacklist_strike_ip(ip: IpAddr, warning: WarningType) { + let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; + + match warning { + WarningType::SoftWarning => { + if let Some(warning_count) = writer.potential_blacklists.get(&ip).cloned() { + if warning_count >= MAX_BLACKLIST_STRIKES { + writer.blacklisted_exits.insert(ip); + writer.potential_blacklists.remove(&ip); + } else { + writer.potential_blacklists.insert(ip, warning_count + 1); + } + } else { + writer.potential_blacklists.insert(ip, 1); + } + } + WarningType::HardWarning => { + writer.blacklisted_exits.insert(ip); + } + } +} + +/// Resets the the warnings from this ip in this blacklist. This function is called whenever we +fn reset_blacklist_warnings(ip: IpAddr) { + let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; + + // This condition should not be reached since if an exit is blacklisted, we should never sucessfully connect to it + if writer.blacklisted_exits.contains(&ip) { + error!("Was able to successfully connect to a blacklisted exit, error in blacklist logic"); + writer.blacklisted_exits.remove(&ip); + } + + if writer.potential_blacklists.contains_key(&ip) { + writer.potential_blacklists.remove(&ip); + } +} + +/// This function clears all blacklist information from the datastore. This function is only called in the situation +/// of false positives where exits that are not supposed to be blacklist have been blacklisted, perhaps for being unresposive for +/// long periods of time +fn reset_exit_blacklist() { + let writer = &mut SELECTED_EXIT_DETAILS.write().unwrap().exit_blacklist; + + writer.blacklisted_exits.clear(); + writer.potential_blacklists.clear(); +} + +fn decrypt_exit_state( + exit_state: EncryptedExitState, + exit_pubkey: PublicKey, +) -> Result { + let rita_client = settings::get_rita_client(); + let network_settings = rita_client.network; + let our_secretkey = network_settings + .wg_private_key + .expect("No private key?") + .into(); + let ciphertext = exit_state.encrypted_exit_state; + let nonce = Nonce(exit_state.nonce); + let decrypted_exit_state: ExitState = + match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { + Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { + Ok(json_string) => match serde_json::from_str(&json_string) { + Ok(exit_state) => exit_state, + Err(e) => { + return Err(e.into()); + } + }, + Err(e) => { + error!("Could not deserialize exit state with {:?}", e); + return Err(e.into()); + } + }, + Err(_) => { + error!("Could not decrypt exit state"); + return Err(RitaClientError::MiscStringError( + "Could not decrypt exit state".to_string(), + )); + } + }; + Ok(decrypted_exit_state) +} + +/// When we retrieve an exit list from an exit, add the compatible exits to the exit server list. +/// This allows these exits to move to GotInfo state, allowing us to switch or connect quickly +pub fn add_exits_to_exit_server_list(list: ExitListV2) { + let mut rita_client = settings::get_rita_client(); + let mut exits = rita_client.exit_client.exits; + + for e in list.exit_list { + exits.entry(e.mesh_ip).or_insert(ExitServer { + exit_id: exit_identity_to_id(e.clone()), + registration_port: e.registration_port, + wg_exit_listen_port: e.wg_exit_listen_port, + info: ExitState::New, + }); + } + + // Update settings with new exits + rita_client.exit_client.exits = exits; + set_rita_client(rita_client); +} + +async fn send_exit_setup_request( + exit_pubkey: WgKey, + to: SocketAddr, + ident: ExitClientIdentity, +) -> Result { + let endpoint = format!("http://[{}]:{}/secure_setup", to.ip(), to.port()); + + let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); + + let client = awc::Client::default(); + + let response = client + .post(&endpoint) + .timeout(CLIENT_LOOP_TIMEOUT) + .send_json(&ident) + .await; + let mut response = match response { + Ok(a) => { + reset_blacklist_warnings(to.ip()); + a + } + Err(awc::error::SendRequestError::Timeout) => { + // Did not get a response, is it a rogue exit or some netork error? + blacklist_strike_ip(to.ip(), WarningType::SoftWarning); + return Err(RitaClientError::SendRequestError( + awc::error::SendRequestError::Timeout.to_string(), + )); + } + Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), + }; + + let value = response.json().await?; + + match decrypt_exit_state(value, exit_pubkey.into()) { + Err(e) => { + blacklist_strike_ip(to.ip(), WarningType::HardWarning); + Err(e) + } + a => { + reset_blacklist_warnings(to.ip()); + a + } + } +} + +async fn send_exit_status_request( + exit_pubkey: WgKey, + to: &SocketAddr, + ident: ExitClientIdentity, +) -> Result { + let endpoint = format!("http://[{}]:{}/secure_status", to.ip(), to.port()); + let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); + + let client = awc::Client::default(); + let response = client + .post(&endpoint) + .timeout(CLIENT_LOOP_TIMEOUT) + .send_json(&ident) + .await; + + let mut response = match response { + Ok(a) => { + reset_blacklist_warnings(to.ip()); + a + } + Err(awc::error::SendRequestError::Timeout) => { + // Did not get a response, is it a rogue exit or some netork error? + blacklist_strike_ip(to.ip(), WarningType::SoftWarning); + return Err(RitaClientError::SendRequestError( + awc::error::SendRequestError::Timeout.to_string(), + )); + } + Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), + }; + let value = response.json().await?; + + match decrypt_exit_state(value, exit_pubkey.into()) { + Err(e) => { + blacklist_strike_ip(to.ip(), WarningType::HardWarning); + Err(e) + } + Ok(a) => { + reset_blacklist_warnings(to.ip()); + Ok(a) + } + } +} + +/// Registration is simply one of the exits requesting an update to a global smart contract +/// with our information. +pub async fn exit_setup_request(code: Option) -> Result<(), RitaClientError> { + let exit_client = settings::get_rita_client().exit_client; + + for (_, exit) in exit_client.exits { + match &exit.info { + ExitState::New { .. } | ExitState::Pending { .. } => { + let exit_pubkey = exit.exit_id.wg_public_key; + + let mut reg_details: ExitRegistrationDetails = + match settings::get_rita_client().exit_client.contact_info { + Some(val) => val.into(), + None => { + return Err(RitaClientError::MiscStringError( + "No registration info set!".to_string(), + )) + } + }; + + // Send a verification code if we have one + reg_details.phone_code = code; + + let ident = ExitClientIdentity { + global: match settings::get_rita_client().get_identity() { + Some(id) => id, + None => { + return Err(RitaClientError::MiscStringError( + "Identity has no mesh IP ready yet".to_string(), + )); + } + }, + wg_port: exit_client.wg_listen_port, + reg_details, + }; + + let endpoint = SocketAddr::new(exit.exit_id.mesh_ip, exit.registration_port); + + info!( + "sending exit setup request {:?} to {:?}, using {:?}", + ident, exit, endpoint + ); + + let exit_response = send_exit_setup_request(exit_pubkey, endpoint, ident).await?; + + info!("Setting an exit setup response"); + let mut rita_client = get_rita_client(); + if let Some(exit_to_update) = + rita_client.exit_client.exits.get_mut(&exit.exit_id.mesh_ip) + { + exit_to_update.info = exit_response; + } else { + warn!("Could not find an exit we just queried?"); + } + + set_rita_client(rita_client); + return Ok(()); + } + ExitState::Denied { message } => { + warn!( + "Exit {} is in ExitState DENIED with {}, not able to be setup", + exit.exit_id.mesh_ip, message + ); + } + ExitState::Registered { .. } => { + warn!( + "Exit {} already reports us as registered", + exit.exit_id.mesh_ip + ) + } + ExitState::GotInfo { .. } => { + warn!("This state should be removed for new clients and is kept around for backward compatibilty, how did we reach it?"); + } + } + } + + Err(RitaClientError::MiscStringError( + "Could not find a valid exit to register to!".to_string(), + )) +} + +async fn exit_status_request(exit: IpAddr) -> Result<(), RitaClientError> { + let current_exit = match settings::get_rita_client().exit_client.exits.get(&exit) { + Some(current_exit) => current_exit.clone(), + None => { + return Err(RitaClientError::NoExitError(exit.to_string())); + } + }; + let reg_details = match settings::get_rita_client().exit_client.contact_info { + Some(val) => val.into(), + None => { + return Err(RitaClientError::MiscStringError( + "No valid details".to_string(), + )) + } + }; + + let exit_pubkey = current_exit.exit_id.wg_public_key; + let ident = ExitClientIdentity { + global: match settings::get_rita_client().get_identity() { + Some(id) => id, + None => { + return Err(RitaClientError::MiscStringError( + "Identity has no mesh IP ready yet".to_string(), + )); + } + }, + wg_port: settings::get_rita_client().exit_client.wg_listen_port, + reg_details, + }; + + let endpoint = SocketAddr::new(current_exit.exit_id.mesh_ip, current_exit.registration_port); + + trace!( + "sending exit status request to {} using {:?}", + exit, + endpoint + ); + + let exit_response = send_exit_status_request(exit_pubkey, &endpoint, ident).await?; + let mut rita_client = settings::get_rita_client(); + let current_exit = match rita_client.exit_client.exits.get_mut(&exit) { + Some(exit_struct) => exit_struct, + None => return Err(RitaClientError::ExitNotFound(exit.to_string())), + }; + current_exit.info = exit_response.clone(); + settings::set_rita_client(rita_client); + + trace!("Got exit status response {:?}", exit_response); + Ok(()) +} + +/// Hits the exit_list endpoint for a given exit. +async fn get_exit_list(exit: IpAddr) -> Result { + let current_exit = match settings::get_rita_client().exit_client.exits.get(&exit) { + Some(current_exit) => current_exit.clone(), + None => { + return Err(RitaClientError::NoExitError(exit.to_string())); + } + }; + + let exit_pubkey = current_exit.exit_id.wg_public_key; + let reg_details = match settings::get_rita_client().exit_client.contact_info { + Some(val) => val.into(), + None => { + return Err(RitaClientError::MiscStringError( + "No valid details".to_string(), + )) + } + }; + let ident = ExitClientIdentity { + global: match settings::get_rita_client().get_identity() { + Some(id) => id, + None => { + return Err(RitaClientError::MiscStringError( + "Identity has no mesh IP ready yet".to_string(), + )); + } + }, + wg_port: settings::get_rita_client().exit_client.wg_listen_port, + reg_details, + }; + + let exit_server = current_exit.exit_id.mesh_ip; + + let endpoint = format!( + "http://[{}]:{}/exit_list_v2", + exit_server, current_exit.registration_port + ); + let ident = encrypt_exit_client_id(&exit_pubkey.into(), ident); + + let client = awc::Client::default(); + let response = client + .post(&endpoint) + .timeout(CLIENT_LOOP_TIMEOUT) + .send_json(&ident) + .await; + let mut response = match response { + Ok(a) => { + reset_blacklist_warnings(exit_server); + a + } + Err(awc::error::SendRequestError::Timeout) => { + // Did not get a response, is it a rogue exit or some netork error? + blacklist_strike_ip(exit_server, WarningType::SoftWarning); + return Err(RitaClientError::SendRequestError( + awc::error::SendRequestError::Timeout.to_string(), + )); + } + Err(e) => return Err(RitaClientError::SendRequestError(e.to_string())), + }; + + let value = response.json().await?; + + match decrypt_exit_list(value, exit_pubkey.into()) { + Err(e) => { + blacklist_strike_ip(exit_server, WarningType::HardWarning); + Err(e) + } + Ok(a) => { + reset_blacklist_warnings(exit_server); + Ok(a) + } + } +} + +fn decrypt_exit_list( + exit_list: EncryptedExitList, + exit_pubkey: PublicKey, +) -> Result { + let rita_client = settings::get_rita_client(); + let network_settings = rita_client.network; + let our_secretkey = network_settings + .wg_private_key + .expect("No private key?") + .into(); + let ciphertext = exit_list.exit_list; + let nonce = Nonce(exit_list.nonce); + let ret: ExitListV2 = match box_::open(&ciphertext, &nonce, &exit_pubkey, &our_secretkey) { + Ok(decrypted_bytes) => match String::from_utf8(decrypted_bytes) { + Ok(json_string) => match serde_json::from_str(&json_string) { + Ok(ip_list) => ip_list, + Err(e) => { + return Err(e.into()); + } + }, + Err(e) => { + error!("Could not deserialize exit state with {:?}", e); + return Err(e.into()); + } + }, + Err(_) => { + error!("Could not decrypt exit state"); + return Err(RitaClientError::MiscStringError( + "Could not decrypt exit state".to_string(), + )); + } + }; + Ok(ret) +} + +fn correct_default_route(input: Option) -> bool { + match input { + Some(v) => v.is_althea_default_route(), + None => false, + } +} + +/// This function takes a list of babel routes and uses this to insert ip -> route +/// instances in the hashmap. This is an optimization that allows us to reduce route lookups from O(n * m ) to O(m + n) +/// when trying to find exit ips in our cluster +fn get_routes_hashmap(routes: Vec) -> HashMap { + let mut ret = HashMap::new(); + for r in routes { + ret.insert(r.prefix.ip(), r); + } + + ret +} + +/// Exits are ready to switch to when they are in the Registered State, we return list of exits that are +pub fn get_ready_to_switch_exits(exit_list: ExitListV2) -> Vec { + let exits = get_rita_client().exit_client.exits; + + let mut ret = vec![]; + for exit in exit_list.exit_list { + match exits.get(&exit.mesh_ip) { + Some(server) => { + if let ExitState::Registered { .. } = server.info { + ret.push(exit_identity_to_id(exit)); + } + } + None => { + error!("Exit List logic error! All entries of exit list should be setup in config!") + } + } + } + ret +} + +pub fn get_client_pub_ipv6() -> Option { + let rita_settings = settings::get_rita_client(); + let current_exit = get_current_exit(); + if let Some(exit) = current_exit { + let exit_ser = rita_settings.exit_client.exits.get(&exit); + if let Some(exit_ser) = exit_ser { + let exit_info = exit_ser.info.clone(); + + if let ExitState::Registered { our_details, .. } = exit_info { + return our_details.internet_ipv6_subnet; + } + } + } + None +} + +/// Verfies if exit has changed to reestablish wg tunnels +/// 1.) When exit instance ip has changed +/// 2.) Exit reg details have chaged +pub fn has_exit_changed( + state: LastExitStates, + selected_exit: Option, + cluster: ExitServer, +) -> bool { + let last_exit = state.last_exit; + + let instance_has_changed = !(last_exit.is_some() + && selected_exit.is_some() + && last_exit.unwrap() == selected_exit.unwrap()); + + let last_exit_details = state.last_exit_details; + let exit_reg_has_changed = + !(last_exit_details.is_some() && last_exit_details.unwrap() == cluster.info); + + instance_has_changed | exit_reg_has_changed +} + +#[cfg(test)] +mod tests { + use althea_types::{ExitVerifMode, SystemChain}; + + use super::*; + + #[test] + fn test_exit_has_changed() { + let mut exit_server = ExitServer { + exit_id: Identity { + mesh_ip: "fd00::1337".parse().unwrap(), + eth_address: "0xd2C5b6dd6ca641BE4c90565b5d3DA34C14949A53" + .parse() + .unwrap(), + wg_public_key: "V9I9yrxAqFqLV+9GeT5pnXPwk4Cxgfvl30Fv8khVGsM=" + .parse() + .unwrap(), + nickname: None, + }, + + registration_port: 3452, + wg_exit_listen_port: 59998, + + info: ExitState::New, + }; + let dummy_exit_details = ExitDetails { + server_internal_ip: "172.0.0.1".parse().unwrap(), + netmask: 0, + wg_exit_port: 123, + exit_price: 123, + exit_currency: SystemChain::Xdai, + description: "".to_string(), + verif_mode: ExitVerifMode::Off, + }; + let mut last_states = LastExitStates::default(); + + // An ip is selected and setup in last_states + let selected_exit = Some("fd00::2602".parse().unwrap()); + + assert!(has_exit_changed( + last_states.clone(), + selected_exit, + exit_server.clone() + )); + + // Last states get updated next tick + last_states.last_exit = Some("fd00::2602".parse().unwrap()); + last_states.last_exit_details = Some(exit_server.info.clone()); + assert!(!has_exit_changed( + last_states.clone(), + selected_exit, + exit_server.clone() + )); + + // Registration Details change + exit_server.info = ExitState::Registered { + general_details: dummy_exit_details.clone(), + our_details: ExitClientDetails { + client_internal_ip: "172.1.1.1".parse().unwrap(), + internet_ipv6_subnet: None, + }, + message: "".to_string(), + }; + assert!(has_exit_changed( + last_states.clone(), + selected_exit, + exit_server.clone() + )); + + // next tick last stats get updated accordingly + last_states.last_exit_details = Some(exit_server.info.clone()); + + // Registration detail for client change + exit_server.info = ExitState::Registered { + general_details: dummy_exit_details, + our_details: ExitClientDetails { + client_internal_ip: "172.1.1.14".parse().unwrap(), + internet_ipv6_subnet: None, + }, + message: "".to_string(), + }; + assert!(has_exit_changed( + last_states.clone(), + selected_exit, + exit_server.clone() + )); + + // next tick its updated accordingly + last_states.last_exit_details = Some(exit_server.info.clone()); + assert!(!has_exit_changed(last_states, selected_exit, exit_server)); + } +} diff --git a/rita_client_registration/src/client_db.rs b/rita_client_registration/src/client_db.rs index 118902391..37014bd1c 100644 --- a/rita_client_registration/src/client_db.rs +++ b/rita_client_registration/src/client_db.rs @@ -3,7 +3,7 @@ //! exit and client routers can read it to coordinate user setup and two way key exchange with the blockchain //! as the trusted party -use althea_types::{regions::Regions, ExitIdentity, Identity, SystemChain, WgKey}; +use althea_types::{regions::Regions, exit_interop::ExitIdentity, Identity, SystemChain, WgKey}; use clarity::{ abi::{encode_call, AbiToken}, utils::bytes_to_hex_str, diff --git a/rita_client_registration/src/lib.rs b/rita_client_registration/src/lib.rs index 9b5490087..e940f9504 100644 --- a/rita_client_registration/src/lib.rs +++ b/rita_client_registration/src/lib.rs @@ -1,4 +1,4 @@ -use althea_types::{ExitClientIdentity, Identity, WgKey}; +use althea_types::{exit_interop::ExitRegistrationIdentity, Identity, WgKey}; use awc::error::SendRequestError; use phonenumber::PhoneNumber; use serde::{Deserialize, Serialize}; @@ -113,7 +113,7 @@ pub struct SmsRequest { /// Handles the minutia of phone registration states pub async fn handle_sms_registration( - client: ExitClientIdentity, + client: ExitRegistrationIdentity, api_key: String, verify_profile_id: String, magic_number: Option, diff --git a/rita_common/src/utils/mod.rs b/rita_common/src/utils/mod.rs index 33345bacf..0fe1684bd 100644 --- a/rita_common/src/utils/mod.rs +++ b/rita_common/src/utils/mod.rs @@ -9,6 +9,17 @@ use babel_monitor::structs::BabeldConfig; /// throw a dead code warning. pub mod ip_increment; +/// Simple utility function to try and keep the loop running +/// at a roughly consistent speed +pub fn compute_next_loop_time(start: Instant, desired_speed: Duration) -> Duration { + let elapsed = start.elapsed(); + if elapsed > desired_speed { + Duration::from_secs(0) + } else { + desired_speed - elapsed + } +} + #[allow(dead_code)] pub fn option_convert, A>(item: Option) -> Option { item.map(|val| val.into()) diff --git a/rita_exit/src/database/mod.rs b/rita_exit/src/database/mod.rs index 7a340180f..a86b710b0 100644 --- a/rita_exit/src/database/mod.rs +++ b/rita_exit/src/database/mod.rs @@ -18,7 +18,7 @@ use althea_kernel_interface::ExitClient; use althea_types::regions::Regions; use althea_types::Identity; use althea_types::WgKey; -use althea_types::{ExitClientDetails, ExitClientIdentity, ExitDetails, ExitState, ExitVerifMode}; +use althea_types::exit_interop::{ExitClientDetails, ExitRegistrationIdentity, ExitDetails, ExitState, ExitVerifMode}; use clarity::Address; use rita_client_registration::client_db::get_registered_client_using_wgkey; use rita_client_registration::ExitSignupReturn; @@ -69,13 +69,15 @@ pub fn get_exit_info() -> ExitDetails { netmask: exit_settings.exit_network.netmask, description: exit_settings.description, verif_mode: ExitVerifMode::Phone, + server_mesh_ip: exit_settings.network.mesh_ip_v2.unwrap(), + server_wg_pubkey: exit_settings.exit_network.wg_public_key, } } /// Handles a new client registration api call. Performs a geoip lookup /// on their registration ip to make sure that they are coming from a valid gateway /// ip and then sends out an email of phone message -pub async fn signup_client(client: ExitClientIdentity) -> Result> { +pub async fn signup_client(client: ExitRegistrationIdentity) -> Result> { let exit_settings = get_rita_exit(); info!("got setup request {:?}", client); let gateway_ip = get_gateway_ip_single(client.global.mesh_ip)?; @@ -130,7 +132,7 @@ pub async fn signup_client(client: ExitClientIdentity) -> Result ExitSignupReturn { +pub async fn forward_client_signup_request(exit_client: ExitRegistrationIdentity) -> ExitSignupReturn { let url: &str; let reg_url = get_rita_exit().client_registration_url; if cfg!(feature = "dev_env") { @@ -181,7 +183,7 @@ pub async fn forward_client_signup_request(exit_client: ExitClientIdentity) -> E /// Gets the status of a client and updates it in the database pub async fn client_status( - client: ExitClientIdentity, + client: ExitRegistrationIdentity, our_address: Address, contract_addr: Address, contact: &Web3, diff --git a/rita_exit/src/error.rs b/rita_exit/src/error.rs index 15291a1d5..5baf2ec58 100644 --- a/rita_exit/src/error.rs +++ b/rita_exit/src/error.rs @@ -1,5 +1,5 @@ use althea_kernel_interface::KernelInterfaceError; -use althea_types::{error::AltheaTypesError, ExitClientIdentity}; +use althea_types::{error::AltheaTypesError, exit_interop::ExitRegistrationIdentity}; use babel_monitor::structs::BabelMonitorError; use handlebars::RenderError; use ipnetwork::IpNetworkError; @@ -13,7 +13,7 @@ use std::{ #[derive(Debug)] pub enum RitaExitError { MiscStringError(String), - EmailNotFound(Box), + EmailNotFound(Box), AddrParseError(AddrParseError), IpAddrError(IpAddr), RitaCommonError(RitaCommonError), diff --git a/rita_exit/src/network_endpoints/mod.rs b/rita_exit/src/network_endpoints/mod.rs index f143b37e5..c1f1913e1 100644 --- a/rita_exit/src/network_endpoints/mod.rs +++ b/rita_exit/src/network_endpoints/mod.rs @@ -13,14 +13,16 @@ use actix::SystemService; #[cfg(feature = "development")] use actix_web::AsyncResponder; use actix_web_async::{http::StatusCode, web::Json, HttpRequest, HttpResponse, Result}; -use althea_types::exit_identity_to_id; -use althea_types::regions::Regions; -use althea_types::ExitListV2; -use althea_types::{ - EncryptedExitClientIdentity, EncryptedExitState, ExitClientIdentity, ExitState, ExitSystemTime, +use althea_types::exit_interop::exit_identity_to_id; +use althea_types::exit_interop::EncryptedAuthMessage; +use althea_types::exit_interop::ExitListV2; +use althea_types::exit_interop::{ + EncryptedExitRegistrationIdentity, EncryptedExitState, ExitRegistrationIdentity, ExitState, + ExitSystemTime, }; -use althea_types::{EncryptedExitList, Identity}; -use althea_types::{ExitList, WgKey}; +use althea_types::regions::Regions; +use althea_types::{exit_interop::EncryptedExitList, Identity}; +use althea_types::{exit_interop::ExitList, WgKey}; use num256::Int256; use rita_client_registration::client_db::get_exits_list; use rita_common::blockchain_oracle::potential_payment_issues_detected; @@ -57,80 +59,8 @@ fn secure_setup_return( }) } -enum DecryptResult { - Success(Box), - Failure(Result, RitaExitError>), -} - -fn decrypt_exit_client_id( - val: EncryptedExitClientIdentity, - our_secretkey: &SecretKey, -) -> DecryptResult { - let their_wg_pubkey = val.pubkey; - let their_nacl_pubkey = val.pubkey.into(); - let their_nonce = Nonce(val.nonce); - let ciphertext = val.encrypted_exit_client_id; - - let decrypted_bytes = - match box_::open(&ciphertext, &their_nonce, &their_nacl_pubkey, our_secretkey) { - Ok(value) => value, - Err(e) => { - warn!( - "Error decrypting exit setup request for {} with {:?}", - their_wg_pubkey, e - ); - let state = ExitState::Denied { - message: "could not decrypt your message!".to_string(), - }; - return DecryptResult::Failure(Ok(secure_setup_return( - state, - our_secretkey, - their_nacl_pubkey, - ))); - } - }; - - let decrypted_string = match String::from_utf8(decrypted_bytes) { - Ok(value) => value, - Err(e) => { - error!( - "Error decrypting exit setup request for {} with {:?}", - their_wg_pubkey, e - ); - let state = ExitState::Denied { - message: "could not decrypt your message!".to_string(), - }; - return DecryptResult::Failure(Ok(secure_setup_return( - state, - our_secretkey, - their_nacl_pubkey, - ))); - } - }; - - let decrypted_id = match serde_json::from_str(&decrypted_string) { - Ok(value) => value, - Err(e) => { - error!( - "Error deserializing exit setup request for {} with {:?}", - their_wg_pubkey, e - ); - let state = ExitState::Denied { - message: "could not deserialize your message!".to_string(), - }; - return DecryptResult::Failure(Ok(secure_setup_return( - state, - our_secretkey, - their_nacl_pubkey, - ))); - } - }; - - DecryptResult::Success(Box::new(decrypted_id)) -} - pub async fn secure_setup_request( - request: (Json, HttpRequest), + request: (Json, HttpRequest), ) -> HttpResponse { let exit_settings = get_rita_exit(); @@ -214,7 +144,9 @@ pub async fn secure_setup_request( } } -pub async fn secure_status_request(request: Json) -> HttpResponse { +pub async fn secure_status_request( + request: Json, +) -> HttpResponse { let exit_settings = get_rita_exit(); let our_old_secretkey: WgKey = exit_settings.exit_network.wg_private_key; let our_new_secretkey = exit_settings.network.wg_private_key.unwrap(); @@ -304,7 +236,7 @@ pub async fn get_exit_timestamp_http(_req: HttpRequest) -> HttpResponse { /// if this exit fits the region and currenty requirements it will always return a list containing itself /// even if this exit is not in the smart contract. If a client is speaking with this exit then the exit /// data is in the config and this is considered to be a key exchange in and of itself. -pub async fn get_exit_list(request: Json) -> HttpResponse { +pub async fn get_exit_list(request: Json) -> HttpResponse { let exit_settings = get_rita_exit(); let our_secretkey: WgKey = exit_settings.exit_network.wg_private_key; let our_secretkey = our_secretkey.into(); @@ -378,7 +310,7 @@ pub async fn get_exit_list(request: Json) -> HttpRe /// Exit list v2, for newer router that do the fitering (region and payment type) themselves, this endpoint /// returns the entire list -pub async fn get_exit_list_v2(request: Json) -> HttpResponse { +pub async fn get_exit_list_v2(request: Json) -> HttpResponse { let exit_settings = get_rita_exit(); let our_secretkey: WgKey = match exit_settings.network.wg_private_key { Some(a) => a, @@ -425,6 +357,20 @@ pub async fn get_exit_list_v2(request: Json) -> Htt })) } +pub async fn get_exit_list_v3(request: Json) -> HttpResponse { + let exit_settings = get_rita_exit(); + let our_secretkey: WgKey = match exit_settings.network.wg_private_key { + Some(a) => a, + None => { + error!("This exit doesnt have a network wg key?"); + return HttpResponse::InternalServerError().finish(); + } + }; + let our_secretkey = our_secretkey.into(); + + let their_nacl_pubkey = request.pubkey.into(); +} + /// Used by clients to get their debt from the exits. While it is in theory possible for the /// client to totally compute their own bill it's not possible for the exit and the client /// to agree on the billed amount in the presence of packet loss. Normally Althea is pay per forward diff --git a/settings/src/client.rs b/settings/src/client.rs index 175926c12..3d53c564b 100644 --- a/settings/src/client.rs +++ b/settings/src/client.rs @@ -4,14 +4,13 @@ use crate::network::NetworkSettings; use crate::operator::OperatorSettings; use crate::payment::PaymentSettings; use crate::{json_merge, set_rita_client, setup_accepted_denoms, SettingsError}; -use althea_types::{ExitState, Identity}; +use althea_types::{exit_interop::ExitState, Identity}; use std::collections::HashSet; -use std::net::IpAddr; use std::path::{Path, PathBuf}; pub const APP_NAME: &str = "rita"; -pub const DUMMY_ROOT_IP: &str = "1.1.1.1"; +pub const EXIT_CLIENT_LISTEN_PORT: u16 = 59999; pub fn default_app_name() -> String { APP_NAME.to_string() @@ -46,48 +45,6 @@ fn default_registration_port() -> u16 { 4875 } -/// Simple struct that keeps track of details related to the exit we are currently connected to, as well as the next potential exit to switch to -#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub struct SelectedExit { - // Exit we currently forward to - pub selected_id: Option, - - // Advertised Metric of selected_id. This is different from that advertised by babel due to bias of metric being degraded by current traffic - pub selected_id_metric: Option, - - // Since our advertised metric doesnt change through babel, we measure how much the average metric degrades over time and add this to sel_id_metric - pub selected_id_degradation: Option, - - // This could be different from selected_id, we dont switch immediately to avoid route flapping - // This is what we keep track of in lazy static metric vector - pub tracking_exit: Option, -} - -/// This is the state machine for exit switching logic. Given there are three exits we track: current, best, and tracking, there are several situations we can be in -/// Given that there are several scenarios to be in, We use this enum to tracking our state during every tick -/// -/// InitialExitSetup: We have just connected to the first exit and tracking vector is empty -/// -/// ContinueCurrentReset: Best exit, current exit and tracking exit are all the same. We continue with the same exit. However our tracking vector is full, -/// so we reset it, with no change to exits -/// -/// ContinueCurrent: Same as above, but vector is not full. We continue adding metrics to tracking vector. -/// -/// SwitchExit: Current exit is different but tracking and best are same. Vec is full, we switch to best/tracking exit -/// -/// ContinueTracking: Current exit is different but tracking == best. Vec is not full, so we dont switch yet, just continue updating the tracking vector -/// -/// ResetTracking: tracking and best are diffrent. We reset timer/vector and start tracking new best -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub enum ExitSwitchingCode { - InitialExitSetup, - ContinueCurrentReset, - ContinueCurrent, - SwitchExit, - ContinueTracking, - ResetTracking, -} - fn exit_db_smart_contract_on_xdai() -> String { "0x29a3800C28dc133f864C22533B649704c6CD7e15".to_string() } diff --git a/settings/src/exit.rs b/settings/src/exit.rs index bfa462fb1..dc76397a7 100644 --- a/settings/src/exit.rs +++ b/settings/src/exit.rs @@ -2,7 +2,7 @@ use crate::localization::LocalizationSettings; use crate::network::NetworkSettings; use crate::payment::PaymentSettings; use crate::{json_merge, set_rita_exit, setup_accepted_denoms, SettingsError}; -use althea_types::{regions::Regions, ExitIdentity, FromStr, Identity, WgKey}; +use althea_types::{regions::Regions, exit_interop::ExitIdentity, FromStr, Identity, WgKey}; use clarity::Address; use ipnetwork::IpNetwork; use std::collections::HashSet;