Merge changes from upstream
This commit is contained in:
commit
0bf3b30669
|
@ -1862,18 +1862,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.190"
|
version = "1.0.192"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.190"
|
version = "1.0.192"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -4,9 +4,13 @@ version = "0.1.0"
|
||||||
authors = ["The Tor Project, Inc.", "Lindsey Tulloch <onyinyang@torproject.org", "Cecylia Bocovich <cohosh@torproject.org>"]
|
authors = ["The Tor Project, Inc.", "Lindsey Tulloch <onyinyang@torproject.org", "Cecylia Bocovich <cohosh@torproject.org>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.65.0"
|
rust-version = "1.65.0"
|
||||||
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox-project/~/wikis/home"
|
license = "MIT"
|
||||||
description = "Tool for receving resources from rdsys and distributing them to users"
|
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
||||||
keywords = ["tor", "lox", "bridges"]
|
description = "Tool for receving Tor bridges from rdsys and distributing them to users"
|
||||||
|
keywords = ["tor", "lox", "bridges","censorship-resistance"]
|
||||||
|
categories = ["web-programming::http-server"]
|
||||||
|
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/lox-distributor"
|
||||||
|
readme = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/blob/main/crates/lox-distributor/README.md"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -2,40 +2,107 @@
|
||||||
|
|
||||||
The Lox distributor receives resources from [rdsys](https://gitlab.torproject.org/tpo/anti-censorship/rdsys) and writes them to [Lox
|
The Lox distributor receives resources from [rdsys](https://gitlab.torproject.org/tpo/anti-censorship/rdsys) and writes them to [Lox
|
||||||
BridgeLines](https://git-crysp.uwaterloo.ca/iang/lox/src/master/src/bridge_table.rs#L42). Concurrently, it receives and responds to requests from [Lox clients](https://gitlab.torproject.org/tpo/anti-censorship/lox/lox-wasm). It saves the [LoxContext](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-distributor/src/lox_context.rs) to a database every time the Lox bridgetable is updated and before the distributor is shutdown.
|
BridgeLines](https://git-crysp.uwaterloo.ca/iang/lox/src/master/src/bridge_table.rs#L42). Concurrently, it receives and responds to requests from [Lox clients](https://gitlab.torproject.org/tpo/anti-censorship/lox/lox-wasm). It saves the [LoxContext](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-distributor/src/lox_context.rs) to a database every time the Lox bridgetable is updated and before the distributor is shutdown.
|
||||||
## Configure rdsys stream
|
|
||||||
|
|
||||||
A test `config.json` is included for testing on a local instance of rdsys. This
|
|
||||||
can be edited to correspond to the desired types of resources, endpoints and database configuration.
|
|
||||||
|
|
||||||
## Test Run
|
## Configuration
|
||||||
|
|
||||||
For testing purposes, you will need a running instance of rdsys as well as a running Lox client.
|
A test `config.json` is included for testing on a local instance of rdsys. There are several configurable
|
||||||
|
fields in this config file:
|
||||||
|
|
||||||
|
### DB Config
|
||||||
|
|
||||||
|
The DB config `db` accepts a `db_path` where the Lox distributor will look for or create a new Lox database as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
"db": {
|
||||||
|
"db_path": "path/to/db"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rdsys Config
|
||||||
|
|
||||||
|
The rdsys request `rtype` has the following fields:
|
||||||
|
|
||||||
|
`endpoint` the endpoint of the rdsys instance that the distributor will make requests to,
|
||||||
|
|
||||||
|
`name` the type of distributor we are requesting. In most cases this should be `lox`,
|
||||||
|
|
||||||
|
`token` the corresponding Api Token,
|
||||||
|
|
||||||
|
`types` the type of bridges that are being accepted.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```
|
||||||
|
"rtype": {
|
||||||
|
"endpoint": "http://127.0.0.1:7100/resources",
|
||||||
|
"name": "lox",
|
||||||
|
"token": "LoxApiTokenPlaceholder",
|
||||||
|
"types": [
|
||||||
|
"obfs4",
|
||||||
|
"scramblesuit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bridge Config
|
||||||
|
|
||||||
|
The Bridge config, `bridge_config` has the following fields:
|
||||||
|
|
||||||
|
`watched_blockages` lists the regions (as ISO 3166 country codes) that Lox will monitor for listed blockages
|
||||||
|
|
||||||
|
`percent_spares` is the percentage of buckets that should be allocated as hot spares (as opposed to open invitation buckets)
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```
|
||||||
|
"bridge_config": {
|
||||||
|
"watched_blockages": [
|
||||||
|
"RU"
|
||||||
|
],
|
||||||
|
"percent_spares": 50
|
||||||
|
},
|
||||||
|
```
|
||||||
|
### Metrics Port
|
||||||
|
|
||||||
|
The `metrics_port` field is the port that the prometheus server will run on.
|
||||||
|
|
||||||
|
### Command Line Arguments for Advanced Database Config
|
||||||
|
|
||||||
|
There are a few configurations for the Lox database that can be passed as arguments at run time since they are not likely to be suitable as persistent configuration options.
|
||||||
|
|
||||||
|
Rolling back to a previous version of the database is possible by passing the
|
||||||
|
`roll_back_date` flag at runtime and providing the date/time as a `%Y-%m-%d_%H:%M:%S` string. This argument should be passed if the `LoxContext` should be rolled back to a previous state due to, for example, a mass blocking event that is likely not due to Lox user behaviour. If the exact roll back date/time is not known, the last db entry within 24 hours from the passed `roll_back_date` will be used or else the program will fail gracefully.
|
||||||
|
|
||||||
|
|
||||||
|
## Distributor Staging Environnment
|
||||||
|
|
||||||
|
The lox distributor is currently deployed for testing on `rdsys-frontend-01`.
|
||||||
|
Client requests can be made to this distributor by following the instructions in the [`lox-wasm` README](../lox-wasm/README.md/#testing)
|
||||||
|
|
||||||
|
## Running the Lox Distributor Locally
|
||||||
|
|
||||||
|
For testing purposes, you will need a locally running instance of [rdsys](https://gitlab.torproject.org/tpo/anti-censorship/rdsys) as well as a running [Lox client](../lox-wasm/).
|
||||||
|
|
||||||
### Run rdsys locally
|
### Run rdsys locally
|
||||||
|
|
||||||
First clone rdsys from [here](https://gitlab.torproject.org/tpo/anti-censorship/rdsys) then change into the backend directory:
|
First clone rdsys from [here](https://gitlab.torproject.org/tpo/anti-censorship/rdsys) then follow the instructions in the [README](https://gitlab.torproject.org/tpo/anti-censorship/rdsys/-/blob/main/README.md) to create a locally running rdsys instance with fake bridge descriptors.
|
||||||
|
|
||||||
```
|
|
||||||
cd rdsys/cmd/backend
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally run rdsys:
|
|
||||||
|
|
||||||
```
|
|
||||||
./backend --config config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Config
|
|
||||||
|
|
||||||
The database has a few configuration options. The path for where the database
|
|
||||||
should be read/written can be specified in the `config.json`. Rolling back to a
|
|
||||||
previous version of the database is also possible by passing the
|
|
||||||
`roll_back_date` flag at runtime and providing the date/time as a `%Y-%m-%d_%H:%M:%S` string. This argument should be passed if the `LoxContext` should be rolled back to a previous state due to, for example, a mass blocking event that is likely not due to Lox user behaviour. If the exact roll back date/time is not known, the last db entry within 24 hours from the passed `roll_back_date` will be used or else the program will fail gracefully.
|
|
||||||
|
|
||||||
### Run Lox Distributor locally
|
### Run Lox Distributor locally
|
||||||
|
|
||||||
Simply run `cargo run -- config.json` :)
|
The easiest way to test with rdsys is to adjust the [config.json](config.json) so that the `rtype` reads as follows:
|
||||||
|
```
|
||||||
|
"rtype": {
|
||||||
|
"endpoint": "http://127.0.0.1:7100/resources",
|
||||||
|
"name": "https",
|
||||||
|
"token": "HttpsApiTokenPlaceholder",
|
||||||
|
"types": [
|
||||||
|
"obfs4",
|
||||||
|
"snowflake"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then simply run `cargo run -- config.json` :)
|
||||||
|
|
||||||
### Run a Lox client locally
|
### Run a Lox client locally
|
||||||
|
|
||||||
First clone lox-wasm from [here](https://gitlab.torproject.org/tpo/anti-censorship/lox/lox-wasm). Follow the instructions in the [README](https://gitlab.torproject.org/tpo/anti-censorship/lox/lox-wasm/-/blob/main/README.md) to build and test the Lox client.
|
First clone lox-wasm from [here](https://gitlab.torproject.org/tpo/anti-censorship/lox/lox-wasm). Follow the instructions in the [README](../lox-wasm/README.md) to build and test the Lox client.
|
||||||
|
|
|
@ -5,15 +5,18 @@
|
||||||
},
|
},
|
||||||
"metrics_port": 5222,
|
"metrics_port": 5222,
|
||||||
"bridge_config": {
|
"bridge_config": {
|
||||||
|
"watched_blockages": [
|
||||||
|
"RU"
|
||||||
|
],
|
||||||
"percent_spares": 50
|
"percent_spares": 50
|
||||||
},
|
},
|
||||||
"rtype": {
|
"rtype": {
|
||||||
"endpoint": "http://127.0.0.1:7100/resources",
|
"endpoint": "http://127.0.0.1:7100/resources",
|
||||||
"name": "https",
|
"name": "lox",
|
||||||
"token": "HttpsApiTokenPlaceholder",
|
"token": "LoxApiTokenPlaceholder",
|
||||||
"types": [
|
"types": [
|
||||||
"obfs2",
|
"obfs4",
|
||||||
"scramblesuit"
|
"snowflake"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -57,27 +57,27 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_working_resources(&self, working_resources: Vec<Resource>) -> Vec<u64> {
|
pub fn handle_working_resources(
|
||||||
|
&self,
|
||||||
|
watched_blockages: Vec<String>,
|
||||||
|
working_resources: Vec<Resource>,
|
||||||
|
) -> Vec<u64> {
|
||||||
let mut accounted_for_bridges: Vec<u64> = Vec::new();
|
let mut accounted_for_bridges: Vec<u64> = Vec::new();
|
||||||
let bridgelines = parse_into_bridgelines(working_resources);
|
let (bridgelines, blocked_bridgelines) =
|
||||||
for bridge in bridgelines {
|
parse_into_bridgelines(watched_blockages, working_resources);
|
||||||
/* TODO: Functionality for marking bridges as unreachable/blocked should eventually happen here.
|
for bridge in blocked_bridgelines {
|
||||||
It is currently not enabled as there is not yet a reliable way to determine that a bridge is blocked.
|
let res = self.mark_blocked(bridge);
|
||||||
This means that migrations to unblocked bridges do not currently work but can be easily enabled by parsing the
|
|
||||||
list of `blocked resources` from rdsys or another source with something like the following:
|
|
||||||
let res = context.add_unreachable(bridgeline);
|
|
||||||
if res {
|
if res {
|
||||||
println!(
|
println!("BridgeLine {:?} successfully marked unreachable", bridge);
|
||||||
"BridgeLine {:?} successfully marked unreachable: {:?}",
|
self.metrics.blocked_bridges.inc();
|
||||||
bridgeline
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"BridgeLine {:?} NOT marked unreachable, saved for next update!",
|
"BridgeLine {:?} NOT marked unreachable, not found in bridgetable!",
|
||||||
bridge.uid_fingerprint
|
bridge.uid_fingerprint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*/
|
}
|
||||||
|
for bridge in bridgelines {
|
||||||
let res = self.update_bridge(bridge);
|
let res = self.update_bridge(bridge);
|
||||||
if res {
|
if res {
|
||||||
println!(
|
println!(
|
||||||
|
@ -96,12 +96,30 @@ impl LoxServerContext {
|
||||||
accounted_for_bridges
|
accounted_for_bridges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When syncing resources with rdsys, handle the non-working resources
|
||||||
|
// Those that are blocked in the target region are marked as unreachable/blocked
|
||||||
|
// All others are matched by fingerprint and if they are still in the grace period, they are updated
|
||||||
|
// otherwise they are replaced with new bridges
|
||||||
pub fn handle_not_working_resources(
|
pub fn handle_not_working_resources(
|
||||||
&self,
|
&self,
|
||||||
|
watched_blockages: Vec<String>,
|
||||||
not_working_resources: Vec<Resource>,
|
not_working_resources: Vec<Resource>,
|
||||||
mut accounted_for_bridges: Vec<u64>,
|
mut accounted_for_bridges: Vec<u64>,
|
||||||
) -> Vec<u64> {
|
) -> Vec<u64> {
|
||||||
let (grace_period, failing) = sort_for_parsing(not_working_resources);
|
let (grace_period, failing, blocked) =
|
||||||
|
sort_for_parsing(watched_blockages, not_working_resources);
|
||||||
|
for bridge in blocked {
|
||||||
|
let res = self.mark_blocked(bridge);
|
||||||
|
if res {
|
||||||
|
println!("BridgeLine {:?} successfully marked unreachable", bridge);
|
||||||
|
self.metrics.blocked_bridges.inc();
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"BridgeLine {:?} NOT marked unreachable, not found in bridgetable!",
|
||||||
|
bridge.uid_fingerprint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Update bridges in the bridge table that are failing but within the grace period
|
// Update bridges in the bridge table that are failing but within the grace period
|
||||||
for bridge in grace_period {
|
for bridge in grace_period {
|
||||||
let res = self.update_bridge(bridge);
|
let res = self.update_bridge(bridge);
|
||||||
|
@ -151,18 +169,22 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync resources received from rdsys with the Lox bridgetable
|
// Sync resources received from rdsys with the Lox bridgetable
|
||||||
pub fn sync_with_bridgetable(&self, resources: ResourceState) {
|
pub fn sync_with_bridgetable(&self, watched_blockages: Vec<String>, resources: ResourceState) {
|
||||||
// Check if each resource is already in the Lox bridgetable. If it is, it's probably fine
|
// Check if each resource is already in the Lox bridgetable. If it is, it's probably fine
|
||||||
// to replace the existing resource with the incoming one to account for changes
|
// to replace the existing resource with the incoming one to account for changes
|
||||||
// save a list of accounted for bridges and deal with the unaccounted for bridges at the end
|
// save a list of accounted for bridges and deal with the unaccounted for bridges at the end
|
||||||
let mut accounted_for_bridges: Vec<u64> = Vec::new();
|
let mut accounted_for_bridges: Vec<u64> = Vec::new();
|
||||||
// ensure all working resources are updated and accounted for
|
// ensure all working resources are updated and accounted for
|
||||||
if let Some(working_resources) = resources.working {
|
if let Some(working_resources) = resources.working {
|
||||||
accounted_for_bridges = self.handle_working_resources(working_resources);
|
accounted_for_bridges =
|
||||||
|
self.handle_working_resources(watched_blockages.clone(), working_resources);
|
||||||
}
|
}
|
||||||
if let Some(not_working_resources) = resources.not_working {
|
if let Some(not_working_resources) = resources.not_working {
|
||||||
accounted_for_bridges =
|
accounted_for_bridges = self.handle_not_working_resources(
|
||||||
self.handle_not_working_resources(not_working_resources, accounted_for_bridges);
|
watched_blockages,
|
||||||
|
not_working_resources,
|
||||||
|
accounted_for_bridges,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let mut ba_clone = self.ba.lock().unwrap();
|
let mut ba_clone = self.ba.lock().unwrap();
|
||||||
let total_reachable = ba_clone.bridge_table.reachable.len();
|
let total_reachable = ba_clone.bridge_table.reachable.len();
|
||||||
|
@ -239,6 +261,9 @@ impl LoxServerContext {
|
||||||
to_be_replaced_bridges.push(bridge);
|
to_be_replaced_bridges.push(bridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add extra_bridges to the Lox bridge table as open invitation bridges
|
||||||
|
// TODO: Add some consideration for whether or not bridges should be sorted as
|
||||||
|
// open invitation buckets or hot spare buckets
|
||||||
pub fn allocate_leftover_bridges(&self) {
|
pub fn allocate_leftover_bridges(&self) {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
let mut db_obj = self.db.lock().unwrap();
|
let mut db_obj = self.db.lock().unwrap();
|
||||||
|
@ -246,6 +271,7 @@ impl LoxServerContext {
|
||||||
ba_obj.allocate_bridges(&mut extra_bridges, &mut db_obj);
|
ba_obj.allocate_bridges(&mut extra_bridges, &mut db_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add an open invitation bucket to the Lox db
|
||||||
pub fn add_openinv_bucket(&self, bucket: [BridgeLine; 3]) {
|
pub fn add_openinv_bucket(&self, bucket: [BridgeLine; 3]) {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
let mut db_obj = self.db.lock().unwrap();
|
let mut db_obj = self.db.lock().unwrap();
|
||||||
|
@ -260,6 +286,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a hot spare bucket to the Lox db
|
||||||
pub fn add_spare_bucket(&self, bucket: [BridgeLine; 3]) {
|
pub fn add_spare_bucket(&self, bucket: [BridgeLine; 3]) {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
let mut db_obj = self.db.lock().unwrap();
|
let mut db_obj = self.db.lock().unwrap();
|
||||||
|
@ -289,14 +316,16 @@ impl LoxServerContext {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Uncomment when bridge blocking is finalized
|
pub fn mark_blocked(&self, bridgeline: BridgeLine) -> bool {
|
||||||
pub fn add_unreachable(&self, bridgeline: BridgeLine) -> bool {
|
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
let mut db_obj = self.db.lock().unwrap();
|
let mut db_obj = self.db.lock().unwrap();
|
||||||
ba_obj.bridge_unreachable(&bridgeline, &mut db_obj)
|
ba_obj.bridge_blocked(&bridgeline, &mut db_obj)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
// Find the bridgeline in the Lox bridge table that matches the fingerprint
|
||||||
|
// of the bridgeline passed by argument. Once found, replace it with the bridgeline
|
||||||
|
// passed by argument to ensure all fields besides the fingerprint are updated
|
||||||
|
// appropriately.
|
||||||
pub fn update_bridge(&self, bridgeline: BridgeLine) -> bool {
|
pub fn update_bridge(&self, bridgeline: BridgeLine) -> bool {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
ba_obj.bridge_update(&bridgeline)
|
ba_obj.bridge_update(&bridgeline)
|
||||||
|
@ -311,11 +340,13 @@ impl LoxServerContext {
|
||||||
println!("Today's date according to server: {}", ba_obj.today());
|
println!("Today's date according to server: {}", ba_obj.today());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypts the Lox bridge table, should be called after every sync
|
||||||
pub fn encrypt_table(&self) -> HashMap<u32, EncryptedBucket> {
|
pub fn encrypt_table(&self) -> HashMap<u32, EncryptedBucket> {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
ba_obj.enc_bridge_table().clone()
|
ba_obj.enc_bridge_table().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a vector of the Lox Authority's public keys
|
||||||
fn pubkeys(&self) -> Vec<IssuerPubKey> {
|
fn pubkeys(&self) -> Vec<IssuerPubKey> {
|
||||||
let ba_obj = self.ba.lock().unwrap();
|
let ba_obj = self.ba.lock().unwrap();
|
||||||
// vector of public keys (to serialize)
|
// vector of public keys (to serialize)
|
||||||
|
@ -332,6 +363,8 @@ impl LoxServerContext {
|
||||||
self.ba.lock().unwrap().today()
|
self.ba.lock().unwrap().today()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a Lox invitation if fewer than MAX_BRIDGES_PER_DAY have been
|
||||||
|
// requested on a given day
|
||||||
fn gen_invite(&self) -> Result<lox_utils::Invite, ExceededMaxBridgesError> {
|
fn gen_invite(&self) -> Result<lox_utils::Invite, ExceededMaxBridgesError> {
|
||||||
let mut obj = self.db.lock().unwrap();
|
let mut obj = self.db.lock().unwrap();
|
||||||
match obj.invite() {
|
match obj.invite() {
|
||||||
|
@ -345,11 +378,13 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid open_invite::Response if the open_invite::Request is valid
|
||||||
fn open_inv(&self, req: open_invite::Request) -> Result<open_invite::Response, ProofError> {
|
fn open_inv(&self, req: open_invite::Request) -> Result<open_invite::Response, ProofError> {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
ba_obj.handle_open_invite(req)
|
ba_obj.handle_open_invite(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid trust_promotion:: Response if the trust_promotion::Request is valid
|
||||||
fn trust_promo(
|
fn trust_promo(
|
||||||
&self,
|
&self,
|
||||||
req: trust_promotion::Request,
|
req: trust_promotion::Request,
|
||||||
|
@ -358,16 +393,19 @@ impl LoxServerContext {
|
||||||
ba_obj.handle_trust_promotion(req)
|
ba_obj.handle_trust_promotion(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid trust_migration::Response if the trust_migration::Request is valid
|
||||||
fn trust_migration(&self, req: migration::Request) -> Result<migration::Response, ProofError> {
|
fn trust_migration(&self, req: migration::Request) -> Result<migration::Response, ProofError> {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
ba_obj.handle_migration(req)
|
ba_obj.handle_migration(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid level_up:: Response if the level_up::Request is valid
|
||||||
fn level_up(&self, req: level_up::Request) -> Result<level_up::Response, ProofError> {
|
fn level_up(&self, req: level_up::Request) -> Result<level_up::Response, ProofError> {
|
||||||
let mut ba_obj = self.ba.lock().unwrap();
|
let mut ba_obj = self.ba.lock().unwrap();
|
||||||
ba_obj.handle_level_up(req)
|
ba_obj.handle_level_up(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid issue_invite::Response if the issue_invite::Request is valid
|
||||||
fn issue_invite(
|
fn issue_invite(
|
||||||
&self,
|
&self,
|
||||||
req: issue_invite::Request,
|
req: issue_invite::Request,
|
||||||
|
@ -376,6 +414,7 @@ impl LoxServerContext {
|
||||||
ba_obj.handle_issue_invite(req)
|
ba_obj.handle_issue_invite(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid redeem_invite::Response if the redeem_invite::Request is valid
|
||||||
fn redeem_invite(
|
fn redeem_invite(
|
||||||
&self,
|
&self,
|
||||||
req: redeem_invite::Request,
|
req: redeem_invite::Request,
|
||||||
|
@ -384,6 +423,7 @@ impl LoxServerContext {
|
||||||
ba_obj.handle_redeem_invite(req)
|
ba_obj.handle_redeem_invite(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid check_blockage::Response if the check_blockage::Request is valid
|
||||||
fn check_blockage(
|
fn check_blockage(
|
||||||
&self,
|
&self,
|
||||||
req: check_blockage::Request,
|
req: check_blockage::Request,
|
||||||
|
@ -392,6 +432,7 @@ impl LoxServerContext {
|
||||||
ba_obj.handle_check_blockage(req)
|
ba_obj.handle_check_blockage(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a valid blockage_migration::Response if the blockage_migration::Request is valid
|
||||||
fn blockage_migration(
|
fn blockage_migration(
|
||||||
&self,
|
&self,
|
||||||
req: blockage_migration::Request,
|
req: blockage_migration::Request,
|
||||||
|
@ -400,7 +441,7 @@ impl LoxServerContext {
|
||||||
ba_obj.handle_blockage_migration(req)
|
ba_obj.handle_blockage_migration(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and return an open invitation token
|
// Generate and return an open invitation token as an HTTP response
|
||||||
pub fn generate_invite(self) -> Response<Body> {
|
pub fn generate_invite(self) -> Response<Body> {
|
||||||
self.metrics.invites_requested.inc();
|
self.metrics.invites_requested.inc();
|
||||||
let invite = self.gen_invite();
|
let invite = self.gen_invite();
|
||||||
|
@ -419,7 +460,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the serialized encrypted bridge table
|
// Return the serialized encrypted bridge table as an HTTP response
|
||||||
pub fn send_reachability_cred(self) -> Response<Body> {
|
pub fn send_reachability_cred(self) -> Response<Body> {
|
||||||
let enc_table = self.encrypt_table();
|
let enc_table = self.encrypt_table();
|
||||||
let etable = lox_utils::EncBridgeTable { etable: enc_table };
|
let etable = lox_utils::EncBridgeTable { etable: enc_table };
|
||||||
|
@ -432,7 +473,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the serialized pubkeys for the Bridge Authority
|
// Return the serialized pubkeys for the Bridge Authority as an HTTP response
|
||||||
pub fn send_keys(self) -> Response<Body> {
|
pub fn send_keys(self) -> Response<Body> {
|
||||||
let pubkeys = self.pubkeys();
|
let pubkeys = self.pubkeys();
|
||||||
match serde_json::to_string(&pubkeys) {
|
match serde_json::to_string(&pubkeys) {
|
||||||
|
@ -455,6 +496,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the open invitation request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_open_cred(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_open_cred(self, request: Bytes) -> Response<Body> {
|
||||||
let req = match serde_json::from_slice(&request) {
|
let req = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -473,6 +515,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the trust promotion request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_trust_promo(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_trust_promo(self, request: Bytes) -> Response<Body> {
|
||||||
let req: trust_promotion::Request = match serde_json::from_slice(&request) {
|
let req: trust_promotion::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -491,6 +534,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the trust migration request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_trust_migration(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_trust_migration(self, request: Bytes) -> Response<Body> {
|
||||||
let req: migration::Request = match serde_json::from_slice(&request) {
|
let req: migration::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -509,6 +553,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the level up request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_level_up(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_level_up(self, request: Bytes) -> Response<Body> {
|
||||||
let req: level_up::Request = match serde_json::from_slice(&request) {
|
let req: level_up::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -527,6 +572,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the open invitation request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_issue_invite(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_issue_invite(self, request: Bytes) -> Response<Body> {
|
||||||
let req: issue_invite::Request = match serde_json::from_slice(&request) {
|
let req: issue_invite::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -545,6 +591,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the redeem invite request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_redeem_invite(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_redeem_invite(self, request: Bytes) -> Response<Body> {
|
||||||
let req: redeem_invite::Request = match serde_json::from_slice(&request) {
|
let req: redeem_invite::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -563,6 +610,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the check blockage request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_check_blockage(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_check_blockage(self, request: Bytes) -> Response<Body> {
|
||||||
let req: check_blockage::Request = match serde_json::from_slice(&request) {
|
let req: check_blockage::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -581,6 +629,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the blockage migration request and return the result as an HTTP response
|
||||||
pub fn verify_and_send_blockage_migration(self, request: Bytes) -> Response<Body> {
|
pub fn verify_and_send_blockage_migration(self, request: Bytes) -> Response<Body> {
|
||||||
let req: blockage_migration::Request = match serde_json::from_slice(&request) {
|
let req: blockage_migration::Request = match serde_json::from_slice(&request) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
|
@ -612,6 +661,7 @@ impl LoxServerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare HTTP Response for successful Server Request
|
||||||
fn prepare_header(response: String) -> Response<Body> {
|
fn prepare_header(response: String) -> Response<Body> {
|
||||||
let mut resp = Response::new(Body::from(response));
|
let mut resp = Response::new(Body::from(response));
|
||||||
resp.headers_mut()
|
resp.headers_mut()
|
||||||
|
@ -619,6 +669,7 @@ fn prepare_header(response: String) -> Response<Body> {
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare HTTP Response for errored Server Request
|
||||||
fn prepare_error_header(error: String) -> Response<Body> {
|
fn prepare_error_header(error: String) -> Response<Body> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(hyper::StatusCode::BAD_REQUEST)
|
.status(hyper::StatusCode::BAD_REQUEST)
|
||||||
|
|
|
@ -85,6 +85,10 @@ impl Default for DbConfig {
|
||||||
// Config information for how bridges should be allocated to buckets
|
// Config information for how bridges should be allocated to buckets
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct BridgeConfig {
|
pub struct BridgeConfig {
|
||||||
|
// A list of regions (as ISO 3166 country codes) that Lox will monitor resources for.
|
||||||
|
// Any region indicated here that is listed in the `blocked_in` field of a resource will be marked as
|
||||||
|
// blocked by Lox's bridge authority.
|
||||||
|
watched_blockages: Vec<String>,
|
||||||
// The percentage of buckets (made up of MAX_BRIDGES_PER_BUCKET bridges)
|
// The percentage of buckets (made up of MAX_BRIDGES_PER_BUCKET bridges)
|
||||||
// that should be allocated as spare buckets
|
// that should be allocated as spare buckets
|
||||||
// This will be calculated as the floor of buckets.len() * percent_spares / 100
|
// This will be calculated as the floor of buckets.len() * percent_spares / 100
|
||||||
|
@ -100,9 +104,9 @@ struct ResourceInfo {
|
||||||
}
|
}
|
||||||
// Populate Bridgedb from rdsys
|
// Populate Bridgedb from rdsys
|
||||||
|
|
||||||
// Rdsys sender creates a ResourceStream with the api_endpoint, resource token and type specified
|
// Rdsys sender creates a Resource request with the api_endpoint, resource token and type specified
|
||||||
// in the config.json file.
|
// in the config.json file.
|
||||||
async fn rdsys_stream(
|
async fn rdsys_request_creator(
|
||||||
rtype: ResourceInfo,
|
rtype: ResourceInfo,
|
||||||
tx: mpsc::Sender<ResourceState>,
|
tx: mpsc::Sender<ResourceState>,
|
||||||
mut kill: broadcast::Receiver<()>,
|
mut kill: broadcast::Receiver<()>,
|
||||||
|
@ -114,6 +118,8 @@ async fn rdsys_stream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Makes a request to rdsys for the full set of Resources assigned to lox every interval
|
||||||
|
// (defined in the function)
|
||||||
async fn rdsys_request(rtype: ResourceInfo, tx: mpsc::Sender<ResourceState>) {
|
async fn rdsys_request(rtype: ResourceInfo, tx: mpsc::Sender<ResourceState>) {
|
||||||
let mut interval = interval(Duration::from_secs(5));
|
let mut interval = interval(Duration::from_secs(5));
|
||||||
loop {
|
loop {
|
||||||
|
@ -130,6 +136,7 @@ async fn rdsys_request(rtype: ResourceInfo, tx: mpsc::Sender<ResourceState>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse bridges received from rdsys and sync with Lox context
|
||||||
async fn rdsys_bridge_parser(
|
async fn rdsys_bridge_parser(
|
||||||
rdsys_tx: mpsc::Sender<Command>,
|
rdsys_tx: mpsc::Sender<Command>,
|
||||||
rx: mpsc::Receiver<ResourceState>,
|
rx: mpsc::Receiver<ResourceState>,
|
||||||
|
@ -152,6 +159,7 @@ async fn parse_bridges(rdsys_tx: mpsc::Sender<Command>, mut rx: mpsc::Receiver<R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a prometheus metrics server
|
||||||
async fn start_metrics_collector(
|
async fn start_metrics_collector(
|
||||||
metrics_addr: SocketAddr,
|
metrics_addr: SocketAddr,
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
|
@ -203,7 +211,10 @@ async fn context_manager(
|
||||||
// bridgetable with all of the working bridges received from rdsys.
|
// bridgetable with all of the working bridges received from rdsys.
|
||||||
if context.bridgetable_is_empty() {
|
if context.bridgetable_is_empty() {
|
||||||
if let Some(working_resources) = resources.working {
|
if let Some(working_resources) = resources.working {
|
||||||
let bridgelines = parse_into_bridgelines(working_resources);
|
let (bridgelines, _) = parse_into_bridgelines(
|
||||||
|
bridge_config.watched_blockages.clone(),
|
||||||
|
working_resources,
|
||||||
|
);
|
||||||
context.metrics.new_bridges.inc_by(bridgelines.len() as u64);
|
context.metrics.new_bridges.inc_by(bridgelines.len() as u64);
|
||||||
let (buckets, leftovers) = parse_into_buckets(bridgelines);
|
let (buckets, leftovers) = parse_into_buckets(bridgelines);
|
||||||
for leftover in leftovers {
|
for leftover in leftovers {
|
||||||
|
@ -218,7 +229,8 @@ async fn context_manager(
|
||||||
// If bridges are labelled as blocked_in, we should also handle blocking behaviour.
|
// If bridges are labelled as blocked_in, we should also handle blocking behaviour.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
context.sync_with_bridgetable(resources);
|
context
|
||||||
|
.sync_with_bridgetable(bridge_config.watched_blockages.clone(), resources);
|
||||||
}
|
}
|
||||||
// Handle any bridges that are leftover in the bridge authority from the sync
|
// Handle any bridges that are leftover in the bridge authority from the sync
|
||||||
context.allocate_leftover_bridges();
|
context.allocate_leftover_bridges();
|
||||||
|
@ -314,7 +326,7 @@ async fn main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(32);
|
let (tx, rx) = mpsc::channel(32);
|
||||||
let rdsys_request_handler = spawn(async { rdsys_stream(config.rtype, tx, kill_stream).await });
|
let rdsys_request_handler = spawn(async { rdsys_request_creator(config.rtype, tx, kill_stream).await });
|
||||||
|
|
||||||
let rdsys_resource_receiver =
|
let rdsys_resource_receiver =
|
||||||
spawn(async { rdsys_bridge_parser(rdsys_tx, rx, kill_parser).await });
|
spawn(async { rdsys_bridge_parser(rdsys_tx, rx, kill_parser).await });
|
||||||
|
|
|
@ -161,7 +161,7 @@ pub async fn start_metrics_server(metrics_addr: SocketAddr, registry: Registry)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function returns a HTTP handler (i.e. another function)
|
/// This function returns an HTTP handler (i.e. another function)
|
||||||
pub fn make_handler(
|
pub fn make_handler(
|
||||||
registry: Arc<Registry>,
|
registry: Arc<Registry>,
|
||||||
) -> impl Fn(Request<Body>) -> Pin<Box<dyn Future<Output = io::Result<Response<Body>>> + Send>> {
|
) -> impl Fn(Request<Body>) -> Pin<Box<dyn Future<Output = io::Result<Response<Body>>> + Send>> {
|
||||||
|
|
|
@ -259,8 +259,8 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(bucket.1.is_some());
|
assert!(bucket.1.is_some());
|
||||||
// Block two of our bridges
|
// Block two of our bridges
|
||||||
lox_auth.bridge_unreachable(&bucket.0[0], &mut bdb);
|
lox_auth.bridge_blocked(&bucket.0[0], &mut bdb);
|
||||||
lox_auth.bridge_unreachable(&bucket.0[2], &mut bdb);
|
lox_auth.bridge_blocked(&bucket.0[2], &mut bdb);
|
||||||
|
|
||||||
(cred, id, key)
|
(cred, id, key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use lox_library::bridge_table::{BridgeLine, BRIDGE_BYTES, MAX_BRIDGES_PER_BUCKET};
|
use lox_library::bridge_table::{BridgeLine, BRIDGE_BYTES, MAX_BRIDGES_PER_BUCKET};
|
||||||
use rdsys_backend::proto::Resource;
|
use rdsys_backend::proto::Resource;
|
||||||
|
|
||||||
pub const ACCEPTED_HOURS_OF_FAILURE: i64 = 3;
|
pub const ACCEPTED_HOURS_OF_FAILURE: i64 = 3;
|
||||||
|
|
||||||
// Parse each resource from rdsys into a Bridgeline as expected by the Lox Bridgetable
|
// Parse each resource from rdsys into a Bridgeline as expected by the Lox Bridgetable and return
|
||||||
pub fn parse_into_bridgelines(resources: Vec<Resource>) -> Vec<BridgeLine> {
|
// Bridgelines as two vectors, those that are marked as blocked in a specified region (indicated in the config file)
|
||||||
|
// and those that are not blocked.
|
||||||
|
pub fn parse_into_bridgelines(
|
||||||
|
watched_blockages: Vec<String>,
|
||||||
|
resources: Vec<Resource>,
|
||||||
|
) -> (Vec<BridgeLine>, Vec<BridgeLine>) {
|
||||||
let mut bridgelines: Vec<BridgeLine> = Vec::new();
|
let mut bridgelines: Vec<BridgeLine> = Vec::new();
|
||||||
|
let mut blocked_bridgelines: Vec<BridgeLine> = Vec::new();
|
||||||
for resource in resources {
|
for resource in resources {
|
||||||
let mut ip_bytes: [u8; 16] = [0; 16];
|
let mut ip_bytes: [u8; 16] = [0; 16];
|
||||||
ip_bytes[..resource.address.len()].copy_from_slice(resource.address.as_bytes());
|
ip_bytes[..resource.address.len()].copy_from_slice(resource.address.as_bytes());
|
||||||
|
@ -14,27 +22,38 @@ pub fn parse_into_bridgelines(resources: Vec<Resource>) -> Vec<BridgeLine> {
|
||||||
.get_uid()
|
.get_uid()
|
||||||
.expect("Unable to get Fingerprint UID of resource");
|
.expect("Unable to get Fingerprint UID of resource");
|
||||||
let infostr: String = format!(
|
let infostr: String = format!(
|
||||||
"type={} blocked_in={:?} protocol={} fingerprint={:?} or_addresses={:?} distribution={} flags={:?} params={:?}",
|
"type={} fingerprint={:?} params={:?}",
|
||||||
resource.r#type,
|
resource.r#type, resource.fingerprint, resource.params,
|
||||||
resource.blocked_in,
|
);
|
||||||
resource.protocol,
|
|
||||||
resource.fingerprint,
|
|
||||||
resource.or_addresses,
|
|
||||||
resource.distribution,
|
|
||||||
resource.flags,
|
|
||||||
resource.params,
|
|
||||||
);
|
|
||||||
let mut info_bytes: [u8; BRIDGE_BYTES - 26] = [0; BRIDGE_BYTES - 26];
|
let mut info_bytes: [u8; BRIDGE_BYTES - 26] = [0; BRIDGE_BYTES - 26];
|
||||||
|
|
||||||
info_bytes[..infostr.len()].copy_from_slice(infostr.as_bytes());
|
info_bytes[..infostr.len()].copy_from_slice(infostr.as_bytes());
|
||||||
|
let mut blocked = false;
|
||||||
|
for watched_blockage in watched_blockages.clone() {
|
||||||
|
if let Some(blockage) = resource.blocked_in.get(&watched_blockage) {
|
||||||
|
if *blockage {
|
||||||
|
blocked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if blocked {
|
||||||
|
blocked_bridgelines.push(BridgeLine {
|
||||||
|
addr: ip_bytes,
|
||||||
|
port: resource.port,
|
||||||
|
uid_fingerprint: resource_uid,
|
||||||
|
info: info_bytes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
bridgelines.push(BridgeLine {
|
bridgelines.push(BridgeLine {
|
||||||
addr: ip_bytes,
|
addr: ip_bytes,
|
||||||
port: resource.port,
|
port: resource.port,
|
||||||
uid_fingerprint: resource_uid,
|
uid_fingerprint: resource_uid,
|
||||||
info: info_bytes,
|
info: info_bytes,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
bridgelines
|
}
|
||||||
|
(bridgelines, blocked_bridgelines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate each Bridgeline into a bucket that will later be allocated into spare buckets or open invitation buckets
|
// Allocate each Bridgeline into a bucket that will later be allocated into spare buckets or open invitation buckets
|
||||||
|
@ -73,12 +92,16 @@ pub fn parse_into_buckets(
|
||||||
(buckets, leftovers)
|
(buckets, leftovers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort Resources into those that are functional and those that are failing based on the last time
|
// Sort Resources into those that are functional, those that are failing based on the last time
|
||||||
// they were passing tests. Before passing them back to the calling function, they are parsed into
|
// they were passing tests, and those that are blocked in the region(s) specified in the config file.
|
||||||
// BridgeLines
|
// Before passing them back to the calling function, they are parsed into BridgeLines
|
||||||
pub fn sort_for_parsing(resources: Vec<Resource>) -> (Vec<BridgeLine>, Vec<BridgeLine>) {
|
pub fn sort_for_parsing(
|
||||||
|
watched_blockages: Vec<String>,
|
||||||
|
resources: Vec<Resource>,
|
||||||
|
) -> (Vec<BridgeLine>, Vec<BridgeLine>, Vec<BridgeLine>) {
|
||||||
let mut grace_period: Vec<Resource> = Vec::new();
|
let mut grace_period: Vec<Resource> = Vec::new();
|
||||||
let mut failing: Vec<Resource> = Vec::new();
|
let mut failing: Vec<Resource> = Vec::new();
|
||||||
|
let mut blocked: Vec<BridgeLine> = Vec::new();
|
||||||
for resource in resources {
|
for resource in resources {
|
||||||
// TODO: Maybe filter for untested resources first if last_passed alone would skew
|
// TODO: Maybe filter for untested resources first if last_passed alone would skew
|
||||||
// the filter in an unintended direction
|
// the filter in an unintended direction
|
||||||
|
@ -90,10 +113,14 @@ pub fn sort_for_parsing(resources: Vec<Resource>) -> (Vec<BridgeLine>, Vec<Bridg
|
||||||
failing.push(resource);
|
failing.push(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let grace_period_bridgelines = parse_into_bridgelines(grace_period);
|
let (grace_period_bridgelines, mut grace_period_blocked) =
|
||||||
let failing_bridgelines = parse_into_bridgelines(failing);
|
parse_into_bridgelines(watched_blockages.clone(), grace_period);
|
||||||
|
let (failing_bridgelines, mut failing_blocked) =
|
||||||
|
parse_into_bridgelines(watched_blockages, failing);
|
||||||
|
blocked.append(&mut grace_period_blocked);
|
||||||
|
blocked.append(&mut failing_blocked);
|
||||||
|
|
||||||
(grace_period_bridgelines, failing_bridgelines)
|
(grace_period_bridgelines, failing_bridgelines, blocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -107,6 +134,7 @@ mod tests {
|
||||||
|
|
||||||
pub fn make_resource(
|
pub fn make_resource(
|
||||||
rtype: String,
|
rtype: String,
|
||||||
|
blocked_in: HashMap<String, bool>,
|
||||||
address: String,
|
address: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
|
@ -122,7 +150,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
Resource {
|
Resource {
|
||||||
r#type: String::from(rtype),
|
r#type: String::from(rtype),
|
||||||
blocked_in: HashMap::new(),
|
blocked_in: blocked_in,
|
||||||
test_result: TestResults {
|
test_result: TestResults {
|
||||||
last_passed: Utc::now() - Duration::hours(last_passed),
|
last_passed: Utc::now() - Duration::hours(last_passed),
|
||||||
},
|
},
|
||||||
|
@ -141,6 +169,13 @@ mod tests {
|
||||||
fn test_sort_for_parsing() {
|
fn test_sort_for_parsing() {
|
||||||
let resource_one = make_resource(
|
let resource_one = make_resource(
|
||||||
"scramblesuit".to_owned(),
|
"scramblesuit".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("AS".to_owned(), false),
|
||||||
|
("IR".to_owned(), false),
|
||||||
|
("PS".to_owned(), false),
|
||||||
|
("CN".to_owned(), false),
|
||||||
|
("RU".to_owned(), false),
|
||||||
|
]),
|
||||||
"123.456.789.100".to_owned(),
|
"123.456.789.100".to_owned(),
|
||||||
3002,
|
3002,
|
||||||
"BE84A97D02130470A1C77839954392BA979F7EE1".to_owned(),
|
"BE84A97D02130470A1C77839954392BA979F7EE1".to_owned(),
|
||||||
|
@ -148,6 +183,13 @@ mod tests {
|
||||||
);
|
);
|
||||||
let resource_two = make_resource(
|
let resource_two = make_resource(
|
||||||
"https".to_owned(),
|
"https".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("AI".to_owned(), false),
|
||||||
|
("AG".to_owned(), false),
|
||||||
|
("BD".to_owned(), false),
|
||||||
|
("BB".to_owned(), false),
|
||||||
|
("RU".to_owned(), false),
|
||||||
|
]),
|
||||||
"123.222.333.444".to_owned(),
|
"123.222.333.444".to_owned(),
|
||||||
6002,
|
6002,
|
||||||
"C56B9EF202130470A1C77839954392BA979F7FF9".to_owned(),
|
"C56B9EF202130470A1C77839954392BA979F7FF9".to_owned(),
|
||||||
|
@ -155,13 +197,27 @@ mod tests {
|
||||||
);
|
);
|
||||||
let resource_three = make_resource(
|
let resource_three = make_resource(
|
||||||
"scramblesuit".to_owned(),
|
"scramblesuit".to_owned(),
|
||||||
"444.888.222.100".to_owned(),
|
HashMap::from([
|
||||||
|
("SZ".to_owned(), true),
|
||||||
|
("DO".to_owned(), false),
|
||||||
|
("GN".to_owned(), false),
|
||||||
|
("KR".to_owned(), false),
|
||||||
|
("RU".to_owned(), false),
|
||||||
|
]),
|
||||||
|
"443.288.222.100".to_owned(),
|
||||||
3042,
|
3042,
|
||||||
"1A4C8BD902130470A1C77839954392BA979F7B46".to_owned(),
|
"5E3A8BD902130470A1C77839954392BA979F7B46".to_owned(),
|
||||||
4,
|
4,
|
||||||
);
|
);
|
||||||
let resource_four = make_resource(
|
let resource_four = make_resource(
|
||||||
"https".to_owned(),
|
"https".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("SH".to_owned(), true),
|
||||||
|
("ZA".to_owned(), true),
|
||||||
|
("UM".to_owned(), true),
|
||||||
|
("ZW".to_owned(), true),
|
||||||
|
("SK".to_owned(), true),
|
||||||
|
]),
|
||||||
"555.444.212.100".to_owned(),
|
"555.444.212.100".to_owned(),
|
||||||
8022,
|
8022,
|
||||||
"FF024DC302130470A1C77839954392BA979F7AE2".to_owned(),
|
"FF024DC302130470A1C77839954392BA979F7AE2".to_owned(),
|
||||||
|
@ -169,22 +225,63 @@ mod tests {
|
||||||
);
|
);
|
||||||
let resource_five = make_resource(
|
let resource_five = make_resource(
|
||||||
"https".to_owned(),
|
"https".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("CA".to_owned(), false),
|
||||||
|
("UK".to_owned(), true),
|
||||||
|
("SR".to_owned(), false),
|
||||||
|
("RW".to_owned(), true),
|
||||||
|
("RU".to_owned(), false),
|
||||||
|
]),
|
||||||
"234.111.212.100".to_owned(),
|
"234.111.212.100".to_owned(),
|
||||||
10432,
|
10432,
|
||||||
"7B4DE14CB2130470A1C77839954392BA979F7AE2".to_owned(),
|
"7B4DE14CB2130470A1C77839954392BA979F7AE2".to_owned(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
let resource_six = make_resource(
|
||||||
|
"https".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("CA".to_owned(), false),
|
||||||
|
("UK".to_owned(), false),
|
||||||
|
("SR".to_owned(), false),
|
||||||
|
("RW".to_owned(), false),
|
||||||
|
("RU".to_owned(), true),
|
||||||
|
]),
|
||||||
|
"434.777.212.100".to_owned(),
|
||||||
|
10112,
|
||||||
|
"7B4DE04A22130470A1C77839954392BA979F7AE2".to_owned(),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let resource_seven = make_resource(
|
||||||
|
"https".to_owned(),
|
||||||
|
HashMap::from([
|
||||||
|
("CA".to_owned(), true),
|
||||||
|
("UK".to_owned(), false),
|
||||||
|
("SR".to_owned(), false),
|
||||||
|
("RW".to_owned(), false),
|
||||||
|
("RU".to_owned(), true),
|
||||||
|
]),
|
||||||
|
"434.777.212.211".to_owned(),
|
||||||
|
8112,
|
||||||
|
"01E6FA4A22130470A1C77839954392BA979F7AE2".to_owned(),
|
||||||
|
5,
|
||||||
|
);
|
||||||
let mut test_vec: Vec<Resource> = Vec::new();
|
let mut test_vec: Vec<Resource> = Vec::new();
|
||||||
test_vec.push(resource_one);
|
test_vec.push(resource_one);
|
||||||
test_vec.push(resource_two);
|
test_vec.push(resource_two);
|
||||||
test_vec.push(resource_three);
|
test_vec.push(resource_three);
|
||||||
test_vec.push(resource_four);
|
test_vec.push(resource_four);
|
||||||
test_vec.push(resource_five);
|
test_vec.push(resource_five);
|
||||||
let (functional, failing) = sort_for_parsing(test_vec);
|
test_vec.push(resource_six);
|
||||||
|
test_vec.push(resource_seven);
|
||||||
|
println!("How many in test? {:?}", test_vec.len());
|
||||||
|
let mut watched_blockages: Vec<String> = Vec::new();
|
||||||
|
watched_blockages.push("RU".to_string());
|
||||||
|
let (functional, failing, blocked) = sort_for_parsing(watched_blockages, test_vec);
|
||||||
assert!(
|
assert!(
|
||||||
functional.len() == 2,
|
functional.len() == 2,
|
||||||
"There should be 2 functional bridges"
|
"There should be 2 functional bridges"
|
||||||
);
|
);
|
||||||
assert!(failing.len() == 3, "There should be 3 failing bridges");
|
assert!(failing.len() == 3, "There should be 3 failing bridges");
|
||||||
|
assert!(blocked.len() == 2, "There should be 2 blocked bridges");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lox-library"
|
name = "lox-library"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Ian Goldberg <iang@uwaterloo.ca>"]
|
authors = ["Ian Goldberg <iang@uwaterloo.ca>, Lindsey Tulloch <onyinyang@torporject.org"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
rust-version = "1.65.0"
|
rust-version = "1.65.0"
|
||||||
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox-project/~/wikis/home"
|
license = "MIT"
|
||||||
|
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
||||||
description = "Main Lox library with protocols and functions that that make up Lox"
|
description = "Main Lox library with protocols and functions that that make up Lox"
|
||||||
keywords = ["tor", "lox", "bridges"]
|
keywords = ["tor", "lox", "bridge-distribution","censorship-resistance"]
|
||||||
|
categories = ["cryptography"]
|
||||||
|
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/lox-library"
|
||||||
|
readme = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/blob/main/crates/lox-library/README.md"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
|
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
|
||||||
|
@ -14,7 +19,7 @@ ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
rand = { version = "0.8", features = ["std_rng"]}
|
rand = { version = "0.8", features = ["std_rng"]}
|
||||||
serde = "1.0.190"
|
serde = "1.0.192"
|
||||||
serde_with = {version = "3.4.0", features = ["json"]}
|
serde_with = {version = "3.4.0", features = ["json"]}
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
statistical = "1.0.0"
|
statistical = "1.0.0"
|
||||||
|
|
|
@ -1,8 +1,40 @@
|
||||||
# Lox
|
# Lox
|
||||||
|
|
||||||
Lox is a reputation-based bridge distribution system that provides privacy protection to users and their social graph and is open to all users.
|
Lox is a reputation-based bridge distribution system that provides privacy protection to users and their social graph and is open to all users.
|
||||||
Lox is written in rust and requires `cargo` to test. [Install Rust](https://www.rust-lang.org/tools/install). We used Rust version 1.56.0.
|
|
||||||
|
|
||||||
Note that this implementation is coded such that the reachability certificate expires at 00:00 UTC. In reality, if the bucket is still reachable, a user could simply request a new reachability token if their request fails for this reason (a new certificate should be available prior to the outdated certificate expiring).
|
The protocols in the Lox-library are consistent with the Lox system described
|
||||||
|
in [Tulloch and Goldberg](https://petsymposium.org/popets/2023/popets-2023-0029.php) (and in greater detail [here](https://uwspace.uwaterloo.ca/handle/10012/18333)). However, this implementation may diverge from the theory over time as the system is deployed and its limitations are better illuminated. The [original version of this library](https://git-crysp.uwaterloo.ca/iang/lox) will remain a more precise implementation of the theory.
|
||||||
|
|
||||||
|
Lox is written in rust and requires `cargo` to test. [Install Rust](https://www.rust-lang.org/tools/install). We used Rust version 1.65.0.
|
||||||
|
|
||||||
|
## Notable Changes from the original repository
|
||||||
|
|
||||||
|
Some changes have been made to integrate the existing Lox protocols with Tor's
|
||||||
|
bridge distributor [rdsys](https://gitlab.torproject.org/tpo/anti-censorship/rdsys),
|
||||||
|
but so far, these have not affected the Lox protocols themselves.
|
||||||
|
|
||||||
|
These changes are necessary to keep the consistentcy of bridges in buckets that Lox requires while working with the reality of how rdsys/Tor currently receives and distributes information about bridges. The changes to Lox are:
|
||||||
|
1. Add a `uid_fingerprint` field to the BridgeLine which helps with bridge lookup and
|
||||||
|
corresponds (roughly) to the unique fingerprint rdsys gives to each bridge
|
||||||
|
(made up of a hash of the IP and pluggable transport type)
|
||||||
|
2. Allow for the details of a bridge to be updated. This has been added to
|
||||||
|
[`crates/lox-library/src/lib.rs`](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-library/src/lib.rs) and accounts for the fact that some details
|
||||||
|
of an existing bridge (i.e., that has a matching fingerprint) may be updated
|
||||||
|
from time to time.
|
||||||
|
3. Allow for a bridge to be replaced without penalty. This has also been added to
|
||||||
|
[`crates/lox-library/src/lib.rs`](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-library/src/lib.rs)
|
||||||
|
and accounts for the fact that Tor currently does not have a robust way of
|
||||||
|
[knowing that a bridge is blocked](https://gitlab.torproject.org/tpo/anti-censorship/censorship-analysis/-/issues/40035), but does have some tests (namely,
|
||||||
|
[bridgestrap](https://gitlab.torproject.org/tpo/anti-censorship/bridgestrap) and [onbasca](https://gitlab.torproject.org/tpo/network-health/onbasca)) that help to determine if a
|
||||||
|
bridge should not be distributed. Since we do not know if the results of
|
||||||
|
these tests indicate a blocking event, we are allowing for bridges that
|
||||||
|
rdsys marks as unsuitable for distribution to be updated without penalty in the Lox library.
|
||||||
|
4. The vectors within `bridge_table.rs` have been refactored into HashMaps that use a unique `u32` for lookup. This has led to a
|
||||||
|
number of changes around how bridges are inserted/removed from the bridge table but does not impact the overall functionality of the Lox system.
|
||||||
|
5. The `DupFilter` has been changed from a `HashMap` to a `HashSet`, primarily because this is easier to Serialize/Deserialize when storing the state of the Lox system to recover from failure or to be able to roll back to a previous state.
|
||||||
|
6. The [`dalek-cryptography`](https://dalek.rs/) libraries have been updated to their most recent versions and the `zkp` library has been forked (until/unless this is fixed in one of the existing upstream repos) to fix a bug that appears when a public attribute is set to 0 (previously impacting only the blockage migration protocol when a user's invitations are set to 0 after migrating). The fork of `zkp` also includes similar updates to `dalek-cryptography` dependencies and some others such as `rand`.
|
||||||
|
7. Many tests that were used to create the Lox paper/thesis and measure the performance of the system were removed from this repository as they are unnecessary in a deployment scenario. They are still available in the [original repository](https://git-crysp.uwaterloo.ca/iang/lox).
|
||||||
|
|
||||||
|
### Other important Notes
|
||||||
|
|
||||||
|
As with the original implementation, this implementation is coded such that the reachability certificate expires at 00:00 UTC. Therefore, if an unlucky user requests a reachability certificate just before the 00:00 UTC and tries to use it just after, the request will fail. If the bucket is still reachable, a user can simply request a new reachability token if their request fails for this reason (a new certificate should be available prior to the outdated certificate expiring).
|
||||||
|
|
|
@ -25,7 +25,7 @@ use std::convert::{TryFrom, TryInto};
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
/// Each bridge information line is serialized into this many bytes
|
/// Each bridge information line is serialized into this many bytes
|
||||||
pub const BRIDGE_BYTES: usize = 300;
|
pub const BRIDGE_BYTES: usize = 200;
|
||||||
|
|
||||||
/// The max number of bridges per bucket
|
/// The max number of bridges per bucket
|
||||||
pub const MAX_BRIDGES_PER_BUCKET: usize = 3;
|
pub const MAX_BRIDGES_PER_BUCKET: usize = 3;
|
||||||
|
@ -246,35 +246,40 @@ struct K {
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct BridgeTable {
|
pub struct BridgeTable {
|
||||||
// All structures in the bridgetable are indexed by counter
|
/// All structures in the bridgetable are indexed by counter
|
||||||
pub counter: u32,
|
pub counter: u32,
|
||||||
|
/// The keys of all buckets, indexed by counter, that are still part of the bridge table.
|
||||||
pub keys: HashMap<u32, [u8; 16]>,
|
pub keys: HashMap<u32, [u8; 16]>,
|
||||||
|
/// All buckets, indexed by counter corresponding to the key above, that are
|
||||||
|
/// part of the bridge table.
|
||||||
pub buckets: HashMap<u32, [BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
|
pub buckets: HashMap<u32, [BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
|
||||||
pub encbuckets: HashMap<u32, EncryptedBucket>,
|
pub encbuckets: HashMap<u32, EncryptedBucket>,
|
||||||
/// Individual bridges that are reachable
|
/// Individual bridges that are reachable.
|
||||||
#[serde_as(as = "HashMap<serde_with::json::JsonString, _>")]
|
#[serde_as(as = "HashMap<serde_with::json::JsonString, _>")]
|
||||||
pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
|
pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
|
||||||
/// bucket ids of "hot spare" buckets. These buckets are not handed
|
/// Bucket ids of "hot spare" buckets. These buckets are not handed
|
||||||
/// to users, nor do they have any Migration credentials pointing to
|
/// to users, nor do they have any Migration credentials pointing to
|
||||||
/// them. When a new Migration credential is needed, a bucket is
|
/// them. When a new Migration credential is needed, a bucket is
|
||||||
/// removed from this set and used for that purpose.
|
/// removed from this set and used for that purpose.
|
||||||
pub spares: HashSet<u32>,
|
pub spares: HashSet<u32>,
|
||||||
/// In some instances a single bridge may need to be added to a bucket
|
/// In some instances a single bridge may need to be added to a bucket as a replacement
|
||||||
/// In that case, a spare bucket will be removed from the set of spare bridges. One
|
/// or otherwise. In that case, a spare bucket will be removed from the set of spares, one
|
||||||
|
/// bridge will be used as the replacement and the left over bridges will be appended to
|
||||||
|
/// unallocated_bridges.
|
||||||
pub unallocated_bridges: Vec<BridgeLine>,
|
pub unallocated_bridges: Vec<BridgeLine>,
|
||||||
// To prevent issues with a counter for the hashmap keys, we keep a list of keys that
|
// To prevent issues with the counter for the hashmap keys, keep a list of keys that
|
||||||
// no longer match any buckets that can be used before increasing the counter
|
// no longer match any buckets that can be used before increasing the counter.
|
||||||
pub recycleable_keys: Vec<u32>,
|
pub recycleable_keys: Vec<u32>,
|
||||||
// We maintain a list of keys that have been blocked (bucket_id: u32), as well as the
|
// A list of keys that have been blocked (bucket_id: u32), as well as the
|
||||||
// time (julian_date: u32) of their blocking so that they can be repurposed with new
|
// time (julian_date: u32) of their blocking so that they can be repurposed with new
|
||||||
// buckets after the EXPIRY_DATE
|
// buckets after the EXPIRY_DATE.
|
||||||
pub blocked_keys: Vec<(u32, u32)>,
|
pub blocked_keys: Vec<(u32, u32)>,
|
||||||
// Similarly, we maintain a list of open entry buckets (bucket_id: u32) and the time they were
|
// Similarly, a list of open entry buckets (bucket_id: u32) and the time they were
|
||||||
// created (julian_date: u32) so they will be listed as expired after the EXPIRY_DATE
|
// created (julian_date: u32) so they will be listed as expired after the EXPIRY_DATE.
|
||||||
// TODO: add open entry buckets to the open_inv_keys only once they have been distributed
|
// TODO: add open entry buckets to the open_inv_keys only once they have been distributed
|
||||||
pub open_inv_keys: Vec<(u32, u32)>,
|
pub open_inv_keys: Vec<(u32, u32)>,
|
||||||
/// The date the buckets were last encrypted to make the encbucket.
|
/// The date the buckets were last encrypted to make the encbucket.
|
||||||
/// The encbucket must be rebuilt each day so that the Bucket
|
/// The encbucket must be rebuilt at least each day so that the Bucket
|
||||||
/// Reachability credentials in the buckets can be refreshed.
|
/// Reachability credentials in the buckets can be refreshed.
|
||||||
pub date_last_enc: u32,
|
pub date_last_enc: u32,
|
||||||
}
|
}
|
||||||
|
@ -288,7 +293,7 @@ impl BridgeTable {
|
||||||
self.buckets.len()
|
self.buckets.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a new bucket to the bridge table, returning its index
|
/// Insert a new bucket into the bridge table, returning its index
|
||||||
pub fn new_bucket(&mut self, index: u32, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
|
pub fn new_bucket(&mut self, index: u32, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
|
||||||
// Pick a random key to encrypt this bucket
|
// Pick a random key to encrypt this bucket
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
In each case, (P,Q) forms the MAC on the credential. This MAC is
|
In each case, (P,Q) forms the MAC on the credential. This MAC is
|
||||||
verifiable only by the issuing party, or if the issuing party issues a
|
verifiable only by the issuing party, or if the issuing party issues a
|
||||||
zero-knowledge proof of its correctness (as it does at issuing time). */
|
zero-knowledge proof of its correctness (as it does at issuing time).*/
|
||||||
|
|
||||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||||
use curve25519_dalek::scalar::Scalar;
|
use curve25519_dalek::scalar::Scalar;
|
||||||
|
|
|
@ -62,10 +62,12 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPIRY_DATE is set to EXPIRY_DATE days for open-entry and blocked buckets in order to match
|
// EXPIRY_DATE is set to EXPIRY_DATE days for open-entry and blocked buckets in order to match
|
||||||
// the expiry date for Lox credentials. This particular value (EXPIRY_DATE) is chosen because
|
// the expiry date for Lox credentials.This particular value (EXPIRY_DATE) is chosen because
|
||||||
// values that are 2^k − 1 make range proofs more efficient, but this can be changed to any value
|
// values that are 2^k − 1 make range proofs more efficient, but this can be changed to any value
|
||||||
pub const EXPIRY_DATE: u32 = 511;
|
pub const EXPIRY_DATE: u32 = 511;
|
||||||
|
|
||||||
|
/// ReplaceSuccess sends a signal to the lox-distributor to inform
|
||||||
|
/// whether or not a bridge was successfully replaced
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum ReplaceSuccess {
|
pub enum ReplaceSuccess {
|
||||||
NotFound = 0,
|
NotFound = 0,
|
||||||
|
@ -73,18 +75,23 @@ pub enum ReplaceSuccess {
|
||||||
Replaced = 2,
|
Replaced = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This error is thrown if the number of buckets/keys in the bridge table
|
||||||
|
/// exceeds u32 MAX.It is unlikely this error will ever occur.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum NoAvailableIDError {
|
pub enum NoAvailableIDError {
|
||||||
#[error("Find key exhausted with no available index found!")]
|
#[error("Find key exhausted with no available index found!")]
|
||||||
ExhaustedIndexer,
|
ExhaustedIndexer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This error is thrown after the MAX_DAILY_BRIDGES threshold for bridges
|
||||||
|
/// distributed in a day has been reached
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExceededMaxBridgesError {
|
pub enum ExceededMaxBridgesError {
|
||||||
#[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
|
#[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
|
||||||
ExceededMaxBridges,
|
ExceededMaxBridges,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private Key of the Issuer
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct IssuerPrivKey {
|
pub struct IssuerPrivKey {
|
||||||
x0tilde: Scalar,
|
x0tilde: Scalar,
|
||||||
|
@ -106,11 +113,13 @@ impl IssuerPrivKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct IssuerPubKey {
|
pub struct IssuerPubKey {
|
||||||
X: Vec<RistrettoPoint>,
|
X: Vec<RistrettoPoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public Key of the Issuer
|
||||||
impl IssuerPubKey {
|
impl IssuerPubKey {
|
||||||
/// Create an IssuerPubKey from the corresponding IssuerPrivKey
|
/// Create an IssuerPubKey from the corresponding IssuerPrivKey
|
||||||
pub fn new(privkey: &IssuerPrivKey) -> IssuerPubKey {
|
pub fn new(privkey: &IssuerPrivKey) -> IssuerPubKey {
|
||||||
|
@ -130,9 +139,9 @@ impl IssuerPubKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of times a given invitation is ditributed
|
/// Number of times a given invitation is ditributed
|
||||||
pub const OPENINV_K: u32 = 10;
|
pub const OPENINV_K: u32 = 10;
|
||||||
// TODO: Decide on maximum daily number of invitations to be distributed
|
/// TODO: Decide on maximum daily number of invitations to be distributed
|
||||||
pub const MAX_DAILY_BRIDGES: u32 = 100;
|
pub const MAX_DAILY_BRIDGES: u32 = 100;
|
||||||
/// The BridgeDb. This will typically be a singleton object. The
|
/// The BridgeDb. This will typically be a singleton object. The
|
||||||
/// BridgeDb's role is simply to issue signed "open invitations" to
|
/// BridgeDb's role is simply to issue signed "open invitations" to
|
||||||
|
@ -188,6 +197,8 @@ impl BridgeDb {
|
||||||
self.openinv_buckets.remove(bucket);
|
self.openinv_buckets.remove(bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove open invitation and/or otherwise distributed buckets that have
|
||||||
|
/// become blocked or are expired to free up the index for a new bucket
|
||||||
pub fn remove_blocked_or_expired_buckets(&mut self, bucket: &u32) {
|
pub fn remove_blocked_or_expired_buckets(&mut self, bucket: &u32) {
|
||||||
if self.openinv_buckets.contains(bucket) {
|
if self.openinv_buckets.contains(bucket) {
|
||||||
println!("Removing a bucket that has not been distributed yet!");
|
println!("Removing a bucket that has not been distributed yet!");
|
||||||
|
@ -197,6 +208,7 @@ impl BridgeDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark a bucket as distributed
|
||||||
pub fn mark_distributed(&mut self, bucket: u32) {
|
pub fn mark_distributed(&mut self, bucket: u32) {
|
||||||
self.distributed_buckets.push(bucket);
|
self.distributed_buckets.push(bucket);
|
||||||
}
|
}
|
||||||
|
@ -406,6 +418,9 @@ impl BridgeAuth {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When syncing the Lox bridge table with rdsys, this function returns any bridges
|
||||||
|
/// that are found in the Lox bridge table that are not found in the Vector
|
||||||
|
/// of bridges received from rdsys through the Lox distributor.
|
||||||
pub fn find_and_remove_unaccounted_for_bridges(
|
pub fn find_and_remove_unaccounted_for_bridges(
|
||||||
&mut self,
|
&mut self,
|
||||||
accounted_for_bridges: Vec<u64>,
|
accounted_for_bridges: Vec<u64>,
|
||||||
|
@ -419,6 +434,7 @@ impl BridgeAuth {
|
||||||
unaccounted_for
|
unaccounted_for
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocate single left over bridges to an open invitation bucket
|
||||||
pub fn allocate_bridges(
|
pub fn allocate_bridges(
|
||||||
&mut self,
|
&mut self,
|
||||||
distributor_bridges: &mut Vec<BridgeLine>,
|
distributor_bridges: &mut Vec<BridgeLine>,
|
||||||
|
@ -447,12 +463,10 @@ impl BridgeAuth {
|
||||||
// Update the details of a bridge in the bridge table. This assumes that the IP and Port
|
// Update the details of a bridge in the bridge table. This assumes that the IP and Port
|
||||||
// of a given bridge remains the same and thus can be updated.
|
// of a given bridge remains the same and thus can be updated.
|
||||||
// First we must retrieve the list of reachable bridges, then we must search for any matching our partial key
|
// First we must retrieve the list of reachable bridges, then we must search for any matching our partial key
|
||||||
// which will include the IP and Port. Then we can replace the original bridge with the updated bridge
|
// which will include the IP and Port. Finally we can replace the original bridge with the updated bridge.
|
||||||
// Returns true if the bridge has successfully updated
|
// Returns true if the bridge has successfully updated
|
||||||
pub fn bridge_update(&mut self, bridge: &BridgeLine) -> bool {
|
pub fn bridge_update(&mut self, bridge: &BridgeLine) -> bool {
|
||||||
let mut res: bool = false; //default False to assume that update failed
|
let mut res: bool = false; //default False to assume that update failed
|
||||||
//Needs to be updated since bridge will only match on some fields.
|
|
||||||
|
|
||||||
let reachable_bridges = self.bridge_table.reachable.clone();
|
let reachable_bridges = self.bridge_table.reachable.clone();
|
||||||
for reachable_bridge in reachable_bridges {
|
for reachable_bridge in reachable_bridges {
|
||||||
if reachable_bridge.0.uid_fingerprint == bridge.uid_fingerprint {
|
if reachable_bridge.0.uid_fingerprint == bridge.uid_fingerprint {
|
||||||
|
@ -490,6 +504,8 @@ impl BridgeAuth {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to remove a bridge that is failing tests and replace it with a bridge from
|
||||||
|
/// available_bridge or from a spare bucket
|
||||||
pub fn bridge_replace(
|
pub fn bridge_replace(
|
||||||
&mut self,
|
&mut self,
|
||||||
bridge: &BridgeLine,
|
bridge: &BridgeLine,
|
||||||
|
@ -588,7 +604,7 @@ impl BridgeAuth {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark a bridge as unreachable
|
/// Mark a bridge as blocked
|
||||||
///
|
///
|
||||||
/// This bridge will be removed from each of the buckets that
|
/// This bridge will be removed from each of the buckets that
|
||||||
/// contains it. If any of those are open-invitation buckets, the
|
/// contains it. If any of those are open-invitation buckets, the
|
||||||
|
@ -602,7 +618,7 @@ impl BridgeAuth {
|
||||||
/// migration, change the target to the new (formerly spare) bucket.
|
/// migration, change the target to the new (formerly spare) bucket.
|
||||||
/// Returns true if sucessful, or false if it needed a hot spare but
|
/// Returns true if sucessful, or false if it needed a hot spare but
|
||||||
/// there was none available.
|
/// there was none available.
|
||||||
pub fn bridge_unreachable(&mut self, bridge: &BridgeLine, bdb: &mut BridgeDb) -> bool {
|
pub fn bridge_blocked(&mut self, bridge: &BridgeLine, bdb: &mut BridgeDb) -> bool {
|
||||||
let mut res: bool = true;
|
let mut res: bool = true;
|
||||||
if self.bridge_table.unallocated_bridges.contains(bridge) {
|
if self.bridge_table.unallocated_bridges.contains(bridge) {
|
||||||
let index = self
|
let index = self
|
||||||
|
@ -692,7 +708,7 @@ impl BridgeAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since buckets are moved around in the bridge_table, finding a lookup key that
|
// Since buckets are moved around in the bridge_table, finding a lookup key that
|
||||||
// does not overwrite existing bridges could become an issue. We keep a list
|
// does not overwrite existing bridges could become an issue.We keep a list
|
||||||
// of recycleable lookup keys from buckets that have been removed and prioritize
|
// of recycleable lookup keys from buckets that have been removed and prioritize
|
||||||
// this list before increasing the counter
|
// this list before increasing the counter
|
||||||
fn find_next_available_key(&mut self, bdb: &mut BridgeDb) -> Result<u32, NoAvailableIDError> {
|
fn find_next_available_key(&mut self, bdb: &mut BridgeDb) -> Result<u32, NoAvailableIDError> {
|
||||||
|
@ -728,6 +744,7 @@ impl BridgeAuth {
|
||||||
self.clean_up_open_entry(bdb);
|
self.clean_up_open_entry(bdb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cleans up exipred blocked buckets
|
||||||
fn clean_up_blocked(&mut self) {
|
fn clean_up_blocked(&mut self) {
|
||||||
if !self.bridge_table.blocked_keys.is_empty()
|
if !self.bridge_table.blocked_keys.is_empty()
|
||||||
&& self
|
&& self
|
||||||
|
@ -754,7 +771,7 @@ impl BridgeAuth {
|
||||||
if bridgeline.port > 0 {
|
if bridgeline.port > 0 {
|
||||||
// Move to unallocated bridges
|
// Move to unallocated bridges
|
||||||
self.bridge_table.unallocated_bridges.push(*bridgeline);
|
self.bridge_table.unallocated_bridges.push(*bridgeline);
|
||||||
// Check if it's still in the reachable bridges. It should be if we've gotten this far.
|
// Check if it's still in the reachable bridges.It should be if we've gotten this far.
|
||||||
if let Some(_reachable_indexes_for_bridgeline) =
|
if let Some(_reachable_indexes_for_bridgeline) =
|
||||||
self.bridge_table.reachable.get(bridgeline)
|
self.bridge_table.reachable.get(bridgeline)
|
||||||
{
|
{
|
||||||
|
@ -780,6 +797,7 @@ impl BridgeAuth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cleans up expired open invitation buckets
|
||||||
fn clean_up_open_entry(&mut self, bdb: &mut BridgeDb) {
|
fn clean_up_open_entry(&mut self, bdb: &mut BridgeDb) {
|
||||||
// First check if there are any open invitation indexes that are old enough to be replaced
|
// First check if there are any open invitation indexes that are old enough to be replaced
|
||||||
if !self.bridge_table.open_inv_keys.is_empty()
|
if !self.bridge_table.open_inv_keys.is_empty()
|
||||||
|
@ -825,14 +843,14 @@ impl BridgeAuth {
|
||||||
self.time_offset += time::Duration::days(1);
|
self.time_offset += time::Duration::days(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[cfg(test)]
|
///#[cfg(test)]
|
||||||
/// For testing only: manually advance the day by the given number
|
/// For testing only: manually advance the day by the given number
|
||||||
/// of days
|
/// of days
|
||||||
pub fn advance_days(&mut self, days: u16) {
|
pub fn advance_days(&mut self, days: u16) {
|
||||||
self.time_offset += time::Duration::days(days.into());
|
self.time_offset += time::Duration::days(days.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get today's (real or simulated) date
|
/// Get today's (real or simulated) date as u32
|
||||||
pub fn today(&self) -> u32 {
|
pub fn today(&self) -> u32 {
|
||||||
// We will not encounter negative Julian dates (~6700 years ago)
|
// We will not encounter negative Julian dates (~6700 years ago)
|
||||||
// or ones larger than 32 bits
|
// or ones larger than 32 bits
|
||||||
|
@ -842,7 +860,7 @@ impl BridgeAuth {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get today's (real or simulated) date
|
/// Get today's (real or simulated) date as a DateTime<Utc> value
|
||||||
pub fn today_date(&self) -> DateTime<Utc> {
|
pub fn today_date(&self) -> DateTime<Utc> {
|
||||||
Utc::now()
|
Utc::now()
|
||||||
}
|
}
|
||||||
|
|
|
@ -888,7 +888,7 @@ fn block_bridges(th: &mut TestHarness, to_block: usize) {
|
||||||
let ba_clone = th.ba.bridge_table.buckets.clone();
|
let ba_clone = th.ba.bridge_table.buckets.clone();
|
||||||
if let Some(bridgelines) = ba_clone.get(&u32::try_from(index).unwrap()) {
|
if let Some(bridgelines) = ba_clone.get(&u32::try_from(index).unwrap()) {
|
||||||
for bridgeline in bridgelines {
|
for bridgeline in bridgelines {
|
||||||
th.ba.bridge_unreachable(bridgeline, &mut th.bdb);
|
th.ba.bridge_blocked(bridgeline, &mut th.bdb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1229,7 +1229,7 @@ fn test_mark_unreachable() {
|
||||||
// Mark a bridge in an untrusted bucket as unreachable
|
// Mark a bridge in an untrusted bucket as unreachable
|
||||||
let bucket6 = th.ba.bridge_table.buckets.get(&6u32).unwrap();
|
let bucket6 = th.ba.bridge_table.buckets.get(&6u32).unwrap();
|
||||||
let b6 = bucket6[0];
|
let b6 = bucket6[0];
|
||||||
th.ba.bridge_unreachable(&b6, &mut th.bdb);
|
th.ba.bridge_blocked(&b6, &mut th.bdb);
|
||||||
|
|
||||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||||
|
@ -1240,7 +1240,7 @@ fn test_mark_unreachable() {
|
||||||
// unreachable
|
// unreachable
|
||||||
let bucket7 = th.ba.bridge_table.buckets.get(&7u32).unwrap();
|
let bucket7 = th.ba.bridge_table.buckets.get(&7u32).unwrap();
|
||||||
let b7 = bucket7[0];
|
let b7 = bucket7[0];
|
||||||
th.ba.bridge_unreachable(&b7, &mut th.bdb);
|
th.ba.bridge_blocked(&b7, &mut th.bdb);
|
||||||
|
|
||||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||||
|
@ -1262,8 +1262,8 @@ fn test_mark_unreachable() {
|
||||||
let bt1 = bucket1[1];
|
let bt1 = bucket1[1];
|
||||||
let bucket2 = th.ba.bridge_table.buckets.get(&target).unwrap();
|
let bucket2 = th.ba.bridge_table.buckets.get(&target).unwrap();
|
||||||
let bt2 = bucket2[2];
|
let bt2 = bucket2[2];
|
||||||
th.ba.bridge_unreachable(&bt1, &mut th.bdb);
|
th.ba.bridge_blocked(&bt1, &mut th.bdb);
|
||||||
th.ba.bridge_unreachable(&bt2, &mut th.bdb);
|
th.ba.bridge_blocked(&bt2, &mut th.bdb);
|
||||||
|
|
||||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||||
|
@ -1313,8 +1313,8 @@ fn test_blockage_migration() {
|
||||||
assert!(bucket.1.is_some());
|
assert!(bucket.1.is_some());
|
||||||
|
|
||||||
// Oh, no! Two of our bridges are blocked!
|
// Oh, no! Two of our bridges are blocked!
|
||||||
th.ba.bridge_unreachable(&bucket.0[0], &mut th.bdb);
|
th.ba.bridge_blocked(&bucket.0[0], &mut th.bdb);
|
||||||
th.ba.bridge_unreachable(&bucket.0[2], &mut th.bdb);
|
th.ba.bridge_blocked(&bucket.0[2], &mut th.bdb);
|
||||||
|
|
||||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||||
|
|
|
@ -7,7 +7,6 @@ rust-version = "1.65"
|
||||||
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
||||||
description = "General helpers used by Lox"
|
description = "General helpers used by Lox"
|
||||||
keywords = ["tor", "lox"]
|
keywords = ["tor", "lox"]
|
||||||
# We must put *something* here and this will do
|
|
||||||
categories = ["rust-patterns"]
|
categories = ["rust-patterns"]
|
||||||
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git/"
|
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git/"
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lox-wasm"
|
name = "lox-wasm"
|
||||||
authors = ["Cecylia Bocovich <cohosh@torproject.org>"]
|
authors = ["Cecylia Bocovich <cohosh@torproject.org>, Lindsey Tulloch <onyinyang@torproject.org"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "WASM bindings for lox"
|
description = "WASM bindings for lox"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
||||||
|
keywords = ["tor", "lox", "bridges","censorship-resistance"]
|
||||||
|
categories = ["wasm", "web-programming::http-client","external-ffi-bindings"]
|
||||||
|
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/lox-wasm"
|
||||||
|
readme = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/blob/main/crates/lox-wasm/README.md"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
|
@ -16,6 +16,9 @@ wasm-pack build --target web
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
|
|
||||||
|
### Testing Locally
|
||||||
|
|
||||||
The provided `index.html` file can be used for testing the lox bindings. First, follow the instructions to [run a lox server](https://gitlab.torproject.org/cohosh/lox-server).
|
The provided `index.html` file can be used for testing the lox bindings. First, follow the instructions to [run a lox server](https://gitlab.torproject.org/cohosh/lox-server).
|
||||||
|
|
||||||
Then, spin up a simple local webserver in the current directory:
|
Then, spin up a simple local webserver in the current directory:
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rdsys_backend"
|
name = "rdsys_backend"
|
||||||
authors = ["Cecylia Bocovich <cohosh@torproject.org>"]
|
authors = ["Cecylia Bocovich <cohosh@torproject.org>, Lindsey Tulloch <onyinyang@torproject.org"]
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
|
||||||
|
keywords = ["tor", "lox", "bridges","censorship-resistance"]
|
||||||
|
categories = ["api-bindings", "encoding"]
|
||||||
|
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/rdsys-backend-api"
|
||||||
|
readme = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/blob/main/crates/rdsys-backend-api/README.md"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
Loading…
Reference in New Issue