Skip to content

Commit

Permalink
WIP: Phase 2 refactor exit manager
Browse files Browse the repository at this point in the history
Big changes
 - identities should be ipv6 type for the ip
 - integrate rust babeld
 - no billing mode

Small Changes
- use a single uniform function for keeping track of loop time
- update all the deps
- use one routes and neighbors call everywhere
  • Loading branch information
jkilpatr committed Feb 13, 2024
1 parent e9f266c commit 2a96ddc
Show file tree
Hide file tree
Showing 14 changed files with 1,136 additions and 875 deletions.
2 changes: 2 additions & 0 deletions althea_types/src/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ fn default_verif_mode() -> ExitVerifMode {

#[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,
Expand Down
25 changes: 25 additions & 0 deletions babel_monitor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,31 @@ pub fn parse_routes(stream: &mut TcpStream) -> Result<Vec<Route>, BabelMonitorEr
parse_routes_sync(babel_out)
}

pub fn parse_routes_and_neighs(stream: &mut TcpStream) -> Result<RoutesAndNeighbors, BabelMonitorError> {
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<Route>,
pub neighs: Vec<Neighbor>,
}

/// 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<RoutesAndNeighbors, BabelMonitorError> {
let mut stream = open_babel_stream(babel_port, timeout)?;
parse_routes_and_neighs(&mut stream)
}


#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 4 additions & 0 deletions rita_client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LoggerError> for RitaClientError {
Expand Down Expand Up @@ -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?"),
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions rita_client/src/exit_manager/encryption_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::RitaClientError;
use actix_web_async::Result;
use althea_types::EncryptedExitList;
use althea_types::ExitListV2;
use sodiumoxide::crypto::box_;
use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::Nonce;
use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::PublicKey;

fn decrypt_exit_list(
exit_list: EncryptedExitList,
exit_pubkey: PublicKey,
) -> Result<ExitListV2, RitaClientError> {
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)
}
88 changes: 88 additions & 0 deletions rita_client/src/exit_manager/exit_manager_loop.rs
Original file line number Diff line number Diff line change
@@ -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();
}
});
}


Loading

0 comments on commit 2a96ddc

Please sign in to comment.