diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-06-16 15:13:32 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-08-20 14:41:41 +0200 |
| commit | 8cf02b29a718a7856c80323ee0cf496b9ee24648 (patch) | |
| tree | 16f378f2cc3d3101d01d58435bf54824e57683a6 | |
| parent | c2e9303cc7aff29df7941fc08df19b8ffcffa48f (diff) | |
| download | mullvadvpn-8cf02b29a718a7856c80323ee0cf496b9ee24648.tar.xz mullvadvpn-8cf02b29a718a7856c80323ee0cf496b9ee24648.zip | |
Use gRPC for management interface in backend and CLI
47 files changed, 3098 insertions, 2163 deletions
diff --git a/Cargo.lock b/Cargo.lock index 1c7a581d0d..64dc79c378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,11 +74,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "assert_matches" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "async-stream" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -99,7 +94,7 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.31" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -180,14 +175,6 @@ dependencies = [ ] [[package]] -name = "bstr" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "bumpalo" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -785,30 +772,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "globset" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "globset" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "h2" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1027,20 +990,6 @@ dependencies = [ [[package]] name = "jsonrpc-client-core" version = "0.5.0" -source = "git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b#68aac55b6ddff5e1242594b54f7f9149fe215ff7" -dependencies = [ - "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-utils 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-client-core" -version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1052,43 +1001,6 @@ dependencies = [ ] [[package]] -name = "jsonrpc-client-ipc" -version = "0.5.0" -source = "git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b#68aac55b6ddff5e1242594b54f7f9149fe215ff7" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-server-utils 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-tokio-ipc 0.2.0 (git+https://github.com/nikvolf/parity-tokio-ipc)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-client-pubsub" -version = "0.1.0" -source = "git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b#68aac55b6ddff5e1242594b54f7f9149fe215ff7" -dependencies = [ - "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-utils 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-client-utils" -version = "0.1.0" -source = "git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b#68aac55b6ddff5e1242594b54f7f9149fe215ff7" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "jsonrpc-core" version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1101,79 +1013,6 @@ dependencies = [ ] [[package]] -name = "jsonrpc-core" -version = "8.0.2" -source = "git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork#9de0ea1ad4c296465f44f53050d99459ca885157" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-ipc-server" -version = "8.0.1" -source = "git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork#9de0ea1ad4c296465f44f53050d99459ca885157" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-server-utils 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-tokio-ipc 0.2.0 (git+https://github.com/nikvolf/parity-tokio-ipc)", - "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-macros" -version = "8.0.1" -source = "git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork#9de0ea1ad4c296465f44f53050d99459ca885157" -dependencies = [ - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-pubsub 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-pubsub" -version = "8.0.1" -source = "git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork#9de0ea1ad4c296465f44f53050d99459ca885157" -dependencies = [ - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-server-utils" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "globset 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jsonrpc-server-utils" -version = "8.0.1" -source = "git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork#9de0ea1ad4c296465f44f53050d99459ca885157" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1207,15 +1046,6 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lock_api" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1248,14 +1078,6 @@ dependencies = [ [[package]] name = "memchr" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1360,19 +1182,26 @@ dependencies = [ name = "mullvad-cli" version = "2020.5.0" dependencies = [ + "async-trait 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "err-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "mullvad-ipc-client 0.1.0", + "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "mullvad-paths 0.1.0", "mullvad-types 0.1.0", "natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-tokio-ipc 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-types 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "talpid-ipc 0.1.0", "talpid-types 0.1.0", + "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tonic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tonic-build 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tower 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1393,33 +1222,33 @@ dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "ipnetwork 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-ipc-server 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-macros 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-pubsub 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mullvad-ipc-client 0.1.0", "mullvad-paths 0.1.0", "mullvad-rpc 0.1.0", "mullvad-types 0.1.0", "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-tokio-ipc 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-types 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "simple-signal 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "talpid-core 0.1.0", - "talpid-ipc 0.1.0", "talpid-types 0.1.0", "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-retry 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tonic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tonic-build 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tower 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "triggered 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "windows-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1437,26 +1266,6 @@ dependencies = [ ] [[package]] -name = "mullvad-ipc-client" -version = "0.1.0" -dependencies = [ - "err-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-ipc 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-pubsub 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mullvad-paths 0.1.0", - "mullvad-types 0.1.0", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "talpid-ipc 0.1.0", - "talpid-types 0.1.0", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "mullvad-jni" version = "0.1.0" dependencies = [ @@ -1544,41 +1353,18 @@ dependencies = [ "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "err-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "mullvad-ipc-client 0.1.0", "mullvad-paths 0.1.0", - "talpid-core 0.1.0", - "talpid-types 0.1.0", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mullvad-tests" -version = "0.1.0" -dependencies = [ - "duct 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-ipc 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-pubsub 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "mullvad-ipc-client 0.1.0", - "mullvad-paths 0.1.0", - "mullvad-rpc 0.1.0", - "mullvad-types 0.1.0", - "notify 4.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "openvpn-plugin 0.3.0 (git+https://github.com/mullvad/openvpn-plugin-rs?branch=auth-failed-event)", "parity-tokio-ipc 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "talpid-ipc 0.1.0", + "prost-types 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "talpid-core 0.1.0", "talpid-types 0.1.0", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tonic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "tonic-build 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tower 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1596,6 +1382,7 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "talpid-types 0.1.0", + "tonic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1831,31 +1618,6 @@ dependencies = [ ] [[package]] -name = "owning_ref" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parity-tokio-ipc" -version = "0.2.0" -source = "git+https://github.com/nikvolf/parity-tokio-ipc#2bfc4354fda235f0249b963853701e1571f04dbc" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-named-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "parity-tokio-ipc" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1872,15 +1634,6 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1891,18 +1644,6 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -2177,18 +1918,6 @@ dependencies = [ [[package]] name = "rand" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -2534,11 +2263,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "scopeguard" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "scopeguard" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2696,11 +2420,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "stable_deref_trait" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2845,8 +2564,6 @@ dependencies = [ "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipnetwork 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "jnix 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-macros 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2873,7 +2590,6 @@ dependencies = [ "shell-escape 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", "system-configuration 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "talpid-ipc 0.1.0", "talpid-types 0.1.0", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2893,28 +2609,6 @@ dependencies = [ ] [[package]] -name = "talpid-ipc" -version = "0.1.0" -dependencies = [ - "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "err-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-client-ipc 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)", - "jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-ipc-server 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-macros 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "jsonrpc-pubsub 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "talpid-openvpn-plugin" version = "2020.5.0" dependencies = [ @@ -3143,18 +2837,6 @@ dependencies = [ ] [[package]] -name = "tokio-named-pipes" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "tokio-reactor" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3330,7 +3012,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "async-trait 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3930,10 +3612,9 @@ dependencies = [ "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum ascii 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" -"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" "checksum async-stream 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" "checksum async-stream-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -"checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" +"checksum async-trait 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" @@ -3944,7 +3625,6 @@ dependencies = [ "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" -"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245" "checksum bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" @@ -4014,8 +3694,6 @@ dependencies = [ "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum globset 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90d069fe6beb9be359ef505650b3f73228c5591a3c4b1f32be2f4f44459ffa3a" -"checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum h2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e" @@ -4039,29 +3717,17 @@ dependencies = [ "checksum jnix 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6354ae923ca4df982181ae2cd77eb4214f8c11d11d0c0cd8606c9347ac2abc57" "checksum jnix-macros 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c78132fe420156f13b30518fcda9449b0ab8ae3b5584e8a1c53ce390fe770b44" "checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" -"checksum jsonrpc-client-core 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)" = "<none>" "checksum jsonrpc-client-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f29cb249837420fb0cee7fb0fbf1d22679e121b160e71bb5e0d90b9df241c23e" -"checksum jsonrpc-client-ipc 0.5.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)" = "<none>" -"checksum jsonrpc-client-pubsub 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)" = "<none>" -"checksum jsonrpc-client-utils 0.1.0 (git+https://github.com/mullvad/jsonrpc-client-rs?rev=68aac55b)" = "<none>" "checksum jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf83704f4e79979a424d1082dd2c1e52683058056c9280efa19ac5f6bc9033c" -"checksum jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)" = "<none>" -"checksum jsonrpc-ipc-server 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)" = "<none>" -"checksum jsonrpc-macros 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)" = "<none>" -"checksum jsonrpc-pubsub 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)" = "<none>" -"checksum jsonrpc-server-utils 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "513e981828a4953ea7ddbb64c24d15d4983ecf6900dc1cd36f257d61c27138d5" -"checksum jsonrpc-server-utils 8.0.1 (git+https://github.com/mullvad/jsonrpc?branch=mullvad-fork)" = "<none>" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" "checksum libdbus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" -"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ae0136257df209261daa18d6c16394757c63e032e27aafd8b07788b051082bef" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" "checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" @@ -4096,12 +3762,8 @@ dependencies = [ "checksum openvpn-plugin 0.3.0 (git+https://github.com/mullvad/openvpn-plugin-rs?branch=auth-failed-event)" = "<none>" "checksum os_pipe 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "81e8dfa4c69d7bde595e9a940fcf1d7f60966d3fce8a8c4cad67c60e35ea2a11" "checksum os_pipe 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "db4d06355a7090ce852965b2d08e11426c315438462638c6d721448d0b47aa22" -"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" -"checksum parity-tokio-ipc 0.2.0 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>" "checksum parity-tokio-ipc 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c1d417ba1ab454723ff2271bf999fd700027dc48759a13d43e488cc8ca38b87f" -"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -"checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum paste 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab4fb1930692d1b6a9cfabdde3d06ea0a7d186518e2f4d67660d8970e2fa647a" "checksum paste-impl 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "a62486e111e571b1e93b710b61e8f493c0013be39629b714cb166bdb06aa5a8a" @@ -4133,7 +3795,6 @@ dependencies = [ "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -4173,7 +3834,6 @@ dependencies = [ "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" "checksum schannel 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" "checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" "checksum security-framework 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" @@ -4194,7 +3854,6 @@ dependencies = [ "checksum snailquote 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fc3e2894a343234fb8a8653cf9d49ef6aea44e6581612ca311c91c4bd356dec4" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" "checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" @@ -4226,7 +3885,6 @@ dependencies = [ "checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" "checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" -"checksum tokio-named-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d282d483052288b2308ba5ee795f5673b159c9bdf63c385a05609da782a5eae" "checksum tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d" "checksum tokio-retry 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c03755b956458582182941061def32b8123a26c98b08fc6ddcf49ae89d18f33" "checksum tokio-rustls 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" diff --git a/Cargo.toml b/Cargo.toml index fab359c830..fed2deb291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,14 @@ members = [ "mullvad-cli", "mullvad-setup", "mullvad-problem-report", - "mullvad-ipc-client", "mullvad-jni", "mullvad-paths", "mullvad-types", "mullvad-rpc", - "mullvad-tests", +# "mullvad-tests", "mullvad-exclude", "talpid-openvpn-plugin", "talpid-core", - "talpid-ipc", ] exclude = ["dist-assets/binaries/shadowsocks-rust"] diff --git a/mullvad-cli/Cargo.toml b/mullvad-cli/Cargo.toml index 084be415fe..5cb9ecb187 100644 --- a/mullvad-cli/Cargo.toml +++ b/mullvad-cli/Cargo.toml @@ -16,6 +16,7 @@ name = "mullvad" path = "src/main.rs" [dependencies] +async-trait = "0.1" base64 = "0.10" chrono = { version = "0.4", features = ["serde"] } clap = "2.32" @@ -25,11 +26,21 @@ futures = "0.1" natord = "1.0.9" serde = "1.0" -mullvad-ipc-client = { path = "../mullvad-ipc-client" } mullvad-types = { path = "../mullvad-types" } mullvad-paths = { path = "../mullvad-paths" } talpid-types = { path = "../talpid-types" } -talpid-ipc = { path = "../talpid-ipc" } + +tonic = "0.2" +tower = "0.3" +prost = "0.6" +prost-types = "0.6" +futures03 = { package = "futures", version = "0.3", features = [ "compat" ]} +tokio = { version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream"] } + +parity-tokio-ipc = "0.7" + +[build-dependencies] +tonic-build = { version = "0.2", default-features = false, features = ["transport", "prost"] } [target.'cfg(windows)'.build-dependencies] winres = "0.1" diff --git a/mullvad-cli/build.rs b/mullvad-cli/build.rs index 4c19603b76..6d1586a655 100644 --- a/mullvad-cli/build.rs +++ b/mullvad-cli/build.rs @@ -1,6 +1,8 @@ use std::{env, fs, path::PathBuf}; fn main() { + tonic_build::compile_protos("../mullvad-daemon/proto/management_interface.proto").unwrap(); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let product_version = env!("CARGO_PKG_VERSION").replacen(".0", "", 1); fs::write(out_dir.join("product-version.txt"), &product_version).unwrap(); diff --git a/mullvad-cli/src/cmds/account.rs b/mullvad-cli/src/cmds/account.rs index e35699605c..a0a223eaf2 100644 --- a/mullvad-cli/src/cmds/account.rs +++ b/mullvad-cli/src/cmds/account.rs @@ -1,9 +1,10 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Error, Result}; use clap::value_t_or_exit; use mullvad_types::account::{AccountToken, VoucherError}; pub struct Account; +#[async_trait::async_trait] impl Command for Account { fn name(&self) -> &'static str { "account" @@ -49,21 +50,21 @@ impl Command for Account { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let token = value_t_or_exit!(set_matches.value_of("token"), String); - self.set(Some(token)) + self.set(Some(token)).await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else if let Some(_matches) = matches.subcommand_matches("unset") { - self.set(None) + self.set(None).await } else if let Some(_matches) = matches.subcommand_matches("clear-history") { - self.clear_history() + self.clear_history().await } else if let Some(_matches) = matches.subcommand_matches("create") { - self.create() + self.create().await } else if let Some(matches) = matches.subcommand_matches("redeem") { let voucher = value_t_or_exit!(matches.value_of("voucher"), String); - self.redeem_voucher(voucher) + self.redeem_voucher(voucher).await } else { unreachable!("No account command given"); } @@ -71,9 +72,9 @@ impl Command for Account { } impl Account { - fn set(&self, token: Option<AccountToken>) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_account(token.clone())?; + async fn set(&self, token: Option<AccountToken>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_account(token.clone().unwrap_or_default()).await?; if let Some(token) = token { println!("Mullvad account \"{}\" set", token); } else { @@ -82,45 +83,55 @@ impl Account { Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let settings = rpc.get_settings()?; - if let Some(account_token) = settings.get_account_token() { - println!("Mullvad account: {}", account_token); - let expiry = rpc.get_account_data(account_token)?; - println!("Expires at : {}", expiry.expiry); + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + if settings.account_token != "" { + println!("Mullvad account: {}", settings.account_token); + let expiry = rpc + .get_account_data(settings.account_token) + .await? + .into_inner(); + println!( + "Expires at : {}", + Self::format_expiry(&expiry.expiry.unwrap()) + ); } else { println!("No account configured"); } Ok(()) } - fn create(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.create_new_account()?; + async fn create(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.create_new_account(()).await?; println!("New account created!"); - self.get() + self.get().await } - fn redeem_voucher(&self, mut voucher: String) -> Result<()> { - let mut rpc = new_rpc_client()?; + async fn redeem_voucher(&self, mut voucher: String) -> Result<()> { + let mut rpc = new_grpc_client().await?; voucher.retain(|c| c.is_alphanumeric()); - match rpc.submit_voucher(voucher) { + match rpc.submit_voucher(voucher).await { Ok(submission) => { + let submission = submission.into_inner(); println!( "Added {} to the account", - Self::format_duration(submission.time_added) + Self::format_duration(submission.seconds_added) + ); + println!( + "New expiry date: {}", + Self::format_expiry(&submission.new_expiry.unwrap()) ); - println!("New expiry date: {}", submission.new_expiry); Ok(()) } Err(err) => { eprintln!( "Failed to submit voucher.\n{}", - VoucherError::from_rpc_error_code(Self::get_redeem_rpc_error_code(&err)) + VoucherError::from_rpc_error_code(err.code() as i64) ); - Err(err.into()) + Err(Error::GrpcClientError(err)) } } } @@ -138,16 +149,13 @@ impl Account { } } - fn get_redeem_rpc_error_code(error: &mullvad_ipc_client::Error) -> i64 { - match error.kind() { - mullvad_ipc_client::ErrorKind::JsonRpcError(ref rpc_error) => rpc_error.code.code(), - _ => 0, - } + fn format_expiry(expiry: &prost_types::Timestamp) -> String { + chrono::NaiveDateTime::from_timestamp(expiry.seconds, expiry.nanos as u32).to_string() } - fn clear_history(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.clear_account_history()?; + async fn clear_history(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.clear_account_history(()).await?; println!("Removed account history and all associated keys"); Ok(()) } diff --git a/mullvad-cli/src/cmds/auto_connect.rs b/mullvad-cli/src/cmds/auto_connect.rs index 385a2de7b6..e934e8e37c 100644 --- a/mullvad-cli/src/cmds/auto_connect.rs +++ b/mullvad-cli/src/cmds/auto_connect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct AutoConnect; +#[async_trait::async_trait] impl Command for AutoConnect { fn name(&self) -> &'static str { "auto-connect" @@ -27,12 +28,12 @@ impl Command for AutoConnect { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let auto_connect = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(auto_connect == "on") + self.set(auto_connect == "on").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No auto-connect command given"); } @@ -40,16 +41,16 @@ impl Command for AutoConnect { } impl AutoConnect { - fn set(&self, auto_connect: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_auto_connect(auto_connect)?; + async fn set(&self, auto_connect: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_auto_connect(auto_connect).await?; println!("Changed auto-connect sharing setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let auto_connect = rpc.get_settings()?.auto_connect; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let auto_connect = rpc.get_settings(()).await?.into_inner().auto_connect; println!("Autoconnect: {}", if auto_connect { "on" } else { "off" }); Ok(()) } diff --git a/mullvad-cli/src/cmds/beta_program.rs b/mullvad-cli/src/cmds/beta_program.rs index c4cbe6cf21..3950d54433 100644 --- a/mullvad-cli/src/cmds/beta_program.rs +++ b/mullvad-cli/src/cmds/beta_program.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Error, Result, PRODUCT_VERSION}; +use crate::{new_grpc_client, Command, Error, Result, PRODUCT_VERSION}; use clap::value_t_or_exit; pub struct BetaProgram; +#[async_trait::async_trait] impl Command for BetaProgram { fn name(&self) -> &'static str { "beta-program" @@ -24,11 +25,11 @@ impl Command for BetaProgram { .subcommand(clap::SubCommand::with_name("get").about("Get beta notifications setting")) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("get", Some(_)) => { - let mut rpc = new_rpc_client()?; - let settings = rpc.get_settings()?; + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); let enabled_str = if settings.show_beta_releases { "on" } else { @@ -47,8 +48,8 @@ impl Command for BetaProgram { )); } - let mut rpc = new_rpc_client()?; - rpc.set_show_beta_releases(enable)?; + let mut rpc = new_grpc_client().await?; + rpc.set_show_beta_releases(enable).await?; println!("Beta program: {}", enable_str); Ok(()) diff --git a/mullvad-cli/src/cmds/block_when_disconnected.rs b/mullvad-cli/src/cmds/block_when_disconnected.rs index 57239963d7..b66acf168d 100644 --- a/mullvad-cli/src/cmds/block_when_disconnected.rs +++ b/mullvad-cli/src/cmds/block_when_disconnected.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct BlockWhenDisconnected; +#[async_trait::async_trait] impl Command for BlockWhenDisconnected { fn name(&self) -> &'static str { "always-require-vpn" @@ -27,12 +28,12 @@ impl Command for BlockWhenDisconnected { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let block_when_disconnected = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(block_when_disconnected == "on") + self.set(block_when_disconnected == "on").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No block-when-disconnected command given"); } @@ -40,16 +41,21 @@ impl Command for BlockWhenDisconnected { } impl BlockWhenDisconnected { - fn set(&self, block_when_disconnected: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_block_when_disconnected(block_when_disconnected)?; + async fn set(&self, block_when_disconnected: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_block_when_disconnected(block_when_disconnected) + .await?; println!("Changed always require VPN setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let block_when_disconnected = rpc.get_settings()?.block_when_disconnected; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let block_when_disconnected = rpc + .get_settings(()) + .await? + .into_inner() + .block_when_disconnected; println!( "Network traffic will be {} when the VPN is disconnected", if block_when_disconnected { diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 0e5c339744..7ad4cf1b22 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -1,13 +1,18 @@ -use crate::{location, new_rpc_client, Command, Result}; +use crate::{location, new_grpc_client, Command, Result}; use clap::value_t; -use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings, BridgeState}; -use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; +use crate::proto::{ + bridge_settings::{Type as BridgeSettingsType, *}, + bridge_state::State as BridgeStateType, + BridgeSettings, BridgeState, +}; +use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; use std::net::{IpAddr, SocketAddr}; pub struct Bridge; +#[async_trait::async_trait] impl Command for Bridge { fn name(&self) -> &'static str { "bridge" @@ -24,11 +29,11 @@ impl Command for Bridge { .subcommand(clap::SubCommand::with_name("list").about("List bridge relays")) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("set", Some(set_matches)) => Self::handle_set(set_matches), - ("get", _) => Self::handle_get(), - ("list", _) => Self::list_bridge_relays(), + ("set", Some(set_matches)) => Self::handle_set(set_matches).await, + ("get", _) => Self::handle_get().await, + ("list", _) => Self::list_bridge_relays().await, _ => unreachable!("unhandled command"), } } @@ -145,64 +150,66 @@ fn create_set_state_subcommand() -> clap::App<'static, 'static> { } impl Bridge { - fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("location", Some(location_matches)) => { - Self::handle_set_bridge_location(location_matches) + Self::handle_set_bridge_location(location_matches).await } ("custom", Some(custom_matches)) => { - Self::handle_bridge_set_custom_settings(custom_matches) + Self::handle_bridge_set_custom_settings(custom_matches).await } - ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches), + ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches).await, _ => unreachable!("unhandled command"), } } - fn handle_get() -> Result<()> { - let mut rpc = new_rpc_client()?; - let settings = rpc.get_settings()?; - println!("Bridge state - {}", settings.get_bridge_state()); - match settings.bridge_settings { - BridgeSettings::Custom(proxy) => { - match proxy { - openvpn::ProxySettings::Local(local_proxy) => { - Self::print_local_proxy(&local_proxy) - } - openvpn::ProxySettings::Remote(remote_proxy) => { - Self::print_remote_proxy(&remote_proxy) - } - openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) => { - Self::print_shadowsocks_proxy(&shadowsocks_proxy) - } - }; + async fn handle_get() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + Self::print_state(settings.bridge_state.unwrap()); + match settings.bridge_settings.unwrap().r#type.unwrap() { + BridgeSettingsType::Local(local_proxy) => Self::print_local_proxy(&local_proxy), + BridgeSettingsType::Remote(remote_proxy) => Self::print_remote_proxy(&remote_proxy), + BridgeSettingsType::Shadowsocks(shadowsocks_proxy) => { + Self::print_shadowsocks_proxy(&shadowsocks_proxy) } - BridgeSettings::Normal(constraints) => { - println!("Bridge constraints: {}", constraints); + BridgeSettingsType::Normal(constraints) => { + println!( + "Bridge constraints - {}", + location::format_location(constraints.location.as_ref()) + ); } }; Ok(()) } - fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> { - let location = location::get_constraint(matches); - let mut rpc = new_rpc_client()?; - rpc.set_bridge_settings(BridgeSettings::Normal(BridgeConstraints { location }))?; + async fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> { + let constraints = location::get_constraint(matches); + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Normal(BridgeConstraints { + location: Some(constraints), + })), + }) + .await?; Ok(()) } - fn handle_set_bridge_state(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_set_bridge_state(matches: &clap::ArgMatches<'_>) -> Result<()> { let state = match matches.value_of("state").unwrap() { - "auto" => BridgeState::Auto, - "on" => BridgeState::On, - "off" => BridgeState::Off, + "auto" => BridgeStateType::Auto as i32, + "on" => BridgeStateType::On as i32, + "off" => BridgeStateType::Off as i32, _ => unreachable!(), }; - let mut rpc = new_rpc_client()?; - rpc.set_bridge_state(state)?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_state(BridgeState { state }).await?; Ok(()) } - fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> { + use talpid_types::net::openvpn; + if let Some(args) = matches.subcommand_matches("local") { let local_port = value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit()); @@ -211,19 +218,24 @@ impl Bridge { let remote_port = value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); - let proxy = openvpn::LocalProxySettings { + let local_proxy = openvpn::LocalProxySettings { port: local_port, peer: SocketAddr::new(remote_ip, remote_port), }; - - let packed_proxy = openvpn::ProxySettings::Local(proxy); - + let prost_proxy = LocalProxySettings { + port: local_proxy.port as u32, + peer: local_proxy.peer.to_string(), + }; + let packed_proxy = openvpn::ProxySettings::Local(local_proxy); if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { panic!(error); } - let mut rpc = new_rpc_client()?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Local(prost_proxy)), + }) + .await?; } else if let Some(args) = matches.subcommand_matches("remote") { let remote_ip = value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); @@ -239,20 +251,30 @@ impl Bridge { }), _ => None, }; + let prost_auth = auth.clone().map(|auth| RemoteProxyAuth { + username: auth.username.clone(), + password: auth.password.clone(), + }); let proxy = openvpn::RemoteProxySettings { address: SocketAddr::new(remote_ip, remote_port), auth, }; + let prost_proxy = RemoteProxySettings { + address: proxy.address.to_string(), + auth: prost_auth, + }; let packed_proxy = openvpn::ProxySettings::Remote(proxy); - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { panic!(error); } - let mut rpc = new_rpc_client()?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Remote(prost_proxy)), + }) + .await?; } else if let Some(args) = matches.subcommand_matches("shadowsocks") { let remote_ip = value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); @@ -266,15 +288,22 @@ impl Bridge { password, cipher, }; + let prost_proxy = ShadowsocksProxySettings { + peer: proxy.peer.to_string(), + password: proxy.password.clone(), + cipher: proxy.cipher.clone(), + }; let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy); - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { panic!(error); } - let mut rpc = new_rpc_client()?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Shadowsocks(prost_proxy)), + }) + .await?; } else { unreachable!("unhandled proxy type"); } @@ -283,17 +312,24 @@ impl Bridge { Ok(()) } - fn print_local_proxy(proxy: &openvpn::LocalProxySettings) { + fn print_state(state: BridgeState) { + let state = match BridgeStateType::from_i32(state.state).expect("unknown bridge state") { + BridgeStateType::Auto => "auto", + BridgeStateType::On => "on", + BridgeStateType::Off => "off", + }; + println!("Bridge state - {}", state); + } + + fn print_local_proxy(proxy: &LocalProxySettings) { println!("proxy: local"); println!(" local port: {}", proxy.port); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); + println!(" peer address: {}", proxy.peer); } - fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) { + fn print_remote_proxy(proxy: &RemoteProxySettings) { println!("proxy: remote"); - println!(" server IP: {}", proxy.address.ip()); - println!(" server port: {}", proxy.address.port()); + println!(" server address: {}", proxy.address); if let Some(ref auth) = proxy.auth { println!(" auth username: {}", auth.username); @@ -303,47 +339,43 @@ impl Bridge { } } - fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) { + fn print_shadowsocks_proxy(proxy: &ShadowsocksProxySettings) { println!("proxy: Shadowsocks"); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); + println!(" peer address: {}", proxy.peer); println!(" password: {}", proxy.password); println!(" cipher: {}", proxy.cipher); } - fn list_bridge_relays() -> Result<()> { - let mut rpc = new_rpc_client()?; - let mut locations = rpc.get_relay_locations()?; + async fn list_bridge_relays() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let mut locations = rpc.get_relay_locations(()).await?.into_inner(); - locations.countries = locations - .countries - .into_iter() - .filter_map(|mut country| { - country.cities = country - .cities - .into_iter() - .filter_map(|mut city| { - city.relays - .retain(|relay| relay.active && !relay.bridges.is_empty()); - if !city.relays.is_empty() { - Some(city) - } else { - None - } - }) - .collect(); - if !country.cities.is_empty() { - Some(country) - } else { - None - } - }) - .collect(); + let mut countries = Vec::new(); + + while let Some(mut country) = locations.message().await? { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays.retain(|relay| { + relay.active + && relay.bridges.is_some() + && !relay.bridges.as_ref().unwrap().shadowsocks.is_empty() + }); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + countries.push(country); + } + } - locations - .countries - .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); - for mut country in locations.countries { + countries.sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); + for mut country in countries { country .cities .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); diff --git a/mullvad-cli/src/cmds/connect.rs b/mullvad-cli/src/cmds/connect.rs index 89cc0f2d2e..899a450547 100644 --- a/mullvad-cli/src/cmds/connect.rs +++ b/mullvad-cli/src/cmds/connect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use talpid_types::ErrorExt; pub struct Connect; +#[async_trait::async_trait] impl Command for Connect { fn name(&self) -> &'static str { "connect" @@ -13,9 +14,9 @@ impl Command for Connect { .about("Command the client to start establishing a VPN tunnel") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - if let Err(e) = rpc.connect() { + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + if let Err(e) = rpc.connect_tunnel(()).await { eprintln!("{}", e.display_chain()); } Ok(()) diff --git a/mullvad-cli/src/cmds/disconnect.rs b/mullvad-cli/src/cmds/disconnect.rs index bf85f80310..a3d6698fc9 100644 --- a/mullvad-cli/src/cmds/disconnect.rs +++ b/mullvad-cli/src/cmds/disconnect.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; pub struct Disconnect; +#[async_trait::async_trait] impl Command for Disconnect { fn name(&self) -> &'static str { "disconnect" @@ -12,9 +13,9 @@ impl Command for Disconnect { .about("Command the client to disconnect the VPN tunnel") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.disconnect()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.disconnect_tunnel(()).await?; Ok(()) } } diff --git a/mullvad-cli/src/cmds/lan.rs b/mullvad-cli/src/cmds/lan.rs index 15d30cde50..05f4f867d2 100644 --- a/mullvad-cli/src/cmds/lan.rs +++ b/mullvad-cli/src/cmds/lan.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct Lan; +#[async_trait::async_trait] impl Command for Lan { fn name(&self) -> &'static str { "lan" @@ -27,12 +28,12 @@ impl Command for Lan { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let allow_lan = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(allow_lan == "allow") + self.set(allow_lan == "allow").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No lan command given"); } @@ -40,16 +41,16 @@ impl Command for Lan { } impl Lan { - fn set(&self, allow_lan: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_allow_lan(allow_lan)?; + async fn set(&self, allow_lan: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_allow_lan(allow_lan).await?; println!("Changed local network sharing setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let allow_lan = rpc.get_settings()?.allow_lan; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let allow_lan = rpc.get_settings(()).await?.into_inner().allow_lan; println!( "Local network sharing setting: {}", if allow_lan { "allow" } else { "block" } diff --git a/mullvad-cli/src/cmds/reconnect.rs b/mullvad-cli/src/cmds/reconnect.rs index 0cc7f6bfea..d281266e11 100644 --- a/mullvad-cli/src/cmds/reconnect.rs +++ b/mullvad-cli/src/cmds/reconnect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use talpid_types::ErrorExt; pub struct Reconnect; +#[async_trait::async_trait] impl Command for Reconnect { fn name(&self) -> &'static str { "reconnect" @@ -12,9 +13,9 @@ impl Command for Reconnect { clap::SubCommand::with_name(self.name()).about("Command the client to reconnect") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - if let Err(e) = rpc.reconnect() { + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + if let Err(e) = rpc.reconnect_tunnel(()).await { eprintln!("{}", e.display_chain()); } Ok(()) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index c4e8c72c81..07d07fc447 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -1,4 +1,4 @@ -use crate::{location, new_rpc_client, Command, Error, Result}; +use crate::{location, new_grpc_client, proto, Command, Error, Result}; use clap::{value_t, values_t}; use std::{ io::{self, BufRead}, @@ -6,19 +6,18 @@ use std::{ str::FromStr, }; -use mullvad_types::{ - relay_constraints::{ - Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettingsUpdate, - WireguardConstraints, - }, - ConnectionConfig, CustomTunnelEndpoint, -}; -use talpid_types::net::{ - all_of_the_internet, openvpn, wireguard, Endpoint, TransportProtocol, TunnelType, +use mullvad_types::relay_constraints::Constraint; +use proto::{ + connection_config::{self, OpenvpnConfig, WireguardConfig}, + relay_settings, relay_settings_update, ConnectionConfig, CustomRelaySettings, + NormalRelaySettingsUpdate, OpenvpnConstraints, RelaySettingsUpdate, TransportProtocol, + TunnelType, TunnelTypeUpdate, WireguardConstraints, }; +use talpid_types::net::all_of_the_internet; pub struct Relay; +#[async_trait::async_trait] impl Command for Relay { fn name(&self) -> &'static str { "relay" @@ -155,15 +154,15 @@ impl Command for Relay { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { - self.set(set_matches) + self.set(set_matches).await } else if matches.subcommand_matches("get").is_some() { - self.get() + self.get().await } else if matches.subcommand_matches("list").is_some() { - self.list() + self.list().await } else if matches.subcommand_matches("update").is_some() { - self.update() + self.update().await } else { unreachable!("No relay command given"); } @@ -171,54 +170,73 @@ impl Command for Relay { } impl Relay { - fn update_constraints(&self, update: RelaySettingsUpdate) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.update_relay_settings(update)?; + async fn update_constraints(&self, update: RelaySettingsUpdate) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.update_relay_settings(update).await?; println!("Relay constraints updated"); Ok(()) } - fn set(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(custom_matches) = matches.subcommand_matches("custom") { - self.set_custom(custom_matches) + self.set_custom(custom_matches).await } else if let Some(location_matches) = matches.subcommand_matches("location") { - self.set_location(location_matches) + self.set_location(location_matches).await } else if let Some(tunnel_matches) = matches.subcommand_matches("tunnel") { - self.set_tunnel(tunnel_matches) + self.set_tunnel(tunnel_matches).await } else if let Some(tunnel_matches) = matches.subcommand_matches("tunnel-protocol") { - self.set_tunnel_protocol(tunnel_matches) + self.set_tunnel_protocol(tunnel_matches).await } else { unreachable!("No set relay command given"); } } - fn set_custom(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_custom(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let custom_endpoint = match matches.subcommand() { ("openvpn", Some(openvpn_matches)) => Self::read_custom_openvpn_relay(openvpn_matches), ("wireguard", Some(wg_matches)) => Self::read_custom_wireguard_relay(wg_matches), (_unknown_tunnel, _) => unreachable!("No set relay command given"), }; - self.update_constraints(RelaySettingsUpdate::CustomTunnelEndpoint(custom_endpoint)) + + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Custom(custom_endpoint)), + }) + .await } - fn read_custom_openvpn_relay(matches: &clap::ArgMatches<'_>) -> CustomTunnelEndpoint { + fn read_custom_openvpn_relay(matches: &clap::ArgMatches<'_>) -> CustomRelaySettings { let host = value_t!(matches.value_of("host"), String).unwrap_or_else(|e| e.exit()); let port = value_t!(matches.value_of("port"), u16).unwrap_or_else(|e| e.exit()); let username = value_t!(matches.value_of("username"), String).unwrap_or_else(|e| e.exit()); let password = value_t!(matches.value_of("password"), String).unwrap_or_else(|e| e.exit()); - let protocol = - value_t!(matches.value_of("protocol"), TransportProtocol).unwrap_or_else(|e| e.exit()); - CustomTunnelEndpoint::new( + let protocol = value_t!(matches.value_of("protocol"), String).unwrap_or_else(|e| e.exit()); + + let protocol = match protocol.as_str() { + "udp" => TransportProtocol::Udp, + "tcp" => TransportProtocol::Tcp, + _ => clap::Error::with_description( + "unknown transport protocol", + clap::ErrorKind::ValueValidation, + ) + .exit(), + }; + + CustomRelaySettings { host, - ConnectionConfig::OpenVpn(openvpn::ConnectionConfig { - endpoint: Endpoint::new(Ipv4Addr::UNSPECIFIED, port, protocol), - username, - password, + config: Some(ConnectionConfig { + config: Some(connection_config::Config::Openvpn(OpenvpnConfig { + address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port).to_string(), + protocol: protocol as i32, + username, + password, + })), }), - ) + } } - fn read_custom_wireguard_relay(matches: &clap::ArgMatches<'_>) -> CustomTunnelEndpoint { + fn read_custom_wireguard_relay(matches: &clap::ArgMatches<'_>) -> CustomRelaySettings { + use connection_config::wireguard_config; + let host = value_t!(matches.value_of("host"), String).unwrap_or_else(|e| e.exit()); let port = value_t!(matches.value_of("port"), u16).unwrap_or_else(|e| e.exit()); let addresses = values_t!(matches.values_of("addr"), IpAddr).unwrap_or_else(|e| e.exit()); @@ -239,26 +257,37 @@ impl Relay { if private_key_str.trim().is_empty() { eprintln!("Expected to read private key from standard input"); } - let private_key = Self::validate_wireguard_key(&private_key_str).into(); - let peer_public_key = Self::validate_wireguard_key(&peer_key_str).into(); - + let private_key = Self::validate_wireguard_key(&private_key_str); + let peer_public_key = Self::validate_wireguard_key(&peer_key_str); - CustomTunnelEndpoint::new( + CustomRelaySettings { host, - ConnectionConfig::Wireguard(wireguard::ConnectionConfig { - tunnel: wireguard::TunnelConfig { - private_key, - addresses, - }, - peer: wireguard::PeerConfig { - public_key: peer_public_key, - allowed_ips: all_of_the_internet(), - endpoint: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port), - }, - ipv4_gateway, - ipv6_gateway, + config: Some(ConnectionConfig { + config: Some(connection_config::Config::Wireguard(WireguardConfig { + tunnel: Some(wireguard_config::TunnelConfig { + private_key: private_key.to_vec(), + addresses: addresses + .iter() + .map(|address| address.to_string()) + .collect(), + }), + peer: Some(wireguard_config::PeerConfig { + public_key: peer_public_key.to_vec(), + allowed_ips: all_of_the_internet() + .iter() + .map(|address| address.to_string()) + .collect(), + endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port) + .to_string(), + }), + ipv4_gateway: ipv4_gateway.to_string(), + ipv6_gateway: ipv6_gateway + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(), + })), }), - ) + } } fn validate_wireguard_key(key_str: &str) -> [u8; 32] { @@ -280,16 +309,21 @@ impl Relay { key } - fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let location_constraint = location::get_constraint(matches); - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: Some(location_constraint), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + location: Some(location_constraint), + ..Default::default() + }, + )), + }) + .await } - fn set_tunnel(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_tunnel(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let vpn_protocol = matches.value_of("vpn protocol").unwrap(); let port = parse_port_constraint(matches.value_of("port").unwrap())?; let protocol = parse_protocol_constraint(matches.value_of("transport protocol").unwrap()); @@ -299,79 +333,157 @@ impl Relay { if let Constraint::Only(TransportProtocol::Tcp) = protocol { return Err(Error::InvalidCommand("WireGuard does not support TCP")); } - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: None, - tunnel_protocol: None, - wireguard_constraints: Some(WireguardConstraints { port }), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + wireguard_constraints: Some(WireguardConstraints { + port: port.unwrap_or(0) as u32, + }), + ..Default::default() + }, + )), + }) + .await } "openvpn" => { - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: None, - tunnel_protocol: None, - openvpn_constraints: Some(OpenVpnConstraints { port, protocol }), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + openvpn_constraints: Some(OpenvpnConstraints { + port: port.unwrap_or(0) as u32, + protocol: protocol.unwrap_or(TransportProtocol::AnyProtocol) as i32, + }), + ..Default::default() + }, + )), + }) + .await } _ => unreachable!(), } } - fn set_tunnel_protocol(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let tunnel_protocol = match matches.value_of("tunnel protocol").unwrap() { - "wireguard" => Constraint::Only(TunnelType::Wireguard), - "openvpn" => Constraint::Only(TunnelType::OpenVpn), - "any" => Constraint::Any, + async fn set_tunnel_protocol(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + let tunnel_type = match matches.value_of("tunnel protocol").unwrap() { + "wireguard" => TunnelType::Wireguard, + "openvpn" => TunnelType::Openvpn, + "any" => TunnelType::AnyTunnel, _ => unreachable!(), }; - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - tunnel_protocol: Some(tunnel_protocol), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + tunnel_type: Some(TunnelTypeUpdate { + tunnel_type: tunnel_type as i32, + }), + ..Default::default() + }, + )), + }) + .await } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let constraints = rpc.get_settings()?.get_relay_settings(); - println!("Current constraints: {}", constraints); + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let constraints = rpc + .get_settings(()) + .await? + .into_inner() + .relay_settings + .unwrap(); + + print!("Current constraints: "); + + match constraints.endpoint.unwrap() { + relay_settings::Endpoint::Normal(settings) => { + match TunnelType::from_i32(settings.tunnel_type).expect("unknown tunnel type") { + TunnelType::AnyTunnel => { + println!( + "Any tunnel protocol with OpenVPN over {} and WireGuard over {} in {}", + Self::format_openvpn_constraints(settings.openvpn_constraints.as_ref()), + Self::format_wireguard_constraints( + settings.wireguard_constraints.as_ref() + ), + location::format_location(settings.location.as_ref()) + ); + } + TunnelType::Wireguard => { + println!( + "WireGuard over {} in {}", + Self::format_wireguard_constraints( + settings.wireguard_constraints.as_ref() + ), + location::format_location(settings.location.as_ref()) + ); + } + TunnelType::Openvpn => { + println!( + "OpenVPN over {} in {}", + Self::format_openvpn_constraints(settings.openvpn_constraints.as_ref()), + location::format_location(settings.location.as_ref()) + ); + } + } + } + + relay_settings::Endpoint::Custom(settings) => { + let config = settings.config.unwrap(); + match config.config.unwrap() { + connection_config::Config::Openvpn(config) => { + println!( + "custom OpenVPN relay - {} {}", + config.address, + Self::format_transport_protocol( + TransportProtocol::from_i32(config.protocol).unwrap() + ), + ); + } + connection_config::Config::Wireguard(config) => { + let peer = config.peer.unwrap(); + println!( + "custom WireGuard relay - {} with public key {}", + peer.endpoint, + base64::encode(&peer.public_key), + ); + } + } + } + } Ok(()) } - fn list(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let mut locations = rpc.get_relay_locations()?; + async fn list(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let mut locations = rpc.get_relay_locations(()).await?.into_inner(); - locations.countries = locations - .countries - .into_iter() - .filter_map(|mut country| { - country.cities = country - .cities - .into_iter() - .filter_map(|mut city| { - city.relays - .retain(|relay| relay.active && !relay.tunnels.is_empty()); - if !city.relays.is_empty() { - Some(city) - } else { - None - } - }) - .collect(); - if !country.cities.is_empty() { - Some(country) - } else { - None - } - }) - .collect(); + let mut countries = Vec::new(); - locations - .countries - .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); - for mut country in locations.countries { + while let Some(mut country) = locations.message().await? { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays.retain(|relay| { + relay.active + && relay.tunnels.is_some() + && !(relay.tunnels.as_ref().unwrap().openvpn.is_empty() + && relay.tunnels.as_ref().unwrap().wireguard.is_empty()) + }); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + countries.push(country); + } + } + + countries.sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); + for mut country in countries { country .cities .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); @@ -384,8 +496,9 @@ impl Relay { city.name, city.code, city.latitude, city.longitude ); for relay in &city.relays { - let supports_openvpn = !relay.tunnels.openvpn.is_empty(); - let supports_wireguard = !relay.tunnels.wireguard.is_empty(); + let tunnels = relay.tunnels.as_ref().unwrap(); + let supports_openvpn = !tunnels.openvpn.is_empty(); + let supports_wireguard = !tunnels.wireguard.is_empty(); let support_msg = match (supports_openvpn, supports_wireguard) { (true, true) => "OpenVPN and WireGuard", (true, false) => "OpenVPN", @@ -403,11 +516,49 @@ impl Relay { Ok(()) } - fn update(&self) -> Result<()> { - new_rpc_client()?.update_relay_locations()?; + async fn update(&self) -> Result<()> { + new_grpc_client().await?.update_relay_locations(()).await?; println!("Updating relay list in the background..."); Ok(()) } + + fn format_transport_protocol(protocol: TransportProtocol) -> &'static str { + match protocol { + TransportProtocol::AnyProtocol => "any transport protocol", + TransportProtocol::Udp => "UDP", + TransportProtocol::Tcp => "TCP", + } + } + + fn format_port(port: u32) -> String { + if port != 0 { + format!("port {}", port) + } else { + "any port".to_string() + } + } + + fn format_openvpn_constraints(constraints: Option<&OpenvpnConstraints>) -> String { + if let Some(constraints) = constraints { + format!( + "{} over {}", + Self::format_port(constraints.port), + Self::format_transport_protocol( + TransportProtocol::from_i32(constraints.protocol).unwrap() + ) + ) + } else { + "any port over any transport protocol".to_string() + } + } + + fn format_wireguard_constraints(constraints: Option<&WireguardConstraints>) -> String { + if let Some(constraints) = constraints { + Self::format_port(constraints.port) + } else { + "any port".to_string() + } + } } diff --git a/mullvad-cli/src/cmds/reset.rs b/mullvad-cli/src/cmds/reset.rs index d859d1e436..2461e86ad5 100644 --- a/mullvad-cli/src/cmds/reset.rs +++ b/mullvad-cli/src/cmds/reset.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use std::io::stdin; pub struct Reset; +#[async_trait::async_trait] impl Command for Reset { fn name(&self) -> &'static str { "factory-reset" @@ -11,10 +12,10 @@ impl Command for Reset { clap::SubCommand::with_name(self.name()).about("Reset settings, caches and logs") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; if Self::receive_confirmation() { - if rpc.factory_reset().is_err() { + if rpc.factory_reset(()).await.is_err() { eprintln!("FAILED TO PERFORM FACTORY RESET"); } else { #[cfg(target_os = "linux")] diff --git a/mullvad-cli/src/cmds/split_tunnel/linux.rs b/mullvad-cli/src/cmds/split_tunnel/linux.rs index 95b172eb6b..5e8a9b9644 100644 --- a/mullvad-cli/src/cmds/split_tunnel/linux.rs +++ b/mullvad-cli/src/cmds/split_tunnel/linux.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct SplitTunnel; +#[async_trait::async_trait] impl Command for SplitTunnel { fn name(&self) -> &'static str { "split-tunnel" @@ -15,9 +16,9 @@ impl Command for SplitTunnel { .subcommand(create_pid_subcommand()) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("pid", Some(pid_matches)) => Self::handle_pid_cmd(pid_matches), + ("pid", Some(pid_matches)) => Self::handle_pid_cmd(pid_matches).await, _ => unreachable!("unhandled comand"), } } @@ -38,27 +39,40 @@ fn create_pid_subcommand() -> clap::App<'static, 'static> { } impl SplitTunnel { - fn handle_pid_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_pid_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("add", Some(matches)) => { let pid = value_t_or_exit!(matches.value_of("pid"), i32); - new_rpc_client()?.add_split_tunnel_process(pid)?; + new_grpc_client() + .await? + .add_split_tunnel_process(pid) + .await?; Ok(()) } ("delete", Some(matches)) => { let pid = value_t_or_exit!(matches.value_of("pid"), i32); - new_rpc_client()?.remove_split_tunnel_process(pid)?; + new_grpc_client() + .await? + .remove_split_tunnel_process(pid) + .await?; Ok(()) } ("clear", Some(_)) => { - new_rpc_client()?.clear_split_tunnel_processes()?; + new_grpc_client() + .await? + .clear_split_tunnel_processes(()) + .await?; Ok(()) } ("list", Some(_)) => { - let pids = new_rpc_client()?.get_split_tunnel_processes()?; + let mut pids_stream = new_grpc_client() + .await? + .get_split_tunnel_processes(()) + .await? + .into_inner(); println!("Excluded PIDs:"); - for pid in pids.iter() { + while let Some(pid) = pids_stream.message().await? { println!(" {}", pid); } diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index ffa834134e..ec05492c46 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -1,11 +1,19 @@ -use crate::{new_rpc_client, Command, Error, Result}; -use futures::{Future, Stream}; -use mullvad_ipc_client::DaemonRpcClient; -use mullvad_types::{auth_failed::AuthFailed, states::TunnelState, DaemonEvent}; -use talpid_types::tunnel::{ErrorState, ErrorStateCause}; +use crate::{format::print_keygen_event, new_grpc_client, proto, Command, Error, Result}; +use mullvad_types::auth_failed::AuthFailed; +use proto::{ + daemon_event::Event as EventType, + error_state::{ + firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause, + FirewallPolicyError, GenerationError, + }, + management_service_client::ManagementServiceClient, + ErrorState, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType, +}; +use std::fmt::Write; pub struct Status; +#[async_trait::async_trait] impl Command for Status { fn name(&self) -> &'static str { "status" @@ -31,127 +39,237 @@ impl Command for Status { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - let state = rpc.get_state()?; + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let state = rpc.get_tunnel_state(()).await?.into_inner(); print_state(&state); if matches.is_present("location") { - print_location(&mut rpc)?; + print_location(&mut rpc).await?; } if let Some(listen_matches) = matches.subcommand_matches("listen") { let verbose = listen_matches.is_present("verbose"); - let subscription = rpc - .daemon_event_subscribe() - .wait() - .map_err(Error::CantSubscribe)?; - for event in subscription.wait() { - match event? { - DaemonEvent::TunnelState(new_state) => { + + let mut events = rpc.events_listen(()).await?.into_inner(); + + while let Some(event) = events.message().await? { + match event.event.unwrap() { + EventType::TunnelState(new_state) => { print_state(&new_state); - use self::TunnelState::*; - match new_state { - Connected { .. } | Disconnected => { + use proto::tunnel_state::State::*; + match new_state.state.unwrap() { + Connected(..) | Disconnected(..) => { if matches.is_present("location") { - print_location(&mut rpc)?; + print_location(&mut rpc).await?; } } _ => {} } } - DaemonEvent::Settings(settings) => { + EventType::Settings(settings) => { if verbose { println!("New settings: {:#?}", settings); } } - DaemonEvent::RelayList(relay_list) => { + EventType::RelayList(relay_list) => { if verbose { println!("New relay list: {:#?}", relay_list); } } - DaemonEvent::AppVersionInfo(app_version_info) => { + EventType::VersionInfo(app_version_info) => { if verbose { println!("New app version info: {:#?}", app_version_info); } } - DaemonEvent::WireguardKey(key_event) => { + EventType::KeyEvent(key_event) => { if verbose { - println!("{}", key_event); + print!("Key event: "); + print_keygen_event(&key_event); } } } } } + Ok(()) } } fn print_state(state: &TunnelState) { - use self::TunnelState::*; + use proto::{tunnel_state, tunnel_state::State::*}; + print!("Tunnel status: "); - match state { - Error(reason) => print_error_state(reason), - Connected { endpoint, .. } => { - println!("Connected to {}", endpoint); + match state.state.as_ref().unwrap() { + Error(error) => print_error_state(error.error_state.as_ref().unwrap()), + Connected(tunnel_state::Connected { relay_info }) => { + let endpoint = relay_info + .as_ref() + .unwrap() + .tunnel_endpoint + .as_ref() + .unwrap(); + println!("Connected to {}", format_endpoint(&endpoint)); } - Connecting { endpoint, .. } => println!("Connecting to {}...", endpoint), - Disconnected => println!("Disconnected"), + Connecting(tunnel_state::Connecting { relay_info }) => { + let endpoint = relay_info + .as_ref() + .unwrap() + .tunnel_endpoint + .as_ref() + .unwrap(); + println!("Connecting to {}...", format_endpoint(&endpoint)); + } + Disconnected(_) => println!("Disconnected"), Disconnecting(_) => println!("Disconnecting..."), } } +fn format_endpoint(endpoint: &TunnelEndpoint) -> String { + let mut out = format!( + "{} {} over {}", + match TunnelType::from_i32(endpoint.tunnel_type).expect("unknown tunnel protocol") { + TunnelType::Wireguard => "WireGuard", + TunnelType::Openvpn => "OpenVPN", + TunnelType::AnyTunnel => panic!("unexpected tunnel protocol"), + }, + endpoint.address, + format_protocol( + TransportProtocol::from_i32(endpoint.protocol).expect("unknown transport protocol") + ), + ); + + if let Some(ref proxy) = endpoint.proxy { + write!( + &mut out, + " via {} {} over {}", + match ProxyType::from_i32(proxy.proxy_type).expect("unknown proxy type") { + ProxyType::Shadowsocks => "Shadowsocks", + ProxyType::Custom => "custom bridge", + }, + proxy.address, + format_protocol( + TransportProtocol::from_i32(proxy.protocol).expect("unknown transport protocol") + ), + ) + .unwrap(); + } + + out +} + fn print_error_state(error_state: &ErrorState) { - if !error_state.is_blocking() { + if !error_state.is_blocking { eprintln!("Mullvad daemon failed to setup firewall rules!"); eprintln!("Deamon cannot block traffic from flowing, non-local traffic will leak"); } - print_blocked_reason(error_state.cause()); -} - -fn print_blocked_reason(reason: &ErrorStateCause) { - match reason { - ErrorStateCause::AuthFailed(ref auth_failure) => { - let auth_failure_str = auth_failure - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("Account authentication failed"); - println!("Blocked: {}", AuthFailed::from(auth_failure_str)); - } - #[cfg(target_os = "linux")] - ErrorStateCause::SetFirewallPolicyError(error) => { + match ErrorStateCause::from_i32(error_state.cause) { + Some(ErrorStateCause::AuthFailed) => { println!( "Blocked: {}", - ErrorStateCause::SetFirewallPolicyError(error.clone()) + AuthFailed::from(error_state.auth_fail_reason.as_ref()) ); + } + #[cfg(target_os = "linux")] + Some(ErrorStateCause::SetFirewallPolicyError) => { + println!("Blocked: {}", error_state_to_string(error_state)); println!("Your kernel might be terribly out of date or missing nftables"); } - other => println!("Blocked: {}", other), + _ => println!("Blocked: {}", error_state_to_string(error_state)), } } -fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> { - let location = match rpc.get_current_location()? { - Some(loc) => loc, - None => { - println!("Location data unavailable"); - return Ok(()); +fn error_state_to_string(error_state: &ErrorState) -> String { + use ErrorStateCause::*; + + let error_str = match ErrorStateCause::from_i32(error_state.cause).expect("unknown error cause") + { + AuthFailed => { + return if error_state.auth_fail_reason.is_empty() { + "Authentication with remote server failed".to_string() + } else { + format!( + "Authentication with remote server failed: {}", + error_state.auth_fail_reason + ) + }; + } + Ipv6Unavailable => "Failed to configure IPv6 because it's disabled in the platform", + SetFirewallPolicyError => { + return policy_error_to_string(error_state.policy_error.as_ref().unwrap()) } + SetDnsError => "Failed to set system DNS server", + StartTunnelError => "Failed to start connection to remote server", + TunnelParameterError => { + return format!( + "Failure to generate tunnel parameters: {}", + tunnel_parameter_error_to_string(error_state.parameter_error) + ); + } + IsOffline => "This device is offline, no tunnels can be established", + TapAdapterProblem => "A problem with the TAP adapter has been detected", + #[cfg(target_os = "android")] + VpnPermissionDenied => "The Android VPN permission was denied when creating the tunnel", + #[cfg(not(target_os = "android"))] + _ => unreachable!("unknown error cause"), }; - if let Some(hostname) = location.hostname { - println!("Relay: {}", hostname); + + error_str.to_string() +} + +fn tunnel_parameter_error_to_string(parameter_error: i32) -> &'static str { + match GenerationError::from_i32(parameter_error).expect("unknown generation error") { + GenerationError::NoMatchingRelay => "Failure to select a matching tunnel relay", + GenerationError::NoMatchingBridgeRelay => "Failure to select a matching bridge relay", + GenerationError::NoWireguardKey => "No wireguard key available", + GenerationError::CustomTunnelHostResolutionError => { + "Can't resolve hostname for custom tunnel host" + } } - if let Some(ipv4) = location.ipv4 { - println!("IPv4: {}", ipv4); +} + +fn policy_error_to_string(policy_error: &FirewallPolicyError) -> String { + let cause = match FirewallPolicyErrorType::from_i32(policy_error.r#type) + .expect("unknown policy error") + { + FirewallPolicyErrorType::Generic => return "Failed to set firewall policy".to_string(), + FirewallPolicyErrorType::Locked => format!( + "An application prevented the firewall policy from being set: {} (pid {})", + policy_error.lock_name, policy_error.lock_pid + ), + }; + format!("Failed to set firewall policy: {}", cause) +} + +async fn print_location( + rpc: &mut ManagementServiceClient<tonic::transport::Channel>, +) -> Result<()> { + let location = rpc.get_current_location(()).await; + let location = match location { + Ok(response) => response.into_inner(), + Err(status) => { + if status.code() == tonic::Code::NotFound { + println!("Location data unavailable"); + return Ok(()); + } else { + return Err(Error::GrpcClientError(status)); + } + } + }; + if !location.hostname.is_empty() { + println!("Relay: {}", location.hostname); + } + if !location.ipv4.is_empty() { + println!("IPv4: {}", location.ipv4); } - if let Some(ipv6) = location.ipv6 { - println!("IPv6: {}", ipv6); + if !location.ipv6.is_empty() { + println!("IPv6: {}", location.ipv6); } print!("Location: "); - if let Some(city) = location.city { - print!("{}, ", city); + if !location.city.is_empty() { + print!("{}, ", location.city); } println!("{}", location.country); @@ -161,3 +279,11 @@ fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> { ); Ok(()) } + +fn format_protocol(protocol: TransportProtocol) -> &'static str { + match protocol { + TransportProtocol::Udp => "UDP", + TransportProtocol::Tcp => "TCP", + TransportProtocol::AnyProtocol => panic!("unexpected transport protocol"), + } +} diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 5349174d4c..3c3d55092f 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -1,10 +1,10 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{format::print_keygen_event, new_grpc_client, proto, Command, Error, Result}; use clap::value_t; - -use mullvad_types::settings::TunnelOptions; +use proto::TunnelOptions; pub struct Tunnel; +#[async_trait::async_trait] impl Command for Tunnel { fn name(&self) -> &'static str { "tunnel" @@ -19,11 +19,11 @@ impl Command for Tunnel { .subcommand(create_ipv6_subcommand()) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("openvpn", Some(openvpn_matches)) => Self::handle_openvpn_cmd(openvpn_matches), - ("wireguard", Some(wg_matches)) => Self::handle_wireguard_cmd(wg_matches), - ("ipv6", Some(ipv6_matches)) => Self::handle_ipv6_cmd(ipv6_matches), + ("openvpn", Some(openvpn_matches)) => Self::handle_openvpn_cmd(openvpn_matches).await, + ("wireguard", Some(wg_matches)) => Self::handle_wireguard_cmd(wg_matches).await, + ("ipv6", Some(ipv6_matches)) => Self::handle_ipv6_cmd(ipv6_matches).await, _ => { unreachable!("unhandled comand"); } @@ -104,40 +104,42 @@ fn create_ipv6_subcommand() -> clap::App<'static, 'static> { } impl Tunnel { - fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("mssfix", Some(mssfix_matches)) => Self::handle_openvpn_mssfix_cmd(mssfix_matches), + ("mssfix", Some(mssfix_matches)) => { + Self::handle_openvpn_mssfix_cmd(mssfix_matches).await + } _ => unreachable!("unhandled command"), } } - fn handle_openvpn_mssfix_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_openvpn_mssfix_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("get", Some(_)) => Self::process_openvpn_mssfix_get(), - ("unset", Some(_)) => Self::process_openvpn_mssfix_unset(), - ("set", Some(set_matches)) => Self::process_openvpn_mssfix_set(set_matches), + ("get", Some(_)) => Self::process_openvpn_mssfix_get().await, + ("unset", Some(_)) => Self::process_openvpn_mssfix_unset().await, + ("set", Some(set_matches)) => Self::process_openvpn_mssfix_set(set_matches).await, _ => unreachable!("unhandled command"), } } - fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("mtu", Some(matches)) => match matches.subcommand() { - ("get", _) => Self::process_wireguard_mtu_get(), - ("set", Some(matches)) => Self::process_wireguard_mtu_set(matches), - ("unset", _) => Self::process_wireguard_mtu_unset(), + ("get", _) => Self::process_wireguard_mtu_get().await, + ("set", Some(matches)) => Self::process_wireguard_mtu_set(matches).await, + ("unset", _) => Self::process_wireguard_mtu_unset().await, _ => unreachable!("unhandled command"), }, ("key", Some(matches)) => match matches.subcommand() { - ("check", _) => Self::process_wireguard_key_check(), - ("regenerate", _) => Self::process_wireguard_key_generate(), + ("check", _) => Self::process_wireguard_key_check().await, + ("regenerate", _) => Self::process_wireguard_key_generate().await, ("rotation-interval", Some(matches)) => match matches.subcommand() { - ("get", _) => Self::process_wireguard_rotation_interval_get(), + ("get", _) => Self::process_wireguard_rotation_interval_get().await, ("set", Some(matches)) => { - Self::process_wireguard_rotation_interval_set(matches) + Self::process_wireguard_rotation_interval_set(matches).await } - ("reset", _) => Self::process_wireguard_rotation_interval_reset(), + ("reset", _) => Self::process_wireguard_rotation_interval_reset().await, _ => unreachable!("unhandled command"), }, _ => unreachable!("unhandled command"), @@ -147,140 +149,150 @@ impl Tunnel { } } - fn process_wireguard_mtu_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_wireguard_mtu_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; + let mtu = tunnel_options.wireguard.unwrap().mtu; println!( "mtu: {}", - tunnel_options - .wireguard - .mtu - .map(|mtu| mtu.to_string()) - .unwrap_or_else(|| "unset".to_owned()) + if mtu != 0 { + mtu.to_string() + } else { + "unset".to_string() + }, ); Ok(()) } - fn process_wireguard_mtu_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_wireguard_mtu_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let mtu = value_t!(matches.value_of("mtu"), u16).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_mtu(Some(mtu))?; + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_mtu(mtu as u32).await?; println!("Wireguard MTU has been updated"); Ok(()) } - fn process_wireguard_mtu_unset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_mtu(None)?; + async fn process_wireguard_mtu_unset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_mtu(0).await?; println!("Wireguard MTU has been unset"); Ok(()) } - fn process_wireguard_key_check() -> Result<()> { - let mut rpc = new_rpc_client()?; - match rpc.get_wireguard_key()? { - Some(key) => { - println!("Current key : {}", &key.key); - println!( - "Key created on : {}", - &key.created.with_timezone(&chrono::offset::Local) - ); - } - None => { - println!("No key is set"); - return Ok(()); + async fn process_wireguard_key_check() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let key = rpc.get_wireguard_key(()).await; + let key = match key { + Ok(response) => Some(response.into_inner()), + Err(status) => { + if status.code() == tonic::Code::NotFound { + None + } else { + return Err(Error::GrpcClientError(status)); + } } }; + if let Some(key) = key { + println!("Current key : {}", base64::encode(&key.key)); + println!( + "Key created on : {}", + Self::format_key_timestamp(&key.created.unwrap()) + ); + } else { + println!("No key is set"); + return Ok(()); + } - let is_valid = rpc.verify_wireguard_key()?; + let is_valid = rpc.verify_wireguard_key(()).await?.into_inner(); println!("Key is valid for use with current account: {}", is_valid); Ok(()) } - fn process_wireguard_key_generate() -> Result<()> { - let mut rpc = new_rpc_client()?; - let result = rpc - .generate_wireguard_key() - .map_err(|e| crate::Error::RpcClientError(e))?; - println!("{}", result); + async fn process_wireguard_key_generate() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let keygen_event = rpc.generate_wireguard_key(()).await?; + print_keygen_event(&keygen_event.into_inner()); Ok(()) } - fn process_wireguard_rotation_interval_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_wireguard_rotation_interval_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; println!( "Rotation interval: {} hour(s)", - tunnel_options - .wireguard - .automatic_rotation - .map(|interval| interval.to_string()) - .unwrap_or_else(|| "default".to_owned()) + tunnel_options.wireguard.unwrap().automatic_rotation ); Ok(()) } - fn process_wireguard_rotation_interval_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_wireguard_rotation_interval_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let rotate_interval = value_t!(matches.value_of("interval"), u32).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_rotation_interval(Some(rotate_interval))?; + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_rotation_interval(rotate_interval).await?; println!("Set key rotation interval: {} hour(s)", rotate_interval); Ok(()) } - fn process_wireguard_rotation_interval_reset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_rotation_interval(None)?; + async fn process_wireguard_rotation_interval_reset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.reset_wireguard_rotation_interval(()).await?; println!("Set key rotation interval: default"); Ok(()) } - fn handle_ipv6_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_ipv6_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { if matches.subcommand_matches("get").is_some() { - Self::process_ipv6_get() + Self::process_ipv6_get().await } else if let Some(m) = matches.subcommand_matches("set") { - Self::process_ipv6_set(m) + Self::process_ipv6_set(m).await } else { unreachable!("unhandled command"); } } - fn process_openvpn_mssfix_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_openvpn_mssfix_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; + let mssfix = tunnel_options.openvpn.unwrap().mssfix; println!( "mssfix: {}", - tunnel_options - .openvpn - .mssfix - .map_or_else(|| "unset".to_owned(), |v| v.to_string()) + if mssfix != 0 { + mssfix.to_string() + } else { + "unset".to_string() + }, ); Ok(()) } - fn get_tunnel_options() -> Result<TunnelOptions> { - let mut rpc = new_rpc_client()?; - Ok(rpc.get_settings()?.tunnel_options) + async fn get_tunnel_options() -> Result<TunnelOptions> { + let mut rpc = new_grpc_client().await?; + Ok(rpc + .get_settings(()) + .await? + .into_inner() + .tunnel_options + .unwrap()) } - fn process_openvpn_mssfix_unset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_mssfix(None)?; + async fn process_openvpn_mssfix_unset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_openvpn_mssfix(0).await?; println!("mssfix parameter has been unset"); Ok(()) } - fn process_openvpn_mssfix_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_openvpn_mssfix_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let new_value = value_t!(matches.value_of("mssfix"), u16).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_mssfix(Some(new_value))?; + let mut rpc = new_grpc_client().await?; + rpc.set_openvpn_mssfix(new_value as u32).await?; println!("mssfix parameter has been updated"); Ok(()) } - fn process_ipv6_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_ipv6_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; println!( "IPv6: {}", - if tunnel_options.generic.enable_ipv6 { + if tunnel_options.generic.unwrap().enable_ipv6 { "on" } else { "off" @@ -289,12 +301,20 @@ impl Tunnel { Ok(()) } - fn process_ipv6_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_ipv6_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let enabled = matches.value_of("enable").unwrap() == "on"; - let mut rpc = new_rpc_client()?; - rpc.set_enable_ipv6(enabled)?; - println!("IPv6 setting has been updated"); + let mut rpc = new_grpc_client().await?; + rpc.set_enable_ipv6(enabled).await?; + if enabled { + println!("Enabled IPv6"); + } else { + println!("Disabled IPv6"); + } Ok(()) } + + fn format_key_timestamp(timestamp: &prost_types::Timestamp) -> String { + chrono::NaiveDateTime::from_timestamp(timestamp.seconds, timestamp.nanos as u32).to_string() + } } diff --git a/mullvad-cli/src/cmds/version.rs b/mullvad-cli/src/cmds/version.rs index 4e0f3a0d8b..48b3e621ed 100644 --- a/mullvad-cli/src/cmds/version.rs +++ b/mullvad-cli/src/cmds/version.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; pub struct Version; +#[async_trait::async_trait] impl Command for Version { fn name(&self) -> &'static str { "version" @@ -12,23 +13,24 @@ impl Command for Version { .about("Shows current version, and the currently supported versions") } - fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - let current_version = rpc.get_current_version()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let current_version = rpc.get_current_version(()).await?.into_inner(); println!("Current version: {}", current_version); - let version_info = rpc.get_version_info()?; + let version_info = rpc.get_version_info(()).await?.into_inner(); println!("\tIs supported: {}", version_info.supported); - match version_info.suggested_upgrade { - Some(version) => println!("\tSuggested update: {}", version), - None => println!("\tNo newer version is available"), + if !version_info.suggested_upgrade.is_empty() { + println!("\tSuggested update: {}", version_info.suggested_upgrade); + } else { + println!("\tNo newer version is available"); } if !version_info.latest_stable.is_empty() { println!("\tLatest stable version: {}", version_info.latest_stable); } - let settings = rpc.get_settings()?; + let settings = rpc.get_settings(()).await?.into_inner(); if settings.show_beta_releases { println!("\t Latest beta version: {}", version_info.latest_beta); }; diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs new file mode 100644 index 0000000000..c2e71fce0f --- /dev/null +++ b/mullvad-cli/src/format.rs @@ -0,0 +1,20 @@ +use crate::proto::KeygenEvent; + +pub fn print_keygen_event(key_event: &KeygenEvent) { + use crate::proto::keygen_event::KeygenEvent as EventType; + + match EventType::from_i32(key_event.event).unwrap() { + EventType::NewKey => { + println!( + "New WireGuard key: {}", + base64::encode(&key_event.new_key.as_ref().unwrap().key) + ); + } + EventType::TooManyKeys => { + println!("Account has too many keys already"); + } + EventType::GenerationFailure => { + println!("Failed to generate new WireGuard key"); + } + } +} diff --git a/mullvad-cli/src/location.rs b/mullvad-cli/src/location.rs index 20853893b8..3d2705ae88 100644 --- a/mullvad-cli/src/location.rs +++ b/mullvad-cli/src/location.rs @@ -1,4 +1,4 @@ -use mullvad_types::relay_constraints::{Constraint, LocationConstraint}; +use crate::proto::RelayLocation; pub fn get_subcommand() -> clap::App<'static, 'static> { clap::SubCommand::with_name("location") @@ -22,24 +22,33 @@ pub fn get_subcommand() -> clap::App<'static, 'static> { ) } -pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> Constraint<LocationConstraint> { +pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> RelayLocation { let country_original = matches.value_of("country").unwrap(); let country = country_original.to_lowercase(); let city = matches.value_of("city").map(str::to_lowercase); let hostname = matches.value_of("hostname").map(str::to_lowercase); match (country_original, city, hostname) { - ("any", None, None) => Constraint::Any, + ("any", None, None) => RelayLocation::default(), ("any", ..) => clap::Error::with_description( "City can't be given when selecting 'any' country", clap::ErrorKind::InvalidValue, ) .exit(), - (_, None, None) => Constraint::Only(LocationConstraint::Country(country)), - (_, Some(city), None) => Constraint::Only(LocationConstraint::City(country, city)), - (_, Some(city), Some(hostname)) => { - Constraint::Only(LocationConstraint::Hostname(country, city, hostname)) - } + (_, None, None) => RelayLocation { + country, + ..Default::default() + }, + (_, Some(city), None) => RelayLocation { + country, + city, + ..Default::default() + }, + (_, Some(city), Some(hostname)) => RelayLocation { + country, + city, + hostname, + }, (..) => clap::Error::with_description( "Invalid country, city and hostname combination given", clap::ErrorKind::InvalidValue, @@ -48,6 +57,22 @@ pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> Constraint<LocationCons } } +pub fn format_location(location: Option<&RelayLocation>) -> String { + if let Some(location) = location { + if !location.hostname.is_empty() { + return format!( + "city {}, {}, hostname {}", + location.city, location.country, location.hostname + ); + } else if !location.city.is_empty() { + return format!("city {}, {}", location.city, location.country); + } else if !location.country.is_empty() { + return format!("country {}", location.country); + } + } + "any location".to_string() +} + fn country_code_validator(code: String) -> std::result::Result<(), String> { if code.len() == 2 || code == "any" { Ok(()) diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 3d8d2a4aef..11309ff019 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -1,11 +1,12 @@ #![deny(rust_2018_idioms)] +use async_trait::async_trait; use clap::{crate_authors, crate_description}; -use mullvad_ipc_client::{new_standalone_ipc_client, DaemonRpcClient}; use std::{collections::HashMap, io}; use talpid_types::ErrorExt; mod cmds; +mod format; mod location; pub const BIN_NAME: &str = "mullvad"; @@ -13,31 +14,50 @@ pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/produc pub type Result<T> = std::result::Result<T, Error>; +mod proto { + tonic::include_proto!("mullvad_daemon.management_interface"); +} +use proto::management_service_client::ManagementServiceClient; + +use parity_tokio_ipc::Endpoint as IpcEndpoint; +use tonic::{ + self, + transport::{Endpoint, Uri}, +}; +use tower::service_fn; + #[derive(err_derive::Error, Debug)] pub enum Error { #[error(display = "Failed to connect to daemon")] DaemonNotRunning(#[error(source)] io::Error), - #[error(display = "Can't subscribe to daemon states")] - CantSubscribe(#[error(source)] mullvad_ipc_client::PubSubError), + #[error(display = "Failed to connect to mullvad-daemon over RPC")] + GrpcTransportError(#[error(source)] tonic::transport::Error), #[error(display = "Failed to communicate with mullvad-daemon over RPC")] - RpcClientError(#[error(source)] mullvad_ipc_client::Error), + GrpcClientError(#[error(source)] tonic::Status), /// The given command is not correct in some way #[error(display = "Invalid command: {}", _0)] InvalidCommand(&'static str), } -pub fn new_rpc_client() -> Result<DaemonRpcClient> { - match new_standalone_ipc_client(&mullvad_paths::get_rpc_socket_path()) { - Err(e) => Err(Error::DaemonNotRunning(e)), - Ok(client) => Ok(client), - } +pub async fn new_grpc_client() -> Result<ManagementServiceClient<tonic::transport::Channel>> { + let ipc_path = mullvad_paths::get_rpc_socket_path(); + + // The URI will be ignored + let channel = Endpoint::from_static("lttp://[::]:50051") + .connect_with_connector(service_fn(move |_: Uri| { + IpcEndpoint::connect(ipc_path.clone()) + })) + .await?; + + Ok(ManagementServiceClient::new(channel)) } -fn main() { - let exit_code = match run() { +#[tokio::main] +async fn main() { + let exit_code = match run().await { Ok(_) => 0, Err(error) => { eprintln!("{}", error.display_chain()); @@ -47,7 +67,7 @@ fn main() { std::process::exit(exit_code); } -fn run() -> Result<()> { +async fn run() -> Result<()> { env_logger::init(); let commands = cmds::get_commands(); @@ -85,7 +105,7 @@ fn run() -> Result<()> { } (sub_name, Some(sub_matches)) => { if let Some(cmd) = commands.get(sub_name) { - cmd.run(sub_matches) + cmd.run(sub_matches).await } else { unreachable!("No command matched"); } @@ -109,10 +129,11 @@ fn build_cli(commands: &HashMap<&'static str, Box<dyn Command>>) -> clap::App<'s .subcommands(commands.values().map(|cmd| cmd.clap_subcommand())) } +#[async_trait] pub trait Command { fn name(&self) -> &'static str; fn clap_subcommand(&self) -> clap::App<'static, 'static>; - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()>; + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()>; } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index a33f808ec2..19419a8a81 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -16,11 +16,6 @@ fern = { version = "0.5", features = ["colored"] } futures01 = { package = "futures", version = "0.1" } futures = { package = "futures", version = "0.3", features = [ "compat" ]} ipnetwork = "0.16" -jsonrpc-client-core = "0.5" -jsonrpc-core = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-ipc-server = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-macros = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-pubsub = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } lazy_static = "1.0" log = "0.4" log-panics = "2.0.0" @@ -35,14 +30,24 @@ tokio-retry = "0.2" tokio-timer = "0.1" uuid = { version = "0.7", features = ["v4"] } -mullvad-ipc-client = { path = "../mullvad-ipc-client" } mullvad-paths = { path = "../mullvad-paths" } mullvad-types = { path = "../mullvad-types" } mullvad-rpc = { path = "../mullvad-rpc" } talpid-core = { path = "../talpid-core" } -talpid-ipc = { path = "../talpid-ipc" } talpid-types = { path = "../talpid-types" } +tonic = "0.2" +tower = "0.3" +prost = "0.6" +prost-types = "0.6" + +parity-tokio-ipc = "0.7" + +triggered = "0.1.1" + +[build-dependencies] +tonic-build = { version = "0.2", default-features = false, features = ["transport", "prost"] } + [target.'cfg(target_os="android")'.dependencies] android_logger = "0.8" diff --git a/mullvad-daemon/build.rs b/mullvad-daemon/build.rs index c2571ca799..ef6fc38a7c 100644 --- a/mullvad-daemon/build.rs +++ b/mullvad-daemon/build.rs @@ -1,6 +1,8 @@ use std::{env, fs, path::PathBuf, process::Command}; fn main() { + tonic_build::compile_protos("proto/management_interface.proto").unwrap(); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let product_version = env!("CARGO_PKG_VERSION").replacen(".0", "", 1); diff --git a/mullvad-daemon/proto/management_interface.proto b/mullvad-daemon/proto/management_interface.proto new file mode 100644 index 0000000000..ea62f6976e --- /dev/null +++ b/mullvad-daemon/proto/management_interface.proto @@ -0,0 +1,466 @@ +syntax = "proto3"; + +package mullvad_daemon.management_interface; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +service ManagementService { + // Control and get tunnel state + rpc ConnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc DisconnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc ReconnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GetTunnelState(google.protobuf.Empty) returns (TunnelState) {} + + // Control the daemon and receive events + rpc EventsListen(google.protobuf.Empty) returns (stream DaemonEvent) {} + rpc PrepareRestart(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc Shutdown(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc FactoryReset(google.protobuf.Empty) returns (google.protobuf.Empty) {} + + rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {} + rpc GetVersionInfo(google.protobuf.Empty) returns (AppVersionInfo) {} + + // Relays and tunnel constraints + rpc UpdateRelayLocations(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc UpdateRelaySettings(RelaySettingsUpdate) returns (google.protobuf.Empty) {} + rpc GetRelayLocations(google.protobuf.Empty) returns (stream RelayListCountry) {} + rpc GetCurrentLocation(google.protobuf.Empty) returns (GeoIpLocation) {} + rpc SetBridgeSettings(BridgeSettings) returns (google.protobuf.Empty) {} + rpc SetBridgeState(BridgeState) returns (google.protobuf.Empty) {} + + // Settings + rpc GetSettings(google.protobuf.Empty) returns (Settings) {} + rpc SetAllowLan(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetShowBetaReleases(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetBlockWhenDisconnected(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetAutoConnect(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetOpenvpnMssfix(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} + rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} + rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + + // Account management + rpc CreateNewAccount(google.protobuf.Empty) returns (google.protobuf.StringValue) {} + rpc SetAccount(google.protobuf.StringValue) returns (google.protobuf.Empty) {} + rpc GetAccountData(google.protobuf.StringValue) returns (AccountData) {} + rpc GetAccountHistory(google.protobuf.Empty) returns (AccountHistory) {} + rpc RemoveAccountFromHistory(google.protobuf.StringValue) returns (google.protobuf.Empty) {} + rpc ClearAccountHistory(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GetWwwAuthToken(google.protobuf.Empty) returns (google.protobuf.StringValue) {} + rpc SubmitVoucher(google.protobuf.StringValue) returns (VoucherSubmission) {} + + // WireGuard key management + rpc SetWireguardRotationInterval(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} + rpc ResetWireguardRotationInterval(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GenerateWireguardKey(google.protobuf.Empty) returns (KeygenEvent) {} + rpc GetWireguardKey(google.protobuf.Empty) returns (PublicKey) {} + rpc VerifyWireguardKey(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} + + // Split tunneling + rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} + rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {} + rpc RemoveSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {} + rpc ClearSplitTunnelProcesses(google.protobuf.Empty) returns (google.protobuf.Empty) {} +} + +message RelaySettingsUpdate { + oneof type { + CustomRelaySettings custom = 1; + NormalRelaySettingsUpdate normal = 2; + } +} + +message AccountData { + google.protobuf.Timestamp expiry = 1; +} + +message VoucherSubmission { + uint64 seconds_added = 1; + google.protobuf.Timestamp new_expiry = 2; +} + +enum AfterDisconnect { + NOTHING = 0; + BLOCK = 1; + RECONNECT = 2; +} + +message ErrorState { + enum Cause { + AUTH_FAILED = 0; + IPV6_UNAVAILABLE = 1; + SET_FIREWALL_POLICY_ERROR = 2; + SET_DNS_ERROR = 3; + START_TUNNEL_ERROR = 4; + TUNNEL_PARAMETER_ERROR = 5; + IS_OFFLINE = 6; + TAP_ADAPTER_PROBLEM = 7; + VPN_PERMISSION_DENIED = 8; + } + + enum GenerationError { + NO_MATCHING_RELAY = 0; + NO_MATCHING_BRIDGE_RELAY = 1; + NO_WIREGUARD_KEY = 2; + CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR = 3; + } + + message FirewallPolicyError { + enum ErrorType { + GENERIC = 0; + LOCKED = 1; + } + ErrorType type = 1; + + // LOCKED + uint32 lock_pid = 2; + string lock_name = 3; + } + + Cause cause = 1; + bool is_blocking = 2; + + // AUTH_FAILED + string auth_fail_reason = 3; + // TUNNEL_PARAMETER_ERROR + GenerationError parameter_error = 4; + // SET_FIREWALL_POLICY_ERROR + FirewallPolicyError policy_error = 5; +} + +message TunnelState { + message Disconnected { + } + message Connecting { + TunnelStateRelayInfo relay_info = 1; + } + message Connected { + TunnelStateRelayInfo relay_info = 1; + } + message Disconnecting { + AfterDisconnect after_disconnect = 1; + } + message Error { + ErrorState error_state = 1; + } + + oneof state { + Disconnected disconnected = 1; + Connecting connecting = 2; + Connected connected = 3; + Disconnecting disconnecting = 4; + Error error = 5; + } +} + +enum TunnelType { + ANY_TUNNEL = 0; + OPENVPN = 1; + WIREGUARD = 2; +} + +message TunnelStateRelayInfo { + TunnelEndpoint tunnel_endpoint = 1; + GeoIpLocation location = 2; +} + +message TunnelEndpoint { + string address = 1; + TransportProtocol protocol = 2; + TunnelType tunnel_type = 3; + ProxyEndpoint proxy = 4; +} + +enum ProxyType { + SHADOWSOCKS = 0; + CUSTOM = 1; +} + +message ProxyEndpoint { + string address = 1; + TransportProtocol protocol = 2; + ProxyType proxy_type = 3; +} + +message GeoIpLocation { + string ipv4 = 1; + string ipv6 = 2; + string country = 3; + string city = 4; + double latitude = 5; + double longitude = 6; + bool mullvad_exit_ip = 7; + string hostname = 8; + string bridge_hostname = 9; +} + +message AccountHistory { + repeated string token = 1; +} + +message BridgeSettings { + message BridgeConstraints { + RelayLocation location = 1; + } + + message LocalProxySettings { + uint32 port = 1; + string peer = 2; + } + message RemoteProxySettings { + string address = 1; + // NOTE: optional + RemoteProxyAuth auth = 2; + } + message RemoteProxyAuth { + string username = 1; + string password = 2; + } + message ShadowsocksProxySettings { + string peer = 1; + string password = 2; + string cipher = 3; + } + + oneof type { + BridgeConstraints normal = 1; + LocalProxySettings local = 2; + RemoteProxySettings remote = 3; + ShadowsocksProxySettings shadowsocks = 4; + } +} + +message RelayLocation { + string country = 1; + string city = 2; + string hostname = 3; +} + +message BridgeState { + enum State { + AUTO = 0; + ON = 1; + OFF = 2; + } + State state = 1; +} + +message Settings { + // NOTE: token is optional + string account_token = 1; + RelaySettings relay_settings = 2; + BridgeSettings bridge_settings = 3; + BridgeState bridge_state = 4; + bool allow_lan = 5; + bool block_when_disconnected = 6; + bool auto_connect = 7; + TunnelOptions tunnel_options = 8; + bool show_beta_releases = 9; + // NOTE: skipping version field +} + +message RelaySettings { + oneof endpoint { + CustomRelaySettings custom = 1; + NormalRelaySettings normal = 2; + } +} + +message NormalRelaySettings { + RelayLocation location = 1; + TunnelType tunnel_type = 2; + WireguardConstraints wireguard_constraints = 3; + OpenvpnConstraints openvpn_constraints = 4; +} + +// Constraints are only updated for fields that are provided +message NormalRelaySettingsUpdate { + RelayLocation location = 1; + TunnelTypeUpdate tunnel_type = 2; + WireguardConstraints wireguard_constraints = 3; + OpenvpnConstraints openvpn_constraints = 4; +} + +message TunnelTypeUpdate { + TunnelType tunnel_type = 1; +} + +message OpenvpnConstraints { + // NOTE: optional + uint32 port = 1; + // NOTE: optional + TransportProtocol protocol = 2; +} +message WireguardConstraints { + // NOTE: optional + uint32 port = 1; +} + +message CustomRelaySettings { + string host = 1; + ConnectionConfig config = 2; +} + +message ConnectionConfig { + message OpenvpnConfig { + string address = 1; + TransportProtocol protocol = 2; + string username = 3; + string password = 4; + } + message WireguardConfig { + message TunnelConfig { + bytes private_key = 1; + repeated string addresses = 2; + } + message PeerConfig { + bytes public_key = 1; + repeated string allowed_ips = 2; + string endpoint = 3; + } + + TunnelConfig tunnel = 1; + PeerConfig peer = 2; + string ipv4_gateway = 3; + // NOTE: optional + string ipv6_gateway = 4; + } + + oneof config { + OpenvpnConfig openvpn = 1; + WireguardConfig wireguard = 2; + } +} + +message TunnelOptions { + message OpenvpnOptions { + // NOTE: optional + uint32 mssfix = 1; + } + message WireguardOptions { + // NOTE: optional + uint32 mtu = 1; + // NOTE: optional + uint32 automatic_rotation = 2; + } + message GenericOptions { + bool enable_ipv6 = 1; + } + + OpenvpnOptions openvpn = 1; + WireguardOptions wireguard = 2; + GenericOptions generic = 3; +} + +message PublicKey { + bytes key = 1; + google.protobuf.Timestamp created = 2; +} + +message KeygenEvent { + enum KeygenEvent { + NEW_KEY = 0; + TOO_MANY_KEYS = 1; + GENERATION_FAILURE = 2; + } + KeygenEvent event = 1; + PublicKey new_key = 2; +} + +message AppVersionInfo { + bool supported = 1; + string latest_stable = 2; + string latest_beta = 3; + string suggested_upgrade = 4; +} + +message RelayListCountry { + string name = 1; + string code = 2; + repeated RelayListCity cities = 3; +} + +message RelayListCity { + string name = 1; + string code = 2; + double latitude = 3; + double longitude = 4; + repeated Relay relays = 5; +} + +message Relay { + string hostname = 1; + string ipv4_addr_in = 2; + string ipv6_addr_in = 3; + bool include_in_country = 4; + bool active = 5; + bool owned = 6; + string provider = 7; + fixed64 weight = 8; + RelayTunnels tunnels = 9; + RelayBridges bridges = 10; + Location location = 11; +} + +message Location { + string country = 1; + string country_code = 2; + string city = 3; + string city_code = 4; + double latitude = 5; + double longitude = 6; +} + +message RelayTunnels { + repeated OpenVpnEndpointData openvpn = 1; + repeated WireguardEndpointData wireguard = 2; +} + +message RelayBridges { + repeated ShadowsocksEndpointData shadowsocks = 1; +} + +enum TransportProtocol { + ANY_PROTOCOL = 0; + UDP = 1; + TCP = 2; +} + +message ShadowsocksEndpointData { + uint32 port = 1; + string cipher = 2; + string password = 3; + TransportProtocol protocol = 4; +} + +message OpenVpnEndpointData { + uint32 port = 1; + TransportProtocol protocol = 2; +} + +message WireguardEndpointData { + repeated PortRange port_ranges = 1; + string ipv4_gateway = 2; + string ipv6_gateway = 3; + bytes public_key = 4; +} + +message PortRange { + uint32 first = 1; + uint32 last = 2; +} + +message DaemonEvent { + oneof event { + TunnelState tunnel_state = 1; + Settings settings = 2; + RelayList relay_list = 3; + AppVersionInfo version_info = 4; + KeygenEvent key_event = 5; + } +} + +message RelayList { + repeated RelayListCountry countries = 1; +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 4dc7dcfb47..ef0cff448e 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -17,6 +17,8 @@ pub mod rpc_uniqueness_check; mod settings; pub mod version; mod version_check; +#[cfg(target_os = "windows")] +mod windows_permissions; use futures::future::{abortable, AbortHandle}; use futures01::{ diff --git a/mullvad-daemon/src/logging.rs b/mullvad-daemon/src/logging.rs index 88fd387c5d..58e10cd3b7 100644 --- a/mullvad-daemon/src/logging.rs +++ b/mullvad-daemon/src/logging.rs @@ -26,15 +26,12 @@ pub enum Error { pub const SILENCED_CRATES: &[&str] = &[ "h2", - "jsonrpc_core", - // jsonrpc_core does some logging under the "rpc" target as well. - "rpc", "tokio_core", "tokio_io", "tokio_proto", "tokio_reactor", "tokio_threadpool", - "jsonrpc_ws_server", + "tokio_util", "want", "ws", "mio", diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 0b02dfbe5e..adb7c39f0a 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -86,8 +86,15 @@ fn run_platform(_config: &cli::Config, log_dir: Option<PathBuf>) -> Result<(), S } fn run_standalone(log_dir: Option<PathBuf>) -> Result<(), String> { - if rpc_uniqueness_check::is_another_instance_running() { - return Err("Another instance of the daemon is already running".to_owned()); + { + let mut runtime = tokio02::runtime::Builder::new() + .basic_scheduler() + .enable_all() + .build() + .map_err(|e| e.display_chain())?; + if runtime.block_on(rpc_uniqueness_check::is_another_instance_running()) { + return Err("Another instance of the daemon is already running".to_owned()); + } } if !running_as_admin() { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index d593f0aa9d..5619c8efd6 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -1,892 +1,1745 @@ -use crate::{BoxFuture, DaemonCommand, DaemonCommandSender, EventListener}; -use jsonrpc_core::{ - futures::{future, sync, Future}, - Error, ErrorCode, MetaIoHandler, Metadata, +use crate::{ + wireguard::DEFAULT_AUTOMATIC_KEY_ROTATION, DaemonCommand, DaemonCommandSender, EventListener, }; -use jsonrpc_ipc_server; -use jsonrpc_macros::{build_rpc_trait, metadata, pubsub}; -use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; +use futures::compat::Future01CompatExt; +use futures01::{future, sync, Future}; use mullvad_paths; use mullvad_rpc::{rest::Error as RestError, StatusCode}; use mullvad_types::{ - account::{AccountData, AccountToken, VoucherSubmission}, + account::AccountToken, location::GeoIpLocation, - relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, - relay_list::RelayList, - settings::Settings, + relay_constraints::{ + BridgeConstraints, BridgeSettings, BridgeState, Constraint, LocationConstraint, + OpenVpnConstraints, RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, + WireguardConstraints, + }, + relay_list::{Relay, RelayList, RelayListCountry}, + settings::{Settings, TunnelOptions}, states::{TargetState, TunnelState}, - version, wireguard, DaemonEvent, + version, wireguard, ConnectionConfig, }; use parking_lot::RwLock; use std::{ - collections::{hash_map::Entry, HashMap}, - sync::Arc, + cmp, io, + sync::{mpsc, Arc}, +}; +use talpid_types::{ + net::{TransportProtocol, TunnelType}, + ErrorExt, }; -use talpid_ipc; -use talpid_types::ErrorExt; -use uuid; - -pub const INVALID_VOUCHER_CODE: i64 = -400; -pub const VOUCHER_USED_ALREADY_CODE: i64 = -401; -pub const INVALID_ACCOUNT_CODE: i64 = -200; - - -build_rpc_trait! { - pub trait ManagementInterfaceApi { - type Metadata; - - /// Creates and sets a new account - #[rpc(meta, name = "create_new_account")] - fn create_new_account(&self, Self::Metadata) -> BoxFuture<String, Error>; - - /// Fetches and returns metadata about an account. Returns an error on non-existing - /// accounts. - #[rpc(meta, name = "get_account_data")] - fn get_account_data(&self, Self::Metadata, AccountToken) -> BoxFuture<AccountData, Error>; - - #[rpc(meta, name = "get_www_auth_token")] - fn get_www_auth_token(&self, Self::Metadata) -> BoxFuture<String, Error>; - - /// Submit voucher to add time to account - #[rpc(meta, name = "submit_voucher")] - fn submit_voucher(&self, Self::Metadata, String) -> BoxFuture<VoucherSubmission, Error>; - - /// Returns available countries. - #[rpc(meta, name = "get_relay_locations")] - fn get_relay_locations(&self, Self::Metadata) -> BoxFuture<RelayList, Error>; - - /// Triggers a relay list update - #[rpc(meta, name = "update_relay_locations")] - fn update_relay_locations(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Set which account to connect with. - #[rpc(meta, name = "set_account")] - fn set_account(&self, Self::Metadata, Option<AccountToken>) -> BoxFuture<(), Error>; - - /// Update constraints put on the type of tunnel connection to use - #[rpc(meta, name = "update_relay_settings")] - fn update_relay_settings( - &self, - Self::Metadata, RelaySettingsUpdate - ) -> BoxFuture<(), Error>; - - /// Set if the client should allow communication with the LAN while in secured state. - #[rpc(meta, name = "set_allow_lan")] - fn set_allow_lan(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; - - /// Set whether to enable the beta program. - #[rpc(meta, name = "set_show_beta_releases")] - fn set_show_beta_releases(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; - - /// Set if the client should allow network communication when in the disconnected state. - #[rpc(meta, name = "set_block_when_disconnected")] - fn set_block_when_disconnected(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; - - /// Set if the daemon should automatically establish a tunnel on start or not. - #[rpc(meta, name = "set_auto_connect")] - fn set_auto_connect(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; - - /// Try to connect if disconnected, or do nothing if already connecting/connected. - #[rpc(meta, name = "connect")] - fn connect(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Disconnect the VPN tunnel if it is connecting/connected. Does nothing if already - /// disconnected. - #[rpc(meta, name = "disconnect")] - fn disconnect(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Reconnect if connecting/connected, or do nothing if disconnected. - #[rpc(meta, name = "reconnect")] - fn reconnect(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Returns the current state of the Mullvad client. Changes to this state will - /// be announced to subscribers of `new_state`. - #[rpc(meta, name = "get_state")] - fn get_state(&self, Self::Metadata) -> BoxFuture<TunnelState, Error>; - - /// Performs a geoIP lookup and returns the current location as perceived by the public - /// internet. - #[rpc(meta, name = "get_current_location")] - fn get_current_location(&self, Self::Metadata) -> BoxFuture<Option<GeoIpLocation>, Error>; - - /// Makes the daemon exit its main loop and quit. - #[rpc(meta, name = "shutdown")] - fn shutdown(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Saves the target tunnel state and enters a blocking state. The state is restored - /// upon restart. - #[rpc(meta, name = "prepare_restart")] - fn prepare_restart(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Get previously used account tokens from the account history - #[rpc(meta, name = "get_account_history")] - fn get_account_history(&self, Self::Metadata) -> BoxFuture<Vec<AccountToken>, Error>; - - /// Remove given account token from the account history - #[rpc(meta, name = "remove_account_from_history")] - fn remove_account_from_history(&self, Self::Metadata, AccountToken) -> BoxFuture<(), Error>; - - /// Removes all accounts from history, removing any associated keys in the process - #[rpc(meta, name = "clear_account_history")] - fn clear_account_history(&self, Self::Metadata) -> BoxFuture<(), Error>; - - /// Sets openvpn's mssfix parameter - #[rpc(meta, name = "set_openvpn_mssfix")] - fn set_openvpn_mssfix(&self, Self::Metadata, Option<u16>) -> BoxFuture<(), Error>; - - /// Sets proxy details for OpenVPN - #[rpc(meta, name = "set_bridge_settings")] - fn set_bridge_settings(&self, Self::Metadata, BridgeSettings) -> BoxFuture<(), Error>; - - /// Sets bridge state - #[rpc(meta, name = "set_bridge_state")] - fn set_bridge_state(&self, Self::Metadata, BridgeState) -> BoxFuture<(), Error>; - - /// Set if IPv6 is enabled in the tunnel - #[rpc(meta, name = "set_enable_ipv6")] - fn set_enable_ipv6(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; - - /// Set MTU for wireguard tunnels - #[rpc(meta, name = "set_wireguard_mtu")] - fn set_wireguard_mtu(&self, Self::Metadata, Option<u16>) -> BoxFuture<(), Error>; - - /// Set automatic key rotation interval for wireguard tunnels - #[rpc(meta, name = "set_wireguard_rotation_interval")] - fn set_wireguard_rotation_interval(&self, Self::Metadata, Option<u32>) -> BoxFuture<(), Error>; - /// Returns the current daemon settings - #[rpc(meta, name = "get_settings")] - fn get_settings(&self, Self::Metadata) -> BoxFuture<Settings, Error>; - /// Generates new wireguard key for current account - #[rpc(meta, name = "generate_wireguard_key")] - fn generate_wireguard_key(&self, Self::Metadata) -> BoxFuture<wireguard::KeygenEvent, Error>; +mod proto { + tonic::include_proto!("mullvad_daemon.management_interface"); +} - /// Retrieve a public key for current account if the account has one. - #[rpc(meta, name = "get_wireguard_key")] - fn get_wireguard_key(&self, Self::Metadata) -> BoxFuture<Option<wireguard::PublicKey>, Error>; +use proto::{ + daemon_event::Event as DaemonEventType, + management_service_server::{ManagementService, ManagementServiceServer}, +}; - /// Verify if current wireguard key is still valid - #[rpc(meta, name = "verify_wireguard_key")] - fn verify_wireguard_key(&self, Self::Metadata) -> BoxFuture<bool, Error>; +use tonic::{ + self, + transport::{server::Connected, Server}, + Request, Response, +}; - /// Retreive version of the app - #[rpc(meta, name = "get_current_version")] - fn get_current_version(&self, Self::Metadata) -> BoxFuture<String, Error>; +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + // Unable to start the management interface server + #[error(display = "Unable to start management interface server")] + SetupError(tonic::transport::Error), - /// Retrieve information about the currently running and latest versions of the app - #[rpc(meta, name = "get_version_info")] - fn get_version_info(&self, Self::Metadata) -> BoxFuture<version::AppVersionInfo, Error>; + // Unable to set the permissions on the named pipe + #[error(display = "Unable to set permissions for IPC endpoint")] + PermissionsError(#[error(source)] io::Error), - /// Remove all configuration and cache files - #[rpc(meta, name = "factory_reset")] - fn factory_reset(&self, Self::Metadata) -> BoxFuture<(), Error>; + // Unable to start the tokio runtime + #[error(display = "Failed to create the tokio runtime")] + TokioRuntimeError(#[error(source)] tokio02::io::Error), +} - /// Retrieve PIDs to exclude from the tunnel - #[rpc(meta, name = "get_split_tunnel_processes")] - fn get_split_tunnel_processes(&self, Self::Metadata) -> BoxFuture<Vec<i32>, Error>; +struct ManagementServiceImpl { + daemon_tx: DaemonCommandSender, + subscriptions: Arc<RwLock<Vec<EventsListenerSender>>>, +} - /// Add a process to exclude from the tunnel - #[rpc(meta, name = "add_split_tunnel_process")] - fn add_split_tunnel_process(&self, Self::Metadata, i32) -> BoxFuture<(), Error>; +pub type ServiceResult<T> = std::result::Result<Response<T>, tonic::Status>; +type EventsListenerReceiver = + tokio02::sync::mpsc::UnboundedReceiver<Result<proto::DaemonEvent, tonic::Status>>; +type EventsListenerSender = + tokio02::sync::mpsc::UnboundedSender<Result<proto::DaemonEvent, tonic::Status>>; - /// Remove a process excluded from the tunnel - #[rpc(meta, name = "remove_split_tunnel_process")] - fn remove_split_tunnel_process(&self, Self::Metadata, i32) -> BoxFuture<(), Error>; +#[tonic::async_trait] +impl ManagementService for ManagementServiceImpl { + type GetRelayLocationsStream = + tokio02::sync::mpsc::Receiver<Result<proto::RelayListCountry, tonic::Status>>; + type GetSplitTunnelProcessesStream = + tokio02::sync::mpsc::UnboundedReceiver<Result<i32, tonic::Status>>; + type EventsListenStream = EventsListenerReceiver; - /// Clear list of processes to exclude from the tunnel - #[rpc(meta, name = "clear_split_tunnel_processes")] - fn clear_split_tunnel_processes(&self, Self::Metadata) -> BoxFuture<(), Error>; + // Control and get the tunnel state + // - #[pubsub(name = "daemon_event")] { - /// Subscribes to events from the daemon. - #[rpc(name = "daemon_event_subscribe")] - fn daemon_event_subscribe( - &self, - Self::Metadata, - pubsub::Subscriber<DaemonEvent> - ); + async fn connect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("connect_tunnel"); - /// Unsubscribes from the `daemon_event` event notifications. - #[rpc(name = "daemon_event_unsubscribe")] - fn daemon_event_unsubscribe(&self, SubscriptionId) -> BoxFuture<(), Error>; - } + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Secured)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|result| match result { + Ok(()) => Ok(Response::new(())), + Err(()) => Err(tonic::Status::new( + tonic::Code::from(-900), + "No account token configured", + )), + }) + .compat() + .await } -} - -pub struct ManagementInterfaceServer { - server: talpid_ipc::IpcServer, - subscriptions: Arc<RwLock<HashMap<SubscriptionId, pubsub::Sink<DaemonEvent>>>>, -} -impl ManagementInterfaceServer { - pub fn start(tunnel_tx: DaemonCommandSender) -> Result<Self, talpid_ipc::Error> { - let rpc = ManagementInterface::new(tunnel_tx); - let subscriptions = rpc.subscriptions.clone(); + async fn disconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("disconnect_tunnel"); - let mut io = PubSubHandler::default(); - io.extend_with(rpc.to_delegate()); - let meta_io: MetaIoHandler<Meta> = io.into(); - let path = mullvad_paths::get_rpc_socket_path(); - let server = talpid_ipc::IpcServer::start_with_metadata( - meta_io, - meta_extractor, - &path.to_string_lossy(), - )?; - Ok(ManagementInterfaceServer { - server, - subscriptions, - }) + let (tx, _) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Unsecured)) + .then(|_| Ok(Response::new(()))) + .compat() + .await } - pub fn socket_path(&self) -> &str { - self.server.path() + async fn reconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("reconnect_tunnel"); + self.send_command_to_daemon(DaemonCommand::Reconnect) + .map(Response::new) + .compat() + .await } - pub fn event_broadcaster(&self) -> ManagementInterfaceEventBroadcaster { - ManagementInterfaceEventBroadcaster { - subscriptions: self.subscriptions.clone(), - close_handle: Some(self.server.close_handle()), - } + async fn get_tunnel_state(&self, _: Request<()>) -> ServiceResult<proto::TunnelState> { + log::debug!("get_tunnel_state"); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetState(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|state| Ok(Response::new(convert_state(state)))) + .compat() + .await } - /// Consumes the server and waits for it to finish. Returns an error if the server exited - /// due to an error. - pub fn wait(self) { - self.server.wait() - } -} + // Control the daemon and receive events + // -/// A handle that allows broadcasting messages to all subscribers of the management interface. -#[derive(Clone)] -pub struct ManagementInterfaceEventBroadcaster { - subscriptions: Arc<RwLock<HashMap<SubscriptionId, pubsub::Sink<DaemonEvent>>>>, - close_handle: Option<talpid_ipc::CloseHandle>, -} - -impl EventListener for ManagementInterfaceEventBroadcaster { - /// Sends a new state update to all `new_state` subscribers of the management interface. - fn notify_new_state(&self, new_state: TunnelState) { - self.notify(DaemonEvent::TunnelState(new_state)); - } + async fn events_listen(&self, _: Request<()>) -> ServiceResult<Self::EventsListenStream> { + let (tx, rx) = tokio02::sync::mpsc::unbounded_channel(); - /// Sends settings to all `settings` subscribers of the management interface. - fn notify_settings(&self, settings: Settings) { - log::debug!("Broadcasting new settings"); - self.notify(DaemonEvent::Settings(settings)); - } + let mut subscriptions = self.subscriptions.write(); + subscriptions.push(tx); - /// Sends settings to all `settings` subscribers of the management interface. - fn notify_relay_list(&self, relay_list: RelayList) { - log::debug!("Broadcasting new relay list"); - self.notify(DaemonEvent::RelayList(relay_list)); + Ok(Response::new(rx)) } - fn notify_app_version(&self, app_version_info: version::AppVersionInfo) { - log::debug!("Broadcasting new app version info"); - self.notify(DaemonEvent::AppVersionInfo(app_version_info)); + async fn prepare_restart(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("prepare_restart"); + self.send_command_to_daemon(DaemonCommand::PrepareRestart) + .map(Response::new) + .compat() + .await } - fn notify_key_event(&self, key_event: mullvad_types::wireguard::KeygenEvent) { - log::debug!("Broadcasting new wireguard key event"); - self.notify(DaemonEvent::WireguardKey(key_event)); + async fn shutdown(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("shutdown"); + self.send_command_to_daemon(DaemonCommand::Shutdown) + .map(Response::new) + .compat() + .await } -} -impl ManagementInterfaceEventBroadcaster { - fn notify(&self, value: DaemonEvent) { - let subscriptions = self.subscriptions.read(); - for sink in subscriptions.values() { - let _ = sink.notify(Ok(value.clone())).wait(); + async fn factory_reset(&self, _: Request<()>) -> ServiceResult<()> { + #[cfg(not(target_os = "android"))] + { + log::debug!("factory_reset"); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::FactoryReset(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await + } + #[cfg(target_os = "android")] + { + Response::new(()) } } -} -impl Drop for ManagementInterfaceEventBroadcaster { - fn drop(&mut self) { - if let Some(close_handle) = self.close_handle.take() { - close_handle.close(); - } + async fn get_current_version(&self, _: Request<()>) -> ServiceResult<String> { + log::debug!("get_current_version"); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetCurrentVersion(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } -} -struct ManagementInterface { - subscriptions: Arc<RwLock<HashMap<SubscriptionId, pubsub::Sink<DaemonEvent>>>>, - tx: DaemonCommandSender, -} + async fn get_version_info(&self, _: Request<()>) -> ServiceResult<proto::AppVersionInfo> { + log::debug!("get_version_info"); -impl ManagementInterface { - pub fn new(tx: DaemonCommandSender) -> Self { - ManagementInterface { - subscriptions: Default::default(), - tx, - } - } + let (tx, rx) = sync::oneshot::channel(); + let app_version_info = self + .send_command_to_daemon(DaemonCommand::GetVersionInfo(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .compat() + .await?; - /// Sends a command to the daemon and maps the error to an RPC error. - fn send_command_to_daemon( - &self, - command: DaemonCommand, - ) -> impl Future<Item = (), Error = Error> { - future::result(self.tx.send(command)).map_err(|_| Error::internal_error()) + Ok(Response::new(convert_version_info(&app_version_info))) } - /// Converts a REST API error for an account into a JSONRPC error for the JSONRPC client. - fn map_rest_account_error(error: RestError) -> Error { - match error { - RestError::ApiError(status, message) - if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN => - { - Error { - code: ErrorCode::from(INVALID_ACCOUNT_CODE), - message, - data: None, - } - } - _ => Error::internal_error(), - } - } -} + // Relays and tunnel constraints + // -impl ManagementInterfaceApi for ManagementInterface { - type Metadata = Meta; + async fn update_relay_locations(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("update_relay_locations"); + self.send_command_to_daemon(DaemonCommand::UpdateRelayLocations) + .compat() + .await + .map(Response::new) + } - fn create_new_account(&self, _: Self::Metadata) -> BoxFuture<String, Error> { + async fn update_relay_settings( + &self, + request: Request<proto::RelaySettingsUpdate>, + ) -> ServiceResult<()> { + log::debug!("update_relay_settings"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::CreateNewAccount(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|result| match result { - Ok(account_token) => Ok(account_token), - Err(_) => Err(Error::internal_error()), - }); + let constraints_update = convert_relay_settings_update(&request.into_inner())?; - Box::new(future) + let message = DaemonCommand::UpdateRelaySettings(tx, constraints_update); + self.send_command_to_daemon(message) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn get_account_data( + async fn get_relay_locations( &self, - _: Self::Metadata, - account_token: AccountToken, - ) -> BoxFuture<AccountData, Error> { - log::debug!("get_account_data"); + _: Request<()>, + ) -> ServiceResult<Self::GetRelayLocationsStream> { + log::debug!("get_relay_locations"); + let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetAccountData(tx, account_token)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|rpc_future| { - rpc_future.map_err(|error: RestError| { + let locations = self + .send_command_to_daemon(DaemonCommand::GetRelayLocations(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .compat() + .await?; + + let (mut stream_tx, stream_rx) = + tokio02::sync::mpsc::channel(cmp::max(1, locations.countries.len())); + + tokio02::spawn(async move { + for country in &locations.countries { + if let Err(error) = stream_tx + .send(Ok(convert_relay_list_country(country))) + .await + { log::error!( - "Unable to get account data from API: {}", + "Error while sending relays to client: {}", error.display_chain() ); - Self::map_rest_account_error(error) - }) - }); - Box::new(future) + } + } + }); + + Ok(Response::new(stream_rx)) } - fn get_www_auth_token(&self, _: Self::Metadata) -> BoxFuture<String, Error> { - log::debug!("get_account_data"); + async fn get_current_location(&self, _: Request<()>) -> ServiceResult<proto::GeoIpLocation> { + log::debug!("get_current_location"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetWwwAuthToken(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|rpc_future| { - rpc_future.map_err(|error: mullvad_rpc::rest::Error| { - log::error!( - "Unable to get account data from API: {}", - error.display_chain() - ); - Self::map_rest_account_error(error) - }) - }); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::GetCurrentLocation(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|geoip| { + if let Some(geoip) = geoip { + Ok(Response::new(convert_geoip_location(geoip))) + } else { + Err(tonic::Status::not_found("no location was found")) + } + }) + .compat() + .await } - fn submit_voucher( + async fn set_bridge_settings( &self, - _: Self::Metadata, - voucher: String, - ) -> BoxFuture<VoucherSubmission, Error> { - log::debug!("submit_voucher"); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SubmitVoucher(tx, voucher)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|f| { - f.map_err(|e| match e { - RestError::ApiError(StatusCode::BAD_REQUEST, message) => { - match &message.as_str() { - &mullvad_rpc::INVALID_VOUCHER => Error { - code: ErrorCode::from(INVALID_VOUCHER_CODE), - message, - data: None, - }, + request: Request<proto::BridgeSettings>, + ) -> ServiceResult<()> { + use proto::bridge_settings::Type as BridgeSettingType; + use talpid_types::net; - &mullvad_rpc::VOUCHER_USED => Error { - code: ErrorCode::from(VOUCHER_USED_ALREADY_CODE), - message, - data: None, - }, + let settings = request + .into_inner() + .r#type + .ok_or(tonic::Status::invalid_argument("no settings provided"))?; - _ => Error::internal_error(), - } - } - _ => Error::internal_error(), - }) - }); - Box::new(future) - } + let settings = + match settings { + BridgeSettingType::Normal(constraints) => { + let constraint = match constraints.location { + None => Constraint::Any, + Some(location) => convert_proto_location(location), + }; + + BridgeSettings::Normal(BridgeConstraints { + location: constraint, + }) + } + BridgeSettingType::Local(proxy_settings) => { + let peer = proxy_settings.peer.parse().map_err(|_| { + tonic::Status::invalid_argument("failed to parse peer address") + })?; + let proxy_settings = + net::openvpn::ProxySettings::Local(net::openvpn::LocalProxySettings { + port: proxy_settings.port as u16, + peer, + }); + BridgeSettings::Custom(proxy_settings) + } + BridgeSettingType::Remote(proxy_settings) => { + let address = proxy_settings.address.parse().map_err(|_| { + tonic::Status::invalid_argument("failed to parse IP address") + })?; + let auth = proxy_settings.auth.map(|auth| net::openvpn::ProxyAuth { + username: auth.username, + password: auth.password, + }); + let proxy_settings = + net::openvpn::ProxySettings::Remote(net::openvpn::RemoteProxySettings { + address, + auth, + }); + BridgeSettings::Custom(proxy_settings) + } + BridgeSettingType::Shadowsocks(proxy_settings) => { + let peer = proxy_settings.peer.parse().map_err(|_| { + tonic::Status::invalid_argument("failed to parse peer address") + })?; + let proxy_settings = net::openvpn::ProxySettings::Shadowsocks( + net::openvpn::ShadowsocksProxySettings { + peer, + password: proxy_settings.password, + cipher: proxy_settings.cipher, + }, + ); + BridgeSettings::Custom(proxy_settings) + } + }; + + log::debug!("set_bridge_settings({:?})", settings); - fn get_relay_locations(&self, _: Self::Metadata) -> BoxFuture<RelayList, Error> { - log::debug!("get_relay_locations"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetRelayLocations(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, settings)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|settings_result| { + settings_result.map_err(|_| tonic::Status::internal("internal error")) + }) + .map(Response::new) + .compat() + .await } - fn update_relay_locations(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("update_relay_locations"); - Box::new(self.send_command_to_daemon(DaemonCommand::UpdateRelayLocations)) - } + async fn set_bridge_state(&self, request: Request<proto::BridgeState>) -> ServiceResult<()> { + use proto::bridge_state::State; - fn set_account( - &self, - _: Self::Metadata, - account_token: Option<AccountToken>, - ) -> BoxFuture<(), Error> { - log::debug!("set_account"); + let bridge_state = match State::from_i32(request.into_inner().state) { + Some(State::Auto) => BridgeState::Auto, + Some(State::On) => BridgeState::On, + Some(State::Off) => BridgeState::Off, + None => return Err(tonic::Status::invalid_argument("unknown bridge state")), + }; + + log::debug!("set_bridge_state({:?})", bridge_state); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetAccount(tx, account_token)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetBridgeState(tx, bridge_state)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|settings_result| { + settings_result.map_err(|_| tonic::Status::internal("internal error")) + }) + .map(Response::new) + .compat() + .await } - fn update_relay_settings( - &self, - _: Self::Metadata, - constraints_update: RelaySettingsUpdate, - ) -> BoxFuture<(), Error> { - log::debug!("update_relay_settings"); - let (tx, rx) = sync::oneshot::channel(); + // Settings + // - let message = DaemonCommand::UpdateRelaySettings(tx, constraints_update); - let future = self - .send_command_to_daemon(message) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + async fn get_settings(&self, _: Request<()>) -> ServiceResult<proto::Settings> { + log::debug!("get_settings"); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetSettings(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(|settings| Response::new(convert_settings(&settings))) + .compat() + .await } - fn set_allow_lan(&self, _: Self::Metadata, allow_lan: bool) -> BoxFuture<(), Error> { + async fn set_allow_lan(&self, request: Request<bool>) -> ServiceResult<()> { + let allow_lan = request.into_inner(); log::debug!("set_allow_lan({})", allow_lan); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetAllowLan(tx, allow_lan)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetAllowLan(tx, allow_lan)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn set_show_beta_releases(&self, _: Self::Metadata, enabled: bool) -> BoxFuture<(), Error> { + async fn set_show_beta_releases(&self, request: Request<bool>) -> ServiceResult<()> { + let enabled = request.into_inner(); log::debug!("set_show_beta_releases({})", enabled); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetShowBetaReleases(tx, enabled)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetShowBetaReleases(tx, enabled)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn set_block_when_disconnected( - &self, - _: Self::Metadata, - block_when_disconnected: bool, - ) -> BoxFuture<(), Error> { + async fn set_block_when_disconnected(&self, request: Request<bool>) -> ServiceResult<()> { + let block_when_disconnected = request.into_inner(); log::debug!("set_block_when_disconnected({})", block_when_disconnected); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetBlockWhenDisconnected( - tx, - block_when_disconnected, - )) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetBlockWhenDisconnected( + tx, + block_when_disconnected, + )) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn set_auto_connect(&self, _: Self::Metadata, auto_connect: bool) -> BoxFuture<(), Error> { + async fn set_auto_connect(&self, request: Request<bool>) -> ServiceResult<()> { + let auto_connect = request.into_inner(); log::debug!("set_auto_connect({})", auto_connect); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetAutoConnect(tx, auto_connect)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetAutoConnect(tx, auto_connect)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn connect(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("connect"); + async fn set_openvpn_mssfix(&self, request: Request<u32>) -> ServiceResult<()> { + let mssfix = request.into_inner(); + let mssfix = if mssfix != 0 { + Some(mssfix as u16) + } else { + None + }; + log::debug!("set_openvpn_mssfix({:?})", mssfix); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Secured)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|result| match result { - Ok(()) => future::ok(()), - Err(()) => future::err(Error { - code: ErrorCode::ServerError(-900), - message: "No account token configured".to_owned(), - data: None, - }), - }); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetOpenVpnMssfix(tx, mssfix)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn disconnect(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("disconnect"); - let (tx, _) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Unsecured)) - .then(|_| future::ok(())); - Box::new(future) + async fn set_wireguard_mtu(&self, request: Request<u32>) -> ServiceResult<()> { + let mtu = request.into_inner(); + let mtu = if mtu != 0 { Some(mtu as u16) } else { None }; + log::debug!("set_wireguard_mtu({:?})", mtu); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetWireguardMtu(tx, mtu)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn reconnect(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("reconnect"); - let future = self.send_command_to_daemon(DaemonCommand::Reconnect); - Box::new(future) + async fn set_enable_ipv6(&self, request: Request<bool>) -> ServiceResult<()> { + let enable_ipv6 = request.into_inner(); + log::debug!("set_enable_ipv6({})", enable_ipv6); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetEnableIpv6(tx, enable_ipv6)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn get_state(&self, _: Self::Metadata) -> BoxFuture<TunnelState, Error> { - log::debug!("get_state"); - let (state_tx, state_rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetState(state_tx)) - .and_then(|_| state_rx.map_err(|_| Error::internal_error())); - Box::new(future) - } + // Account management + // - fn get_current_location(&self, _: Self::Metadata) -> BoxFuture<Option<GeoIpLocation>, Error> { - log::debug!("get_current_location"); + async fn create_new_account(&self, _: Request<()>) -> ServiceResult<String> { let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetCurrentLocation(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::CreateNewAccount(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|result| match result { + Ok(account_token) => Ok(Response::new(account_token)), + Err(_) => Err(tonic::Status::internal("internal error")), + }) + .compat() + .await } - fn shutdown(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("shutdown"); - Box::new(self.send_command_to_daemon(DaemonCommand::Shutdown)) + async fn set_account(&self, request: Request<AccountToken>) -> ServiceResult<()> { + log::debug!("set_account"); + let account_token = request.into_inner(); + let account_token = if account_token == "" { + None + } else { + Some(account_token) + }; + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetAccount(tx, account_token)) + .and_then(|_| { + rx.map(Response::new) + .map_err(|_| tonic::Status::internal("internal error")) + }) + .compat() + .await } - fn prepare_restart(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("prepare_restart"); - Box::new(self.send_command_to_daemon(DaemonCommand::PrepareRestart)) + async fn get_account_data( + &self, + request: Request<AccountToken>, + ) -> ServiceResult<proto::AccountData> { + log::debug!("get_account_data"); + let account_token = request.into_inner(); + let (tx, rx) = sync::oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetAccountData(tx, account_token)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|rpc_future| { + rpc_future + .map(|account_data| { + Response::new(proto::AccountData { + expiry: Some(prost_types::Timestamp { + seconds: account_data.expiry.timestamp(), + nanos: 0, + }), + }) + }) + .map_err(|error: RestError| { + log::error!( + "Unable to get account data from API: {}", + error.display_chain() + ); + map_rest_account_error(error) + }) + }) + .compat() + .await } - fn get_account_history(&self, _: Self::Metadata) -> BoxFuture<Vec<AccountToken>, Error> { + async fn get_account_history(&self, _: Request<()>) -> ServiceResult<proto::AccountHistory> { + // TODO: this might be a stream log::debug!("get_account_history"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetAccountHistory(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::GetAccountHistory(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(|history| Response::new(proto::AccountHistory { token: history })) + .compat() + .await } - fn remove_account_from_history( + async fn remove_account_from_history( &self, - _: Self::Metadata, - account_token: AccountToken, - ) -> BoxFuture<(), Error> { + request: Request<AccountToken>, + ) -> ServiceResult<()> { log::debug!("remove_account_from_history"); + let account_token = request.into_inner(); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::RemoveAccountFromHistory(tx, account_token)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::RemoveAccountFromHistory(tx, account_token)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn clear_account_history(&self, _: Self::Metadata) -> BoxFuture<(), Error> { + async fn clear_account_history(&self, _: Request<()>) -> ServiceResult<()> { log::debug!("clear_account_history"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::ClearAccountHistory(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::ClearAccountHistory(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn set_openvpn_mssfix(&self, _: Self::Metadata, mssfix: Option<u16>) -> BoxFuture<(), Error> { - log::debug!("set_openvpn_mssfix({:?})", mssfix); + async fn get_www_auth_token(&self, _: Request<()>) -> ServiceResult<String> { + log::debug!("get_www_auth_token"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetOpenVpnMssfix(tx, mssfix)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - - Box::new(future) + self.send_command_to_daemon(DaemonCommand::GetWwwAuthToken(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|rpc_future| { + rpc_future + .map(Response::new) + .map_err(|error: mullvad_rpc::rest::Error| { + log::error!( + "Unable to get account data from API: {}", + error.display_chain() + ); + map_rest_account_error(error) + }) + }) + .compat() + .await } - fn set_bridge_settings( + async fn submit_voucher( &self, - _: Self::Metadata, - bridge_settings: BridgeSettings, - ) -> BoxFuture<(), Error> { - log::debug!("set_bridge_settings({:?})", bridge_settings); + request: Request<String>, + ) -> ServiceResult<proto::VoucherSubmission> { + log::debug!("submit_voucher"); + let voucher = request.into_inner(); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, bridge_settings)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|settings_result| settings_result.map_err(|_| Error::internal_error())); - - Box::new(future) - } + self.send_command_to_daemon(DaemonCommand::SubmitVoucher(tx, voucher)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .and_then(|f| { + f.map(|submission| { + Response::new(proto::VoucherSubmission { + seconds_added: submission.time_added, + new_expiry: Some(prost_types::Timestamp { + seconds: submission.new_expiry.timestamp(), + nanos: 0, + }), + }) + }) + .map_err(|e| match e { + RestError::ApiError(StatusCode::BAD_REQUEST, message) => { + match &message.as_str() { + &mullvad_rpc::INVALID_VOUCHER => { + tonic::Status::new(tonic::Code::NotFound, message) + } - fn set_bridge_state( - &self, - _: Self::Metadata, - bridge_state: BridgeState, - ) -> BoxFuture<(), Error> { - log::debug!("set_bridge_state({:?})", bridge_state); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetBridgeState(tx, bridge_state)) - .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|settings_result| settings_result.map_err(|_| Error::internal_error())); + &mullvad_rpc::VOUCHER_USED => { + tonic::Status::new(tonic::Code::ResourceExhausted, message) + } - Box::new(future) + _ => tonic::Status::internal("internal error"), + } + } + _ => tonic::Status::internal("internal error"), + }) + }) + .compat() + .await } - fn set_enable_ipv6(&self, _: Self::Metadata, enable_ipv6: bool) -> BoxFuture<(), Error> { - log::debug!("set_enable_ipv6({})", enable_ipv6); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetEnableIpv6(tx, enable_ipv6)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - - Box::new(future) - } + // WireGuard key management + // - /// Set MTU for wireguard tunnels - fn set_wireguard_mtu(&self, _: Self::Metadata, mtu: Option<u16>) -> BoxFuture<(), Error> { - log::debug!("set_wireguard_mtu({:?})", mtu); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetWireguardMtu(tx, mtu)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) - } + async fn set_wireguard_rotation_interval(&self, request: Request<u32>) -> ServiceResult<()> { + let interval = request.into_inner(); - /// Set automatic key rotation interval for wireguard tunnels - fn set_wireguard_rotation_interval( - &self, - _: Self::Metadata, - interval: Option<u32>, - ) -> BoxFuture<(), Error> { log::debug!("set_wireguard_rotation_interval({:?})", interval); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::SetWireguardRotationInterval(tx, interval)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetWireguardRotationInterval( + tx, + Some(interval), + )) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn get_settings(&self, _: Self::Metadata) -> BoxFuture<Settings, Error> { - log::debug!("get_settings"); + async fn reset_wireguard_rotation_interval(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("reset_wireguard_rotation_interval"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetSettings(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::SetWireguardRotationInterval(tx, None)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn generate_wireguard_key( - &self, - _: Self::Metadata, - ) -> BoxFuture<mullvad_types::wireguard::KeygenEvent, Error> { + async fn generate_wireguard_key(&self, _: Request<()>) -> ServiceResult<proto::KeygenEvent> { + // TODO: return error for TooManyKeys, GenerationFailure + // on success, simply return the new key or nil log::debug!("generate_wireguard_key"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GenerateWireguardKey(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::GenerateWireguardKey(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(|event| Response::new(convert_wireguard_key_event(&event))) + .compat() + .await } - fn get_wireguard_key( - &self, - _: Self::Metadata, - ) -> BoxFuture<Option<wireguard::PublicKey>, Error> { + async fn get_wireguard_key(&self, _: Request<()>) -> ServiceResult<proto::PublicKey> { log::debug!("get_wireguard_key"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetWireguardKey(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::GetWireguardKey(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .then(|response| match response { + Ok(Some(key)) => Ok(Response::new(convert_public_key(&key))), + Ok(None) => Err(tonic::Status::not_found("no WireGuard key was found")), + Err(e) => Err(e), + }) + .compat() + .await } - fn verify_wireguard_key(&self, _: Self::Metadata) -> BoxFuture<bool, Error> { + async fn verify_wireguard_key(&self, _: Request<()>) -> ServiceResult<bool> { log::debug!("verify_wireguard_key"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::VerifyWireguardKey(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) - } - - fn get_current_version(&self, _: Self::Metadata) -> BoxFuture<String, Error> { - log::debug!("get_current_version"); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetCurrentVersion(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - - Box::new(future) - } - - fn get_version_info(&self, _: Self::Metadata) -> BoxFuture<version::AppVersionInfo, Error> { - log::debug!("get_version_info"); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::GetVersionInfo(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - - Box::new(future) + self.send_command_to_daemon(DaemonCommand::VerifyWireguardKey(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } - fn factory_reset(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - #[cfg(not(target_os = "android"))] - { - log::debug!("factory_reset"); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::FactoryReset(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - - Box::new(future) - } - #[cfg(target_os = "android")] - { - Box::new(future::ok(())) - } - } + // Split tunneling + // - fn get_split_tunnel_processes(&self, _: Self::Metadata) -> BoxFuture<Vec<i32>, Error> { + async fn get_split_tunnel_processes( + &self, + _: Request<()>, + ) -> ServiceResult<Self::GetSplitTunnelProcessesStream> { #[cfg(target_os = "linux")] { log::debug!("get_split_tunnel_processes"); let (tx, rx) = sync::oneshot::channel(); - let future = self + let pids = self .send_command_to_daemon(DaemonCommand::GetSplitTunnelProcesses(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .compat() + .await?; + + let (tx, rx) = tokio02::sync::mpsc::unbounded_channel(); + tokio02::spawn(async move { + for pid in pids { + let _ = tx.send(Ok(pid)); + } + }); + + Ok(Response::new(rx)) } #[cfg(not(target_os = "linux"))] { - Box::new(future::ok(Vec::with_capacity(0))) + let (_, rx) = tokio02::sync::mpsc::unbounded_channel(); + Ok(Response::new(rx)) } } #[cfg(target_os = "linux")] - fn add_split_tunnel_process(&self, _: Self::Metadata, pid: i32) -> BoxFuture<(), Error> { + async fn add_split_tunnel_process(&self, request: Request<i32>) -> ServiceResult<()> { + let pid = request.into_inner(); log::debug!("add_split_tunnel_process"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::AddSplitTunnelProcess(tx, pid)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::AddSplitTunnelProcess(tx, pid)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } #[cfg(not(target_os = "linux"))] - fn add_split_tunnel_process(&self, _: Self::Metadata, _: i32) -> BoxFuture<(), Error> { - Box::new(future::ok(())) + async fn add_split_tunnel_process(&self, _: Request<i32>) -> ServiceResult<()> { + Ok(Response::new(())) } #[cfg(target_os = "linux")] - fn remove_split_tunnel_process(&self, _: Self::Metadata, pid: i32) -> BoxFuture<(), Error> { + async fn remove_split_tunnel_process(&self, request: Request<i32>) -> ServiceResult<()> { + let pid = request.into_inner(); log::debug!("remove_split_tunnel_process"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::RemoveSplitTunnelProcess(tx, pid)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::RemoveSplitTunnelProcess(tx, pid)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } #[cfg(not(target_os = "linux"))] - fn remove_split_tunnel_process(&self, _: Self::Metadata, _: i32) -> BoxFuture<(), Error> { - Box::new(future::ok(())) + async fn remove_split_tunnel_process(&self, _: Request<i32>) -> ServiceResult<()> { + Ok(Response::new(())) } - fn clear_split_tunnel_processes(&self, _: Self::Metadata) -> BoxFuture<(), Error> { + async fn clear_split_tunnel_processes(&self, _: Request<()>) -> ServiceResult<()> { #[cfg(target_os = "linux")] { log::debug!("clear_split_tunnel_processes"); let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(DaemonCommand::ClearSplitTunnelProcesses(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + self.send_command_to_daemon(DaemonCommand::ClearSplitTunnelProcesses(tx)) + .and_then(|_| rx.map_err(|_| tonic::Status::internal("internal error"))) + .map(Response::new) + .compat() + .await } #[cfg(not(target_os = "linux"))] { - Box::new(future::ok(())) + Ok(Response::new(())) } } +} - - fn daemon_event_subscribe( +impl ManagementServiceImpl { + /// Sends a command to the daemon and maps the error to an RPC error. + fn send_command_to_daemon( &self, - _: Self::Metadata, - subscriber: pubsub::Subscriber<DaemonEvent>, - ) { - log::debug!("daemon_event_subscribe"); - let mut subscriptions = self.subscriptions.write(); - loop { - let id = SubscriptionId::String(uuid::Uuid::new_v4().to_string()); - if let Entry::Vacant(entry) = subscriptions.entry(id.clone()) { - if let Ok(sink) = subscriber.assign_id(id.clone()) { - log::debug!("Accepting new subscription with id {:?}", id); - entry.insert(sink); + command: DaemonCommand, + ) -> impl Future<Item = (), Error = tonic::Status> { + future::result( + self.daemon_tx + .send(command) + .map_err(|_| tonic::Status::internal("internal error")), + ) + } +} + +fn convert_settings(settings: &Settings) -> proto::Settings { + proto::Settings { + account_token: settings.get_account_token().unwrap_or_default(), + relay_settings: Some(convert_relay_settings(&settings.get_relay_settings())), + bridge_settings: Some(convert_bridge_settings(&settings.bridge_settings)), + bridge_state: Some(convert_bridge_state(settings.get_bridge_state())), + allow_lan: settings.allow_lan, + block_when_disconnected: settings.block_when_disconnected, + auto_connect: settings.auto_connect, + tunnel_options: Some(convert_tunnel_options(&settings.tunnel_options)), + show_beta_releases: settings.show_beta_releases, + } +} + +fn convert_relay_settings_update( + settings: &proto::RelaySettingsUpdate, +) -> Result<RelaySettingsUpdate, tonic::Status> { + use mullvad_types::CustomTunnelEndpoint; + use proto::{ + connection_config::Config as ProtoConnectionConfig, + relay_settings_update::Type as ProtoUpdateType, + }; + use talpid_types::net::{self, openvpn, wireguard}; + + let update_value = settings + .r#type + .clone() + .ok_or(tonic::Status::invalid_argument("missing relay settings"))?; + + match update_value { + ProtoUpdateType::Custom(settings) => { + let config = settings + .config + .ok_or(tonic::Status::invalid_argument("missing relay settings"))?; + let config = config + .config + .ok_or(tonic::Status::invalid_argument("missing relay settings"))?; + let config = match config { + ProtoConnectionConfig::Openvpn(config) => { + let address = match config.address.parse() { + Ok(address) => address, + Err(_) => return Err(tonic::Status::invalid_argument("invalid address")), + }; + + ConnectionConfig::OpenVpn(openvpn::ConnectionConfig { + endpoint: net::Endpoint { + address, + protocol: match proto::TransportProtocol::from_i32(config.protocol) { + Some(proto::TransportProtocol::Udp) => TransportProtocol::Udp, + Some(proto::TransportProtocol::Tcp) => TransportProtocol::Tcp, + None | Some(proto::TransportProtocol::AnyProtocol) => { + return Err(tonic::Status::invalid_argument( + "unknown transport protocol", + )) + } + }, + }, + username: config.username.clone(), + password: config.password.clone(), + }) } - break; + ProtoConnectionConfig::Wireguard(config) => { + let tunnel = config + .tunnel + .ok_or(tonic::Status::invalid_argument("missing tunnel config"))?; + + // Copy the private key to an array + if tunnel.private_key.len() != 32 { + return Err(tonic::Status::invalid_argument("invalid private key")); + } + + let mut private_key = [0; 32]; + let buffer = &tunnel.private_key[..private_key.len()]; + private_key.copy_from_slice(buffer); + + let peer = config + .peer + .ok_or(tonic::Status::invalid_argument("missing peer config"))?; + + // Copy the public key to an array + if peer.public_key.len() != 32 { + return Err(tonic::Status::invalid_argument("invalid public key")); + } + + let mut public_key = [0; 32]; + let buffer = &peer.public_key[..public_key.len()]; + public_key.copy_from_slice(buffer); + + let ipv4_gateway = match config.ipv4_gateway.parse() { + Ok(address) => address, + Err(_) => { + return Err(tonic::Status::invalid_argument("invalid IPv4 gateway")) + } + }; + let ipv6_gateway = if !config.ipv6_gateway.is_empty() { + let address = match config.ipv6_gateway.parse() { + Ok(address) => address, + Err(_) => { + return Err(tonic::Status::invalid_argument("invalid IPv6 gateway")) + } + }; + Some(address) + } else { + None + }; + + let endpoint = match peer.endpoint.parse() { + Ok(address) => address, + Err(_) => { + return Err(tonic::Status::invalid_argument("invalid peer address")) + } + }; + + let mut tunnel_addresses = Vec::new(); + for address in tunnel.addresses { + let address = address + .parse() + .map_err(|_| tonic::Status::invalid_argument("invalid address"))?; + tunnel_addresses.push(address); + } + + let mut allowed_ips = Vec::new(); + for address in peer.allowed_ips { + let address = address + .parse() + .map_err(|_| tonic::Status::invalid_argument("invalid address"))?; + allowed_ips.push(address); + } + + ConnectionConfig::Wireguard(wireguard::ConnectionConfig { + tunnel: wireguard::TunnelConfig { + private_key: wireguard::PrivateKey::from(private_key), + addresses: tunnel_addresses, + }, + peer: wireguard::PeerConfig { + public_key: wireguard::PublicKey::from(public_key), + allowed_ips, + endpoint, + }, + ipv4_gateway, + ipv6_gateway, + }) + } + }; + + Ok(RelaySettingsUpdate::CustomTunnelEndpoint( + CustomTunnelEndpoint { + host: settings.host.clone(), + config, + }, + )) + } + + ProtoUpdateType::Normal(settings) => { + // If `location` isn't provided, no changes are made. + // If `location` is provided, but is an empty vector, + // then the constraint is set to `Constraint::Any`. + let location = settings.location.map(convert_proto_location); + + let tunnel_protocol = if let Some(update) = settings.tunnel_type { + match proto::TunnelType::from_i32(update.tunnel_type) { + Some(proto::TunnelType::AnyTunnel) => Some(Constraint::Any), + Some(proto::TunnelType::Openvpn) => Some(Constraint::Only(TunnelType::OpenVpn)), + Some(proto::TunnelType::Wireguard) => { + Some(Constraint::Only(TunnelType::Wireguard)) + } + None => return Err(tonic::Status::invalid_argument("unknown tunnel protocol")), + } + } else { + None + }; + + Ok(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { + location, + tunnel_protocol, + wireguard_constraints: settings.wireguard_constraints.map(|constraints| { + WireguardConstraints { + port: if constraints.port != 0 { + Constraint::Only(constraints.port as u16) + } else { + Constraint::Any + }, + } + }), + openvpn_constraints: settings.openvpn_constraints.map(|constraints| { + OpenVpnConstraints { + port: if constraints.port != 0 { + Constraint::Only(constraints.port as u16) + } else { + Constraint::Any + }, + protocol: match proto::TransportProtocol::from_i32(constraints.protocol) { + Some(proto::TransportProtocol::Udp) => { + Constraint::Only(TransportProtocol::Udp) + } + Some(proto::TransportProtocol::Tcp) => { + Constraint::Only(TransportProtocol::Tcp) + } + _ => Constraint::Any, + }, + } + }), + })) + } + } +} + +fn convert_relay_settings(settings: &RelaySettings) -> proto::RelaySettings { + use proto::relay_settings; + + let endpoint = match settings { + RelaySettings::CustomTunnelEndpoint(endpoint) => { + relay_settings::Endpoint::Custom(proto::CustomRelaySettings { + host: endpoint.host.clone(), + config: Some(convert_connection_config(&endpoint.config)), + }) + } + RelaySettings::Normal(constraints) => { + relay_settings::Endpoint::Normal(proto::NormalRelaySettings { + location: convert_location_constraint(&constraints.location), + tunnel_type: match constraints.tunnel_protocol { + Constraint::Any => i32::from(proto::TunnelType::AnyTunnel), + Constraint::Only(TunnelType::Wireguard) => { + i32::from(proto::TunnelType::Wireguard) + } + Constraint::Only(TunnelType::OpenVpn) => i32::from(proto::TunnelType::Openvpn), + }, + + wireguard_constraints: Some(proto::WireguardConstraints { + port: u32::from(constraints.wireguard_constraints.port.unwrap_or(0)), + }), + + openvpn_constraints: Some(proto::OpenvpnConstraints { + port: u32::from(constraints.openvpn_constraints.port.unwrap_or(0)), + protocol: i32::from( + constraints + .openvpn_constraints + .protocol + .map(|protocol| match protocol { + TransportProtocol::Tcp => proto::TransportProtocol::Tcp, + TransportProtocol::Udp => proto::TransportProtocol::Udp, + }) + .unwrap_or(proto::TransportProtocol::AnyProtocol), + ), + }), + }) + } + }; + + proto::RelaySettings { + endpoint: Some(endpoint), + } +} + +fn convert_connection_config(config: &ConnectionConfig) -> proto::ConnectionConfig { + use proto::connection_config; + + proto::ConnectionConfig { + config: Some(match config { + ConnectionConfig::OpenVpn(config) => { + connection_config::Config::Openvpn(connection_config::OpenvpnConfig { + address: config.endpoint.address.to_string(), + protocol: match config.endpoint.protocol { + TransportProtocol::Tcp => i32::from(proto::TransportProtocol::Tcp), + TransportProtocol::Udp => i32::from(proto::TransportProtocol::Udp), + }, + username: config.username.clone(), + password: config.password.clone(), + }) + } + ConnectionConfig::Wireguard(config) => { + connection_config::Config::Wireguard(connection_config::WireguardConfig { + tunnel: Some(connection_config::wireguard_config::TunnelConfig { + private_key: config.tunnel.private_key.to_bytes().to_vec(), + addresses: config + .tunnel + .addresses + .iter() + .map(|address| address.to_string()) + .collect(), + }), + peer: Some(connection_config::wireguard_config::PeerConfig { + public_key: config.peer.public_key.as_bytes().to_vec(), + allowed_ips: config + .peer + .allowed_ips + .iter() + .map(|address| address.to_string()) + .collect(), + endpoint: config.peer.endpoint.to_string(), + }), + ipv4_gateway: config.ipv4_gateway.to_string(), + ipv6_gateway: config + .ipv6_gateway + .as_ref() + .map(|address| address.to_string()) + .unwrap_or_default(), + }) } + }), + } +} + +fn convert_bridge_settings(settings: &BridgeSettings) -> proto::BridgeSettings { + use proto::bridge_settings::{self, Type as BridgeSettingType}; + use talpid_types::net; + + let settings = match settings { + BridgeSettings::Normal(constraints) => { + BridgeSettingType::Normal(proto::bridge_settings::BridgeConstraints { + location: convert_location_constraint(&constraints.location), + }) } + BridgeSettings::Custom(proxy_settings) => match proxy_settings { + net::openvpn::ProxySettings::Local(proxy_settings) => { + BridgeSettingType::Local(bridge_settings::LocalProxySettings { + port: u32::from(proxy_settings.port), + peer: proxy_settings.peer.to_string(), + }) + } + net::openvpn::ProxySettings::Remote(proxy_settings) => { + BridgeSettingType::Remote(bridge_settings::RemoteProxySettings { + address: proxy_settings.address.to_string(), + auth: proxy_settings.auth.as_ref().map(|auth| { + bridge_settings::RemoteProxyAuth { + username: auth.username.clone(), + password: auth.password.clone(), + } + }), + }) + } + net::openvpn::ProxySettings::Shadowsocks(proxy_settings) => { + BridgeSettingType::Shadowsocks(bridge_settings::ShadowsocksProxySettings { + peer: proxy_settings.peer.to_string(), + password: proxy_settings.password.clone(), + cipher: proxy_settings.cipher.clone(), + }) + } + }, + }; + + proto::BridgeSettings { + r#type: Some(settings), } +} - fn daemon_event_unsubscribe(&self, id: SubscriptionId) -> BoxFuture<(), Error> { - log::debug!("daemon_event_unsubscribe"); - let was_removed = self.subscriptions.write().remove(&id).is_some(); - let result = if was_removed { - log::debug!("Unsubscribing id {:?}", id); - future::ok(()) +fn convert_wireguard_key_event( + event: &mullvad_types::wireguard::KeygenEvent, +) -> proto::KeygenEvent { + use mullvad_types::wireguard::KeygenEvent::*; + use proto::keygen_event::KeygenEvent as ProtoEvent; + + proto::KeygenEvent { + event: match event { + NewKey(_) => i32::from(ProtoEvent::NewKey), + TooManyKeys => i32::from(ProtoEvent::TooManyKeys), + GenerationFailure => i32::from(ProtoEvent::GenerationFailure), + }, + new_key: if let NewKey(key) = event { + Some(convert_public_key(&key)) } else { - future::err(Error { - code: ErrorCode::InvalidParams, - message: "Invalid subscription".to_owned(), - data: None, - }) + None + }, + } +} + +fn convert_public_key(public_key: &wireguard::PublicKey) -> proto::PublicKey { + proto::PublicKey { + key: public_key.key.as_bytes().to_vec(), + created: Some(prost_types::Timestamp { + seconds: public_key.created.timestamp(), + nanos: 0, + }), + } +} + +fn convert_location_constraint( + location: &Constraint<LocationConstraint>, +) -> Option<proto::RelayLocation> { + if location.is_any() { + return None; + } + + Some(match location.as_ref().unwrap() { + LocationConstraint::Country(country) => proto::RelayLocation { + country: country.to_string(), + ..Default::default() + }, + LocationConstraint::City(country, city) => proto::RelayLocation { + country: country.to_string(), + city: city.to_string(), + ..Default::default() + }, + LocationConstraint::Hostname(country, city, hostname) => proto::RelayLocation { + country: country.to_string(), + city: city.to_string(), + hostname: hostname.to_string(), + }, + }) +} + +fn convert_bridge_state(state: &BridgeState) -> proto::BridgeState { + let state = match state { + BridgeState::Auto => proto::bridge_state::State::Auto, + BridgeState::On => proto::bridge_state::State::On, + BridgeState::Off => proto::bridge_state::State::Off, + }; + proto::BridgeState { + state: i32::from(state), + } +} + +fn convert_tunnel_options(options: &TunnelOptions) -> proto::TunnelOptions { + proto::TunnelOptions { + openvpn: Some(proto::tunnel_options::OpenvpnOptions { + mssfix: u32::from(options.openvpn.mssfix.unwrap_or_default()), + }), + wireguard: Some(proto::tunnel_options::WireguardOptions { + mtu: u32::from(options.wireguard.mtu.unwrap_or_default()), + automatic_rotation: options + .wireguard + .automatic_rotation + .unwrap_or((DEFAULT_AUTOMATIC_KEY_ROTATION.as_secs() / 60u64 / 60u64) as u32), + }), + generic: Some(proto::tunnel_options::GenericOptions { + enable_ipv6: options.generic.enable_ipv6, + }), + } +} + +fn convert_relay_list_country(country: &RelayListCountry) -> proto::RelayListCountry { + let mut proto_country = proto::RelayListCountry { + name: country.name.clone(), + code: country.code.clone(), + cities: Vec::with_capacity(country.cities.len()), + }; + + for city in &country.cities { + proto_country.cities.push(proto::RelayListCity { + name: city.name.clone(), + code: city.code.clone(), + latitude: city.latitude, + longitude: city.longitude, + relays: city + .relays + .iter() + .map(|relay| convert_relay(relay)) + .collect(), + }); + } + + proto_country +} + +fn convert_relay(relay: &Relay) -> proto::Relay { + proto::Relay { + hostname: relay.hostname.clone(), + ipv4_addr_in: relay.ipv4_addr_in.to_string(), + ipv6_addr_in: relay + .ipv6_addr_in + .map(|addr| addr.to_string()) + .unwrap_or_default(), + include_in_country: relay.include_in_country, + active: relay.active, + owned: relay.owned, + provider: relay.provider.clone(), + weight: relay.weight, + tunnels: Some(proto::RelayTunnels { + openvpn: relay + .tunnels + .openvpn + .iter() + .map(|endpoint| { + let protocol = match endpoint.protocol { + TransportProtocol::Udp => proto::TransportProtocol::Udp, + TransportProtocol::Tcp => proto::TransportProtocol::Tcp, + }; + proto::OpenVpnEndpointData { + port: u32::from(endpoint.port), + protocol: i32::from(protocol), + } + }) + .collect(), + wireguard: relay + .tunnels + .wireguard + .iter() + .map(|endpoint| { + let port_ranges = endpoint + .port_ranges + .iter() + .map(|range| proto::PortRange { + first: u32::from(range.0), + last: u32::from(range.1), + }) + .collect(); + proto::WireguardEndpointData { + port_ranges, + ipv4_gateway: endpoint.ipv4_gateway.to_string(), + ipv6_gateway: endpoint.ipv6_gateway.to_string(), + public_key: endpoint.public_key.as_bytes().to_vec(), + } + }) + .collect(), + }), + bridges: Some(proto::RelayBridges { + shadowsocks: relay + .bridges + .shadowsocks + .iter() + .map(|endpoint| { + let protocol = match endpoint.protocol { + TransportProtocol::Udp => proto::TransportProtocol::Udp, + TransportProtocol::Tcp => proto::TransportProtocol::Tcp, + }; + proto::ShadowsocksEndpointData { + port: u32::from(endpoint.port), + cipher: endpoint.cipher.clone(), + password: endpoint.password.clone(), + protocol: i32::from(protocol), + } + }) + .collect(), + }), + location: relay.location.as_ref().map(|location| proto::Location { + country: location.country.clone(), + country_code: location.country_code.clone(), + city: location.city.clone(), + city_code: location.city_code.clone(), + latitude: location.latitude, + longitude: location.longitude, + }), + } +} + +fn convert_state(state: TunnelState) -> proto::TunnelState { + use proto::{ + error_state::{ + firewall_policy_error::ErrorType as PolicyErrorType, Cause as ProtoErrorCause, + FirewallPolicyError as ProtoFirewallPolicyError, + GenerationError as ProtoGenerationError, + }, + tunnel_state::{self, State as ProtoState}, + }; + use talpid_types::tunnel::{ + ActionAfterDisconnect, ErrorStateCause, FirewallPolicyError, ParameterGenerationError, + }; + use TunnelState::*; + + let state = match state { + Disconnected => ProtoState::Disconnected(tunnel_state::Disconnected {}), + Connecting { endpoint, location } => ProtoState::Connecting(tunnel_state::Connecting { + relay_info: Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(convert_endpoint(endpoint)), + location: location.map(convert_geoip_location), + }), + }), + Connected { endpoint, location } => ProtoState::Connected(tunnel_state::Connected { + relay_info: Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(convert_endpoint(endpoint)), + location: location.map(convert_geoip_location), + }), + }), + Disconnecting(after_disconnect) => ProtoState::Disconnecting(tunnel_state::Disconnecting { + after_disconnect: match after_disconnect { + ActionAfterDisconnect::Nothing => i32::from(proto::AfterDisconnect::Nothing), + ActionAfterDisconnect::Block => i32::from(proto::AfterDisconnect::Block), + ActionAfterDisconnect::Reconnect => i32::from(proto::AfterDisconnect::Reconnect), + }, + }), + Error(error_state) => ProtoState::Error(tunnel_state::Error { + error_state: Some(proto::ErrorState { + cause: match error_state.cause() { + ErrorStateCause::AuthFailed(_) => i32::from(ProtoErrorCause::AuthFailed), + ErrorStateCause::Ipv6Unavailable => i32::from(ProtoErrorCause::Ipv6Unavailable), + ErrorStateCause::SetFirewallPolicyError(_) => { + i32::from(ProtoErrorCause::SetFirewallPolicyError) + } + ErrorStateCause::SetDnsError => i32::from(ProtoErrorCause::SetDnsError), + ErrorStateCause::StartTunnelError => { + i32::from(ProtoErrorCause::StartTunnelError) + } + ErrorStateCause::TunnelParameterError(_) => { + i32::from(ProtoErrorCause::TunnelParameterError) + } + ErrorStateCause::IsOffline => i32::from(ProtoErrorCause::IsOffline), + ErrorStateCause::TapAdapterProblem => { + i32::from(ProtoErrorCause::TapAdapterProblem) + } + #[cfg(target_os = "android")] + ErrorStateCause::VpnPermissionDenied => { + i32::from(ProtoErrorCause::VpnPermissionDenied) + } + }, + is_blocking: error_state.is_blocking(), + auth_fail_reason: if let ErrorStateCause::AuthFailed(reason) = error_state.cause() { + reason.clone().unwrap_or_default() + } else { + "".to_string() + }, + parameter_error: if let ErrorStateCause::TunnelParameterError(reason) = + error_state.cause() + { + match reason { + ParameterGenerationError::NoMatchingRelay => { + i32::from(ProtoGenerationError::NoMatchingRelay) + } + ParameterGenerationError::NoMatchingBridgeRelay => { + i32::from(ProtoGenerationError::NoMatchingBridgeRelay) + } + ParameterGenerationError::NoWireguardKey => { + i32::from(ProtoGenerationError::NoWireguardKey) + } + ParameterGenerationError::CustomTunnelHostResultionError => { + i32::from(ProtoGenerationError::CustomTunnelHostResolutionError) + } + } + } else { + 0 + }, + policy_error: if let ErrorStateCause::SetFirewallPolicyError(reason) = + error_state.cause() + { + match reason { + FirewallPolicyError::Generic => Some(ProtoFirewallPolicyError { + r#type: i32::from(PolicyErrorType::Generic), + ..Default::default() + }), + #[cfg(windows)] + FirewallPolicyError::Locked(blocking_app) => { + let (lock_pid, lock_name) = match blocking_app { + Some(app) => (app.pid, app.name.clone()), + None => (0, "".to_string()), + }; + + Some(ProtoFirewallPolicyError { + r#type: i32::from(PolicyErrorType::Locked), + lock_pid, + lock_name, + }) + } + } + } else { + None + }, + }), + }), + }; + + proto::TunnelState { state: Some(state) } +} + +fn convert_endpoint(endpoint: talpid_types::net::TunnelEndpoint) -> proto::TunnelEndpoint { + use talpid_types::net; + + proto::TunnelEndpoint { + address: endpoint.endpoint.address.to_string(), + protocol: match endpoint.endpoint.protocol { + TransportProtocol::Tcp => i32::from(proto::TransportProtocol::Tcp), + TransportProtocol::Udp => i32::from(proto::TransportProtocol::Udp), + }, + tunnel_type: match endpoint.tunnel_type { + net::TunnelType::Wireguard => i32::from(proto::TunnelType::Wireguard), + net::TunnelType::OpenVpn => i32::from(proto::TunnelType::Openvpn), + }, + proxy: endpoint.proxy.map(|proxy_ep| proto::ProxyEndpoint { + address: proxy_ep.endpoint.address.to_string(), + protocol: match proxy_ep.endpoint.protocol { + TransportProtocol::Tcp => i32::from(proto::TransportProtocol::Tcp), + TransportProtocol::Udp => i32::from(proto::TransportProtocol::Udp), + }, + proxy_type: match proxy_ep.proxy_type { + net::proxy::ProxyType::Shadowsocks => i32::from(proto::ProxyType::Shadowsocks), + net::proxy::ProxyType::Custom => i32::from(proto::ProxyType::Custom), + }, + }), + } +} + +fn convert_geoip_location(geoip: GeoIpLocation) -> proto::GeoIpLocation { + proto::GeoIpLocation { + ipv4: geoip.ipv4.map(|ip| ip.to_string()).unwrap_or_default(), + ipv6: geoip.ipv6.map(|ip| ip.to_string()).unwrap_or_default(), + country: geoip.country, + city: geoip.city.unwrap_or_default(), + latitude: geoip.latitude, + longitude: geoip.longitude, + mullvad_exit_ip: geoip.mullvad_exit_ip, + hostname: geoip.hostname.unwrap_or_default(), + bridge_hostname: geoip.bridge_hostname.unwrap_or_default(), + } +} + +fn convert_version_info(version_info: &version::AppVersionInfo) -> proto::AppVersionInfo { + proto::AppVersionInfo { + supported: version_info.supported, + latest_stable: version_info.latest_stable.clone(), + latest_beta: version_info.latest_beta.clone(), + suggested_upgrade: version_info.suggested_upgrade.clone().unwrap_or_default(), + } +} + +fn convert_proto_location(location: proto::RelayLocation) -> Constraint<LocationConstraint> { + if !location.hostname.is_empty() { + Constraint::Only(LocationConstraint::Hostname( + location.country, + location.city, + location.hostname, + )) + } else if !location.city.is_empty() { + Constraint::Only(LocationConstraint::City(location.country, location.city)) + } else if !location.country.is_empty() { + Constraint::Only(LocationConstraint::Country(location.country)) + } else { + Constraint::Any + } +} + +pub struct ManagementInterfaceServer { + subscriptions: Arc<RwLock<Vec<EventsListenerSender>>>, + socket_path: String, + runtime: tokio02::runtime::Runtime, + server_abort_tx: triggered::Trigger, + server_join_handle: + Option<tokio02::task::JoinHandle<std::result::Result<(), tonic::transport::Error>>>, +} + +impl ManagementInterfaceServer { + async fn start_server( + socket_path: String, + daemon_tx: DaemonCommandSender, + server_start_tx: std::sync::mpsc::Sender<()>, + abort_rx: triggered::Listener, + subscriptions: Arc<RwLock<Vec<EventsListenerSender>>>, + ) -> std::result::Result<(), tonic::transport::Error> { + use futures::stream::TryStreamExt; + use parity_tokio_ipc::{Endpoint as IpcEndpoint, SecurityAttributes}; + + let mut endpoint = IpcEndpoint::new(socket_path); + endpoint.set_security_attributes( + SecurityAttributes::allow_everyone_create() + .unwrap() + .set_mode(777) + .unwrap(), + ); + let incoming = endpoint.incoming().unwrap(); + let _ = server_start_tx.send(()); + + let server = ManagementServiceImpl { + daemon_tx, + subscriptions, }; - Box::new(result) + + Server::builder() + .add_service(ManagementServiceServer::new(server)) + .serve_with_incoming_shutdown(incoming.map_ok(StreamBox), abort_rx) + .await + } + + pub fn start(tunnel_tx: DaemonCommandSender) -> Result<Self, Error> { + // TODO: don't spawn a tokio runtime here; make this function async + let mut runtime = tokio02::runtime::Builder::new() + .threaded_scheduler() + .core_threads(1) + .enable_all() + .build() + .map_err(Error::TokioRuntimeError)?; + + let subscriptions = Arc::<RwLock<Vec<EventsListenerSender>>>::default(); + + let socket_path = mullvad_paths::get_rpc_socket_path() + .to_string_lossy() + .to_string(); + + let (server_abort_tx, server_abort_rx) = triggered::trigger(); + let (start_tx, start_rx) = mpsc::channel(); + let server_join_handle = runtime.spawn(Self::start_server( + socket_path.clone(), + tunnel_tx, + start_tx, + server_abort_rx, + subscriptions.clone(), + )); + + if let Err(_) = start_rx.recv() { + return Err(runtime + .block_on(server_join_handle) + .expect("Failed to resolve quit handle future") + .map_err(Error::SetupError) + .unwrap_err()); + } + + #[cfg(unix)] + { + use std::{fs, os::unix::fs::PermissionsExt}; + fs::set_permissions(&socket_path, PermissionsExt::from_mode(0o766)) + .map_err(Error::PermissionsError)?; + } + #[cfg(windows)] + crate::windows_permissions::deny_network_access(&socket_path) + .map_err(Error::PermissionsError)?; + + Ok(ManagementInterfaceServer { + subscriptions, + socket_path: socket_path.to_string(), + runtime, + server_abort_tx, + server_join_handle: Some(server_join_handle), + }) + } + + pub fn socket_path(&self) -> &str { + &self.socket_path + } + + pub fn event_broadcaster(&self) -> ManagementInterfaceEventBroadcaster { + ManagementInterfaceEventBroadcaster { + runtime: self.runtime.handle().clone(), + subscriptions: self.subscriptions.clone(), + close_handle: self.server_abort_tx.clone(), + } + } + + /// Consumes the server and waits for it to finish. Returns an error if the server exited + /// due to an error. + pub fn wait(mut self) { + if let Some(server_join_handle) = self.server_join_handle { + if let Err(error) = self.runtime.block_on(server_join_handle) { + log::error!("Management server panic: {:?}", error); + } + } + } +} + +/// A handle that allows broadcasting messages to all subscribers of the management interface. +#[derive(Clone)] +pub struct ManagementInterfaceEventBroadcaster { + runtime: tokio02::runtime::Handle, + subscriptions: Arc<RwLock<Vec<EventsListenerSender>>>, + close_handle: triggered::Trigger, +} + +impl EventListener for ManagementInterfaceEventBroadcaster { + /// Sends a new state update to all `new_state` subscribers of the management interface. + fn notify_new_state(&self, new_state: TunnelState) { + self.notify(proto::DaemonEvent { + event: Some(DaemonEventType::TunnelState(convert_state(new_state))), + }) + } + + /// Sends settings to all `settings` subscribers of the management interface. + fn notify_settings(&self, settings: Settings) { + log::debug!("Broadcasting new settings"); + self.notify(proto::DaemonEvent { + event: Some(DaemonEventType::Settings(convert_settings(&settings))), + }) + } + + /// Sends relays to all subscribers of the management interface. + fn notify_relay_list(&self, relay_list: RelayList) { + log::debug!("Broadcasting new relay list"); + let mut new_list = proto::RelayList { + countries: Vec::new(), + }; + new_list.countries.reserve(relay_list.countries.len()); + for country in &relay_list.countries { + new_list.countries.push(convert_relay_list_country(country)); + } + self.notify(proto::DaemonEvent { + event: Some(DaemonEventType::RelayList(new_list)), + }) + } + + fn notify_app_version(&self, app_version_info: version::AppVersionInfo) { + log::debug!("Broadcasting new app version info"); + let new_info = convert_version_info(&app_version_info); + self.notify(proto::DaemonEvent { + event: Some(DaemonEventType::VersionInfo(new_info)), + }) + } + + fn notify_key_event(&self, key_event: mullvad_types::wireguard::KeygenEvent) { + log::debug!("Broadcasting new wireguard key event"); + let new_event = convert_wireguard_key_event(&key_event); + self.notify(proto::DaemonEvent { + event: Some(DaemonEventType::KeyEvent(new_event)), + }) } } +impl ManagementInterfaceEventBroadcaster { + fn notify(&self, value: proto::DaemonEvent) { + let mut subscriptions = self.subscriptions.write(); + // TODO: using write-lock everywhere. use a mutex instead? + subscriptions.retain(|tx| tx.send(Ok(value.clone())).is_ok()); + } +} -/// The metadata type. There is one instance associated with each connection. In this pubsub -/// scenario they are created by `meta_extractor` by the server on each new incoming -/// connection. -#[derive(Clone, Debug, Default)] -pub struct Meta { - session: Option<Arc<Session>>, +impl Drop for ManagementInterfaceEventBroadcaster { + fn drop(&mut self) { + self.close_handle.trigger(); + } } -/// Make the `Meta` type possible to use as jsonrpc metadata type. -impl Metadata for Meta {} +// Converts a REST API error for an account into a tonic status. +fn map_rest_account_error(error: RestError) -> tonic::Status { + match error { + RestError::ApiError(status, message) + if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN => + { + tonic::Status::new(tonic::Code::Unauthenticated, message) + } + _ => tonic::Status::internal("internal error"), + } +} -/// Make the `Meta` type possible to use as a pubsub metadata type. -impl PubSubMetadata for Meta { - fn session(&self) -> Option<Arc<Session>> { - self.session.clone() + +// FIXME +use std::{ + pin::Pin, + task::{Context, Poll}, +}; +use tokio02::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct StreamBox<T: AsyncRead + AsyncWrite>(pub T); +impl<T: AsyncRead + AsyncWrite> Connected for StreamBox<T> {} +impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for StreamBox<T> { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll<std::io::Result<usize>> { + Pin::new(&mut self.0).poll_read(cx, buf) } } +impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for StreamBox<T> { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll<std::io::Result<usize>> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { + Pin::new(&mut self.0).poll_flush(cx) + } -/// Metadata extractor function for `Meta`. -fn meta_extractor(context: &jsonrpc_ipc_server::RequestContext<'_>) -> Meta { - Meta { - session: Some(Arc::new(Session::new(context.sender.clone()))), + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { + Pin::new(&mut self.0).poll_shutdown(cx) } } diff --git a/mullvad-daemon/src/rpc_uniqueness_check.rs b/mullvad-daemon/src/rpc_uniqueness_check.rs index 7befdb4ba1..50a9a150a7 100644 --- a/mullvad-daemon/src/rpc_uniqueness_check.rs +++ b/mullvad-daemon/src/rpc_uniqueness_check.rs @@ -1,13 +1,37 @@ -use mullvad_ipc_client::new_standalone_ipc_client; use mullvad_paths; +use parity_tokio_ipc::Endpoint as IpcEndpoint; use talpid_types::ErrorExt; +use tonic::{ + self, + transport::{self, Endpoint, Uri}, +}; +use tower::service_fn; + +mod proto { + tonic::include_proto!("mullvad_daemon.management_interface"); +} +use proto::management_service_client::ManagementServiceClient; + +async fn new_grpc_client() -> Result<ManagementServiceClient<transport::Channel>, transport::Error> +{ + let ipc_path = mullvad_paths::get_rpc_socket_path(); + + // The URI will be ignored + let channel = Endpoint::from_static("lttp://[::]:50051") + .connect_with_connector(service_fn(move |_: Uri| { + IpcEndpoint::connect(ipc_path.clone()) + })) + .await?; + + Ok(ManagementServiceClient::new(channel)) +} /// Checks if there is another instance of the daemon running. /// /// Tries to connect to another daemon and perform a simple RPC call. If it fails, assumes the /// other daemon has stopped. -pub fn is_another_instance_running() -> bool { - match new_standalone_ipc_client(&mullvad_paths::get_rpc_socket_path()) { +pub async fn is_another_instance_running() -> bool { + match new_grpc_client().await { Ok(_) => true, Err(error) => { let msg = diff --git a/talpid-ipc/src/win.rs b/mullvad-daemon/src/windows_permissions.rs index 0b39282348..0b39282348 100644 --- a/talpid-ipc/src/win.rs +++ b/mullvad-daemon/src/windows_permissions.rs diff --git a/mullvad-daemon/src/wireguard.rs b/mullvad-daemon/src/wireguard.rs index 2ebd5fae50..58daf58ca5 100644 --- a/mullvad-daemon/src/wireguard.rs +++ b/mullvad-daemon/src/wireguard.rs @@ -22,7 +22,7 @@ use talpid_types::ErrorExt; use tokio_timer; /// Default automatic key rotation -const DEFAULT_AUTOMATIC_KEY_ROTATION: Duration = Duration::from_secs(7 * 24 * 60 * 60); +pub const DEFAULT_AUTOMATIC_KEY_ROTATION: Duration = Duration::from_secs(7 * 24 * 60 * 60); /// How long to wait before reattempting to rotate keys on failure const AUTOMATIC_ROTATION_RETRY_DELAY: Duration = Duration::from_secs(60 * 15); /// How often to check whether the key has expired. diff --git a/mullvad-ipc-client/Cargo.toml b/mullvad-ipc-client/Cargo.toml deleted file mode 100644 index 1107c4f68a..0000000000 --- a/mullvad-ipc-client/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "mullvad-ipc-client" -version = "0.1.0" -authors = ["Mullvad VPN"] -description = "RPC client for Mullvad daemon" -license = "GPL-3.0" -edition = "2018" -publish = false - -[dependencies] -err-derive = "0.2.1" -mullvad-types = { path = "../mullvad-types" } -serde = "1.0" -talpid-ipc = { path = "../talpid-ipc" } -talpid-types = { path = "../talpid-types" } -mullvad-paths = { path = "../mullvad-paths" } -jsonrpc-client-core = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } -jsonrpc-client-ipc = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } -jsonrpc-client-pubsub = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } -tokio = "0.1.15" -tokio-timer = "0.1" -futures = "0.1" -log = "0.4" - - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["accctrl", "aclapi", "securitybaseapi", "winbase", "winerror", "winnt"] } diff --git a/mullvad-ipc-client/src/lib.rs b/mullvad-ipc-client/src/lib.rs deleted file mode 100644 index f1bae521b8..0000000000 --- a/mullvad-ipc-client/src/lib.rs +++ /dev/null @@ -1,281 +0,0 @@ -#![deny(rust_2018_idioms)] - -use futures::sync::oneshot; -use jsonrpc_client_core::{Client, ClientHandle, Future}; -use jsonrpc_client_ipc::IpcTransport; -use mullvad_types::{ - account::{AccountData, AccountToken, VoucherSubmission}, - location::GeoIpLocation, - relay_constraints::{BridgeSettings, BridgeState, RelaySettings, RelaySettingsUpdate}, - relay_list::RelayList, - settings::{Settings, TunnelOptions}, - states::TunnelState, - version::AppVersionInfo, - wireguard, DaemonEvent, -}; -use serde::{Deserialize, Serialize}; -use std::{io, path::Path, thread}; - -static NO_ARGS: [u8; 0] = []; - -pub type Result<T> = std::result::Result<T, jsonrpc_client_core::Error>; -pub use jsonrpc_client_core::{Error, ErrorKind}; -pub use jsonrpc_client_pubsub::Error as PubSubError; - -pub fn new_standalone_ipc_client(path: &impl AsRef<Path>) -> io::Result<DaemonRpcClient> { - let path = path.as_ref().to_string_lossy().to_string(); - - new_standalone_transport(path, |path| { - IpcTransport::new(&path, &tokio::reactor::Handle::default()) - }) -} - -pub fn new_standalone_transport< - F: Send + 'static + FnOnce(String) -> io::Result<T>, - T: jsonrpc_client_core::DuplexTransport + 'static, ->( - rpc_path: String, - transport_func: F, -) -> io::Result<DaemonRpcClient> { - let (tx, rx) = oneshot::channel(); - thread::spawn(move || match spawn_transport(rpc_path, transport_func) { - Err(e) => tx - .send(Err(e)) - .expect("Failed to send error back to caller"), - Ok((client, server_handle, client_handle)) => { - let mut rt = tokio::runtime::current_thread::Runtime::new() - .expect("Failed to start a standalone tokio runtime for mullvad ipc"); - let handle = rt.handle(); - tx.send(Ok((client_handle, server_handle, handle))) - .expect("Failed to send client handle"); - - if let Err(e) = rt.block_on(client) { - log::error!("JSON-RPC client failed: {}", e.description()); - } - } - }); - - rx.wait() - .map_err(|_| io::Error::new(io::ErrorKind::NotFound, "No transport handles returned"))? - .map(|(rpc_client, server_handle, executor)| { - let subscriber = - jsonrpc_client_pubsub::Subscriber::new(executor, rpc_client.clone(), server_handle); - DaemonRpcClient { - rpc_client, - subscriber, - } - }) -} - -fn spawn_transport< - F: Send + FnOnce(String) -> io::Result<T>, - T: jsonrpc_client_core::DuplexTransport + 'static, ->( - address: String, - transport_func: F, -) -> io::Result<( - Client<T, jsonrpc_client_core::server::Server>, - jsonrpc_client_core::server::ServerHandle, - ClientHandle, -)> { - let (server, server_handle) = jsonrpc_client_core::server::Server::new(); - let transport = transport_func(address)?; - let (client, client_handle) = jsonrpc_client_core::Client::with_server(transport, server); - Ok((client, server_handle, client_handle)) -} - -pub struct DaemonRpcClient { - rpc_client: jsonrpc_client_core::ClientHandle, - subscriber: jsonrpc_client_pubsub::Subscriber<tokio::runtime::current_thread::Handle>, -} - - -impl DaemonRpcClient { - pub fn connect(&mut self) -> Result<()> { - self.call("connect", &NO_ARGS) - } - - pub fn disconnect(&mut self) -> Result<()> { - self.call("disconnect", &NO_ARGS) - } - - pub fn reconnect(&mut self) -> Result<()> { - self.call("reconnect", &NO_ARGS) - } - - pub fn create_new_account(&mut self) -> Result<()> { - self.call("create_new_account", &NO_ARGS) - } - - pub fn get_account(&mut self) -> Result<Option<AccountToken>> { - self.call("get_account", &NO_ARGS) - } - - pub fn get_account_data(&mut self, account: AccountToken) -> Result<AccountData> { - self.call("get_account_data", &[account]) - } - - pub fn submit_voucher(&mut self, voucher: String) -> Result<VoucherSubmission> { - self.call("submit_voucher", &[voucher]) - } - - pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<()> { - self.call("set_allow_lan", &[allow_lan]) - } - - pub fn set_show_beta_releases(&mut self, enabled: bool) -> Result<()> { - self.call("set_show_beta_releases", &[enabled]) - } - - pub fn set_block_when_disconnected(&mut self, block_when_disconnected: bool) -> Result<()> { - self.call("set_block_when_disconnected", &[block_when_disconnected]) - } - - pub fn get_allow_lan(&mut self) -> Result<bool> { - self.call("get_allow_lan", &NO_ARGS) - } - - pub fn set_auto_connect(&mut self, auto_connect: bool) -> Result<()> { - self.call("set_auto_connect", &[auto_connect]) - } - - pub fn get_auto_connect(&mut self) -> Result<bool> { - self.call("get_auto_connect", &NO_ARGS) - } - - pub fn get_current_location(&mut self) -> Result<Option<GeoIpLocation>> { - self.call("get_current_location", &NO_ARGS) - } - - pub fn get_current_version(&mut self) -> Result<String> { - self.call("get_current_version", &NO_ARGS) - } - - pub fn get_relay_locations(&mut self) -> Result<RelayList> { - self.call("get_relay_locations", &NO_ARGS) - } - - pub fn update_relay_locations(&mut self) -> Result<()> { - self.call("update_relay_locations", &NO_ARGS) - } - - pub fn get_relay_settings(&mut self) -> Result<RelaySettings> { - self.call("get_relay_settings", &NO_ARGS) - } - - pub fn get_state(&mut self) -> Result<TunnelState> { - self.call("get_state", &NO_ARGS) - } - - pub fn get_tunnel_options(&mut self) -> Result<TunnelOptions> { - self.call("get_tunnel_options", &NO_ARGS) - } - - pub fn get_settings(&mut self) -> Result<Settings> { - self.call("get_settings", &NO_ARGS) - } - - pub fn generate_wireguard_key(&mut self) -> Result<wireguard::KeygenEvent> { - self.call("generate_wireguard_key", &NO_ARGS) - } - - pub fn get_wireguard_key(&mut self) -> Result<Option<wireguard::PublicKey>> { - self.call("get_wireguard_key", &NO_ARGS) - } - - pub fn verify_wireguard_key(&mut self) -> Result<bool> { - self.call("verify_wireguard_key", &NO_ARGS) - } - - pub fn get_version_info(&mut self) -> Result<AppVersionInfo> { - self.call("get_version_info", &NO_ARGS) - } - - pub fn set_account(&mut self, account: Option<AccountToken>) -> Result<()> { - self.call("set_account", &[account]) - } - - pub fn clear_account_history(&mut self) -> Result<()> { - self.call("clear_account_history", &NO_ARGS) - } - - pub fn set_enable_ipv6(&mut self, enabled: bool) -> Result<()> { - self.call("set_enable_ipv6", &[enabled]) - } - - pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<()> { - self.call("set_wireguard_mtu", &[mtu]) - } - - pub fn set_wireguard_rotation_interval(&mut self, interval: Option<u32>) -> Result<()> { - self.call("set_wireguard_rotation_interval", &[interval]) - } - - pub fn set_openvpn_mssfix(&mut self, mssfix: Option<u16>) -> Result<()> { - self.call("set_openvpn_mssfix", &[mssfix]) - } - - pub fn set_bridge_settings(&mut self, settings: BridgeSettings) -> Result<()> { - self.call("set_bridge_settings", &[settings]) - } - - pub fn set_bridge_state(&mut self, state: BridgeState) -> Result<()> { - self.call("set_bridge_state", &[state]) - } - - pub fn shutdown(&mut self) -> Result<()> { - self.call("shutdown", &NO_ARGS) - } - - pub fn prepare_restart(&mut self) -> Result<()> { - self.call("prepare_restart", &NO_ARGS) - } - - pub fn factory_reset(&mut self) -> Result<()> { - self.call("factory_reset", &NO_ARGS) - } - - pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<()> { - self.call("update_relay_settings", &[update]) - } - - pub fn get_split_tunnel_processes(&mut self) -> Result<Vec<i32>> { - self.call("get_split_tunnel_processes", &NO_ARGS) - } - - pub fn add_split_tunnel_process(&mut self, pid: i32) -> Result<()> { - self.call("add_split_tunnel_process", &[pid]) - } - - pub fn remove_split_tunnel_process(&mut self, pid: i32) -> Result<()> { - self.call("remove_split_tunnel_process", &[pid]) - } - - pub fn clear_split_tunnel_processes(&mut self) -> Result<()> { - self.call("clear_split_tunnel_processes", &NO_ARGS) - } - - - pub fn call<A, O>(&mut self, method: &'static str, args: &A) -> Result<O> - where - A: Serialize + Send + 'static, - O: for<'de> Deserialize<'de> + Send + 'static, - { - self.rpc_client.call_method(method, args).wait() - } - - pub fn daemon_event_subscribe( - &mut self, - ) -> impl Future< - Item = jsonrpc_client_pubsub::Subscription<DaemonEvent>, - Error = jsonrpc_client_pubsub::Error, - > { - self.subscriber.subscribe( - "daemon_event_subscribe".to_string(), - "daemon_event_unsubscribe".to_string(), - "daemon_event".to_string(), - 0, - &NO_ARGS, - ) - } -} diff --git a/mullvad-setup/Cargo.toml b/mullvad-setup/Cargo.toml index 3d113870d4..dd2e87ad71 100644 --- a/mullvad-setup/Cargo.toml +++ b/mullvad-setup/Cargo.toml @@ -15,12 +15,20 @@ path = "src/main.rs" clap = "2.32" env_logger = "0.7" err-derive = "0.2.1" +prost = "0.6" +prost-types = "0.6" +tonic = "0.2" +tower = "0.3" +tokio = { version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream"] } +parity-tokio-ipc = "0.7" -mullvad-ipc-client = { path = "../mullvad-ipc-client" } mullvad-paths = { path = "../mullvad-paths" } talpid-core = { path = "../talpid-core" } talpid-types = { path = "../talpid-types" } +[build-dependencies] +tonic-build = { version = "0.2", default-features = false, features = ["transport", "prost"] } + [target.'cfg(windows)'.build-dependencies] winres = "0.1" winapi = "0.3" diff --git a/mullvad-setup/build.rs b/mullvad-setup/build.rs index 8cdd992c7c..61b805cbcb 100644 --- a/mullvad-setup/build.rs +++ b/mullvad-setup/build.rs @@ -1,6 +1,8 @@ use std::{env, fs, path::PathBuf}; fn main() { + tonic_build::compile_protos("../mullvad-daemon/proto/management_interface.proto").unwrap(); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let product_version = env!("CARGO_PKG_VERSION").replacen(".0", "", 1); fs::write(out_dir.join("product-version.txt"), &product_version).unwrap(); diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs index f8fda169b5..6f80be9df8 100644 --- a/mullvad-setup/src/main.rs +++ b/mullvad-setup/src/main.rs @@ -1,18 +1,28 @@ use clap::{crate_authors, crate_description, crate_name, SubCommand}; -use mullvad_ipc_client::{new_standalone_ipc_client, DaemonRpcClient}; -use std::{io, process}; +use parity_tokio_ipc::Endpoint as IpcEndpoint; +use std::process; use talpid_core::firewall::{self, Firewall, FirewallArguments}; use talpid_types::ErrorExt; +use tonic::{ + self, + transport::{Endpoint, Uri}, +}; +use tower::service_fn; + +mod proto { + tonic::include_proto!("mullvad_daemon.management_interface"); +} +use proto::management_service_client::ManagementServiceClient; pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt")); #[derive(err_derive::Error, Debug)] pub enum Error { #[error(display = "Failed to connect to daemon")] - DaemonConnect(#[error(source)] io::Error), + DaemonConnect(#[error(source)] tonic::transport::Error), #[error(display = "RPC call failed")] - DaemonRpcError(#[error(source)] mullvad_ipc_client::Error), + DaemonRpcError(#[error(source)] tonic::Status), #[error(display = "This command cannot be run if the daemon is active")] DaemonIsRunning, @@ -21,7 +31,8 @@ pub enum Error { FirewallError(#[error(source)] firewall::Error), } -fn main() { +#[tokio::main] +async fn main() { env_logger::init(); let subcommands = vec![ @@ -44,8 +55,8 @@ fn main() { let matches = app.get_matches(); let result = match matches.subcommand_name().expect("Subcommand has no name") { - "prepare-restart" => prepare_restart(), - "reset-firewall" => reset_firewall(), + "prepare-restart" => prepare_restart().await, + "reset-firewall" => reset_firewall().await, _ => unreachable!("No command matched"), }; @@ -55,14 +66,17 @@ fn main() { } } -fn prepare_restart() -> Result<(), Error> { - let mut rpc = new_rpc_client()?; - rpc.prepare_restart().map_err(Error::DaemonRpcError) +async fn prepare_restart() -> Result<(), Error> { + let mut rpc = new_grpc_client().await?; + rpc.prepare_restart(()) + .await + .map_err(Error::DaemonRpcError)?; + Ok(()) } -fn reset_firewall() -> Result<(), Error> { +async fn reset_firewall() -> Result<(), Error> { // Ensure that the daemon isn't running - if let Ok(_) = new_rpc_client() { + if let Ok(_) = new_grpc_client().await { return Err(Error::DaemonIsRunning); } @@ -75,6 +89,17 @@ fn reset_firewall() -> Result<(), Error> { firewall.reset_policy().map_err(Error::FirewallError) } -fn new_rpc_client() -> Result<DaemonRpcClient, Error> { - new_standalone_ipc_client(&mullvad_paths::get_rpc_socket_path()).map_err(Error::DaemonConnect) +pub async fn new_grpc_client() -> Result<ManagementServiceClient<tonic::transport::Channel>, Error> +{ + let ipc_path = mullvad_paths::get_rpc_socket_path(); + + // The URI will be ignored + let channel = Endpoint::from_static("lttp://[::]:50051") + .connect_with_connector(service_fn(move |_: Uri| { + IpcEndpoint::connect(ipc_path.clone()) + })) + .await + .map_err(Error::DaemonConnect)?; + + Ok(ManagementServiceClient::new(channel)) } diff --git a/mullvad-tests/Cargo.toml b/mullvad-tests/Cargo.toml index 152343c1e9..f840c207eb 100644 --- a/mullvad-tests/Cargo.toml +++ b/mullvad-tests/Cargo.toml @@ -12,13 +12,11 @@ integration-tests = [] [dependencies] duct = "0.13" -mullvad-ipc-client = { path = "../mullvad-ipc-client" } mullvad-paths = { path = "../mullvad-paths" } mullvad-rpc = { path = "../mullvad-rpc" } mullvad-types = { path = "../mullvad-types" } notify = "4.0" openvpn-plugin = { git = "https://github.com/mullvad/openvpn-plugin-rs", branch = "auth-failed-event", features = ["serde"] } -talpid-ipc = { path = "../talpid-ipc" } talpid-types = { path = "../talpid-types" } tempfile = "3.0" jsonrpc-client-core = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } diff --git a/mullvad-types/Cargo.toml b/mullvad-types/Cargo.toml index d4f7871e52..ca3dab3c86 100644 --- a/mullvad-types/Cargo.toml +++ b/mullvad-types/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4" regex = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +tonic = "0.2" talpid-types = { path = "../talpid-types" } mullvad-paths = { path = "../mullvad-paths" } diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs index a22b574856..a2ec85ebba 100644 --- a/mullvad-types/src/account.rs +++ b/mullvad-types/src/account.rs @@ -33,13 +33,13 @@ pub struct VoucherSubmission { /// was rejected by the `/v1/submit-voucher` RPC. #[derive(err_derive::Error, Debug)] pub enum VoucherError { - /// Error code -400 + /// Error code `tonic::Code::NotFound` #[error(display = "Bad voucher code")] BadVoucher, - /// Error code -401 + /// Error code `tonic::Code::ResourceExhausted` #[error(display = "Voucher already used")] VoucherAlreadyUsed, - /// Error code -100 + /// Error code `tonic::Code::Internal` #[error(display = "Server internal error")] InternalError, #[error(display = "Unknown error, {}", _0)] @@ -50,9 +50,9 @@ impl VoucherError { /// Create error from RPC error code. pub fn from_rpc_error_code(err_code: i64) -> VoucherError { match err_code { - -400 => VoucherError::BadVoucher, - -401 => VoucherError::VoucherAlreadyUsed, - -100 => VoucherError::InternalError, + x if x == tonic::Code::NotFound as i64 => VoucherError::BadVoucher, + x if x == tonic::Code::ResourceExhausted as i64 => VoucherError::VoucherAlreadyUsed, + x if x == tonic::Code::Internal as i64 => VoucherError::InternalError, err => VoucherError::UnknownError(err), } } diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs index 12c293c22d..b3cae97347 100644 --- a/mullvad-types/src/custom_tunnel.rs +++ b/mullvad-types/src/custom_tunnel.rs @@ -25,8 +25,8 @@ pub enum Error { #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] #[cfg_attr(target_os = "android", jnix(skip_all))] pub struct CustomTunnelEndpoint { - host: String, - config: ConnectionConfig, + pub host: String, + pub config: ConnectionConfig, } impl CustomTunnelEndpoint { diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index d77206fc31..e93ab2f606 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -13,23 +13,3 @@ pub mod wireguard; mod custom_tunnel; pub use crate::custom_tunnel::*; - -/// An event sent out from the daemon to frontends. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum DaemonEvent { - /// The daemon transitioned into a new state. - TunnelState(states::TunnelState), - - /// The daemon settings changed. - Settings(settings::Settings), - - /// The daemon got an updated relay list. - RelayList(relay_list::RelayList), - - /// The daemon got update version info. - AppVersionInfo(version::AppVersionInfo), - - /// Key event - WireguardKey(wireguard::KeygenEvent), -} diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index b983c30657..6eba0fbd95 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -28,6 +28,13 @@ pub enum Constraint<T: fmt::Debug + Clone + Eq + PartialEq> { } impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> { + pub fn unwrap(self) -> T { + match self { + Constraint::Any => panic!("called `Constraint::unwrap()` on an `Any` value"), + Constraint::Only(value) => value, + } + } + pub fn unwrap_or(self, other: T) -> T { match self { Constraint::Any => other, @@ -43,7 +50,8 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> { } pub fn map<U: fmt::Debug + Clone + Eq + PartialEq, F: FnOnce(T) -> U>( - self, f: F + self, + f: F, ) -> Constraint<U> { match self { Constraint::Any => Constraint::Any, @@ -57,6 +65,13 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> { Constraint::Only(_value) => false, } } + + pub fn as_ref(&self) -> Constraint<&T> { + match self { + Constraint::Any => Constraint::Any, + Constraint::Only(ref value) => Constraint::Only(value), + } + } } impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> { diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 07ffb7263a..6b40af9465 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -16,8 +16,6 @@ futures01 = { package = "futures", version = "0.1" } futures = { package = "futures", version = "0.3", features = [ "compat" ]} hex = "0.4" ipnetwork = "0.16" -jsonrpc-core = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-macros = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } lazy_static = "1.0" libc = "0.2" log = "0.4" @@ -26,7 +24,6 @@ os_pipe = "0.8" parking_lot = "0.9" regex = "1.1.0" shell-escape = "0.1" -talpid-ipc = { path = "../talpid-ipc" } talpid-types = { path = "../talpid-types" } tokio-core = "0.1" tokio-executor = "0.1" diff --git a/talpid-ipc/Cargo.toml b/talpid-ipc/Cargo.toml deleted file mode 100644 index 0a15db0f48..0000000000 --- a/talpid-ipc/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "talpid-ipc" -version = "0.1.0" -authors = ["Mullvad VPN"] -description = "IPC client and server for talpid" -license = "GPL-3.0" -edition = "2018" -publish = false - -[dependencies] -err-derive = "0.2.1" -serde = "1.0" -serde_json = "1.0" -log = "0.4" -jsonrpc-core = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-pubsub = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -jsonrpc-ipc-server = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -tokio = "0.1" -futures = "0.1" - -jsonrpc-client-core = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } -jsonrpc-client-ipc = { git = "https://github.com/mullvad/jsonrpc-client-rs", rev = "68aac55b" } - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["accctrl", "aclapi", "securitybaseapi", "winbase", "winerror", "winnt"] } - -[dev-dependencies] -assert_matches = "1.0" -env_logger = "0.7" -jsonrpc-macros = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" } -uuid = { version = "0.7", features = ["v4"] } -futures = "0.1.23" -tokio = "0.1" diff --git a/talpid-ipc/src/lib.rs b/talpid-ipc/src/lib.rs deleted file mode 100644 index 2ab6d506c3..0000000000 --- a/talpid-ipc/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![deny(rust_2018_idioms)] - -use futures::Future; -use std::{io, thread}; - -use jsonrpc_core::{MetaIoHandler, Metadata}; -use jsonrpc_ipc_server::{MetaExtractor, NoopExtractor, SecurityAttributes, Server, ServerBuilder}; - -use std::fmt; - -#[cfg(windows)] -mod win; - -/// An Id created by the Ipc server that the client can use to connect to it -pub type IpcServerId = String; - -#[derive(err_derive::Error, Debug)] -#[error(no_from)] -pub enum Error { - #[error(display = "Unable to start IPC server")] - StartServerError(#[error(source)] io::Error), - - #[error(display = "IPC server thread panicked and never returned a start result")] - ServerThreadPanicError, - - #[error(display = "Error in IPC server")] - IpcServerError(#[error(source)] io::Error), - - #[error(display = "Unable to set permissions for IPC endpoint")] - PermissionsError(#[error(source)] io::Error), -} - - -pub struct IpcServer { - path: String, - server: Server, -} - -impl IpcServer { - pub fn start<M: Metadata + Default>( - handler: MetaIoHandler<M>, - path: &str, - ) -> Result<Self, Error> { - Self::start_with_metadata(handler, NoopExtractor, path) - } - - pub fn start_with_metadata<M, E>( - handler: MetaIoHandler<M>, - meta_extractor: E, - path: &str, - ) -> Result<Self, Error> - where - M: Metadata + Default, - E: MetaExtractor<M>, - { - let security_attributes = - SecurityAttributes::allow_everyone_create().map_err(Error::PermissionsError)?; - let server = ServerBuilder::with_meta_extractor(handler, meta_extractor) - .set_security_attributes(security_attributes) - .start(path) - .map_err(Error::StartServerError) - .and_then(|(fut, start, server)| { - thread::spawn(move || tokio::run(fut)); - if let Some(error) = start - .wait() - .map_err(|_cancelled| Error::ServerThreadPanicError)? - { - return Err(Error::IpcServerError(error)); - } - Ok(server) - }) - .map(|server| IpcServer { - path: path.to_owned(), - server, - })?; - - #[cfg(unix)] - { - use std::{fs, os::unix::fs::PermissionsExt}; - fs::set_permissions(&path, PermissionsExt::from_mode(0o766)) - .map_err(Error::PermissionsError)?; - } - #[cfg(windows)] - win::deny_network_access(path).map_err(Error::PermissionsError)?; - Ok(server) - } - - /// Returns the uds/named pipe path this `IpcServer` is listening on. - pub fn path(&self) -> &str { - &self.path - } - - /// Creates a handle bound to this `IpcServer` that can be used to shut it down. - pub fn close_handle(&self) -> CloseHandle { - CloseHandle(self.server.close_handle()) - } - - /// Consumes the server and waits for it to finish. Get a `CloseHandle` before calling this - /// if you want to be able to shut the server down. - pub fn wait(self) { - self.server.wait(); - } -} - -// FIXME: This custom impl is because `Server` does not implement `Debug` yet: -// https://github.com/paritytech/jsonrpc/pull/195 -impl fmt::Debug for IpcServer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("IpcServer") - .field("path", &self.path) - .finish() - } -} - -#[derive(Clone)] -pub struct CloseHandle(jsonrpc_ipc_server::CloseHandle); - -impl CloseHandle { - pub fn close(self) { - self.0.close(); - } -} diff --git a/talpid-ipc/tests/ipc-client-server.rs b/talpid-ipc/tests/ipc-client-server.rs deleted file mode 100644 index 6be8349487..0000000000 --- a/talpid-ipc/tests/ipc-client-server.rs +++ /dev/null @@ -1,85 +0,0 @@ -// TODO fix these tests on Windows -#![cfg(not(windows))] - -use assert_matches::assert_matches; -use futures::{sync::oneshot, Future}; - -use jsonrpc_client_core::{Error as ClientError, Transport}; -use jsonrpc_core::{Error, IoHandler}; -use jsonrpc_macros::build_rpc_trait; -use std::{ - sync::{mpsc, Mutex}, - time::Duration, -}; - -build_rpc_trait! { - pub trait TestApi { - #[rpc(name = "foo")] - fn foo(&self, i64) -> Result<(), Error>; - } -} - -struct ApiImpl { - tx: Mutex<mpsc::Sender<i64>>, -} - -impl TestApi for ApiImpl { - fn foo(&self, i: i64) -> Result<(), Error> { - self.tx.lock().unwrap().send(i).unwrap(); - Ok(()) - } -} - -#[test] -fn can_call_rpcs_on_server() { - env_logger::init(); - - let (server, rx) = create_server(); - let server_path = server.path().to_owned(); - let client = create_client(server_path); - - let _result: () = client.call_method("foo", &[97]).wait().unwrap(); - assert_eq!(Ok(97), rx.recv_timeout(Duration::from_millis(500))); - - let result: Result<(), ClientError> = client.call_method("invalid_method", &[0]).wait(); - assert_matches!(result, Err(_)); - server.close_handle().close(); -} - -#[test] -#[should_panic] -fn ipc_client_invalid_url() { - let _client = create_client("INVALID ID".to_owned()); -} - -fn create_server() -> (talpid_ipc::IpcServer, mpsc::Receiver<i64>) { - let (tx, rx) = mpsc::channel(); - let rpc = ApiImpl { tx: Mutex::new(tx) }; - let mut io = IoHandler::new(); - io.extend_with(rpc.to_delegate()); - - let uuid = uuid::Uuid::new_v4().to_string(); - let ipc_path = if cfg!(windows) { - format!(r"\\.\pipe\ipc-test-{}", uuid) - } else { - format!("/tmp/ipc-test-{}", uuid) - }; - let server = talpid_ipc::IpcServer::start(io.into(), &ipc_path).unwrap(); - (server, rx) -} - -fn create_client(ipc_path: String) -> jsonrpc_client_core::ClientHandle { - use std::thread; - let (tx, rx) = oneshot::channel(); - - thread::spawn(move || { - let (client, client_handle) = - jsonrpc_client_ipc::IpcTransport::new(&ipc_path, &tokio::reactor::Handle::default()) - .expect("failed to construct a transport") - .into_client(); - tx.send(client_handle).unwrap(); - client.wait().unwrap(); - }); - - rx.wait().expect("Failed to construct a valid client") -} |
