Browse Source

Some basic thresholds

Vecna 4 months ago
parent
commit
264e3824d6
2 changed files with 119 additions and 83 deletions
  1. 117 82
      src/lox_analysis.rs
  2. 2 1
      src/main.rs

+ 117 - 82
src/lox_analysis.rs

@@ -1,12 +1,6 @@
 use crate::analysis::Analyzer;
-use lox_library::{
-    bridge_table::MAX_BRIDGES_PER_BUCKET,
-    proto::{
-        level_up::{LEVEL_INTERVAL, LEVEL_INVITATIONS},
-        trust_promotion::UNTRUSTED_INTERVAL,
-    },
-    OPENINV_K,
-};
+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 {
@@ -19,73 +13,20 @@ fn max(nums: &[u32]) -> u32 {
     max_num
 }
 
-// Max users expected on a Lox bridge based on how long it's been around
-fn max_users(days: u32) -> u32 {
-    if days <= UNTRUSTED_INTERVAL {
-        // k users for open-entry bucket
-        OPENINV_K
-    } else if days <= UNTRUSTED_INTERVAL + LEVEL_INTERVAL[1] {
-        // k users per bridge x 3 bridges in invite-only bucket
-        OPENINV_K * MAX_BRIDGES_PER_BUCKET as u32
-    } else if days <= UNTRUSTED_INTERVAL + LEVEL_INTERVAL[2] {
-        // suppose users have used all their invitations
-        OPENINV_K * MAX_BRIDGES_PER_BUCKET as u32 * (1 + LEVEL_INVITATIONS[1])
-    } else {
-        // stop counting here, just say it's a lot
-        100
-    }
-}
-
-// Maximum number of negative reports without considering the bridge blocked
-fn max_negative_reports(days: u32) -> u32 {
-    // How many users do we expect to use this bridge?
-    let max_users = max_users(days);
-
-    // Based on that, allow this many negative reports
-    if max_users <= 10 {
-        1
-    } else if max_users <= 30 {
-        5
-    } else {
-        10
-    }
-}
-
-fn too_few_bridge_ips(
-    days: u32,
-    _max_bridge_ips: u32,
-    bridge_ips_today: u32,
-    positive_reports_today: u32,
-) -> bool {
-    // How many users do we expect to use this bridge?
-    let max_users = max_users(days);
-
-    // Based on that, expect this many bridge ips
-    let min_bip = if max_users <= 16 {
-        // expect 10
+// Safe u32 subtraction: x - y or 0 if y > x
+fn ssub(x: u32, y: u32) -> u32 {
+    if y > x {
         0
-    } else if max_users <= 40 {
-        // expect 30, possibly as few as 21
-        0
-    } else if max_users <= 96 {
-        // expect 90
-        8
     } else {
-        16
-    };
-
-    // If we see positive reports from trusted users, halve the minimum
-    let min_bip = if positive_reports_today > 0 {
-        min_bip / 2
-    } else {
-        min_bip
-    };
-
-    bridge_ips_today < min_bip
+        x - y
+    }
 }
 
 /// Analyzer for Lox bridges based on how long they've been active
-pub struct LoxAnalyzer {}
+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
@@ -101,13 +42,13 @@ impl Analyzer for LoxAnalyzer {
         // Get max bridge_ips we've seen
         let max_bridge_ips = max(bridge_ips);
 
-        if too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
+        if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
             return true;
         }
 
-        // If we have more negative reports than expected, consider the
-        // bridge blocked
-        negative_reports_today > max_negative_reports(age)
+        // If we have too many negative reports, consider the bridge
+        // blocked
+        self.too_many_negative_reports(negative_reports_today)
     }
 
     fn stage_two(
@@ -122,13 +63,13 @@ impl Analyzer for LoxAnalyzer {
         // Get max bridge_ips we've seen
         let max_bridge_ips = max(bridge_ips);
 
-        if too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
+        if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
             return true;
         }
 
-        // If we have more negative reports than expected, consider the
-        // bridge blocked
-        negative_reports_today > max_negative_reports(age)
+        // If we have too many negative reports, consider the bridge
+        // blocked
+        self.too_many_negative_reports(negative_reports_today)
     }
 
     fn stage_three(
@@ -145,7 +86,7 @@ impl Analyzer for LoxAnalyzer {
         // Get max bridge_ips we've seen
         let max_bridge_ips = max(bridge_ips);
 
-        if too_few_bridge_ips(
+        if self.too_few_bridge_ips(
             age,
             max_bridge_ips,
             bridge_ips_today,
@@ -154,8 +95,102 @@ impl Analyzer for LoxAnalyzer {
             return true;
         }
 
-        // If we have more negative reports than expected, consider the
-        // bridge blocked
-        negative_reports_today > max_negative_reports(age)
+        // 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,
-        &lox_analysis::LoxAnalyzer {},
+        // Using max_threshold for convenience
+        &lox_analysis::LoxAnalyzer::new(max_threshold),
         confidence,
         min_historical_days,
         max_historical_days,