Reduce number of unnecessary censor users for efficiency

This commit is contained in:
Vecna 2024-07-11 00:40:07 -04:00
parent d03f86d4f6
commit 642827e228
6 changed files with 234 additions and 116 deletions

View File

@ -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" }

View File

@ -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
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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))
}
}