diff --git a/CHANGELOG.md b/CHANGELOG.md index 272bde44..20fbf0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Websocket support for the ASB. The ASB is now capable to listen on both TCP and Websocket connections. Default websocket listening port is 9940. +- Tor support as an optional feature. + If ASB detects that Tor's control port is open, an hidden service is created for + network it is listening on (currently 2). + The Tor control port as well as Tor socks5 proxy port is configurable. ## [0.4.0] - 2021-04-06 diff --git a/Cargo.lock b/Cargo.lock index ddeb23db..2908019c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,15 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -139,9 +148,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -150,9 +159,9 @@ version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -220,6 +229,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base58-monero" version = "0.2.1" @@ -290,9 +305,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45570b78250774145859a8f85bfdb6e310663fc82640d7e159a44b1386074a2" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -829,9 +844,23 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", +] + +[[package]] +name = "derive_more" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe" +dependencies = [ + "lazy_static", + "proc-macro2 0.4.30", + "quote 0.6.13", + "regex", + "rustc_version", + "syn 0.15.44", ] [[package]] @@ -997,9 +1026,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -1057,6 +1086,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1139,9 +1183,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -1656,8 +1700,8 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f3d1e50fefe4252d2e44c805663e73a8c0b2002b73f834ea055c8ed7fc46a8" dependencies = [ - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -1666,10 +1710,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97c11e429f0eaa41fe659013680b459d2368d8f0a3e69dccfb7a35800b0dc27b" dependencies = [ - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.64", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "keccak-hash" version = "0.7.0" @@ -1876,8 +1926,8 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365b0a699fea5168676840567582a012ea297b1ca02eee467e58301b9c9c5eed" dependencies = [ - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -2096,9 +2146,9 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce18b5423c573a13e80cb3046ea0af6379ef725dc3af4886bdb8f4e5093068" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -2209,9 +2259,9 @@ checksum = "85ee3c48cb9d9b275ad967a0e96715badc13c6029adb92f34fa17b9ff28fd81f" dependencies = [ "proc-macro-crate", "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", "synstructure", ] @@ -2367,6 +2417,33 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-sys" +version = "0.9.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" +dependencies = [ + "autocfg 1.0.1", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parity-multiaddr" version = "0.11.2" @@ -2491,9 +2568,9 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -2502,9 +2579,9 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -2604,9 +2681,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", "version_check 0.9.3", ] @@ -2616,8 +2693,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.24", + "quote 1.0.9", "version_check 0.9.3", ] @@ -2633,13 +2710,22 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid", + "unicode-xid 0.2.1", ] [[package]] @@ -2678,9 +2764,9 @@ checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" dependencies = [ "anyhow", "itertools 0.9.0", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -2710,13 +2796,22 @@ dependencies = [ "pin-project-lite 0.1.12", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.24", ] [[package]] @@ -2970,6 +3065,8 @@ version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -3026,6 +3123,7 @@ dependencies = [ "serde_urlencoded", "tokio 1.5.0", "tokio-rustls", + "tokio-socks", "url 2.2.1", "wasm-bindgen", "wasm-bindgen-futures", @@ -3272,9 +3370,9 @@ version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3317,9 +3415,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3366,6 +3464,19 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +dependencies = [ + "block-buffer 0.7.3", + "byte-tools", + "digest 0.8.1", + "keccak", + "opaque-debug 0.2.3", +] + [[package]] name = "sharded-slab" version = "0.1.1" @@ -3548,11 +3659,11 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.24", + "quote 1.0.9", "serde", "serde_derive", - "syn", + "syn 1.0.64", ] [[package]] @@ -3562,13 +3673,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2", - "quote", + "proc-macro2 1.0.24", + "quote 1.0.9", "serde", "serde_derive", "serde_json", "sha1", - "syn", + "syn 1.0.64", ] [[package]] @@ -3612,9 +3723,9 @@ checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" dependencies = [ "heck", "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3633,9 +3744,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3669,6 +3780,7 @@ dependencies = [ "config", "conquer-once", "curve25519-dalek", + "data-encoding", "dialoguer", "directories-next", "ecdsa_fun", @@ -3703,10 +3815,12 @@ dependencies = [ "thiserror", "time 0.2.26", "tokio 1.5.0", + "tokio-socks", "tokio-tar", "tokio-tungstenite", "tokio-util", "toml", + "torut", "tracing", "tracing-appender", "tracing-futures", @@ -3717,15 +3831,26 @@ dependencies = [ "zip", ] +[[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.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "proc-macro2 1.0.24", + "quote 1.0.9", + "unicode-xid 0.2.1", ] [[package]] @@ -3734,10 +3859,10 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", + "unicode-xid 0.2.1", ] [[package]] @@ -3830,9 +3955,9 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3886,10 +4011,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", + "proc-macro2 1.0.24", + "quote 1.0.9", "standback", - "syn", + "syn 1.0.64", ] [[package]] @@ -3954,9 +4079,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -3970,6 +4095,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio 1.5.0", +] + [[package]] name = "tokio-stream" version = "0.1.4" @@ -4035,6 +4172,28 @@ dependencies = [ "serde", ] +[[package]] +name = "torut" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08dc613abef9de8afce27c7bb73aa735d60d0e479a81ba50a4cd3c382fc46fa3" +dependencies = [ + "base32", + "base64 0.10.1", + "derive_more", + "ed25519-dalek", + "hex 0.4.3", + "hmac 0.7.1", + "openssl", + "rand 0.7.3", + "serde", + "serde_derive", + "sha1", + "sha2 0.8.2", + "sha3", + "tokio 1.5.0", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -4070,9 +4229,9 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", ] [[package]] @@ -4268,6 +4427,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -4421,9 +4586,9 @@ dependencies = [ "bumpalo", "lazy_static", "log 0.4.14", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", "wasm-bindgen-shared", ] @@ -4445,7 +4610,7 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" dependencies = [ - "quote", + "quote 1.0.9", "wasm-bindgen-macro-support", ] @@ -4455,9 +4620,9 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4648,9 +4813,9 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.64", "synstructure", ] diff --git a/swap/Cargo.toml b/swap/Cargo.toml index bee6bde8..9d0608f5 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -22,6 +22,7 @@ bmrng = "0.5" config = { version = "0.11", default-features = false, features = ["toml"] } conquer-once = "0.3" curve25519-dalek = "3" +data-encoding = "2.3" dialoguer = "0.8" directories-next = "2" ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", features = ["libsecp_compat", "serde"] } @@ -36,7 +37,7 @@ pem = "0.8" prettytable-rs = "0.8" rand = "0.7" rand_chacha = "0.2" -reqwest = { version = "0.11", features = ["rustls-tls", "stream"], default-features = false } +reqwest = { version = "0.11", features = ["rustls-tls", "stream", "socks"], default-features = false } rust_decimal = "1" serde = { version = "1", features = ["derive"] } serde_cbor = "0.11" @@ -48,10 +49,12 @@ structopt = "0.3" strum = { version = "0.20", features = ["derive"] } thiserror = "1" time = "0.2" -tokio = { version = "1", features = ["rt-multi-thread", "time", "macros", "sync", "process", "fs"] } +tokio = { version = "1", features = ["rt-multi-thread", "time", "macros", "sync", "process", "fs", "net"] } +tokio-socks = "0.5" tokio-tungstenite = { version = "0.14", features = [ "rustls-tls" ] } tokio-util = { version = "0.6", features = ["io"] } toml = "0.5" +torut = "0.1" tracing = { version = "0.1", features = ["attributes"] } tracing-appender = "0.1" tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] } diff --git a/swap/src/asb/config.rs b/swap/src/asb/config.rs index 25aec58a..5c720988 100644 --- a/swap/src/asb/config.rs +++ b/swap/src/asb/config.rs @@ -1,4 +1,5 @@ use crate::fs::{default_data_dir, ensure_directory_exists}; +use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT}; use anyhow::{Context, Result}; use config::ConfigError; use dialoguer::theme::ColorfulTheme; @@ -22,6 +23,7 @@ pub struct Config { pub network: Network, pub bitcoin: Bitcoin, pub monero: Monero, + pub tor: TorConf, } impl Config { @@ -61,6 +63,22 @@ pub struct Monero { pub wallet_rpc_url: Url, } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct TorConf { + pub control_port: u16, + pub socks5_port: u16, +} + +impl Default for TorConf { + fn default() -> Self { + Self { + control_port: DEFAULT_CONTROL_PORT, + socks5_port: DEFAULT_SOCKS5_PORT, + } + } +} + #[derive(thiserror::Error, Debug, Clone, Copy)] #[error("config not initialized")] pub struct ConfigNotInitialized {} @@ -133,6 +151,17 @@ pub fn query_user_for_initial_testnet_config() -> Result { .default(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL.to_owned()) .interact_text()?; let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?; + + let tor_control_port = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter Tor control port or hit enter to use default. If Tor is not running on your machine, no hidden service will be created.") + .default(DEFAULT_CONTROL_PORT.to_owned()) + .interact_text()?; + + let tor_socks5_port = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter Tor socks5 port or hit enter to use default") + .default(DEFAULT_SOCKS5_PORT.to_owned()) + .interact_text()?; + println!(); Ok(Config { @@ -144,6 +173,10 @@ pub fn query_user_for_initial_testnet_config() -> Result { monero: Monero { wallet_rpc_url: monero_wallet_rpc_url, }, + tor: TorConf { + control_port: tor_control_port, + socks5_port: tor_socks5_port, + }, }) } @@ -175,6 +208,7 @@ mod tests { monero: Monero { wallet_rpc_url: Url::from_str(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL).unwrap(), }, + tor: Default::default(), }; initial_setup(config_path.clone(), || Ok(expected.clone())).unwrap(); diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 45e25ff1..e2aea45f 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -13,8 +13,11 @@ #![allow(non_snake_case)] use anyhow::{Context, Result}; +use libp2p::core::multiaddr::Protocol; +use libp2p::core::Multiaddr; use libp2p::Swarm; use prettytable::{row, Table}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; use structopt::StructOpt; use swap::asb::command::{Arguments, Command}; @@ -29,7 +32,8 @@ use swap::network::swarm; use swap::protocol::alice::event_loop::KrakenRate; use swap::protocol::alice::{run, EventLoop}; use swap::seed::Seed; -use swap::{asb, bitcoin, env, kraken, monero}; +use swap::tor::AuthenticatedClient; +use swap::{asb, bitcoin, env, kraken, monero, tor}; use tracing::{info, warn}; use tracing_subscriber::filter::LevelFilter; @@ -97,6 +101,23 @@ async fn main() -> Result<()> { let kraken_price_updates = kraken::connect()?; + // setup Tor hidden services + let tor_client = + tor::Client::new(config.tor.socks5_port).with_control_port(config.tor.control_port); + let _ac = match tor_client.assert_tor_running().await { + Ok(_) => { + tracing::info!("Tor found. Setting up hidden service. "); + let ac = + register_tor_services(config.network.clone().listen, tor_client, &seed) + .await?; + Some(ac) + } + Err(_) => { + tracing::warn!("Tor not found. Running on clear net. "); + None + } + }; + let mut swarm = swarm::alice(&seed)?; for listen in config.network.listen { @@ -212,3 +233,45 @@ async fn init_monero_wallet( Ok(wallet) } + +/// Registers a hidden service for each network. +/// Note: Once ac goes out of scope, the services will be de-registered. +async fn register_tor_services( + networks: Vec, + tor_client: tor::Client, + seed: &Seed, +) -> Result { + let mut ac = tor_client.into_authenticated_client().await?; + + let hidden_services_details = networks + .iter() + .flat_map(|network| { + network.iter().map(|protocol| match protocol { + Protocol::Tcp(port) => Some(( + port, + SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), port), + )), + _ => { + // We only care for Tcp for now. + None + } + }) + }) + .filter_map(|details| details) + .collect::>(); + + let key = seed.derive_torv3_key(); + + ac.add_services(&hidden_services_details, &key).await?; + + let onion_address = key + .public() + .get_onion_address() + .get_address_without_dot_onion(); + + hidden_services_details.iter().for_each(|(port, _)| { + tracing::info!("/onion3/{}:{}", onion_address, port); + }); + + Ok(ac) +} diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index e66c7514..eb5e9322 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -51,6 +51,7 @@ async fn main() -> Result<()> { monero_daemon_host, }, electrum_rpc_url, + tor_socks5_port, } => { let swap_id = Uuid::new_v4(); @@ -76,7 +77,7 @@ async fn main() -> Result<()> { init_monero_wallet(data_dir, monero_daemon_host, env_config).await?; let bitcoin_wallet = Arc::new(bitcoin_wallet); - let mut swarm = swarm::bob(&seed, alice_peer_id)?; + let mut swarm = swarm::bob(&seed, alice_peer_id, tor_socks5_port).await?; swarm .behaviour_mut() .add_address(alice_peer_id, alice_multiaddr); @@ -152,6 +153,7 @@ async fn main() -> Result<()> { monero_daemon_host, }, electrum_rpc_url, + tor_socks5_port, } => { let data_dir = data.0; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; @@ -172,7 +174,8 @@ async fn main() -> Result<()> { let bitcoin_wallet = Arc::new(bitcoin_wallet); let alice_peer_id = db.get_peer_id(swap_id)?; - let mut swarm = swarm::bob(&seed, alice_peer_id)?; + + let mut swarm = swarm::bob(&seed, alice_peer_id, tor_socks5_port).await?; let bob_peer_id = swarm.local_peer_id(); tracing::debug!("Our peer-id: {}", bob_peer_id); swarm diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 295dbfd0..90db2c3c 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -13,6 +13,8 @@ pub const DEFAULT_STAGENET_MONERO_DAEMON_HOST: &str = "monero-stagenet.exan.tech pub const DEFAULT_ELECTRUM_HTTP_URL: &str = "https://blockstream.info/testnet/api/"; const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:60002"; +const DEFAULT_TOR_SOCKS5_PORT: &str = "9050"; + #[derive(structopt::StructOpt, Debug)] #[structopt(name = "swap", about = "CLI for swapping BTC for XMR", author)] pub struct Arguments { @@ -48,6 +50,9 @@ pub enum Command { #[structopt(flatten)] monero_params: MoneroParams, + + #[structopt(long = "tor-socks5-port", help = "Your local Tor socks5 proxy port", default_value = DEFAULT_TOR_SOCKS5_PORT)] + tor_socks5_port: u16, }, /// Show a list of past ongoing and completed swaps History, @@ -70,6 +75,9 @@ pub enum Command { #[structopt(flatten)] monero_params: MoneroParams, + + #[structopt(long = "tor-socks5-port", help = "Your local Tor socks5 proxy port", default_value = DEFAULT_TOR_SOCKS5_PORT)] + tor_socks5_port: u16, }, /// Try to cancel an ongoing swap (expert users only) Cancel { diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 4af15cc1..3fb58372 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -27,5 +27,6 @@ pub mod monero; pub mod network; pub mod protocol; pub mod seed; +pub mod tor; mod monero_ext; diff --git a/swap/src/network.rs b/swap/src/network.rs index 211445c3..59b2b46f 100644 --- a/swap/src/network.rs +++ b/swap/src/network.rs @@ -7,5 +7,6 @@ pub mod quote; pub mod redial; pub mod spot_price; pub mod swarm; +pub mod tor_transport; pub mod transfer_proof; pub mod transport; diff --git a/swap/src/network/swarm.rs b/swap/src/network/swarm.rs index 67a24581..ebcce5fa 100644 --- a/swap/src/network/swarm.rs +++ b/swap/src/network/swarm.rs @@ -1,24 +1,53 @@ use crate::network::transport; use crate::protocol::{alice, bob}; use crate::seed::Seed; +use crate::tor; use anyhow::Result; use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; use libp2p::{PeerId, Swarm}; pub fn alice(seed: &Seed) -> Result> { - new(seed, alice::Behaviour::default()) + with_clear_net(seed, alice::Behaviour::default()) } -pub fn bob(seed: &Seed, alice: PeerId) -> Result> { - new(seed, bob::Behaviour::new(alice)) +pub async fn bob( + seed: &Seed, + alice: PeerId, + tor_socks5_port: u16, +) -> Result> { + let client = tor::Client::new(tor_socks5_port); + if client.assert_tor_running().await.is_ok() { + return with_tor(seed, bob::Behaviour::new(alice), tor_socks5_port).await; + } + with_clear_net(seed, bob::Behaviour::new(alice)) } -fn new(seed: &Seed, behaviour: B) -> Result> +fn with_clear_net(seed: &Seed, behaviour: B) -> Result> where B: NetworkBehaviour, { + tracing::info!("All connections will go through clear net."); let identity = seed.derive_libp2p_identity(); - let transport = transport::build(&identity)?; + let transport = transport::build_clear_net(&identity)?; + let peer_id = identity.public().into_peer_id(); + tracing::debug!("Our peer-id: {}", peer_id); + + let swarm = SwarmBuilder::new(transport, behaviour, peer_id) + .executor(Box::new(|f| { + tokio::spawn(f); + })) + .build(); + + Ok(swarm) +} + +async fn with_tor(seed: &Seed, behaviour: B, tor_socks5_port: u16) -> Result> +where + B: NetworkBehaviour, +{ + tracing::info!("All connections will go through Tor socks5 proxy."); + let identity = seed.derive_libp2p_identity(); + let transport = transport::build_tor(&identity, tor_socks5_port)?; let peer_id = identity.public().into_peer_id(); tracing::debug!("Our peer-id: {}", peer_id); diff --git a/swap/src/network/tor_transport.rs b/swap/src/network/tor_transport.rs new file mode 100644 index 00000000..33abd376 --- /dev/null +++ b/swap/src/network/tor_transport.rs @@ -0,0 +1,238 @@ +use data_encoding::BASE32; +use futures::future::Ready; +use futures::prelude::*; +use libp2p::core::multiaddr::{Multiaddr, Protocol}; +use libp2p::core::transport::TransportError; +use libp2p::core::Transport; +use libp2p::tcp::tokio::{Tcp, TcpStream}; +use libp2p::tcp::{GenTcpConfig, TcpListenStream, TokioTcpConfig}; +use std::cmp::Ordering; +use std::io; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::pin::Pin; +use tokio_socks::tcp::Socks5Stream; +use tokio_socks::IntoTargetAddr; + +/// Represents the configuration for a TCP/IP transport capability for libp2p. +#[derive(Clone)] +pub struct TorTcpConfig { + inner: GenTcpConfig, + /// Tor SOCKS5 proxy port number. + socks_port: u16, +} + +impl TorTcpConfig { + pub fn new(tcp: TokioTcpConfig, socks_port: u16) -> Self { + Self { + inner: tcp, + socks_port, + } + } +} + +impl Transport for TorTcpConfig { + type Output = TcpStream; + type Error = io::Error; + type Listener = TcpListenStream; + type ListenerUpgrade = Ready>; + #[allow(clippy::type_complexity)] + type Dial = Pin> + Send>>; + + fn listen_on(self, addr: Multiaddr) -> Result> { + self.inner.listen_on(addr) + } + + // dials via Tor's socks5 proxy if configured and if the provided address is an + // onion address. or it falls back to Tcp dialling + fn dial(self, addr: Multiaddr) -> Result> { + async fn do_tor_dial(socks_port: u16, dest: String) -> Result { + tracing::trace!("Connecting through Tor proxy to address: {}", dest); + let stream = connect_to_socks_proxy(dest, socks_port) + .await + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; + tracing::trace!("Connection through Tor established"); + Ok(stream) + } + + match to_address_string(addr.clone()) { + Some(tor_address_string) => { + Ok(Box::pin(do_tor_dial(self.socks_port, tor_address_string))) + } + _ => { + tracing::warn!( + "Address {} could not be formatted. Dialling via clear net", + addr + ); + self.inner.dial(addr) + } + } + } + + fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { + self.inner.address_translation(listen, observed) + } +} + +/// Tor expects an address format of ADDR:PORT. +/// This helper function tries to convert the provided multi-address into this +/// format. None is returned if an unsupported protocol was provided. +fn to_address_string(multi: Multiaddr) -> Option { + let components = multi.iter(); + for protocol in components { + match protocol { + Protocol::Onion(addr, port) => { + tracing::warn!("Onion service v2 is being deprecated, consider upgrading to v3"); + return Some(format!( + "{}.onion:{}", + BASE32.encode(addr.as_ref()).to_lowercase(), + port + )); + } + Protocol::Onion3(addr) => { + return Some(format!( + "{}.onion:{}", + BASE32.encode(addr.hash()).to_lowercase(), + addr.port() + )); + } + _ => { + // ignore + } + } + } + + // Deal with non-onion addresses + let protocols = multi.iter().collect::>(); + let address_string = protocols + .iter() + .filter_map(|protocol| match protocol { + Protocol::Ip4(addr) => Some(format!("{}", addr)), + Protocol::Ip6(addr) => Some(format!("{}", addr)), + Protocol::Dns(addr) => Some(format!("{}", addr)), + Protocol::Dns4(addr) => Some(format!("{}", addr)), + _ => None, + }) + .collect::>(); + if address_string.is_empty() { + tracing::warn!( + "Could not format address {}. Please consider reporting this issue. ", + multi + ); + return None; + } + + let address_string = address_string + .get(0) + .expect("Valid multiaddr consist out of max 1 address") + .clone(); + + // check for port + let port = protocols + .iter() + .filter_map(|protocol| match protocol { + Protocol::Tcp(port) => Some(format!("{}", port)), + Protocol::Udp(port) => Some(format!("{}", port)), + _ => None, + }) + .collect::>(); + + match port.len().cmp(&1) { + Ordering::Greater => { + tracing::warn!( + "Did not expect more than 1 port in address {}. Please consider reporting this issue.", + multi + ); + Some(address_string) + } + Ordering::Less => Some(address_string), + Ordering::Equal => Some(format!( + "{}:{}", + address_string, + port.get(0) + .expect("Already verified the length of the vec.") + )), + } +} + +/// Connect to the SOCKS5 proxy socket. +async fn connect_to_socks_proxy<'a>( + dest: impl IntoTargetAddr<'a>, + port: u16, +) -> Result { + let sock = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)); + let stream = Socks5Stream::connect(sock, dest).await?; + Ok(TcpStream(stream.into_inner())) +} + +#[cfg(test)] +pub mod test { + use crate::network::tor_transport::to_address_string; + + #[test] + fn test_tor_address_string() { + let address = + "/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9" + ; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a multi formatted address."); + assert_eq!( + address_string, + "oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024" + ); + } + + #[test] + fn tcp_to_address_string_should_be_some() { + let address = "/ip4/127.0.0.1/tcp/7777"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "127.0.0.1:7777"); + } + + #[test] + fn ip6_to_address_string_should_be_some() { + let address = "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777"); + } + + #[test] + fn udp_to_address_string_should_be_some() { + let address = "/ip4/127.0.0.1/udp/7777"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "127.0.0.1:7777"); + } + + #[test] + fn ws_to_address_string_should_be_some() { + let address = "/ip4/127.0.0.1/tcp/7777/ws"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "127.0.0.1:7777"); + } + + #[test] + fn dns4_to_address_string_should_be_some() { + let address = "/dns4/randomdomain.com/tcp/7777"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "randomdomain.com:7777"); + } + + #[test] + fn dns_to_address_string_should_be_some() { + let address = "/dns/randomdomain.com/tcp/7777"; + let address_string = + to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. "); + assert_eq!(address_string, "randomdomain.com:7777"); + } + + #[test] + fn dnsaddr_to_address_string_should_be_none() { + let address = "/dnsaddr/randomdomain.com"; + let address_string = to_address_string(address.parse().unwrap()); + assert_eq!(address_string, None); + } +} diff --git a/swap/src/network/transport.rs b/swap/src/network/transport.rs index eec3dadb..b5f01f85 100644 --- a/swap/src/network/transport.rs +++ b/swap/src/network/transport.rs @@ -1,13 +1,14 @@ +use crate::network::tor_transport::TorTcpConfig; use anyhow::Result; use libp2p::core::muxing::StreamMuxerBox; use libp2p::core::transport::Boxed; use libp2p::core::upgrade::{SelectUpgrade, Version}; -use libp2p::core::{identity, Transport}; use libp2p::dns::TokioDnsConfig; use libp2p::mplex::MplexConfig; use libp2p::noise::{self, NoiseConfig, X25519Spec}; +use libp2p::tcp::TokioTcpConfig; use libp2p::websocket::WsConfig; -use libp2p::{yamux, PeerId}; +use libp2p::{identity, yamux, PeerId, Transport}; use std::time::Duration; /// Builds a libp2p transport with the following features: @@ -16,13 +17,41 @@ use std::time::Duration; /// - DNS name resolution /// - authentication via noise /// - multiplexing via yamux or mplex -pub fn build(id_keys: &identity::Keypair) -> Result { - use libp2p::tcp::TokioTcpConfig; +pub fn build_clear_net(id_keys: &identity::Keypair) -> Result { + let dh_keys = noise::Keypair::::new().into_authentic(id_keys)?; + let noise = NoiseConfig::xx(dh_keys).into_authenticated(); + + let tcp = TokioTcpConfig::new().nodelay(true); + let dns = TokioDnsConfig::system(tcp)?; + let websocket = WsConfig::new(dns.clone()); + + let transport = websocket + .or_transport(dns) + .upgrade(Version::V1) + .authenticate(noise) + .multiplex(SelectUpgrade::new( + yamux::YamuxConfig::default(), + MplexConfig::new(), + )) + .timeout(Duration::from_secs(20)) + .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) + .boxed(); + Ok(transport) +} + +/// Builds a libp2p transport with the following features: +/// - TorTcpConnection +/// - WebSocketConnection +/// - DNS name resolution +/// - authentication via noise +/// - multiplexing via yamux or mplex +pub fn build_tor(id_keys: &identity::Keypair, tor_socks5_port: u16) -> Result { let dh_keys = noise::Keypair::::new().into_authentic(id_keys)?; let noise = NoiseConfig::xx(dh_keys).into_authenticated(); let tcp = TokioTcpConfig::new().nodelay(true); + let tcp = TorTcpConfig::new(tcp, tor_socks5_port); let dns = TokioDnsConfig::system(tcp)?; let websocket = WsConfig::new(dns.clone()); diff --git a/swap/src/tor.rs b/swap/src/tor.rs new file mode 100644 index 00000000..97978b0c --- /dev/null +++ b/swap/src/tor.rs @@ -0,0 +1,154 @@ +use anyhow::{anyhow, bail, Context, Result}; +use std::future::Future; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use tokio::net::TcpStream; +use torut::control::{AsyncEvent, AuthenticatedConn, ConnError, UnauthenticatedConn}; +use torut::onion::TorSecretKeyV3; + +pub const DEFAULT_SOCKS5_PORT: u16 = 9050; +pub const DEFAULT_CONTROL_PORT: u16 = 9051; + +#[derive(Debug, Clone, Copy)] +pub struct Client { + socks5_address: SocketAddrV4, + control_port_address: SocketAddr, +} + +impl Default for Client { + fn default() -> Self { + Self { + socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_SOCKS5_PORT), + control_port_address: SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::LOCALHOST, + DEFAULT_CONTROL_PORT, + )), + } + } +} + +impl Client { + pub fn new(socks5_port: u16) -> Self { + Self { + socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, socks5_port), + control_port_address: SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::LOCALHOST, + DEFAULT_CONTROL_PORT, + )), + } + } + pub fn with_control_port(self, control_port: u16) -> Self { + Self { + control_port_address: SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::LOCALHOST, + control_port, + )), + ..self + } + } + + /// checks if tor is running + pub async fn assert_tor_running(&self) -> Result<()> { + // Make sure you are running tor and this is your socks port + let proxy = reqwest::Proxy::all(format!("socks5h://{}", self.socks5_address).as_str()) + .map_err(|_| anyhow!("tor proxy should be there"))?; + let client = reqwest::Client::builder().proxy(proxy).build()?; + + let res = client.get("https://check.torproject.org").send().await?; + let text = res.text().await?; + + if !text.contains("Congratulations. This browser is configured to use Tor.") { + bail!("Tor is currently not running") + } + + Ok(()) + } + + async fn init_unauthenticated_connection(&self) -> Result> { + // Connect to local tor service via control port + let sock = TcpStream::connect(self.control_port_address).await?; + let uc = UnauthenticatedConn::new(sock); + Ok(uc) + } + + /// Create a new authenticated connection to your local Tor service + pub async fn into_authenticated_client(self) -> Result { + self.assert_tor_running().await?; + + let mut uc = self + .init_unauthenticated_connection() + .await + .map_err(|_| anyhow!("Could not connect to Tor. Tor might not be running or the control port is incorrect."))?; + + let tor_info = uc + .load_protocol_info() + .await + .map_err(|_| anyhow!("Failed to load protocol info from Tor."))?; + + let tor_auth_data = tor_info + .make_auth_data()? + .context("Failed to make Tor auth data.")?; + + // Get an authenticated connection to the Tor via the Tor Controller protocol. + uc.authenticate(&tor_auth_data) + .await + .map_err(|_| anyhow!("Failed to authenticate with Tor"))?; + + Ok(AuthenticatedClient { + inner: uc.into_authenticated().await, + }) + } + + pub fn tor_proxy_port(&self) -> u16 { + self.socks5_address.port() + } +} + +type Handler = fn(AsyncEvent<'_>) -> Box> + Unpin>; + +#[allow(missing_debug_implementations)] +pub struct AuthenticatedClient { + inner: AuthenticatedConn, +} + +impl AuthenticatedClient { + /// Add an ephemeral tor service on localhost with the provided key + /// `service_port` and `onion_port` can be different but don't have to as + /// they are on different networks. + pub async fn add_service( + &mut self, + service_port: u16, + onion_port: u16, + tor_key: &TorSecretKeyV3, + ) -> Result<()> { + self.inner + .add_onion_v3( + tor_key, + false, + false, + false, + None, + &mut [( + onion_port, + SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), service_port), + )] + .iter(), + ) + .await + .map_err(|e| anyhow!("Could not add onion service.: {:#?}", e)) + } + + /// Add an ephemeral tor service on localhost with the provided key + /// `service_port` and `onion_port` can be different but don't have to as + /// they are on different networks. + pub async fn add_services( + &mut self, + services: &[(u16, SocketAddr)], + tor_key: &TorSecretKeyV3, + ) -> Result<()> { + let mut listeners = services.iter(); + self.inner + .add_onion_v3(tor_key, false, false, false, None, &mut listeners) + .await + .map_err(|e| anyhow!("Could not add onion service.: {:#?}", e)) + } +} diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 09ef421a..fcdc77bb 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -383,8 +383,8 @@ struct BobParams { } impl BobParams { - pub fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, bob::EventLoop)> { - let (event_loop, handle) = self.new_eventloop(swap_id)?; + pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, bob::EventLoop)> { + let (event_loop, handle) = self.new_eventloop(swap_id).await?; let db = Database::open(&self.db_path)?; let swap = bob::Swap::from_db( @@ -400,10 +400,13 @@ impl BobParams { Ok((swap, event_loop)) } - pub fn new_swap(&self, btc_amount: bitcoin::Amount) -> Result<(bob::Swap, bob::EventLoop)> { + pub async fn new_swap( + &self, + btc_amount: bitcoin::Amount, + ) -> Result<(bob::Swap, bob::EventLoop)> { let swap_id = Uuid::new_v4(); - let (event_loop, handle) = self.new_eventloop(swap_id)?; + let (event_loop, handle) = self.new_eventloop(swap_id).await?; let db = Database::open(&self.db_path)?; let swap = bob::Swap::new( @@ -420,8 +423,13 @@ impl BobParams { Ok((swap, event_loop)) } - pub fn new_eventloop(&self, swap_id: Uuid) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { - let mut swarm = swarm::bob(&self.seed, self.alice_peer_id)?; + pub async fn new_eventloop( + &self, + swap_id: Uuid, + ) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { + let tor_socks5_port = get_port() + .expect("We don't care about Tor in the tests so we get a free port to disable it."); + let mut swarm = swarm::bob(&self.seed, self.alice_peer_id, tor_socks5_port).await?; swarm .behaviour_mut() .add_address(self.alice_peer_id, self.alice_address.clone()); @@ -501,7 +509,7 @@ impl TestContext { } pub async fn bob_swap(&mut self) -> (bob::Swap, BobApplicationHandle) { - let (swap, event_loop) = self.bob_params.new_swap(self.btc_amount).unwrap(); + let (swap, event_loop) = self.bob_params.new_swap(self.btc_amount).await.unwrap(); // ensure the wallet is up to date for concurrent swap tests swap.bitcoin_wallet.sync().await.unwrap(); @@ -518,7 +526,7 @@ impl TestContext { ) -> (bob::Swap, BobApplicationHandle) { join_handle.abort(); - let (swap, event_loop) = self.bob_params.new_swap_from_db(swap_id).unwrap(); + let (swap, event_loop) = self.bob_params.new_swap_from_db(swap_id).await.unwrap(); let join_handle = tokio::spawn(event_loop.run());