diff --git a/crates/lox-distributor/src/lox_context.rs b/crates/lox-distributor/src/lox_context.rs
index 458e265..4a4732b 100644
--- a/crates/lox-distributor/src/lox_context.rs
+++ b/crates/lox-distributor/src/lox_context.rs
@@ -794,6 +794,34 @@ impl LoxServerContext {
// Troll Patrol-related tasks
+ // Troll Patrol reports that a bridge is blocked somewhere
+ pub fn report_blocked_bridges(self, request: Bytes) -> Response
{
+ let blocked_bridges: HashMap> =
+ match serde_json::from_slice(&request) {
+ Ok(req) => req,
+ Err(e) => {
+ let response = json!({"error": e.to_string()});
+ let val = serde_json::to_string(&response).unwrap();
+ return prepare_header(val);
+ }
+ };
+ // TODO: Forward this information to rdsys
+ for bridge_str in blocked_bridges.keys() {
+ let bridge = array_bytes::hex2array(bridge_str).unwrap();
+ if self.tp_bridge_infos.lock().unwrap().contains_key(&bridge) {
+ let bl = self
+ .tp_bridge_infos
+ .lock()
+ .unwrap()
+ .get(&bridge)
+ .unwrap()
+ .bridge_line;
+ self.mark_blocked(bl);
+ }
+ }
+ prepare_header("OK".to_string())
+ }
+
// Verify one negative report, return true if verification succeeds
pub fn verify_negative_report(&self, report: NegativeReport) -> bool {
match self
diff --git a/crates/lox-distributor/src/troll_patrol_handler.rs b/crates/lox-distributor/src/troll_patrol_handler.rs
index 3bbdec2..aceb3d0 100644
--- a/crates/lox-distributor/src/troll_patrol_handler.rs
+++ b/crates/lox-distributor/src/troll_patrol_handler.rs
@@ -18,6 +18,10 @@ pub async fn handle(
.body(Body::from("Allow POST"))
.unwrap()),
_ => match (req.method(), req.uri().path()) {
+ (&Method::POST, "/reportblocked") => Ok::<_, Infallible>({
+ let bytes = body::to_bytes(req.into_body()).await.unwrap();
+ cloned_context.report_blocked_bridges(bytes)
+ }),
(&Method::POST, "/verifynegative") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.verify_negative_reports(bytes)
@@ -49,6 +53,7 @@ mod tests {
proto::*,
scalar_u32, BridgeAuth, BridgeDb,
};
+ use lox_zkp::ProofError;
use rand::RngCore;
use sha1::{Digest, Sha1};
use std::{
@@ -65,6 +70,8 @@ mod tests {
use super::*;
trait TpClient {
+ fn reportblocked(&self, blocked_bridges: HashMap>)
+ -> Request;
fn verifynegative(&self, reports: BTreeMap) -> Request;
fn verifypositive(&self, reports: Vec) -> Request;
}
@@ -72,6 +79,18 @@ mod tests {
struct TpClientMock {}
impl TpClient for TpClientMock {
+ fn reportblocked(
+ &self,
+ blocked_bridges: HashMap>,
+ ) -> Request {
+ let req = serde_json::to_string(&blocked_bridges).unwrap();
+ Request::builder()
+ .method("POST")
+ .uri("http://localhost/reportblocked")
+ .body(Body::from(req))
+ .unwrap()
+ }
+
fn verifynegative(&self, reports: BTreeMap) -> Request {
let req = serde_json::to_string(&reports).unwrap();
Request::builder()
@@ -165,6 +184,19 @@ mod tests {
String::from_utf8(body_bytes.to_vec()).unwrap()
}
+ async fn get_bucket(
+ th: &mut TestHarness,
+ cred: &Lox,
+ ) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
+ let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
+ let mut ba = th.context.ba.lock().unwrap();
+ let encbuckets = ba.enc_bridge_table();
+ let bucket =
+ bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
+ .unwrap();
+ bucket.0
+ }
+
async fn get_new_credential(th: &mut TestHarness) -> Lox {
let inv = th.context.db.lock().unwrap().invite().unwrap();
let (req, state) = open_invite::request(&inv);
@@ -181,7 +213,7 @@ mod tests {
cred
}
- async fn level_up(th: &mut TestHarness, cred: &Lox) -> Lox {
+ async fn level_up(th: &mut TestHarness, cred: &Lox) -> Result {
let current_level = scalar_u32(&cred.trust_level).unwrap();
if current_level == 0 {
th.context
@@ -189,13 +221,13 @@ mod tests {
let mut ba = th.context.ba.lock().unwrap();
let (promreq, promstate) =
trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap();
- let promresp = ba.handle_trust_promotion(promreq).unwrap();
- let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
+ let promresp = ba.handle_trust_promotion(promreq)?;
+ let migcred = trust_promotion::handle_response(promstate, promresp)?;
let (migreq, migstate) =
migration::request(cred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap();
- let migresp = ba.handle_migration(migreq).unwrap();
+ let migresp = ba.handle_migration(migreq)?;
let new_cred = migration::handle_response(migstate, migresp, &ba.lox_pub).unwrap();
- new_cred
+ Ok(new_cred)
} else {
th.context.advance_days_test(
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
@@ -208,7 +240,11 @@ mod tests {
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
- let reachcred = bucket.1.unwrap();
+ let reachcred = match bucket.1 {
+ Some(v) => v,
+ None => return Err(ProofError::VerificationFailure),
+ };
+ //let reachcred = bucket.1.unwrap();
let (lvreq, lvstate) = level_up::request(
cred,
&reachcred,
@@ -217,12 +253,103 @@ mod tests {
ba.today(),
)
.unwrap();
- let lvresp = ba.handle_level_up(lvreq).unwrap();
+ let lvresp = ba.handle_level_up(lvreq)?;
let new_cred = level_up::handle_response(lvstate, lvresp, &ba.lox_pub).unwrap();
- new_cred
+ Ok(new_cred)
}
}
+ #[tokio::test]
+ async fn test_report_blocked_bridges() {
+ let mut th = TestHarness::new();
+ let tpc = TpClientMock {};
+ let mut Htables = HashMap::::new();
+
+ // helper function to create map of bridges from bucket to mark blocked
+ fn bridges_to_block(
+ bucket: [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET],
+ num_bridges_to_block: usize,
+ ) -> HashMap> {
+ let mut blocked_bridges = HashMap::>::new();
+ for i in 0..num_bridges_to_block {
+ let mut hasher = Sha1::new();
+ hasher.update(bucket[i].fingerprint);
+ let mut countries = HashSet::::new();
+ countries.insert("RU".to_string());
+ blocked_bridges.insert(array_bytes::bytes2hex("", hasher.finalize()), countries);
+ }
+ blocked_bridges
+ }
+
+ // Get new level 0 credential
+ let cred = get_new_credential(&mut th).await;
+
+ th.context.generate_tp_bridge_infos();
+
+ let bridges = get_bucket(&mut th, &cred).await;
+
+ // Block our first (and only) bridge
+ let blocked_bridges = bridges_to_block(bridges, 1);
+ let request = tpc.reportblocked(blocked_bridges);
+ let response = handle(th.context.clone(), &mut Htables, request)
+ .await
+ .unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ let resp_str = body_to_string(response).await;
+ assert_eq!(resp_str, "OK");
+
+ th.context.generate_tp_bridge_infos();
+
+ // We should not be able to migrate to level 1
+ assert!(level_up(&mut th, &cred).await.is_err());
+
+ // Get new level 1 credential
+ let cred = get_new_credential(&mut th).await;
+ let cred = level_up(&mut th, &cred).await.unwrap();
+
+ th.context.generate_tp_bridge_infos();
+
+ let bridges = get_bucket(&mut th, &cred).await;
+
+ // Block as many bridges as possible without preventing level up
+ let blocked_bridges = bridges_to_block(
+ bridges,
+ bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY,
+ );
+ let request = tpc.reportblocked(blocked_bridges);
+ let response = handle(th.context.clone(), &mut Htables, request)
+ .await
+ .unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ let resp_str = body_to_string(response).await;
+ assert_eq!(resp_str, "OK");
+
+ th.context.generate_tp_bridge_infos();
+
+ // We should still be able to level up
+ let cred = level_up(&mut th, &cred).await.unwrap();
+
+ th.context.generate_tp_bridge_infos();
+
+ let bridges = get_bucket(&mut th, &cred).await;
+
+ // Block enough bridges to prevent level up
+ let blocked_bridges = bridges_to_block(
+ bridges,
+ bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY + 1,
+ );
+ let request = tpc.reportblocked(blocked_bridges);
+ let response = handle(th.context.clone(), &mut Htables, request)
+ .await
+ .unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ let resp_str = body_to_string(response).await;
+ assert_eq!(resp_str, "OK");
+
+ // We should not be able to level up
+ assert!(level_up(&mut th, &cred).await.is_err());
+ }
+
#[tokio::test]
async fn test_negative_reports() {
let mut th = TestHarness::new();
@@ -231,19 +358,11 @@ mod tests {
// Get new level 1 credential
let cred = get_new_credential(&mut th).await;
- let cred = level_up(&mut th, &cred).await;
+ let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos();
- let mut ba = th.context.ba.lock().unwrap();
-
- // Get bucket
- let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
- let encbuckets = ba.enc_bridge_table();
- let bucket =
- bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
- .unwrap();
- let bridges = bucket.0;
+ let bridges = get_bucket(&mut th, &cred).await;
// Create random number of negative reports for each bridge in bucket
let mut rng = rand::thread_rng();
@@ -340,21 +459,13 @@ mod tests {
// Get new level 3 credential
let cred = get_new_credential(&mut th).await;
- let cred = level_up(&mut th, &cred).await;
- let cred = level_up(&mut th, &cred).await;
- let cred = level_up(&mut th, &cred).await;
+ let cred = level_up(&mut th, &cred).await.unwrap();
+ let cred = level_up(&mut th, &cred).await.unwrap();
+ let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos();
- let mut ba = th.context.ba.lock().unwrap();
-
- // Get bucket
- let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
- let encbuckets = ba.enc_bridge_table();
- let bucket =
- bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
- .unwrap();
- let bridges = bucket.0;
+ let bridges = get_bucket(&mut th, &cred).await;
// Create a positive report for each bridge in bucket
let mut reports = Vec::::new();
@@ -363,7 +474,7 @@ mod tests {
bridge.fingerprint,
None,
&cred,
- &ba.lox_pub,
+ &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
reports.push(report.to_serializable_report());
@@ -377,7 +488,7 @@ mod tests {
bridges[0].fingerprint,
None,
&cred,
- &ba.lox_pub,
+ &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
invalid_report_1.date = invalid_report_1.date + 2;
@@ -388,7 +499,7 @@ mod tests {
bridges[1].fingerprint,
None,
&cred,
- &ba.lox_pub,
+ &th.context.ba.lock().unwrap().lox_pub,
"xx".to_string(),
);
reports.push(invalid_report_2.to_serializable_report());
@@ -404,15 +515,12 @@ mod tests {
bridges[2].fingerprint,
None,
&cred,
- &ba.lox_pub,
+ &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(),
);
invalid_report_3.fingerprint = empty_bridgeline_fingerprint;
reports.push(invalid_report_3.to_serializable_report());
- // Release lock so BA can be used to verify reports below
- drop(ba);
-
let request = tpc.verifypositive(reports);
let response = handle(th.context.clone(), &mut Htables, request)
.await