6 Revize dc7531689c ... 7acba0a6f0

Autor SHA1 Zpráva Datum
  Vecna 7acba0a6f0 If positive reports change anything, print a notice před 4 měsíci
  Vecna 264e3824d6 Some basic thresholds před 4 měsíci
  Vecna ed37bf1874 Further reduce threshold před 5 měsíci
  Vecna 58de633f3d Play with thresholds some more před 5 měsíci
  Vecna aab51731e1 Adjust thresholds před 5 měsíci
  Vecna ac1f09a8c1 Use simple thresholds před 5 měsíci
4 změnil soubory, kde provedl 232 přidání a 1 odebrání
  1. 33 0
      src/analysis.rs
  2. 1 0
      src/lib.rs
  3. 196 0
      src/lox_analysis.rs
  4. 2 1
      src/main.rs

+ 33 - 0
src/analysis.rs

@@ -8,11 +8,15 @@ use std::{
     collections::{BTreeMap, HashSet},
 };
 
+#[cfg(feature = "simulation")]
+use crate::get_date;
+
 /// Provides a function for predicting which countries block this bridge
 pub trait Analyzer {
     /// Evaluate open-entry bridge. Returns true if blocked, false otherwise.
     fn stage_one(
         &self,
+        age: u32,
         confidence: f64,
         bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -24,6 +28,7 @@ pub trait Analyzer {
     /// blocked, false otherwise.
     fn stage_two(
         &self,
+        age: u32,
         confidence: f64,
         bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -35,6 +40,7 @@ pub trait Analyzer {
     /// blocked, false otherwise.
     fn stage_three(
         &self,
+        age: u32,
         confidence: f64,
         bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -118,6 +124,7 @@ pub fn blocked_in(
                 // open-entry bridge and/or not enough days of
                 // historical days for stages 2 and 3
                 if analyzer.stage_one(
+                    age,
                     confidence,
                     &bridge_ips,
                     bridge_ips_today,
@@ -132,6 +139,7 @@ pub fn blocked_in(
                 // invite-only bridge without min_historical_days of
                 // historical data on positive reports
                 if analyzer.stage_two(
+                    age,
                     confidence,
                     &bridge_ips,
                     bridge_ips_today,
@@ -144,6 +152,7 @@ pub fn blocked_in(
                 // invite-only bridge that has min_historical_days or
                 // more of historical data since the first positive report
                 if analyzer.stage_three(
+                    age,
                     confidence,
                     &bridge_ips,
                     bridge_ips_today,
@@ -153,6 +162,23 @@ pub fn blocked_in(
                     positive_reports_today,
                 ) {
                     blocked_in.insert(country.to_string());
+                } else {
+                    // Logging in simulation mode
+                    #[cfg(feature = "simulation")]
+                    if analyzer.stage_two(
+                        age,
+                        confidence,
+                        &bridge_ips,
+                        bridge_ips_today,
+                        &negative_reports,
+                        negative_reports_today,
+                    ) {
+                        println!(
+                            "{} detected not blocked due to positive reports on day {}",
+                            array_bytes::bytes2hex("", bridge_info.fingerprint),
+                            get_date()
+                        );
+                    }
                 }
             }
         }
@@ -168,6 +194,7 @@ pub struct ExampleAnalyzer {}
 impl Analyzer for ExampleAnalyzer {
     fn stage_one(
         &self,
+        _age: u32,
         _confidence: f64,
         _bridge_ips: &[u32],
         _bridge_ips_today: u32,
@@ -179,6 +206,7 @@ impl Analyzer for ExampleAnalyzer {
 
     fn stage_two(
         &self,
+        _age: u32,
         _confidence: f64,
         _bridge_ips: &[u32],
         _bridge_ips_today: u32,
@@ -190,6 +218,7 @@ impl Analyzer for ExampleAnalyzer {
 
     fn stage_three(
         &self,
+        _age: u32,
         _confidence: f64,
         _bridge_ips: &[u32],
         _bridge_ips_today: u32,
@@ -221,6 +250,7 @@ impl Analyzer for NormalAnalyzer {
     /// Evaluate open-entry bridge based on only today's data
     fn stage_one(
         &self,
+        _age: u32,
         _confidence: f64,
         _bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -234,6 +264,7 @@ impl Analyzer for NormalAnalyzer {
     /// Evaluate invite-only bridge based on historical data
     fn stage_two(
         &self,
+        _age: u32,
         confidence: f64,
         bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -305,6 +336,7 @@ impl Analyzer for NormalAnalyzer {
     /// Evaluate invite-only bridge with lv3+ users submitting positive reports
     fn stage_three(
         &self,
+        age: u32,
         confidence: f64,
         bridge_ips: &[u32],
         bridge_ips_today: u32,
@@ -401,6 +433,7 @@ impl Analyzer for NormalAnalyzer {
                 // evaluate each variable. Ignore positive reports and
                 // compute as in stage 2
                 if self.stage_two(
+                    age,
                     confidence,
                     bridge_ips,
                     bridge_ips_today,

+ 1 - 0
src/lib.rs

@@ -23,6 +23,7 @@ pub mod analysis;
 pub mod bridge_verification_info;
 pub mod crypto;
 pub mod extra_info;
+pub mod lox_analysis;
 pub mod negative_report;
 pub mod positive_report;
 pub mod request_handler;

+ 196 - 0
src/lox_analysis.rs

@@ -0,0 +1,196 @@
+use crate::analysis::Analyzer;
+use lox_library::proto::{level_up::LEVEL_INTERVAL, trust_promotion::UNTRUSTED_INTERVAL};
+use std::cmp::min;
+
+// Get max value from slice, or 0 if empty
+fn max(nums: &[u32]) -> u32 {
+    let mut max_num = 0;
+    for i in nums {
+        if *i > max_num {
+            max_num = *i;
+        }
+    }
+    max_num
+}
+
+// Safe u32 subtraction: x - y or 0 if y > x
+fn ssub(x: u32, y: u32) -> u32 {
+    if y > x {
+        0
+    } else {
+        x - y
+    }
+}
+
+/// Analyzer for Lox bridges based on how long they've been active
+pub struct LoxAnalyzer {
+    // simple variable threshold from 0-4 or something
+    harshness: u32,
+}
+
+impl Analyzer for LoxAnalyzer {
+    // At this stage, we expect 0-10 users, so 0, 8, or 16 bridge-ips
+    fn stage_one(
+        &self,
+        age: u32,
+        _confidence: f64,
+        bridge_ips: &[u32],
+        bridge_ips_today: u32,
+        _negative_reports: &[u32],
+        negative_reports_today: u32,
+    ) -> bool {
+        // Get max bridge_ips we've seen
+        let max_bridge_ips = max(bridge_ips);
+
+        if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
+            return true;
+        }
+
+        // If we have too many negative reports, consider the bridge
+        // blocked
+        self.too_many_negative_reports(negative_reports_today)
+    }
+
+    fn stage_two(
+        &self,
+        age: u32,
+        _confidence: f64,
+        bridge_ips: &[u32],
+        bridge_ips_today: u32,
+        _negative_reports: &[u32],
+        negative_reports_today: u32,
+    ) -> bool {
+        // Get max bridge_ips we've seen
+        let max_bridge_ips = max(bridge_ips);
+
+        if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
+            return true;
+        }
+
+        // If we have too many negative reports, consider the bridge
+        // blocked
+        self.too_many_negative_reports(negative_reports_today)
+    }
+
+    fn stage_three(
+        &self,
+        age: u32,
+        _confidence: f64,
+        bridge_ips: &[u32],
+        bridge_ips_today: u32,
+        _negative_reports: &[u32],
+        negative_reports_today: u32,
+        _positive_reports: &[u32],
+        positive_reports_today: u32,
+    ) -> bool {
+        // Get max bridge_ips we've seen
+        let max_bridge_ips = max(bridge_ips);
+
+        if self.too_few_bridge_ips(
+            age,
+            max_bridge_ips,
+            bridge_ips_today,
+            positive_reports_today,
+        ) {
+            return true;
+        }
+
+        // If we have too many negative reports, consider the bridge
+        // blocked
+        self.too_many_negative_reports(negative_reports_today)
+    }
+}
+
+impl LoxAnalyzer {
+    pub fn new(harshness: u32) -> Self {
+        Self { harshness }
+    }
+
+    // Maximum number of negative reports without considering the bridge blocked
+    fn too_many_negative_reports(&self, negative_reports_today: u32) -> bool {
+        // If we have more negative reports than 4 - harshness
+        negative_reports_today > 4 - self.harshness
+
+        // harshness: 4, blocked if 1 negative report
+        // harshness: 3, blocked if 2 negative reports
+        // harshness: 2, blocked if 3 negative reports
+        // harshness: 1, blocked if 4 negative reports
+        // harshness: 0, blocked if 5 negative reports
+    }
+
+    // Based on the age of the bridge and the max bridge-ips observed, do we
+    // have too few today?
+    fn too_few_bridge_ips(
+        &self,
+        days: u32,
+        max_bridge_ips: u32,
+        bridge_ips_today: u32,
+        positive_reports_today: u32,
+    ) -> bool {
+        if days <= UNTRUSTED_INTERVAL {
+            // We expect 0-10 users
+            if self.harshness == 4 {
+                // In the most extreme case, mark any bridge with 0 connections
+                bridge_ips_today == 0
+            } else if self.harshness >= 2 {
+                // With medium harshness, mark any bridge that has had
+                // 9+ connections and now sees 0
+                max_bridge_ips > 8 && bridge_ips_today == 0
+            } else {
+                // With low harshness, we cannot make a judgement from
+                // bridge stats
+                false
+            }
+        } else if days <= UNTRUSTED_INTERVAL + LEVEL_INTERVAL[1] {
+            // We expect 1-30 users
+
+            let threshold = min(max_bridge_ips, 32);
+
+            // If we're at least 4 - self.harshness bins below the
+            // maximum value we've seen or 32 if we've seen a lot of
+            // connections, consider the bridge blocked.
+            bridge_ips_today < ssub(threshold, (4 - self.harshness) * 8)
+
+            // Examples:
+            // max 64 connections, harshness = 4
+            //    Return true if today's count is less than 32
+            // max 32 connections, harshness = 4
+            //    Return true if today's count is less than 32
+            // max 32 connections, harshness = 3
+            //    Return true if today's count is less than 24
+            // max 32 connections, harshness = 2
+            //    Return true if today's count is less than 16
+            // max 8 connections, harshness = 4
+            //    Return true if today's count is less than 8
+            // max 8 connections, harshness = 3
+            //    Return false
+        } else {
+            // Similar, but consider positive reports as well
+            let threshold = min(max_bridge_ips, 32);
+
+            // We allow positive reports to increase the threshold for
+            // considering the bridge blocked. 8 positive reports
+            // (rounded up) remove 1 level reduction from the threshold
+            let threshold = ssub(
+                threshold,
+                (4 - self.harshness + (positive_reports_today + 7) / 8) * 8,
+            );
+
+            // For example, suppose we've seen 32+ connections.
+            //
+            // If we have harshness 4, we mark the bridge blocked if it
+            // has fewer than 32 connections.
+            //
+            // If we have harshness 4 but have seen 1-7 positive
+            // reports, we mark the bridge blocked only if it has fewer
+            // than 24 connections.
+            //
+            // 25 positive reports reduce all thresholds to 0, meaning
+            // bridges will never be marked blocked from bridge stats
+            // if the adversary can submit 25 positive reports.
+
+            // Consider the bridge blocked if we have too few connections
+            bridge_ips_today < threshold
+        }
+    }
+}

+ 2 - 1
src/main.rs

@@ -103,7 +103,8 @@ async fn update_daily_info(
     update_positive_reports(db, distributors).await;
     let new_blockages = guess_blockages(
         db,
-        &analysis::NormalAnalyzer::new(max_threshold, scaling_factor),
+        // Using max_threshold for convenience
+        &lox_analysis::LoxAnalyzer::new(max_threshold),
         confidence,
         min_historical_days,
         max_historical_days,