Skip to content

Commit

Permalink
Overhaul exit switcher, region management, and exit selection
Browse files Browse the repository at this point in the history
This patch represents a continued overhaul of the exit manager
functionality. Two major things are occuring a simplification and an
overall change in the functionality of the code to track a single
cluster of exits managed by a smart contract instead of many clusters of
exits managed by ip ranges.

This implies a lot of touch point changes, to exactly how logic changes.
This makes up a significant part of this commit.

The biggest single change in this commit is a total re-write of the exit
switching functionality which was designed for subnet based exits
originally and inherited complexity that is no longer needed given our
new reality of exits with a fixed list of members. The new simplified
design maintains the vast majority of the old feature set but does not
for example implement the blacklisting feature, where exits that where
online but enough to be in babel but otherwise not working correctly are
removed from the selection process.
  • Loading branch information
jkilpatr committed Aug 29, 2024
1 parent cfd3f23 commit 046f0c8
Show file tree
Hide file tree
Showing 19 changed files with 725 additions and 1,434 deletions.
17 changes: 17 additions & 0 deletions althea_types/src/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ impl Hash for Identity {
}
}

/// This struct represents a single exit server. It contains all the details
/// needed to contact and register to the exit.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExitIdentity {
/// This is the unique identity of the exit. Previously exit
/// had a shared wg key and mesh ip, this struct needs to have unique
/// meship, wgkey and ethaddress for each entry
pub mesh_ip: IpAddr,
pub wg_key: WgKey,
pub eth_addr: Address,
Expand Down Expand Up @@ -146,6 +151,17 @@ impl From<ExitIdentity> for Identity {
}
}

impl From<&ExitIdentity> for Identity {
fn from(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,
}
}
}

pub fn exit_identity_to_id(exit_id: ExitIdentity) -> Identity {
exit_id.into()
}
Expand Down Expand Up @@ -287,6 +303,7 @@ pub enum ExitState {
/// we are currently registered and operating, update this state
/// incase the exit for example wants to assign us a new ip
Registered {
identity: Box<ExitIdentity>,
general_details: ExitDetails,
our_details: ExitClientDetails,
message: String,
Expand Down
8 changes: 6 additions & 2 deletions integration_tests/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,14 @@ pub fn get_default_settings(
let exit_id = TEST_EXIT_DETAILS.get(&instance_name).unwrap();
exit_servers.insert(
exit_id.exit_id.mesh_ip,
settings::client::ExitServer {
exit_id: exit_id.exit_id,
ExitIdentity {
mesh_ip: exit_id.exit_id.mesh_ip,
wg_key: exit_id.wg_priv_key,
eth_addr: exit_id.exit_id.eth_address,
registration_port: exit.exit_network.exit_hello_port,
wg_exit_listen_port: exit.exit_network.wg_v2_tunnel_port,
allowed_regions: HashSet::new(),
payment_types: HashSet::new(),
},
);
}
Expand Down
25 changes: 13 additions & 12 deletions rita_client/src/dashboard/exits.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
//! The Exit info endpoint gathers infromation about exit status and presents it to the dashbaord.

use crate::exit_manager::get_current_exit;
use crate::exit_manager::requests::exit_setup_request;
use crate::heartbeat::get_selected_exit_server;
use crate::RitaClientError;
use actix_web_async::http::StatusCode;
use actix_web_async::{web::Path, HttpRequest, HttpResponse};
use althea_types::ExitState;
use althea_types::{ExitIdentity, ExitState};
use babel_monitor::open_babel_stream;
use babel_monitor::parse_routes;
use babel_monitor::parsing::do_we_have_route;
use rita_common::KI;
use settings::client::ExitServer;
use std::collections::HashMap;
use std::time::Duration;

#[derive(Serialize)]
pub struct ExitInfo {
nickname: String,
exit_settings: ExitServer,
exit_settings: ExitIdentity,
is_selected: bool,
have_route: bool,
is_reachable: bool,
Expand All @@ -29,7 +28,7 @@ pub struct GetExitInfo;
const EXIT_PING_TIMEOUT: Duration = Duration::from_millis(200);

/// Checks if the provided exit is selected
fn is_selected(exit: &ExitServer, current_exit: Option<ExitServer>) -> bool {
fn is_selected(exit: &ExitIdentity, current_exit: Option<ExitIdentity>) -> bool {
match current_exit {
None => false,
Some(i) => i == *exit,
Expand All @@ -39,8 +38,8 @@ fn is_selected(exit: &ExitServer, current_exit: Option<ExitServer>) -> bool {
/// Determines if the provided exit is currently selected, if it's setup, and then if it can be reached over
/// the exit tunnel via a ping
fn is_tunnel_working(
exit: &ExitServer,
current_exit: Option<ExitServer>,
exit: &ExitIdentity,
current_exit: Option<ExitIdentity>,
exit_status: ExitState,
) -> bool {
match (current_exit.clone(), is_selected(exit, current_exit)) {
Expand Down Expand Up @@ -69,10 +68,10 @@ pub fn dashboard_get_exit_info() -> Result<Vec<ExitInfo>, RitaClientError> {
let rita_client = settings::get_rita_client();
let exit_client = rita_client.exit_client;
let reg_state = exit_client.registration_state;
let current_exit = get_selected_exit_server();
let current_exit = get_current_exit();

for exit in exit_client.bootstrapping_exits.clone().into_iter() {
let selected = is_selected(&exit.1, current_exit.clone());
let selected = is_selected(&exit.1, Some(current_exit.clone()));
info!("Trying to get exit: {}", exit.0.clone());
let route_ip = exit.0;
let have_route = do_we_have_route(&route_ip, &route_table_sample)?;
Expand All @@ -85,9 +84,11 @@ pub fn dashboard_get_exit_info() -> Result<Vec<ExitInfo>, RitaClientError> {
false
};
let tunnel_working = match (have_route, selected) {
(true, true) => {
is_tunnel_working(&exit.1, current_exit.clone(), reg_state.clone())
}
(true, true) => is_tunnel_working(
&exit.1,
Some(current_exit.clone()),
reg_state.clone(),
),
_ => false,
};

Expand Down
4 changes: 2 additions & 2 deletions rita_client/src/dashboard/neighbors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rita_common::tunnel_manager::{tm_get_neighbors, Neighbor};
use std::collections::HashMap;
use std::time::Duration;

use crate::exit_manager::get_current_exit;
use crate::exit_manager::get_current_exit_ip;
use crate::RitaClientError;

const BABEL_TIMEOUT: Duration = Duration::from_secs(5);
Expand Down Expand Up @@ -107,7 +107,7 @@ fn generate_neighbors_list(
}
let neigh_route = maybe_route.unwrap();

let exit_ip = get_current_exit();
let exit_ip = get_current_exit_ip();
if let Some(stats_entry) = stats.get(&neigh_route.iface) {
let maybe_exit_route =
get_route_via_neigh(identity.mesh_ip, exit_ip, &route_table_sample);
Expand Down
86 changes: 37 additions & 49 deletions rita_client/src/exit_manager/exit_loop.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
use super::exit_switcher::{get_babel_routes, set_best_exit};
use super::utils::get_routes_hashmap;
use super::{ExitManager, LastExitStates};
use super::utils::get_babel_routes;
use super::{get_current_exit, ExitManager, LastExitStates};
use crate::exit_manager::exit_selector::select_best_exit;
use crate::exit_manager::requests::exit_status_request;
use crate::exit_manager::requests::get_exit_list;
use crate::exit_manager::time_sync::maybe_set_local_to_exit_time;
use crate::exit_manager::utils::{
add_exits_to_exit_server_list, correct_default_route, get_client_pub_ipv6, has_exit_changed,
linux_setup_exit_tunnel, remove_nat, restore_nat,
};
use crate::exit_manager::{get_current_exit, get_full_selected_exit, set_exit_list};
use crate::exit_manager::{get_current_exit_ip, set_exit_list};
use crate::heartbeat::get_exit_registration_state;
use crate::traffic_watcher::{query_exit_debts, QueryExitDebts};
use actix_async::System as AsyncSystem;
use althea_types::ExitState;
use althea_types::{ExitDetails, ExitListV2};
use babel_monitor::structs::Route;

use althea_types::{ExitIdentity, ExitState};
use rita_common::blockchain_oracle::low_balance;
use rita_common::KI;
use settings::client::ExitServer;

use std::collections::HashMap;
use std::net::IpAddr;
use std::thread;
use std::time::{Duration, Instant};

const EXIT_LOOP_SPEED: Duration = Duration::from_secs(5);
pub const EXIT_LOOP_SPEED: Duration = Duration::from_secs(5);
/// How often we make a exit status request for registered exits. Prevents us from bogging up exit processing
/// power
const STATUS_REQUEST_QUERY: Duration = Duration::from_secs(600);
Expand All @@ -40,14 +35,15 @@ pub fn start_exit_manager_loop() {
while let Err(e) = {
thread::spawn(move || {
// Our Exit state variable
let em_state = &mut ExitManager::default();
let em_state = &mut ExitManager::new(get_current_exit());
let babel_port = settings::get_rita_client().network.babel_port;
let runner = AsyncSystem::new();

runner.block_on(async move {
loop {
let start = Instant::now();

exit_manager_loop(em_state).await;
exit_manager_loop(em_state, babel_port).await;

// sleep until it has been FAST_LOOP_SPEED seconds from start, whenever that may be
// if it has been more than FAST_LOOP_SPEED seconds from start, go right ahead
Expand Down Expand Up @@ -79,11 +75,11 @@ pub fn start_exit_manager_loop() {
}

/// This function manages the lifecycle of exits, including updating our registration states, querying exit debts, and setting up exit tunnels.
async fn exit_manager_loop(em_state: &mut ExitManager) {
async fn exit_manager_loop(em_state: &mut ExitManager, babel_port: u16) {
info!("Exit_Switcher: exit manager tick");
let client_can_use_free_tier = { settings::get_rita_client().payment.client_can_use_free_tier };
let rita_client = settings::get_rita_client();
let current_exit_ip = get_current_exit();
let current_exit_ip = get_current_exit_ip();

let mut exits = rita_client.exit_client.bootstrapping_exits;

Expand All @@ -98,18 +94,21 @@ async fn exit_manager_loop(em_state: &mut ExitManager) {
let registration_state = get_exit_registration_state();
if let Some(general_details) = registration_state.clone().general_details() {
info!("We have details for the selected exit!");
let selected_exit = handle_exit_switching(em_state, current_exit_ip).await;
// TODO setup exit using old selected exit the first run, of the loop, right now we force a wait
// for this request to complete before we get things setup, we can store the ExitIdentity somewhere
handle_exit_switching(em_state, current_exit_ip, babel_port).await;

info!("Exit_Switcher: After selecting best exit this tick, we have selected_exit_details: {:?}", get_full_selected_exit());
setup_exit_tunnel(
selected_exit,
em_state.exit_switcher_state.currently_selected.clone(),
general_details,
em_state.last_exit_state.clone(),
);

// Set last state vairables
em_state.last_exit_state.last_exit = Some(selected_exit);
em_state.last_exit_state.last_exit_details = Some(registration_state);
em_state.last_exit_state = Some(LastExitStates {
last_exit: em_state.exit_switcher_state.currently_selected.clone(),
last_exit_details: registration_state.clone(),
});

// check the exit's time and update locally if it's very different
maybe_set_local_to_exit_time(exit.clone()).await;
Expand All @@ -127,7 +126,11 @@ async fn exit_manager_loop(em_state: &mut ExitManager) {
}

/// This function handles deciding if we need to switch exits, the new selected exit is returned. If no exit is selected, the current exit is returned.
async fn handle_exit_switching(em_state: &mut ExitManager, current_exit_id: IpAddr) -> IpAddr {
async fn handle_exit_switching(
em_state: &mut ExitManager,
current_exit_id: IpAddr,
babel_port: u16,
) {
// Get cluster exit list. This is saved locally and updated every tick depending on what exit we connect to.
// When it is empty, it means an exit we connected to went down, and we use the list from memory to connect to a new instance
let exit_list = match get_exit_list(current_exit_id).await {
Expand Down Expand Up @@ -156,37 +159,22 @@ async fn handle_exit_switching(em_state: &mut ExitManager, current_exit_id: IpAd
}
// Calling set best exit function, this looks though a list of exit in a cluster, does some math, and determines what exit we should connect to
let exit_list = em_state.exit_list.clone();
info!("Exit_Switcher: Calling set best exit");
trace!("Using exit list: {:?}", exit_list);
match set_best_exit(em_state, exit_list.into_identities(), prep_babel_routes()) {
Ok(a) => a,
Err(e) => {
error!("Exit_Switcher: Unable to select best exit: {:?}", e);
current_exit_id
}
}
}

fn prep_babel_routes() -> HashMap<IpAddr, Route> {
let babel_port = settings::get_rita_client().network.babel_port;
match get_babel_routes(babel_port) {
Ok(a) => get_routes_hashmap(a),
Err(_) => {
warn!("No babel routes present to setup an exit");
get_routes_hashmap(Vec::new())
}
}
select_best_exit(&mut em_state.exit_switcher_state, exit_list, babel_port)
}

fn setup_exit_tunnel(
selected_exit: IpAddr,
selected_exit: ExitIdentity,
general_details: &ExitDetails,
last_exit_states: LastExitStates,
last_exit_states: Option<LastExitStates>,
) -> bool {
// Determine states to setup tunnels
let registration_state = get_exit_registration_state();
let exit_has_changed =
has_exit_changed(last_exit_states, selected_exit, registration_state.clone());
let exit_has_changed = has_exit_changed(
last_exit_states,
selected_exit.clone(),
registration_state.clone(),
);
let signed_up_for_exit = registration_state.our_details();

let default_route = match KI.get_default_route() {
Expand All @@ -202,13 +190,13 @@ fn setup_exit_tunnel(
match (signed_up_for_exit, exit_has_changed, correct_default_route) {
(Some(details), true, _) => {
trace!("Exit change, setting up exit tunnel");
linux_setup_exit_tunnel(&general_details.clone(), details)
linux_setup_exit_tunnel(selected_exit, &general_details.clone(), details)
.expect("failure setting up exit tunnel");
true
}
(Some(details), false, false) => {
trace!("DHCP overwrite setup exit tunnel again");
linux_setup_exit_tunnel(&general_details.clone(), details)
linux_setup_exit_tunnel(selected_exit, &general_details.clone(), details)
.expect("failure setting up exit tunnel");
true
}
Expand All @@ -221,15 +209,15 @@ fn setup_exit_tunnel(
}
}

async fn run_exit_billing(general_details: &ExitDetails, exit: &ExitServer) {
async fn run_exit_billing(general_details: &ExitDetails, exit: &ExitIdentity) {
if get_exit_registration_state().our_details().is_none() {
return;
}

let exit_price = general_details.clone().exit_price;
let exit_internal_addr = general_details.clone().server_internal_ip;
let exit_port = exit.registration_port;
let exit_id = exit.exit_id;
let exit_id = exit.into();
let babel_port = settings::get_rita_client().network.babel_port;
info!("We are signed up for the selected exit!");
let routes = match get_babel_routes(babel_port) {
Expand Down Expand Up @@ -290,7 +278,7 @@ async fn handle_exit_status_request(em_state: &mut ExitManager) {
// code that manages requesting details, we make this query to a single exit in a cluster.
// as they will all have the same registration state, but different individual ip or other info
let mut exit_status_requested = false;
let k = get_current_exit();
let k = get_current_exit_ip();
let registration_state = get_exit_registration_state();
match registration_state {
// Once one exit is registered, this moves all exits from New -> Registered
Expand Down
Loading

0 comments on commit 046f0c8

Please sign in to comment.