Procházet zdrojové kódy

Clean up, fix some credential handling logic

Vecna před 7 měsíci
rodič
revize
b39811a089
2 změnil soubory, kde provedl 264 přidání a 227 odebrání
  1. 14 2
      src/censor.rs
  2. 250 225
      src/user.rs

+ 14 - 2
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));
         }
     }
 

+ 250 - 225
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::<NegativeReport>::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::<NegativeReport>::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::<NegativeReport>::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::<NegativeReport>::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<BridgeLine>, HashSet<BridgeLine>) {
+        let mut failed = HashSet::<BridgeLine>::new();
+        let mut succeeded = HashSet::<BridgeLine>::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::<BridgeLine>::new();
-            let mut succeeded = Vec::<BridgeLine>::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::<BridgeLine>::new();
+            let mut failed = HashSet::<BridgeLine>::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,21 +635,14 @@ 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()) {
-                let second_cred = second_cred.as_ref().unwrap();
-                let cred = trust_migration(
+            } else if can_migrate {
+                // If we can't level up, try to migrate
+                let cred = blockage_migration(
                     &config.la_net,
-                    &second_cred,
-                    &trust_promotion(
+                    &self.primary_cred,
+                    &check_blockage(
                         &config.la_net,
-                        &second_cred,
+                        &self.primary_cred,
                         get_lox_pub(&config.la_pubkeys),
                     )
                     .await?,
@@ -640,13 +652,16 @@ impl User {
                 .await?;
                 self.primary_cred = cred;
                 self.secondary_cred = None;
-            } else if can_migrate {
-                let cred = blockage_migration(
+            } 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,
-                    &self.primary_cred,
-                    &check_blockage(
+                    &second_cred,
+                    &trust_promotion(
                         &config.la_net,
-                        &self.primary_cred,
+                        &second_cred,
                         get_lox_pub(&config.la_pubkeys),
                     )
                     .await?,
@@ -658,16 +673,16 @@ impl User {
                 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 {