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:
Dave Hrycyszyn
2020-08-12 16:23:23 +01:00
committed by GitHub
parent d9d549fd0f
commit 1546088904
60 changed files with 5901 additions and 52 deletions
+2
View File
@@ -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
View File
@@ -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",
]
+2
View File
@@ -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=
+13 -7
View File
@@ -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,
+47
View File
@@ -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"
+19
View File
@@ -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");
}
+16
View File
@@ -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 }
}
}
+168
View File
@@ -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)
}
}
+91
View File
@@ -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;
})
}
}
+443
View File
@@ -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()
);
}
}
@@ -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?")
}
}
@@ -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
})
}
}
@@ -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?")
}
}
@@ -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;
}
})
}
}
+211
View File
@@ -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")
}
+43
View File
@@ -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
}
+68
View File
@@ -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();
}
+503
View File
@@ -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;
+102
View File
@@ -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 }}
"#
}
+17
View File
@@ -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;
+86
View File
@@ -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();
}
+133
View File
@@ -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));
}
}
}
+334
View File
@@ -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");
}
}
+14
View File
@@ -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;
+162
View File
@@ -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,
}
}
}
+104
View File
@@ -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
}
});
}
}
}
}
+62
View File
@@ -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,
}
}
}
+71
View File
@@ -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]
+1 -1
View File
@@ -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)
+24 -24
View File
@@ -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());
}
+1
View File
@@ -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,
+1 -1
View File
@@ -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,
}