diff --git a/src/censor.rs b/src/censor.rs index 77d943f..f7c879f 100644 --- a/src/censor.rs +++ b/src/censor.rs @@ -81,7 +81,7 @@ impl Censor { && self.lox_credentials.get(fingerprint).unwrap().1 > 0 } - pub fn give_lox_cred(&mut self, fingerprint: &[u8; 20], cred: &Lox) { + pub fn give_lox_cred(&mut self, fingerprint: &[u8; 20], cred: &Lox, new_cred: bool) { // We only need one level 3+ credential per bridge. (This will // change if we restrict positive reports to one per bridge per // credential.) @@ -99,8 +99,20 @@ impl Censor { Some((_cred, count)) => *count, None => 0, }; + // Sometimes we want to add a fresh credential but assume it + // has the same gamma and does not give the ability to make + // more reports. + let new_count = if new_cred { + // If we're adding a new gamma, increase count + count + 1 + } else { + // If we're adding a fresh cred with old gamma, don't + count + }; + // Insert the new credential. We always want the newest + // credential to ensure freshness. self.lox_credentials - .insert(*fingerprint, (cloned_cred, count + 1)); + .insert(*fingerprint, (cloned_cred, new_count)); } } diff --git a/src/user.rs b/src/user.rs index 15553fa..5574925 100644 --- a/src/user.rs +++ b/src/user.rs @@ -15,7 +15,7 @@ use lox_library::{ scalar_u32, }; use rand::Rng; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use troll_patrol::{ get_date, negative_report::NegativeReport, positive_report::PositiveReport, BridgeDistributor, }; @@ -28,6 +28,24 @@ pub fn event_happens(probability: f64) -> bool { num < probability } +// Make sure each bridge is in global bridges set and known by censor +pub fn give_bucket_to_censor( + bucket: &[BridgeLine], + bridges: &mut HashMap<[u8; 20], Bridge>, + censor: &mut Censor, +) { + for bridgeline in bucket { + if *bridgeline != BridgeLine::default() { + let fingerprint = bridgeline.get_hashed_fingerprint(); + if !bridges.contains_key(&fingerprint) { + let bridge = Bridge::from_bridge_line(&bridgeline); + bridges.insert(fingerprint, bridge); + } + censor.learn_bridge(&fingerprint); + } + } +} + pub struct User { // Does this user cooperate with a censor? pub is_censor: bool, @@ -84,46 +102,7 @@ impl User { true }; - let mut able_to_connect = false; - - // Immediately download and try to use our bridges, or report them to the censor. - let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?; - for bridgeline in bucket { - if bridgeline != BridgeLine::default() { - let fingerprint = bridgeline.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bridgeline); - bridges.insert(fingerprint, bridge); - } - let bridge = bridges.get_mut(&fingerprint).unwrap(); - if is_censor { - censor.learn_bridge(&fingerprint); - } else { - // If this is the first time the bridge has been - // distributed to a real user, store that info - if bridge.first_real_user == 0 { - bridge.first_real_user = get_date(); - } - - if Self::connect(in_censorship_range, config, bridge, censor) { - able_to_connect = true; - } else if submits_reports { - // New user only has one bridge, so no need - // to collect the negative reports before - // sending. Just send one now. - let mut negative_reports = Vec::::new(); - negative_reports.push(NegativeReport::from_bridgeline( - bridgeline, - config.country.to_string(), - BridgeDistributor::Lox, - )); - Self::send_negative_reports(&config, negative_reports).await?; - } - } - } - } - - Ok(Self { + let mut result = Self { is_censor, primary_cred: cred, secondary_cred: None, @@ -131,9 +110,41 @@ impl User { prob_use_bridges: prob_use_bridges, in_censorship_range, join_date: get_date(), - able_to_connect, + able_to_connect: false, attempted_connection: true, - }) + }; + + // Immediately download and try to use our bridges, or report them to the censor. + let (bucket, _reachcred) = get_bucket(&config.la_net, &result.primary_cred).await?; + + if is_censor { + // Give bridges to censor + give_bucket_to_censor(&bucket, bridges, censor); + } else { + // Test bridges to see if they work + let (s, f) = result.test_bridges(&bucket, config, bridges, censor); + + // Our bridge worked! Yay! + if !s.is_empty() { + result.able_to_connect = true; + } + + // Report any failures if we submit reports + if submits_reports { + for bl in f { + let mut negative_reports = Vec::::new(); + negative_reports.push(NegativeReport::from_bridgeline( + bl, + config.country.to_string(), + BridgeDistributor::Lox, + )); + Self::send_negative_reports(&config, negative_reports).await?; + } + } + } + + // Return our new user + Ok(result) } // Get an invite if able @@ -154,26 +165,14 @@ impl User { ) .await?; self.primary_cred = new_cred; - // Make sure bridge is in list of bridges and that censor has - // access if applicable - let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; - for bl in bucket { - if bl != BridgeLine::default() { - let fingerprint = bl.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bl); - bridges.insert(fingerprint, bridge); - } - let bridge = bridges.get_mut(&fingerprint).unwrap(); - if self.is_censor { - censor.learn_bridge(&fingerprint); - censor.give_lox_cred(&fingerprint, &self.primary_cred); - // If this is the first time the bridge has been - // distributed to a real user, store that info - } else if bridge.first_real_user == 0 { - bridge.first_real_user = get_date(); - } - } + + // If user cooperates with censor, give bridges and credential + // now. Otherwise, wait until the user actually wants to use + // Tor. + if self.is_censor { + let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; + + give_bucket_to_censor(&bucket, bridges, censor); } Ok(invite) } @@ -211,53 +210,7 @@ impl User { true }; - let mut able_to_connect = false; - - // Immediately download bucket and test bridges or give them to - // the censor - let mut negative_reports = Vec::::new(); - let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?; - for bridgeline in bucket { - let fingerprint = bridgeline.get_hashed_fingerprint(); - if bridgeline != BridgeLine::default() { - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bridgeline); - bridges.insert(fingerprint, bridge); - } - let bridge = bridges.get_mut(&fingerprint).unwrap(); - if is_censor { - censor.learn_bridge(&fingerprint); - } else { - // If this is the first time the bridge has been - // distributed to a real user, store that info - if bridge.first_real_user == 0 { - bridge.first_real_user = get_date(); - } - - if Self::connect(in_censorship_range, config, bridge, censor) { - able_to_connect = true; - } else if submits_reports { - negative_reports.push(NegativeReport::from_bridgeline( - bridgeline, - config.country.to_string(), - BridgeDistributor::Lox, - )); - } - } - } - } - // Submit reports if we have them - if negative_reports.len() > 0 { - Self::send_negative_reports(&config, negative_reports).await?; - } - - let in_censorship_range = if config.censor_totality == Partial { - event_happens(config.censor_partial_blocking_percent) - } else { - true - }; - - Ok(Self { + let mut result = Self { is_censor, primary_cred: cred, secondary_cred: None, @@ -265,9 +218,37 @@ impl User { prob_use_bridges: prob_use_bridges, in_censorship_range, join_date: get_date(), - able_to_connect, + able_to_connect: false, attempted_connection: true, - }) + }; + + let (bucket, _reachcred) = get_bucket(&config.la_net, &result.primary_cred).await?; + + if is_censor { + give_bucket_to_censor(&bucket, bridges, censor); + } else { + let (s, f) = result.test_bridges(&bucket, config, bridges, censor); + + // Our bridge worked! Yay! + if !s.is_empty() { + result.able_to_connect = true; + } + + // Report any failures if we submit reports + if submits_reports { + for bl in f { + let mut negative_reports = Vec::::new(); + negative_reports.push(NegativeReport::from_bridgeline( + bl, + config.country.to_string(), + BridgeDistributor::Lox, + )); + Self::send_negative_reports(&config, negative_reports).await?; + } + } + } + + Ok(result) } // Attempt to "connect" to the bridge, returns true if successful. @@ -324,6 +305,55 @@ impl User { true } + // Given a Lox credential, download its bucket, test its bridges, and + // return sets of working and non-working bridges + pub fn test_bridges( + &mut self, + bucket: &[BridgeLine], + config: &Config, + bridges: &mut HashMap<[u8; 20], Bridge>, + censor: &Censor, + ) -> (HashSet, HashSet) { + let mut failed = HashSet::::new(); + let mut succeeded = HashSet::::new(); + + for bridgeline in bucket { + if *bridgeline != BridgeLine::default() { + let fingerprint = bridgeline.get_hashed_fingerprint(); + + // Make sure bridge is in the global set + if !bridges.contains_key(&fingerprint) { + let bridge = Bridge::from_bridge_line(&bridgeline); + bridges.insert(fingerprint, bridge); + } + + // If this is the first time the bridge has been + // distributed to a real user, store that info + let bridge = bridges.get_mut(&fingerprint).unwrap(); + if bridge.first_real_user == 0 { + bridge.first_real_user = get_date(); + } + + // Attempt to connect to the bridge + if Self::connect( + self.in_censorship_range, + config, + bridges + .get_mut(&bridgeline.get_hashed_fingerprint()) + .unwrap(), + censor, + ) { + self.able_to_connect = true; + succeeded.insert(*bridgeline); + } else { + failed.insert(*bridgeline); + } + } + } + + (succeeded, failed) + } + pub async fn get_new_credential(config: &Config) -> Result<(Lox, BridgeLine)> { get_lox_credential( &config.la_net, @@ -402,30 +432,6 @@ impl User { if event_happens(self.prob_use_bridges) { self.attempted_connection = true; - // If we couldn't connect yesterday, try to get a new credential via invite. - if !self.able_to_connect { - while let Some(invite) = invites.pop() { - match redeem_invite( - &config.la_net, - &invite, - get_lox_pub(&config.la_pubkeys), - get_invitation_pub(&config.la_pubkeys), - ) - .await - { - Ok((cred, _bucket)) => { - self.primary_cred = cred; - - // We got a credential. Stop now. - break; - } - Err(e) => { - println!("Failed to redeem invite. Error: {}", e); - } - } - } - } - // Start with the assumption that we can't connect, change // only if we can self.able_to_connect = false; @@ -439,24 +445,6 @@ impl User { 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() { - let fingerprint = bridgeline.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bridgeline); - bridges.insert(fingerprint, bridge); - } - - // If this is the first time the bridge has been - // distributed to a real user, store that info - let bridge = bridges.get_mut(&fingerprint).unwrap(); - if bridge.first_real_user == 0 { - bridge.first_real_user = get_date(); - } - } - } - // Can we level up the main credential? let can_level_up = reachcred.is_some() && (level == 0 @@ -470,31 +458,63 @@ impl User { // 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( - self.in_censorship_range, - &config, - bridges - .get_mut(&bucket[i].get_hashed_fingerprint()) - .unwrap(), - &censor, - ) { - self.able_to_connect = true; - succeeded.push(bucket[i]); - } else { - failed.push(bucket[i]); + // Track which connections succeeded and which failed. We use sets + // instead of vectors for deduplication in case we test a bridge + // multiple times. + let mut succeeded = HashSet::::new(); + let mut failed = HashSet::::new(); + + let (s, f) = self.test_bridges(&bucket, config, bridges, censor); + + // Add working bridges to succeeded set + for b in s { + succeeded.insert(b); + } + + // Add non-working bridges to failed set + for b in f { + failed.insert(b); + } + + // If we were not able to connect to any bridges but also + // cannot migrate, ask for a new invitation + if !self.able_to_connect && !can_migrate { + while let Some(invite) = invites.pop() { + match redeem_invite( + &config.la_net, + &invite, + get_lox_pub(&config.la_pubkeys), + get_invitation_pub(&config.la_pubkeys), + ) + .await + { + Ok((cred, bucket)) => { + self.primary_cred = cred; + + // Test our bridges + let (s, f) = self.test_bridges(&bucket, config, bridges, censor); + + for b in s { + succeeded.insert(b); + } + + for b in f { + failed.insert(b); + } + + // We got a credential. Stop now. + break; + } + Err(e) => { + println!("Failed to redeem invite. Error: {}", e); + } } } } - // If we were not able to connect to any bridges, get a - // second credential - let second_cred = if succeeded.len() < 1 { + // If we were still not able to connect to any bridges, get + // a second credential + let second_cred = if !self.able_to_connect { if self.secondary_cred.is_some() { std::mem::replace(&mut self.secondary_cred, None) } else { @@ -519,34 +539,28 @@ impl User { }; if second_cred.is_some() { let second_cred = second_cred.as_ref().unwrap(); + + // Test our bridges let (second_bucket, second_reachcred) = get_bucket(&config.la_net, &second_cred).await?; - for bridgeline in second_bucket { - if bridgeline != BridgeLine::default() { - let fingerprint = bridgeline.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - bridges.insert(fingerprint, Bridge::from_bridge_line(&bridgeline)); - } + let (s, f) = self.test_bridges(&second_bucket, config, bridges, censor); - // If this is the first time the bridge has been - // distributed to a real user, store that info - let bridge = bridges.get_mut(&fingerprint).unwrap(); - if bridge.first_real_user == 0 { - bridge.first_real_user = get_date(); - } + // Add each working bridge to succeeded set + for b in s { + succeeded.insert(b); + } - // Attempt to connect to second cred's bridge - if Self::connect(self.in_censorship_range, &config, bridge, censor) { - self.able_to_connect = true; - 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); - } + // Add each non-working bridge to failed set + for b in f { + failed.insert(b); + } + + // If we're able to connect, see if we can level up + if self.able_to_connect { + if second_reachcred.is_some() + && eligible_for_trust_promotion(&config.la_net, &second_cred).await + { + second_level_up = true; } } } @@ -589,7 +603,11 @@ impl User { // 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 { + // If we can level up/trust migrate, do so + + // Trust migration from level 0 to level 1 let cred = if level == 0 { trust_migration( &config.la_net, @@ -605,6 +623,7 @@ impl User { ) .await? } else { + // Level up from level 1+ level_up( &config.la_net, &self.primary_cred, @@ -616,14 +635,26 @@ impl User { }; 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()) { + } else if can_migrate { + // If we can't level up, try to 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_level_up { + // If we can't migrate, try to level up our secondary + // credential let second_cred = second_cred.as_ref().unwrap(); let cred = trust_migration( &config.la_net, @@ -640,34 +671,18 @@ impl User { .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 { + if self.able_to_connect { // Keep the second credential only if it's useful self.secondary_cred = second_cred; } } - if negative_reports.len() > 0 { + if !negative_reports.is_empty() { Self::send_negative_reports(&config, negative_reports).await?; } - if positive_reports.len() > 0 { + if !positive_reports.is_empty() { Self::send_positive_reports(&config, positive_reports).await?; } @@ -688,10 +703,10 @@ impl User { // censors because they have more to lose. let level_scale = match scalar_u32(&self.primary_cred.trust_level) { // These numbers are fairly arbitrary. - Some(4) => 0.01, - Some(3) => 0.1, - Some(2) => 0.5, - _ => 1.0, + Some(4) => 0.25, + Some(3) => 0.5, + Some(2) => 1.0, + _ => 1.0, // should not have invitations }; for _ in 0..invitations { if event_happens(config.prob_user_invites_friend) { @@ -804,7 +819,17 @@ impl User { bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); - censor.give_lox_cred(&fingerprint, &self.primary_cred); + if level == 2 { + // level up to 3 + // Give censor an additional credential + censor.give_lox_cred(&fingerprint, &self.primary_cred, true); + } else if level > 2 { + // level up to 4 + // Replace censor's credential with newer one, + // but don't add to count of censor's + // credentials + censor.give_lox_cred(&fingerprint, &self.primary_cred, false); + } } } } else {