From dc7531689c2a5ec5b4c757f95a87f576c4d4020c Mon Sep 17 00:00:00 2001 From: Vecna Date: Tue, 18 Jun 2024 07:27:13 -0400 Subject: [PATCH] Move simulation code to its own repo --- Cargo.toml | 7 +- src/bin/simulation.rs | 513 ----------------------------- src/lib.rs | 9 - src/simulation/bridge.rs | 84 ----- src/simulation/censor.rs | 251 --------------- src/simulation/config.rs | 30 -- src/simulation/user.rs | 676 --------------------------------------- 7 files changed, 1 insertion(+), 1569 deletions(-) delete mode 100644 src/bin/simulation.rs delete mode 100644 src/simulation/bridge.rs delete mode 100644 src/simulation/censor.rs delete mode 100644 src/simulation/config.rs delete mode 100644 src/simulation/user.rs diff --git a/Cargo.toml b/Cargo.toml index a1e0b68..47ab9eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ hyper-rustls = "0.26.0" hyper-util = { version = "0.1", features = ["full"] } julianday = "1.2.0" lazy_static = "1" -lox_cli = { git = "https://git-crysp.uwaterloo.ca/vvecna/lox_cli.git", version = "0.1", optional = true } lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" } memory-stats = { version = "1.0.0", optional = true } nalgebra = "0.29" @@ -47,8 +46,4 @@ base64 = "0.21.7" faketime = "0.2" [features] -simulation = ["faketime", "lox_cli", "memory-stats"] - -[[bin]] -name = "simulation" -required-features = ["simulation"] +simulation = ["faketime", "memory-stats"] diff --git a/src/bin/simulation.rs b/src/bin/simulation.rs deleted file mode 100644 index a9438f0..0000000 --- a/src/bin/simulation.rs +++ /dev/null @@ -1,513 +0,0 @@ -// Before running this, run: -// 1. rdsys -// 2. lox-distributor -// 3. troll-patrol with the feature "simulation" - -use troll_patrol::{ - extra_info::ExtraInfo, - get_date, increment_simulated_date, set_simulated_date, - simulation::{ - bridge::Bridge, - censor::{self, Censor}, - config::Config as SConfig, - extra_infos_server, - user::User, - }, -}; - -use clap::Parser; -use lox_cli::{networking::*, *}; -use lox_library::proto::{level_up::LEVEL_INTERVAL, trust_promotion::UNTRUSTED_INTERVAL}; -use memory_stats::memory_stats; -use rand::{prelude::SliceRandom, Rng}; -use serde::Deserialize; -use std::{ - collections::{HashMap, HashSet}, - fs::File, - io::BufReader, - path::PathBuf, - time::Duration, -}; -use tokio::{spawn, time::sleep}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - /// Name/path of the configuration file - #[arg(short, long, default_value = "simulation_config.json")] - config: PathBuf, -} - -#[derive(Debug, Deserialize)] -pub struct Config { - pub la_port: u16, - pub la_test_port: u16, - pub tp_port: u16, - pub tp_test_port: u16, - pub censor_secrecy: censor::Secrecy, - pub censor_speed: censor::Speed, - pub censor_event_duration: u32, - pub censor_totality: censor::Totality, - pub censor_partial_blocking_percent: f64, - pub country: String, - pub min_new_users_per_day: u32, - pub max_new_users_per_day: u32, - // How many days to simulate - pub num_days: u32, - // We start with this many level 4 users - pub num_initial_trusted_users: u32, - pub one_positive_report_per_cred: bool, - pub prob_censor_gets_invite: f64, - pub prob_connection_fails: f64, - pub prob_user_invites_friend: f64, - pub prob_user_is_censor: f64, - pub prob_user_submits_reports: f64, - pub prob_user_treats_throttling_as_blocking: f64, -} - -#[tokio::main] -pub async fn main() { - let args: Args = Args::parse(); - - let config: Config = serde_json::from_reader(BufReader::new( - File::open(&args.config).expect("Could not read config file"), - )) - .expect("Reading config file from JSON failed"); - - let la_net = HyperNet { - hostname: format!("http://localhost:{}", config.la_port), - }; - let la_net_test = HyperNet { - hostname: format!("http://localhost:{}", config.la_test_port), - }; - let tp_net = HyperNet { - hostname: format!("http://localhost:{}", config.tp_port), - }; - let tp_net_test = HyperNet { - hostname: format!("http://localhost:{}", config.tp_test_port), - }; - let extra_infos_net = HyperNet { - hostname: "http://localhost:8004".to_string(), - }; - - let la_pubkeys = get_lox_auth_keys(&la_net).await.unwrap(); - - let sconfig = SConfig { - la_pubkeys, - la_net, - tp_net, - censor_secrecy: config.censor_secrecy, - censor_speed: config.censor_speed, - censor_event_duration: config.censor_event_duration, - censor_totality: config.censor_totality, - censor_partial_blocking_percent: config.censor_partial_blocking_percent, - country: config.country, - one_positive_report_per_cred: config.one_positive_report_per_cred, - prob_censor_gets_invite: config.prob_censor_gets_invite, - prob_connection_fails: config.prob_connection_fails, - prob_user_invites_friend: config.prob_user_invites_friend, - prob_user_is_censor: config.prob_user_is_censor, - prob_user_submits_reports: config.prob_user_submits_reports, - prob_user_treats_throttling_as_blocking: config.prob_user_treats_throttling_as_blocking, - }; - - let mut rng = rand::thread_rng(); - - // Set up censor - let mut censor = Censor::new(&sconfig); - - // Set up bridges (no bridges yet) - let mut bridges = HashMap::<[u8; 20], Bridge>::new(); - - // Set up users - let mut users = Vec::::new(); - - if config.num_initial_trusted_users > 0 { - // Add some number of trusted users initially - for _ in 0..config.num_initial_trusted_users { - let new_user = User::trusted_user(&sconfig).await; - if new_user.is_ok() { - users.push(new_user.unwrap()); - } - } - - // Level trusted users up to level 4 - - // Advance LA's time - let result = la_net_test - .request( - "/advancedays".to_string(), - serde_json::to_string(&(UNTRUSTED_INTERVAL as u16)) - .unwrap() - .into(), - ) - .await; - if result.is_ok() { - result.unwrap(); - } else { - eprintln!("Failed to advance time for LA"); - } - - // Advance simulated time - set_simulated_date(get_date() + UNTRUSTED_INTERVAL); - - for user in &mut users { - let migcred = trust_promotion( - &sconfig.la_net, - &user.primary_cred, - get_lox_pub(&sconfig.la_pubkeys), - ) - .await; - if migcred.is_ok() { - let new_cred = trust_migration( - &sconfig.la_net, - &user.primary_cred, - &migcred.unwrap(), - get_lox_pub(&sconfig.la_pubkeys), - get_migration_pub(&sconfig.la_pubkeys), - ) - .await; - if new_cred.is_ok() { - user.primary_cred = new_cred.unwrap(); - } - } - } - - for i in 1..LEVEL_INTERVAL.len() - 2 { - // Advance LA's time - let result = la_net_test - .request( - "/advancedays".to_string(), - serde_json::to_string(&(LEVEL_INTERVAL[i] as u16)) - .unwrap() - .into(), - ) - .await; - if result.is_ok() { - result.unwrap(); - } else { - eprintln!("Failed to advance time for LA"); - } - - // Advance simulated time - set_simulated_date(get_date() + LEVEL_INTERVAL[i]); - - for user in &mut users { - let reachcred_res = get_bucket(&sconfig.la_net, &user.primary_cred).await; - if reachcred_res.is_ok() { - let reachcred = reachcred_res.unwrap().1; - if reachcred.is_some() { - let new_cred = level_up( - &sconfig.la_net, - &user.primary_cred, - &reachcred.unwrap(), - get_lox_pub(&sconfig.la_pubkeys), - get_reachability_pub(&sconfig.la_pubkeys), - ) - .await; - if new_cred.is_ok() { - user.primary_cred = new_cred.unwrap(); - } else { - eprintln!("Failed to level up trusted user's credential at start"); - } - } else { - eprintln!("Failed to level up trusted user's credential at start"); - } - } - } - } - - // Advance LA's time to tomorrow - let result = la_net_test - .request( - "/advancedays".to_string(), - serde_json::to_string(&(1 as u16)).unwrap().into(), - ) - .await; - if result.is_ok() { - result.unwrap(); - } else { - eprintln!("Failed to advance time for LA"); - } - - // Advance simulated time to tomorrow - increment_simulated_date(); - } - - // Set up extra-infos server - spawn(async move { - extra_infos_server::server().await; - }); - sleep(Duration::from_millis(1)).await; - - let mut false_neg = 0; - let mut false_pos = 0; - let mut true_neg = 0; - let mut true_pos = 0; - - // Track memory use during simulation - let mut max_physical_mem = 0; - let mut max_virtual_mem = 0; - - // Main loop - for day in 1..=config.num_days { - println!("Starting day {} of the simulation", day); - println!( - " We have {} users and {} bridges", - users.len(), - bridges.len() - ); - println!( - " The censor has learned {} bridges", - censor.known_bridges.len() - ); - println!(" Accuracy thus far:"); - println!(" True Positives: {}", true_pos); - println!(" True Negatives: {}", true_neg); - println!(" False Positives: {}", false_pos); - println!(" False Negatives: {}", false_neg); - - if let Some(usage) = memory_stats() { - if usage.physical_mem > max_physical_mem { - max_physical_mem = usage.physical_mem; - } - if usage.virtual_mem > max_virtual_mem { - max_virtual_mem = usage.virtual_mem; - } - } else { - println!("Failed to get the current memory usage"); - } - - // USER TASKS - - // Number of users who want to join today - let mut num_users_requesting_invites: u32 = - rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day); - - // How many of the new users are censors? - let mut num_new_censor_users = 0; - for _ in 0..num_users_requesting_invites { - let num: f64 = rng.gen_range(0.0..1.0); - if num < config.prob_user_is_censor { - num_new_censor_users += 1; - num_users_requesting_invites -= 1; - } - } - - // Determine whether each new censor user can get an invite from - // an existing trusted user or needs to join via open-entry - // invite. Note: We still favor honest users by giving them - // invites *first*. This means if only a small number of invites - // are available, the censor may still not get invited. - let mut num_censor_invitations = 0; - for _ in 0..num_new_censor_users { - let num: f64 = rng.gen_range(0.0..1.0); - if num < config.prob_censor_gets_invite { - num_censor_invitations += 1; - num_new_censor_users -= 1; - } - } - - let mut new_users = Vec::::new(); - - // Shuffle users so they act in a random order - users.shuffle(&mut rng); - - // Users do daily actions - for user in &mut users { - let invited_friends = user - .daily_tasks( - &sconfig, - num_users_requesting_invites, - num_censor_invitations, - &mut bridges, - &mut censor, - ) - .await; - - if invited_friends.is_ok() { - let mut invited_friends = invited_friends.unwrap(); - if invited_friends.len() > 0 { - if !user.is_censor { - // Censors always invite as many censor friends - // as possible. Honest users may invite honest - // friends, or they may accidentally invite - // censor friends. - for inv_friend in &invited_friends { - if inv_friend.is_censor { - num_censor_invitations -= 1; - } else { - num_users_requesting_invites -= 1; - } - } - } - // If this user invited any friends, add them to the - // list of users - new_users.append(&mut invited_friends); - } - } - } - - // Add new users - users.append(&mut new_users); - - // If any users couldn't get invites, they join with open-entry - // invitations - for _ in 0..num_users_requesting_invites { - let user = User::new(&sconfig, false).await; - if user.is_ok() { - users.push(user.unwrap()); - } else { - eprintln!("Failed to create new user."); - } - } - - // If any censor users couldn't get invites, they also join with - // open-entry invitations - for _ in 0..(num_new_censor_users + num_censor_invitations) { - let user = User::new(&sconfig, true).await; - if user.is_ok() { - users.push(user.unwrap()); - } else { - eprintln!("Failed to create new censor user."); - } - } - - // CENSOR TASKS - censor.end_of_day_tasks(&sconfig, &mut bridges).await; - - // BRIDGE TASKS - let mut new_extra_infos = HashSet::::new(); - for (_, bridge) in bridges.iter_mut() { - // Bridge reports its connections for the day - new_extra_infos.insert(bridge.gen_extra_info(&sconfig.country)); - - // Bridge resets for tomorrow - bridge.reset_for_tomorrow(); - } - - // Publish all the bridges' extra-infos for today - let result = extra_infos_net - .request( - "/add".to_string(), - serde_json::to_string(&new_extra_infos).unwrap().into(), - ) - .await; - if result.is_ok() { - result.unwrap(); - } else { - eprintln!("Failed to publish new extra-infos"); - } - - // TROLL PATROL TASKS - let new_blockages_resp = tp_net_test.request("/update".to_string(), vec![]).await; - let new_blockages = match new_blockages_resp { - Ok(resp) => match serde_json::from_slice(&resp) { - Ok(new_blockages) => new_blockages, - Err(e) => { - eprintln!("Failed to deserialize new blockages, error: {}", e); - HashMap::>::new() - } - }, - Err(e) => { - eprintln!( - "Failed to get new blockages from Troll Patrol, error: {}", - e - ); - HashMap::>::new() - } - }; - - // Since we have only one censor, just convert to a set of bridges - let mut blocked_bridges = HashSet::<[u8; 20]>::new(); - for (bridge, ccs) in new_blockages { - let fingerprint = array_bytes::hex2array(bridge).unwrap(); - if ccs.contains(&sconfig.country) { - blocked_bridges.insert(fingerprint); - } - } - - for (fingerprint, bridge) in &mut bridges { - let detected_blocked = blocked_bridges.contains(fingerprint); - - // If this is the first day Troll Patrol has determined this - // bridge is blocked, note that for stats - if detected_blocked && bridge.first_detected_blocked == 0 { - bridge.first_detected_blocked = get_date(); - } - - // Check if censor actually blocks this bridge - let really_blocked = censor.blocks_bridge(&sconfig, fingerprint); - if really_blocked && bridge.first_blocked == 0 { - bridge.first_blocked = get_date(); - } - if detected_blocked && really_blocked { - true_pos += 1; - } else if detected_blocked { - false_pos += 1; - } else if really_blocked { - false_neg += 1; - } else { - true_neg += 1; - } - } - - // LOX AUTHORITY TASKS - - // Advance LA's time to tomorrow - let result = la_net_test - .request( - "/advancedays".to_string(), - serde_json::to_string(&(1 as u16)).unwrap().into(), - ) - .await; - if result.is_ok() { - result.unwrap(); - } else { - eprintln!("Failed to advance time for LA"); - } - - // SIMULATION TASKS - - // Advance simulated time to tomorrow - increment_simulated_date(); - } - - // Print various information about the simulation run - println!( - "\nSimulation ended with {} users and {} bridges", - users.len(), - bridges.len() - ); - println!("The censor learned {} bridges", censor.known_bridges.len()); - - println!( - "\nMaximum physical memory usage during simulation: {}", - max_physical_mem - ); - println!( - "Maximum virtual memory usage during simulation: {}\n", - max_virtual_mem - ); - - println!("True Positives: {}", true_pos); - println!("True Negatives: {}", true_neg); - println!("False Positives: {}", false_pos); - println!("False Negatives: {}", false_neg); - - println!("\nFull stats per bridge:"); - - println!( - "Fingerprint,first_distributed,first_blocked,first_detected_blocked,first_positive_report" - ); - for (fingerprint, bridge) in bridges { - println!( - "{},{},{},{},{}", - array_bytes::bytes2hex("", fingerprint), - bridge.first_distributed, - bridge.first_blocked, - bridge.first_detected_blocked, - bridge.first_positive_report - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 9854a34..7bdfa18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,15 +27,6 @@ pub mod negative_report; pub mod positive_report; pub mod request_handler; -#[cfg(feature = "simulation")] -pub mod simulation { - pub mod bridge; - pub mod censor; - pub mod config; - pub mod extra_infos_server; - pub mod user; -} - #[cfg(test)] pub mod simulation { pub mod extra_infos_server; diff --git a/src/simulation/bridge.rs b/src/simulation/bridge.rs deleted file mode 100644 index 8b01cf3..0000000 --- a/src/simulation/bridge.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{extra_info::ExtraInfo, get_date}; -use lox_library::bridge_table::BridgeLine; -use std::collections::BTreeMap; - -// The Bridge struct only tracks data for today -pub struct Bridge { - pub fingerprint: [u8; 20], - - // The following four values are Julian dates used to track - // accuracy of the Troll Patrol system. A value of 0 is used to - // indicate this event *has not happened yet*. Note that 0 is not a - // date we will ever encounter. - - // Date the bridge was first distributed to a user, i.e., the date - // we created this Bridge object - pub first_distributed: u32, - - // First date a censor blocked this bridge - pub first_blocked: u32, - - // First date Troll Patrol detected that this bridge was blocked - // (whether or not it was correct) - pub first_detected_blocked: u32, - - // First date Troll Patrol received a positive report for this - // bridge (for identifying stage three) - pub first_positive_report: u32, - - pub real_connections: u32, - pub total_connections: u32, -} - -impl Bridge { - pub fn new(fingerprint: &[u8; 20]) -> Self { - Self { - fingerprint: *fingerprint, - first_distributed: get_date(), - first_blocked: 0, - first_detected_blocked: 0, - first_positive_report: 0, - real_connections: 0, - total_connections: 0, - } - } - - pub fn from_bridge_line(bridgeline: &BridgeLine) -> Self { - Self::new(&bridgeline.get_hashed_fingerprint()) - } - - pub fn connect_real(&mut self) { - self.real_connections += 1; - self.total_connections += 1; - } - - pub fn connect_total(&mut self) { - self.total_connections += 1; - } - - // Let the censor simulate a bunch of connections at once - pub fn censor_flood(&mut self, num_connections: u32) { - self.total_connections += num_connections; - } - - // Generate an extra-info report for today - pub fn gen_extra_info(&self, country: &str) -> ExtraInfo { - let mut bridge_ips = BTreeMap::::new(); - // Round up to a multiple of 8 - let rounded_connection_count = - self.total_connections + 7 - (self.total_connections + 7) % 8; - //let rounded_connection_count = (self.total_connections + 7) / 8 * 8; - bridge_ips.insert(country.to_string(), rounded_connection_count); - ExtraInfo { - nickname: String::from("simulation-bridge"), - fingerprint: self.fingerprint, - date: get_date(), - bridge_ips, - } - } - - pub fn reset_for_tomorrow(&mut self) { - self.real_connections = 0; - self.total_connections = 0; - } -} diff --git a/src/simulation/censor.rs b/src/simulation/censor.rs deleted file mode 100644 index ba8f4e8..0000000 --- a/src/simulation/censor.rs +++ /dev/null @@ -1,251 +0,0 @@ -use crate::{ - get_date, - simulation::{bridge::Bridge, config::Config}, - PositiveReport, -}; - -use lox_cli::{get_lox_pub, networking::Networking}; -use lox_library::{cred::Lox, scalar_u32}; -use rand::Rng; -use serde::Deserialize; -use std::{ - cmp::min, - collections::{HashMap, HashSet}, -}; - -pub struct Censor { - pub known_bridges: HashSet<[u8; 20]>, - - // We don't actually implement the technical restriction to prevent - // one Lox credential from being used to submit many reports, so we - // just implement this as a map of bridge fingerprint to (most - // recent Lox credential for this bridge, count of unique level 3+ - // credentials we have for this bridge). - pub lox_credentials: HashMap<[u8; 20], (Lox, u32)>, - - // If censor implements random blocking, this is the date when it - // will start blocking all the bridges it knows. - pub delay_date: u32, - - // If censor implements partial blocking, what percent of - // connections are blocked? - pub partial_blocking_percent: f64, -} - -impl Censor { - pub fn new(config: &Config) -> Self { - let mut rng = rand::thread_rng(); - let delay_date = if config.censor_speed == Speed::Random { - let num: u32 = rng.gen_range(1..365); - get_date() + num - } else { - 0 - }; - let partial_blocking_percent = if config.censor_totality == Totality::Partial { - config.censor_partial_blocking_percent - } else { - 1.0 - }; - Censor { - known_bridges: HashSet::<[u8; 20]>::new(), - lox_credentials: HashMap::<[u8; 20], (Lox, u32)>::new(), - delay_date: delay_date, - partial_blocking_percent: partial_blocking_percent, - } - } - - pub fn knows_bridge(&self, fingerprint: &[u8; 20]) -> bool { - self.known_bridges.contains(fingerprint) - } - - pub fn blocks_bridge(&self, config: &Config, fingerprint: &[u8; 20]) -> bool { - self.knows_bridge(fingerprint) - && (config.censor_speed == Speed::Fast - || config.censor_speed == Speed::Lox && self.has_lox_cred(fingerprint) - || config.censor_speed == Speed::Random && self.delay_date <= get_date()) - } - - pub fn learn_bridge(&mut self, fingerprint: &[u8; 20]) { - self.known_bridges.insert(*fingerprint); - } - - pub fn has_lox_cred(&self, fingerprint: &[u8; 20]) -> bool { - self.lox_credentials.contains_key(fingerprint) - && self.lox_credentials.get(fingerprint).unwrap().1 > 0 - } - - pub fn give_lox_cred(&mut self, fingerprint: &[u8; 20], cred: &Lox) { - // We only need one level 3+ credential per bridge. (This will - // change if we restrict positive reports to one per bridge per - // credential.) - if scalar_u32(&cred.trust_level).unwrap() >= 3 { - // We want to clone the credential, but that's not allowed, - // so we're going to serialize it and then deserialize it. - let cloned_cred = bincode::deserialize(&bincode::serialize(&cred).unwrap()).unwrap(); - - // Insert the new credential and add to the count of unique - // credentials we have. We assume that a duplicate - // credential will never be given. If we don't want to make - // this assumption, we could change the count from a u32 to - // a set of credential IDs and get the count as its length. - let count = match self.lox_credentials.get(fingerprint) { - Some((_cred, count)) => *count, - None => 0, - }; - self.lox_credentials - .insert(*fingerprint, (cloned_cred, count + 1)); - } - } - - // Censor sends a positive report for the given bridge. Returns true - // if successful, false otherwise. - pub async fn send_positive_report(&self, config: &Config, fingerprint: &[u8; 20]) -> bool { - // If we don't have an appropriate Lox credential, we can't send - // a report. Return false. - if !self.has_lox_cred(fingerprint) { - return false; - } - - let (cred, _) = &self.lox_credentials.get(fingerprint).unwrap(); - let pr = PositiveReport::from_lox_credential( - *fingerprint, - None, - cred, - get_lox_pub(&config.la_pubkeys), - config.country.clone(), - ) - .unwrap(); - if config - .tp_net - .request("/positivereport".to_string(), pr.to_json().into_bytes()) - .await - .is_err() - { - // failed to send positive report - return false; - } - true - } - - // Make a bunch of connections and submit positive reports if possible - async fn flood(&self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>) { - // Only do this if Flooding censor - if config.censor_secrecy == Secrecy::Flooding { - for fingerprint in &self.known_bridges { - // Only do this if we're blocking the bridge - if config.censor_speed == Speed::Fast - || config.censor_speed == Speed::Lox && self.has_lox_cred(fingerprint) - || config.censor_speed == Speed::Random && self.delay_date <= get_date() - { - let bridge = bridges.get_mut(fingerprint).unwrap(); - - // A large number - let num_connections = 30000; - - // Make a bunch of connections to the bridge - bridge.censor_flood(num_connections); - - // If we have a lv3+ credential, submit a bunch of - // positive reports - if self.has_lox_cred(fingerprint) { - let (_cred, cred_count) = - &self.lox_credentials.get(&bridge.fingerprint).unwrap(); - let num_prs = if config.one_positive_report_per_cred { - *cred_count - } else { - 30000 - }; - for _ in 0..num_prs { - self.send_positive_report(config, &bridge.fingerprint).await; - } - } - } - } - } - } - - // Send one positive report per connection we blocked - async fn send_positive_reports( - &self, - config: &Config, - bridges: &mut HashMap<[u8; 20], Bridge>, - ) { - // Only do this if Hiding censor. Flooding censors should use - // flood() instead. - if config.censor_secrecy == Secrecy::Hiding { - for fingerprint in &self.known_bridges { - // Only do this if we're blocking the bridge - if self.blocks_bridge(config, fingerprint) && self.has_lox_cred(fingerprint) { - let bridge = bridges.get_mut(fingerprint).unwrap(); - - // We may be restricted to one positive report per - // credential - let num_reports_to_send = if config.one_positive_report_per_cred { - min( - bridge.total_connections - bridge.real_connections, - self.lox_credentials.get(fingerprint).unwrap().1, - ) - } else { - bridge.total_connections - bridge.real_connections - }; - for _ in 0..num_reports_to_send { - self.send_positive_report(config, fingerprint).await; - } - } - } - } - } - - fn recompute_delay(&mut self, config: &Config) { - // Only do this if Random censor - if config.censor_speed == Speed::Random - && self.delay_date + config.censor_event_duration <= get_date() - { - // Compute new delay date - self.delay_date = { - let mut rng = rand::thread_rng(); - let num: u32 = rng.gen_range(1..365); - get_date() + num - } - } - } - - pub async fn end_of_day_tasks( - &mut self, - config: &Config, - bridges: &mut HashMap<[u8; 20], Bridge>, - ) { - if config.censor_secrecy == Secrecy::Flooding - && !(config.censor_speed == Speed::Random && self.delay_date <= get_date()) - { - self.flood(config, bridges).await; - } else if config.censor_secrecy == Secrecy::Hiding - && !(config.censor_speed == Speed::Random && self.delay_date <= get_date()) - { - self.send_positive_reports(config, bridges).await; - } - - self.recompute_delay(config); - } -} - -#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum Speed { - Fast, - Lox, - Random, -} - -#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum Secrecy { - Overt, - Hiding, - Flooding, -} - -#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum Totality { - Full, - Partial, - Throttling, -} diff --git a/src/simulation/config.rs b/src/simulation/config.rs deleted file mode 100644 index a9b2ad0..0000000 --- a/src/simulation/config.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::simulation::censor; - -use lox_cli::networking::*; -use lox_library::IssuerPubKey; - -pub struct Config { - pub la_pubkeys: Vec, - pub la_net: HyperNet, - pub tp_net: HyperNet, - // Define censor behavior - pub censor_secrecy: censor::Secrecy, - pub censor_speed: censor::Speed, - pub censor_event_duration: u32, - pub censor_totality: censor::Totality, - pub censor_partial_blocking_percent: f64, - // We model only one country at a time because Lox assumes censors - // share information with each other. - pub country: String, - pub one_positive_report_per_cred: bool, - // Probability that a censor-cooperating user can convince an honest - // user to give them an invite. - pub prob_censor_gets_invite: f64, - // Probability that a connection randomly fails, even though censor - // does not block the bridge - pub prob_connection_fails: f64, - pub prob_user_invites_friend: f64, - pub prob_user_is_censor: f64, - pub prob_user_submits_reports: f64, - pub prob_user_treats_throttling_as_blocking: f64, -} diff --git a/src/simulation/user.rs b/src/simulation/user.rs deleted file mode 100644 index 45816db..0000000 --- a/src/simulation/user.rs +++ /dev/null @@ -1,676 +0,0 @@ -// User behavior in simulation - -use crate::{ - get_date, - negative_report::NegativeReport, - positive_report::PositiveReport, - simulation::{ - bridge::Bridge, - censor::{Censor, Secrecy::*, Totality::*}, - config::Config, - }, - BridgeDistributor, -}; -use anyhow::{anyhow, Result}; -use lox_cli::{networking::*, *}; -use lox_library::{ - bridge_table::BridgeLine, cred::Lox, proto::check_blockage::MIN_TRUST_LEVEL, scalar_u32, -}; -use rand::Rng; -use std::{cmp::min, collections::HashMap}; -use x25519_dalek::PublicKey; - -// Helper function to probabilistically return true or false -pub fn event_happens(probability: f64) -> bool { - let mut rng = rand::thread_rng(); - let num: f64 = rng.gen_range(0.0..1.0); - num < probability -} - -pub struct User { - // Does this user cooperate with a censor? - pub is_censor: bool, - - // The user always has a primary credential. If this credential's bucket is - // blocked, the user may replace it or temporarily hold two credentials - // while waiting to migrate from the primary credential. - pub primary_cred: Lox, - secondary_cred: Option, - - // Does the user submit reports to Troll Patrol? - submits_reports: bool, - - // How likely is this user to use bridges on a given day? - prob_use_bridges: f64, -} - -impl User { - pub async fn new(config: &Config, is_censor: bool) -> Result { - let cred = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await?, - get_lox_pub(&config.la_pubkeys), - ) - .await? - .0; - - // Probabilistically decide whether this user submits reports - let submits_reports = if is_censor { - false - } else { - event_happens(config.prob_user_submits_reports) - }; - - // Randomly determine how likely this user is to use bridges on - // a given day - let mut rng = rand::thread_rng(); - let prob_use_bridges = rng.gen_range(0.0..=1.0); - - Ok(Self { - is_censor, - primary_cred: cred, - secondary_cred: None, - submits_reports: submits_reports, - prob_use_bridges: prob_use_bridges, - }) - } - - pub async fn trusted_user(config: &Config) -> Result { - let cred = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await?, - get_lox_pub(&config.la_pubkeys), - ) - .await? - .0; - Ok(Self { - is_censor: false, - primary_cred: cred, - secondary_cred: None, - submits_reports: true, - prob_use_bridges: 1.0, - }) - } - - // TODO: This should probably return an actual error type - pub async fn invite( - &mut self, - config: &Config, - censor: &mut Censor, - invited_user_is_censor: bool, - ) -> Result { - let etable = get_reachability_credential(&config.la_net).await?; - let (new_cred, invite) = issue_invite( - &config.la_net, - &self.primary_cred, - &etable, - get_lox_pub(&config.la_pubkeys), - get_reachability_pub(&config.la_pubkeys), - get_invitation_pub(&config.la_pubkeys), - ) - .await?; - self.primary_cred = new_cred; - if self.is_censor { - // Make sure censor has access to each bridge and each - // credential - let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; - for bl in bucket { - let fingerprint = bl.get_hashed_fingerprint(); - censor.learn_bridge(&fingerprint); - censor.give_lox_cred(&fingerprint, &self.primary_cred); - } - } - let friend_cred = redeem_invite( - &config.la_net, - &invite, - get_lox_pub(&config.la_pubkeys), - get_invitation_pub(&config.la_pubkeys), - ) - .await? - .0; - - // Calling function decides if the invited user is a censor - let is_censor = invited_user_is_censor; - - // Probabilistically decide whether this user submits reports - let submits_reports = if is_censor { - false - } else { - event_happens(config.prob_user_submits_reports) - }; - - // Randomly determine how likely this user is to use bridges on - // a given day - let mut rng = rand::thread_rng(); - let prob_use_bridges = rng.gen_range(0.0..=1.0); - - Ok(Self { - is_censor, - primary_cred: friend_cred, - secondary_cred: None, - submits_reports: submits_reports, - prob_use_bridges: prob_use_bridges, - }) - } - - // Attempt to "connect" to the bridge, returns true if successful. - // Note that this does not involve making a real connection to a - // real bridge. The function is async because the *censor* might - // submit a positive report during this function. - pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool { - if censor.blocks_bridge(config, &bridge.fingerprint) { - if config.censor_totality == Full - || config.censor_totality == Partial - && event_happens(censor.partial_blocking_percent) - { - // If censor tries to hide its censorship, record a - // false connection - if config.censor_secrecy == Hiding { - bridge.connect_total(); - } - - // Return false because the connection failed - return false; - } else if config.censor_totality == Throttling { - // With some probability, the user connects but gives up - // because there is too much interference. In this case, - // a real connection occurs, but we treat it like a - // false connection from the censor. - if event_happens(config.prob_user_treats_throttling_as_blocking) { - bridge.connect_total(); - - // Return false because there was interference - // detected in the connection - return false; - } - } - } - - // Connection may randomly fail, without censor intervention - if event_happens(config.prob_connection_fails) { - return false; - } - - // If we haven't returned yet, the connection succeeded - bridge.connect_real(); - true - } - - pub async fn get_new_credential(config: &Config) -> Result<(Lox, BridgeLine)> { - get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await?, - get_lox_pub(&config.la_pubkeys), - ) - .await - } - - pub async fn send_negative_reports( - config: &Config, - reports: Vec, - ) -> Result<()> { - let date = get_date(); - let pubkey = match serde_json::from_slice::>( - &config - .tp_net - .request("/nrkey".to_string(), serde_json::to_string(&date)?.into()) - .await?, - )? { - Some(v) => v, - None => return Err(anyhow!("No available negative report encryption key")), - }; - for report in reports { - config - .tp_net - .request( - "/negativereport".to_string(), - bincode::serialize(&report.encrypt(&pubkey))?, - ) - .await?; - } - Ok(()) - } - - pub async fn send_positive_reports( - config: &Config, - reports: Vec, - ) -> Result<()> { - for report in reports { - config - .tp_net - .request("/positivereport".to_string(), report.to_json().into_bytes()) - .await?; - } - Ok(()) - } - - pub async fn daily_tasks( - &mut self, - config: &Config, - num_users_requesting_invites: u32, - num_censor_invites: u32, - bridges: &mut HashMap<[u8; 20], Bridge>, - censor: &mut Censor, - ) -> Result> { - if self.is_censor { - self.daily_tasks_censor(config, bridges, censor).await - } else { - self.daily_tasks_non_censor( - config, - num_users_requesting_invites, - num_censor_invites, - bridges, - censor, - ) - .await - } - } - - // User performs daily connection attempts, etc. and returns a - // vector of newly invited friends. - // TODO: The map of bridges and the censor should be Arc> - // or something so we can parallelize this. - pub async fn daily_tasks_non_censor( - &mut self, - config: &Config, - num_users_requesting_invites: u32, - num_censor_invites: u32, - bridges: &mut HashMap<[u8; 20], Bridge>, - censor: &mut Censor, - ) -> Result> { - // Probabilistically decide if the user should use bridges today - if event_happens(self.prob_use_bridges) { - // Download bucket to see if bridge is still reachable. (We - // assume that this step can be done even if the user can't - // actually talk to the LA.) - let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; - let level = match scalar_u32(&self.primary_cred.trust_level) { - Some(v) => v, - None => return Err(anyhow!("Failed to get trust level from credential")), - }; - - // Make sure each bridge in bucket is in the global bridges set - for bridgeline in bucket { - if bridgeline != BridgeLine::default() { - if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { - let bridge = Bridge::from_bridge_line(&bridgeline); - bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); - } - } - } - - // Can we level up the main credential? - let can_level_up = reachcred.is_some() - && (level == 0 - && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await - || level > 0 - && eligible_for_level_up(&config.la_net, &self.primary_cred).await); - - // Can we migrate the main credential? - let can_migrate = reachcred.is_none() && level >= MIN_TRUST_LEVEL; - - // Can we level up the secondary credential? - let mut second_level_up = false; - - let mut failed = Vec::::new(); - let mut succeeded = Vec::::new(); - // Try to connect to each bridge - for i in 0..bucket.len() { - // At level 0, we only have 1 bridge - if bucket[i] != BridgeLine::default() { - if self.connect( - &config, - bridges - .get_mut(&bucket[i].get_hashed_fingerprint()) - .unwrap(), - &censor, - ) { - succeeded.push(bucket[i]); - } else { - failed.push(bucket[i]); - } - } - } - - // If we were not able to connect to any bridges, get a - // second credential - let second_cred = if succeeded.len() < 1 { - if self.secondary_cred.is_some() { - std::mem::replace(&mut self.secondary_cred, None) - } else { - // Get new credential - match Self::get_new_credential(&config).await { - Ok((cred, _bl)) => Some(cred), - Err(e) => { - eprintln!("Failed to get new Lox credential. Error: {}", e); - None - } - } - } - } else { - // If we're able to connect with the primary credential, don't - // keep a secondary one. - None - }; - if second_cred.is_some() { - let second_cred = second_cred.as_ref().unwrap(); - let (second_bucket, second_reachcred) = - get_bucket(&config.la_net, &second_cred).await?; - for bridgeline in second_bucket { - if bridgeline != BridgeLine::default() { - if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { - bridges.insert( - bridgeline.get_hashed_fingerprint(), - Bridge::from_bridge_line(&bridgeline), - ); - } - // Attempt to connect to second cred's bridge - if self.connect( - &config, - bridges - .get_mut(&bridgeline.get_hashed_fingerprint()) - .unwrap(), - censor, - ) { - succeeded.push(bridgeline); - if second_reachcred.is_some() - && eligible_for_trust_promotion(&config.la_net, &second_cred).await - { - second_level_up = true; - } - } else { - failed.push(bridgeline); - } - } - } - } - - let mut negative_reports = Vec::::new(); - let mut positive_reports = Vec::::new(); - - if self.submits_reports { - for bridgeline in &failed { - negative_reports.push(NegativeReport::from_bridgeline( - *bridgeline, - config.country.to_string(), - BridgeDistributor::Lox, - )); - } - if level >= 3 { - for bridgeline in &succeeded { - // If we haven't received a positive report yet, - // add a record about it with today's date - let bridge = bridges - .get_mut(&bridgeline.get_hashed_fingerprint()) - .unwrap(); - if bridge.first_positive_report == 0 { - bridge.first_positive_report = get_date(); - } - - positive_reports.push( - PositiveReport::from_lox_credential( - bridgeline.get_hashed_fingerprint(), - None, - &self.primary_cred, - get_lox_pub(&config.la_pubkeys), - config.country.to_string(), - ) - .unwrap(), - ); - } - } - } - - // We might restrict these steps to succeeded.len() > 0, but - // we do assume the user can contact the LA somehow, so - // let's just allow it. - if can_level_up { - let cred = if level == 0 { - trust_migration( - &config.la_net, - &self.primary_cred, - &trust_promotion( - &config.la_net, - &self.primary_cred, - get_lox_pub(&config.la_pubkeys), - ) - .await?, - get_lox_pub(&config.la_pubkeys), - get_migration_pub(&config.la_pubkeys), - ) - .await? - } else { - level_up( - &config.la_net, - &self.primary_cred, - &reachcred.unwrap(), - get_lox_pub(&config.la_pubkeys), - get_reachability_pub(&config.la_pubkeys), - ) - .await? - }; - self.primary_cred = cred; - self.secondary_cred = None; - } - // We favor starting over at level 1 to migrating to level - // 1, but if we have a level 4 credential for a bridge that - // hasn't been marked blocked, save the credential so we can - // migrate to a level 2 cred. Note that second_level_up is - // only true if we were unable to connect with bridges from - // our primary credential. - else if second_level_up && (level <= MIN_TRUST_LEVEL || reachcred.is_none()) { - let second_cred = second_cred.as_ref().unwrap(); - let cred = trust_migration( - &config.la_net, - &second_cred, - &trust_promotion( - &config.la_net, - &second_cred, - get_lox_pub(&config.la_pubkeys), - ) - .await?, - get_lox_pub(&config.la_pubkeys), - get_migration_pub(&config.la_pubkeys), - ) - .await?; - self.primary_cred = cred; - self.secondary_cred = None; - } else if can_migrate { - let cred = blockage_migration( - &config.la_net, - &self.primary_cred, - &check_blockage( - &config.la_net, - &self.primary_cred, - get_lox_pub(&config.la_pubkeys), - ) - .await?, - get_lox_pub(&config.la_pubkeys), - get_migration_pub(&config.la_pubkeys), - ) - .await?; - self.primary_cred = cred; - self.secondary_cred = None; - } else if second_cred.is_some() { - // Couldn't connect with primary credential - if succeeded.len() > 0 { - // Keep the second credential only if it's useful - self.secondary_cred = second_cred; - } - } - - if negative_reports.len() > 0 { - Self::send_negative_reports(&config, negative_reports).await?; - } - if positive_reports.len() > 0 { - Self::send_positive_reports(&config, positive_reports).await?; - } - - // Invite friends if applicable - let invitations = match scalar_u32(&self.primary_cred.invites_remaining) { - Some(v) => v, - None => 0, // This is probably an error case that should not happen - }; - let mut new_friends = Vec::::new(); - for _i in 0..min(invitations, num_users_requesting_invites) { - if event_happens(config.prob_user_invites_friend) { - // Invite non-censor friend - match self.invite(&config, censor, false).await { - Ok(friend) => { - // You really shouldn't push your friends, - // especially new ones whose boundaries you - // might not know well. - new_friends.push(friend); - } - Err(e) => { - println!("{}", e); - } - } - } - } - - // Invite censor users if applicable - let invitations = invitations - new_friends.len() as u32; - for _i in 0..min(invitations, num_censor_invites) { - if event_happens(config.prob_user_invites_friend) { - // Invite non-censor friend - match self.invite(&config, censor, true).await { - Ok(friend) => { - new_friends.push(friend); - } - Err(e) => { - println!("{}", e); - } - } - } - } - - Ok(new_friends) - } else { - Ok(Vec::::new()) - } - } - - // User cooperates with censor and performs daily tasks to try to - // learn more bridges. - pub async fn daily_tasks_censor( - &mut self, - config: &Config, - bridges: &mut HashMap<[u8; 20], Bridge>, - censor: &mut Censor, - ) -> Result> { - // Download bucket to see if bridge is still reachable and if we - // have any new bridges - let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; - let level = scalar_u32(&self.primary_cred.trust_level).unwrap(); - - // Make sure each bridge is in global bridges set and known by - // censor - for bridgeline in bucket { - if bridgeline != BridgeLine::default() { - if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { - let bridge = Bridge::from_bridge_line(&bridgeline); - bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); - } - censor.learn_bridge(&bridgeline.get_hashed_fingerprint()); - } - } - - // Censor user tries to level up their primary credential - if reachcred.is_some() { - if level == 0 && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await - || level > 0 && eligible_for_level_up(&config.la_net, &self.primary_cred).await - { - let new_cred = if level == 0 { - trust_migration( - &config.la_net, - &self.primary_cred, - &trust_promotion( - &config.la_net, - &self.primary_cred, - get_lox_pub(&config.la_pubkeys), - ) - .await?, - get_lox_pub(&config.la_pubkeys), - get_migration_pub(&config.la_pubkeys), - ) - .await? - } else { - level_up( - &config.la_net, - &self.primary_cred, - &reachcred.unwrap(), - get_lox_pub(&config.la_pubkeys), - get_reachability_pub(&config.la_pubkeys), - ) - .await? - }; - self.primary_cred = new_cred; - let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; - // Make sure each bridge is in global bridges set and - // known by censor - for bl in bucket { - let fingerprint = bl.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bl); - bridges.insert(fingerprint, bridge); - } - censor.learn_bridge(&fingerprint); - censor.give_lox_cred(&fingerprint, &self.primary_cred); - } - } - } else { - // LA has identified this bucket as blocked. This change - // will not be reverted, so replace the primary credential - // with a new level 0 credential and work on gaining trust - // for that one. - let res = Self::get_new_credential(&config).await; - if res.is_ok() { - let (new_cred, bl) = res.unwrap(); - let fingerprint = bl.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bl); - bridges.insert(fingerprint, bridge); - } - censor.learn_bridge(&fingerprint); - // Censor doesn't want new_cred yet - self.primary_cred = new_cred; - } else { - eprintln!("Censor failed to get new credential"); - } - } - - // Separately from primary credential, censor user requests a - // new secondary credential each day just to block the - // open-entry bridges. This is stored but not reused. - let res = Self::get_new_credential(&config).await; - if res.is_ok() { - let (_new_cred, bl) = res.unwrap(); - let fingerprint = bl.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bl); - bridges.insert(fingerprint, bridge); - } - censor.learn_bridge(&fingerprint); - // Censor doesn't want new_cred. User doesn't actually use - // secondary_cred, so don't store it. - } else { - eprintln!("Censor failed to get new credential"); - } - - // Censor user invites as many censor friends as possible - let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); - let mut new_friends = Vec::::new(); - for _ in 0..invitations { - match self.invite(&config, censor, true).await { - Ok(friend) => { - new_friends.push(friend); - } - Err(e) => { - println!("{}", e); - } - } - } - Ok(new_friends) - } -}