ソースを参照

Reduce number of unnecessary censor users for efficiency

Vecna 4 ヶ月 前
コミット
642827e228
6 ファイル変更234 行追加116 行削除
  1. 1 0
      Cargo.toml
  2. 10 0
      src/bridge.rs
  3. 30 8
      src/censor.rs
  4. 5 0
      src/config.rs
  5. 24 5
      src/main.rs
  6. 164 103
      src/user.rs

+ 1 - 0
Cargo.toml

@@ -10,6 +10,7 @@ anyhow = "1.0"
 array-bytes = "6.2.0"
 bincode = "1"
 clap = { version = "4.4.14", features = ["derive"] }
+curve25519-dalek = { version = "4", default-features = false }
 hyper = { version = "0.14.28", features = ["full"] }
 lox_cli = { git = "https://git-crysp.uwaterloo.ca/vvecna/lox_cli.git", version = "0.1" }
 lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }

+ 10 - 0
src/bridge.rs

@@ -86,4 +86,14 @@ impl Bridge {
         self.real_connections = 0;
         self.total_connections = 0;
     }
+
+    // Has this bridge been distributed to a non-censor user?
+    pub fn has_been_distributed(&self) -> bool {
+        self.first_real_user > 0
+    }
+
+    // Does Troll Patrol think this bridge is blocked?
+    pub fn troll_patrol_blocked(&self) -> bool {
+        self.first_detected_blocked > 0
+    }
 }

+ 30 - 8
src/censor.rs

@@ -1,5 +1,6 @@
 use crate::{bridge::Bridge, config::Config};
 
+use curve25519_dalek::scalar::Scalar;
 use lox_cli::{get_lox_pub, networking::Networking};
 use lox_library::{cred::Lox, scalar_u32};
 use rand::Rng;
@@ -24,6 +25,12 @@ pub struct Censor {
     // credentials we have for this bridge).
     pub lox_credentials: HashMap<[u8; 20], (Lox, u32)>,
 
+    // Map of buckets to count of censor users with that bucket. Note
+    // that this is the count of users that have *ever* had that bucket,
+    // so this variable should NOT be used to count the overall number
+    // of censor agents.
+    pub agents: HashMap<Scalar, u32>,
+
     // If censor implements random blocking, this is the date when it
     // will start blocking all the bridges it knows.
     pub delay_date: u32,
@@ -52,6 +59,7 @@ impl Censor {
             start_date,
             known_bridges: HashSet::<[u8; 20]>::new(),
             lox_credentials: HashMap::<[u8; 20], (Lox, u32)>::new(),
+            agents: HashMap::<Scalar, u32>::new(),
             delay_date: delay_date,
             partial_blocking_percent: partial_blocking_percent,
         }
@@ -116,6 +124,25 @@ impl Censor {
         }
     }
 
+    // Get the number of agents the censor has with a given bucket
+    pub fn num_agents(&self, bucket: &Scalar) -> u32 {
+        match self.agents.get(bucket) {
+            Some(v) => *v,
+            None => 0,
+        }
+    }
+
+    // Add to the number of agents a censor has for a given bucket
+    pub fn add_agent(&mut self, bucket: &Scalar) {
+        self.agents.insert(
+            *bucket,
+            match self.agents.get(bucket) {
+                Some(v) => *v + 1,
+                None => 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 {
@@ -158,11 +185,8 @@ impl Censor {
                 {
                     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);
+                    bridge.censor_flood(config.censor_max_connections);
 
                     // If we have a lv3+ credential, submit a bunch of
                     // positive reports
@@ -176,11 +200,9 @@ impl Censor {
                         // detection algorithm. In practice, the censor
                         // should submit as many reports as possible.
                         let num_prs = if config.one_positive_report_per_cred {
-                            // *cred_count
-                            min(*cred_count, 25)
+                            min(*cred_count, config.censor_max_pr)
                         } else {
-                            //30000
-                            25
+                            config.censor_max_pr
                         };
                         for _ in 0..num_prs {
                             self.send_positive_report(config, &bridge.fingerprint).await;

+ 5 - 0
src/config.rs

@@ -10,6 +10,11 @@ pub struct Config {
     pub bootstrapping_period_duration: u32,
     // Define censor behavior
     pub censor_secrecy: censor::Secrecy,
+    // The maximum number of connections for the censor to make to each
+    // bridge
+    pub censor_max_connections: u32,
+    // The maximum number of positive reports for the censor to submit
+    pub censor_max_pr: u32,
     pub censor_speed: censor::Speed,
     pub censor_event_duration: u32,
     pub censor_totality: censor::Totality,

+ 24 - 5
src/main.rs

@@ -43,6 +43,8 @@ pub struct Config {
     pub tp_test_port: u16,
     pub bootstrapping_period_duration: u32,
     pub censor_secrecy: censor::Secrecy,
+    pub censor_max_connections: u32,
+    pub censor_max_pr: u32,
     pub censor_speed: censor::Speed,
     pub censor_event_duration: u32,
     pub censor_totality: censor::Totality,
@@ -95,6 +97,8 @@ pub async fn main() {
         tp_net,
         bootstrapping_period_duration: config.bootstrapping_period_duration,
         censor_secrecy: config.censor_secrecy,
+        censor_max_connections: config.censor_max_connections,
+        censor_max_pr: config.censor_max_pr,
         censor_speed: config.censor_speed,
         censor_event_duration: config.censor_event_duration,
         censor_totality: config.censor_totality,
@@ -207,13 +211,23 @@ pub async fn main() {
         users.shuffle(&mut rng);
 
         // Users do daily actions
-        for user in &mut users {
-            let invited_friends = user
+        let mut i = 0;
+        while i < users.len() {
+            let user = users.get_mut(i).unwrap();
+            if let Ok((mut invited_friends, remove_user)) = user
                 .daily_tasks(&sconfig, &mut bridges, &mut censor, &mut invites)
-                .await;
+                .await
+            {
+                // We remove censor users once they stop serving a purpose
+                if remove_user {
+                    // This removes the user and replaces them with the
+                    // last element of the vector, for efficiency. This
+                    // is fine to do because the users act in a random
+                    // order anyway.
+                    users.swap_remove(i);
+                    continue;
+                }
 
-            if invited_friends.is_ok() {
-                let mut invited_friends = invited_friends.unwrap();
                 if invited_friends.len() > 0 {
                     new_users.append(&mut invited_friends);
                 }
@@ -228,6 +242,11 @@ pub async fn main() {
                     count_users_cannot_connect += 1;
                 }
             }
+
+            // Iterate loop (note that we do not reach this if we remove
+            // a user, so we'll get the replacement user at that same
+            // index)
+            i += 1;
         }
 
         // Also count number of new users with/without connections

+ 164 - 103
src/user.rs

@@ -46,6 +46,64 @@ pub fn give_bucket_to_censor(
     }
 }
 
+// Check if bucket is blocked, according to the LA (regardless of
+// whether the censor actually blocks the bridges)
+pub fn bucket_blocked_lox(bucket: &[BridgeLine], bridges: &HashMap<[u8; 20], Bridge>) -> bool {
+    // Count number of non-default bridges (either 1 or 3)
+    let mut num_real_bridges = 0;
+    let mut num_blocked_bridges = 0;
+
+    for bridge in bucket {
+        if *bridge != BridgeLine::default() {
+            match bridges.get(&bridge.get_hashed_fingerprint()) {
+                Some(b) => {
+                    num_real_bridges += 1;
+                    if b.troll_patrol_blocked() {
+                        num_blocked_bridges += 1;
+                    }
+                }
+                None => {
+                    // Something went wrong, I guess
+                    println!(
+                        "Tried to check if bridge was blocked before it was added to simulation"
+                    );
+                }
+            }
+        }
+    }
+
+    // Return true if open-entry bucket with a blocked bridge or
+    // invite-only bucket with 2+ blocked bridges
+    num_real_bridges == 1 && num_blocked_bridges == 1
+        || num_real_bridges == 3 && num_blocked_bridges >= 2
+}
+
+// Check if bucket contains a bridge that has ever been distributed to a
+// real user
+pub fn bucket_has_been_distributed(
+    bucket: &[BridgeLine],
+    bridges: &HashMap<[u8; 20], Bridge>,
+) -> bool {
+    for bridge in bucket {
+        if *bridge != BridgeLine::default() {
+            match bridges.get(&bridge.get_hashed_fingerprint()) {
+                Some(b) => {
+                    if b.has_been_distributed() {
+                        return true;
+                    }
+                }
+                None => {
+                    // Something went wrong, I guess
+                    println!(
+                        "Tried to check if bridge had been distributed before it was added to simulation"
+                    );
+                }
+            }
+        }
+    }
+    false
+}
+
 pub struct User {
     // Does this user cooperate with a censor?
     pub is_censor: bool,
@@ -123,6 +181,30 @@ impl User {
         if is_censor {
             // Give bridges to censor
             give_bucket_to_censor(&bucket, bridges, censor);
+
+            // Various conditions to avoid creating censor users if we don't need to
+
+            // If the bucket is already marked blocked, don't create this user
+            if bucket_blocked_lox(&bucket, bridges) {
+                return Err(anyhow!("Bucket blocked, don't create new censor user"));
+            }
+
+            // If the bucket has never been distributed to a real user, don't create this user
+            if !bucket_has_been_distributed(&bucket, bridges) {
+                return Err(anyhow!(
+                    "Bucket never distributed to a real user, don't create new censor user"
+                ));
+            }
+
+            // If we already have enough agents with this bucket, don't create this user
+            if censor.num_agents(&result.primary_cred.bucket) > config.censor_max_pr {
+                return Err(anyhow!(
+                    "We already have enough censor users with this bucket"
+                ));
+            }
+
+            // If we haven't returned yet, add this user to censor's list
+            censor.add_agent(&result.primary_cred.bucket);
         } else {
             // Test bridges to see if they work
             let (s, f) = result.test_bridges(&bucket, config, bridges, censor);
@@ -229,6 +311,23 @@ impl User {
 
         if is_censor {
             give_bucket_to_censor(&bucket, bridges, censor);
+
+            // Various conditions to avoid creating censor users unnecessarily
+
+            // If the bucket is already marked blocked, don't create this user
+            if bucket_blocked_lox(&bucket, bridges) {
+                return Err(anyhow!("Bucket blocked, don't create new censor user"));
+            }
+
+            // If we already have enough agents with this bucket, don't create this user
+            if censor.num_agents(&result.primary_cred.bucket) > config.censor_max_pr {
+                return Err(anyhow!(
+                    "We already have enough censor users with this bucket"
+                ));
+            }
+
+            // If we haven't returned yet, add this user to censor's list
+            censor.add_agent(&result.primary_cred.bucket);
         } else {
             let (s, f) = result.test_bridges(&bucket, config, bridges, censor);
 
@@ -411,12 +510,17 @@ impl User {
         bridges: &mut HashMap<[u8; 20], Bridge>,
         censor: &mut Censor,
         invites: &mut Vec<Invitation>,
-    ) -> Result<Vec<User>> {
+    ) -> Result<(Vec<User>, bool)> {
         if self.is_censor {
             self.daily_tasks_censor(config, bridges, censor).await
         } else {
-            self.daily_tasks_non_censor(config, bridges, censor, invites)
+            match self
+                .daily_tasks_non_censor(config, bridges, censor, invites)
                 .await
+            {
+                Ok(users) => Ok((users, false)),
+                Err(e) => Err(e),
+            }
         }
     }
 
@@ -756,13 +860,15 @@ impl User {
     }
 
     // User cooperates with censor and performs daily tasks to try to
-    // learn more bridges.
+    // learn more bridges. Returns a vector of newly invited users
+    // and a boolean indicating whether this censor user should be
+    // removed from the global user set (for efficiency).
     pub async fn daily_tasks_censor(
         &mut self,
         config: &Config,
         bridges: &mut HashMap<[u8; 20], Bridge>,
         censor: &mut Censor,
-    ) -> Result<Vec<User>> {
+    ) -> Result<(Vec<User>, bool)> {
         // 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?;
@@ -770,14 +876,52 @@ impl User {
 
         // Make sure each bridge is in global bridges set and known by
         // 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);
+        give_bucket_to_censor(&bucket, bridges, censor);
+
+        // If Lox has marked the bridge blocked, migrate if possible
+        if bucket_blocked_lox(&bucket, bridges) {
+            // If we can migrate, migrate
+            if level >= MIN_TRUST_LEVEL {
+                if let Ok(migcred) = check_blockage(
+                    &config.la_net,
+                    &self.primary_cred,
+                    get_lox_pub(&config.la_pubkeys),
+                )
+                .await
+                {
+                    if let Ok(cred) = blockage_migration(
+                        &config.la_net,
+                        &self.primary_cred,
+                        &migcred,
+                        get_lox_pub(&config.la_pubkeys),
+                        get_migration_pub(&config.la_pubkeys),
+                    )
+                    .await
+                    {
+                        // Successfully migrated!
+
+                        // Update credential
+                        self.primary_cred = cred;
+
+                        // You can't migrate to level 3 or 4, so the
+                        // censor doesn't want this new credential
+
+                        // 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?;
+
+                        // Give new bucket to censor
+                        give_bucket_to_censor(&bucket, bridges, censor);
+
+                        // Add this user to censor's list
+                        censor.add_agent(&self.primary_cred.bucket);
+                    }
                 }
-                censor.learn_bridge(&fingerprint);
+            } else {
+                // We can't migrate, and we can't level up or anything.
+                // Mark this user for deletion.
+                return Ok((Vec::<User>::new(), true));
             }
         }
 
@@ -788,7 +932,7 @@ impl User {
                 && eligible_for_level_up(&config.la_net, &self.primary_cred).await
         {
             let new_cred = if level == 0 {
-                trust_migration(
+                let nc = trust_migration(
                     &config.la_net,
                     &self.primary_cred,
                     &trust_promotion(
@@ -800,7 +944,13 @@ impl User {
                     get_lox_pub(&config.la_pubkeys),
                     get_migration_pub(&config.la_pubkeys),
                 )
-                .await?
+                .await?;
+
+                // We now have a new bucket value, so add the user to censor's list
+                censor.add_agent(&nc.bucket);
+
+                // New credential as new_cred
+                nc
             } else {
                 level_up(
                     &config.la_net,
@@ -834,97 +984,8 @@ impl User {
                     censor.give_lox_cred(&fingerprint, &self.primary_cred, false);
                 }
             }
-        } else {
-            // LA has identified this bucket as blocked. This change
-            // will not be reverted, so either migrate or replace the
-            // primary credential with a new level 0 credential and work
-            // on gaining trust for that one.
-
-            // Migrate if able
-            if level >= MIN_TRUST_LEVEL {
-                if let Ok(migcred) = check_blockage(
-                    &config.la_net,
-                    &self.primary_cred,
-                    get_lox_pub(&config.la_pubkeys),
-                )
-                .await
-                {
-                    if let Ok(cred) = blockage_migration(
-                        &config.la_net,
-                        &self.primary_cred,
-                        &migcred,
-                        get_lox_pub(&config.la_pubkeys),
-                        get_migration_pub(&config.la_pubkeys),
-                    )
-                    .await
-                    {
-                        self.primary_cred = cred;
-
-                        // You can't migrate to level 3 or 4, so the
-                        // censor doesn't want this new credential
-
-                        // 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?;
-
-                        // Make sure each bridge is in global bridges
-                        // set and known by 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);
-                            }
-                        }
-                    }
-                }
-
-                // Removing this case for efficiency. If the censor is in
-                // play, we just assume it wins the open-entry game and stop
-                // distributing open-entry invites altogether.
-                /*
-                            } else {
-                                // If unable to migrate, try to get a new open-entry
-                                // credential and start over
-                                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;
-                                }
-                */
-            }
         }
 
-        // Also removing this case for efficiency.
-        /*
-                // 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.
-                }
-        */
-
         // Censor user invites as many censor friends as possible
         let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap();
         let mut new_friends = Vec::<User>::new();
@@ -945,6 +1006,6 @@ impl User {
                 }
             }
         }
-        Ok(new_friends)
+        Ok((new_friends, false))
     }
 }