Feature/socks5 (#302)
* Adding the beginnings of a socks5 crate * Removing unused import * Adding built.rs * Figured out test failure, stuck a note in detailing under what conditions it fails. * Added lib * wip on the way to compile * First compile with much of the client in place * Comment reflow to 80 lines * Changed Socks5 client help message * Latest changes to from develop applied to socks5 client * Minor cleanup on unused code * Adding snafu dependency * Adding socks library * Getting socks into the module structure * Tokio conversion for socks code nearly completed. * Starting traffic controllers again * Bitcoin SP starting to breathe with Socks5 proxy. Responses not yet being sent. * Adding in some hugely verbose print action so we can see things happening * WIP refactor of socks code. * Renamed structs to be more rubyish * Refactored the run command a bit. * handle_client doesn't need to be public * Starting to split the handle method up into smaller, refactorable chunks * Renamed a test * Finished initial refactor * Minor cleanup * Made a few notes for my future self * Being a bit more explicit in authtentication test * Ensuring that user/password authentication attempts fail if that auth mode is off * Documentation * Refactord types into a types module * Sending the request ID across and reading the response when it comes back. * Added the request_id to the response header * Adding exception handling to websocket send * Semi-working... * Removing non-functional examples. * Minor output clarification * Adding a Socks5 service provider * Websocket connection is now being made. * Added some simple and ungraceful websocket connection error handling * Renamed socks5_proxy back to proxy * ibid * Nicer websocket start method * Receiving messages via websocket * Socks requests work in the simple case, SSL requests don't (yet). * Minor cleanup, renaming variables and moving private functions around * Comments on try_read_request * Moved some code around * Removed commented code and printlns * Comments sp request * Commented response data read * Changing Request to Connection * ibid * Added a controller and split connection / request parsing * Built out error handling on requests a bit * Initial router action * Request deserialization tests back in action * Request constructors * Constructor for controller * Renamed message_router to controller * Starting to build out the responses * Returning proxied connection data * Moving towards new Socks5 request crate * Sending Socks5 multi-part requests through mixnet * Removed the detritus of exploratory coding. * Breaking the socks client read loop when empty bytes are read * Documenting the message format for serialized socks requests * Returning a response from the socks proxy * Removing unused import * Removing more detritus * Restarting loop if no response is received * The off-by-one change that fixed it all * Removing unused response.rs module * Removed unused import * Comment cleanup * More detritus * Cleaning... * Docs for socks5-requests * Using the simple-socks5-requests crate Response in the socks5 client * Removing unused error types * Split request/response into their own files and wrote more tests * Removing temporary README notes * Renamed all instances of request_id to connection_id * Docs on Connection struct * Caving in to connecting inside the constructor for the moment * Fixing up comments on socks5 service provider start * Simplified errors in the Socks5 requests crate * Flattened service provider module hierarchy a bit. * Removed println * Comment to explain return on timeout * Logging controller connect errors * Renamed websocket reads and writes to make them a bit more understandable * Renamed TodoError to ConnectionError * Logging errors instead of panicking on connection read/write failures * Fixed error handling in controller * Removing dead comments * Cargo fmt applied * Removing print statements * Removed more comments, prints, etc Co-authored-by: jstuczyn <jedrzej.stuczynski@gmail.com>
This commit is contained in:
@@ -13,3 +13,5 @@ scripts/deploy_qa.sh
|
||||
scripts/run_gate.sh
|
||||
scripts/run_mix.sh
|
||||
scripts/start_local_tmux_network.sh
|
||||
/.floo
|
||||
/.flooignore
|
||||
|
||||
Generated
+110
-15
@@ -669,7 +669,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -741,6 +741,12 @@ dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@@ -824,7 +830,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -845,7 +851,7 @@ checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -993,7 +999,7 @@ dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2246,7 +2252,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2316,7 +2322,7 @@ checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2372,7 +2378,7 @@ dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"version_check 0.9.1",
|
||||
]
|
||||
|
||||
@@ -2384,7 +2390,7 @@ checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"syn-mid",
|
||||
"version_check 0.9.1",
|
||||
]
|
||||
@@ -2893,7 +2899,7 @@ checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2965,6 +2971,26 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-socks5"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
"futures 0.3.4",
|
||||
"futures-util",
|
||||
"log 0.4.8",
|
||||
"nymsphinx",
|
||||
"pretty_env_logger",
|
||||
"simple-socks5-requests",
|
||||
"tokio 0.2.16",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-socks5-requests"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.2.3"
|
||||
@@ -3031,6 +3057,28 @@ version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b028158eb06caa8345bee10cccfb25fa632beccf0ef5308832b4fd4b78a7db48"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf50aaef500c248a590e2696e8bf8c7620ca2235b9bb90a70363d82dd1abec6a"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
"syn 0.15.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.12"
|
||||
@@ -3043,6 +3091,42 @@ dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socks5"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"built",
|
||||
"clap",
|
||||
"config",
|
||||
"crypto",
|
||||
"curve25519-dalek",
|
||||
"directory-client",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures 0.3.4",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log 0.4.8",
|
||||
"mixnet-client",
|
||||
"nymsphinx",
|
||||
"pem",
|
||||
"pemstore",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"reqwest 0.9.24",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple-socks5-requests",
|
||||
"sled 0.33.0",
|
||||
"snafu",
|
||||
"tempfile",
|
||||
"tokio 0.2.16",
|
||||
"tokio-tungstenite",
|
||||
"topology",
|
||||
"url 2.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "0.1.0"
|
||||
@@ -3110,6 +3194,17 @@ version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
"unicode-xid 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.17"
|
||||
@@ -3129,7 +3224,7 @@ checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3140,7 +3235,7 @@ checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"unicode-xid 0.2.0",
|
||||
]
|
||||
|
||||
@@ -3301,7 +3396,7 @@ checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3699,7 +3794,7 @@ dependencies = [
|
||||
"log 0.4.8",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3746,7 +3841,7 @@ checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3894,6 +3989,6 @@ checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn",
|
||||
"syn 1.0.17",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -6,6 +6,7 @@ opt-level = "s"
|
||||
|
||||
members = [
|
||||
"clients/native",
|
||||
"clients/socks5",
|
||||
"clients/webassembly",
|
||||
"common/client-libs/directory-client",
|
||||
"common/client-libs/directory-client/models",
|
||||
@@ -27,6 +28,7 @@ members = [
|
||||
"common/topology",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"service-providers/simple-socks5",
|
||||
"mixnode",
|
||||
"validator",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module github.com/nymtech/nym/clients/native/examples/go
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcutil v1.0.2 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -207,19 +207,13 @@ impl NymClient {
|
||||
self.config.get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
let shared_key = self.runtime.block_on(async {
|
||||
self.runtime.block_on(async {
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection")
|
||||
});
|
||||
|
||||
// TODO: if we didn't have a shared_key initially, save it to config or something?
|
||||
info!(
|
||||
"Performed initial authentication. Auth token is {:?}",
|
||||
shared_key.to_base58_string()
|
||||
);
|
||||
|
||||
gateway_client
|
||||
}
|
||||
|
||||
@@ -413,5 +407,17 @@ impl NymClient {
|
||||
}
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!(
|
||||
"Client identity public key is: {}",
|
||||
self.as_mix_recipient().identity().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Client encryption public key is: {}",
|
||||
self.as_mix_recipient().encryption_key().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Gateway identity public key is: {:?}",
|
||||
self.config.get_gateway_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ impl TopologyAccessor {
|
||||
}
|
||||
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because healthchecker is disabled due to required changes)
|
||||
// (currently displays as unused because health checker is disabled due to required changes)
|
||||
pub(crate) async fn is_routable(&self) -> bool {
|
||||
match &self.inner.read().await.0 {
|
||||
None => false,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "socks5"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "nym_socks5"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.33.0"
|
||||
curve25519-dalek = "2.0.0"
|
||||
dirs = "2.0.2"
|
||||
dotenv = "0.15.0"
|
||||
futures = "0.3.1"
|
||||
log = "0.4"
|
||||
pem = "0.7.0"
|
||||
pretty_env_logger = "0.3"
|
||||
rand = {version = "0.7.3", features = ["wasm-bindgen"]}
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
sled = "0.33"
|
||||
snafu = "0.4.1"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio-tungstenite = "0.10.1"
|
||||
url = "2.1"
|
||||
|
||||
## internal
|
||||
config = {path = "../../common/config"}
|
||||
crypto = {path = "../../common/crypto"}
|
||||
directory-client = { path = "../../common/client-libs/directory-client" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
mixnet-client = { path = "../../common/client-libs/mixnet-client" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = {path = "../../common/pemstore"}
|
||||
simple-socks5-requests = { path = "../../service-providers/simple-socks5/simple-socks5-requests"}
|
||||
topology = {path = "../../common/topology" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The file has been placed there by the build script.
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
|
||||
pub(crate) struct LoopCoverTrafficStream<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: time::Duration,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: time::Duration,
|
||||
|
||||
/// Average delay between sending subsequent cover packets.
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: time::Delay,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: MixMessageSender,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
// Item is only used to indicate we should create a new message rather than actual cover message
|
||||
// reason being to not introduce unnecessary complexity by having to keep state of topology
|
||||
// mutex when trying to acquire it. So right now the Stream trait serves as a glorified timer.
|
||||
// Perhaps this should be changed in the future.
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.average_cover_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
Poll::Ready(Some(()))
|
||||
}
|
||||
}
|
||||
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl LoopCoverTrafficStream<OsRng> {
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
average_ack_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
mix_tx: MixMessageSender,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
LoopCoverTrafficStream {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_cover_message_sending_delay,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_new_message(&mut self) {
|
||||
trace!("next cover message!");
|
||||
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
let topology_ref_option = topology_permit.try_get_valid_topology_ref(
|
||||
&self.our_full_destination,
|
||||
Some(&self.our_full_destination),
|
||||
);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("No valid topology detected - won't send any loop cover message this time");
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&*self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx
|
||||
.unbounded_send(MixMessage::new(cover_message.0, cover_message.1))
|
||||
.unwrap();
|
||||
|
||||
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
|
||||
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
|
||||
// properly blocks. So to play it on the safe side, just explicitly drop the read permit
|
||||
drop(topology_permit);
|
||||
|
||||
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
|
||||
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = time::delay_for(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.average_cover_message_sending_delay,
|
||||
));
|
||||
|
||||
while let Some(_) = self.next().await {
|
||||
self.on_new_message().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use futures::channel::mpsc;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySURB;
|
||||
|
||||
pub(crate) type InputMessageSender = mpsc::UnboundedSender<InputMessage>;
|
||||
pub(crate) type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum InputMessage {
|
||||
Fresh {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
},
|
||||
Reply {
|
||||
reply_surb: ReplySURB,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub(crate) fn new_fresh(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Self {
|
||||
InputMessage::Fresh {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_reply(reply_surb: ReplySURB, data: Vec<u8>) -> Self {
|
||||
InputMessage::Reply { reply_surb, data }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
|
||||
// And I guess there will need to be some mechanism for a grace period when you can still
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
pub(crate) struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Option<Arc<SharedKeys>>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
// The expected flow of a KeyManager "lifetime" is as follows:
|
||||
/*
|
||||
1. ::new() is called during client-init
|
||||
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
|
||||
3. ::store_keys() is called before init finishes execution.
|
||||
4. ::load_keys() is called at the beginning of each subsequent client-run
|
||||
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
|
||||
*/
|
||||
|
||||
impl KeyManager {
|
||||
// this is actually **NOT** dead code
|
||||
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
|
||||
#[allow(dead_code)]
|
||||
/// Creates new instance of a [`KeyManager`]
|
||||
pub(crate) fn new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
KeyManager {
|
||||
identity_keypair: Arc::new(identity::KeyPair::new_with_rng(rng)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new_with_rng(rng)),
|
||||
gateway_shared_key: None,
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
}
|
||||
}
|
||||
|
||||
// this is actually **NOT** dead code
|
||||
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
|
||||
#[allow(dead_code)]
|
||||
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
|
||||
pub(crate) fn insert_gateway_shared_key(&mut self, gateway_shared_key: SharedKeys) {
|
||||
self.gateway_shared_key = Some(Arc::new(gateway_shared_key))
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk.
|
||||
pub(crate) fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
))?;
|
||||
let encryption_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
))?;
|
||||
|
||||
let gateway_shared_key: SharedKeys =
|
||||
pemstore::load_key(&client_pathfinder.gateway_shared_key().to_owned())?;
|
||||
|
||||
let ack_key: AckKey = pemstore::load_key(&client_pathfinder.ack_key().to_owned())?;
|
||||
|
||||
// TODO: ack key is never stored so it is generated now. But perhaps it should be stored
|
||||
// after all for consistency sake?
|
||||
Ok(KeyManager {
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
encryption_keypair: Arc::new(encryption_keypair),
|
||||
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
|
||||
ack_key: Arc::new(ack_key),
|
||||
})
|
||||
}
|
||||
|
||||
// this is actually **NOT** dead code
|
||||
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
|
||||
#[allow(dead_code)]
|
||||
/// Stores all available keys on the disk.
|
||||
// While perhaps there is no much point in storing the `AckKey` on the disk,
|
||||
// it is done so for the consistency sake so that you wouldn't require an rng instance
|
||||
// during `load_keys` to generate the said key.
|
||||
pub(crate) fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
pemstore::store_keypair(
|
||||
self.identity_keypair.as_ref(),
|
||||
&pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
pemstore::store_keypair(
|
||||
self.encryption_keypair.as_ref(),
|
||||
&pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
|
||||
pemstore::store_key(self.ack_key.as_ref(), &client_pathfinder.ack_key())?;
|
||||
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => warn!("No gateway shared key available to store!"),
|
||||
Some(gate_key) => {
|
||||
pemstore::store_key(gate_key.as_ref(), &client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub(crate) fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub(crate) fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
// since this function is not fully public, it is not expected to be used externally and
|
||||
// hence it's up to us to ensure it's called in correct context
|
||||
pub(crate) fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(
|
||||
&self
|
||||
.gateway_shared_key
|
||||
.as_ref()
|
||||
.expect("tried to unwrap empty gateway key!"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub(crate) fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::GatewayClient;
|
||||
use log::*;
|
||||
use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub(crate) struct MixMessage(NymNodeRoutingAddress, SphinxPacket);
|
||||
pub(crate) type MixMessageSender = mpsc::UnboundedSender<MixMessage>;
|
||||
pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver<MixMessage>;
|
||||
|
||||
impl MixMessage {
|
||||
pub(crate) fn new(address: NymNodeRoutingAddress, packet: SphinxPacket) -> Self {
|
||||
MixMessage(address, packet)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
pub(crate) struct MixTrafficController<'a> {
|
||||
// TODO: most likely to be replaced by some higher level construct as
|
||||
// later on gateway_client will need to be accessible by other entities
|
||||
gateway_client: GatewayClient<'a, url::Url>,
|
||||
mix_rx: MixMessageReceiver,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
}
|
||||
|
||||
impl<'a> MixTrafficController<'static> {
|
||||
pub(crate) fn new(
|
||||
mix_rx: MixMessageReceiver,
|
||||
gateway_client: GatewayClient<'a, url::Url>,
|
||||
) -> MixTrafficController<'a> {
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, mix_message: MixMessage) {
|
||||
debug!("Got a mix_message for {:?}", mix_message.0);
|
||||
match self
|
||||
.gateway_client
|
||||
.send_sphinx_packet(mix_message.0, mix_message.1)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
error!("Failed to send sphinx packet to the gateway! - {:?}", e);
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// todo: in the future this should initiate a 'graceful' shutdown
|
||||
panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT)
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
trace!("We *might* have managed to forward sphinx packet to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
while let Some(mix_message) = self.mix_rx.next().await {
|
||||
self.on_message(mix_message).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use crate::client::mix_traffic::{MixMessageReceiver, MixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
|
||||
};
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use crate::config::{Config, SocketType};
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::{ClientEncryptionKey, ClientIdentity, Recipient};
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySURB;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
mod cover_traffic_stream;
|
||||
pub(crate) mod inbound_messages;
|
||||
pub(crate) mod key_manager;
|
||||
mod mix_traffic;
|
||||
pub(crate) mod real_messages_control;
|
||||
pub(crate) mod received_buffer;
|
||||
mod reply_key_storage;
|
||||
pub(crate) mod topology_control;
|
||||
|
||||
pub struct NymClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// Tokio runtime used for futures execution.
|
||||
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
|
||||
// gateway.
|
||||
runtime: Runtime,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
|
||||
/// through the mix network.
|
||||
/// It is only available if the client started with the websocket listener disabled.
|
||||
input_tx: Option<InputMessageSender>,
|
||||
|
||||
/// Channel used for obtaining reconstructed messages received from the mix network.
|
||||
/// It is only available if the client started with the websocket listener disabled.
|
||||
receive_tx: Option<ReconstructedMessagesReceiver>,
|
||||
}
|
||||
|
||||
impl NymClient {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(&config);
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
key_manager,
|
||||
input_tx: None,
|
||||
receive_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mix_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
self.key_manager.encryption_keypair().public_key().clone(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(self.config.get_gateway_id()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
// future constantly pumping loop cover traffic at some specified average rate
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: MixMessageSender,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
self.runtime
|
||||
.enter(|| {
|
||||
LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_average_ack_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
self.config.get_loop_cover_traffic_average_delay(),
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
})
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: MixMessageSender,
|
||||
) {
|
||||
let controller_config = real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_ack_wait_multiplier(),
|
||||
self.config.get_ack_wait_addition(),
|
||||
self.config.get_average_ack_delay(),
|
||||
self.config.get_message_sending_average_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
|
||||
// When refactoring this restriction should definitely be removed.
|
||||
let real_messages_controller = self.runtime.enter(|| {
|
||||
RealMessagesController::new(
|
||||
controller_config,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
});
|
||||
real_messages_controller.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
// required so that other components would be able to use them (say the websocket)
|
||||
fn start_received_messages_buffer_controller(
|
||||
&self,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(
|
||||
self.key_manager.encryption_keypair(),
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
) -> GatewayClient<'static, url::Url> {
|
||||
let gateway_id = self.config.get_gateway_id();
|
||||
if gateway_id.is_empty() {
|
||||
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
let gateway_address_str = self.config.get_gateway_listener();
|
||||
if gateway_address_str.is_empty() {
|
||||
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
// TODO: since we presumably now get something valid-ish from the `init`, can we just
|
||||
// ditch url::Url and operate on `String`?
|
||||
let gateway_address =
|
||||
url::Url::parse(&gateway_address_str).expect("provided gateway address is invalid!");
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
self.runtime.block_on(async {
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection")
|
||||
});
|
||||
|
||||
gateway_client
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_directory_server(),
|
||||
self.config.get_topology_refresh_rate(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
TopologyRefresher::new_directory_client(topology_refresher_config, topology_accessor);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!(
|
||||
"Obtaining initial network topology from {}",
|
||||
self.config.get_directory_server()
|
||||
);
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !self
|
||||
.runtime
|
||||
.block_on(topology_refresher.is_topology_routable())
|
||||
{
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
&mut self,
|
||||
mix_rx: MixMessageReceiver,
|
||||
gateway_client: GatewayClient<'static, url::Url>,
|
||||
) {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_socks5_listener(
|
||||
&self,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
msg_input: InputMessageSender,
|
||||
) {
|
||||
info!("Starting socks5 listener...");
|
||||
let mut auth_methods: Vec<u8> = Vec::new();
|
||||
auth_methods.push(AuthenticationMethods::NoAuth as u8);
|
||||
let allowed_users: Vec<User> = Vec::new();
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
let recipient = self.load_socks5_service_provider();
|
||||
let mut sphinx_socks = SphinxSocksServer::new(1080, "127.0.0.1", authenticator, recipient);
|
||||
self.runtime
|
||||
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
|
||||
}
|
||||
// TODO: make this configurable in the client config file
|
||||
// TODO: Talk to JS about where I can easily find these.
|
||||
fn load_socks5_service_provider(&self) -> Recipient {
|
||||
// load from file here, or better yet, inject it
|
||||
let identity = "7qBXQor8nHXUXDAUM4aLvqDJ2MECFKn3AJ3brQsu5qz8";
|
||||
let encryption_key = "4uzn7m3vPEy5MhPHLqzFCJHk2BCnvbCsGdrYzng7jnor";
|
||||
let gateway_key = "e3vUAo6YhB7zq3GH8B4k3iiGT4H2USjdd5ZMZoUsHdF";
|
||||
|
||||
let client_identity = ClientIdentity::from_base58_string(identity).unwrap();
|
||||
let client_encryption_key =
|
||||
ClientEncryptionKey::from_base58_string(encryption_key).unwrap();
|
||||
let gateway = NodeIdentity::from_base58_string(gateway_key).unwrap();
|
||||
Recipient::new(client_identity, client_encryption_key, gateway)
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_reply(&mut self, reply_surb: ReplySURB, message: Vec<u8>) {
|
||||
let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
/// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple
|
||||
/// messages, you might have to call this function repeatedly.
|
||||
// TODO: I guess this should really return something that `impl Stream<Item=ReconstructedMessage>`
|
||||
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
|
||||
use futures::StreamExt;
|
||||
|
||||
self.receive_tx
|
||||
.as_mut()
|
||||
.expect("start method was not called before!")
|
||||
.next()
|
||||
.await
|
||||
.expect("buffer controller seems to have somehow died!")
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Received SIGINT - the client will terminate now (threads are not YET nicely stopped)"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
// and would allow anyone to clone the sender channel
|
||||
|
||||
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
|
||||
// they are used by cover traffic stream and real traffic stream
|
||||
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
|
||||
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
|
||||
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
let reply_key_storage =
|
||||
ReplyKeyStorage::load(self.config.get_reply_encryption_key_store_path())
|
||||
.expect("Failed to load reply key storage!");
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_key_storage.clone(),
|
||||
);
|
||||
|
||||
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
reply_key_storage,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
);
|
||||
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
|
||||
|
||||
match self.config.get_socket_type() {
|
||||
SocketType::WebSocket => {
|
||||
self.start_socks5_listener(received_buffer_request_sender, input_sender)
|
||||
}
|
||||
SocketType::None => {
|
||||
// if we did not start the socket, it means we're running (supposedly) in the native mode
|
||||
// and hence we should announce 'ourselves' to the buffer
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
received_buffer_request_sender
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.receive_tx = Some(reconstructed_receiver);
|
||||
self.input_tx = Some(input_sender);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!(
|
||||
"Client identity public key is: {}",
|
||||
self.as_mix_recipient().identity().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Client encryption public key is: {}",
|
||||
self.as_mix_recipient().encryption_key().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Gateway identity public key is: {:?}",
|
||||
self.config.get_gateway_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::PendingAcksMap;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
// responsible for cancelling retransmission timers and removed entries from the map
|
||||
pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
pending_acks,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
debug!("Received an ack");
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content) {
|
||||
None => {
|
||||
warn!("Received invalid ACK!"); // should we do anything else about that?
|
||||
return;
|
||||
}
|
||||
Some(frag_id_bytes) => match FragmentIdentifier::try_from_bytes(frag_id_bytes) {
|
||||
Ok(frag_id) => frag_id,
|
||||
Err(err) => {
|
||||
warn!("Received invalid ACK! - {:?}", err); // should we do anything else about that?
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
return;
|
||||
} else if frag_id.is_reply() {
|
||||
debug!("Received an ack for a reply message - no need to do anything!");
|
||||
// TODO: probably there will need to be some extra procedure here, something to notify
|
||||
// user that his reply reached the recipient (since we got an ack)
|
||||
info!("We received an ack for one of the replies we sent!");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(pending_ack) = self.pending_acks.write().await.remove(&frag_id) {
|
||||
// cancel the retransmission future
|
||||
pending_ack.retransmission_cancel.notify();
|
||||
} else {
|
||||
warn!("received ACK for packet we haven't stored! - {:?}", frag_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started AcknowledgementListener");
|
||||
while let Some(acks) = self.ack_receiver.next().await {
|
||||
// realistically we would only be getting one ack at the time, but if we managed to
|
||||
// introduce batching in gateway client, this call should be improved to not re-acquire
|
||||
// write permit on the map every loop iteration
|
||||
for ack in acks {
|
||||
self.on_ack(ack).await;
|
||||
}
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcknowledgement, PendingAcksMap};
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{
|
||||
inbound_messages::{InputMessage, InputMessageReceiver},
|
||||
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::ReplySURB;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
// responsible for splitting received message and initial sending attempt
|
||||
pub(super) struct InputMessageListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
input_receiver,
|
||||
message_preparer,
|
||||
pending_acks,
|
||||
real_message_sender,
|
||||
topology_access,
|
||||
reply_key_storage,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_reply(&mut self, reply_surb: ReplySURB, data: Vec<u8>) -> Option<RealMessage> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology_ref_option =
|
||||
topology_permit.try_get_valid_topology_ref(&self.ack_recipient, None);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("Could not process the message - the network topology is invalid");
|
||||
return None;
|
||||
}
|
||||
let topology = topology_ref_option.unwrap();
|
||||
|
||||
match self
|
||||
.message_preparer
|
||||
.prepare_reply_for_use(data, reply_surb, topology, &self.ack_key)
|
||||
{
|
||||
Ok((reply_id, sphinx_packet, first_hop)) => {
|
||||
// TODO: later probably write pending ack here
|
||||
// and deal with them....
|
||||
// ... somehow
|
||||
|
||||
Some(RealMessage::new(first_hop, sphinx_packet, reply_id))
|
||||
}
|
||||
Err(err) => {
|
||||
// TODO: should we have some mechanism to indicate to the user that the `reply_surb`
|
||||
// could be reused since technically it wasn't used up here?
|
||||
warn!("failed to deal with received reply surb - {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_fresh_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
content: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
) -> Vec<RealMessage> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology_ref_option =
|
||||
topology_permit.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient));
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("Could not process the message - the network topology is invalid");
|
||||
return Vec::new();
|
||||
}
|
||||
let topology = topology_ref_option.unwrap();
|
||||
|
||||
// split the message, attach optional reply surb
|
||||
let (split_message, reply_key) = self
|
||||
.message_preparer
|
||||
.prepare_and_split_message(content, with_reply_surb, topology)
|
||||
.expect("somehow the topology was invalid after all!");
|
||||
|
||||
if let Some(reply_key) = reply_key {
|
||||
self.reply_key_storage
|
||||
.insert_encryption_key(reply_key)
|
||||
.expect("Failed to insert surb reply key to the store!")
|
||||
}
|
||||
|
||||
// encrypt chunks, put them inside sphinx packets and generate acks
|
||||
let mut pending_acks = Vec::with_capacity(split_message.len());
|
||||
let mut real_messages = Vec::with_capacity(split_message.len());
|
||||
for message_chunk in split_message {
|
||||
// since the paths can be constructed, this CAN'T fail, if it does, there's a bug somewhere
|
||||
let frag_id = message_chunk.fragment_identifier();
|
||||
// we need to clone it because we need to keep it in memory in case we had to retransmit
|
||||
// it. And then we'd need to recreate entire ACK again.
|
||||
let chunk_clone = message_chunk.clone();
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient)
|
||||
.unwrap();
|
||||
|
||||
real_messages.push(RealMessage::new(
|
||||
prepared_fragment.first_hop_address,
|
||||
prepared_fragment.sphinx_packet,
|
||||
frag_id,
|
||||
));
|
||||
|
||||
let pending_ack = PendingAcknowledgement::new(
|
||||
message_chunk,
|
||||
prepared_fragment.total_delay,
|
||||
recipient.clone(),
|
||||
);
|
||||
|
||||
pending_acks.push((frag_id, pending_ack));
|
||||
}
|
||||
|
||||
// first insert pending_acks only then request fragments to be sent, otherwise you might get
|
||||
// some very nasty (and time-consuming to figure out...) race condition.
|
||||
let mut pending_acks_map_write_guard = self.pending_acks.write().await;
|
||||
for (frag_id, pending_ack) in pending_acks.into_iter() {
|
||||
if pending_acks_map_write_guard
|
||||
.insert(frag_id, pending_ack)
|
||||
.is_some()
|
||||
{
|
||||
panic!("Tried to insert duplicate pending ack")
|
||||
}
|
||||
}
|
||||
|
||||
real_messages
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
let real_messages = match msg {
|
||||
InputMessage::Fresh {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
} => {
|
||||
self.handle_fresh_message(recipient, data, with_reply_surb)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply { reply_surb, data } => {
|
||||
if let Some(real_message) = self.handle_reply(reply_surb, data).await {
|
||||
vec![real_message]
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for real_message in real_messages {
|
||||
self.real_message_sender
|
||||
.unbounded_send(real_message)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started InputMessageListener");
|
||||
while let Some(input_msg) = self.input_receiver.next().await {
|
||||
self.on_input_message(input_msg).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use self::{
|
||||
acknowledgement_listener::AcknowledgementListener,
|
||||
input_message_listener::InputMessageListener,
|
||||
retransmission_request_listener::RetransmissionRequestListener,
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use super::real_traffic_stream::RealMessageSender;
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
chunking::fragment::{Fragment, FragmentIdentifier},
|
||||
Delay,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::{
|
||||
sync::{Notify, RwLock},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
mod acknowledgement_listener;
|
||||
mod input_message_listener;
|
||||
mod retransmission_request_listener;
|
||||
mod sent_notification_listener;
|
||||
|
||||
type RetransmissionRequestSender = mpsc::UnboundedSender<FragmentIdentifier>;
|
||||
type RetransmissionRequestReceiver = mpsc::UnboundedReceiver<FragmentIdentifier>;
|
||||
|
||||
pub(super) type SentPacketNotificationSender = mpsc::UnboundedSender<FragmentIdentifier>;
|
||||
type SentPacketNotificationReceiver = mpsc::UnboundedReceiver<FragmentIdentifier>;
|
||||
|
||||
type PendingAcksMap = Arc<RwLock<HashMap<FragmentIdentifier, PendingAcknowledgement>>>;
|
||||
|
||||
struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: Delay,
|
||||
recipient: Recipient,
|
||||
retransmission_cancel: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl PendingAcknowledgement {
|
||||
fn new(message_chunk: Fragment, delay: Delay, recipient: Recipient) -> Self {
|
||||
PendingAcknowledgement {
|
||||
message_chunk,
|
||||
delay,
|
||||
retransmission_cancel: Arc::new(Notify::new()),
|
||||
recipient,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_delay(&mut self, new_delay: Delay) {
|
||||
self.delay = new_delay;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementControllerConnectors {
|
||||
real_message_sender: RealMessageSender,
|
||||
input_receiver: InputMessageReceiver,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
}
|
||||
|
||||
impl AcknowledgementControllerConnectors {
|
||||
pub(super) fn new(
|
||||
real_message_sender: RealMessageSender,
|
||||
input_receiver: InputMessageReceiver,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
) -> Self {
|
||||
AcknowledgementControllerConnectors {
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier,
|
||||
ack_receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
acknowledgement_listener: Option<AcknowledgementListener>,
|
||||
input_message_listener: Option<InputMessageListener<R>>,
|
||||
retransmission_request_listener: Option<RetransmissionRequestListener<R>>,
|
||||
sent_notification_listener: Option<SentNotificationListener>,
|
||||
}
|
||||
|
||||
impl<R> AcknowledgementController<R>
|
||||
where
|
||||
R: 'static + CryptoRng + Rng + Clone + Send,
|
||||
{
|
||||
pub(super) fn new(
|
||||
rng: R,
|
||||
topology_access: TopologyAccessor,
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
) -> Self {
|
||||
let pending_acks = Arc::new(RwLock::new(HashMap::new()));
|
||||
let message_preparer = MessagePreparer::new(
|
||||
rng,
|
||||
ack_recipient.clone(),
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
);
|
||||
|
||||
let acknowledgement_listener = AcknowledgementListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
connectors.ack_receiver,
|
||||
Arc::clone(&pending_acks),
|
||||
);
|
||||
|
||||
let input_message_listener = InputMessageListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient.clone(),
|
||||
connectors.input_receiver,
|
||||
message_preparer.clone(),
|
||||
Arc::clone(&pending_acks),
|
||||
connectors.real_message_sender.clone(),
|
||||
topology_access.clone(),
|
||||
reply_key_storage,
|
||||
);
|
||||
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
let retransmission_request_listener = RetransmissionRequestListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient,
|
||||
message_preparer,
|
||||
Arc::clone(&pending_acks),
|
||||
connectors.real_message_sender,
|
||||
retransmission_rx,
|
||||
topology_access,
|
||||
);
|
||||
|
||||
let sent_notification_listener = SentNotificationListener::new(
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
connectors.sent_notifier,
|
||||
pending_acks,
|
||||
retransmission_tx,
|
||||
);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener: Some(acknowledgement_listener),
|
||||
input_message_listener: Some(input_message_listener),
|
||||
retransmission_request_listener: Some(retransmission_request_listener),
|
||||
sent_notification_listener: Some(sent_notification_listener),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener.take().unwrap();
|
||||
let mut input_message_listener = self.input_message_listener.take().unwrap();
|
||||
let mut retransmission_request_listener =
|
||||
self.retransmission_request_listener.take().unwrap();
|
||||
let mut sent_notification_listener = self.sent_notification_listener.take().unwrap();
|
||||
|
||||
// TODO: perhaps an extra 'DEBUG' task that would periodically check for stale entries in
|
||||
// pending acks map?
|
||||
// It would only be 'DEBUG' as I don't expect any stale entries to exist there to begin with,
|
||||
// but when can bugs be expected to begin with?
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let ack_listener_fut = tokio::spawn(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
error!("The acknowledgement listener has finished execution!");
|
||||
acknowledgement_listener
|
||||
});
|
||||
let input_listener_fut = tokio::spawn(async move {
|
||||
input_message_listener.run().await;
|
||||
error!("The input listener has finished execution!");
|
||||
input_message_listener
|
||||
});
|
||||
let retransmission_req_fut = tokio::spawn(async move {
|
||||
retransmission_request_listener.run().await;
|
||||
error!("The retransmission request listener has finished execution!");
|
||||
retransmission_request_listener
|
||||
});
|
||||
let sent_notification_fut = tokio::spawn(async move {
|
||||
sent_notification_listener.run().await;
|
||||
error!("The sent notification listener has finished execution!");
|
||||
sent_notification_listener
|
||||
});
|
||||
|
||||
// technically we don't have to bring `AcknowledgementController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.acknowledgement_listener = Some(ack_listener_fut.await.unwrap());
|
||||
self.input_message_listener = Some(input_listener_fut.await.unwrap());
|
||||
self.retransmission_request_listener = Some(retransmission_req_fut.await.unwrap());
|
||||
self.sent_notification_listener = Some(sent_notification_fut.await.unwrap());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcksMap, RetransmissionRequestReceiver};
|
||||
use crate::client::{
|
||||
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey, addressing::clients::Recipient,
|
||||
chunking::fragment::FragmentIdentifier,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
message_preparer,
|
||||
pending_acks,
|
||||
real_message_sender,
|
||||
request_receiver,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(&mut self, frag_id: FragmentIdentifier) {
|
||||
let pending_acks_map_read_guard = self.pending_acks.read().await;
|
||||
// if the unwrap failed here, we have some weird bug somewhere - honestly, I'm not sure
|
||||
// if it's even possible for it to happen
|
||||
let unreceived_ack_fragment = pending_acks_map_read_guard
|
||||
.get(&frag_id)
|
||||
.expect("wanted to retransmit ack'd fragment");
|
||||
|
||||
let packet_recipient = unreceived_ack_fragment.recipient.clone();
|
||||
let chunk_clone = unreceived_ack_fragment.message_chunk.clone();
|
||||
let frag_id = unreceived_ack_fragment.message_chunk.fragment_identifier();
|
||||
|
||||
// TODO: we need some proper benchmarking here to determine whether it could
|
||||
// be more efficient to just get write lock and keep it while doing sphinx computation,
|
||||
// but my gut feeling tells me we should re-acquire it.
|
||||
drop(pending_acks_map_read_guard);
|
||||
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology_ref_option = topology_permit
|
||||
.try_get_valid_topology_ref(&self.ack_recipient, Some(&packet_recipient));
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("Could not retransmit the packet - the network topology is invalid");
|
||||
// TODO: perhaps put back into pending acks and reset the timer?
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, &packet_recipient)
|
||||
.unwrap();
|
||||
|
||||
// minor optimization to not hold the permit while we no longer need it and might have to block
|
||||
// waiting for the write lock on `pending_acks`
|
||||
drop(topology_permit);
|
||||
|
||||
self.pending_acks
|
||||
.write()
|
||||
.await
|
||||
.get_mut(&frag_id)
|
||||
.expect(
|
||||
"on_retransmission_request: somehow we already received an ack for this packet?",
|
||||
)
|
||||
.update_delay(prepared_fragment.total_delay);
|
||||
|
||||
self.real_message_sender
|
||||
.unbounded_send(RealMessage::new(
|
||||
prepared_fragment.first_hop_address,
|
||||
prepared_fragment.sphinx_packet,
|
||||
frag_id,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started RetransmissionRequestListener");
|
||||
while let Some(frag_id) = self.request_receiver.next().await {
|
||||
self.on_retransmission_request(frag_id).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcksMap, RetransmissionRequestSender, SentPacketNotificationReceiver};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// responsible for starting and controlling retransmission timers
|
||||
// it is required because when we send our packet to the `real traffic stream` controlled
|
||||
// with poisson timer, there's no guarantee the message will be sent immediately, so we might
|
||||
// accidentally fire retransmission way quicker than we would have wanted.
|
||||
pub(super) struct SentNotificationListener {
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
sent_notifier,
|
||||
pending_acks,
|
||||
retransmission_sender,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_sent_message(&mut self, frag_id: FragmentIdentifier) {
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||
return;
|
||||
} else if frag_id.is_reply() {
|
||||
debug!("sent off a reply message - no need to start retransmission timer!");
|
||||
// TODO: probably there will need to be some extra procedure here, like it would
|
||||
// be nice to know that our reply actually reached the recipient (i.e. we got the ack)
|
||||
return;
|
||||
}
|
||||
|
||||
let pending_acks_map_read_guard = self.pending_acks.read().await;
|
||||
// if the unwrap failed here, we have some weird bug somewhere
|
||||
// although when I think about it, it *theoretically* could happen under extremely heavy client
|
||||
// load that `on_sent_message()` is not called (and we do not receive the read permit)
|
||||
// until we already received and processed an ack for the packet
|
||||
// but this seems extremely unrealistic, but perhaps we should guard against that?
|
||||
let pending_ack_data = pending_acks_map_read_guard
|
||||
.get(&frag_id)
|
||||
.expect("on_sent_message: somehow we already received an ack for this packet?");
|
||||
|
||||
// if this assertion ever fails, we have some bug due to some unintended leak.
|
||||
// the only reason I see it could happen if the `tokio::select` in the spawned
|
||||
// task below somehow did not drop it
|
||||
debug_assert_eq!(
|
||||
Arc::strong_count(&pending_ack_data.retransmission_cancel),
|
||||
1
|
||||
);
|
||||
|
||||
// TODO: read more about Arc::downgrade. it could be useful here
|
||||
let retransmission_cancel = Arc::clone(&pending_ack_data.retransmission_cancel);
|
||||
|
||||
let retransmission_timeout = tokio::time::delay_for(
|
||||
(pending_ack_data.delay.clone() * self.ack_wait_multiplier).to_duration()
|
||||
+ self.ack_wait_addition,
|
||||
);
|
||||
|
||||
let retransmission_sender = self.retransmission_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = retransmission_cancel.notified() => {
|
||||
trace!("received ack for the fragment. Cancelling retransmission future");
|
||||
}
|
||||
_ = retransmission_timeout => {
|
||||
trace!("did not receive an ack - will retransmit the packet");
|
||||
retransmission_sender.unbounded_send(frag_id).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started SentNotificationListener");
|
||||
while let Some(frag_id) = self.sent_notifier.next().await {
|
||||
self.on_sent_message(frag_id).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// INPUT: InputMessage from user
|
||||
// INPUT2: Acks from mix
|
||||
// OUTPUT: MixMessage to mix traffic
|
||||
|
||||
use self::{
|
||||
acknowlegement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||
};
|
||||
use crate::client::real_messages_control::acknowlegement_control::AcknowledgementControllerConnectors;
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::MixMessageSender,
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
mod acknowlegement_control;
|
||||
mod real_traffic_stream;
|
||||
|
||||
pub(crate) struct Config {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
self_recipient: Recipient,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
average_packet_delay_duration: Duration,
|
||||
self_recipient: Recipient,
|
||||
) -> Self {
|
||||
Config {
|
||||
ack_key,
|
||||
self_recipient,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
average_message_sending_delay,
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessagesController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
out_queue_control: Option<OutQueueControl<R>>,
|
||||
ack_control: Option<AcknowledgementController<R>>,
|
||||
}
|
||||
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl RealMessagesController<OsRng> {
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: MixMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
let (real_message_sender, real_message_receiver) = mpsc::unbounded();
|
||||
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
|
||||
|
||||
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier_rx,
|
||||
ack_receiver,
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
rng,
|
||||
topology_access.clone(),
|
||||
Arc::clone(&config.ack_key),
|
||||
config.self_recipient.clone(),
|
||||
reply_key_storage,
|
||||
config.average_packet_delay_duration,
|
||||
config.average_ack_delay_duration,
|
||||
config.ack_wait_multiplier,
|
||||
config.ack_wait_addition,
|
||||
ack_controller_connectors,
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
Arc::clone(&config.ack_key),
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
config.average_message_sending_delay,
|
||||
sent_notifier_tx,
|
||||
mix_sender,
|
||||
real_message_receiver,
|
||||
rng,
|
||||
config.self_recipient,
|
||||
topology_access,
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
out_queue_control: Some(out_queue_control),
|
||||
ack_control: Some(ack_control),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut out_queue_control = self.out_queue_control.take().unwrap();
|
||||
let mut ack_control = self.ack_control.take().unwrap();
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let out_queue_control_fut = tokio::spawn(async move {
|
||||
out_queue_control.run_out_queue_control().await;
|
||||
error!("The out queue controller has finished execution!");
|
||||
out_queue_control
|
||||
});
|
||||
let ack_control_fut = tokio::spawn(async move {
|
||||
ack_control.run().await;
|
||||
error!("The acknowledgement controller has finished execution!");
|
||||
ack_control
|
||||
});
|
||||
|
||||
// technically we don't have to bring `RealMessagesController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.out_queue_control = Some(out_queue_control_fut.await.unwrap());
|
||||
self.ack_control = Some(ack_control_fut.await.unwrap());
|
||||
}
|
||||
|
||||
// &Handle is only passed for consistency sake with other client modules, but I think
|
||||
// when we get to refactoring, we should apply gateway approach and make it implicit
|
||||
pub(super) fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
|
||||
use crate::client::real_messages_control::acknowlegement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::{clients::Recipient, nodes::NymNodeRoutingAddress};
|
||||
use nymsphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use nymsphinx::SphinxPacket;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time;
|
||||
|
||||
pub(crate) struct OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Average delay between sending subsequent packets.
|
||||
average_message_sending_delay: Duration,
|
||||
|
||||
/// Channel used for notifying of a real packet being sent out. Used to start up retransmission timer.
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: time::Delay,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: MixMessageSender,
|
||||
|
||||
/// Channel used for receiving real, prepared, messages that must be first sufficiently delayed
|
||||
/// before being sent out into the network.
|
||||
real_receiver: RealMessageReceiver,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor,
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessage {
|
||||
first_hop_address: NymNodeRoutingAddress,
|
||||
packet: SphinxPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
}
|
||||
|
||||
impl RealMessage {
|
||||
pub(crate) fn new(
|
||||
first_hop_address: NymNodeRoutingAddress,
|
||||
packet: SphinxPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
) -> Self {
|
||||
RealMessage {
|
||||
first_hop_address,
|
||||
packet,
|
||||
fragment_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
|
||||
// after sufficient delay
|
||||
pub(crate) type RealMessageSender = mpsc::UnboundedSender<RealMessage>;
|
||||
type RealMessageReceiver = mpsc::UnboundedReceiver<RealMessage>;
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
Real(RealMessage),
|
||||
}
|
||||
|
||||
impl<R> Stream for OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.average_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there's an actual message - return it
|
||||
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
mix_tx: MixMessageSender,
|
||||
real_receiver: RealMessageReceiver,
|
||||
rng: R,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
sent_notifier,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
real_receiver,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_message = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
let topology_ref_option = topology_permit.try_get_valid_topology_ref(
|
||||
&self.our_full_destination,
|
||||
Some(&self.our_full_destination),
|
||||
);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!(
|
||||
"No valid topology detected - won't send any loop cover message this time"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&*self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
MixMessage::new(cover_message.0, cover_message.1)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
// well technically the message was not sent just yet, but now it's up to internal
|
||||
// queues and client load rather than the required delay. So realistically we can treat
|
||||
// whatever is about to happen as negligible additional delay.
|
||||
self.sent_notifier
|
||||
.unbounded_send(real_message.fragment_id)
|
||||
.unwrap();
|
||||
MixMessage::new(real_message.first_hop_address, real_message.packet)
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx.unbounded_send(next_message).unwrap();
|
||||
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
// ready and hence was immediately re-scheduled causing other tasks to be starved;
|
||||
// yield makes it go back the scheduling queue regardless of its value availability
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_out_queue_control(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = time::delay_for(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.average_message_sending_delay,
|
||||
));
|
||||
|
||||
info!("Starting out queue controller...");
|
||||
while let Some(next_message) = self.next().await {
|
||||
self.on_message(next_message).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crypto::asymmetric::encryption;
|
||||
use crypto::symmetric::stream_cipher;
|
||||
use crypto::Digest;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::MixnetMessageReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SURBEncryptionKey};
|
||||
use nymsphinx::params::{ReplySURBEncryptionAlgorithm, ReplySURBKeyDigestAlgorithm};
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
|
||||
pub(crate) type ReceivedBufferRequestSender = mpsc::UnboundedSender<ReceivedBufferMessage>;
|
||||
pub(crate) type ReceivedBufferRequestReceiver = mpsc::UnboundedReceiver<ReceivedBufferMessage>;
|
||||
|
||||
// The channel set for the above
|
||||
pub(crate) type ReconstructedMessagesSender = mpsc::UnboundedSender<Vec<ReconstructedMessage>>;
|
||||
pub(crate) type ReconstructedMessagesReceiver = mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>;
|
||||
|
||||
struct ReceivedMessagesBufferInner {
|
||||
messages: Vec<ReconstructedMessage>,
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
// TODO: looking how it 'looks' here, perhaps `MessageReceiver` should be renamed to something
|
||||
// else instead.
|
||||
message_receiver: MessageReceiver,
|
||||
message_sender: Option<ReconstructedMessagesSender>,
|
||||
|
||||
// TODO: this will get cleared upon re-running the client
|
||||
// but perhaps it should be changed to include timestamps of when the message was reconstructed
|
||||
// and every now and then remove ids older than X
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferInner {
|
||||
fn process_received_fragment(&mut self, raw_fragment: Vec<u8>) -> Option<ReconstructedMessage> {
|
||||
let fragment_data = match self
|
||||
.message_receiver
|
||||
.recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment)
|
||||
{
|
||||
Err(e) => {
|
||||
warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
return None;
|
||||
}
|
||||
Ok(frag_data) => frag_data,
|
||||
};
|
||||
|
||||
if nymsphinx::cover::is_cover(&fragment_data) {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
return None;
|
||||
}
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(&fragment_data) {
|
||||
Err(e) => {
|
||||
warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
return None;
|
||||
}
|
||||
|
||||
// if we returned an error the underlying message is malformed in some way
|
||||
match self.message_receiver.insert_new_fragment(fragment) {
|
||||
Err(err) => match err {
|
||||
MessageRecoveryError::MalformedReconstructedMessage(message_sets) => {
|
||||
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
|
||||
for set_id in message_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
error!("Reconstructed another message containing already used set id!")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => unreachable!(
|
||||
"no other error kind should have been returned here! If so, it's a bug!"
|
||||
),
|
||||
},
|
||||
Ok(reconstruction_result) => match reconstruction_result {
|
||||
Some((reconstructed_message, used_sets)) => {
|
||||
for set_id in used_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
error!("Reconstructed another message containing already used set id!")
|
||||
}
|
||||
}
|
||||
Some(reconstructed_message)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Note: you should NEVER create more than a single instance of this using 'new()'.
|
||||
// You should always use .clone() to create additional instances
|
||||
struct ReceivedMessagesBuffer {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner>>,
|
||||
|
||||
/// Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back.
|
||||
// There's no need to put it behind a Mutex since it's already properly concurrent
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBuffer {
|
||||
fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
messages: Vec::new(),
|
||||
local_encryption_keypair,
|
||||
message_receiver: MessageReceiver::new(),
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
})),
|
||||
reply_key_storage,
|
||||
}
|
||||
}
|
||||
|
||||
async fn disconnect_sender(&mut self) {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if guard.message_sender.is_none() {
|
||||
// in theory we could just ignore it, but that situation should have never happened
|
||||
// in the first place, so this way we at least know we have an important bug to fix
|
||||
panic!("trying to disconnect non-existent sender!")
|
||||
}
|
||||
guard.message_sender = None;
|
||||
}
|
||||
|
||||
async fn connect_sender(&mut self, sender: ReconstructedMessagesSender) {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if guard.message_sender.is_some() {
|
||||
// in theory we could just ignore it, but that situation should have never happened
|
||||
// in the first place, so this way we at least know we have an important bug to fix
|
||||
panic!("trying overwrite an existing sender!")
|
||||
}
|
||||
|
||||
// while we're at it, also empty the buffer if we happened to receive anything while
|
||||
// no sender was connected
|
||||
let stored_messages = std::mem::replace(&mut guard.messages, Vec::new());
|
||||
if !stored_messages.is_empty() {
|
||||
if let Err(err) = sender.unbounded_send(stored_messages) {
|
||||
error!(
|
||||
"The sender channel we just received is already invalidated - {:?}",
|
||||
err
|
||||
);
|
||||
// put the values back to the buffer
|
||||
// the returned error has two fields: err: SendError and val: T,
|
||||
// where val is the value that was failed to get sent;
|
||||
// it's returned by the `into_inner` call
|
||||
guard.messages = err.into_inner();
|
||||
return;
|
||||
}
|
||||
}
|
||||
guard.message_sender = Some(sender);
|
||||
}
|
||||
|
||||
async fn add_reconstructed_messages(&mut self, msgs: Vec<ReconstructedMessage>) {
|
||||
debug!("Adding {:?} new messages to the buffer!", msgs.len());
|
||||
trace!("Adding new messages to the buffer! {:?}", msgs);
|
||||
self.inner.lock().await.messages.extend(msgs)
|
||||
}
|
||||
|
||||
fn process_received_reply(
|
||||
reply_ciphertext: &[u8],
|
||||
reply_key: SURBEncryptionKey,
|
||||
) -> Option<ReconstructedMessage> {
|
||||
let zero_iv = stream_cipher::zero_iv::<ReplySURBEncryptionAlgorithm>();
|
||||
|
||||
let mut reply_msg = stream_cipher::decrypt::<ReplySURBEncryptionAlgorithm>(
|
||||
&reply_key.inner(),
|
||||
&zero_iv,
|
||||
reply_ciphertext,
|
||||
);
|
||||
if let Err(err) = MessageReceiver::remove_padding(&mut reply_msg) {
|
||||
warn!("Received reply had malformed padding! - {:?}", err);
|
||||
None
|
||||
} else {
|
||||
// TODO: perhaps having to say it doesn't have a surb an indication the type should be changed?
|
||||
Some(ReconstructedMessage {
|
||||
message: reply_msg,
|
||||
reply_SURB: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!(
|
||||
"Processing {:?} new message that might get added to the buffer!",
|
||||
msgs.len()
|
||||
);
|
||||
|
||||
let mut completed_messages = Vec::new();
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
|
||||
let reply_surb_digest_size = ReplySURBKeyDigestAlgorithm::output_size();
|
||||
|
||||
// first check if this is a reply or a chunked message
|
||||
// TODO: verify with @AP if this way of doing it is safe or whether it could
|
||||
// cause some attacks due to, I don't know, stupid edge case collisions?
|
||||
// Update: this DOES introduce a possible leakage: https://github.com/nymtech/nym/issues/296
|
||||
for msg in msgs {
|
||||
let possible_key_digest =
|
||||
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
|
||||
|
||||
// check first `HasherOutputSize` bytes if they correspond to known encryption key
|
||||
// if yes - this is a reply message
|
||||
|
||||
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
|
||||
// are doing a disk operation every single received fragment
|
||||
if let Some(reply_encryption_key) = self
|
||||
.reply_key_storage
|
||||
.get_and_remove_encryption_key(possible_key_digest)
|
||||
.expect("storage operation failed!")
|
||||
{
|
||||
if let Some(completed_message) = Self::process_received_reply(
|
||||
&msg[reply_surb_digest_size..],
|
||||
reply_encryption_key,
|
||||
) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
} else {
|
||||
// otherwise - it's a 'normal' message
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !completed_messages.is_empty() {
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(completed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {:?})", err);
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
inner_guard.message_sender = None;
|
||||
drop(inner_guard);
|
||||
self.add_reconstructed_messages(err.into_inner()).await;
|
||||
}
|
||||
} else {
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
drop(inner_guard);
|
||||
trace!("No sender available - buffering reconstructed messages");
|
||||
self.add_reconstructed_messages(completed_messages).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ReceivedBufferMessage {
|
||||
// Signals a websocket connection (or a native implementation) was established and we should stop buffering messages,
|
||||
// and instead send them directly to the received channel
|
||||
ReceiverAnnounce(ReconstructedMessagesSender),
|
||||
|
||||
// Explicit signal that Receiver connection will no longer accept messages
|
||||
ReceiverDisconnect,
|
||||
}
|
||||
|
||||
struct RequestReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
}
|
||||
|
||||
impl RequestReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(request) = self.query_receiver.next().await {
|
||||
match request {
|
||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
||||
self.received_buffer.connect_sender(sender).await;
|
||||
}
|
||||
ReceivedBufferMessage::ReceiverDisconnect => {
|
||||
self.received_buffer.disconnect_sender().await
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct FragmentedMessageReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
}
|
||||
|
||||
impl FragmentedMessageReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
mixnet_packet_receiver,
|
||||
}
|
||||
}
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver,
|
||||
request_receiver: RequestReceiver,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferController {
|
||||
pub(crate) fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let received_buffer =
|
||||
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
mixnet_packet_receiver,
|
||||
),
|
||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, handle: &Handle) {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.fragmented_message_receiver.start(handle);
|
||||
self.request_receiver.start(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{
|
||||
encryption_key::EncryptionKeyDigest, encryption_key::Unsigned, SURBEncryptionKey,
|
||||
SURBEncryptionKeySize,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReplyKeyStorageError {
|
||||
DbReadError(sled::Error),
|
||||
DbWriteError(sled::Error),
|
||||
DbOpenError(sled::Error),
|
||||
}
|
||||
|
||||
/// Permanent storage for keys in all sent [`ReplySURB`]
|
||||
///
|
||||
/// Each sent out [`ReplySURB`] has a new key associated with it that is going to be used for
|
||||
/// payload encryption. In order to decrypt whatever reply we receive, we need to know which
|
||||
/// key to use for that purpose. We do it based on received `H(t)` which has to be included
|
||||
/// with each reply.
|
||||
/// Moreover, there is no restriction when the [`ReplySURB`] might get used so we need to
|
||||
/// have a permanent storage for all the keys that we might ever see in the future.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyKeyStorage {
|
||||
db: sled::Db,
|
||||
}
|
||||
|
||||
impl ReplyKeyStorage {
|
||||
pub(crate) fn load<P: AsRef<Path>>(path: P) -> Result<Self, ReplyKeyStorageError> {
|
||||
let db = match sled::open(path) {
|
||||
Err(e) => return Err(ReplyKeyStorageError::DbOpenError(e)),
|
||||
Ok(db) => db,
|
||||
};
|
||||
|
||||
Ok(ReplyKeyStorage { db })
|
||||
}
|
||||
|
||||
fn read_encryption_key(&self, raw_key: sled::IVec) -> SURBEncryptionKey {
|
||||
let key_bytes_ref = raw_key.as_ref();
|
||||
// if this fails it means we have some database corruption and we
|
||||
// absolutely can't continue
|
||||
|
||||
if key_bytes_ref.len() != SURBEncryptionKeySize::to_usize() {
|
||||
error!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
panic!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
}
|
||||
|
||||
// this can only fail if the bytes have invalid length but we already asserted it
|
||||
SURBEncryptionKey::try_from_bytes(key_bytes_ref).unwrap()
|
||||
}
|
||||
|
||||
// TOOD: perhaps we could also store some part of original message here too?
|
||||
pub(crate) fn insert_encryption_key(
|
||||
&mut self,
|
||||
encryption_key: SURBEncryptionKey,
|
||||
) -> Result<(), ReplyKeyStorageError> {
|
||||
let digest = encryption_key.compute_digest();
|
||||
|
||||
let insertion_result = match self.db.insert(digest.to_vec(), encryption_key.to_bytes()) {
|
||||
Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)),
|
||||
Ok(existing_key) => {
|
||||
if existing_key.is_some() {
|
||||
panic!("HASH COLLISION DETECTED")
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: perhaps we could implement some batching mechanism to avoid frequent flushes?
|
||||
self.db.flush().unwrap();
|
||||
insertion_result
|
||||
}
|
||||
|
||||
// Once we use key once, we do not expect to use it again
|
||||
pub(crate) fn get_and_remove_encryption_key(
|
||||
&self,
|
||||
key_digest: EncryptionKeyDigest,
|
||||
) -> Result<Option<SURBEncryptionKey>, ReplyKeyStorageError> {
|
||||
let removal_result = match self.db.remove(&key_digest.to_vec()) {
|
||||
Err(e) => Err(ReplyKeyStorageError::DbReadError(e)),
|
||||
Ok(existing_key) => {
|
||||
Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key)))
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: not sure how to feel about flushing it every single time here...
|
||||
// same with insertion
|
||||
self.db.flush().unwrap();
|
||||
removal_result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::built_info;
|
||||
use directory_client::DirectoryClient;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::NymTopology;
|
||||
|
||||
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TopologyAccessorInner(Option<NymTopology>);
|
||||
|
||||
impl AsRef<Option<NymTopology>> for TopologyAccessorInner {
|
||||
fn as_ref(&self) -> &Option<NymTopology> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TopologyAccessorInner {
|
||||
fn new() -> Self {
|
||||
TopologyAccessorInner(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, new: Option<NymTopology>) {
|
||||
self.0 = new;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, TopologyAccessorInner>,
|
||||
}
|
||||
|
||||
impl<'a> Deref for TopologyReadPermit<'a> {
|
||||
type Target = TopologyAccessorInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.permit
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TopologyReadPermit<'a> {
|
||||
/// Using provided topology read permit, tries to get an immutable reference to the underlying
|
||||
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
|
||||
pub(super) fn try_get_valid_topology_ref(
|
||||
&'a self,
|
||||
ack_recipient: &Recipient,
|
||||
packet_recipient: Option<&Recipient>,
|
||||
) -> Option<&'a NymTopology> {
|
||||
// Note: implicit deref with Deref for TopologyReadPermit is happening here
|
||||
let topology_ref_option = self.permit.as_ref();
|
||||
match topology_ref_option {
|
||||
None => None,
|
||||
Some(topology_ref) => {
|
||||
// see if it's possible to route the packet to both gateways
|
||||
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|
||||
|| !topology_ref.gateway_exists(&ack_recipient.gateway())
|
||||
|| if let Some(packet_recipient) = packet_recipient {
|
||||
!topology_ref.gateway_exists(&packet_recipient.gateway())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(topology_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<RwLockReadGuard<'a, TopologyAccessorInner>> for TopologyReadPermit<'a> {
|
||||
fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner>) -> Self {
|
||||
TopologyReadPermit {
|
||||
permit: read_permit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TopologyAccessor {
|
||||
// `RwLock` *seems to* be the better approach for this as write access is only requested every
|
||||
// few seconds, while reads are needed every single packet generated.
|
||||
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
|
||||
// approach than a `Mutex`
|
||||
inner: Arc<RwLock<TopologyAccessorInner>>,
|
||||
}
|
||||
|
||||
impl TopologyAccessor {
|
||||
pub(crate) fn new() -> Self {
|
||||
TopologyAccessor {
|
||||
inner: Arc::new(RwLock::new(TopologyAccessorInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
|
||||
self.inner.read().await.into()
|
||||
}
|
||||
|
||||
async fn update_global_topology(&mut self, new_topology: Option<NymTopology>) {
|
||||
self.inner.write().await.update(new_topology);
|
||||
}
|
||||
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because health checker is disabled due to required changes)
|
||||
pub(crate) async fn is_routable(&self) -> bool {
|
||||
match &self.inner.read().await.0 {
|
||||
None => false,
|
||||
Some(ref topology) => topology.can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TopologyRefresherConfig {
|
||||
directory_server: String,
|
||||
refresh_rate: time::Duration,
|
||||
}
|
||||
|
||||
impl TopologyRefresherConfig {
|
||||
pub(crate) fn new(directory_server: String, refresh_rate: time::Duration) -> Self {
|
||||
TopologyRefresherConfig {
|
||||
directory_server,
|
||||
refresh_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TopologyRefresher {
|
||||
directory_client: directory_client::Client,
|
||||
topology_accessor: TopologyAccessor,
|
||||
refresh_rate: Duration,
|
||||
}
|
||||
|
||||
impl TopologyRefresher {
|
||||
pub(crate) fn new_directory_client(
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
) -> Self {
|
||||
let directory_client_config = directory_client::Config::new(cfg.directory_server);
|
||||
let directory_client = directory_client::Client::new(directory_client_config);
|
||||
|
||||
TopologyRefresher {
|
||||
directory_client,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
|
||||
match self.directory_client.get_topology().await {
|
||||
Err(err) => {
|
||||
error!("failed to get network topology! - {:?}", err);
|
||||
None
|
||||
}
|
||||
Ok(topology) => {
|
||||
let nym_topology: NymTopology = topology.try_into().ok()?;
|
||||
Some(nym_topology.filter_system_version(built_info::PKG_VERSION))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn refresh(&mut self) {
|
||||
trace!("Refreshing the topology");
|
||||
let new_topology = self.get_current_compatible_topology().await;
|
||||
|
||||
self.topology_accessor
|
||||
.update_global_topology(new_topology)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn is_topology_routable(&self) -> bool {
|
||||
self.topology_accessor.is_routable().await
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
loop {
|
||||
tokio::time::delay_for(self.refresh_rate).await;
|
||||
self.refresh().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::built_info;
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use crate::commands::override_config;
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use directory_client::DirectoryClient;
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use rand::rngs::OsRng;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{gateway, NymTopology};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to create config for.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we have preference to connect to. If left empty, a random gateway will be chosen.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the client is getting topology from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on in all subsequent runs")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("fastmode")
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_gateway_registration(
|
||||
gateways: &[gateway::Node],
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Option<(String, String, SharedKeys)> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
for gateway in gateways {
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
url::Url::parse(&gateway.client_listener).unwrap(),
|
||||
gateway.identity_key,
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
if gateway_client.establish_connection().await.is_ok() {
|
||||
if let Ok(shared_key) = gateway_client.register().await {
|
||||
if let Err(err) = gateway_client.close_connection().await {
|
||||
eprintln!("Error while closing connection to the gateway! - {:?}", err);
|
||||
continue;
|
||||
} else {
|
||||
return Some((
|
||||
gateway.identity_key.to_base58_string(),
|
||||
gateway.client_listener.clone(),
|
||||
shared_key,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn choose_gateway(
|
||||
directory_server: String,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> (String, String, SharedKeys) {
|
||||
let directory_client_config = directory_client::Config::new(directory_server.clone());
|
||||
let directory_client = directory_client::Client::new(directory_client_config);
|
||||
let topology = directory_client.get_topology().await.unwrap();
|
||||
let nym_topology: NymTopology = topology.try_into().expect("Invalid topology data!");
|
||||
|
||||
let version_filtered_topology = nym_topology.filter_system_version(built_info::PKG_VERSION);
|
||||
// don't care about health of the networks as mixes can go up and down any time,
|
||||
// but DO care about gateways
|
||||
let gateways = version_filtered_topology.gateways();
|
||||
|
||||
// try to perform registration so that we wouldn't need to do it at startup
|
||||
// + at the same time we'll know if we can actually talk with that gateway
|
||||
let registration_result = try_gateway_registration(gateways, our_identity).await;
|
||||
match registration_result {
|
||||
None => {
|
||||
// while technically there's no issue client-side, it will be impossible to execute
|
||||
// `nym-client run` as no gateway is available so it might be best to not finalize
|
||||
// the init and rely on users trying to init another time?
|
||||
panic!(
|
||||
"Currently there are no valid gateways available on the network ({}). \
|
||||
Please try to run `init` again at later time or change your directory server",
|
||||
directory_server
|
||||
)
|
||||
}
|
||||
Some((gateway_id, gateway_listener, shared_key)) => {
|
||||
(gateway_id, gateway_listener, shared_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_gateway_listener(directory_server: String, gateway_identity: &str) -> Option<String> {
|
||||
let directory_client_config = directory_client::Config::new(directory_server);
|
||||
let directory_client = directory_client::Client::new(directory_client_config);
|
||||
let topology = directory_client.get_topology().await.unwrap();
|
||||
|
||||
// technically we don't need to do conversion here, but let's be consistent
|
||||
let nym_topology: NymTopology = topology.try_into().ok()?;
|
||||
let gateways = nym_topology.gateways();
|
||||
|
||||
for gateway in gateways {
|
||||
if gateway.identity_key.to_base58_string() == gateway_identity {
|
||||
return Some(gateway.client_listener.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
let mut config = crate::config::Config::new(id);
|
||||
let mut rng = OsRng;
|
||||
|
||||
config = override_config(config, matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config = config.set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
// create identity, encryption and ack keys.
|
||||
let mut key_manager = KeyManager::new(&mut rng);
|
||||
|
||||
// if there is no gateway chosen, get a random-ish one from the topology
|
||||
if config.get_gateway_id().is_empty() {
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (gateway_id, gateway_listener, shared_key) = rt.block_on(choose_gateway(
|
||||
config.get_directory_server(),
|
||||
key_manager.identity_keypair(),
|
||||
));
|
||||
|
||||
config = config
|
||||
.with_gateway_id(gateway_id)
|
||||
.with_gateway_listener(gateway_listener);
|
||||
|
||||
key_manager.insert_gateway_shared_key(shared_key)
|
||||
}
|
||||
|
||||
// we specified our gateway but don't know its physical address
|
||||
if config.get_gateway_listener().is_empty() {
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let gateway_listener = rt
|
||||
.block_on(get_gateway_listener(
|
||||
config.get_directory_server(),
|
||||
&config.get_gateway_id(),
|
||||
))
|
||||
.expect("No gateway with provided id exists!");
|
||||
|
||||
config = config.with_gateway_listener(gateway_listener);
|
||||
}
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(&config);
|
||||
key_manager
|
||||
.store_keys(&pathfinder)
|
||||
.expect("Failed to generated keys");
|
||||
println!("Saved all generated keys");
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
|
||||
println!(
|
||||
"Unless overridden in all `nym-client run` we will be talking to the following gateway: {}...",
|
||||
config.get_gateway_id(),
|
||||
);
|
||||
|
||||
println!("Client configuration completed.\n\n\n")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::{Config, SocketType};
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub mod init;
|
||||
pub mod run;
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
|
||||
if let Some(directory) = matches.value_of("directory") {
|
||||
config = config.with_custom_directory(directory);
|
||||
}
|
||||
|
||||
if let Some(gateway_id) = matches.value_of("gateway") {
|
||||
config = config.with_gateway_id(gateway_id);
|
||||
}
|
||||
|
||||
if matches.is_present("disable-socket") {
|
||||
config = config.with_socket(SocketType::None);
|
||||
}
|
||||
|
||||
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
|
||||
if let Err(err) = port {
|
||||
// if port was overridden, it must be parsable
|
||||
panic!("Invalid port value provided - {:?}", err);
|
||||
}
|
||||
config = config.with_port(port.unwrap());
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
use crate::config::Config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
App::new("run")
|
||||
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to run.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
// the rest of arguments are optional, they are used to override settings in config file
|
||||
.arg(Arg::with_name("config")
|
||||
.long("config")
|
||||
.help("Custom path to the nym-mixnet-client configuration file")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the client is getting topology from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on")
|
||||
.takes_value(true)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
|
||||
let mut config =
|
||||
Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id))
|
||||
.expect("Failed to load config file");
|
||||
|
||||
config = override_config(config, matches);
|
||||
|
||||
NymClient::new(config).run_forever();
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::template::config_template;
|
||||
use config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::time;
|
||||
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
// 'CLIENT'
|
||||
const DEFAULT_LISTENING_PORT: u16 = 1977;
|
||||
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
const DEFAULT_ACK_WAIT_ADDITION: u64 = 800;
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; // 1s
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 500; // 0.5s
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 200; // 0.2s
|
||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000; // 30s
|
||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; // 5s
|
||||
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; // 1.5s
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum SocketType {
|
||||
WebSocket,
|
||||
None,
|
||||
}
|
||||
|
||||
impl SocketType {
|
||||
pub fn from_string<S: Into<String>>(val: S) -> Self {
|
||||
let mut upper = val.into();
|
||||
upper.make_ascii_uppercase();
|
||||
match upper.as_ref() {
|
||||
"WEBSOCKET" | "WS" => SocketType::WebSocket,
|
||||
_ => SocketType::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
client: Client,
|
||||
socket: Socket,
|
||||
|
||||
#[serde(default)]
|
||||
logging: Logging,
|
||||
#[serde(default)]
|
||||
debug: Debug,
|
||||
}
|
||||
|
||||
impl NymConfig for Config {
|
||||
fn template() -> &'static str {
|
||||
config_template()
|
||||
}
|
||||
|
||||
fn config_file_name() -> String {
|
||||
"config.toml".to_string()
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.client.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.client
|
||||
.nym_root_directory
|
||||
.join(&self.client.id)
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.client
|
||||
.nym_root_directory
|
||||
.join(&self.client.id)
|
||||
.join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||
Config::default().with_id(id)
|
||||
}
|
||||
|
||||
// builder methods
|
||||
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
// identity key setting
|
||||
if self.client.private_identity_key_file.as_os_str().is_empty() {
|
||||
self.client.private_identity_key_file =
|
||||
self::Client::default_private_identity_key_file(&id);
|
||||
}
|
||||
if self.client.public_identity_key_file.as_os_str().is_empty() {
|
||||
self.client.public_identity_key_file =
|
||||
self::Client::default_public_identity_key_file(&id);
|
||||
}
|
||||
|
||||
// encryption key setting
|
||||
if self
|
||||
.client
|
||||
.private_encryption_key_file
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
self.client.private_encryption_key_file =
|
||||
self::Client::default_private_encryption_key_file(&id);
|
||||
}
|
||||
if self
|
||||
.client
|
||||
.public_encryption_key_file
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
self.client.public_encryption_key_file =
|
||||
self::Client::default_public_encryption_key_file(&id);
|
||||
}
|
||||
|
||||
// shared gateway key setting
|
||||
if self.client.gateway_shared_key_file.as_os_str().is_empty() {
|
||||
self.client.gateway_shared_key_file =
|
||||
self::Client::default_gateway_shared_key_file(&id);
|
||||
}
|
||||
|
||||
// ack key setting
|
||||
if self.client.ack_key_file.as_os_str().is_empty() {
|
||||
self.client.ack_key_file = self::Client::default_ack_key_file(&id);
|
||||
}
|
||||
|
||||
if self
|
||||
.client
|
||||
.reply_encryption_key_store_path
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
self.client.reply_encryption_key_store_path =
|
||||
self::Client::default_reply_encryption_key_store_path(&id);
|
||||
}
|
||||
|
||||
self.client.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_id<S: Into<String>>(mut self, id: S) -> Self {
|
||||
self.client.gateway_id = id.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_listener<S: Into<String>>(mut self, gateway_listener: S) -> Self {
|
||||
self.client.gateway_listener = gateway_listener.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_directory<S: Into<String>>(mut self, directory_server: S) -> Self {
|
||||
self.client.directory_server = directory_server.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_socket(mut self, socket_type: SocketType) -> Self {
|
||||
self.socket.socket_type = socket_type;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socket.listening_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_high_default_traffic_volume(mut self) -> Self {
|
||||
self.debug.average_packet_delay = 10;
|
||||
self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s
|
||||
self.debug.message_sending_average_delay = 5; // 200 "real" messages / s
|
||||
self
|
||||
}
|
||||
|
||||
// getters
|
||||
pub fn get_config_file_save_location(&self) -> PathBuf {
|
||||
self.config_directory().join(Self::config_file_name())
|
||||
}
|
||||
|
||||
pub fn get_private_identity_key_file(&self) -> PathBuf {
|
||||
self.client.private_identity_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_public_identity_key_file(&self) -> PathBuf {
|
||||
self.client.public_identity_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_private_encryption_key_file(&self) -> PathBuf {
|
||||
self.client.private_encryption_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_public_encryption_key_file(&self) -> PathBuf {
|
||||
self.client.public_encryption_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_shared_key_file(&self) -> PathBuf {
|
||||
self.client.gateway_shared_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_reply_encryption_key_store_path(&self) -> PathBuf {
|
||||
self.client.reply_encryption_key_store_path.clone()
|
||||
}
|
||||
|
||||
pub fn get_ack_key_file(&self) -> PathBuf {
|
||||
self.client.ack_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_directory_server(&self) -> String {
|
||||
self.client.directory_server.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_id(&self) -> String {
|
||||
self.client.gateway_id.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_listener(&self) -> String {
|
||||
self.client.gateway_listener.clone()
|
||||
}
|
||||
|
||||
pub fn get_socket_type(&self) -> SocketType {
|
||||
self.socket.socket_type
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
|
||||
// Debug getters
|
||||
pub fn get_average_packet_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.average_packet_delay)
|
||||
}
|
||||
|
||||
pub fn get_average_ack_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.average_ack_delay)
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_multiplier(&self) -> f64 {
|
||||
self.debug.ack_wait_multiplier
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_addition(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.ack_wait_addition)
|
||||
}
|
||||
|
||||
pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay)
|
||||
}
|
||||
|
||||
pub fn get_message_sending_average_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.message_sending_average_delay)
|
||||
}
|
||||
|
||||
pub fn get_gateway_response_timeout(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.gateway_response_timeout)
|
||||
}
|
||||
|
||||
pub fn get_topology_refresh_rate(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.topology_refresh_rate)
|
||||
}
|
||||
|
||||
pub fn get_topology_resolution_timeout(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.topology_resolution_timeout)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Client {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
id: String,
|
||||
|
||||
/// URL to the directory server.
|
||||
directory_server: String,
|
||||
|
||||
/// Path to file containing private identity key.
|
||||
private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
public_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing private encryption key.
|
||||
private_encryption_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public encryption key.
|
||||
public_encryption_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing shared key derived with the specified gateway that is used
|
||||
/// for all communication with it.
|
||||
gateway_shared_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file: PathBuf,
|
||||
|
||||
/// Full path to file containing reply encryption keys of all reply-SURBs we have ever
|
||||
/// sent but not received back.
|
||||
reply_encryption_key_store_path: PathBuf,
|
||||
|
||||
/// gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
gateway_id: String,
|
||||
|
||||
/// Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener: String,
|
||||
|
||||
/// nym_home_directory specifies absolute path to the home nym Clients directory.
|
||||
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||
nym_root_directory: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
// there must be explicit checks for whether id is not empty later
|
||||
Client {
|
||||
id: "".to_string(),
|
||||
directory_server: DEFAULT_DIRECTORY_SERVER.to_string(),
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_encryption_key_file: Default::default(),
|
||||
public_encryption_key_file: Default::default(),
|
||||
gateway_shared_key_file: Default::default(),
|
||||
ack_key_file: Default::default(),
|
||||
reply_encryption_key_store_path: Default::default(),
|
||||
gateway_id: "".to_string(),
|
||||
gateway_listener: "".to_string(),
|
||||
nym_root_directory: Config::default_root_directory(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn default_private_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_identity.pem")
|
||||
}
|
||||
|
||||
fn default_public_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_identity.pem")
|
||||
}
|
||||
|
||||
fn default_private_encryption_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_encryption.pem")
|
||||
}
|
||||
|
||||
fn default_public_encryption_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_encryption.pem")
|
||||
}
|
||||
|
||||
fn default_gateway_shared_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("gateway_shared.pem")
|
||||
}
|
||||
|
||||
fn default_ack_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("ack_key.pem")
|
||||
}
|
||||
|
||||
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("reply_key_store")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
socket_type: SocketType,
|
||||
listening_port: u16,
|
||||
}
|
||||
|
||||
impl Default for Socket {
|
||||
fn default() -> Self {
|
||||
Socket {
|
||||
socket_type: SocketType::WebSocket,
|
||||
listening_port: DEFAULT_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Logging {}
|
||||
|
||||
impl Default for Logging {
|
||||
fn default() -> Self {
|
||||
Logging {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Debug {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
average_packet_delay: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
average_ack_delay: u64,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
ack_wait_addition: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
loop_cover_traffic_average_delay: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
message_sending_average_delay: u64,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
gateway_response_timeout: u64,
|
||||
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
topology_refresh_rate: u64,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
topology_resolution_timeout: u64,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod client_config {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn after_saving_default_config_the_loaded_one_is_identical() {
|
||||
// need to figure out how to do something similar but without touching the disk
|
||||
// or the file system at all...
|
||||
let temp_location = tempfile::tempdir().unwrap().path().join("config.toml");
|
||||
let default_config = Config::default().with_id("foomp".to_string());
|
||||
default_config
|
||||
.save_to_file(Some(temp_location.clone()))
|
||||
.unwrap();
|
||||
|
||||
let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap();
|
||||
|
||||
assert_eq!(default_config, loaded_config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::Config;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientKeyPathfinder {
|
||||
identity_private_key: PathBuf,
|
||||
identity_public_key: PathBuf,
|
||||
encryption_private_key: PathBuf,
|
||||
encryption_public_key: PathBuf,
|
||||
gateway_shared_key: PathBuf,
|
||||
ack_key: PathBuf,
|
||||
}
|
||||
|
||||
impl ClientKeyPathfinder {
|
||||
pub fn new(id: String) -> Self {
|
||||
let os_config_dir = dirs::config_dir().expect("no config directory known for this OS"); // grabs the OS default config dir
|
||||
let config_dir = os_config_dir.join("nym").join("clients").join(id);
|
||||
ClientKeyPathfinder {
|
||||
identity_private_key: config_dir.join("private_identity.pem"),
|
||||
identity_public_key: config_dir.join("public_identity.pem"),
|
||||
encryption_private_key: config_dir.join("public_encryption.pem"),
|
||||
encryption_public_key: config_dir.join("private_encryption.pem"),
|
||||
gateway_shared_key: config_dir.join("gateway_shared.pem"),
|
||||
ack_key: config_dir.join("ack_key.pem"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_config(config: &Config) -> Self {
|
||||
ClientKeyPathfinder {
|
||||
identity_private_key: config.get_private_identity_key_file(),
|
||||
identity_public_key: config.get_public_identity_key_file(),
|
||||
encryption_private_key: config.get_private_encryption_key_file(),
|
||||
encryption_public_key: config.get_public_encryption_key_file(),
|
||||
gateway_shared_key: config.get_gateway_shared_key_file(),
|
||||
ack_key: config.get_ack_key_file(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn private_identity_key(&self) -> &Path {
|
||||
&self.identity_private_key
|
||||
}
|
||||
|
||||
pub fn public_identity_key(&self) -> &Path {
|
||||
&self.identity_public_key
|
||||
}
|
||||
|
||||
pub fn private_encryption_key(&self) -> &Path {
|
||||
&self.encryption_private_key
|
||||
}
|
||||
|
||||
pub fn public_encryption_key(&self) -> &Path {
|
||||
&self.encryption_public_key
|
||||
}
|
||||
|
||||
pub fn gateway_shared_key(&self) -> &Path {
|
||||
&self.gateway_shared_key
|
||||
}
|
||||
|
||||
pub fn ack_key(&self) -> &Path {
|
||||
&self.ack_key
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod key_pathfinder;
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub(crate) fn config_template() -> &'static str {
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs in mod.rs.
|
||||
r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
##### main base client config options #####
|
||||
|
||||
[client]
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
|
||||
# URL to the directory server.
|
||||
directory_server = '{{ client.directory_server }}'
|
||||
|
||||
# Path to file containing private identity key.
|
||||
private_identity_key_file = '{{ client.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
public_identity_key_file = '{{ client.public_identity_key_file }}'
|
||||
|
||||
# Path to file containing private encryption key.
|
||||
private_encryption_key_file = '{{ client.private_encryption_key_file }}'
|
||||
|
||||
# Path to file containing public encryption key.
|
||||
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
|
||||
# Full path to file containing reply encryption keys of all reply-SURBs we have ever
|
||||
# sent but not received back.
|
||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_id }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_listener }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file = '{{ client.ack_key_file }}'
|
||||
|
||||
##### advanced configuration options #####
|
||||
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
[socket]
|
||||
|
||||
# allowed values are 'WebSocket' or 'None'
|
||||
socket_type = '{{ socket.socket_type }}'
|
||||
|
||||
# if applicable (for the case of 'WebSocket'), the port on which the client
|
||||
# will be listening for incoming requests
|
||||
listening_port = {{ socket.listening_port }}
|
||||
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
[logging]
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
##### debug configuration options #####
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
[debug]
|
||||
|
||||
average_packet_delay = {{ debug.average_packet_delay }}
|
||||
average_ack_delay = {{ debug.average_ack_delay }}
|
||||
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
|
||||
message_sending_average_delay = {{ debug.message_sending_average_delay }}
|
||||
|
||||
"#
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
pub mod built_info;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod socks;
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
|
||||
pub mod built_info;
|
||||
pub mod client;
|
||||
mod commands;
|
||||
pub mod config;
|
||||
pub mod socks;
|
||||
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Socks5 Proxy")
|
||||
.version(built_info::PKG_VERSION)
|
||||
.author("Nymtech")
|
||||
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: --help to see available options.\n\n"
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
format!(
|
||||
r#"
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(socks5 proxy - version {:})
|
||||
|
||||
"#,
|
||||
built_info::PKG_VERSION
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Client Authentication Methods
|
||||
pub(crate) enum AuthenticationMethods {
|
||||
/// No Authentication
|
||||
NoAuth = 0x00,
|
||||
// GssApi = 0x01,
|
||||
/// Authenticate with a username / password
|
||||
UserPass = 0x02,
|
||||
/// Cannot authenticate
|
||||
NoMethods = 0xFF,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
pub(crate) struct Authenticator {
|
||||
allowed_users: Vec<User>,
|
||||
pub auth_methods: Vec<u8>, // DH TODO: this should not be public
|
||||
}
|
||||
|
||||
impl Authenticator {
|
||||
pub(crate) fn new(auth_methods: Vec<u8>, allowed_users: Vec<User>) -> Authenticator {
|
||||
Authenticator {
|
||||
allowed_users,
|
||||
auth_methods,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if username + password pair are valid
|
||||
pub fn is_allowed(&self, user: &User) -> bool {
|
||||
if self
|
||||
.auth_methods
|
||||
.contains(&(AuthenticationMethods::UserPass as u8))
|
||||
{
|
||||
self.allowed_users.contains(user)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn constructor_works() {
|
||||
let mut auth_methods: Vec<u8> = Vec::new();
|
||||
auth_methods.push(AuthenticationMethods::NoAuth as u8);
|
||||
auth_methods.push(AuthenticationMethods::UserPass as u8);
|
||||
|
||||
let admin = User {
|
||||
username: "foo".to_string(),
|
||||
password: "bar".to_string(),
|
||||
};
|
||||
let allowed_users = vec![admin.clone()];
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
|
||||
assert!(authenticator.allowed_users.contains(&admin));
|
||||
assert!(authenticator
|
||||
.auth_methods
|
||||
.contains(&(AuthenticationMethods::NoAuth as u8)));
|
||||
assert!(authenticator
|
||||
.auth_methods
|
||||
.contains(&(AuthenticationMethods::UserPass as u8)));
|
||||
}
|
||||
|
||||
mod without_user_and_password_auth_enabled {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn user_pass_authentication_fails() {
|
||||
let auth_methods: Vec<u8> = Vec::new(); // it's empty
|
||||
|
||||
let admin = User {
|
||||
username: "foo".to_string(),
|
||||
password: "bar".to_string(),
|
||||
};
|
||||
|
||||
let allowed_users = vec![admin.clone()];
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
|
||||
assert!(!authenticator.is_allowed(&admin));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod with_user_and_password_auth_enabled {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn allowed_user_passes_authentication_check() {
|
||||
let mut auth_methods: Vec<u8> = Vec::new();
|
||||
auth_methods.push(AuthenticationMethods::UserPass as u8);
|
||||
|
||||
let admin = User {
|
||||
username: "foo".to_string(),
|
||||
password: "bar".to_string(),
|
||||
};
|
||||
|
||||
let allowed_users = vec![admin.clone()];
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
|
||||
assert!(authenticator.is_allowed(&admin));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallowed_user_fails_authentication_check() {
|
||||
let mut auth_methods: Vec<u8> = Vec::new();
|
||||
auth_methods.push(AuthenticationMethods::UserPass as u8);
|
||||
|
||||
let bad_user = User {
|
||||
username: "ashy".to_string(),
|
||||
password: "slashy".to_string(),
|
||||
};
|
||||
|
||||
let allowed_users = Vec::new();
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
|
||||
assert!(!authenticator.is_allowed(&bad_user));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use rand::RngCore;
|
||||
use std::{collections::HashMap, net::Shutdown, sync::Arc};
|
||||
|
||||
use log::*;
|
||||
use tokio::prelude::*;
|
||||
use tokio::{self, net::TcpStream};
|
||||
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
|
||||
use crate::client::inbound_messages::InputMessage;
|
||||
use crate::client::inbound_messages::InputMessageSender;
|
||||
use futures::{channel::oneshot, lock::Mutex};
|
||||
|
||||
use super::authentication::{AuthenticationMethods, Authenticator, User};
|
||||
use super::request::{SocksCommand, SocksRequest};
|
||||
use super::types::{ResponseCode, SocksProxyError};
|
||||
use super::{RESERVED, SOCKS_VERSION};
|
||||
use simple_socks5_requests::{ConnectionId, Request};
|
||||
|
||||
/// A client connecting to the Socks proxy server, because
|
||||
/// it wants to make a Nym-protected outbound request. Typically, this is
|
||||
/// something like e.g. a wallet app running on your laptop connecting to
|
||||
/// SphinxSocksServer.
|
||||
pub(crate) struct SocksClient {
|
||||
active_streams: ActiveStreams,
|
||||
stream: TcpStream,
|
||||
auth_nmethods: u8,
|
||||
authenticator: Authenticator,
|
||||
socks_version: u8,
|
||||
input_sender: InputMessageSender,
|
||||
connection_id: ConnectionId,
|
||||
service_provider: Recipient,
|
||||
}
|
||||
|
||||
type StreamResponseSender = oneshot::Sender<Vec<u8>>;
|
||||
|
||||
pub(crate) type ActiveStreams = Arc<Mutex<HashMap<ConnectionId, StreamResponseSender>>>;
|
||||
|
||||
impl Drop for SocksClient {
|
||||
fn drop(&mut self) {
|
||||
println!("socksclient is going out of scope - the stream is getting dropped!")
|
||||
}
|
||||
}
|
||||
|
||||
impl SocksClient {
|
||||
/// Create a new SOCKClient
|
||||
pub fn new(
|
||||
stream: TcpStream,
|
||||
authenticator: Authenticator,
|
||||
input_sender: InputMessageSender,
|
||||
service_provider: Recipient,
|
||||
active_streams: ActiveStreams,
|
||||
) -> Self {
|
||||
let connection_id = Self::generate_random();
|
||||
SocksClient {
|
||||
active_streams,
|
||||
connection_id,
|
||||
stream,
|
||||
auth_nmethods: 0,
|
||||
socks_version: 0,
|
||||
authenticator,
|
||||
input_sender,
|
||||
service_provider,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_random() -> ConnectionId {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
// Send an error back to the client
|
||||
pub async fn error(&mut self, r: ResponseCode) -> Result<(), SocksProxyError> {
|
||||
self.stream.write_all(&[5, r as u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shutdown the TcpStream to the client and end the session
|
||||
pub fn shutdown(&mut self) -> Result<(), SocksProxyError> {
|
||||
println!("client is shutting down its TCP stream");
|
||||
self.stream.shutdown(Shutdown::Both)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initializes the new client, checking that the correct Socks version (5)
|
||||
/// is in use and that the client is authenticated, then runs the request.
|
||||
pub async fn run(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("New connection from: {}", self.stream.peer_addr()?.ip());
|
||||
let mut header = [0u8; 2];
|
||||
// Read a byte from the stream and determine the version being requested
|
||||
self.stream.read_exact(&mut header).await?;
|
||||
|
||||
self.socks_version = header[0];
|
||||
self.auth_nmethods = header[1];
|
||||
|
||||
// Handle SOCKS4 requests
|
||||
if header[0] != SOCKS_VERSION {
|
||||
warn!("Init: Unsupported version: SOCKS{}", self.socks_version);
|
||||
self.shutdown()?;
|
||||
}
|
||||
// Valid SOCKS5
|
||||
else {
|
||||
// Authenticate w/ client
|
||||
self.authenticate().await?;
|
||||
// Handle requests
|
||||
self.handle_request().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_request_to_mixnet(&mut self, request: Request) {
|
||||
self.send_to_mixnet(request.into_bytes()).await;
|
||||
}
|
||||
|
||||
async fn send_request_to_mixnet_and_get_response(&mut self, request: Request) -> Vec<u8> {
|
||||
self.send_to_mixnet(request.into_bytes()).await;
|
||||
|
||||
// refactor idea: crossbeam oneshot channels are faster
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let mut active_streams_guard = self.active_streams.lock().await;
|
||||
if active_streams_guard
|
||||
.insert(self.connection_id, sender)
|
||||
.is_some()
|
||||
{
|
||||
panic!("there is already an active request with the same id present - it's probably a bug!")
|
||||
};
|
||||
drop(active_streams_guard);
|
||||
receiver.await.unwrap()
|
||||
}
|
||||
|
||||
/// Handles a client request.
|
||||
async fn handle_request(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("Handling CONNECT Command");
|
||||
|
||||
let request = SocksRequest::from_stream(&mut self.stream).await?;
|
||||
let remote_address = request.to_string();
|
||||
|
||||
match request.command {
|
||||
// Use the Proxy to connect to the specified addr/port
|
||||
SocksCommand::Connect => {
|
||||
trace!("Connecting to: {:?}", request.to_socket());
|
||||
self.acknowledge_socks5().await;
|
||||
|
||||
let request_data_bytes =
|
||||
SocksRequest::try_read_request_data(&mut self.stream).await?;
|
||||
let socks_provider_request = Request::new_connect(
|
||||
self.connection_id,
|
||||
remote_address.clone(),
|
||||
request_data_bytes,
|
||||
);
|
||||
let response_data = self
|
||||
.send_request_to_mixnet_and_get_response(socks_provider_request)
|
||||
.await;
|
||||
self.stream.write_all(&response_data).await.unwrap();
|
||||
|
||||
loop {
|
||||
if let Ok(request_data_bytes) =
|
||||
SocksRequest::try_read_request_data(&mut self.stream).await
|
||||
{
|
||||
if request_data_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let socks_provider_request =
|
||||
Request::new_send(self.connection_id, request_data_bytes);
|
||||
let response_data = self
|
||||
.send_request_to_mixnet_and_get_response(socks_provider_request)
|
||||
.await;
|
||||
self.stream.write_all(&response_data).await.unwrap();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let socks_provider_request = Request::new_close(self.connection_id);
|
||||
self.send_request_to_mixnet(socks_provider_request).await;
|
||||
// TODO: where is connection removed from active connection??
|
||||
}
|
||||
|
||||
SocksCommand::Bind => unimplemented!(), // not handled
|
||||
SocksCommand::UdpAssociate => unimplemented!(), // not handled
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send serialized Socks5 request bytes to the mixnet. The request stream
|
||||
/// will be chunked up into a series of one or more Sphinx packets and
|
||||
/// reassembled at the destination service provider at the other end, then
|
||||
/// sent onwards anonymously.
|
||||
async fn send_to_mixnet(&self, request_bytes: Vec<u8>) {
|
||||
let input_message =
|
||||
InputMessage::new_fresh(self.service_provider.clone(), request_bytes, false);
|
||||
self.input_sender.unbounded_send(input_message).unwrap();
|
||||
}
|
||||
|
||||
/// Writes a Socks5 header back to the requesting client's TCP stream,
|
||||
/// basically saying "I acknowledge your request and am dealing with it".
|
||||
async fn acknowledge_socks5(&mut self) {
|
||||
self.stream
|
||||
.write_all(&[
|
||||
SOCKS_VERSION,
|
||||
ResponseCode::Success as u8,
|
||||
RESERVED,
|
||||
1,
|
||||
127,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Authenticate the incoming request. Each request is checked for its
|
||||
/// authentication method. A user/password request will extract the
|
||||
/// username and password from the stream, then check with the Authenticator
|
||||
/// to see if the resulting user is allowed.
|
||||
///
|
||||
/// A lot of this could probably be put into the the `SocksRequest::from_stream()`
|
||||
/// constructor, and/or cleaned up with tokio::codec. It's mostly just
|
||||
/// read-a-byte-or-two. The bytes being extracted look like this:
|
||||
///
|
||||
/// +----+------+----------+------+------------+
|
||||
// |ver | ulen | uname | plen | password |
|
||||
/// +----+------+----------+------+------------+
|
||||
/// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
|
||||
/// +----+------+----------+------+------------+
|
||||
///
|
||||
/// Pulling out the stream code into its own home, and moving the if/else logic
|
||||
/// into the Authenticator (where it'll be more easily testable)
|
||||
/// would be a good next step.
|
||||
async fn authenticate(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip());
|
||||
// Get valid auth methods
|
||||
let methods = self.get_available_methods().await?;
|
||||
trace!("methods: {:?}", methods);
|
||||
|
||||
let mut response = [0u8; 2];
|
||||
|
||||
// Set the version in the response
|
||||
response[0] = SOCKS_VERSION;
|
||||
if methods.contains(&(AuthenticationMethods::UserPass as u8)) {
|
||||
// Set the default auth method (NO AUTH)
|
||||
response[1] = AuthenticationMethods::UserPass as u8;
|
||||
|
||||
debug!("Sending USER/PASS packet");
|
||||
self.stream.write_all(&response).await?;
|
||||
|
||||
let mut header = [0u8; 2];
|
||||
|
||||
// Read a byte from the stream and determine the version being requested
|
||||
self.stream.read_exact(&mut header).await?;
|
||||
|
||||
// debug!("Auth Header: [{}, {}]", header[0], header[1]);
|
||||
|
||||
// Username parsing
|
||||
let ulen = header[1];
|
||||
|
||||
let mut username = Vec::with_capacity(ulen as usize);
|
||||
|
||||
// For some reason the vector needs to actually be full
|
||||
for _ in 0..ulen {
|
||||
username.push(0);
|
||||
}
|
||||
|
||||
self.stream.read_exact(&mut username).await?;
|
||||
|
||||
// Password Parsing
|
||||
let mut plen = [0u8; 1];
|
||||
self.stream.read_exact(&mut plen).await?;
|
||||
|
||||
let mut password = Vec::with_capacity(plen[0] as usize);
|
||||
|
||||
// For some reason the vector needs to actually be full
|
||||
for _ in 0..plen[0] {
|
||||
password.push(0);
|
||||
}
|
||||
|
||||
self.stream.read_exact(&mut password).await?;
|
||||
|
||||
let username_str = String::from_utf8(username)?;
|
||||
let password_str = String::from_utf8(password)?;
|
||||
|
||||
let user = User {
|
||||
username: username_str,
|
||||
password: password_str,
|
||||
};
|
||||
|
||||
// Authenticate passwords
|
||||
if self.authenticator.is_allowed(&user) {
|
||||
debug!("Access Granted. User: {}", user.username);
|
||||
let response = [1, ResponseCode::Success as u8];
|
||||
self.stream.write_all(&response).await?;
|
||||
} else {
|
||||
debug!("Access Denied. User: {}", user.username);
|
||||
let response = [1, ResponseCode::Failure as u8];
|
||||
self.stream.write_all(&response).await?;
|
||||
|
||||
// Shutdown
|
||||
self.shutdown()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else if methods.contains(&(AuthenticationMethods::NoAuth as u8)) {
|
||||
// set the default auth method (no auth)
|
||||
response[1] = AuthenticationMethods::NoAuth as u8;
|
||||
debug!("Sending NOAUTH packet");
|
||||
self.stream.write_all(&response).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("Client has no suitable authentication methods!");
|
||||
response[1] = AuthenticationMethods::NoMethods as u8;
|
||||
self.stream.write_all(&response).await?;
|
||||
self.shutdown()?;
|
||||
Err(ResponseCode::Failure.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the available methods based on `self.auth_nmethods`
|
||||
async fn get_available_methods(&mut self) -> Result<Vec<u8>, SocksProxyError> {
|
||||
let mut methods: Vec<u8> = Vec::with_capacity(self.auth_nmethods as usize);
|
||||
for _ in 0..self.auth_nmethods {
|
||||
let mut method = [0u8; 1];
|
||||
self.stream.read_exact(&mut method).await?;
|
||||
if self.authenticator.auth_methods.contains(&method[0]) {
|
||||
methods.append(&mut method.to_vec());
|
||||
}
|
||||
}
|
||||
Ok(methods)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use super::client::ActiveStreams;
|
||||
use crate::client::received_buffer::ReconstructedMessagesReceiver;
|
||||
use crate::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use simple_socks5_requests::Response;
|
||||
|
||||
pub(crate) struct MixnetResponseListener {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
mix_response_receiver: ReconstructedMessagesReceiver,
|
||||
active_streams: ActiveStreams,
|
||||
}
|
||||
|
||||
impl Drop for MixnetResponseListener {
|
||||
fn drop(&mut self) {
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
.expect("the buffer request failed!")
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetResponseListener {
|
||||
pub(crate) fn new(
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
active_streams: ActiveStreams,
|
||||
) -> Self {
|
||||
let (mix_response_sender, mix_response_receiver) = mpsc::unbounded();
|
||||
buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(mix_response_sender))
|
||||
.unwrap();
|
||||
|
||||
MixnetResponseListener {
|
||||
active_streams,
|
||||
buffer_requester,
|
||||
mix_response_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&self, bytes: Vec<u8>) {
|
||||
let reconstructed_message =
|
||||
ReconstructedMessage::try_from_bytes(&bytes).expect("todo: error handling");
|
||||
let raw_message = reconstructed_message.message;
|
||||
if reconstructed_message.reply_SURB.is_some() {
|
||||
println!("this message had a surb - we didn't do anything with it");
|
||||
}
|
||||
|
||||
let response = match Response::try_from_bytes(&raw_message) {
|
||||
Err(err) => {
|
||||
warn!("failed to parse received response - {:?}", err);
|
||||
return;
|
||||
}
|
||||
Ok(data) => data,
|
||||
};
|
||||
|
||||
let mut active_streams_guard = self.active_streams.lock().await;
|
||||
|
||||
// `remove` gives back the entry (assuming it exists). There's no reason
|
||||
// for it to persist after we send data back
|
||||
if let Some(stream_receiver) = active_streams_guard.remove(&response.connection_id) {
|
||||
stream_receiver.send(response.data).unwrap()
|
||||
} else {
|
||||
warn!(
|
||||
"no connection_id exists with id: {:?}",
|
||||
&response.connection_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
while let Some(received_responses) = self.mix_response_receiver.next().await {
|
||||
for received_response in received_responses {
|
||||
self.on_message(received_response.into_bytes()).await;
|
||||
}
|
||||
}
|
||||
println!("We should never see this message");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod authentication;
|
||||
mod client;
|
||||
pub(crate) mod mixnet_responses;
|
||||
mod request;
|
||||
pub mod server;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
/// Version of socks
|
||||
const SOCKS_VERSION: u8 = 0x05;
|
||||
|
||||
const RESERVED: u8 = 0x00;
|
||||
@@ -0,0 +1,162 @@
|
||||
use super::types::{AddrType, ResponseCode, SocksProxyError};
|
||||
use super::{utils, SOCKS_VERSION};
|
||||
use log::*;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
/// A Socks5 request hitting the proxy.
|
||||
pub(crate) struct SocksRequest {
|
||||
pub version: u8,
|
||||
pub command: SocksCommand,
|
||||
pub addr_type: AddrType,
|
||||
pub addr: Vec<u8>,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl SocksRequest {
|
||||
/// Parse a SOCKS5 request from a TcpStream
|
||||
pub async fn from_stream(stream: &mut TcpStream) -> Result<Self, SocksProxyError> {
|
||||
let mut packet = [0u8; 4];
|
||||
// Read a byte from the stream and determine the version being requested
|
||||
stream.read_exact(&mut packet).await?;
|
||||
|
||||
if packet[0] != SOCKS_VERSION {
|
||||
warn!("from_stream Unsupported version: SOCKS{}", packet[0]);
|
||||
stream.shutdown();
|
||||
}
|
||||
|
||||
// Get command
|
||||
let mut command: SocksCommand = SocksCommand::Connect;
|
||||
match SocksCommand::from(packet[1] as usize) {
|
||||
Some(com) => {
|
||||
command = com;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
warn!("Invalid Command");
|
||||
stream.shutdown();
|
||||
Err(ResponseCode::CommandNotSupported)
|
||||
}
|
||||
}?;
|
||||
|
||||
// DST.address
|
||||
|
||||
let mut addr_type: AddrType = AddrType::V6;
|
||||
match AddrType::from(packet[3] as usize) {
|
||||
Some(addr) => {
|
||||
addr_type = addr;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
error!("No Addr");
|
||||
stream.shutdown();
|
||||
Err(ResponseCode::AddrTypeNotSupported)
|
||||
}
|
||||
}?;
|
||||
|
||||
trace!("Getting Addr");
|
||||
// Get Addr from addr_type and stream
|
||||
let addr: Result<Vec<u8>, SocksProxyError> = match addr_type {
|
||||
AddrType::Domain => {
|
||||
let mut domain_length = [0u8; 1];
|
||||
stream.read_exact(&mut domain_length).await?;
|
||||
|
||||
let mut domain = vec![0u8; domain_length[0] as usize];
|
||||
stream.read_exact(&mut domain).await?;
|
||||
|
||||
Ok(domain)
|
||||
}
|
||||
AddrType::V4 => {
|
||||
let mut addr = [0u8; 4];
|
||||
stream.read_exact(&mut addr).await?;
|
||||
Ok(addr.to_vec())
|
||||
}
|
||||
AddrType::V6 => {
|
||||
let mut addr = [0u8; 16];
|
||||
stream.read_exact(&mut addr).await?;
|
||||
Ok(addr.to_vec())
|
||||
}
|
||||
};
|
||||
|
||||
let addr = addr?;
|
||||
|
||||
// read DST.port
|
||||
let mut port = [0u8; 2];
|
||||
stream.read_exact(&mut port).await?;
|
||||
// Merge two u8s into u16
|
||||
let port = (u16::from(port[0]) << 8) | u16::from(port[1]);
|
||||
|
||||
// Return parsed request
|
||||
Ok(SocksRequest {
|
||||
version: packet[0],
|
||||
command,
|
||||
addr_type,
|
||||
addr,
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Print out the address and port to a String.
|
||||
/// This might return domain:port, ipv6:port, or ipv4:port.
|
||||
pub(crate) fn to_string(&self) -> String {
|
||||
let address = utils::pretty_print_addr(&self.addr_type, &self.addr);
|
||||
format!("{}:{}", address, self.port)
|
||||
}
|
||||
|
||||
/// Convert the request object to a SocketAddr
|
||||
pub(crate) fn to_socket(&self) -> Result<Vec<SocketAddr>, SocksProxyError> {
|
||||
utils::addr_to_socket(&self.addr_type, &self.addr, self.port)
|
||||
}
|
||||
|
||||
/// Attempts to read data from the Socks5 request stream. Times out and
|
||||
/// returns what it's got if no data is read for the timeout_duration
|
||||
pub(crate) async fn try_read_request_data<R: AsyncRead + Unpin>(
|
||||
reader: &mut R,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
let timeout_duration = std::time::Duration::from_millis(500);
|
||||
let mut data = Vec::new();
|
||||
let mut timeout = tokio::time::delay_for(timeout_duration);
|
||||
loop {
|
||||
let mut buf = [0u8; 1024];
|
||||
tokio::select! {
|
||||
_ = &mut timeout => {
|
||||
println!("we timed out!");
|
||||
return Ok(data)
|
||||
}
|
||||
read_data = reader.read(&mut buf) => {
|
||||
match read_data {
|
||||
Err(err) => return Err(err),
|
||||
Ok(0) => return Ok(data),
|
||||
Ok(n) => {
|
||||
let now = timeout.deadline();
|
||||
let next = now + timeout_duration;
|
||||
timeout.reset(next);
|
||||
data.extend_from_slice(&buf[..n])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SOCK5 CMD type
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SocksCommand {
|
||||
Connect = 0x01,
|
||||
Bind = 0x02,
|
||||
UdpAssociate = 0x3,
|
||||
}
|
||||
|
||||
impl SocksCommand {
|
||||
/// Parse bytes to SocksCommand
|
||||
fn from(n: usize) -> Option<SocksCommand> {
|
||||
match n {
|
||||
1 => Some(SocksCommand::Connect),
|
||||
2 => Some(SocksCommand::Bind),
|
||||
3 => Some(SocksCommand::UdpAssociate),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use super::authentication::Authenticator;
|
||||
use super::client::SocksClient;
|
||||
use super::{
|
||||
mixnet_responses::MixnetResponseListener,
|
||||
types::{ResponseCode, SocksProxyError},
|
||||
};
|
||||
use crate::client::{
|
||||
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
|
||||
};
|
||||
use futures::lock::Mutex;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
/// A Socks5 server that listens for connections.
|
||||
pub struct SphinxSocksServer {
|
||||
authenticator: Authenticator,
|
||||
listening_address: SocketAddr,
|
||||
service_provider: Recipient,
|
||||
}
|
||||
|
||||
impl SphinxSocksServer {
|
||||
/// Create a new SphinxSocks instance
|
||||
pub(crate) fn new(
|
||||
port: u16,
|
||||
ip: &str,
|
||||
authenticator: Authenticator,
|
||||
service_provider: Recipient,
|
||||
) -> Self {
|
||||
info!("Listening on {}:{}", ip, port);
|
||||
SphinxSocksServer {
|
||||
authenticator,
|
||||
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
|
||||
service_provider,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up the listener and initiate connection handling when something
|
||||
/// connects to the server.
|
||||
pub(crate) async fn serve(
|
||||
&mut self,
|
||||
input_sender: InputMessageSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
) -> Result<(), SocksProxyError> {
|
||||
info!("Serving Connections...");
|
||||
let mut listener = TcpListener::bind(self.listening_address).await.unwrap();
|
||||
|
||||
// todo: probably just create a wrapper type for this guy
|
||||
let active_streams = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
let mut mixnet_response_listener =
|
||||
MixnetResponseListener::new(buffer_requester, Arc::clone(&active_streams));
|
||||
|
||||
tokio::spawn(async move {
|
||||
mixnet_response_listener.run().await;
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Ok((stream, _remote)) = listener.accept().await {
|
||||
// TODO Optimize this
|
||||
let mut client = SocksClient::new(
|
||||
stream,
|
||||
self.authenticator.clone(),
|
||||
input_sender.clone(),
|
||||
self.service_provider.clone(),
|
||||
Arc::clone(&active_streams),
|
||||
);
|
||||
|
||||
tokio::spawn(async move {
|
||||
{
|
||||
match client.run().await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
error!("Error! {}", error);
|
||||
let error_text = format!("{}", error);
|
||||
|
||||
let response: ResponseCode;
|
||||
|
||||
if error_text.contains("Host") {
|
||||
response = ResponseCode::HostUnreachable;
|
||||
} else if error_text.contains("Network") {
|
||||
response = ResponseCode::NetworkUnreachable;
|
||||
} else if error_text.contains("ttl") {
|
||||
response = ResponseCode::TtlExpired
|
||||
} else {
|
||||
response = ResponseCode::Failure
|
||||
}
|
||||
|
||||
if client.error(response).await.is_err() {
|
||||
warn!("Failed to send error code");
|
||||
};
|
||||
if client.shutdown().is_err() {
|
||||
warn!("Failed to shutdown TcpStream");
|
||||
};
|
||||
}
|
||||
};
|
||||
// client gets dropped here
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use snafu::Snafu;
|
||||
#[derive(Debug, Snafu)]
|
||||
/// Possible SOCKS5 Response Codes
|
||||
pub(crate) enum ResponseCode {
|
||||
Success = 0x00,
|
||||
#[snafu(display("SOCKS5 Server Failure"))]
|
||||
Failure = 0x01,
|
||||
#[snafu(display("SOCKS5 Rule failure"))]
|
||||
RuleFailure = 0x02,
|
||||
#[snafu(display("network unreachable"))]
|
||||
NetworkUnreachable = 0x03,
|
||||
#[snafu(display("host unreachable"))]
|
||||
HostUnreachable = 0x04,
|
||||
#[snafu(display("connection refused"))]
|
||||
ConnectionRefused = 0x05,
|
||||
#[snafu(display("TTL expired"))]
|
||||
TtlExpired = 0x06,
|
||||
#[snafu(display("Command not supported"))]
|
||||
CommandNotSupported = 0x07,
|
||||
#[snafu(display("Addr Type not supported"))]
|
||||
AddrTypeNotSupported = 0x08,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SocksProxyError {
|
||||
GenericError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SocksProxyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "foomp")
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for SocksProxyError
|
||||
where
|
||||
E: std::error::Error,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
SocksProxyError::GenericError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// DST.addr variant types
|
||||
#[derive(PartialEq)]
|
||||
pub(crate) enum AddrType {
|
||||
V4 = 0x01,
|
||||
Domain = 0x03,
|
||||
V6 = 0x04,
|
||||
}
|
||||
|
||||
impl AddrType {
|
||||
/// Parse Byte to Command
|
||||
pub(crate) fn from(n: usize) -> Option<AddrType> {
|
||||
match n {
|
||||
1 => Some(AddrType::V4),
|
||||
3 => Some(AddrType::Domain),
|
||||
4 => Some(AddrType::V6),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use super::types::{AddrType, SocksProxyError};
|
||||
use log::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
|
||||
/// Convert an address and AddrType to a SocketAddr
|
||||
pub(crate) fn addr_to_socket(
|
||||
addr_type: &AddrType,
|
||||
addr: &[u8],
|
||||
port: u16,
|
||||
) -> Result<Vec<SocketAddr>, SocksProxyError> {
|
||||
match addr_type {
|
||||
AddrType::V6 => {
|
||||
let new_addr = (0..8)
|
||||
.map(|x| {
|
||||
trace!("{} and {}", x * 2, (x * 2) + 1);
|
||||
(u16::from(addr[(x * 2)]) << 8) | u16::from(addr[(x * 2) + 1])
|
||||
})
|
||||
.collect::<Vec<u16>>();
|
||||
|
||||
Ok(vec![SocketAddr::from(SocketAddrV6::new(
|
||||
Ipv6Addr::new(
|
||||
new_addr[0],
|
||||
new_addr[1],
|
||||
new_addr[2],
|
||||
new_addr[3],
|
||||
new_addr[4],
|
||||
new_addr[5],
|
||||
new_addr[6],
|
||||
new_addr[7],
|
||||
),
|
||||
port,
|
||||
0,
|
||||
0,
|
||||
))])
|
||||
}
|
||||
AddrType::V4 => Ok(vec![SocketAddr::from(SocketAddrV4::new(
|
||||
Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]),
|
||||
port,
|
||||
))]),
|
||||
AddrType::Domain => {
|
||||
let mut domain = String::from_utf8_lossy(&addr[..]).to_string();
|
||||
domain.push_str(&":");
|
||||
domain.push_str(&port.to_string());
|
||||
|
||||
Ok(domain.to_socket_addrs()?.collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an AddrType and address to String
|
||||
pub(crate) fn pretty_print_addr(addr_type: &AddrType, addr: &[u8]) -> String {
|
||||
match addr_type {
|
||||
AddrType::Domain => String::from_utf8_lossy(addr).to_string(),
|
||||
AddrType::V4 => addr
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join("."),
|
||||
AddrType::V6 => {
|
||||
let addr_16 = (0..8)
|
||||
.map(|x| (u16::from(addr[(x * 2)]) << 8) | u16::from(addr[(x * 2) + 1]))
|
||||
.collect::<Vec<u16>>();
|
||||
|
||||
addr_16
|
||||
.iter()
|
||||
.map(|x| format!("{:x}", x))
|
||||
.collect::<Vec<String>>()
|
||||
.join(":")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +99,7 @@ mod converting_mixnode_presence_into_topology_mixnode {
|
||||
};
|
||||
|
||||
let result: Result<mix::Node, self::mixnodes::ConversionError> = mix_presence.try_into();
|
||||
assert!(result.is_err()) // This fails only for me. Why?
|
||||
// ¯\_(ツ)_/¯ - works on my machine (and travis)
|
||||
// Is it still broken?
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -508,7 +508,7 @@ impl<'a, R> GatewayClient<'static, R> {
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
|
||||
// that call is NON-blocking
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
Ok(shared_key)
|
||||
|
||||
@@ -625,7 +625,7 @@ mod fragment {
|
||||
10,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -635,7 +635,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
@@ -646,7 +646,7 @@ mod fragment {
|
||||
10,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -656,7 +656,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -678,7 +678,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -688,7 +688,7 @@ mod fragment {
|
||||
5,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -699,7 +699,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -709,7 +709,7 @@ mod fragment {
|
||||
5,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -719,7 +719,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -730,7 +730,7 @@ mod fragment {
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -740,7 +740,7 @@ mod fragment {
|
||||
5,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
@@ -761,7 +761,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -771,7 +771,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -781,7 +781,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -791,7 +791,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
@@ -802,7 +802,7 @@ mod fragment {
|
||||
u8::max_value(),
|
||||
None,
|
||||
Some(link_id),
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -823,7 +823,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -833,7 +833,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -843,7 +843,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -853,7 +853,7 @@ mod fragment {
|
||||
1,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -864,7 +864,7 @@ mod fragment {
|
||||
u8::max_value(),
|
||||
None,
|
||||
Some(link_id),
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
assert!(Fragment::try_new(
|
||||
@@ -874,7 +874,7 @@ mod fragment {
|
||||
u8::max_value(),
|
||||
None,
|
||||
Some(link_id),
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -885,7 +885,7 @@ mod fragment {
|
||||
u8::max_value(),
|
||||
None,
|
||||
Some(link_id),
|
||||
max_plaintext_size()
|
||||
max_plaintext_size(),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
@@ -989,7 +989,7 @@ mod fragment_header {
|
||||
u8::max_value(),
|
||||
u8::max_value(),
|
||||
None,
|
||||
Some(0)
|
||||
Some(0),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ where
|
||||
|
||||
// serialize fragment and encrypt its content
|
||||
let mut chunk_data = fragment.into_bytes();
|
||||
|
||||
let zero_iv = stream_cipher::zero_iv::<PacketEncryptionAlgorithm>();
|
||||
stream_cipher::encrypt_in_place::<PacketEncryptionAlgorithm>(
|
||||
&shared_key,
|
||||
|
||||
@@ -37,7 +37,7 @@ kill_old
|
||||
export RUST_LOG=warning
|
||||
# NOTE: If we wanted to suppress stdout and stderr, replace `&` with `> /dev/null 2>&1 &` in the `run`
|
||||
|
||||
cargo run --bin nym-gateway -- init --id gateway-local --mix-host 127.0.0.1:10000 --clients-host 127.0.0.1:10001 --directory $DIR
|
||||
# cargo run --bin nym-gateway -- init --id gateway-local --mix-host 127.0.0.1:10000 --clients-host 127.0.0.1:10001 --directory $DIR
|
||||
cargo run --bin nym-gateway -- run --id gateway-local &
|
||||
|
||||
sleep 1
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "simple-socks5"
|
||||
version = "0.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.33.0"
|
||||
futures = "0.3"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] }
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.3"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio-tungstenite = "0.10.1"
|
||||
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
simple-socks5-requests = { path = "simple-socks5-requests" }
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "simple-socks5-requests"
|
||||
version = "0.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub use request::*;
|
||||
pub use response::*;
|
||||
@@ -0,0 +1,348 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
pub type RemoteAddress = String;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RequestFlag {
|
||||
Connect = 0,
|
||||
Send = 1,
|
||||
Close = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RequestError {
|
||||
AddressLengthTooShort,
|
||||
AddressTooShort,
|
||||
ConnectionIdTooShort,
|
||||
NoData,
|
||||
UnknownRequestFlag,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for RequestFlag {
|
||||
type Error = RequestError;
|
||||
|
||||
fn try_from(value: u8) -> Result<RequestFlag, RequestError> {
|
||||
match value {
|
||||
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
|
||||
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
|
||||
_ if value == (RequestFlag::Close as u8) => Ok(Self::Close),
|
||||
_ => Err(RequestError::UnknownRequestFlag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request from a SOCKS5 client that a Nym Socks5 service provider should
|
||||
/// take an action for an application using a (probably local) Nym Socks5 proxy.
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
/// Start a new TCP connection to the specified `RemoteAddress` and send
|
||||
/// the request data up the connection.
|
||||
Connect(ConnectionId, RemoteAddress, Vec<u8>),
|
||||
|
||||
/// Re-use an existing TCP connection, sending more request data up it.
|
||||
Send(ConnectionId, Vec<u8>),
|
||||
|
||||
/// Close an existing TCP connection.
|
||||
Close(ConnectionId),
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Construct a new Request::Connect instance
|
||||
pub fn new_connect(
|
||||
conn_id: ConnectionId,
|
||||
remote_addr: RemoteAddress,
|
||||
data: Vec<u8>,
|
||||
) -> Request {
|
||||
Request::Connect(conn_id, remote_addr, data)
|
||||
}
|
||||
|
||||
/// Construct a new Request::Send instance
|
||||
pub fn new_send(conn_id: ConnectionId, data: Vec<u8>) -> Request {
|
||||
Request::Send(conn_id, data)
|
||||
}
|
||||
|
||||
/// Construct a new Request::Close instance
|
||||
pub fn new_close(conn_id: ConnectionId) -> Request {
|
||||
Request::Close(conn_id)
|
||||
}
|
||||
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
/// and the request body from bytes.
|
||||
///
|
||||
/// Serialized bytes looks like this:
|
||||
///
|
||||
/// --------------------------------------------------------------------------------------
|
||||
/// request_flag | connection_id | address_length | remote_address_bytes | request_data |
|
||||
/// 1 | 8 | 2 | address_length | ... |
|
||||
/// --------------------------------------------------------------------------------------
|
||||
///
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Request, RequestError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
return Err(RequestError::NoData);
|
||||
}
|
||||
|
||||
if b.len() < 9 {
|
||||
return Err(RequestError::ConnectionIdTooShort);
|
||||
}
|
||||
let connection_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
|
||||
match RequestFlag::try_from(b[0])? {
|
||||
RequestFlag::Connect => {
|
||||
let connect_request_bytes = &b[9..];
|
||||
|
||||
// we need to be able to read at least 2 bytes that specify address length
|
||||
if connect_request_bytes.len() < 2 {
|
||||
return Err(RequestError::AddressLengthTooShort);
|
||||
}
|
||||
|
||||
let address_length =
|
||||
u16::from_be_bytes([connect_request_bytes[0], connect_request_bytes[1]])
|
||||
as usize;
|
||||
|
||||
if connect_request_bytes.len() < 2 + address_length {
|
||||
return Err(RequestError::AddressTooShort);
|
||||
}
|
||||
|
||||
let address_start = 2;
|
||||
let address_end = address_start + address_length;
|
||||
let address_bytes = &connect_request_bytes[address_start..address_end];
|
||||
let remote_address = String::from_utf8_lossy(&address_bytes).to_string();
|
||||
|
||||
let request_data = &connect_request_bytes[address_end..];
|
||||
Ok(Request::Connect(
|
||||
connection_id,
|
||||
remote_address,
|
||||
request_data.to_vec(),
|
||||
))
|
||||
}
|
||||
RequestFlag::Send => Ok(Request::Send(connection_id, b[9..].as_ref().to_vec())),
|
||||
RequestFlag::Close => Ok(Request::Close(connection_id)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a Socks5 request into bytes, so that it can be packed inside
|
||||
/// a Sphinx packet, sent through the mixnet, and deserialized by the Socks5
|
||||
/// service provider which will make the request.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
Request::Connect(conn_id, remote_address, data) => {
|
||||
let remote_address_bytes = remote_address.into_bytes();
|
||||
let remote_address_bytes_len = remote_address_bytes.len() as u16;
|
||||
std::iter::once(RequestFlag::Connect as u8)
|
||||
.chain(conn_id.to_be_bytes().iter().cloned())
|
||||
.chain(remote_address_bytes_len.to_be_bytes().iter().cloned())
|
||||
.chain(remote_address_bytes.into_iter())
|
||||
.chain(data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
Request::Send(conn_id, data) => std::iter::once(RequestFlag::Send as u8)
|
||||
.chain(conn_id.to_be_bytes().iter().cloned())
|
||||
.chain(data.into_iter())
|
||||
.collect(),
|
||||
Request::Close(conn_id) => std::iter::once(RequestFlag::Close as u8)
|
||||
.chain(conn_id.to_be_bytes().iter().cloned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod request_deserialization_tests {
|
||||
use super::*;
|
||||
|
||||
mod all_request_types {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_error_when_zero_bytes() {
|
||||
let request_bytes = Vec::new();
|
||||
assert_eq!(
|
||||
RequestError::NoData,
|
||||
Request::try_from_bytes(&request_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_when_connection_id_too_short() {
|
||||
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7].to_vec(); // 7 bytes connection id
|
||||
assert_eq!(
|
||||
RequestError::ConnectionIdTooShort,
|
||||
Request::try_from_bytes(&request_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sending_data_over_a_new_connection {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_error_when_address_length_is_too_short() {
|
||||
let request_bytes1 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec(); // 8 bytes connection id, 0 bytes address length (2 were expected)
|
||||
let request_bytes2 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); // 8 bytes connection id, 1 bytes address length (2 were expected)
|
||||
|
||||
assert_eq!(
|
||||
RequestError::AddressLengthTooShort,
|
||||
Request::try_from_bytes(&request_bytes1).unwrap_err()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RequestError::AddressLengthTooShort,
|
||||
Request::try_from_bytes(&request_bytes2).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_when_address_too_short_for_given_address_length() {
|
||||
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1].to_vec(); // 8 bytes connection id, 2 bytes address length, missing address
|
||||
assert_eq!(
|
||||
RequestError::AddressTooShort,
|
||||
Request::try_from_bytes(&request_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_even_without_data() {
|
||||
// this one has "foo.com" remote address, correct 8 bytes of connection_id, and 0 bytes request data
|
||||
let request_bytes = [
|
||||
RequestFlag::Connect as u8,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
7,
|
||||
102,
|
||||
111,
|
||||
111,
|
||||
46,
|
||||
99,
|
||||
111,
|
||||
109,
|
||||
]
|
||||
.to_vec();
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Connect(conn_id, remote_address, data) => {
|
||||
assert_eq!("foo.com".to_string(), remote_address);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(Vec::<u8>::new(), data);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_and_has_data() {
|
||||
// this one has a 1-byte remote address, correct 16 bytes of connection_id, and 3 bytes request data
|
||||
let request_bytes = [
|
||||
RequestFlag::Connect as u8,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
7,
|
||||
102,
|
||||
111,
|
||||
111,
|
||||
46,
|
||||
99,
|
||||
111,
|
||||
109,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
]
|
||||
.to_vec();
|
||||
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Connect(conn_id, remote_address, data) => {
|
||||
assert_eq!("foo.com".to_string(), remote_address);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(vec![255, 255, 255], data);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sending_additional_data_over_an_existing_connection {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_even_without_data() {
|
||||
// correct 8 bytes of connection_id, and 0 bytes request data
|
||||
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec();
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Send(conn_id, data) => {
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(Vec::<u8>::new(), data);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_and_has_data() {
|
||||
// correct 8 bytes of connection_id, and 3 bytes request data (all 255)
|
||||
let request_bytes = [
|
||||
RequestFlag::Send as u8,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
]
|
||||
.to_vec();
|
||||
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Send(conn_id, data) => {
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(vec![255, 255, 255], data);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod connection_close_requests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_be_constructed() {
|
||||
let request_bytes = [RequestFlag::Close as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec();
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Close(conn_id) => {
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use crate::ConnectionId;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ResponseError {
|
||||
ConnectionIdTooShort,
|
||||
NoData,
|
||||
}
|
||||
/// A remote network response retrieved by the Socks5 service provider. This
|
||||
/// can be serialized and sent back through the mixnet to the requesting
|
||||
/// application.
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
pub data: Vec<u8>,
|
||||
pub connection_id: ConnectionId,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Constructor for responses
|
||||
pub fn new(connection_id: ConnectionId, data: Vec<u8>) -> Self {
|
||||
Response {
|
||||
connection_id,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Response, ResponseError> {
|
||||
if b.is_empty() {
|
||||
return Err(ResponseError::NoData);
|
||||
}
|
||||
|
||||
if b.len() < 8 {
|
||||
return Err(ResponseError::ConnectionIdTooShort);
|
||||
}
|
||||
|
||||
let mut connection_id_bytes = b.to_vec();
|
||||
let data = connection_id_bytes.split_off(8);
|
||||
|
||||
let connection_id = u64::from_be_bytes([
|
||||
connection_id_bytes[0],
|
||||
connection_id_bytes[1],
|
||||
connection_id_bytes[2],
|
||||
connection_id_bytes[3],
|
||||
connection_id_bytes[4],
|
||||
connection_id_bytes[5],
|
||||
connection_id_bytes[6],
|
||||
connection_id_bytes[7],
|
||||
]);
|
||||
|
||||
let response = Response::new(connection_id, data);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Serializes the response into bytes so that it can be sent back through
|
||||
/// the mixnet to the requesting application.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.connection_id
|
||||
.to_be_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod constructing_socks5_responses_from_bytes {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_when_zero_bytes_are_supplied() {
|
||||
let response_bytes = Vec::new();
|
||||
|
||||
assert_eq!(
|
||||
ResponseError::NoData,
|
||||
Response::try_from_bytes(&response_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_when_connection_id_bytes_are_too_short() {
|
||||
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
|
||||
assert_eq!(
|
||||
ResponseError::ConnectionIdTooShort,
|
||||
Response::try_from_bytes(&response_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_no_data() {
|
||||
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6, 7];
|
||||
let expected = Response::new(u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), Vec::new());
|
||||
let actual = Response::try_from_bytes(&response_bytes).unwrap();
|
||||
assert_eq!(expected.connection_id, actual.connection_id);
|
||||
assert_eq!(expected.data, actual.data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_data() {
|
||||
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255];
|
||||
let expected = Response::new(
|
||||
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
vec![255, 255, 255],
|
||||
);
|
||||
let actual = Response::try_from_bytes(&response_bytes).unwrap();
|
||||
assert_eq!(expected.connection_id, actual.connection_id);
|
||||
assert_eq!(expected.data, actual.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use simple_socks5_requests::{ConnectionId, RemoteAddress};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
/// A TCP connection between the Socks5 service provider, which makes
|
||||
/// outbound requests on behalf of users and returns the responses through
|
||||
/// the mixnet.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Connection {
|
||||
id: ConnectionId,
|
||||
address: RemoteAddress,
|
||||
conn: TcpStream,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub(crate) async fn new(
|
||||
id: ConnectionId,
|
||||
address: RemoteAddress,
|
||||
initial_data: &[u8],
|
||||
) -> io::Result<Self> {
|
||||
let conn = TcpStream::connect(&address).await?;
|
||||
let mut connection = Connection { id, address, conn };
|
||||
connection.send_data(&initial_data).await?;
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
pub(crate) async fn send_data(&mut self, data: &[u8]) -> io::Result<()> {
|
||||
self.conn.write_all(&data).await
|
||||
}
|
||||
|
||||
/// Read response data by looping, waiting for anything we get back from the
|
||||
/// remote server. Returns once it times out or the connection closes.
|
||||
pub(crate) async fn try_read_response_data(&mut self) -> io::Result<Vec<u8>> {
|
||||
let timeout_duration = std::time::Duration::from_millis(500);
|
||||
let mut data = Vec::new();
|
||||
let mut timeout = tokio::time::delay_for(timeout_duration);
|
||||
loop {
|
||||
let mut buf = [0u8; 1024];
|
||||
tokio::select! {
|
||||
_ = &mut timeout => {
|
||||
return Ok(data) // we return all response data on timeout
|
||||
}
|
||||
read_data = self.conn.read(&mut buf) => {
|
||||
match read_data {
|
||||
Err(err) => return Err(err),
|
||||
Ok(0) => return Ok(data),
|
||||
Ok(n) => {
|
||||
let now = timeout.deadline();
|
||||
let next = now + timeout_duration;
|
||||
timeout.reset(next);
|
||||
data.extend_from_slice(&buf[..n])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use crate::connection::Connection;
|
||||
use simple_socks5_requests::{ConnectionId, RemoteAddress, Request, Response};
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConnectionError {
|
||||
ConnectionFailed(io::Error),
|
||||
MissingConnection,
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConnectionError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
ConnectionError::ConnectionFailed(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Controller {
|
||||
open_connections: HashMap<ConnectionId, Connection>,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub(crate) fn new() -> Self {
|
||||
Controller {
|
||||
open_connections: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn process_request(
|
||||
&mut self,
|
||||
request: Request,
|
||||
) -> Result<Option<Response>, ConnectionError> {
|
||||
match request {
|
||||
Request::Connect(conn_id, remote_addr, data) => {
|
||||
let response = self
|
||||
.create_new_connection(conn_id, remote_addr, data)
|
||||
.await?;
|
||||
Ok(Some(response))
|
||||
}
|
||||
Request::Send(conn_id, data) => {
|
||||
let response = self.send_to_connection(conn_id, data).await?;
|
||||
Ok(Some(response))
|
||||
}
|
||||
Request::Close(conn_id) => {
|
||||
self.close_connection(conn_id)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_new_connection(
|
||||
&mut self,
|
||||
conn_id: ConnectionId,
|
||||
remote_addr: RemoteAddress,
|
||||
init_data: Vec<u8>,
|
||||
) -> Result<Response, ConnectionError> {
|
||||
let mut connection = Connection::new(conn_id, remote_addr, &init_data).await?;
|
||||
|
||||
let response_data = connection.try_read_response_data().await?;
|
||||
self.open_connections.insert(conn_id, connection);
|
||||
Ok(Response::new(conn_id, response_data))
|
||||
}
|
||||
|
||||
async fn send_to_connection(
|
||||
&mut self,
|
||||
conn_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
) -> Result<Response, ConnectionError> {
|
||||
let connection = self
|
||||
.open_connections
|
||||
.get_mut(&conn_id)
|
||||
.ok_or_else(|| ConnectionError::MissingConnection)?;
|
||||
connection.send_data(&data).await?;
|
||||
|
||||
let response_data = connection.try_read_response_data().await?;
|
||||
Ok(Response::new(conn_id, response_data))
|
||||
}
|
||||
|
||||
fn close_connection(&mut self, conn_id: ConnectionId) -> Result<(), ConnectionError> {
|
||||
match self.open_connections.remove(&conn_id) {
|
||||
// I *think* connection is closed implicitly on drop, but I'm not 100% sure!
|
||||
Some(_conn) => (),
|
||||
None => log::error!("tried to close non-existent connection - {}", conn_id),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::{controller::Controller, websocket};
|
||||
use futures::SinkExt;
|
||||
use futures_util::StreamExt;
|
||||
use nymsphinx::params::MessageType;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use simple_socks5_requests::Request;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use websocket::WebsocketConnectionError;
|
||||
|
||||
pub struct ServiceProvider {
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
impl ServiceProvider {
|
||||
pub fn new() -> ServiceProvider {
|
||||
let runtime = Runtime::new().unwrap();
|
||||
ServiceProvider { runtime }
|
||||
}
|
||||
|
||||
/// Start all subsystems
|
||||
pub fn start(&mut self) {
|
||||
let websocket_stream = self.connect_websocket("ws://localhost:1977");
|
||||
let (mut websocket_writer, mut websocket_reader) = websocket_stream.split();
|
||||
let mut controller = Controller::new();
|
||||
|
||||
self.runtime.block_on(async {
|
||||
println!("\nAll systems go. Press CTRL-C to stop the server.");
|
||||
while let Some(msg) = websocket_reader.next().await {
|
||||
let data = msg.unwrap().into_data();
|
||||
|
||||
let reconstructed_message = ReconstructedMessage::try_from_bytes(&data).expect("todo: error handling");
|
||||
let raw_message = reconstructed_message.message;
|
||||
let request = Request::try_from_bytes(&raw_message).unwrap();
|
||||
let response = controller.process_request(request).await.unwrap();
|
||||
if response.is_none() { // restart the loop if we got nothing back
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: wire SURBs in here once they're available
|
||||
let return_address = "7tVXwePpo6SM99sqM1xEp6S4T1TSpxYx97fTpEdvmF7i.GgrN8998SmwvQghNEvqtPPZCgMQqJovWBrzspMnBESsE@e3vUAo6YhB7zq3GH8B4k3iiGT4H2USjdd5ZMZoUsHdF";
|
||||
let recipient = nymsphinx::addressing::clients::Recipient::try_from_string(return_address).unwrap();
|
||||
|
||||
let response_message = std::iter::once(MessageType::WithoutReplySURB as u8)
|
||||
.chain(recipient.into_bytes().iter().cloned())
|
||||
.chain(response.unwrap().into_bytes().into_iter())
|
||||
.collect();
|
||||
|
||||
let message = Message::Binary(response_message);
|
||||
websocket_writer.send(message).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Keep running until the user hits CTRL-C.
|
||||
pub fn run_forever(&mut self) {
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
println!("Stopping with error: {:?}", e);
|
||||
}
|
||||
println!("\nStopping...");
|
||||
}
|
||||
|
||||
// Make the websocket connection so we can receive incoming Mixnet messages.
|
||||
fn connect_websocket(&mut self, uri: &str) -> WebSocketStream<TcpStream> {
|
||||
self.runtime.block_on(async {
|
||||
let ws_stream = match websocket::Connection::new(uri).connect().await {
|
||||
Ok(ws_stream) => {
|
||||
println!("* connected to local websocket server at {}", uri);
|
||||
ws_stream
|
||||
}
|
||||
Err(WebsocketConnectionError::ConnectionNotEstablished) => {
|
||||
panic!("Error: websocket connection attempt failed, is the Nym client running?")
|
||||
}
|
||||
};
|
||||
return ws_stream;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod connection;
|
||||
mod controller;
|
||||
mod core;
|
||||
mod websocket;
|
||||
|
||||
fn main() {
|
||||
println!("Starting socks5 service provider:");
|
||||
let mut server = core::ServiceProvider::new();
|
||||
server.start();
|
||||
server.run_forever();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
|
||||
pub struct Connection {
|
||||
uri: String,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(uri: &str) -> Connection {
|
||||
Connection {
|
||||
uri: String::from(uri),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&self) -> Result<WebSocketStream<TcpStream>, WebsocketConnectionError> {
|
||||
match connect_async(&self.uri).await {
|
||||
Ok((ws_stream, _)) => return Ok(ws_stream),
|
||||
Err(_e) => return Err(WebsocketConnectionError::ConnectionNotEstablished),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebsocketConnectionError {
|
||||
ConnectionNotEstablished,
|
||||
}
|
||||
Reference in New Issue
Block a user