summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-08-20 15:04:27 +0200
committerDavid Lönnhager <david.l@mullvad.net>2020-08-20 15:04:27 +0200
commit3e277b10f220d022726430cbed20fab4d6b1957d (patch)
treeb8d26480c9e4bae70eee19c9484e5e7ef85c882c
parent9a67c39d37c042df4b9381bd101a369efd516880 (diff)
parent857a4101f98b564caba1455aabf988eac8adc55e (diff)
downloadmullvadvpn-3e277b10f220d022726430cbed20fab4d6b1957d.tar.xz
mullvadvpn-3e277b10f220d022726430cbed20fab4d6b1957d.zip
Merge branch 'tonic-management-interface'
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock397
-rw-r--r--Cargo.toml5
-rw-r--r--gui/gulpfile.js7
-rw-r--r--gui/package-lock.json1210
-rw-r--r--gui/package.json6
-rwxr-xr-xgui/scripts/build-proto.sh33
-rw-r--r--gui/src/main/daemon-rpc.ts1543
-rw-r--r--gui/src/main/index.ts49
-rw-r--r--gui/src/renderer/components/RedeemVoucher.tsx2
-rw-r--r--gui/src/shared/daemon-rpc-types.ts18
-rw-r--r--gui/tasks/scripts.js9
-rw-r--r--mullvad-cli/Cargo.toml6
-rw-r--r--mullvad-cli/src/cmds/account.rs93
-rw-r--r--mullvad-cli/src/cmds/auto_connect.rs19
-rw-r--r--mullvad-cli/src/cmds/beta_program.rs11
-rw-r--r--mullvad-cli/src/cmds/block_when_disconnected.rs24
-rw-r--r--mullvad-cli/src/cmds/bridge.rs216
-rw-r--r--mullvad-cli/src/cmds/connect.rs7
-rw-r--r--mullvad-cli/src/cmds/disconnect.rs7
-rw-r--r--mullvad-cli/src/cmds/lan.rs19
-rw-r--r--mullvad-cli/src/cmds/reconnect.rs7
-rw-r--r--mullvad-cli/src/cmds/relay.rs393
-rw-r--r--mullvad-cli/src/cmds/reset.rs7
-rw-r--r--mullvad-cli/src/cmds/split_tunnel/linux.rs30
-rw-r--r--mullvad-cli/src/cmds/status.rs250
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs214
-rw-r--r--mullvad-cli/src/cmds/version.rs18
-rw-r--r--mullvad-cli/src/format.rs20
-rw-r--r--mullvad-cli/src/location.rs41
-rw-r--r--mullvad-cli/src/main.rs30
-rw-r--r--mullvad-daemon/Cargo.toml10
-rw-r--r--mullvad-daemon/src/logging.rs5
-rw-r--r--mullvad-daemon/src/main.rs11
-rw-r--r--mullvad-daemon/src/management_interface.rs2145
-rw-r--r--mullvad-daemon/src/rpc_uniqueness_check.rs7
-rw-r--r--mullvad-ipc-client/Cargo.toml27
-rw-r--r--mullvad-ipc-client/src/lib.rs281
-rw-r--r--mullvad-management-interface/Cargo.toml24
-rw-r--r--mullvad-management-interface/build.rs5
-rw-r--r--mullvad-management-interface/proto/management_interface.proto471
-rw-r--r--mullvad-management-interface/src/lib.rs123
-rw-r--r--mullvad-management-interface/src/windows_permissions.rs (renamed from talpid-ipc/src/win.rs)0
-rw-r--r--mullvad-setup/Cargo.toml5
-rw-r--r--mullvad-setup/src/main.rs34
-rw-r--r--mullvad-tests/Cargo.toml2
-rw-r--r--mullvad-types/src/account.rs29
-rw-r--r--mullvad-types/src/custom_tunnel.rs4
-rw-r--r--mullvad-types/src/lib.rs20
-rw-r--r--mullvad-types/src/relay_constraints.rs40
-rw-r--r--talpid-core/Cargo.toml3
-rw-r--r--talpid-core/build.rs4
-rw-r--r--talpid-ipc/Cargo.toml33
-rw-r--r--talpid-ipc/src/lib.rs122
-rw-r--r--talpid-ipc/tests/ipc-client-server.rs85
-rw-r--r--talpid-openvpn-plugin/build.rs4
-rw-r--r--talpid-types/src/tunnel.rs4
58 files changed, 5403 insertions, 2788 deletions
diff --git a/.gitignore b/.gitignore
index bb585e09e2..9db2f0d726 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
/gui/scripts/ne_50m_admin_0_countries/
/gui/scripts/ne_50m_admin_1_states_provinces_lines/
/gui/scripts/out/
+/gui/src/main/management_interface/
/build/
/dist
.DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dab7696050..2f671367ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@ Line wrap the file at 100 chars. Th
- Display the original block reason in the non-blocking error state, and why applying the blocking
policy failed.
- Don't show account time expired notification for newly created accounts.
+- Use gRPC for communication between frontends and the backend instead of JSON-RPC.
#### Android
- Show a system notification when the account time will soon run out.
diff --git a/Cargo.lock b/Cargo.lock
index 1c7a581d0d..1f78c3f373 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"
@@ -1366,13 +1188,14 @@ dependencies = [
"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-management-interface 0.1.0",
"mullvad-paths 0.1.0",
"mullvad-types 0.1.0",
"natord 1.0.9 (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)",
"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,16 +1216,11 @@ 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-management-interface 0.1.0",
"mullvad-paths 0.1.0",
"mullvad-rpc 0.1.0",
"mullvad-types 0.1.0",
@@ -1414,12 +1232,12 @@ dependencies = [
"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)",
+ "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 +1255,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 = [
@@ -1481,6 +1279,24 @@ dependencies = [
]
[[package]]
+name = "mullvad-management-interface"
+version = "0.1.0"
+dependencies = [
+ "err-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mullvad-paths 0.1.0",
+ "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)",
+ "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)",
+ "triggered 0.1.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 = "mullvad-paths"
version = "0.1.0"
dependencies = [
@@ -1544,44 +1360,16 @@ 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-management-interface 0.1.0",
"mullvad-paths 0.1.0",
"talpid-core 0.1.0",
"talpid-types 0.1.0",
+ "tokio 0.2.21 (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]]
-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",
- "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)",
-]
-
-[[package]]
name = "mullvad-types"
version = "0.1.0"
dependencies = [
@@ -1831,31 +1619,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 +1635,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 +1645,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 +1919,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 +2264,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 +2421,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 +2565,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 +2591,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 +2610,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 +2838,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 +3013,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 +3613,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 +3626,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 +3695,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 +3718,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 +3763,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 +3796,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 +3835,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 +3855,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 +3886,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..f87705cf22 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,16 +5,15 @@ 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",
+ "mullvad-management-interface",
]
exclude = ["dist-assets/binaries/shadowsocks-rust"]
diff --git a/gui/gulpfile.js b/gui/gulpfile.js
index f203fc16d0..6a7226c1aa 100644
--- a/gui/gulpfile.js
+++ b/gui/gulpfile.js
@@ -1,4 +1,4 @@
-const { task, series } = require('gulp');
+const { task, series, parallel } = require('gulp');
const rimraf = require('rimraf');
const scripts = require('./tasks/scripts');
@@ -9,8 +9,9 @@ const dist = require('./tasks/distribution');
task('clean', function (done) {
rimraf('./build', done);
});
-task('build', series('clean', assets.copyAll, scripts.build));
-task('develop', series('clean', watch.start));
+task('build-proto', scripts.buildProto);
+task('build', series('clean', parallel(assets.copyAll, scripts.buildProto), scripts.build));
+task('develop', series('clean', scripts.buildProto, watch.start));
task('pack-win', series('build', dist.packWin));
task('pack-linux', series('build', dist.packLinux));
task('pack-mac', series('build', dist.packMac));
diff --git a/gui/package-lock.json b/gui/package-lock.json
index c3b781bf69..57840f46f4 100644
--- a/gui/package-lock.json
+++ b/gui/package-lock.json
@@ -397,6 +397,21 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
+ "@grpc/grpc-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.2.tgz",
+ "integrity": "sha512-k2u86Bkm/3xrjUaSWeIyzXScBt/cC8uE7BznR0cpueQi11R33W6qfJdMrkrsmSHirp5likR55JSXUrcWG6ybHA==",
+ "requires": {
+ "semver": "^6.2.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ }
+ }
+ },
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -559,6 +574,12 @@
"@types/node": "*"
}
},
+ "@types/google-protobuf": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.2.tgz",
+ "integrity": "sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ==",
+ "dev": true
+ },
"@types/history": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz",
@@ -872,8 +893,7 @@
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "optional": true
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"accepts": {
"version": "1.3.7",
@@ -958,6 +978,105 @@
}
}
},
+ "ansi-bgblack": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz",
+ "integrity": "sha1-poulAHiHcBtqr74/oNrf36juPKI=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgblue": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgblue/-/ansi-bgblue-0.1.1.tgz",
+ "integrity": "sha1-Z73ATtybm1J4lp2hlt6j11yMNhM=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgcyan": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgcyan/-/ansi-bgcyan-0.1.1.tgz",
+ "integrity": "sha1-WEiUJWAL3p9VBwaN2Wnr/bUP52g=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bggreen": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bggreen/-/ansi-bggreen-0.1.1.tgz",
+ "integrity": "sha1-TjGRJIUplD9DIelr8THRwTgWr0k=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgmagenta": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgmagenta/-/ansi-bgmagenta-0.1.1.tgz",
+ "integrity": "sha1-myhDLAduqpmUGGcqPvvhk5HCx6E=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgred": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgred/-/ansi-bgred-0.1.1.tgz",
+ "integrity": "sha1-p2+Sg4OCukMpCmwXeEJPmE1vEEE=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgwhite": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgwhite/-/ansi-bgwhite-0.1.1.tgz",
+ "integrity": "sha1-ZQRlE3elim7OzQMxmU5IAljhG6g=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bgyellow": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bgyellow/-/ansi-bgyellow-0.1.1.tgz",
+ "integrity": "sha1-w/4usIzUdmSAKeaHTRWgs49h1E8=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-black": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-black/-/ansi-black-0.1.1.tgz",
+ "integrity": "sha1-9hheiJNgslRaHsUMC/Bj/EMDJFM=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-blue": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-blue/-/ansi-blue-0.1.1.tgz",
+ "integrity": "sha1-FbgEmQ6S/JyoxUds6PaZd3wh7b8=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-bold": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-bold/-/ansi-bold-0.1.1.tgz",
+ "integrity": "sha1-PmOVCvWswq4uZw5vZ96xFdGl9QU=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-colors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
@@ -967,6 +1086,24 @@
"ansi-wrap": "^0.1.0"
}
},
+ "ansi-cyan": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz",
+ "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-dim": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-dim/-/ansi-dim-0.1.1.tgz",
+ "integrity": "sha1-QN5MYDqoCG2Oeoa4/5mNXDbu/Ww=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-escapes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
@@ -993,23 +1130,131 @@
"ansi-wrap": "0.1.0"
}
},
+ "ansi-green": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz",
+ "integrity": "sha1-il2al55FjVfEDjNYCzc5C44Q0Pc=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-grey": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-grey/-/ansi-grey-0.1.1.tgz",
+ "integrity": "sha1-WdmLasK6GfilF5jphT+6eDOaM8E=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-hidden": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-hidden/-/ansi-hidden-0.1.1.tgz",
+ "integrity": "sha1-7WpMSY0rt8uyidvyqNHcyFZ/rg8=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-inverse": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-inverse/-/ansi-inverse-0.1.1.tgz",
+ "integrity": "sha1-tq9Fgm/oJr+1KKbHmIV5Q1XM0mk=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-italic": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-italic/-/ansi-italic-0.1.1.tgz",
+ "integrity": "sha1-EEdDRj9iXBQqA2c5z4XtpoiYbyM=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-magenta": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-magenta/-/ansi-magenta-0.1.1.tgz",
+ "integrity": "sha1-BjtboW+z8j4c/aKwfAqJ3hHkMK4=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-red": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
+ "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
+ "ansi-reset": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-reset/-/ansi-reset-0.1.1.tgz",
+ "integrity": "sha1-5+cSksPH3c1NYu9KbHwFmAkRw7c=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-strikethrough": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-strikethrough/-/ansi-strikethrough-0.1.1.tgz",
+ "integrity": "sha1-2Eh3FAss/wfRyT685pkE9oiF5Wg=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
+ "ansi-underline": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-underline/-/ansi-underline-0.1.1.tgz",
+ "integrity": "sha1-38kg9Ml7WXfqFi34/7mIMIqqcaQ=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-white": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-white/-/ansi-white-0.1.1.tgz",
+ "integrity": "sha1-nHe3wZPF7pkuYBHTbsTJIbRXiUQ=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-wrap": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
"integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
"dev": true
},
+ "ansi-yellow": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-yellow/-/ansi-yellow-0.1.1.tgz",
+ "integrity": "sha1-y5NW8vRscy8OMZnmEClVp32oPB0=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@@ -1179,8 +1424,7 @@
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "optional": true
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"archy": {
"version": "1.0.0",
@@ -1192,7 +1436,6 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@@ -1585,6 +1828,15 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
+ "autolinker": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz",
+ "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=",
+ "dev": true,
+ "requires": {
+ "gulp-header": "^1.7.1"
+ }
+ },
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
@@ -2503,8 +2755,7 @@
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "optional": true
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"chromium-pickle-js": {
"version": "0.2.0",
@@ -2696,6 +2947,23 @@
"typedarray": "^0.0.6"
}
},
+ "concat-with-sourcemaps": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
+ "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
+ "dev": true,
+ "requires": {
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
"config-chain": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
@@ -2762,8 +3030,7 @@
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "optional": true
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"content-type": {
"version": "1.0.4",
@@ -2813,6 +3080,38 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
+ "create-frame": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/create-frame/-/create-frame-1.0.0.tgz",
+ "integrity": "sha1-i5XyaR4ySbYIBEPjPQutn49pdao=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "isobject": "^3.0.0",
+ "lazy-cache": "^2.0.2"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
"cross-env": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz",
@@ -2932,6 +3231,26 @@
"resolve": "^1.1.10"
}
},
+ "date.js": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz",
+ "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==",
+ "dev": true,
+ "requires": {
+ "debug": "~3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -3072,8 +3391,7 @@
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "optional": true
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": {
"version": "1.1.2",
@@ -3096,8 +3414,7 @@
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
- "optional": true
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"detect-node": {
"version": "2.0.4",
@@ -4000,6 +4317,12 @@
"has-binary2": "~1.0.2"
}
},
+ "ent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+ "dev": true
+ },
"entities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
@@ -4089,6 +4412,12 @@
"is-arrayish": "^0.2.1"
}
},
+ "error-symbol": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/error-symbol/-/error-symbol-0.1.0.tgz",
+ "integrity": "sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y=",
+ "dev": true
+ },
"es-abstract": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
@@ -4853,6 +5182,23 @@
}
}
},
+ "falsey": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/falsey/-/falsey-0.3.2.tgz",
+ "integrity": "sha512-lxEuefF5MBIVDmE6XeqCdM4BWk1+vYmGZtkbKZ/VFcg6uBBw6fXNEbWmxCjDdQlFc9hy450nkiWwM3VAW6G1qg==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^5.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
"fancy-log": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
@@ -5108,6 +5454,12 @@
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
"dev": true
},
+ "fs-exists-sync": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
+ "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
+ "dev": true
+ },
"fs-extra": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
@@ -5123,7 +5475,6 @@
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
- "optional": true,
"requires": {
"minipass": "^2.6.0"
}
@@ -5739,7 +6090,6 @@
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@@ -5763,6 +6113,42 @@
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
"dev": true
},
+ "get-object": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/get-object/-/get-object-0.2.0.tgz",
+ "integrity": "sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw=",
+ "dev": true,
+ "requires": {
+ "is-number": "^2.0.2",
+ "isobject": "^0.2.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "isobject": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-0.2.0.tgz",
+ "integrity": "sha1-o0MhkvObkQtfAsyYlIeDbscKqF4=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
"get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -5993,6 +6379,11 @@
"sparkles": "^1.0.0"
}
},
+ "google-protobuf": {
+ "version": "4.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.0-rc.2.tgz",
+ "integrity": "sha512-KANe0ePi8Uk0m17xvjI1bXrE7LRflSuNczkAlHlOEeqXnSBy96tDS5beKwKOnNr0xYmlbC9XiQsj8fOLLyDp2Q=="
+ },
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -6035,6 +6426,89 @@
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
+ "grpc-tools": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.9.1.tgz",
+ "integrity": "sha512-t2JFMPLjxcgwVSJwFEauFaoEiO56kijxSwehQDgZNR/hrStJCH0pHGsjqJNuCOvmI9Z31pYOfgj4zeInTQWh5A==",
+ "dev": true,
+ "requires": {
+ "node-pre-gyp": "^0.15.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "needle": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
+ "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.2.6",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz",
+ "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==",
+ "dev": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.3",
+ "needle": "^2.5.0",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4.4.2"
+ }
+ }
+ }
+ },
+ "grpc_tools_node_protoc_ts": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-4.1.0.tgz",
+ "integrity": "sha512-+10Q2LhTalc41e+hCfP7lCLYfyDlAdntEKTn43QHE3CPafNHRu5KgqAHkDg4cUiDEJFUcKhddaYUPI5uR7c2AA==",
+ "dev": true,
+ "requires": {
+ "google-protobuf": "3.5.0",
+ "handlebars": "4.7.4",
+ "handlebars-helpers": "0.10.0"
+ },
+ "dependencies": {
+ "google-protobuf": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.5.0.tgz",
+ "integrity": "sha1-uMxjx02DRXvYqakEUDyO+ya8ozk=",
+ "dev": true
+ }
+ }
+ },
"gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
@@ -6110,6 +6584,17 @@
}
}
},
+ "gulp-header": {
+ "version": "1.8.12",
+ "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
+ "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==",
+ "dev": true,
+ "requires": {
+ "concat-with-sourcemaps": "*",
+ "lodash.template": "^4.4.0",
+ "through2": "^2.0.0"
+ }
+ },
"gulp-inject-string": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/gulp-inject-string/-/gulp-inject-string-1.1.2.tgz",
@@ -6166,6 +6651,325 @@
"glogg": "^1.0.0"
}
},
+ "handlebars": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.4.tgz",
+ "integrity": "sha512-Is8+SzHv8K9STNadlBVpVhxXrSXxVgTyIvhdg2Qjak1SfSZ7iEozLHdwiX1jJ9lLFkcFJxqGK5s/cI7ZX+qGkQ==",
+ "dev": true,
+ "requires": {
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4",
+ "yargs": "^15.3.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "handlebars-helper-create-frame": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/handlebars-helper-create-frame/-/handlebars-helper-create-frame-0.1.0.tgz",
+ "integrity": "sha1-iqUdEK62QI/MZgXUDXc1YohIegM=",
+ "dev": true,
+ "requires": {
+ "create-frame": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "handlebars-helpers": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/handlebars-helpers/-/handlebars-helpers-0.10.0.tgz",
+ "integrity": "sha512-QiyhQz58u/DbuV41VnfpE0nhy6YCH4vB514ajysV8SoKmP+DxU+pR+fahVyNECHj+jiwEN2VrvxD/34/yHaLUg==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-sort": "^0.1.4",
+ "create-frame": "^1.0.0",
+ "define-property": "^1.0.0",
+ "falsey": "^0.3.2",
+ "for-in": "^1.0.2",
+ "for-own": "^1.0.0",
+ "get-object": "^0.2.0",
+ "get-value": "^2.0.6",
+ "handlebars": "^4.0.11",
+ "handlebars-helper-create-frame": "^0.1.0",
+ "handlebars-utils": "^1.0.6",
+ "has-value": "^1.0.0",
+ "helper-date": "^1.0.1",
+ "helper-markdown": "^1.0.0",
+ "helper-md": "^0.2.2",
+ "html-tag": "^2.0.0",
+ "is-even": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "is-number": "^4.0.0",
+ "kind-of": "^6.0.0",
+ "lazy-cache": "^2.0.2",
+ "logging-helpers": "^1.0.0",
+ "micromatch": "^3.1.4",
+ "relative": "^3.0.2",
+ "striptags": "^3.1.0",
+ "to-gfm-code-block": "^0.1.1",
+ "year": "^0.2.1"
+ },
+ "dependencies": {
+ "array-sort": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-0.1.4.tgz",
+ "integrity": "sha512-BNcM+RXxndPxiZ2rd76k6nyQLRZr2/B/sdi8pQ+Joafr5AH279L40dfokSUTp8O+AaqYjXWhblBWa2st2nc4fQ==",
+ "dev": true,
+ "requires": {
+ "default-compare": "^1.0.0",
+ "get-value": "^2.0.6",
+ "kind-of": "^5.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "handlebars-utils": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/handlebars-utils/-/handlebars-utils-1.0.6.tgz",
+ "integrity": "sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0",
+ "typeof-article": "^0.1.1"
+ }
+ },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -6221,8 +7025,7 @@
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "optional": true
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"has-value": {
"version": "1.0.0",
@@ -6268,6 +7071,57 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
+ "helper-date": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/helper-date/-/helper-date-1.0.1.tgz",
+ "integrity": "sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w==",
+ "dev": true,
+ "requires": {
+ "date.js": "^0.3.1",
+ "handlebars-utils": "^1.0.4",
+ "moment": "^2.18.1"
+ }
+ },
+ "helper-markdown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/helper-markdown/-/helper-markdown-1.0.0.tgz",
+ "integrity": "sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA==",
+ "dev": true,
+ "requires": {
+ "handlebars-utils": "^1.0.2",
+ "highlight.js": "^9.12.0",
+ "remarkable": "^1.7.1"
+ }
+ },
+ "helper-md": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/helper-md/-/helper-md-0.2.2.tgz",
+ "integrity": "sha1-wfWdflW7riM2L9ig6XFgeuxp1B8=",
+ "dev": true,
+ "requires": {
+ "ent": "^2.2.0",
+ "extend-shallow": "^2.0.1",
+ "fs-exists-sync": "^0.1.0",
+ "remarkable": "^1.6.2"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "highlight.js": {
+ "version": "9.18.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz",
+ "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==",
+ "dev": true
+ },
"history": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
@@ -6303,6 +7157,16 @@
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
"dev": true
},
+ "html-tag": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/html-tag/-/html-tag-2.0.0.tgz",
+ "integrity": "sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g==",
+ "dev": true,
+ "requires": {
+ "is-self-closing": "^1.0.1",
+ "kind-of": "^6.0.0"
+ }
+ },
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -6392,7 +7256,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
- "optional": true,
"requires": {
"minimatch": "^3.0.4"
}
@@ -6445,6 +7308,12 @@
"wrappy": "1"
}
},
+ "info-symbol": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/info-symbol/-/info-symbol-0.1.0.tgz",
+ "integrity": "sha1-J4QdcoZ920JCzWEtecEGM4gcang=",
+ "dev": true
+ },
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@@ -6792,6 +7661,15 @@
"integrity": "sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI=",
"dev": true
},
+ "is-even": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-even/-/is-even-1.0.0.tgz",
+ "integrity": "sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY=",
+ "dev": true,
+ "requires": {
+ "is-odd": "^0.1.2"
+ }
+ },
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -6888,6 +7766,15 @@
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"dev": true
},
+ "is-odd": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-0.1.2.tgz",
+ "integrity": "sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0"
+ }
+ },
"is-path-inside": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
@@ -6933,6 +7820,15 @@
"is-unc-path": "^1.0.0"
}
},
+ "is-self-closing": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-self-closing/-/is-self-closing-1.0.1.tgz",
+ "integrity": "sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg==",
+ "dev": true,
+ "requires": {
+ "self-closing-tags": "^1.0.1"
+ }
+ },
"is-set": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz",
@@ -7217,6 +8113,15 @@
"package-json": "^6.3.0"
}
},
+ "lazy-cache": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz",
+ "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=",
+ "dev": true,
+ "requires": {
+ "set-getter": "^0.1.0"
+ }
+ },
"lazy-val": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz",
@@ -7465,6 +8370,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
+ "lodash._reinterpolate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
+ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
+ "dev": true
+ },
"lodash.escape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz",
@@ -7494,6 +8405,35 @@
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
"dev": true
},
+ "lodash.template": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
+ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
+ "dev": true,
+ "requires": {
+ "lodash._reinterpolate": "^3.0.0",
+ "lodash.templatesettings": "^4.0.0"
+ }
+ },
+ "lodash.templatesettings": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
+ "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
+ "dev": true,
+ "requires": {
+ "lodash._reinterpolate": "^3.0.0"
+ }
+ },
+ "log-ok": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/log-ok/-/log-ok-0.1.1.tgz",
+ "integrity": "sha1-vqPdNqzQuKckDXhza1uXxlREozQ=",
+ "dev": true,
+ "requires": {
+ "ansi-green": "^0.1.1",
+ "success-symbol": "^0.1.0"
+ }
+ },
"log-symbols": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
@@ -7555,6 +8495,68 @@
}
}
},
+ "log-utils": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/log-utils/-/log-utils-0.2.1.tgz",
+ "integrity": "sha1-pMIXoN2aUFFdm5ICBgkas9TgMc8=",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^0.2.0",
+ "error-symbol": "^0.1.0",
+ "info-symbol": "^0.1.0",
+ "log-ok": "^0.1.1",
+ "success-symbol": "^0.1.0",
+ "time-stamp": "^1.0.1",
+ "warning-symbol": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-colors": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-0.2.0.tgz",
+ "integrity": "sha1-csMd4qDZoszQysMMyYI+6y9kNLU=",
+ "dev": true,
+ "requires": {
+ "ansi-bgblack": "^0.1.1",
+ "ansi-bgblue": "^0.1.1",
+ "ansi-bgcyan": "^0.1.1",
+ "ansi-bggreen": "^0.1.1",
+ "ansi-bgmagenta": "^0.1.1",
+ "ansi-bgred": "^0.1.1",
+ "ansi-bgwhite": "^0.1.1",
+ "ansi-bgyellow": "^0.1.1",
+ "ansi-black": "^0.1.1",
+ "ansi-blue": "^0.1.1",
+ "ansi-bold": "^0.1.1",
+ "ansi-cyan": "^0.1.1",
+ "ansi-dim": "^0.1.1",
+ "ansi-gray": "^0.1.1",
+ "ansi-green": "^0.1.1",
+ "ansi-grey": "^0.1.1",
+ "ansi-hidden": "^0.1.1",
+ "ansi-inverse": "^0.1.1",
+ "ansi-italic": "^0.1.1",
+ "ansi-magenta": "^0.1.1",
+ "ansi-red": "^0.1.1",
+ "ansi-reset": "^0.1.1",
+ "ansi-strikethrough": "^0.1.1",
+ "ansi-underline": "^0.1.1",
+ "ansi-white": "^0.1.1",
+ "ansi-yellow": "^0.1.1",
+ "lazy-cache": "^2.0.1"
+ }
+ }
+ }
+ },
+ "logging-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/logging-helpers/-/logging-helpers-1.0.0.tgz",
+ "integrity": "sha512-qyIh2goLt1sOgQQrrIWuwkRjUx4NUcEqEGAcYqD8VOnOC6ItwkrVE8/tA4smGpjzyp4Svhc6RodDp9IO5ghpyA==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0",
+ "log-utils": "^0.2.1"
+ }
+ },
"lolex": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz",
@@ -7575,24 +8577,6 @@
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
- "lru-cache": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
- "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
- "dev": true,
- "requires": {
- "pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
- },
- "dependencies": {
- "yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
- "dev": true
- }
- }
- },
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -7781,7 +8765,6 @@
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7791,7 +8774,6 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
- "optional": true,
"requires": {
"minipass": "^2.9.0"
}
@@ -8351,6 +9333,12 @@
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
"dev": true
},
+ "neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@@ -8431,7 +9419,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
- "optional": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
@@ -8474,7 +9461,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
- "optional": true,
"requires": {
"npm-normalize-package-bin": "^1.0.1"
}
@@ -8502,14 +9488,12 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
- "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
- "optional": true
+ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
},
"npm-packlist": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
- "optional": true,
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1",
@@ -8520,7 +9504,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -8790,8 +9773,7 @@
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
- "optional": true
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
"os-locale": {
"version": "1.4.0",
@@ -8811,7 +9793,6 @@
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
- "optional": true,
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
@@ -9246,12 +10227,6 @@
"event-stream": "=3.3.4"
}
},
- "pseudomap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
- "dev": true
- },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -9753,6 +10728,42 @@
"rc": "^1.2.8"
}
},
+ "relative": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz",
+ "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=",
+ "dev": true,
+ "requires": {
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "remarkable": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz",
+ "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.10",
+ "autolinker": "~0.28.0"
+ }
+ },
"remove-bom-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz",
@@ -10031,6 +11042,12 @@
"object-assign": "^4.1.1"
}
},
+ "self-closing-tags": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/self-closing-tags/-/self-closing-tags-1.0.1.tgz",
+ "integrity": "sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==",
+ "dev": true
+ },
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
@@ -10208,6 +11225,15 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
+ "set-getter": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz",
+ "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=",
+ "dev": true,
+ "requires": {
+ "to-object-path": "^0.3.0"
+ }
+ },
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -11150,6 +12176,12 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
+ "striptags": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
+ "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0=",
+ "dev": true
+ },
"styled-components": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.0.tgz",
@@ -11182,6 +12214,12 @@
"resolved": "https://registry.npmjs.org/subscribableevent/-/subscribableevent-1.0.1.tgz",
"integrity": "sha512-rFXit43+QoyWRUV/aTiYMUUJl71xmDcxED2cza3PEED75pgHTofMyPcPQrnHzyPqZh+kazEd36eydUKbOZBCpQ=="
},
+ "success-symbol": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz",
+ "integrity": "sha1-JAIuSG878c3KCUKDt2nEctO3KJc=",
+ "dev": true
+ },
"sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@@ -11291,7 +12329,6 @@
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
- "optional": true,
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
@@ -11306,7 +12343,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
- "optional": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -11379,6 +12415,16 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
"through2-filter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
@@ -11453,6 +12499,12 @@
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
+ "to-gfm-code-block": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz",
+ "integrity": "sha1-JdBFpfrlUxielje1kJANpzLYqoI=",
+ "dev": true
+ },
"to-object-path": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
@@ -11707,6 +12759,26 @@
"is-typedarray": "^1.0.0"
}
},
+ "typeof-article": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/typeof-article/-/typeof-article-0.1.1.tgz",
+ "integrity": "sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.1.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
"typescript": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
@@ -11719,6 +12791,13 @@
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==",
"dev": true
},
+ "uglify-js": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz",
+ "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==",
+ "dev": true,
+ "optional": true
+ },
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
@@ -12120,6 +13199,12 @@
"loose-envify": "^1.0.0"
}
},
+ "warning-symbol": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/warning-symbol/-/warning-symbol-0.1.0.tgz",
+ "integrity": "sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE=",
+ "dev": true
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -12272,6 +13357,12 @@
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
"dev": true
},
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
@@ -12281,8 +13372,7 @@
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "optional": true
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"yargs": {
"version": "15.4.1",
@@ -12632,6 +13722,12 @@
"fd-slicer": "~1.1.0"
}
},
+ "year": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/year/-/year-0.2.1.tgz",
+ "integrity": "sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=",
+ "dev": true
+ },
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
diff --git a/gui/package.json b/gui/package.json
index ff29dcbef0..927db17ba9 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -13,10 +13,12 @@
"license": "GPL-3.0",
"dependencies": {
"argv-split": "^2.0.1",
+ "@grpc/grpc-js": "^1.1.2",
"connected-react-router": "^6.8.0",
"d3-geo-projection": "^2.7.0",
"electron-log": "^4.1.1",
"gettext-parser": "^4.0.3",
+ "google-protobuf": "^4.0.0-rc.2",
"history": "^4.6.1",
"jsonrpc-lite": "^2.0.7",
"linux-app-list": "^1.0.1",
@@ -47,6 +49,7 @@
"@types/d3-geo": "^1.11.0",
"@types/enzyme": "^3.1.15",
"@types/enzyme-adapter-react-16": "^1.0.3",
+ "@types/google-protobuf": "^3.7.2",
"@types/mkdirp": "^1.0.0",
"@types/mocha": "^5.2.6",
"@types/node": "^10.12.3",
@@ -81,6 +84,8 @@
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.18.3",
"gettext-extractor": "^3.5.2",
+ "grpc-tools": "^1.9.1",
+ "grpc_tools_node_protoc_ts": "^4.1.0",
"gulp": "^4.0.2",
"gulp-inject-string": "^1.1.2",
"gulp-typescript": "^5.0.1",
@@ -94,6 +99,7 @@
"scripts": {
"postinstall": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true electron-builder install-app-deps",
"build": "gulp build",
+ "build-proto": "gulp build-proto",
"lint": "eslint --ext tsx,ts .",
"format": "prettier \"**/*.{js,css,ts,tsx}\" --write",
"develop": "gulp develop",
diff --git a/gui/scripts/build-proto.sh b/gui/scripts/build-proto.sh
new file mode 100755
index 0000000000..a6cf6a5d94
--- /dev/null
+++ b/gui/scripts/build-proto.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+set -eu
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd "$SCRIPT_DIR"
+
+NODE_MODULES_DIR="$(cd ../node_modules/.bin && pwd)"
+PROTO_DIR="../../mullvad-management-interface/proto"
+PROTO_FILENAME="management_interface.proto"
+DESTINATION_DIR="../build/src/main/management_interface"
+TYPES_DESTINATION_DIR="../src/main/management_interface"
+
+TS_PROTOC_PLUGIN="$NODE_MODULES_DIR/protoc-gen-ts"
+if [[ "$(uname -s)" == "MINGW"* ]]; then
+ TS_PROTOC_PLUGIN="$TS_PROTOC_PLUGIN.cmd"
+fi
+
+mkdir -p $DESTINATION_DIR
+mkdir -p $TYPES_DESTINATION_DIR
+
+"$NODE_MODULES_DIR/grpc_tools_node_protoc" \
+ --js_out=import_style=commonjs,binary:$DESTINATION_DIR \
+ --grpc_out=generate_package_definition:$DESTINATION_DIR \
+ --proto_path=$PROTO_DIR \
+ $PROTO_DIR/$PROTO_FILENAME
+
+"$NODE_MODULES_DIR/grpc_tools_node_protoc" \
+ --plugin=protoc-gen-ts=$TS_PROTOC_PLUGIN \
+ --ts_out=generate_package_definition:$TYPES_DESTINATION_DIR \
+ --proto_path=$PROTO_DIR \
+ $PROTO_DIR/$PROTO_FILENAME
+
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index c3d3fe33a6..ff755e2a36 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -1,324 +1,75 @@
+import * as grpc from '@grpc/grpc-js';
+import {
+ BoolValue,
+ StringValue,
+ UInt32Value,
+} from 'google-protobuf/google/protobuf/wrappers_pb.js';
+import log from 'electron-log';
+import { Empty } from 'google-protobuf/google/protobuf/empty_pb.js';
+import { promisify } from 'util';
import {
AccountToken,
+ Constraint,
+ IRelayListCountry,
+ IRelayListCity,
+ IRelayListHostname,
+ IWireguardTunnelData,
+ IBridgeConstraints,
+ IWireguardConstraints,
+ ITunnelOptions,
+ IOpenVpnConstraints,
+ IRelayList,
+ IShadowsocksEndpointData,
+ RelayProtocol,
BridgeSettings,
+ FirewallPolicyError,
BridgeState,
- DaemonEvent,
- IAccountData,
- IAppVersionInfo,
ILocation,
- IRelayList,
- ISettings,
- IWireguardPublicKey,
+ IAppVersionInfo,
+ IAccountData,
+ IOpenVpnTunnelData,
+ TunnelState,
+ AfterDisconnect,
+ IErrorState,
+ ErrorStateCause,
+ TunnelParameterError,
+ ITunnelStateRelayInfo,
+ TunnelType,
+ IProxyEndpoint,
+ ProxyType,
KeygenEvent,
+ IWireguardPublicKey,
+ ISettings,
+ ConnectionConfig,
+ DaemonEvent,
+ RelaySettings,
RelaySettingsUpdate,
- TunnelState,
- VoucherErrorCode,
+ RelayLocation,
+ ProxySettings,
VoucherResponse,
+ TunnelProtocol,
} from '../shared/daemon-rpc-types';
-import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors';
-import JsonRpcClient, {
- RemoteError as JsonRpcRemoteError,
- SocketTransport,
- TimeOutError as JsonRpcTimeOutError,
-} from './jsonrpc-client';
-import { camelCaseToSnakeCase, snakeCaseToCamelCase } from './transform-object-keys';
-
-import { validate } from 'validated/object';
-import {
- arrayOf,
- boolean,
- enumeration,
- maybe,
- Node as SchemaNode,
- number,
- object,
- oneOf,
- partialObject,
- string,
-} from 'validated/schema';
-
-const locationSchema = maybe(
- partialObject({
- ipv4: maybe(string),
- ipv6: maybe(string),
- country: string,
- city: maybe(string),
- latitude: number,
- longitude: number,
- mullvad_exit_ip: boolean,
- hostname: maybe(string),
- bridge_hostname: maybe(string),
- }),
-);
-
-const constraint = <T>(constraintValue: SchemaNode<T>) => {
- return oneOf(
- string, // any
- object({
- only: constraintValue,
- }),
- );
-};
-const locationConstraintSchema = constraint(
- oneOf(
- object({
- hostname: arrayOf(string),
- }),
- object({
- city: arrayOf(string),
- }),
- object({
- country: string,
- }),
- ),
-);
-
-const customTunnelEndpointSchema = oneOf(
- object({
- openvpn: object({
- endpoint: object({
- address: string,
- protocol: enumeration('udp', 'tcp'),
- }),
- username: string,
- password: string,
- }),
- }),
- object({
- wireguard: object({
- tunnel: object({
- private_key: string,
- addresses: arrayOf(string),
- }),
- peer: object({
- public_key: string,
- allowed_ips: arrayOf(string),
- endpoint: string,
- }),
- ipv4_gateway: string,
- ipv6_gateway: maybe(string),
- }),
- }),
-);
-
-const relaySettingsSchema = oneOf(
- object({
- normal: partialObject({
- location: locationConstraintSchema,
- tunnel_protocol: constraint(enumeration('wireguard', 'openvpn')),
- wireguard_constraints: partialObject({
- port: constraint(number),
- }),
- openvpn_constraints: partialObject({
- port: constraint(number),
- protocol: constraint(enumeration('udp', 'tcp')),
- }),
- }),
- }),
- object({
- custom_tunnel_endpoint: partialObject({
- host: string,
- config: customTunnelEndpointSchema,
- }),
- }),
-);
+import * as managementInterface from './management_interface/management_interface_grpc_pb';
+import * as grpcTypes from './management_interface/management_interface_pb';
+import { CommunicationError, InvalidAccountError } from './errors';
+import consumePromise from '../shared/promise';
-const relayListSchema = partialObject({
- countries: arrayOf(
- partialObject({
- name: string,
- code: string,
- cities: arrayOf(
- partialObject({
- name: string,
- code: string,
- latitude: number,
- longitude: number,
- relays: arrayOf(
- partialObject({
- hostname: string,
- ipv4_addr_in: string,
- include_in_country: boolean,
- active: boolean,
- weight: number,
- bridges: maybe(
- partialObject({
- shadowsocks: arrayOf(
- object({
- port: number,
- cipher: string,
- password: string,
- protocol: enumeration('tcp', 'udp'),
- }),
- ),
- }),
- ),
- tunnels: maybe(
- partialObject({
- openvpn: arrayOf(
- partialObject({
- port: number,
- protocol: string,
- }),
- ),
- wireguard: arrayOf(
- partialObject({
- port_ranges: arrayOf(arrayOf(number)),
- public_key: string,
- }),
- ),
- }),
- ),
- }),
- ),
- }),
- ),
- }),
- ),
-});
-
-const openVpnProxySchema = maybe(
- oneOf(
- object({
- local: partialObject({
- port: number,
- peer: string,
- }),
- }),
- object({
- remote: partialObject({
- address: string,
- auth: maybe(
- partialObject({
- username: string,
- password: string,
- }),
- ),
- }),
- }),
- object({
- shadowsocks: partialObject({
- peer: string,
- password: string,
- cipher: string,
- }),
- }),
- ),
-);
+const NETWORK_CALL_TIMEOUT = 10000;
+const CHANNEL_STATE_TIMEOUT = 1000 * 60 * 60;
-const bridgeSettingsSchema = oneOf(
- partialObject({ normal: partialObject({ location: locationConstraintSchema }) }),
- partialObject({ custom: openVpnProxySchema }),
+const ManagementServiceClient = grpc.makeClientConstructor(
+ // @ts-ignore
+ managementInterface['mullvad_daemon.management_interface.ManagementService'],
+ 'ManagementService',
);
-const tunnelOptionsSchema = partialObject({
- openvpn: partialObject({
- mssfix: maybe(number),
- }),
- wireguard: partialObject({
- mtu: maybe(number),
- // only relevant on linux
- fmwark: maybe(number),
- }),
- generic: partialObject({
- enable_ipv6: boolean,
- }),
-});
-
-const accountDataSchema = partialObject({
- expiry: string,
-});
-
-const voucherResponseSchema = partialObject({
- new_expiry: string,
-});
-
-const tunnelStateSchema = oneOf(
- object({
- state: enumeration('disconnecting'),
- details: enumeration('nothing', 'block', 'reconnect'),
- }),
- object({
- state: enumeration('connecting', 'connected'),
- details: object({
- endpoint: partialObject({
- address: string,
- protocol: enumeration('tcp', 'udp'),
- tunnel_type: enumeration('wireguard', 'openvpn'),
- proxy: maybe(
- partialObject({
- address: string,
- protocol: enumeration('tcp', 'udp'),
- proxy_type: enumeration('shadowsocks', 'custom'),
- }),
- ),
- }),
- location: maybe(locationSchema),
- }),
- }),
- object({
- state: enumeration('error'),
- details: object({
- block_failure: maybe(
- object({
- reason: enumeration('generic', 'locked'),
- details: maybe(
- object({
- name: string,
- pid: number,
- }),
- ),
- }),
- ),
- cause: oneOf(
- object({
- reason: enumeration(
- 'ipv6_unavailable',
- 'set_dns_error',
- 'start_tunnel_error',
- 'is_offline',
- 'tap_adapter_problem',
- ),
- }),
- object({
- reason: enumeration('set_firewall_policy_error'),
- details: object({
- reason: enumeration('generic', 'locked'),
- details: maybe(
- object({
- name: string,
- pid: number,
- }),
- ),
- }),
- }),
- object({
- reason: enumeration('auth_failed'),
- details: maybe(string),
- }),
- object({
- reason: enumeration('tunnel_parameter_error'),
- details: enumeration(
- 'no_matching_relay',
- 'no_matching_bridge_relay',
- 'no_wireguard_key',
- 'custom_tunnel_host_resultion_error',
- ),
- }),
- ),
- }),
- }),
- object({
- state: enumeration('connected', 'connecting', 'disconnected'),
- }),
+const noConnectionError = new Error('No connection established to daemon');
+const configNotSupported = new Error('Setting custom settings is not supported');
+const invalidErrorStateCause = new Error(
+ 'VPN_PERMISSION_DENIED is not a valid error state cause on desktop',
);
-const appVersionInfoSchema = partialObject({
- supported: boolean,
- suggested_upgrade: maybe(string),
-});
-
export class ConnectionObserver {
constructor(private openHandler: () => void, private closeHandler: (error?: Error) => void) {}
@@ -338,7 +89,7 @@ export class ConnectionObserver {
export class SubscriptionListener<T> {
// Only meant to be used by DaemonRpc
// @internal
- public subscriptionId?: string | number;
+ public subscriptionId?: number;
constructor(
private eventHandler: (payload: T) => void,
@@ -358,328 +109,1130 @@ export class SubscriptionListener<T> {
}
}
-const settingsSchema = partialObject({
- account_token: maybe(string),
- allow_lan: boolean,
- auto_connect: boolean,
- block_when_disconnected: boolean,
- show_beta_releases: boolean,
- bridge_settings: bridgeSettingsSchema,
- bridge_state: enumeration('on', 'auto', 'off'),
- relay_settings: relaySettingsSchema,
- tunnel_options: tunnelOptionsSchema,
-});
+export class ResponseParseError extends Error {
+ constructor(message: string) {
+ super(message);
+ }
+}
-const wireguardPublicKey = object({
- key: string,
- created: string,
-});
+type CallFunctionArgument<T, R> =
+ | ((arg: T, callback: (error: Error | null, result: R) => void) => void)
+ | undefined;
-const keygenEventSchema = oneOf(
- enumeration('too_many_keys', 'generation_failure'),
- object({
- new_key: object({
- key: string,
- created: string,
- }),
- }),
-);
+export class DaemonRpc {
+ constructor(connectionParams: string) {
+ this.client = (new ManagementServiceClient(
+ connectionParams,
+ grpc.credentials.createInsecure(),
+ this.channelOptions(),
+ ) as unknown) as managementInterface.ManagementServiceClient;
+ }
-const daemonEventSchema = oneOf(
- object({
- tunnel_state: tunnelStateSchema,
- }),
- object({
- settings: settingsSchema,
- }),
- object({
- relay_list: relayListSchema,
- }),
- object({
- wireguard_key: keygenEventSchema,
- }),
- object({
- app_version_info: appVersionInfoSchema,
- }),
-);
+ private client: managementInterface.ManagementServiceClient;
+ private isConnected = false;
+ private connectionObservers: ConnectionObserver[] = [];
+ private nextSubscriptionId = 0;
+ private subscriptions: Map<number, grpc.ClientReadableStream<grpcTypes.DaemonEvent>> = new Map();
+ private reconnectionTimeout?: number;
-export class ResponseParseError extends Error {
- constructor(message: string, private validationErrorValue?: Error) {
- super(message);
+ private subscriptionId(): number {
+ const current = this.nextSubscriptionId;
+ this.nextSubscriptionId += 1;
+ return current;
}
- get validationError(): Error | undefined {
- return this.validationErrorValue;
+ private deadlineFromNow() {
+ return Date.now() + NETWORK_CALL_TIMEOUT;
}
-}
-// Timeout used for RPC calls that do networking
-const NETWORK_CALL_TIMEOUT = 10000;
+ private channelStateTimeout(): number {
+ return Date.now() + CHANNEL_STATE_TIMEOUT;
+ }
-export class DaemonRpc {
- private transport = new JsonRpcClient(new SocketTransport());
+ private callEmpty<R>(fn: CallFunctionArgument<Empty, R>): Promise<R> {
+ return this.call<Empty, R>(fn, new Empty());
+ }
+
+ private callString<R>(fn: CallFunctionArgument<StringValue, R>, value?: string): Promise<R> {
+ const googleString = new StringValue();
+
+ if (value !== undefined) {
+ googleString.setValue(value);
+ }
+
+ return this.call<StringValue, R>(fn, googleString);
+ }
+
+ private callBool<R>(fn: CallFunctionArgument<BoolValue, R>, value?: boolean): Promise<R> {
+ const googleBool = new BoolValue();
+
+ if (value !== undefined) {
+ googleBool.setValue(value);
+ }
+
+ return this.call<BoolValue, R>(fn, googleBool);
+ }
+
+ private callNumber<R>(fn: CallFunctionArgument<UInt32Value, R>, value?: number): Promise<R> {
+ const googleNumber = new UInt32Value();
+
+ if (value !== undefined) {
+ googleNumber.setValue(value);
+ }
+
+ return this.call<UInt32Value, R>(fn, googleNumber);
+ }
+
+ private call<T, R>(fn: CallFunctionArgument<T, R>, arg: T): Promise<R> {
+ if (fn && this.isConnected) {
+ return promisify<T, R>(fn.bind(this.client))(arg);
+ } else {
+ throw noConnectionError;
+ }
+ }
+
+ public connect(): Promise<void> {
+ return new Promise((resolve, reject) => {
+ this.client.waitForReady(this.deadlineFromNow(), (error) => {
+ if (error) {
+ this.connectionObservers.forEach((observer) => observer.onClose(error));
+ this.ensureConnectivity();
+ reject(error);
+ } else {
+ this.reconnectionTimeout = undefined;
+ this.isConnected = true;
+ this.connectionObservers.forEach((observer) => observer.onOpen());
+ this.setChannelCallback();
+ resolve();
+ }
+ });
+ });
+ }
- public connect(connectionParams: { path: string }): Promise<void> {
- return this.transport.connect(connectionParams);
+ private channelOptions(): grpc.ClientOptions {
+ return {
+ 'grpc.max_reconnect_backoff_ms': 3000,
+ 'grpc.initial_reconnect_backoff_ms': 3000,
+ 'grpc.keepalive_time_ms': Math.pow(2, 30),
+ 'grpc.keepalive_timeout_ms': Math.pow(2, 30),
+ };
+ }
+
+ private connectivityChangeCallback(timeoutErr?: Error) {
+ const channel = this.client.getChannel();
+ const currentState = channel?.getConnectivityState(true);
+ log.debug(`GRPC Channel connectivity state changed to ${currentState}`);
+ if (channel) {
+ if (timeoutErr) {
+ this.setChannelCallback(currentState);
+ return;
+ }
+ const wasConnected = this.isConnected;
+ if (this.channelDisconnected(currentState)) {
+ this.connectionObservers.forEach((observer) => observer.onClose());
+ this.isConnected = false;
+ // Try and reconnect in case
+ consumePromise(
+ this.connect().catch((error) => {
+ log.error(`Failed to reconnect - ${error}`);
+ }),
+ );
+ this.setChannelCallback(currentState);
+ } else if (!wasConnected && currentState === grpc.connectivityState.READY) {
+ this.isConnected = true;
+ this.connectionObservers.forEach((observer) => observer.onOpen());
+ this.setChannelCallback(currentState);
+ }
+ }
+ }
+
+ private channelDisconnected(state: grpc.connectivityState): boolean {
+ return (
+ (state === grpc.connectivityState.SHUTDOWN ||
+ state === grpc.connectivityState.TRANSIENT_FAILURE ||
+ state === grpc.connectivityState.IDLE) &&
+ this.isConnected
+ );
+ }
+
+ private setChannelCallback(currentState?: grpc.connectivityState) {
+ const channel = this.client.getChannel();
+ if (currentState === undefined && channel) {
+ currentState = channel?.getConnectivityState(false);
+ }
+ if (currentState) {
+ channel.watchConnectivityState(currentState, this.channelStateTimeout(), (error) =>
+ this.connectivityChangeCallback(error),
+ );
+ }
+ }
+
+ // Since grpc.Channel.watchConnectivityState() isn't always running as intended, whenever the
+ // client fails to connect at first, `ensureConnectivity()` should be called so that it tries to
+ // check the connectivity state and nudge the client into connecting.
+ // `grpc.Channel.getConnectivityState(true)` should make it attempt to connect.
+ private ensureConnectivity() {
+ this.reconnectionTimeout = setTimeout(() => {
+ const lastState = this.client.getChannel().getConnectivityState(true);
+ if (this.channelDisconnected(lastState)) {
+ this.connectionObservers.forEach((observer) => observer.onClose());
+ this.isConnected = false;
+ }
+ if (!this.isConnected) {
+ consumePromise(
+ this.connect().catch((error) => {
+ log.error(`Failed to reconnect - ${error}`);
+ }),
+ );
+ }
+ }, 3000);
}
public disconnect() {
- this.transport.disconnect();
+ this.isConnected = false;
+ this.subscriptions.clear();
+ this.client.close();
+ if (this.reconnectionTimeout) {
+ clearTimeout(this.reconnectionTimeout);
+ }
}
public addConnectionObserver(observer: ConnectionObserver) {
- this.transport.on('open', observer.onOpen).on('close', observer.onClose);
+ this.connectionObservers.push(observer);
+ const currentState = this.client.getChannel()?.getConnectivityState(true);
+ if (
+ currentState === grpc.connectivityState.SHUTDOWN ||
+ currentState === grpc.connectivityState.TRANSIENT_FAILURE ||
+ currentState === grpc.connectivityState.IDLE
+ ) {
+ observer.onClose();
+ } else {
+ observer.onOpen();
+ }
}
public removeConnectionObserver(observer: ConnectionObserver) {
- this.transport.off('open', observer.onOpen).off('close', observer.onClose);
+ const index = this.connectionObservers.indexOf(observer);
+ if (index !== -1) {
+ this.connectionObservers.splice(index, 1);
+ }
}
public async getAccountData(accountToken: AccountToken): Promise<IAccountData> {
- let response;
try {
- response = await this.transport.send('get_account_data', accountToken, NETWORK_CALL_TIMEOUT);
+ const response = await this.callString<grpcTypes.AccountData>(
+ this.client.getAccountData,
+ accountToken,
+ );
+ const expiry = response.getExpiry()!.toDate().toISOString();
+ return { expiry };
} catch (error) {
- if (error instanceof JsonRpcRemoteError) {
+ if (error.code) {
switch (error.code) {
- case -200: // Account doesn't exist
+ case grpc.status.UNAUTHENTICATED:
throw new InvalidAccountError();
- case -32603: // Internal error
+ default:
throw new CommunicationError();
}
- } else if (error instanceof JsonRpcTimeOutError) {
- throw new NoDaemonError();
- } else {
- throw error;
}
- }
-
- try {
- return validate(accountDataSchema, response);
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_account_data', error);
+ throw error;
}
}
public async getWwwAuthToken(): Promise<string> {
- const response = await this.transport.send('get_www_auth_token');
- try {
- return validate(string, response);
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_www_auth_token', error);
- }
+ const response = await this.callEmpty<StringValue>(this.client.getWwwAuthToken);
+ return response.getValue();
}
public async submitVoucher(voucherCode: string): Promise<VoucherResponse> {
try {
- const response = await this.transport.send('submit_voucher', voucherCode);
- const new_expiry = validate(voucherResponseSchema, response).new_expiry;
- return { type: 'success', new_expiry };
+ const response = await this.callString<grpcTypes.VoucherSubmission>(
+ this.client.submitVoucher,
+ voucherCode,
+ );
+
+ const secondsAdded = ensureExists(
+ response.getSecondsAdded(),
+ "no 'secondsAdded' field in voucher response",
+ );
+ const newExpiry = ensureExists(
+ response.getNewExpiry(),
+ "no 'newExpiry' field in voucher response",
+ )
+ .toDate()
+ .toISOString();
+ return {
+ type: 'success',
+ secondsAdded,
+ newExpiry,
+ };
} catch (error) {
- if (error instanceof JsonRpcRemoteError) {
+ if (error.code) {
switch (error.code) {
- case VoucherErrorCode.Invalid:
+ case grpc.status.NOT_FOUND:
return { type: 'invalid' };
- case VoucherErrorCode.AlreadyUsed:
+ case grpc.status.RESOURCE_EXHAUSTED:
return { type: 'already_used' };
}
}
+ return { type: 'error' };
}
-
- return { type: 'error' };
}
- public async getRelayLocations(): Promise<IRelayList> {
- const response = await this.transport.send('get_relay_locations');
- try {
- return snakeCaseToCamelCase(validate(relayListSchema, response));
- } catch (error) {
- throw new ResponseParseError(`Invalid response from get_relay_locations: ${error}`, error);
+ public getRelayLocations(): Promise<IRelayList> {
+ if (this.isConnected) {
+ return new Promise((resolve, reject) => {
+ const relayLocations: IRelayListCountry[] = [];
+ const stream = this.client.getRelayLocations(new Empty());
+ stream.on('data', (country: grpcTypes.RelayListCountry) =>
+ relayLocations.push(convertFromRelayListCountry(country.toObject())),
+ );
+ stream.on('end', () => resolve({ countries: relayLocations }));
+ stream.on('close', reject);
+ });
+ } else {
+ throw noConnectionError;
}
}
public async createNewAccount(): Promise<string> {
- const response = await this.transport.send('create_new_account');
- return validate(string, response);
+ const response = await this.callEmpty<StringValue>(this.client.createNewAccount);
+ return response.getValue();
}
public async setAccount(accountToken?: AccountToken): Promise<void> {
- await this.transport.send('set_account', [accountToken]);
+ await this.callString(this.client.setAccount, accountToken);
}
+ // TODO: Custom tunnel configurations are not supported by the GUI.
public async updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> {
- await this.transport.send('update_relay_settings', [camelCaseToSnakeCase(relaySettings)]);
+ if ('normal' in relaySettings) {
+ const settingsUpdate = relaySettings.normal;
+ const grpcRelaySettings = new grpcTypes.RelaySettingsUpdate();
+
+ const normalUpdate = new grpcTypes.NormalRelaySettingsUpdate();
+
+ if (settingsUpdate.tunnelProtocol) {
+ const tunnelTypeUpdate = new grpcTypes.TunnelTypeUpdate();
+ tunnelTypeUpdate.setTunnelType(
+ convertToTunnelTypeConstraint(settingsUpdate.tunnelProtocol),
+ );
+ normalUpdate.setTunnelType(tunnelTypeUpdate);
+ }
+
+ if (settingsUpdate.location) {
+ normalUpdate.setLocation(convertToLocation(liftConstraint(settingsUpdate.location)));
+ }
+
+ if (settingsUpdate.wireguardConstraints) {
+ normalUpdate.setWireguardConstraints(
+ convertToWireguardConstraints(settingsUpdate.wireguardConstraints),
+ );
+ }
+
+ if (settingsUpdate.openvpnConstraints) {
+ normalUpdate.setOpenvpnConstraints(
+ convertToOpenVpnConstraints(settingsUpdate.openvpnConstraints),
+ );
+ }
+
+ grpcRelaySettings.setNormal(normalUpdate);
+ await this.call<grpcTypes.RelaySettingsUpdate, Empty>(
+ this.client.updateRelaySettings,
+ grpcRelaySettings,
+ );
+ }
}
public async setAllowLan(allowLan: boolean): Promise<void> {
- await this.transport.send('set_allow_lan', [allowLan]);
+ await this.callBool(this.client.setAllowLan, allowLan);
}
public async setShowBetaReleases(showBetaReleases: boolean): Promise<void> {
- await this.transport.send('set_show_beta_releases', [showBetaReleases]);
+ await this.callBool(this.client.setShowBetaReleases, showBetaReleases);
}
public async setEnableIpv6(enableIpv6: boolean): Promise<void> {
- await this.transport.send('set_enable_ipv6', [enableIpv6]);
+ await this.callBool(this.client.setEnableIpv6, enableIpv6);
}
public async setBlockWhenDisconnected(blockWhenDisconnected: boolean): Promise<void> {
- await this.transport.send('set_block_when_disconnected', [blockWhenDisconnected]);
+ await this.callBool(this.client.setBlockWhenDisconnected, blockWhenDisconnected);
}
public async setBridgeState(bridgeState: BridgeState): Promise<void> {
- await this.transport.send('set_bridge_state', [bridgeState]);
+ const bridgeStateMap = {
+ auto: grpcTypes.BridgeState.State.AUTO,
+ on: grpcTypes.BridgeState.State.ON,
+ off: grpcTypes.BridgeState.State.OFF,
+ };
+
+ const grpcBridgeState = new grpcTypes.BridgeState();
+ grpcBridgeState.setState(bridgeStateMap[bridgeState]);
+ await this.call<grpcTypes.BridgeState, Empty>(this.client.setBridgeState, grpcBridgeState);
}
public async setBridgeSettings(bridgeSettings: BridgeSettings): Promise<void> {
- await this.transport.send('set_bridge_settings', [bridgeSettings]);
+ const grpcBridgeSettings = new grpcTypes.BridgeSettings();
+
+ if ('normal' in bridgeSettings) {
+ const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal);
+ grpcBridgeSettings.setNormal(normalSettings);
+ }
+
+ if ('custom' in bridgeSettings) {
+ throw configNotSupported;
+ }
+
+ await this.call<grpcTypes.BridgeSettings, Empty>(
+ this.client.setBridgeSettings,
+ grpcBridgeSettings,
+ );
}
public async setOpenVpnMssfix(mssfix?: number): Promise<void> {
- await this.transport.send('set_openvpn_mssfix', [mssfix]);
+ await this.callNumber(this.client.setOpenvpnMssfix, mssfix);
}
public async setWireguardMtu(mtu?: number): Promise<void> {
- await this.transport.send('set_wireguard_mtu', [mtu]);
+ await this.callNumber(this.client.setWireguardMtu, mtu);
}
public async setAutoConnect(autoConnect: boolean): Promise<void> {
- await this.transport.send('set_auto_connect', [autoConnect]);
+ await this.callBool(this.client.setAutoConnect, autoConnect);
}
public async connectTunnel(): Promise<void> {
- await this.transport.send('connect');
+ await this.callEmpty(this.client.connectTunnel);
}
public async disconnectTunnel(): Promise<void> {
- await this.transport.send('disconnect');
+ await this.callEmpty(this.client.disconnectTunnel);
}
public async reconnectTunnel(): Promise<void> {
- await this.transport.send('reconnect');
+ await this.callEmpty(this.client.reconnectTunnel);
}
- public async getLocation(): Promise<ILocation | undefined> {
- const response = await this.transport.send('get_current_location', [], NETWORK_CALL_TIMEOUT);
- try {
- const validatedObject = validate(locationSchema, response);
- if (validatedObject) {
- return snakeCaseToCamelCase(validatedObject);
- } else {
- return undefined;
- }
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_current_location', error);
- }
+ public async getLocation(): Promise<ILocation> {
+ const response = await this.callEmpty<grpcTypes.GeoIpLocation>(this.client.getCurrentLocation);
+ return response.toObject();
}
public async getState(): Promise<TunnelState> {
- const response = await this.transport.send('get_state');
- try {
- return snakeCaseToCamelCase(validate(tunnelStateSchema, response));
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_state', error);
- }
+ const response = await this.callEmpty<grpcTypes.TunnelState>(this.client.getTunnelState);
+ return convertFromTunnelState(response)!;
}
public async getSettings(): Promise<ISettings> {
- const response = await this.transport.send('get_settings');
- try {
- return snakeCaseToCamelCase(validate(settingsSchema, response));
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_settings', error);
- }
+ const response = await this.callEmpty<grpcTypes.Settings>(this.client.getSettings);
+ return convertFromSettings(response)!;
}
- public async subscribeDaemonEventListener(
- listener: SubscriptionListener<DaemonEvent>,
- ): Promise<void> {
- const subscriptionId = await this.transport.subscribe('daemon_event', (payload) => {
- let daemonEvent: DaemonEvent;
+ public subscribeDaemonEventListener(listener: SubscriptionListener<DaemonEvent>) {
+ const call = this.isConnected && this.client.eventsListen(new Empty());
+ if (!call) {
+ throw noConnectionError;
+ }
+ const subscriptionId = this.subscriptionId();
+ listener.subscriptionId = subscriptionId;
+ this.subscriptions.set(subscriptionId, call);
+ call.on('data', (data: grpcTypes.DaemonEvent) => {
try {
- daemonEvent = snakeCaseToCamelCase(validate(daemonEventSchema, payload));
- } catch (error) {
- listener.onError(new ResponseParseError('Invalid payload from daemon_event', error));
- return;
+ const daemonEvent = convertFromDaemonEvent(data);
+ listener.onEvent(daemonEvent);
+ } catch (err) {
+ listener.onError(err);
}
+ });
- listener.onEvent(daemonEvent);
+ call.on('error', (error) => {
+ // if the subscription was cancelled by the client, there's no reason to
+ // invoke the onError handler
+ if (
+ 'code' in error &&
+ error['code'] === grpc.status.CANCELLED &&
+ !this.subscriptions.has(subscriptionId)
+ ) {
+ return;
+ }
+ listener.onError(error);
+ this.removeSubscription(subscriptionId);
});
+ }
- listener.subscriptionId = subscriptionId;
+ public unsubscribeDaemonEventListener(listener: SubscriptionListener<DaemonEvent>) {
+ const id = listener.subscriptionId;
+ if (id !== undefined) {
+ this.removeSubscription(id);
+ }
}
- public async unsubscribeDaemonEventListener(
- listener: SubscriptionListener<DaemonEvent>,
- ): Promise<void> {
- if (listener.subscriptionId) {
- return this.transport.unsubscribe('daemon_event', listener.subscriptionId);
+ private removeSubscription(id: number) {
+ const subscription = this.subscriptions.get(id);
+ if (subscription !== undefined) {
+ this.subscriptions.delete(id);
+ subscription.cancel();
}
}
public async getAccountHistory(): Promise<AccountToken[]> {
- const response = await this.transport.send('get_account_history');
- try {
- return validate(arrayOf(string), response);
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_account_history');
- }
+ const response = await this.callEmpty<grpcTypes.AccountHistory>(this.client.getAccountHistory);
+ return response.toObject().tokenList;
}
public async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
- await this.transport.send('remove_account_from_history', accountToken);
+ await this.callString(this.client.removeAccountFromHistory, accountToken);
}
public async getCurrentVersion(): Promise<string> {
- const response = await this.transport.send('get_current_version');
- try {
- return validate(string, response);
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_current_version');
- }
+ const response = await this.callEmpty<StringValue>(this.client.getCurrentVersion);
+ return response.getValue();
}
public async generateWireguardKey(): Promise<KeygenEvent> {
- const response = await this.transport.send('generate_wireguard_key');
- try {
- const validatedResponse = validate(keygenEventSchema, response);
- switch (validatedResponse) {
- case 'too_many_keys':
- case 'generation_failure':
- return validatedResponse;
- default:
- return snakeCaseToCamelCase(validatedResponse as object);
+ const response = await this.callEmpty<grpcTypes.KeygenEvent>(this.client.generateWireguardKey);
+ return convertFromKeygenEvent(response);
+ }
+
+ public async getWireguardKey(): Promise<IWireguardPublicKey> {
+ const response = await this.callEmpty<grpcTypes.PublicKey>(this.client.getWireguardKey);
+ return {
+ created: response.getCreated()!.toDate().toISOString(),
+ key: convertFromWireguardKey(response.getKey()),
+ };
+ }
+
+ public async verifyWireguardKey(): Promise<boolean> {
+ const response = await this.callEmpty<BoolValue>(this.client.verifyWireguardKey);
+ return response.getValue();
+ }
+
+ public async getVersionInfo(): Promise<IAppVersionInfo> {
+ const response = await this.callEmpty<grpcTypes.AppVersionInfo>(this.client.getVersionInfo);
+ return response.toObject();
+ }
+}
+
+function liftConstraint<T>(constraint: Constraint<T> | undefined): T | undefined {
+ if (constraint !== undefined && constraint !== 'any') {
+ return constraint.only;
+ }
+ return undefined;
+}
+
+function convertFromRelayListCountry(
+ country: grpcTypes.RelayListCountry.AsObject,
+): IRelayListCountry {
+ return {
+ ...country,
+ cities: country.citiesList.map(convertFromRelayListCity),
+ };
+}
+
+function convertFromRelayListCity(city: grpcTypes.RelayListCity.AsObject): IRelayListCity {
+ return {
+ ...city,
+ relays: city.relaysList.map(convertFromRelayListRelay),
+ };
+}
+
+function convertFromRelayListRelay(relay: grpcTypes.Relay.AsObject): IRelayListHostname {
+ return {
+ ...relay,
+ tunnels: relay.tunnels && {
+ ...relay.tunnels,
+ openvpn: relay.tunnels.openvpnList.map(convertFromOpenvpnList),
+ wireguard: relay.tunnels.wireguardList.map(convertFromWireguardList),
+ },
+ bridges: relay.bridges && {
+ shadowsocks: relay.bridges.shadowsocksList.map(convertFromShadowsocksList),
+ },
+ };
+}
+
+function convertFromOpenvpnList(
+ openvpn: grpcTypes.OpenVpnEndpointData.AsObject,
+): IOpenVpnTunnelData {
+ return {
+ ...openvpn,
+ protocol: convertFromTransportProtocol(openvpn.protocol),
+ };
+}
+
+function convertFromWireguardList(
+ wireguard: grpcTypes.WireguardEndpointData.AsObject,
+): IWireguardTunnelData {
+ return {
+ ...wireguard,
+ portRanges: wireguard.portRangesList,
+ publicKey: convertFromWireguardKey(wireguard.publicKey),
+ };
+}
+
+function convertFromWireguardKey(publicKey: Uint8Array | string): string {
+ if (typeof publicKey === 'string') {
+ return publicKey;
+ }
+ return Buffer.from(publicKey).toString('base64');
+}
+
+function convertFromShadowsocksList(
+ shadowsocks: grpcTypes.ShadowsocksEndpointData.AsObject,
+): IShadowsocksEndpointData {
+ return {
+ ...shadowsocks,
+ protocol: convertFromTransportProtocol(shadowsocks.protocol),
+ };
+}
+
+function convertFromTransportProtocol(protocol: grpcTypes.TransportProtocol): RelayProtocol {
+ const protocolMap: Record<grpcTypes.TransportProtocol, RelayProtocol> = {
+ [grpcTypes.TransportProtocol.TCP]: 'tcp',
+ [grpcTypes.TransportProtocol.UDP]: 'udp',
+ };
+ return protocolMap[protocol];
+}
+
+function convertFromTunnelState(tunnelState: grpcTypes.TunnelState): TunnelState | undefined {
+ const tunnelStateObject = tunnelState.toObject();
+ switch (tunnelState.getStateCase()) {
+ case grpcTypes.TunnelState.StateCase.STATE_NOT_SET:
+ return undefined;
+ case grpcTypes.TunnelState.StateCase.DISCONNECTED:
+ return { state: 'disconnected' };
+ case grpcTypes.TunnelState.StateCase.DISCONNECTING: {
+ const detailsMap: Record<grpcTypes.AfterDisconnect, AfterDisconnect> = {
+ [grpcTypes.AfterDisconnect.NOTHING]: 'nothing',
+ [grpcTypes.AfterDisconnect.BLOCK]: 'block',
+ [grpcTypes.AfterDisconnect.RECONNECT]: 'reconnect',
+ };
+ return (
+ tunnelStateObject.disconnecting && {
+ state: 'disconnecting',
+ details: detailsMap[tunnelStateObject.disconnecting.afterDisconnect],
+ }
+ );
+ }
+ case grpcTypes.TunnelState.StateCase.ERROR:
+ return (
+ tunnelStateObject.error?.errorState && {
+ state: 'error',
+ details: convertFromTunnelStateError(tunnelStateObject.error.errorState),
+ }
+ );
+ case grpcTypes.TunnelState.StateCase.CONNECTING:
+ return {
+ state: 'connecting',
+ details:
+ tunnelStateObject.connecting?.relayInfo &&
+ convertFromTunnelStateRelayInfo(tunnelStateObject.connecting.relayInfo),
+ };
+ case grpcTypes.TunnelState.StateCase.CONNECTED: {
+ const relayInfo =
+ tunnelStateObject.connected?.relayInfo &&
+ convertFromTunnelStateRelayInfo(tunnelStateObject.connected.relayInfo);
+ return (
+ relayInfo && {
+ state: 'connected',
+ details: relayInfo,
+ }
+ );
+ }
+ }
+}
+
+function convertFromTunnelStateError(state: grpcTypes.ErrorState.AsObject): IErrorState {
+ return {
+ ...state,
+ cause: convertFromTunnelStateErrorCause(state.cause, state),
+ blockFailure: state.blockingError
+ ? convertFromFirewallPolicyError(state.blockingError)
+ : undefined,
+ };
+}
+
+function convertFromTunnelStateErrorCause(
+ cause: grpcTypes.ErrorState.Cause,
+ state: grpcTypes.ErrorState.AsObject,
+): ErrorStateCause {
+ switch (cause) {
+ case grpcTypes.ErrorState.Cause.IS_OFFLINE:
+ return { reason: 'is_offline' };
+ case grpcTypes.ErrorState.Cause.SET_DNS_ERROR:
+ return { reason: 'set_dns_error' };
+ case grpcTypes.ErrorState.Cause.IPV6_UNAVAILABLE:
+ return { reason: 'ipv6_unavailable' };
+ case grpcTypes.ErrorState.Cause.START_TUNNEL_ERROR:
+ return { reason: 'start_tunnel_error' };
+ case grpcTypes.ErrorState.Cause.TAP_ADAPTER_PROBLEM:
+ return { reason: 'tap_adapter_problem' };
+ case grpcTypes.ErrorState.Cause.SET_FIREWALL_POLICY_ERROR:
+ return {
+ reason: 'set_firewall_policy_error',
+ details: convertFromFirewallPolicyError(state.policyError!),
+ };
+ case grpcTypes.ErrorState.Cause.AUTH_FAILED:
+ return { reason: 'auth_failed', details: state.authFailReason };
+ case grpcTypes.ErrorState.Cause.TUNNEL_PARAMETER_ERROR: {
+ const parameterErrorMap: Record<
+ grpcTypes.ErrorState.GenerationError,
+ TunnelParameterError
+ > = {
+ [grpcTypes.ErrorState.GenerationError.NO_MATCHING_RELAY]: 'no_matching_relay',
+ [grpcTypes.ErrorState.GenerationError.NO_MATCHING_BRIDGE_RELAY]: 'no_matching_bridge_relay',
+ [grpcTypes.ErrorState.GenerationError.NO_WIREGUARD_KEY]: 'no_wireguard_key',
+ [grpcTypes.ErrorState.GenerationError.CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR]:
+ 'custom_tunnel_host_resultion_error',
+ };
+ return { reason: 'tunnel_parameter_error', details: parameterErrorMap[state.parameterError] };
+ }
+ case grpcTypes.ErrorState.Cause.VPN_PERMISSION_DENIED:
+ // VPN_PERMISSION_DENIED is only ever created on Android
+ throw invalidErrorStateCause;
+ }
+}
+
+function convertFromFirewallPolicyError(
+ error: grpcTypes.ErrorState.FirewallPolicyError.AsObject,
+): FirewallPolicyError {
+ switch (error.type) {
+ case grpcTypes.ErrorState.FirewallPolicyError.ErrorType.GENERIC:
+ return { reason: 'generic' };
+ case grpcTypes.ErrorState.FirewallPolicyError.ErrorType.LOCKED: {
+ const pid = error.lockPid;
+ const name = error.lockName;
+ return { reason: 'locked', details: pid && name ? { pid, name } : undefined };
+ }
+ }
+}
+
+function convertFromTunnelStateRelayInfo(
+ state: grpcTypes.TunnelStateRelayInfo.AsObject,
+): ITunnelStateRelayInfo | undefined {
+ if (state.tunnelEndpoint) {
+ return {
+ ...state,
+ endpoint: {
+ ...state.tunnelEndpoint,
+ tunnelType: convertFromTunnelType(state.tunnelEndpoint.tunnelType),
+ protocol: convertFromTransportProtocol(state.tunnelEndpoint.protocol),
+ proxy: state.tunnelEndpoint.proxy && convertFromProxyEndpoint(state.tunnelEndpoint.proxy),
+ },
+ };
+ }
+ return undefined;
+}
+
+function convertFromTunnelType(tunnelType: grpcTypes.TunnelType): TunnelType {
+ const tunnelTypeMap: Record<grpcTypes.TunnelType, TunnelType> = {
+ [grpcTypes.TunnelType.WIREGUARD]: 'wireguard',
+ [grpcTypes.TunnelType.OPENVPN]: 'openvpn',
+ };
+
+ return tunnelTypeMap[tunnelType];
+}
+
+function convertFromProxyEndpoint(proxyEndpoint: grpcTypes.ProxyEndpoint.AsObject): IProxyEndpoint {
+ const proxyTypeMap: Record<grpcTypes.ProxyType, ProxyType> = {
+ [grpcTypes.ProxyType.CUSTOM]: 'custom',
+ [grpcTypes.ProxyType.SHADOWSOCKS]: 'shadowsocks',
+ };
+
+ return {
+ ...proxyEndpoint,
+ protocol: convertFromTransportProtocol(proxyEndpoint.protocol),
+ proxyType: proxyTypeMap[proxyEndpoint.proxyType],
+ };
+}
+
+function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefined {
+ const settingsObject = settings.toObject();
+ const bridgeState = convertFromBridgeState(settingsObject.bridgeState!.state!);
+ const relaySettings = convertFromRelaySettings(settings.getRelaySettings())!;
+ const bridgeSettings = convertFromBridgeSettings(settingsObject.bridgeSettings!);
+ const tunnelOptions = convertFromTunnelOptions(settingsObject.tunnelOptions!);
+ return {
+ ...settings.toObject(),
+ bridgeState,
+ relaySettings,
+ bridgeSettings,
+ tunnelOptions,
+ };
+}
+
+function convertFromBridgeState(bridgeState: grpcTypes.BridgeState.State): BridgeState {
+ const bridgeStateMap: Record<grpcTypes.BridgeState.State, BridgeState> = {
+ [grpcTypes.BridgeState.State.AUTO]: 'auto',
+ [grpcTypes.BridgeState.State.ON]: 'on',
+ [grpcTypes.BridgeState.State.OFF]: 'off',
+ };
+
+ return bridgeStateMap[bridgeState];
+}
+
+function convertFromRelaySettings(
+ relaySettings?: grpcTypes.RelaySettings,
+): RelaySettings | undefined {
+ if (relaySettings) {
+ switch (relaySettings.getEndpointCase()) {
+ case grpcTypes.RelaySettings.EndpointCase.ENDPOINT_NOT_SET:
+ return undefined;
+ case grpcTypes.RelaySettings.EndpointCase.CUSTOM: {
+ const custom = relaySettings.getCustom()?.toObject();
+ const config = relaySettings.getCustom()?.getConfig();
+ const connectionConfig = config && convertFromConnectionConfig(config);
+ return (
+ custom &&
+ connectionConfig && {
+ customTunnelEndpoint: {
+ ...custom,
+ config: connectionConfig,
+ },
+ }
+ );
+ }
+ case grpcTypes.RelaySettings.EndpointCase.NORMAL: {
+ const normal = relaySettings.getNormal()!;
+ const grpcLocation = normal.getLocation();
+ const location = grpcLocation
+ ? { only: convertFromLocation(grpcLocation.toObject()) }
+ : 'any';
+ const tunnelProtocol = convertFromTunnelTypeConstraint(normal.getTunnelType()!);
+ const openvpnConstraints = convertFromOpenVpnConstraints(normal.getOpenvpnConstraints()!);
+ const wireguardConstraints = convertFromWireguardConstraints(
+ normal.getWireguardConstraints()!,
+ );
+
+ return {
+ normal: {
+ location,
+ tunnelProtocol,
+ wireguardConstraints,
+ openvpnConstraints,
+ },
+ };
}
- } catch (error) {
- throw new ResponseParseError(`Invalid response from generate_wireguard_key ${error}`);
}
+ } else {
+ return undefined;
}
+}
- public async getWireguardKey(): Promise<IWireguardPublicKey | undefined> {
- const response = await this.transport.send('get_wireguard_key');
- try {
- return validate(maybe(wireguardPublicKey), response) || undefined;
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_wireguard_key');
+function convertFromBridgeSettings(
+ bridgeSettings: grpcTypes.BridgeSettings.AsObject,
+): BridgeSettings {
+ const normalSettings = bridgeSettings.normal;
+ if (normalSettings) {
+ const grpcLocation = normalSettings.location;
+ const location = grpcLocation ? { only: convertFromLocation(grpcLocation) } : 'any';
+ return {
+ normal: {
+ location,
+ },
+ };
+ }
+
+ const customSettings = (settings: ProxySettings): BridgeSettings => {
+ return { custom: settings };
+ };
+
+ const localSettings = bridgeSettings.local;
+ if (localSettings) {
+ return customSettings({
+ port: localSettings?.port!,
+ peer: localSettings?.peer!,
+ });
+ }
+
+ const remoteSettings = bridgeSettings.remote;
+ if (remoteSettings) {
+ return customSettings({
+ address: remoteSettings?.address!,
+ auth: {
+ ...remoteSettings?.auth!,
+ },
+ });
+ }
+
+ const shadowsocksSettings = bridgeSettings.shadowsocks!;
+ return customSettings({
+ peer: shadowsocksSettings.peer!,
+ password: shadowsocksSettings.password!,
+ cipher: shadowsocksSettings.cipher!,
+ });
+}
+
+function convertFromConnectionConfig(
+ connectionConfig: grpcTypes.ConnectionConfig,
+): ConnectionConfig | undefined {
+ const connectionConfigObject = connectionConfig.toObject();
+ switch (connectionConfig.getConfigCase()) {
+ case grpcTypes.ConnectionConfig.ConfigCase.CONFIG_NOT_SET:
+ return undefined;
+ case grpcTypes.ConnectionConfig.ConfigCase.WIREGUARD:
+ return (
+ connectionConfigObject.wireguard &&
+ connectionConfigObject.wireguard.tunnel &&
+ connectionConfigObject.wireguard.peer && {
+ wireguard: {
+ ...connectionConfigObject.wireguard,
+ tunnel: {
+ privateKey: convertFromWireguardKey(
+ connectionConfigObject.wireguard.tunnel.privateKey,
+ ),
+ addresses: connectionConfigObject.wireguard.tunnel.addressesList,
+ },
+ peer: {
+ ...connectionConfigObject.wireguard.peer,
+ addresses: connectionConfigObject.wireguard.peer.allowedIpsList,
+ publicKey: convertFromWireguardKey(connectionConfigObject.wireguard.peer.publicKey),
+ },
+ },
+ }
+ );
+ case grpcTypes.ConnectionConfig.ConfigCase.OPENVPN: {
+ const [ip, port] = connectionConfigObject.openvpn!.address.split(':');
+ return {
+ openvpn: {
+ ...connectionConfigObject.openvpn!,
+ endpoint: {
+ ip,
+ port: parseInt(port, 10),
+ protocol: convertFromTransportProtocol(connectionConfigObject.openvpn!.protocol),
+ },
+ },
+ };
}
}
+}
- public async verifyWireguardKey(): Promise<boolean> {
- const response = await this.transport.send('verify_wireguard_key');
- try {
- return validate(boolean, response);
- } catch (error) {
- throw new ResponseParseError('Invalid response from verify_wireguard_key');
+function convertFromLocation(location: grpcTypes.RelayLocation.AsObject): RelayLocation {
+ if (location.hostname) {
+ return { hostname: [location.country, location.city, location.hostname] };
+ }
+ if (location.city) {
+ return { city: [location.country, location.city] };
+ }
+
+ return { country: location.country };
+}
+
+function convertFromTunnelOptions(tunnelOptions: grpcTypes.TunnelOptions.AsObject): ITunnelOptions {
+ return {
+ openvpn: {
+ mssfix: tunnelOptions.openvpn!.mssfix,
+ },
+ wireguard: {
+ mtu: tunnelOptions.wireguard!.mtu,
+ },
+ generic: {
+ enableIpv6: tunnelOptions.generic!.enableIpv6,
+ },
+ };
+}
+
+function convertFromDaemonEvent(data: grpcTypes.DaemonEvent): DaemonEvent {
+ const tunnelState = data.getTunnelState();
+ if (tunnelState !== undefined) {
+ return { tunnelState: convertFromTunnelState(tunnelState)! };
+ }
+
+ const settings = data.getSettings();
+ if (settings !== undefined) {
+ return { settings: convertFromSettings(settings)! };
+ }
+
+ const relayList = data.getRelayList();
+ if (relayList !== undefined) {
+ return {
+ relayList: {
+ countries: relayList
+ .getCountriesList()
+ ?.map((country: grpcTypes.RelayListCountry) =>
+ convertFromRelayListCountry(country.toObject()),
+ ),
+ },
+ };
+ }
+
+ const keygenEvent = data.getKeyEvent();
+ if (keygenEvent !== undefined) {
+ return {
+ wireguardKey: convertFromKeygenEvent(keygenEvent),
+ };
+ }
+
+ return {
+ appVersionInfo: data.getVersionInfo()!.toObject(),
+ };
+}
+
+function convertFromKeygenEvent(data: grpcTypes.KeygenEvent): KeygenEvent {
+ switch (data.getEvent()) {
+ case grpcTypes.KeygenEvent.KeygenEvent.TOO_MANY_KEYS:
+ return 'too_many_keys';
+ case grpcTypes.KeygenEvent.KeygenEvent.NEW_KEY: {
+ const newKey = data.getNewKey();
+ return newKey
+ ? {
+ newKey: {
+ created: newKey.getCreated()!.toDate().toISOString(),
+ key: convertFromWireguardKey(newKey.getKey()),
+ },
+ }
+ : 'generation_failure';
}
+ case grpcTypes.KeygenEvent.KeygenEvent.GENERATION_FAILURE:
+ return 'generation_failure';
}
+}
- public async getVersionInfo(): Promise<IAppVersionInfo> {
- const response = await this.transport.send('get_version_info', [], NETWORK_CALL_TIMEOUT);
- try {
- return snakeCaseToCamelCase(validate(appVersionInfoSchema, response));
- } catch (error) {
- throw new ResponseParseError('Invalid response from get_version_info');
+function convertFromOpenVpnConstraints(
+ constraints: grpcTypes.OpenvpnConstraints,
+): IOpenVpnConstraints {
+ const port = convertFromConstraint(constraints.getPort());
+ let protocol: Constraint<RelayProtocol> = 'any';
+ switch (constraints.getProtocol()?.getProtocol()) {
+ case grpcTypes.TransportProtocol.TCP:
+ protocol = { only: 'tcp' };
+ break;
+ case grpcTypes.TransportProtocol.UDP:
+ protocol = { only: 'udp' };
+ break;
+ }
+
+ return { port, protocol };
+}
+
+function convertFromWireguardConstraints(
+ constraints: grpcTypes.WireguardConstraints,
+): IWireguardConstraints {
+ const port = convertFromConstraint(constraints.getPort());
+ return { port };
+}
+
+function convertFromTunnelTypeConstraint(
+ constraint: grpcTypes.TunnelTypeConstraint | undefined,
+): Constraint<TunnelProtocol> {
+ switch (constraint?.getTunnelType()) {
+ case grpcTypes.TunnelType.WIREGUARD: {
+ return { only: 'wireguard' };
+ }
+ case grpcTypes.TunnelType.OPENVPN: {
+ return { only: 'openvpn' };
}
+ default: {
+ return 'any';
+ }
+ }
+}
+
+function convertFromConstraint<T>(value: T | undefined): Constraint<T> {
+ if (value) {
+ return { only: value };
+ } else {
+ return 'any';
+ }
+}
+
+function convertToNormalBridgeSettings(
+ constraints: IBridgeConstraints,
+): grpcTypes.BridgeSettings.BridgeConstraints {
+ const normalBridgeSettings = new grpcTypes.BridgeSettings.BridgeConstraints();
+ normalBridgeSettings.setLocation(convertToLocation(liftConstraint(constraints.location)));
+
+ return normalBridgeSettings;
+}
+
+function convertToLocation(
+ constraint: RelayLocation | undefined,
+): grpcTypes.RelayLocation | undefined {
+ const location = new grpcTypes.RelayLocation();
+ if (constraint && 'hostname' in constraint) {
+ const [countryCode, cityCode, hostname] = constraint.hostname;
+ location.setCountry(countryCode);
+ location.setCity(cityCode);
+ location.setHostname(hostname);
+ return location;
+ } else if (constraint && 'city' in constraint) {
+ location.setCountry(constraint.city[0]);
+ location.setCity(constraint.city[1]);
+ return location;
+ } else if (constraint && 'country' in constraint) {
+ location.setCountry(constraint.country);
+ return location;
+ } else {
+ return undefined;
+ }
+}
+
+function convertToTunnelTypeConstraint(
+ constraint: Constraint<TunnelType>,
+): grpcTypes.TunnelTypeConstraint | undefined {
+ const grpcConstraint = new grpcTypes.TunnelTypeConstraint();
+
+ if (constraint !== undefined && constraint !== 'any' && 'only' in constraint) {
+ switch (constraint.only) {
+ case 'wireguard':
+ grpcConstraint.setTunnelType(grpcTypes.TunnelType.WIREGUARD);
+ return grpcConstraint;
+ case 'openvpn':
+ grpcConstraint.setTunnelType(grpcTypes.TunnelType.OPENVPN);
+ return grpcConstraint;
+ }
+ }
+ return undefined;
+}
+
+function convertToOpenVpnConstraints(
+ constraints: Partial<IOpenVpnConstraints> | undefined,
+): grpcTypes.OpenvpnConstraints | undefined {
+ const openvpnConstraints = new grpcTypes.OpenvpnConstraints();
+ if (constraints) {
+ const port = liftConstraint(constraints.port);
+ if (port) {
+ openvpnConstraints.setPort(port);
+ }
+ const protocol = liftConstraint(constraints.protocol);
+ if (protocol) {
+ const transportConstraint = new grpcTypes.TransportProtocolConstraint();
+ transportConstraint.setProtocol(convertToTransportProtocol(protocol));
+ openvpnConstraints.setProtocol(transportConstraint);
+ }
+ return openvpnConstraints;
+ }
+
+ return undefined;
+}
+
+function convertToWireguardConstraints(
+ constraint: Partial<IWireguardConstraints> | undefined,
+): grpcTypes.WireguardConstraints | undefined {
+ if (constraint) {
+ const wireguardConstraints = new grpcTypes.WireguardConstraints();
+ const port = liftConstraint(constraint.port);
+ if (port) {
+ wireguardConstraints.setPort(port);
+ }
+ return wireguardConstraints;
+ }
+ return undefined;
+}
+
+function convertToTransportProtocol(protocol: RelayProtocol): grpcTypes.TransportProtocol {
+ switch (protocol) {
+ case 'udp':
+ return grpcTypes.TransportProtocol.UDP;
+ case 'tcp':
+ return grpcTypes.TransportProtocol.TCP;
+ }
+}
+
+function ensureExists<T>(value: T | undefined, errorMessage: string): T {
+ if (value) {
+ return value;
}
+ throw new ResponseParseError(errorMessage);
}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 53ee113410..3ad81f453d 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -44,12 +44,7 @@ import consumePromise from '../shared/promise';
import { Scheduler } from '../shared/scheduler';
import AccountDataCache from './account-data-cache';
import { getOpenAtLogin, setOpenAtLogin } from './autostart';
-import {
- ConnectionObserver,
- DaemonRpc,
- ResponseParseError,
- SubscriptionListener,
-} from './daemon-rpc';
+import { ConnectionObserver, DaemonRpc, SubscriptionListener } from './daemon-rpc';
import { InvalidAccountError } from './errors';
import Expectation from './expectation';
import GuiSettings from './gui-settings';
@@ -63,7 +58,7 @@ import WindowController from './window-controller';
const linuxSplitTunneling = process.platform === 'linux' && require('./linux-split-tunneling');
const DAEMON_RPC_PATH =
- process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn';
+ process.platform === 'win32' ? 'unix:////./pipe/Mullvad VPN' : 'unix:///var/run/mullvad-vpn';
const AUTO_CONNECT_FALLBACK_DELAY = 6000;
@@ -95,7 +90,7 @@ class ApplicationMain {
private windowController?: WindowController;
private trayIconController?: TrayIconController;
- private daemonRpc = new DaemonRpc();
+ private daemonRpc = new DaemonRpc(DAEMON_RPC_PATH);
private daemonEventListener?: SubscriptionListener<DaemonEvent>;
private reconnectBackoff = new ReconnectionBackoff();
private connectedToDaemon = false;
@@ -138,7 +133,6 @@ class ApplicationMain {
},
wireguard: {
mtu: undefined,
- fwmark: undefined,
},
},
};
@@ -310,7 +304,7 @@ class ApplicationMain {
// Unsubscribe the event handler
try {
if (this.daemonEventListener) {
- await this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener);
+ this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener);
log.info('Unsubscribed from the daemon events');
}
@@ -326,6 +320,8 @@ class ApplicationMain {
if (process.platform === 'darwin' && this.windowController) {
this.windowController.window.closable = true;
}
+
+ this.daemonRpc.disconnect();
}
private detectLocale(): string {
@@ -419,7 +415,7 @@ class ApplicationMain {
// subscribe to events
try {
- this.daemonEventListener = await this.subscribeEvents();
+ this.daemonEventListener = this.subscribeEvents();
} catch (error) {
log.error(`Failed to subscribe: ${error.message}`);
@@ -500,6 +496,9 @@ class ApplicationMain {
};
private onDaemonDisconnected = (error?: Error) => {
+ if (this.daemonEventListener) {
+ this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener);
+ }
// make sure we were connected before to distinguish between a failed attempt to reconnect and
// connection loss.
const wasConnected = this.connectedToDaemon;
@@ -507,6 +506,9 @@ class ApplicationMain {
// Reset the daemon event listener since it's going to be invalidated on disconnect
this.daemonEventListener = undefined;
+ // TODO: GRPC doesn't set an error, but without an error, the UI won't be updated
+ error = error === undefined && wasConnected ? new Error('Connection to daemon lost') : error;
+
if (wasConnected) {
this.connectedToDaemon = false;
@@ -529,32 +531,24 @@ class ApplicationMain {
} else {
log.error(`Failed to connect to daemon: ${error.message}`);
}
-
- this.reconnectToDaemon();
} else {
log.info('Disconnected from the daemon');
}
};
private connectToDaemon() {
- consumePromise(this.daemonRpc.connect({ path: DAEMON_RPC_PATH }));
- }
-
- private reconnectToDaemon() {
- this.reconnectBackoff.attempt(() => {
- this.connectToDaemon();
- });
+ consumePromise(this.daemonRpc.connect());
}
private recoverFromBootstrapError(_error?: Error) {
// Attempt to reconnect to daemon if the program fails to fetch settings, tunnel state or
// subscribe for RPC events.
- this.daemonRpc.disconnect();
-
- this.reconnectToDaemon();
+ if (this.daemonEventListener) {
+ this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener);
+ }
}
- private async subscribeEvents(): Promise<SubscriptionListener<DaemonEvent>> {
+ private subscribeEvents(): SubscriptionListener<DaemonEvent> {
const daemonEventListener = new SubscriptionListener(
(daemonEvent: DaemonEvent) => {
if ('tunnelState' in daemonEvent) {
@@ -575,14 +569,11 @@ class ApplicationMain {
},
(error: Error) => {
log.error(`Cannot deserialize the daemon event: ${error.message}`);
-
- if (error instanceof ResponseParseError && error.validationError) {
- log.error(error.validationError.message);
- }
+ log.error(error.stack);
},
);
- await this.daemonRpc.subscribeDaemonEventListener(daemonEventListener);
+ this.daemonRpc.subscribeDaemonEventListener(daemonEventListener);
return daemonEventListener;
}
diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx
index daf5074603..7ca83c43d1 100644
--- a/gui/src/renderer/components/RedeemVoucher.tsx
+++ b/gui/src/renderer/components/RedeemVoucher.tsx
@@ -86,7 +86,7 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) {
if (response.type === 'success') {
setValue('');
closeScheduler.schedule(() => {
- updateAccountExpiry(response.new_expiry);
+ updateAccountExpiry(response.newExpiry);
onSuccess?.();
}, 1000);
} else {
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 1f1779a4b0..78b6f60c17 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -46,14 +46,14 @@ export type ErrorStateCause =
export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
-export type TunnelType = 'wireguard' | 'openvpn';
+export type TunnelType = 'any' | 'wireguard' | 'openvpn';
export function tunnelTypeToString(tunnel: TunnelType): string {
switch (tunnel) {
case 'wireguard':
return 'WireGuard';
case 'openvpn':
return 'OpenVPN';
- default:
+ case 'any':
return '';
}
}
@@ -235,14 +235,16 @@ export interface IOpenVpnTunnelData {
}
export interface IWireguardTunnelData {
- // Port ranges are an array of pairs, such as [[53,53], [10_000, 60_000]],
- // which in this case translates that the specific tunnel can be connected on
- // port 53 and ports 10'000 through 60'000.
- portRanges: Array<[number, number]>;
+ portRanges: Array<IPortRange>;
// Public key of the tunnel.
publicKey: string;
}
+export interface IPortRange {
+ first: number;
+ last: number;
+}
+
export interface IShadowsocksEndpointData {
port: number;
cipher: string;
@@ -256,8 +258,6 @@ export interface ITunnelOptions {
};
wireguard: {
mtu?: number;
- // Only relevant on Linux
- fwmark?: number;
};
generic: {
enableIpv6: boolean;
@@ -330,7 +330,7 @@ export interface ISocketAddress {
}
export type VoucherResponse =
- | { type: 'success'; new_expiry: string }
+ | { type: 'success'; newExpiry: string; secondsAdded: number }
| { type: 'invalid' | 'already_used' | 'error' };
export enum VoucherErrorCode {
diff --git a/gui/tasks/scripts.js b/gui/tasks/scripts.js
index 82896dac21..5ee33a9071 100644
--- a/gui/tasks/scripts.js
+++ b/gui/tasks/scripts.js
@@ -1,4 +1,5 @@
-const { src, dest } = require('gulp');
+const { exec } = require('child_process');
+const { src, dest, series } = require('gulp');
const ts = require('gulp-typescript');
const inject = require('gulp-inject-string');
const TscWatchClient = require('tsc-watch/client');
@@ -25,7 +26,13 @@ function compileScripts() {
.pipe(dest('build'));
}
+function buildProto(callback) {
+ exec('bash ./scripts/build-proto.sh', (err) => callback(err));
+}
+
compileScripts.displayName = 'compile-scripts';
+buildProto.displayName = 'build-proto';
exports.build = compileScripts;
+exports.buildProto = buildProto;
exports.makeWatchCompiler = makeWatchCompiler;
diff --git a/mullvad-cli/Cargo.toml b/mullvad-cli/Cargo.toml
index 084be415fe..1746993bff 100644
--- a/mullvad-cli/Cargo.toml
+++ b/mullvad-cli/Cargo.toml
@@ -25,11 +25,13 @@ 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" }
+
+mullvad-management-interface = { path = "../mullvad-management-interface" }
+futures03 = { package = "futures", version = "0.3", features = [ "compat" ]}
+tokio = { version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream"] }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
diff --git a/mullvad-cli/src/cmds/account.rs b/mullvad-cli/src/cmds/account.rs
index e35699605c..ef5c117c41 100644
--- a/mullvad-cli/src/cmds/account.rs
+++ b/mullvad-cli/src/cmds/account.rs
@@ -1,9 +1,11 @@
-use crate::{new_rpc_client, Command, Result};
+use crate::{new_rpc_client, Command, Error, Result};
use clap::value_t_or_exit;
-use mullvad_types::account::{AccountToken, VoucherError};
+use mullvad_management_interface::{types::Timestamp, Code};
+use mullvad_types::account::AccountToken;
pub struct Account;
+#[mullvad_management_interface::async_trait]
impl Command for Account {
fn name(&self) -> &'static str {
"account"
@@ -49,21 +51,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 +73,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_rpc_client().await?;
+ rpc.set_account(token.clone().unwrap_or_default()).await?;
if let Some(token) = token {
println!("Mullvad account \"{}\" set", token);
} else {
@@ -82,45 +84,57 @@ 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_rpc_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_rpc_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_rpc_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))
- );
- Err(err.into())
+ match err.code() {
+ Code::NotFound | Code::ResourceExhausted => {
+ eprintln!("Failed to submit voucher: {}", err.message());
+ }
+ _ => return Err(Error::GrpcClientError(err)),
+ }
+ std::process::exit(1);
}
}
}
@@ -138,16 +152,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: &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_rpc_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..40f8f2bd9e 100644
--- a/mullvad-cli/src/cmds/auto_connect.rs
+++ b/mullvad-cli/src/cmds/auto_connect.rs
@@ -3,6 +3,7 @@ use clap::value_t_or_exit;
pub struct AutoConnect;
+#[mullvad_management_interface::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_rpc_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_rpc_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..73a0ba15c7 100644
--- a/mullvad-cli/src/cmds/beta_program.rs
+++ b/mullvad-cli/src/cmds/beta_program.rs
@@ -3,6 +3,7 @@ use clap::value_t_or_exit;
pub struct BetaProgram;
+#[mullvad_management_interface::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_rpc_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_rpc_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..3cb1baff5a 100644
--- a/mullvad-cli/src/cmds/block_when_disconnected.rs
+++ b/mullvad-cli/src/cmds/block_when_disconnected.rs
@@ -3,6 +3,7 @@ use clap::value_t_or_exit;
pub struct BlockWhenDisconnected;
+#[mullvad_management_interface::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_rpc_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_rpc_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..3118c09a39 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 clap::value_t;
-use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings, BridgeState};
-use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS};
+use mullvad_management_interface::types::{
+ 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;
+#[mullvad_management_interface::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_rpc_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_rpc_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_rpc_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_rpc_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_rpc_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_rpc_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_rpc_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..6fc7072580 100644
--- a/mullvad-cli/src/cmds/connect.rs
+++ b/mullvad-cli/src/cmds/connect.rs
@@ -3,6 +3,7 @@ use talpid_types::ErrorExt;
pub struct Connect;
+#[mullvad_management_interface::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_rpc_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..f976b96fab 100644
--- a/mullvad-cli/src/cmds/disconnect.rs
+++ b/mullvad-cli/src/cmds/disconnect.rs
@@ -2,6 +2,7 @@ use crate::{new_rpc_client, Command, Result};
pub struct Disconnect;
+#[mullvad_management_interface::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_rpc_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..dd84851f33 100644
--- a/mullvad-cli/src/cmds/lan.rs
+++ b/mullvad-cli/src/cmds/lan.rs
@@ -3,6 +3,7 @@ use clap::value_t_or_exit;
pub struct Lan;
+#[mullvad_management_interface::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_rpc_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_rpc_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..ecc0b089c0 100644
--- a/mullvad-cli/src/cmds/reconnect.rs
+++ b/mullvad-cli/src/cmds/reconnect.rs
@@ -3,6 +3,7 @@ use talpid_types::ErrorExt;
pub struct Reconnect;
+#[mullvad_management_interface::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_rpc_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..b23f8638d3 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -6,19 +6,19 @@ 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_management_interface::types::{
+ connection_config::{self, OpenvpnConfig, WireguardConfig},
+ relay_settings, relay_settings_update, ConnectionConfig, CustomRelaySettings,
+ NormalRelaySettingsUpdate, OpenvpnConstraints, RelaySettingsUpdate, TransportProtocol,
+ TransportProtocolConstraint, TunnelType, TunnelTypeConstraint, TunnelTypeUpdate,
+ WireguardConstraints,
};
+use mullvad_types::relay_constraints::Constraint;
+use talpid_types::net::all_of_the_internet;
pub struct Relay;
+#[mullvad_management_interface::async_trait]
impl Command for Relay {
fn name(&self) -> &'static str {
"relay"
@@ -155,15 +155,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 +171,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_rpc_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 +258,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 +310,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 +334,161 @@ 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.option().map(|protocol| {
+ TransportProtocolConstraint {
+ protocol: protocol 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" => Some(TunnelType::Wireguard),
+ "openvpn" => Some(TunnelType::Openvpn),
+ "any" => None,
_ => 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.map(|tunnel_type| TunnelTypeConstraint {
+ 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_rpc_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 settings.tunnel_type {
+ None => {
+ 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())
+ );
+ }
+ Some(constraint) => match TunnelType::from_i32(constraint.tunnel_type).unwrap() {
+ 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(Some(
+ 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_rpc_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 +501,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 +521,52 @@ impl Relay {
Ok(())
}
- fn update(&self) -> Result<()> {
- new_rpc_client()?.update_relay_locations()?;
+ async fn update(&self) -> Result<()> {
+ new_rpc_client().await?.update_relay_locations(()).await?;
println!("Updating relay list in the background...");
Ok(())
}
+
+ fn format_transport_protocol(protocol: Option<TransportProtocol>) -> &'static str {
+ match protocol {
+ None => "any transport protocol",
+ Some(TransportProtocol::Udp) => "UDP",
+ Some(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(
+ constraints
+ .protocol
+ .clone()
+ .map(|protocol| TransportProtocol::from_i32(protocol.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..bb10a3f0fe 100644
--- a/mullvad-cli/src/cmds/reset.rs
+++ b/mullvad-cli/src/cmds/reset.rs
@@ -2,6 +2,7 @@ use crate::{new_rpc_client, Command, Result};
use std::io::stdin;
pub struct Reset;
+#[mullvad_management_interface::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_rpc_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..5903d84f68 100644
--- a/mullvad-cli/src/cmds/split_tunnel/linux.rs
+++ b/mullvad-cli/src/cmds/split_tunnel/linux.rs
@@ -3,6 +3,7 @@ use clap::value_t_or_exit;
pub struct SplitTunnel;
+#[mullvad_management_interface::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_rpc_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_rpc_client()
+ .await?
+ .remove_split_tunnel_process(pid)
+ .await?;
Ok(())
}
("clear", Some(_)) => {
- new_rpc_client()?.clear_split_tunnel_processes()?;
+ new_rpc_client()
+ .await?
+ .clear_split_tunnel_processes(())
+ .await?;
Ok(())
}
("list", Some(_)) => {
- let pids = new_rpc_client()?.get_split_tunnel_processes()?;
+ let mut pids_stream = new_rpc_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..b0c4d40ad0 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -1,11 +1,21 @@
-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_rpc_client, Command, Error, Result};
+use mullvad_management_interface::{
+ types::{
+ daemon_event::Event as EventType,
+ error_state::{
+ firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause,
+ FirewallPolicyError, GenerationError,
+ },
+ ErrorState, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType,
+ },
+ ManagementServiceClient,
+};
+use mullvad_types::auth_failed::AuthFailed;
+use std::fmt::Write;
pub struct Status;
+#[mullvad_management_interface::async_trait]
impl Command for Status {
fn name(&self) -> &'static str {
"status"
@@ -31,127 +41,234 @@ 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_rpc_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 mullvad_management_interface::types::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 mullvad_management_interface::types::{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",
+ },
+ 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.blocking_error.is_some() {
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) -> Result<()> {
+ let location = rpc.get_current_location(()).await;
+ let location = match location {
+ Ok(response) => response.into_inner(),
+ Err(status) => {
+ if status.code() == mullvad_management_interface::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 +278,10 @@ fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> {
);
Ok(())
}
+
+fn format_protocol(protocol: TransportProtocol) -> &'static str {
+ match protocol {
+ TransportProtocol::Udp => "UDP",
+ TransportProtocol::Tcp => "TCP",
+ }
+}
diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs
index 5349174d4c..528ebd005e 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_rpc_client, Command, Error, Result};
use clap::value_t;
-
-use mullvad_types::settings::TunnelOptions;
+use mullvad_management_interface::types::{Timestamp, TunnelOptions};
pub struct Tunnel;
+#[mullvad_management_interface::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,152 @@ 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_rpc_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_rpc_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_rpc_client().await?;
+ let key = rpc.get_wireguard_key(()).await;
+ let key = match key {
+ Ok(response) => Some(response.into_inner()),
+ Err(status) => {
+ if status.code() == mullvad_management_interface::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_rpc_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()?;
- println!(
- "Rotation interval: {} hour(s)",
- tunnel_options
- .wireguard
- .automatic_rotation
- .map(|interval| interval.to_string())
- .unwrap_or_else(|| "default".to_owned())
- );
+ async fn process_wireguard_rotation_interval_get() -> Result<()> {
+ let tunnel_options = Self::get_tunnel_options().await?;
+ match tunnel_options.wireguard.unwrap().automatic_rotation {
+ Some(interval) => {
+ println!("Rotation interval: {} hour(s)", interval.interval);
+ }
+ None => println!("Rotation interval: default"),
+ }
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_rpc_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_rpc_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_rpc_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_rpc_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_rpc_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 +303,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_rpc_client().await?;
+ rpc.set_enable_ipv6(enabled).await?;
+ if enabled {
+ println!("Enabled IPv6");
+ } else {
+ println!("Disabled IPv6");
+ }
Ok(())
}
+
+ fn format_key_timestamp(timestamp: &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..0d66411283 100644
--- a/mullvad-cli/src/cmds/version.rs
+++ b/mullvad-cli/src/cmds/version.rs
@@ -2,6 +2,7 @@ use crate::{new_rpc_client, Command, Result};
pub struct Version;
+#[mullvad_management_interface::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_rpc_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..4f69d05317
--- /dev/null
+++ b/mullvad-cli/src/format.rs
@@ -0,0 +1,20 @@
+use mullvad_management_interface::types::KeygenEvent;
+
+pub fn print_keygen_event(key_event: &KeygenEvent) {
+ use mullvad_management_interface::types::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..21e6a1c35c 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 mullvad_management_interface::types::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..356a50ce69 100644
--- a/mullvad-cli/src/main.rs
+++ b/mullvad-cli/src/main.rs
@@ -1,11 +1,14 @@
#![deny(rust_2018_idioms)]
use clap::{crate_authors, crate_description};
-use mullvad_ipc_client::{new_standalone_ipc_client, DaemonRpcClient};
+use mullvad_management_interface::async_trait;
use std::{collections::HashMap, io};
use talpid_types::ErrorExt;
+pub use mullvad_management_interface::{self, new_rpc_client};
+
mod cmds;
+mod format;
mod location;
pub const BIN_NAME: &str = "mullvad";
@@ -18,26 +21,20 @@ 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 = "Management interface error")]
+ ManagementInterfaceError(#[error(source)] mullvad_management_interface::Error),
#[error(display = "Failed to communicate with mullvad-daemon over RPC")]
- RpcClientError(#[error(source)] mullvad_ipc_client::Error),
+ GrpcClientError(#[error(source)] mullvad_management_interface::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),
- }
-}
-
-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 +44,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 +82,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 +106,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..f752b677a3 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,15 @@ 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" }
+mullvad-management-interface = { path = "../mullvad-management-interface" }
+triggered = "0.1.1"
+
[target.'cfg(target_os="android")'.dependencies]
android_logger = "0.8"
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..c8ec8445d0 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -1,892 +1,1667 @@
-use crate::{BoxFuture, DaemonCommand, DaemonCommandSender, EventListener};
-use jsonrpc_core::{
- futures::{future, sync, Future},
- Error, ErrorCode, MetaIoHandler, Metadata,
+use crate::{DaemonCommand, DaemonCommandSender, EventListener};
+use futures::compat::Future01CompatExt;
+use futures01::{future, sync, Future};
+use mullvad_management_interface::{
+ types::{self, daemon_event, management_service_server::ManagementService},
+ Code, Request, Response, Status,
};
-use jsonrpc_ipc_server;
-use jsonrpc_macros::{build_rpc_trait, metadata, pubsub};
-use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId};
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,
+ 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>;
-
- /// 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>;
-
- /// Verify if current wireguard key is still valid
- #[rpc(meta, name = "verify_wireguard_key")]
- fn verify_wireguard_key(&self, Self::Metadata) -> BoxFuture<bool, Error>;
- /// 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(#[error(source)] mullvad_management_interface::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 start the tokio runtime
+ #[error(display = "Failed to create the tokio runtime")]
+ TokioRuntimeError(#[error(source)] tokio02::io::Error),
+}
- /// Remove all configuration and cache files
- #[rpc(meta, name = "factory_reset")]
- fn factory_reset(&self, Self::Metadata) -> BoxFuture<(), Error>;
+struct ManagementServiceImpl {
+ daemon_tx: DaemonCommandSender,
+ subscriptions: Arc<RwLock<Vec<EventsListenerSender>>>,
+}
- /// 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>;
+pub type ServiceResult<T> = std::result::Result<Response<T>, Status>;
+type EventsListenerReceiver =
+ tokio02::sync::mpsc::UnboundedReceiver<Result<types::DaemonEvent, Status>>;
+type EventsListenerSender =
+ tokio02::sync::mpsc::UnboundedSender<Result<types::DaemonEvent, Status>>;
- /// 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>;
+const INVALID_ACCOUNT_TOKEN_MESSAGE: &str = "No valid account token configured";
+const INVALID_VOUCHER_MESSAGE: &str = "This voucher code is invalid";
+const USED_VOUCHER_MESSAGE: &str = "This voucher code has already been used";
- /// 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>;
+#[mullvad_management_interface::async_trait]
+impl ManagementService for ManagementServiceImpl {
+ type GetRelayLocationsStream =
+ tokio02::sync::mpsc::Receiver<Result<types::RelayListCountry, Status>>;
+ type GetSplitTunnelProcessesStream =
+ tokio02::sync::mpsc::UnboundedReceiver<Result<i32, 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(|_| Status::internal("internal error")))
+ .and_then(|result| match result {
+ Ok(()) => Ok(Response::new(())),
+ Err(()) => Err(Status::new(
+ Code::Unauthenticated,
+ INVALID_ACCOUNT_TOKEN_MESSAGE,
+ )),
+ })
+ .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();
-
- 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,
- })
- }
+ async fn disconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> {
+ log::debug!("disconnect_tunnel");
- pub fn socket_path(&self) -> &str {
- self.server.path()
+ let (tx, _) = sync::oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Unsecured))
+ .then(|_| Ok(Response::new(())))
+ .compat()
+ .await
}
- pub fn event_broadcaster(&self) -> ManagementInterfaceEventBroadcaster {
- ManagementInterfaceEventBroadcaster {
- subscriptions: self.subscriptions.clone(),
- close_handle: Some(self.server.close_handle()),
- }
+ async fn reconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> {
+ log::debug!("reconnect_tunnel");
+ self.send_command_to_daemon(DaemonCommand::Reconnect)
+ .map(Response::new)
+ .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()
+ async fn get_tunnel_state(&self, _: Request<()>) -> ServiceResult<types::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(|_| Status::internal("internal error")))
+ .and_then(|state| Ok(Response::new(convert_state(state))))
+ .compat()
+ .await
}
-}
-/// 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>,
-}
+ // Control the daemon and receive events
+ //
-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(|_| 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(|_| 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<types::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(|_| 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<types::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(|_| 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(|_| 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<types::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(|_| Status::internal("internal error")))
+ .and_then(|geoip| {
+ if let Some(geoip) = geoip {
+ Ok(Response::new(convert_geoip_location(geoip)))
+ } else {
+ Err(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<types::BridgeSettings>,
+ ) -> ServiceResult<()> {
+ use talpid_types::net;
+ use types::bridge_settings::Type as BridgeSettingType;
- &mullvad_rpc::VOUCHER_USED => Error {
- code: ErrorCode::from(VOUCHER_USED_ALREADY_CODE),
- message,
- data: None,
- },
+ let settings = request
+ .into_inner()
+ .r#type
+ .ok_or(Status::invalid_argument("no settings provided"))?;
- _ => Error::internal_error(),
- }
- }
- _ => Error::internal_error(),
+ 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,
})
- });
- Box::new(future)
- }
+ }
+ BridgeSettingType::Local(proxy_settings) => {
+ let peer = proxy_settings
+ .peer
+ .parse()
+ .map_err(|_| 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(|_| 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(|_| 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(|_| Status::internal("internal error")))
+ .and_then(|settings_result| {
+ settings_result.map_err(|_| 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<types::BridgeState>) -> ServiceResult<()> {
+ use types::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(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(|_| Status::internal("internal error")))
+ .and_then(|settings_result| {
+ settings_result.map_err(|_| 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<types::Settings> {
+ log::debug!("get_settings");
+ let (tx, rx) = sync::oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::GetSettings(tx))
+ .and_then(|_| rx.map_err(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| Status::internal("internal error")))
+ .and_then(|result| match result {
+ Ok(account_token) => Ok(Response::new(account_token)),
+ Err(_) => Err(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(|_| 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<types::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(|_| Status::internal("internal error")))
+ .and_then(|rpc_future| {
+ rpc_future
+ .map(|account_data| {
+ Response::new(types::AccountData {
+ expiry: Some(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<types::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(|_| Status::internal("internal error")))
+ .map(|history| Response::new(types::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(|_| 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(|_| 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(|_| 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<types::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(|_| Status::internal("internal error")))
+ .and_then(|f| {
+ f.map(|submission| {
+ Response::new(types::VoucherSubmission {
+ seconds_added: submission.time_added,
+ new_expiry: Some(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 => {
+ Status::new(Code::NotFound, INVALID_VOUCHER_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 => {
+ Status::new(Code::ResourceExhausted, USED_VOUCHER_MESSAGE)
+ }
- Box::new(future)
+ _ => Status::internal("internal error"),
+ }
+ }
+ _ => 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()));
+ // WireGuard key management
+ //
- Box::new(future)
- }
+ async fn set_wireguard_rotation_interval(&self, request: Request<u32>) -> ServiceResult<()> {
+ let interval = request.into_inner();
- /// 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)
- }
-
- /// 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(|_| 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(|_| 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<types::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(|_| 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<types::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(|_| Status::internal("internal error")))
+ .then(|response| match response {
+ Ok(Some(key)) => Ok(Response::new(convert_public_key(&key))),
+ Ok(None) => Err(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)
+ self.send_command_to_daemon(DaemonCommand::VerifyWireguardKey(tx))
+ .and_then(|_| rx.map_err(|_| Status::internal("internal error")))
+ .map(Response::new)
+ .compat()
+ .await
}
- 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()));
+ // Split tunneling
+ //
- Box::new(future)
- }
-
- 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(()))
- }
- }
-
- 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(|_| 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(|_| 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(|_| 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(|_| 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 = Status> {
+ future::result(
+ self.daemon_tx
+ .send(command)
+ .map_err(|_| Status::internal("internal error")),
+ )
+ }
+}
+
+fn convert_settings(settings: &Settings) -> types::Settings {
+ types::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: &types::RelaySettingsUpdate,
+) -> Result<RelaySettingsUpdate, Status> {
+ use mullvad_types::CustomTunnelEndpoint;
+ use talpid_types::net::{self, openvpn, wireguard};
+ use types::{
+ connection_config::Config as ProtoConnectionConfig,
+ relay_settings_update::Type as ProtoUpdateType,
+ };
+
+ let update_value = settings
+ .r#type
+ .clone()
+ .ok_or(Status::invalid_argument("missing relay settings"))?;
+
+ match update_value {
+ ProtoUpdateType::Custom(settings) => {
+ let config = settings
+ .config
+ .ok_or(Status::invalid_argument("missing relay settings"))?;
+ let config = config
+ .config
+ .ok_or(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(Status::invalid_argument("invalid address")),
+ };
+
+ ConnectionConfig::OpenVpn(openvpn::ConnectionConfig {
+ endpoint: net::Endpoint {
+ address,
+ protocol: match types::TransportProtocol::from_i32(config.protocol) {
+ Some(types::TransportProtocol::Udp) => TransportProtocol::Udp,
+ Some(types::TransportProtocol::Tcp) => TransportProtocol::Tcp,
+ None => {
+ return Err(Status::invalid_argument(
+ "invalid transport protocol",
+ ))
+ }
+ },
+ },
+ username: config.username.clone(),
+ password: config.password.clone(),
+ })
+ }
+ ProtoConnectionConfig::Wireguard(config) => {
+ let tunnel = config
+ .tunnel
+ .ok_or(Status::invalid_argument("missing tunnel config"))?;
+
+ // Copy the private key to an array
+ if tunnel.private_key.len() != 32 {
+ return Err(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(Status::invalid_argument("missing peer config"))?;
+
+ // Copy the public key to an array
+ if peer.public_key.len() != 32 {
+ return Err(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(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(Status::invalid_argument("invalid IPv6 gateway")),
+ };
+ Some(address)
+ } else {
+ None
+ };
+
+ let endpoint = match peer.endpoint.parse() {
+ Ok(address) => address,
+ Err(_) => return Err(Status::invalid_argument("invalid peer address")),
+ };
+
+ let mut tunnel_addresses = Vec::new();
+ for address in tunnel.addresses {
+ let address = address
+ .parse()
+ .map_err(|_| 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(|_| 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,
+ })
}
- break;
+ };
+
+ 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 update.tunnel_type {
+ Some(constraint) => match types::TunnelType::from_i32(constraint.tunnel_type) {
+ Some(types::TunnelType::Openvpn) => {
+ Some(Constraint::Only(TunnelType::OpenVpn))
+ }
+ Some(types::TunnelType::Wireguard) => {
+ Some(Constraint::Only(TunnelType::Wireguard))
+ }
+ None => return Err(Status::invalid_argument("unknown tunnel protocol")),
+ },
+ None => Some(Constraint::Any),
+ }
+ } else {
+ None
+ };
+
+ let transport_protocol = if let Some(ref constraints) = settings.openvpn_constraints {
+ match &constraints.protocol {
+ Some(constraint) => {
+ match types::TransportProtocol::from_i32(constraint.protocol) {
+ Some(types::TransportProtocol::Udp) => Some(TransportProtocol::Udp),
+ Some(types::TransportProtocol::Tcp) => Some(TransportProtocol::Tcp),
+ None => {
+ return Err(Status::invalid_argument("unknown transport protocol"))
+ }
+ }
+ }
+ None => None,
+ }
+ } 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: Constraint::from(transport_protocol),
+ }
+ }),
+ }))
+ }
+ }
+}
+
+fn convert_relay_settings(settings: &RelaySettings) -> types::RelaySettings {
+ use types::relay_settings;
+
+ let endpoint = match settings {
+ RelaySettings::CustomTunnelEndpoint(endpoint) => {
+ relay_settings::Endpoint::Custom(types::CustomRelaySettings {
+ host: endpoint.host.clone(),
+ config: Some(convert_connection_config(&endpoint.config)),
+ })
+ }
+ RelaySettings::Normal(constraints) => {
+ relay_settings::Endpoint::Normal(types::NormalRelaySettings {
+ location: convert_location_constraint(&constraints.location),
+ tunnel_type: match constraints.tunnel_protocol {
+ Constraint::Any => None,
+ Constraint::Only(TunnelType::Wireguard) => Some(types::TunnelType::Wireguard),
+ Constraint::Only(TunnelType::OpenVpn) => Some(types::TunnelType::Openvpn),
+ }
+ .map(|tunnel_type| types::TunnelTypeConstraint {
+ tunnel_type: i32::from(tunnel_type),
+ }),
+
+ wireguard_constraints: Some(types::WireguardConstraints {
+ port: u32::from(constraints.wireguard_constraints.port.unwrap_or(0)),
+ }),
+
+ openvpn_constraints: Some(types::OpenvpnConstraints {
+ port: u32::from(constraints.openvpn_constraints.port.unwrap_or(0)),
+ protocol: constraints
+ .openvpn_constraints
+ .protocol
+ .as_ref()
+ .option()
+ .map(|protocol| match protocol {
+ TransportProtocol::Tcp => types::TransportProtocol::Tcp,
+ TransportProtocol::Udp => types::TransportProtocol::Udp,
+ })
+ .map(|protocol| types::TransportProtocolConstraint {
+ protocol: i32::from(protocol),
+ }),
+ }),
+ })
+ }
+ };
+
+ types::RelaySettings {
+ endpoint: Some(endpoint),
+ }
+}
+
+fn convert_connection_config(config: &ConnectionConfig) -> types::ConnectionConfig {
+ use types::connection_config;
+
+ types::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(types::TransportProtocol::Tcp),
+ TransportProtocol::Udp => i32::from(types::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) -> types::BridgeSettings {
+ use talpid_types::net;
+ use types::bridge_settings::{self, Type as BridgeSettingType};
+
+ let settings = match settings {
+ BridgeSettings::Normal(constraints) => {
+ BridgeSettingType::Normal(types::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(),
+ })
+ }
+ },
+ };
+
+ types::BridgeSettings {
+ r#type: Some(settings),
}
+}
+
+fn convert_wireguard_key_event(
+ event: &mullvad_types::wireguard::KeygenEvent,
+) -> types::KeygenEvent {
+ use mullvad_types::wireguard::KeygenEvent::*;
+ use types::keygen_event::KeygenEvent as ProtoEvent;
- 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(())
+ types::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) -> types::PublicKey {
+ types::PublicKey {
+ key: public_key.key.as_bytes().to_vec(),
+ created: Some(types::Timestamp {
+ seconds: public_key.created.timestamp(),
+ nanos: 0,
+ }),
+ }
+}
+
+fn convert_location_constraint(
+ location: &Constraint<LocationConstraint>,
+) -> Option<types::RelayLocation> {
+ location.as_ref().option().map(|location| match location {
+ LocationConstraint::Country(country) => types::RelayLocation {
+ country: country.to_string(),
+ ..Default::default()
+ },
+ LocationConstraint::City(country, city) => types::RelayLocation {
+ country: country.to_string(),
+ city: city.to_string(),
+ ..Default::default()
+ },
+ LocationConstraint::Hostname(country, city, hostname) => types::RelayLocation {
+ country: country.to_string(),
+ city: city.to_string(),
+ hostname: hostname.to_string(),
+ },
+ })
+}
+
+fn convert_bridge_state(state: &BridgeState) -> types::BridgeState {
+ let state = match state {
+ BridgeState::Auto => types::bridge_state::State::Auto,
+ BridgeState::On => types::bridge_state::State::On,
+ BridgeState::Off => types::bridge_state::State::Off,
+ };
+ types::BridgeState {
+ state: i32::from(state),
+ }
+}
+
+fn convert_tunnel_options(options: &TunnelOptions) -> types::TunnelOptions {
+ use types::tunnel_options::wireguard_options::RotationInterval;
+
+ types::TunnelOptions {
+ openvpn: Some(types::tunnel_options::OpenvpnOptions {
+ mssfix: u32::from(options.openvpn.mssfix.unwrap_or_default()),
+ }),
+ wireguard: Some(types::tunnel_options::WireguardOptions {
+ mtu: u32::from(options.wireguard.mtu.unwrap_or_default()),
+ automatic_rotation: options
+ .wireguard
+ .automatic_rotation
+ .map(|interval| RotationInterval { interval }),
+ }),
+ generic: Some(types::tunnel_options::GenericOptions {
+ enable_ipv6: options.generic.enable_ipv6,
+ }),
+ }
+}
+
+fn convert_relay_list_country(country: &RelayListCountry) -> types::RelayListCountry {
+ let mut proto_country = types::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(types::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) -> types::Relay {
+ types::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(types::RelayTunnels {
+ openvpn: relay
+ .tunnels
+ .openvpn
+ .iter()
+ .map(|endpoint| {
+ let protocol = match endpoint.protocol {
+ TransportProtocol::Udp => types::TransportProtocol::Udp,
+ TransportProtocol::Tcp => types::TransportProtocol::Tcp,
+ };
+ types::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| types::PortRange {
+ first: u32::from(range.0),
+ last: u32::from(range.1),
+ })
+ .collect();
+ types::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(types::RelayBridges {
+ shadowsocks: relay
+ .bridges
+ .shadowsocks
+ .iter()
+ .map(|endpoint| {
+ let protocol = match endpoint.protocol {
+ TransportProtocol::Udp => types::TransportProtocol::Udp,
+ TransportProtocol::Tcp => types::TransportProtocol::Tcp,
+ };
+ types::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| types::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) -> types::TunnelState {
+ use talpid_types::tunnel::{
+ ActionAfterDisconnect, ErrorStateCause, FirewallPolicyError, ParameterGenerationError,
+ };
+ use types::{
+ error_state::{
+ firewall_policy_error::ErrorType as PolicyErrorType, Cause as ProtoErrorCause,
+ FirewallPolicyError as ProtoFirewallPolicyError,
+ GenerationError as ProtoGenerationError,
+ },
+ tunnel_state::{self, State as ProtoState},
+ };
+ use TunnelState::*;
+
+ let map_firewall_error = |firewall_error: &FirewallPolicyError| match firewall_error {
+ FirewallPolicyError::Generic => 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()),
+ };
+
+ ProtoFirewallPolicyError {
+ r#type: i32::from(PolicyErrorType::Locked),
+ lock_pid,
+ lock_name,
+ }
+ }
+ };
+
+ let state = match state {
+ Disconnected => ProtoState::Disconnected(tunnel_state::Disconnected {}),
+ Connecting { endpoint, location } => ProtoState::Connecting(tunnel_state::Connecting {
+ relay_info: Some(types::TunnelStateRelayInfo {
+ tunnel_endpoint: Some(convert_endpoint(endpoint)),
+ location: location.map(convert_geoip_location),
+ }),
+ }),
+ Connected { endpoint, location } => ProtoState::Connected(tunnel_state::Connected {
+ relay_info: Some(types::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(types::AfterDisconnect::Nothing),
+ ActionAfterDisconnect::Block => i32::from(types::AfterDisconnect::Block),
+ ActionAfterDisconnect::Reconnect => i32::from(types::AfterDisconnect::Reconnect),
+ },
+ }),
+ Error(error_state) => ProtoState::Error(tunnel_state::Error {
+ error_state: Some(types::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)
+ }
+ },
+ blocking_error: error_state.block_failure().map(map_firewall_error),
+ 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()
+ {
+ Some(map_firewall_error(reason))
+ } else {
+ None
+ },
+ }),
+ }),
+ };
+
+ types::TunnelState { state: Some(state) }
+}
+
+fn convert_endpoint(endpoint: talpid_types::net::TunnelEndpoint) -> types::TunnelEndpoint {
+ use talpid_types::net;
+
+ types::TunnelEndpoint {
+ address: endpoint.endpoint.address.to_string(),
+ protocol: match endpoint.endpoint.protocol {
+ TransportProtocol::Tcp => i32::from(types::TransportProtocol::Tcp),
+ TransportProtocol::Udp => i32::from(types::TransportProtocol::Udp),
+ },
+ tunnel_type: match endpoint.tunnel_type {
+ net::TunnelType::Wireguard => i32::from(types::TunnelType::Wireguard),
+ net::TunnelType::OpenVpn => i32::from(types::TunnelType::Openvpn),
+ },
+ proxy: endpoint.proxy.map(|proxy_ep| types::ProxyEndpoint {
+ address: proxy_ep.endpoint.address.to_string(),
+ protocol: match proxy_ep.endpoint.protocol {
+ TransportProtocol::Tcp => i32::from(types::TransportProtocol::Tcp),
+ TransportProtocol::Udp => i32::from(types::TransportProtocol::Udp),
+ },
+ proxy_type: match proxy_ep.proxy_type {
+ net::proxy::ProxyType::Shadowsocks => i32::from(types::ProxyType::Shadowsocks),
+ net::proxy::ProxyType::Custom => i32::from(types::ProxyType::Custom),
+ },
+ }),
+ }
+}
+
+fn convert_geoip_location(geoip: GeoIpLocation) -> types::GeoIpLocation {
+ types::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) -> types::AppVersionInfo {
+ types::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: types::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<(), mullvad_management_interface::Error>>,
+ >,
+}
+
+impl ManagementInterfaceServer {
+ 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 = ManagementServiceImpl {
+ daemon_tx: tunnel_tx,
+ subscriptions: subscriptions.clone(),
};
- Box::new(result)
+ let server_join_handle = runtime.spawn(mullvad_management_interface::spawn_rpc_server(
+ server,
+ start_tx,
+ server_abort_rx,
+ ));
+
+ 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());
+ }
+
+ Ok(ManagementInterfaceServer {
+ subscriptions,
+ socket_path,
+ 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(types::DaemonEvent {
+ event: Some(daemon_event::Event::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(types::DaemonEvent {
+ event: Some(daemon_event::Event::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 = types::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(types::DaemonEvent {
+ event: Some(daemon_event::Event::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(types::DaemonEvent {
+ event: Some(daemon_event::Event::VersionInfo(new_info)),
+ })
+ }
-/// 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>>,
+ 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(types::DaemonEvent {
+ event: Some(daemon_event::Event::KeyEvent(new_event)),
+ })
+ }
}
-/// Make the `Meta` type possible to use as jsonrpc metadata type.
-impl Metadata for Meta {}
+impl ManagementInterfaceEventBroadcaster {
+ fn notify(&self, value: types::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());
+ }
+}
-/// 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()
+impl Drop for ManagementInterfaceEventBroadcaster {
+ fn drop(&mut self) {
+ self.close_handle.trigger();
}
}
-/// Metadata extractor function for `Meta`.
-fn meta_extractor(context: &jsonrpc_ipc_server::RequestContext<'_>) -> Meta {
- Meta {
- session: Some(Arc::new(Session::new(context.sender.clone()))),
+// Converts a REST API error for an account into a tonic status.
+fn map_rest_account_error(error: RestError) -> Status {
+ match error {
+ RestError::ApiError(status, message)
+ if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN =>
+ {
+ Status::new(Code::Unauthenticated, message)
+ }
+ _ => Status::internal("internal error"),
}
}
diff --git a/mullvad-daemon/src/rpc_uniqueness_check.rs b/mullvad-daemon/src/rpc_uniqueness_check.rs
index 7befdb4ba1..76d524c765 100644
--- a/mullvad-daemon/src/rpc_uniqueness_check.rs
+++ b/mullvad-daemon/src/rpc_uniqueness_check.rs
@@ -1,13 +1,12 @@
-use mullvad_ipc_client::new_standalone_ipc_client;
-use mullvad_paths;
+use mullvad_management_interface::new_rpc_client;
use talpid_types::ErrorExt;
/// 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_rpc_client().await {
Ok(_) => true,
Err(error) => {
let msg =
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-management-interface/Cargo.toml b/mullvad-management-interface/Cargo.toml
new file mode 100644
index 0000000000..31647006b1
--- /dev/null
+++ b/mullvad-management-interface/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "mullvad-management-interface"
+version = "0.1.0"
+authors = ["Mullvad VPN"]
+description = "Mullvad VPN IPC. Contains types and functions for IPC clients and servers."
+license = "GPL-3.0"
+edition = "2018"
+publish = false
+
+[dependencies]
+err-derive = "0.2.1"
+mullvad-paths = { path = "../mullvad-paths" }
+tonic = "0.2"
+tower = "0.3"
+prost = "0.6"
+prost-types = "0.6"
+parity-tokio-ipc = "0.7"
+futures = "0.3"
+tokio = { version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream"] }
+triggered = "0.1.1"
+winapi = { version = "0.3", features = ["errhandlingapi", "handleapi", "libloaderapi", "ntlsa", "synchapi", "tlhelp32", "winbase", "winerror", "winuser"] }
+
+[build-dependencies]
+tonic-build = { version = "0.2", default-features = false, features = ["transport", "prost"] }
diff --git a/mullvad-management-interface/build.rs b/mullvad-management-interface/build.rs
new file mode 100644
index 0000000000..e3dd7a8186
--- /dev/null
+++ b/mullvad-management-interface/build.rs
@@ -0,0 +1,5 @@
+fn main() {
+ const PROTO_FILE: &str = "proto/management_interface.proto";
+ tonic_build::compile_protos(PROTO_FILE).unwrap();
+ println!("cargo:rerun-if-changed={}", PROTO_FILE);
+}
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
new file mode 100644
index 0000000000..8616163f50
--- /dev/null
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -0,0 +1,471 @@
+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;
+ FirewallPolicyError blocking_error = 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 {
+ OPENVPN = 0;
+ WIREGUARD = 1;
+}
+
+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 {
+ 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;
+}
+
+message RelaySettings {
+ oneof endpoint {
+ CustomRelaySettings custom = 1;
+ NormalRelaySettings normal = 2;
+ }
+}
+
+message TunnelTypeConstraint {
+ TunnelType tunnel_type = 1;
+}
+
+message NormalRelaySettings {
+ RelayLocation location = 1;
+ TunnelTypeConstraint 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 {
+ TunnelTypeConstraint tunnel_type = 2;
+}
+
+message TransportProtocolConstraint {
+ TransportProtocol protocol = 1;
+}
+
+message OpenvpnConstraints {
+ uint32 port = 1;
+ TransportProtocolConstraint 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 {
+ message RotationInterval {
+ uint32 interval = 1;
+ }
+
+ // NOTE: optional
+ uint32 mtu = 1;
+ RotationInterval 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 {
+ UDP = 0;
+ TCP = 1;
+}
+
+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-management-interface/src/lib.rs b/mullvad-management-interface/src/lib.rs
new file mode 100644
index 0000000000..419e2d48b1
--- /dev/null
+++ b/mullvad-management-interface/src/lib.rs
@@ -0,0 +1,123 @@
+pub mod types {
+ tonic::include_proto!("mullvad_daemon.management_interface");
+
+ pub use prost_types::Timestamp;
+}
+
+use parity_tokio_ipc::Endpoint as IpcEndpoint;
+#[cfg(unix)]
+use std::{fs, os::unix::fs::PermissionsExt};
+use std::{
+ io,
+ pin::Pin,
+ task::{Context, Poll},
+};
+use tokio::io::{AsyncRead, AsyncWrite};
+use tonic::transport::{server::Connected, Endpoint, Server, Uri};
+use tower::service_fn;
+
+pub use tonic::{async_trait, transport::Channel, Code, Request, Response, Status};
+
+pub type ManagementServiceClient =
+ types::management_service_client::ManagementServiceClient<Channel>;
+pub use types::management_service_server::{ManagementService, ManagementServiceServer};
+
+#[derive(err_derive::Error, Debug)]
+#[error(no_from)]
+pub enum Error {
+ #[error(display = "Management RPC server or client error")]
+ GrpcTransportError(#[error(source)] tonic::transport::Error),
+
+ #[error(display = "Failed to start IPC pipe/socket")]
+ StartServerError(#[error(source)] io::Error),
+
+ #[error(display = "Failed to initialize pipe/socket security attributes")]
+ SecurityAttributes(#[error(source)] io::Error),
+
+ #[error(display = "Unable to set permissions for IPC endpoint")]
+ PermissionsError(#[error(source)] io::Error),
+}
+
+#[cfg(target_os = "windows")]
+mod windows_permissions;
+
+pub async fn new_rpc_client() -> Result<ManagementServiceClient, 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::GrpcTransportError)?;
+
+ Ok(ManagementServiceClient::new(channel))
+}
+
+pub async fn spawn_rpc_server<T: ManagementService>(
+ service: T,
+ server_start_tx: std::sync::mpsc::Sender<()>,
+ abort_rx: triggered::Listener,
+) -> std::result::Result<(), Error> {
+ use futures::stream::TryStreamExt;
+ use parity_tokio_ipc::SecurityAttributes;
+
+ let socket_path = mullvad_paths::get_rpc_socket_path()
+ .to_string_lossy()
+ .to_string();
+
+ let mut endpoint = IpcEndpoint::new(socket_path.to_string());
+ endpoint.set_security_attributes(
+ SecurityAttributes::allow_everyone_create()
+ .map_err(Error::SecurityAttributes)?
+ .set_mode(777)
+ .map_err(Error::SecurityAttributes)?,
+ );
+ let incoming = endpoint.incoming().map_err(Error::StartServerError)?;
+
+ #[cfg(unix)]
+ 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)?;
+
+ let _ = server_start_tx.send(());
+
+ Server::builder()
+ .add_service(ManagementServiceServer::new(service))
+ .serve_with_incoming_shutdown(incoming.map_ok(StreamBox), abort_rx)
+ .await
+ .map_err(Error::GrpcTransportError)
+}
+
+#[derive(Debug)]
+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)
+ }
+
+ 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/talpid-ipc/src/win.rs b/mullvad-management-interface/src/windows_permissions.rs
index 0b39282348..0b39282348 100644
--- a/talpid-ipc/src/win.rs
+++ b/mullvad-management-interface/src/windows_permissions.rs
diff --git a/mullvad-setup/Cargo.toml b/mullvad-setup/Cargo.toml
index 3d113870d4..04da0d49ca 100644
--- a/mullvad-setup/Cargo.toml
+++ b/mullvad-setup/Cargo.toml
@@ -16,7 +16,10 @@ clap = "2.32"
env_logger = "0.7"
err-derive = "0.2.1"
-mullvad-ipc-client = { path = "../mullvad-ipc-client" }
+mullvad-management-interface = { path = "../mullvad-management-interface" }
+
+tokio = { version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream"] }
+
mullvad-paths = { path = "../mullvad-paths" }
talpid-core = { path = "../talpid-core" }
talpid-types = { path = "../talpid-types" }
diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs
index f8fda169b5..c53394a473 100644
--- a/mullvad-setup/src/main.rs
+++ b/mullvad-setup/src/main.rs
@@ -1,6 +1,6 @@
use clap::{crate_authors, crate_description, crate_name, SubCommand};
-use mullvad_ipc_client::{new_standalone_ipc_client, DaemonRpcClient};
-use std::{io, process};
+use mullvad_management_interface::new_rpc_client;
+use std::process;
use talpid_core::firewall::{self, Firewall, FirewallArguments};
use talpid_types::ErrorExt;
@@ -8,11 +8,11 @@ pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/produc
#[derive(err_derive::Error, Debug)]
pub enum Error {
- #[error(display = "Failed to connect to daemon")]
- DaemonConnect(#[error(source)] io::Error),
+ #[error(display = "Failed to connect to RPC client")]
+ RpcConnectionError(#[error(source)] mullvad_management_interface::Error),
#[error(display = "RPC call failed")]
- DaemonRpcError(#[error(source)] mullvad_ipc_client::Error),
+ DaemonRpcError(#[error(source)] mullvad_management_interface::Status),
#[error(display = "This command cannot be run if the daemon is active")]
DaemonIsRunning,
@@ -21,7 +21,8 @@ pub enum Error {
FirewallError(#[error(source)] firewall::Error),
}
-fn main() {
+#[tokio::main]
+async fn main() {
env_logger::init();
let subcommands = vec![
@@ -44,8 +45,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 +56,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_rpc_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_rpc_client().await {
return Err(Error::DaemonIsRunning);
}
@@ -74,7 +78,3 @@ 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)
-}
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/src/account.rs b/mullvad-types/src/account.rs
index a22b574856..0acb0941aa 100644
--- a/mullvad-types/src/account.rs
+++ b/mullvad-types/src/account.rs
@@ -28,32 +28,3 @@ pub struct VoucherSubmission {
#[cfg_attr(target_os = "android", jnix(map = "|expiry| expiry.to_string()"))]
pub new_expiry: DateTime<Utc>,
}
-
-/// Mapping of mullvad-api errors to enum variants. Used by frontends to explain why a voucher
-/// was rejected by the `/v1/submit-voucher` RPC.
-#[derive(err_derive::Error, Debug)]
-pub enum VoucherError {
- /// Error code -400
- #[error(display = "Bad voucher code")]
- BadVoucher,
- /// Error code -401
- #[error(display = "Voucher already used")]
- VoucherAlreadyUsed,
- /// Error code -100
- #[error(display = "Server internal error")]
- InternalError,
- #[error(display = "Unknown error, {}", _0)]
- UnknownError(i64),
-}
-
-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,
- 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 f8907cba9c..c5d58f621f 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,
@@ -42,12 +49,36 @@ 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,
+ ) -> Constraint<U> {
+ match self {
+ Constraint::Any => Constraint::Any,
+ Constraint::Only(value) => Constraint::Only(f(value)),
+ }
+ }
+
pub fn is_any(&self) -> bool {
match self {
Constraint::Any => true,
Constraint::Only(_value) => false,
}
}
+
+ pub fn as_ref(&self) -> Constraint<&T> {
+ match self {
+ Constraint::Any => Constraint::Any,
+ Constraint::Only(ref value) => Constraint::Only(value),
+ }
+ }
+
+ pub fn option(self) -> Option<T> {
+ match self {
+ Constraint::Any => None,
+ Constraint::Only(value) => Some(value),
+ }
+ }
}
impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> {
@@ -67,6 +98,15 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Match<T> for Constraint<T> {
}
}
+impl<T: fmt::Debug + Clone + Eq + PartialEq> From<Option<T>> for Constraint<T> {
+ fn from(value: Option<T>) -> Self {
+ match value {
+ Some(value) => Constraint::Only(value),
+ None => Constraint::Any,
+ }
+ }
+}
+
/// Specifies a specific endpoint or [`RelayConstraints`] to use when `mullvad-daemon` selects a
/// relay.
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
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-core/build.rs b/talpid-core/build.rs
index 26b74f154a..c50f3411fd 100644
--- a/talpid-core/build.rs
+++ b/talpid-core/build.rs
@@ -93,5 +93,7 @@ fn declare_libs_dir(base: &str) {
}
fn generate_grpc_code() {
- tonic_build::compile_protos("../talpid-openvpn-plugin/proto/openvpn_plugin.proto").unwrap();
+ const PROTO_FILE: &str = "../talpid-openvpn-plugin/proto/openvpn_plugin.proto";
+ tonic_build::compile_protos(PROTO_FILE).unwrap();
+ println!("cargo:rerun-if-changed={}", PROTO_FILE);
}
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")
-}
diff --git a/talpid-openvpn-plugin/build.rs b/talpid-openvpn-plugin/build.rs
index acfa18a87c..08c9e4944d 100644
--- a/talpid-openvpn-plugin/build.rs
+++ b/talpid-openvpn-plugin/build.rs
@@ -1,5 +1,7 @@
fn main() {
- tonic_build::compile_protos("proto/openvpn_plugin.proto").unwrap();
+ const PROTO_FILE: &str = "proto/openvpn_plugin.proto";
+ tonic_build::compile_protos(PROTO_FILE).unwrap();
+ println!("cargo:rerun-if-changed={}", PROTO_FILE);
#[cfg(windows)]
{
diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs
index 00e5bcad50..c803a489a7 100644
--- a/talpid-types/src/tunnel.rs
+++ b/talpid-types/src/tunnel.rs
@@ -68,6 +68,10 @@ impl ErrorState {
pub fn cause(&self) -> &ErrorStateCause {
&self.cause
}
+
+ pub fn block_failure(&self) -> Option<&FirewallPolicyError> {
+ self.block_failure.as_ref()
+ }
}