Compare commits
6 Commits
dc7531689c
...
7acba0a6f0
Author | SHA1 | Date |
---|---|---|
|
7acba0a6f0 | |
|
264e3824d6 | |
|
ed37bf1874 | |
|
58de633f3d | |
|
aab51731e1 | |
|
ac1f09a8c1 |
|
@ -8,11 +8,15 @@ use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "simulation")]
|
||||||
|
use crate::get_date;
|
||||||
|
|
||||||
/// Provides a function for predicting which countries block this bridge
|
/// Provides a function for predicting which countries block this bridge
|
||||||
pub trait Analyzer {
|
pub trait Analyzer {
|
||||||
/// Evaluate open-entry bridge. Returns true if blocked, false otherwise.
|
/// Evaluate open-entry bridge. Returns true if blocked, false otherwise.
|
||||||
fn stage_one(
|
fn stage_one(
|
||||||
&self,
|
&self,
|
||||||
|
age: u32,
|
||||||
confidence: f64,
|
confidence: f64,
|
||||||
bridge_ips: &[u32],
|
bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -24,6 +28,7 @@ pub trait Analyzer {
|
||||||
/// blocked, false otherwise.
|
/// blocked, false otherwise.
|
||||||
fn stage_two(
|
fn stage_two(
|
||||||
&self,
|
&self,
|
||||||
|
age: u32,
|
||||||
confidence: f64,
|
confidence: f64,
|
||||||
bridge_ips: &[u32],
|
bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -35,6 +40,7 @@ pub trait Analyzer {
|
||||||
/// blocked, false otherwise.
|
/// blocked, false otherwise.
|
||||||
fn stage_three(
|
fn stage_three(
|
||||||
&self,
|
&self,
|
||||||
|
age: u32,
|
||||||
confidence: f64,
|
confidence: f64,
|
||||||
bridge_ips: &[u32],
|
bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -118,6 +124,7 @@ pub fn blocked_in(
|
||||||
// open-entry bridge and/or not enough days of
|
// open-entry bridge and/or not enough days of
|
||||||
// historical days for stages 2 and 3
|
// historical days for stages 2 and 3
|
||||||
if analyzer.stage_one(
|
if analyzer.stage_one(
|
||||||
|
age,
|
||||||
confidence,
|
confidence,
|
||||||
&bridge_ips,
|
&bridge_ips,
|
||||||
bridge_ips_today,
|
bridge_ips_today,
|
||||||
|
@ -132,6 +139,7 @@ pub fn blocked_in(
|
||||||
// invite-only bridge without min_historical_days of
|
// invite-only bridge without min_historical_days of
|
||||||
// historical data on positive reports
|
// historical data on positive reports
|
||||||
if analyzer.stage_two(
|
if analyzer.stage_two(
|
||||||
|
age,
|
||||||
confidence,
|
confidence,
|
||||||
&bridge_ips,
|
&bridge_ips,
|
||||||
bridge_ips_today,
|
bridge_ips_today,
|
||||||
|
@ -144,6 +152,7 @@ pub fn blocked_in(
|
||||||
// invite-only bridge that has min_historical_days or
|
// invite-only bridge that has min_historical_days or
|
||||||
// more of historical data since the first positive report
|
// more of historical data since the first positive report
|
||||||
if analyzer.stage_three(
|
if analyzer.stage_three(
|
||||||
|
age,
|
||||||
confidence,
|
confidence,
|
||||||
&bridge_ips,
|
&bridge_ips,
|
||||||
bridge_ips_today,
|
bridge_ips_today,
|
||||||
|
@ -153,6 +162,23 @@ pub fn blocked_in(
|
||||||
positive_reports_today,
|
positive_reports_today,
|
||||||
) {
|
) {
|
||||||
blocked_in.insert(country.to_string());
|
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 {
|
impl Analyzer for ExampleAnalyzer {
|
||||||
fn stage_one(
|
fn stage_one(
|
||||||
&self,
|
&self,
|
||||||
|
_age: u32,
|
||||||
_confidence: f64,
|
_confidence: f64,
|
||||||
_bridge_ips: &[u32],
|
_bridge_ips: &[u32],
|
||||||
_bridge_ips_today: u32,
|
_bridge_ips_today: u32,
|
||||||
|
@ -179,6 +206,7 @@ impl Analyzer for ExampleAnalyzer {
|
||||||
|
|
||||||
fn stage_two(
|
fn stage_two(
|
||||||
&self,
|
&self,
|
||||||
|
_age: u32,
|
||||||
_confidence: f64,
|
_confidence: f64,
|
||||||
_bridge_ips: &[u32],
|
_bridge_ips: &[u32],
|
||||||
_bridge_ips_today: u32,
|
_bridge_ips_today: u32,
|
||||||
|
@ -190,6 +218,7 @@ impl Analyzer for ExampleAnalyzer {
|
||||||
|
|
||||||
fn stage_three(
|
fn stage_three(
|
||||||
&self,
|
&self,
|
||||||
|
_age: u32,
|
||||||
_confidence: f64,
|
_confidence: f64,
|
||||||
_bridge_ips: &[u32],
|
_bridge_ips: &[u32],
|
||||||
_bridge_ips_today: u32,
|
_bridge_ips_today: u32,
|
||||||
|
@ -221,6 +250,7 @@ impl Analyzer for NormalAnalyzer {
|
||||||
/// Evaluate open-entry bridge based on only today's data
|
/// Evaluate open-entry bridge based on only today's data
|
||||||
fn stage_one(
|
fn stage_one(
|
||||||
&self,
|
&self,
|
||||||
|
_age: u32,
|
||||||
_confidence: f64,
|
_confidence: f64,
|
||||||
_bridge_ips: &[u32],
|
_bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -234,6 +264,7 @@ impl Analyzer for NormalAnalyzer {
|
||||||
/// Evaluate invite-only bridge based on historical data
|
/// Evaluate invite-only bridge based on historical data
|
||||||
fn stage_two(
|
fn stage_two(
|
||||||
&self,
|
&self,
|
||||||
|
_age: u32,
|
||||||
confidence: f64,
|
confidence: f64,
|
||||||
bridge_ips: &[u32],
|
bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -305,6 +336,7 @@ impl Analyzer for NormalAnalyzer {
|
||||||
/// Evaluate invite-only bridge with lv3+ users submitting positive reports
|
/// Evaluate invite-only bridge with lv3+ users submitting positive reports
|
||||||
fn stage_three(
|
fn stage_three(
|
||||||
&self,
|
&self,
|
||||||
|
age: u32,
|
||||||
confidence: f64,
|
confidence: f64,
|
||||||
bridge_ips: &[u32],
|
bridge_ips: &[u32],
|
||||||
bridge_ips_today: u32,
|
bridge_ips_today: u32,
|
||||||
|
@ -401,6 +433,7 @@ impl Analyzer for NormalAnalyzer {
|
||||||
// evaluate each variable. Ignore positive reports and
|
// evaluate each variable. Ignore positive reports and
|
||||||
// compute as in stage 2
|
// compute as in stage 2
|
||||||
if self.stage_two(
|
if self.stage_two(
|
||||||
|
age,
|
||||||
confidence,
|
confidence,
|
||||||
bridge_ips,
|
bridge_ips,
|
||||||
bridge_ips_today,
|
bridge_ips_today,
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub mod analysis;
|
||||||
pub mod bridge_verification_info;
|
pub mod bridge_verification_info;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod extra_info;
|
pub mod extra_info;
|
||||||
|
pub mod lox_analysis;
|
||||||
pub mod negative_report;
|
pub mod negative_report;
|
||||||
pub mod positive_report;
|
pub mod positive_report;
|
||||||
pub mod request_handler;
|
pub mod request_handler;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,7 +103,8 @@ async fn update_daily_info(
|
||||||
update_positive_reports(db, distributors).await;
|
update_positive_reports(db, distributors).await;
|
||||||
let new_blockages = guess_blockages(
|
let new_blockages = guess_blockages(
|
||||||
db,
|
db,
|
||||||
&analysis::NormalAnalyzer::new(max_threshold, scaling_factor),
|
// Using max_threshold for convenience
|
||||||
|
&lox_analysis::LoxAnalyzer::new(max_threshold),
|
||||||
confidence,
|
confidence,
|
||||||
min_historical_days,
|
min_historical_days,
|
||||||
max_historical_days,
|
max_historical_days,
|
||||||
|
|
Loading…
Reference in New Issue