From 0c74c62e3e87b557fbb763b9a8e48fad7d9b534f Mon Sep 17 00:00:00 2001 From: Vecna Date: Fri, 23 May 2025 13:28:39 -0400 Subject: [PATCH] Verify Troll Patrol's inferences but still count wrong inferences --- Dockerfile | 2 +- configs/troll_patrol_config.json.template | 1 + src/direct_scan_server.rs | 177 ++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 37 ++++- 5 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 src/direct_scan_server.rs diff --git a/Dockerfile b/Dockerfile index 11502fe..b640d33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,7 @@ RUN cp /home/user/build/config.toml .cargo/ WORKDIR /home/user/build RUN git clone https://git-crysp.uwaterloo.ca/vvecna/troll-patrol.git WORKDIR /home/user/build/troll-patrol -RUN git checkout 7acba0a6f00c6ffdb429b4993ee109a8e125b466 +RUN git checkout ff3aa2d71812fe5fb974856c021c98d812d2f880 RUN mkdir -p .cargo RUN cp /home/user/build/config.toml .cargo/ diff --git a/configs/troll_patrol_config.json.template b/configs/troll_patrol_config.json.template index ddcbaae..2a3682c 100644 --- a/configs/troll_patrol_config.json.template +++ b/configs/troll_patrol_config.json.template @@ -6,6 +6,7 @@ "Lox": "http://127.0.0.1:8002" }, "extra_infos_base_url": "http://127.0.0.1:8004/", + "verify_blockages_url": "http://127.0.0.1:8006/verify_blocked_bridges", "confidence": 0.95, "max_threshold": HARSHNESS, "scaling_factor": 0.25, diff --git a/src/direct_scan_server.rs b/src/direct_scan_server.rs new file mode 100644 index 0000000..65a6a4c --- /dev/null +++ b/src/direct_scan_server.rs @@ -0,0 +1,177 @@ +// Simulates direct scans by keeping a list of bridges blocked by the censor + +use hyper::{ + body::{self, Bytes}, + header::HeaderValue, + server::conn::AddrStream, + service::{make_service_fn, service_fn}, + Body, Method, Request, Response, Server, StatusCode, +}; +use serde_json::json; +use std::{ + collections::{HashMap, HashSet}, + convert::Infallible, + net::SocketAddr, + time::Duration, +}; +use tokio::{ + spawn, + sync::{mpsc, oneshot}, + time::sleep, +}; + +async fn serve_direct_scan_api( + // We have to serialize our HashMap keys as Strings instead of [u8; 20]s, + // so it's easier to just use Strings throughout. + blocked_bridges: &mut HashSet, + guessed_bridges: &mut HashMap>, + req: Request, +) -> Result, Infallible> { + match req.method() { + &Method::OPTIONS => Ok(Response::builder() + .header("Access-Control-Allow-Origin", HeaderValue::from_static("*")) + .header("Access-Control-Allow-Headers", "accept, content-type") + .header("Access-Control-Allow-Methods", "POST") + .status(200) + .body(Body::from("Allow POST")) + .unwrap()), + _ => match req.uri().path() { + "/add_blocked_bridges" => Ok::<_, Infallible>({ + let bytes = body::to_bytes(req.into_body()).await.unwrap(); + add_blocked_bridges(blocked_bridges, bytes) + }), + "/verify_blocked_bridges" => Ok::<_, Infallible>({ + let bytes = body::to_bytes(req.into_body()).await.unwrap(); + verify_blocked_bridges(blocked_bridges, guessed_bridges, bytes) + }), + // Get the bridges Troll Patrol guessed were blocked today + "/get_guessed_bridges" => Ok::<_, Infallible>({ + prepare_header(serde_json::to_string(&guessed_bridges).unwrap()) + }), + // Reset guessed bridges at the beginning of the day + "/reset_guessed_bridges" => Ok::<_, Infallible>({ + *guessed_bridges = HashMap::>::new(); + prepare_header("OK".to_string()) + }), + _ => Ok::<_, Infallible>({ + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("Not found")) + .unwrap() + }), + }, + } +} + +pub async fn server() { + let (context_tx, context_rx) = mpsc::channel(32); + let request_tx = context_tx.clone(); + + spawn(async move { create_context_manager(context_rx).await }); + + let addr = SocketAddr::from(([127, 0, 0, 1], 8006)); + let make_svc = make_service_fn(move |_conn: &AddrStream| { + let request_tx = request_tx.clone(); + let service = service_fn(move |req| { + let request_tx = request_tx.clone(); + let (response_tx, response_rx) = oneshot::channel(); + let cmd = Command::Request { + req, + sender: response_tx, + }; + async move { + request_tx.send(cmd).await.unwrap(); + response_rx.await.unwrap() + } + }); + async move { Ok::<_, Infallible>(service) } + }); + let server = Server::bind(&addr).serve(make_svc); + println!("Listening on localhost:8006"); + if let Err(e) = server.await { + eprintln!("server error: {}", e); + } +} + +async fn create_context_manager(context_rx: mpsc::Receiver) { + tokio::select! { + create_context = context_manager(context_rx) => create_context, + } +} + +async fn context_manager(mut context_rx: mpsc::Receiver) { + let mut blocked_bridges = HashSet::::new(); + let mut guessed_bridges = HashMap::>::new(); + + while let Some(cmd) = context_rx.recv().await { + use Command::*; + match cmd { + Request { req, sender } => { + let response = + serve_direct_scan_api(&mut blocked_bridges, &mut guessed_bridges, req).await; + if let Err(e) = sender.send(response) { + eprintln!("Server Response Error: {:?}", e); + } + sleep(Duration::from_millis(1)).await; + } + } + } +} + +#[derive(Debug)] +enum Command { + Request { + req: Request, + sender: oneshot::Sender, Infallible>>, + }, +} + +fn add_blocked_bridges(blocked_bridges: &mut HashSet, request: Bytes) -> Response { + let new_blocked_bridges: HashSet = 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); + } + }; + + blocked_bridges.extend(new_blocked_bridges); + + prepare_header("OK".to_string()) +} + +fn verify_blocked_bridges( + blocked_bridges: &mut HashSet, + guessed_bridges: &mut HashMap>, + request: Bytes, +) -> Response { + let mut verified_blocked_bridges = HashMap::>::new(); + let possibly_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); + } + }; + + *guessed_bridges = possibly_blocked_bridges.clone(); + + for (fpr, countries) in possibly_blocked_bridges { + if blocked_bridges.contains(&fpr) { + verified_blocked_bridges.insert(fpr, countries); + } + } + + prepare_header(serde_json::to_string(&verified_blocked_bridges).unwrap()) +} + +// Prepare HTTP Response for successful Server Request +fn prepare_header(response: String) -> Response { + let mut resp = Response::new(Body::from(response)); + resp.headers_mut() + .insert("Access-Control-Allow-Origin", HeaderValue::from_static("*")); + resp +} diff --git a/src/lib.rs b/src/lib.rs index d4df88d..0b17077 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod bridge; pub mod censor; pub mod config; +pub mod direct_scan_server; pub mod extra_infos_server; pub mod user; diff --git a/src/main.rs b/src/main.rs index c461e64..e277386 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use lox_simulation::{ bridge::Bridge, censor::{self, Censor}, config::Config as SConfig, - extra_infos_server, + direct_scan_server, extra_infos_server, user::User, }; @@ -88,6 +88,9 @@ pub async fn main() { let extra_infos_net = HyperNet { hostname: "http://localhost:8004".to_string(), }; + let scan_net = HyperNet { + hostname: "http://localhost:8006".to_string(), + }; let la_pubkeys = get_lox_auth_keys(&la_net).await.unwrap(); @@ -134,6 +137,12 @@ pub async fn main() { }); sleep(Duration::from_millis(1)).await; + // Set up blockage verification server + spawn(async move { + direct_scan_server::server().await; + }); + sleep(Duration::from_millis(1)).await; + // Only consider bridges that have been distributed to users let mut false_neg = 0; let mut false_pos = 0; @@ -164,6 +173,11 @@ pub async fn main() { // Save some function calls by storing this let date = get_date(); + // Reset Troll Patrol's guesses + let _ = scan_net + .request("/reset_guessed_bridges".to_string(), vec![]) + .await; + // Count of users who could use at least one bridge today let mut count_users_can_connect = 0; let mut count_users_cannot_connect = 0; @@ -335,6 +349,18 @@ pub async fn main() { } if censor.is_active() { + // Update blockage verification server with list of known bridges + let mut known_bridges = HashSet::::new(); + for bridge in &censor.known_bridges { + known_bridges.insert(array_bytes::bytes2hex("", bridge)); + } + let _ = scan_net + .request( + "/add_blocked_bridges".to_string(), + serde_json::to_string(&known_bridges).unwrap().into(), + ) + .await; + censor.end_of_day_tasks(&sconfig, &mut bridges).await; } @@ -362,7 +388,14 @@ pub async fn main() { } // TROLL PATROL TASKS - let new_blockages_resp = tp_net_test.request("/update".to_string(), vec![]).await; + + // This gets verified guesses, which we ignore here + let _new_blockages_resp = tp_net_test.request("/update".to_string(), vec![]).await; + + // Get Troll Patrol's unverified guesses + let new_blockages_resp = scan_net + .request("/get_guessed_bridges".to_string(), vec![]) + .await; let new_blockages = match new_blockages_resp { Ok(resp) => match serde_json::from_slice(&resp) { Ok(new_blockages) => new_blockages,