summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2023-04-19 11:08:52 +0100
committerEmīls <emils@mullvad.net>2023-05-22 10:18:27 +0200
commite5b73d7abc1b50cfbb9c6f056bf66fef28fef5b2 (patch)
tree4e0c6cf3336c69c1c97b0640d5c06bc27819594c
parent77f51e690b26346ff4b251c27eb2ece493820d85 (diff)
downloadmullvadvpn-e5b73d7abc1b50cfbb9c6f056bf66fef28fef5b2.tar.xz
mullvadvpn-e5b73d7abc1b50cfbb9c6f056bf66fef28fef5b2.zip
Add shadowsocks-proxy crate
-rw-r--r--.github/workflows/ios.yml2
-rw-r--r--Cargo.lock237
-rw-r--r--Cargo.toml1
-rw-r--r--ios/MullvadREST/RESTNetworkOperation.swift56
-rw-r--r--ios/MullvadREST/RESTProxy.swift4
-rw-r--r--ios/MullvadREST/RESTProxyFactory.swift2
-rw-r--r--ios/MullvadREST/RESTTransport.swift12
-rw-r--r--ios/MullvadREST/RESTTransportStrategy.swift47
-rw-r--r--ios/MullvadREST/RESTURLSession.swift23
-rw-r--r--ios/MullvadREST/ServerRelaysResponse.swift15
-rw-r--r--ios/MullvadREST/ShadowSocksProxy.swift83
-rw-r--r--ios/MullvadREST/URLSessionTransport.swift46
-rw-r--r--ios/MullvadREST/module.private.modulemap5
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/.gitignore1
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/Cargo.toml25
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/build.rs14
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/build.sh57
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/include/shadowsocks.h20
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/src/bin/run.rs17
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/src/bin/run_unsafe.rs51
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/src/ffi.rs128
-rw-r--r--ios/MullvadREST/shadowsocks-proxy/src/lib.rs160
-rw-r--r--ios/MullvadRESTTests/TransportStrategyTests.swift40
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj56
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved22
-rw-r--r--ios/MullvadVPN/AppDelegate.swift11
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift11
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelTransportProvider.swift26
-rw-r--r--ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift18
-rw-r--r--ios/MullvadVPN/TransportMonitor/TransportMonitor.swift76
-rw-r--r--ios/MullvadVPNTests/RelaySelectorTests.swift2
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift14
-rw-r--r--ios/PacketTunnel/TunnelTransportProvider.swift55
-rw-r--r--ios/RelaySelector/RelaySelector.swift7
-rw-r--r--ios/TunnelProviderMessaging/ProxyURLRequest.swift16
-rw-r--r--ios/TunnelProviderMessaging/URLRequestProxy.swift53
-rw-r--r--mullvad-api/Cargo.toml2
-rw-r--r--mullvad-management-interface/src/types/conversions/settings.rs3
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-openvpn/Cargo.toml2
40 files changed, 1251 insertions, 170 deletions
diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml
index af2d2c70de..b58870edb4 100644
--- a/.github/workflows/ios.yml
+++ b/.github/workflows/ios.yml
@@ -51,6 +51,8 @@ jobs:
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '14.3'
+ - name: Configure Rust
+ run: rustup target add x86_64-apple-ios
- name: Configure Xcode project
run: |
diff --git a/Cargo.lock b/Cargo.lock
index 3a1659baf2..5bad5a1c61 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -380,6 +380,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb"
dependencies = [
+ "clap 3.2.25",
"heck",
"indexmap",
"log",
@@ -463,6 +464,21 @@ dependencies = [
[[package]]
name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_lex 0.2.4",
+ "indexmap",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
@@ -481,7 +497,7 @@ dependencies = [
"anstream",
"anstyle",
"bitflags",
- "clap_lex",
+ "clap_lex 0.4.1",
"once_cell",
"strsim 0.10.0",
]
@@ -492,7 +508,7 @@ version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473"
dependencies = [
- "clap",
+ "clap 4.2.7",
]
[[package]]
@@ -509,6 +525,15 @@ dependencies = [
[[package]]
name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "clap_lex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
@@ -595,6 +620,25 @@ dependencies = [
]
[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "crypto-bigint"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -685,6 +729,17 @@ dependencies = [
]
[[package]]
+name = "dashmap"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8858831f7781322e539ea39e72449c46b059638250c14344fec8d0aa6e539c"
+dependencies = [
+ "cfg-if",
+ "num_cpus",
+ "parking_lot",
+]
+
+[[package]]
name = "data-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -994,6 +1049,18 @@ dependencies = [
]
[[package]]
+name = "filetime"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1007,15 +1074,23 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
- "matches",
"percent-encoding",
]
[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "futures"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1203,6 +1278,15 @@ dependencies = [
[[package]]
name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
@@ -1285,9 +1369,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]]
name = "httparse"
-version = "1.5.1"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
@@ -1303,9 +1387,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
-version = "0.14.16"
+version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
+checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [
"bytes",
"futures-channel",
@@ -1316,7 +1400,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
- "itoa 0.4.8",
+ "itoa 1.0.1",
"pin-project-lite",
"socket2",
"tokio",
@@ -1389,6 +1473,17 @@ dependencies = [
[[package]]
name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf888f9575c290197b2c948dc9e9ff10bd1a39ad1ea8585f734585fa6b9d3f9"
@@ -1589,6 +1684,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838"
[[package]]
+name = "kqueue"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1596,9 +1711,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.139"
+version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "libdbus-sys"
@@ -1816,7 +1931,7 @@ dependencies = [
"anyhow",
"base64 0.13.0",
"chrono",
- "clap",
+ "clap 4.2.7",
"clap_complete",
"env_logger 0.10.0",
"futures",
@@ -1840,7 +1955,7 @@ dependencies = [
"android_logger",
"cfg-if",
"chrono",
- "clap",
+ "clap 4.2.7",
"ctrlc",
"dirs-next",
"duct",
@@ -1969,7 +2084,7 @@ dependencies = [
name = "mullvad-problem-report"
version = "0.0.0"
dependencies = [
- "clap",
+ "clap 4.2.7",
"dirs-next",
"duct",
"env_logger 0.10.0",
@@ -2014,7 +2129,7 @@ dependencies = [
name = "mullvad-setup"
version = "0.0.0"
dependencies = [
- "clap",
+ "clap 4.2.7",
"env_logger 0.10.0",
"err-derive",
"lazy_static",
@@ -2034,7 +2149,7 @@ name = "mullvad-types"
version = "0.0.0"
dependencies = [
"chrono",
- "clap",
+ "clap 4.2.7",
"err-derive",
"ipnetwork",
"jnix",
@@ -2202,6 +2317,24 @@ dependencies = [
]
[[package]]
+name = "notify"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify 0.9.6",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2222,11 +2355,11 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.13.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
- "hermit-abi 0.1.19",
+ "hermit-abi 0.2.6",
"libc",
]
@@ -2288,6 +2421,23 @@ dependencies = [
]
[[package]]
+name = "os_str_bytes"
+version = "6.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
+
+[[package]]
+name = "oslog"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969"
+dependencies = [
+ "cc",
+ "dashmap",
+ "log",
+]
+
+[[package]]
name = "p256"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2352,9 +2502,9 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "percent-encoding"
-version = "2.1.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
@@ -3092,9 +3242,9 @@ dependencies = [
[[package]]
name = "shadowsocks"
version = "1.15.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4137cd7a208461a72d1901ea4990aa8a7307bce070bfe93a01c1cb827c4104a4"
+source = "git+https://github.com/mullvad/shadowsocks-rust?rev=8f6afd081a9440fff2dda565908eddc27a3295f1#8f6afd081a9440fff2dda565908eddc27a3295f1"
dependencies = [
+ "arc-swap",
"async-trait",
"base64 0.21.0",
"blake3",
@@ -3104,6 +3254,7 @@ dependencies = [
"futures",
"libc",
"log",
+ "notify",
"once_cell",
"percent-encoding",
"pin-project",
@@ -3117,6 +3268,7 @@ dependencies = [
"thiserror",
"tokio",
"tokio-tfo",
+ "trust-dns-resolver",
"url",
"windows-sys 0.45.0",
]
@@ -3141,10 +3293,21 @@ dependencies = [
]
[[package]]
+name = "shadowsocks-proxy"
+version = "0.0.0"
+dependencies = [
+ "cbindgen",
+ "libc",
+ "log",
+ "oslog",
+ "shadowsocks-service",
+ "tokio",
+]
+
+[[package]]
name = "shadowsocks-service"
version = "1.15.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22afe1a575e51616709fa0cdae9101d6eea7af95941a27a032801e24bd7b730a"
+source = "git+https://github.com/mullvad/shadowsocks-rust?rev=8f6afd081a9440fff2dda565908eddc27a3295f1#8f6afd081a9440fff2dda565908eddc27a3295f1"
dependencies = [
"arc-swap",
"async-trait",
@@ -3153,6 +3316,7 @@ dependencies = [
"bytes",
"cfg-if",
"futures",
+ "hyper",
"idna 0.3.0",
"ipnet",
"iprange",
@@ -3171,6 +3335,7 @@ dependencies = [
"spin 0.9.2",
"thiserror",
"tokio",
+ "tower",
"windows-sys 0.45.0",
]
@@ -3238,9 +3403,9 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "socket2"
-version = "0.4.4"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
@@ -3378,7 +3543,7 @@ dependencies = [
"err-derive",
"futures",
"hex",
- "inotify",
+ "inotify 0.10.0",
"internet-checksum",
"ipnetwork",
"jnix",
@@ -3403,7 +3568,6 @@ dependencies = [
"regex",
"resolv-conf",
"rtnetlink",
- "shadowsocks-service",
"shell-escape",
"socket2",
"subslice",
@@ -3667,6 +3831,12 @@ dependencies = [
]
[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4168,13 +4338,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
-version = "2.2.2"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
- "idna 0.2.3",
- "matches",
+ "idna 0.3.0",
"percent-encoding",
"serde",
]
diff --git a/Cargo.toml b/Cargo.toml
index 3e7a3bed2f..a496374515 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
resolver = "2"
members = [
"android/translations-converter",
+ "ios/MullvadREST/shadowsocks-proxy",
"mullvad-daemon",
"mullvad-cli",
"mullvad-fs",
diff --git a/ios/MullvadREST/RESTNetworkOperation.swift b/ios/MullvadREST/RESTNetworkOperation.swift
index 6f723e5865..4b3705ce76 100644
--- a/ios/MullvadREST/RESTNetworkOperation.swift
+++ b/ios/MullvadREST/RESTNetworkOperation.swift
@@ -17,7 +17,7 @@ extension REST {
private let responseHandler: AnyResponseHandler<Success>
private let logger: Logger
- private let transportProvider: () -> RESTTransport?
+ private let transportProvider: () -> RESTTransportProvider?
private let addressCacheStore: AddressCache
private var networkTask: Cancellable?
@@ -30,6 +30,7 @@ extension REST {
private var retryDelayIterator: AnyIterator<Duration>
private var retryTimer: DispatchSourceTimer?
private var retryCount = 0
+ private var transportStrategy = TransportStrategy()
init(
name: String,
@@ -140,7 +141,13 @@ extension REST {
private func didReceiveURLRequest(_ restRequest: REST.Request, endpoint: AnyIPEndpoint) {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
- guard let transport = transportProvider() else {
+ let suggestedTransport = transportStrategy.connectionTransport()
+ let transportProvider = transportProvider()
+ let transport = suggestedTransport == .useShadowSocks
+ ? transportProvider?.shadowSocksTransport()
+ : transportProvider?.transport()
+
+ guard let transport = transport else {
logger.error("Failed to obtain transport.")
finish(result: .failure(REST.Error.transport(NoTransportError())))
return
@@ -153,33 +160,27 @@ extension REST {
"""
)
- do {
- networkTask = try transport
- .sendRequest(restRequest.urlRequest) { [weak self] data, response, error in
- guard let self = self else { return }
-
- self.dispatchQueue.async {
- if let error = error {
- self.didReceiveError(
- error,
- transport: transport,
- endpoint: endpoint
- )
- } else {
- let httpResponse = response as! HTTPURLResponse
- let data = data ?? Data()
+ networkTask = transport.sendRequest(restRequest.urlRequest) { [weak self] data, response, error in
+ guard let self = self else { return }
+ self.dispatchQueue.async {
+ if let error = error {
+ self.didReceiveError(
+ error,
+ transport: transport,
+ endpoint: endpoint
+ )
+ } else {
+ let httpResponse = response as! HTTPURLResponse
+ let data = data ?? Data()
- self.didReceiveURLResponse(
- httpResponse,
- transport: transport,
- data: data,
- endpoint: endpoint
- )
- }
- }
+ self.didReceiveURLResponse(
+ httpResponse,
+ transport: transport,
+ data: data,
+ endpoint: endpoint
+ )
}
- } catch {
- didReceiveError(error, transport: transport, endpoint: endpoint)
+ }
}
}
@@ -213,6 +214,7 @@ extension REST {
default:
if !REST.isStagingEnvironment {
_ = addressCacheStore.selectNextEndpoint(endpoint)
+ transportStrategy.didFail()
}
}
}
diff --git a/ios/MullvadREST/RESTProxy.swift b/ios/MullvadREST/RESTProxy.swift
index 5c8a14df81..b252d2e33f 100644
--- a/ios/MullvadREST/RESTProxy.swift
+++ b/ios/MullvadREST/RESTProxy.swift
@@ -67,11 +67,11 @@ extension REST {
}
public class ProxyConfiguration {
- public let transportProvider: () -> RESTTransport?
+ public let transportProvider: () -> RESTTransportProvider?
public let addressCacheStore: AddressCache
public init(
- transportProvider: @escaping () -> RESTTransport?,
+ transportProvider: @escaping () -> RESTTransportProvider?,
addressCacheStore: AddressCache
) {
self.transportProvider = transportProvider
diff --git a/ios/MullvadREST/RESTProxyFactory.swift b/ios/MullvadREST/RESTProxyFactory.swift
index 6bc793a722..8d2bba41d3 100644
--- a/ios/MullvadREST/RESTProxyFactory.swift
+++ b/ios/MullvadREST/RESTProxyFactory.swift
@@ -13,7 +13,7 @@ extension REST {
public let configuration: AuthProxyConfiguration
public class func makeProxyFactory(
- transportProvider: @escaping () -> RESTTransport?,
+ transportProvider: @escaping () -> RESTTransportProvider?,
addressCache: AddressCache
) -> ProxyFactory {
let basicConfiguration = REST.ProxyConfiguration(
diff --git a/ios/MullvadREST/RESTTransport.swift b/ios/MullvadREST/RESTTransport.swift
index c8a1aa5b36..b739828bf2 100644
--- a/ios/MullvadREST/RESTTransport.swift
+++ b/ios/MullvadREST/RESTTransport.swift
@@ -15,5 +15,15 @@ public protocol RESTTransport {
func sendRequest(
_ request: URLRequest,
completion: @escaping (Data?, URLResponse?, Error?) -> Void
- ) throws -> Cancellable
+ ) -> Cancellable
+}
+
+public protocol RESTTransportProvider {
+ /// Requests a new transport
+ /// - Returns: A transport layer
+ func transport() -> RESTTransport?
+
+ /// Requests a Shadowsocks transport
+ /// - Returns: A transport layer that proxies the requests to a local Shadowsocks proxy instance
+ func shadowSocksTransport() -> RESTTransport?
}
diff --git a/ios/MullvadREST/RESTTransportStrategy.swift b/ios/MullvadREST/RESTTransportStrategy.swift
new file mode 100644
index 0000000000..5162100a64
--- /dev/null
+++ b/ios/MullvadREST/RESTTransportStrategy.swift
@@ -0,0 +1,47 @@
+//
+// RESTTransportStrategy.swift
+// MullvadREST
+//
+// Created by Marco Nikic on 2023-04-27.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+extension REST {
+ public struct TransportStrategy: Codable {
+ /// The different transports suggested by the strategy
+ public enum Transport {
+ /// Suggests using a direct connection
+ case useURLSession
+ /// Suggests connecting via Shadowsocks proxy
+ case useShadowSocks
+ }
+
+ /// The internal counter for suggested transports.
+ ///
+ /// A value of `0` means a direct transport suggestion, a value of `1` or `2` means a Shadowsocks transport
+ /// suggestion.
+ private var connectionAttempts: UInt
+
+ public init() {
+ connectionAttempts = 0
+ }
+
+ /// Instructs the strategy that a network connection failed
+ ///
+ /// Every third failure results in a direct transport suggestion.
+ public mutating func didFail() {
+ connectionAttempts += 1
+ // Avoid overflowing by resetting back to 0 every 3rd failure
+ connectionAttempts = connectionAttempts.isMultiple(of: 3) ? 0 : connectionAttempts
+ }
+
+ /// The suggested connection transport
+ ///
+ /// - Returns: `.useURLSession` for every 3rd failed attempt, `.useShadowSocks` otherwise
+ public func connectionTransport() -> Transport {
+ connectionAttempts.isMultiple(of: 3) ? .useURLSession : .useShadowSocks
+ }
+ }
+}
diff --git a/ios/MullvadREST/RESTURLSession.swift b/ios/MullvadREST/RESTURLSession.swift
index 87e2d7abbe..99bafcee3c 100644
--- a/ios/MullvadREST/RESTURLSession.swift
+++ b/ios/MullvadREST/RESTURLSession.swift
@@ -9,27 +9,7 @@
import Foundation
extension REST {
- public struct HTTPProxyConfiguration {
- public var address: String
- public var port: UInt16
-
- public init(address: String, port: UInt16) {
- self.address = address
- self.port = port
- }
-
- fileprivate func apply(to sessionConfiguration: URLSessionConfiguration) {
- var configuration = [CFString: Any]()
-
- configuration[kCFNetworkProxiesHTTPProxy] = address
- configuration[kCFNetworkProxiesHTTPPort] = NSNumber(value: port)
- configuration[kCFNetworkProxiesProxyAutoConfigEnable] = kCFBooleanFalse
-
- sessionConfiguration.connectionProxyDictionary = configuration
- }
- }
-
- public static func makeURLSession(httpProxyConfiguration: HTTPProxyConfiguration? = nil) -> URLSession {
+ public static func makeURLSession() -> URLSession {
let certificatePath = Bundle(for: SSLPinningURLSessionDelegate.self)
.path(forResource: "le_root_cert", ofType: "cer")!
let data = FileManager.default.contents(atPath: certificatePath)!
@@ -41,7 +21,6 @@ extension REST {
)
let sessionConfiguration = URLSessionConfiguration.ephemeral
- httpProxyConfiguration?.apply(to: sessionConfiguration)
let session = URLSession(
configuration: sessionConfiguration,
diff --git a/ios/MullvadREST/ServerRelaysResponse.swift b/ios/MullvadREST/ServerRelaysResponse.swift
index d6e8b454a2..d91d05baee 100644
--- a/ios/MullvadREST/ServerRelaysResponse.swift
+++ b/ios/MullvadREST/ServerRelaysResponse.swift
@@ -26,6 +26,17 @@ extension REST {
}
}
+ public struct BridgeRelay: Codable {
+ public let hostname: String
+ public let active: Bool
+ public let owned: Bool
+ public let location: String
+ public let provider: String
+ public let ipv4AddrIn: IPv4Address
+ public let weight: UInt64
+ public let includeInCountry: Bool
+ }
+
public struct ServerRelay: Codable {
public let hostname: String
public let active: Bool
@@ -98,9 +109,11 @@ extension REST {
public struct ServerBridges: Codable {
public let shadowsocks: [ServerShadowsocks]
+ public let relays: [BridgeRelay]
- public init(shadowsocks: [REST.ServerShadowsocks]) {
+ public init(shadowsocks: [REST.ServerShadowsocks], relays: [BridgeRelay]) {
self.shadowsocks = shadowsocks
+ self.relays = relays
}
}
diff --git a/ios/MullvadREST/ShadowSocksProxy.swift b/ios/MullvadREST/ShadowSocksProxy.swift
new file mode 100644
index 0000000000..a905cdc9c7
--- /dev/null
+++ b/ios/MullvadREST/ShadowSocksProxy.swift
@@ -0,0 +1,83 @@
+//
+// ShadowSocksProxy.swift
+// MullvadREST
+//
+// Created by Emils on 19/04/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+import Shadowsocks
+
+public class ShadowSocksProxy {
+ private var proxyConfig: ProxyHandle
+ private let remoteAddress: IPAddress
+ private let remotePort: UInt16
+ private let password: String
+ private let cipher: String
+ private var didStart = false
+ private let stateLock = NSLock()
+
+ public init(remoteAddress: IPAddress, remotePort: UInt16, password: String, cipher: String) {
+ proxyConfig = ProxyHandle(context: nil, port: 0)
+ self.remoteAddress = remoteAddress
+ self.remotePort = remotePort
+ self.password = password
+ self.cipher = cipher
+ }
+
+ /// The local port for the shadow socks proxy
+ ///
+ /// - Returns: The local port for the shadow socks proxy when it has started, 0 otherwise.
+ public func localPort() -> UInt16 {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+ return proxyConfig.port
+ }
+
+ deinit {
+ stop()
+ }
+
+ /// Starts the socks proxy
+ public func start() {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+ guard didStart == false else { return }
+ didStart = true
+
+ // Get the raw bytes of `addr.rawValue`
+ remoteAddress.rawValue.withUnsafeBytes { unsafeAddressPointer in
+
+ // Rebind the raw bytes to an array of bytes, and get a pointer to its beginning
+ let rawAddr = unsafeAddressPointer.bindMemory(to: UInt8.self).baseAddress
+
+ // Get the raw bytes access to `proxyConfig`
+ _ = withUnsafeMutablePointer(to: &proxyConfig) { config in
+ start_shadowsocks_proxy(
+ rawAddr,
+ UInt(remoteAddress.rawValue.count),
+ remotePort,
+ password,
+ UInt(password.count),
+ cipher,
+ UInt(cipher.count),
+ config
+ )
+ }
+ }
+ }
+
+ /// Stops the socks proxy
+ public func stop() {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+ guard didStart == true else { return }
+ didStart = false
+
+ _ = withUnsafePointer(to: proxyConfig) { pointer in
+ stop_shadowsocks_proxy(UnsafeMutablePointer(mutating: pointer))
+ }
+ }
+}
diff --git a/ios/MullvadREST/URLSessionTransport.swift b/ios/MullvadREST/URLSessionTransport.swift
index 4acc155d58..0bb765c686 100644
--- a/ios/MullvadREST/URLSessionTransport.swift
+++ b/ios/MullvadREST/URLSessionTransport.swift
@@ -26,10 +26,54 @@ extension REST {
public func sendRequest(
_ request: URLRequest,
completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
- ) throws -> Cancellable {
+ ) -> Cancellable {
let dataTask = urlSession.dataTask(with: request, completionHandler: completion)
dataTask.resume()
return dataTask
}
}
+
+ public final class URLSessionShadowSocksTransport: RESTTransport {
+ public var name: String {
+ return "shadow-socks-url-session"
+ }
+
+ public let urlSession: URLSession
+ private let shadowSocksProxy: ShadowSocksProxy
+
+ public init(
+ urlSession: URLSession,
+ shadowSocksConfiguration: ServerShadowsocks,
+ shadowSocksBridgeRelay: BridgeRelay
+ ) {
+ self.urlSession = urlSession
+ shadowSocksProxy = ShadowSocksProxy(
+ remoteAddress: shadowSocksBridgeRelay.ipv4AddrIn,
+ remotePort: shadowSocksConfiguration.port,
+ password: shadowSocksConfiguration.password,
+ cipher: shadowSocksConfiguration.cipher
+ )
+ }
+
+ public func sendRequest(
+ _ request: URLRequest,
+ completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
+ ) -> Cancellable {
+ // Start the shadow socks proxy in order to get a local port
+ shadowSocksProxy.start()
+
+ // Copy the URL request and rewrite the host and port to point to the shadow socks proxy instance
+ var urlRequestCopy = request
+ urlRequestCopy.url = request.url.flatMap { url in
+ var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
+ components?.host = "127.0.0.1"
+ components?.port = Int(shadowSocksProxy.localPort())
+ return components?.url
+ }
+
+ let dataTask = urlSession.dataTask(with: urlRequestCopy, completionHandler: completion)
+ dataTask.resume()
+ return dataTask
+ }
+ }
}
diff --git a/ios/MullvadREST/module.private.modulemap b/ios/MullvadREST/module.private.modulemap
new file mode 100644
index 0000000000..ed8bb99f2e
--- /dev/null
+++ b/ios/MullvadREST/module.private.modulemap
@@ -0,0 +1,5 @@
+framework module Shadowsocks {
+ header "shadowsocks.h"
+ link "libshadowsocks_proxy"
+ export *
+}
diff --git a/ios/MullvadREST/shadowsocks-proxy/.gitignore b/ios/MullvadREST/shadowsocks-proxy/.gitignore
new file mode 100644
index 0000000000..2f7896d1d1
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/ios/MullvadREST/shadowsocks-proxy/Cargo.toml b/ios/MullvadREST/shadowsocks-proxy/Cargo.toml
new file mode 100644
index 0000000000..364952f73c
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "shadowsocks-proxy"
+version = "0.0.0"
+edition = "2021"
+license = "GPL-3.0"
+publish = false
+
+[lib]
+crate-type = [ "rlib", "staticlib" ]
+bench = false
+
+[dependencies]
+shadowsocks-service.git = "https://github.com/mullvad/shadowsocks-rust"
+shadowsocks-service.rev = "8f6afd081a9440fff2dda565908eddc27a3295f1"
+shadowsocks-service.features = [ "local", "stream-cipher", "local-http", "local-tunnel" ]
+
+tokio = "1"
+libc = "0.2"
+log = "0.4"
+
+[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
+oslog = "0.2"
+
+[target.'cfg(target_os = "ios")'.build-dependencies]
+cbindgen = "0.24"
diff --git a/ios/MullvadREST/shadowsocks-proxy/build.rs b/ios/MullvadREST/shadowsocks-proxy/build.rs
new file mode 100644
index 0000000000..3a0ffb572b
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/build.rs
@@ -0,0 +1,14 @@
+#[cfg(target_os = "ios")]
+fn main() {
+ let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ cbindgen::Builder::new()
+ .with_crate(crate_dir)
+ .with_language(cbindgen::Language::C)
+ .generate()
+ .expect("failed to generate bindings")
+ .write_to_file("include/shadowsocks.h");
+}
+
+#[cfg(not(target_os = "ios"))]
+fn main() {}
diff --git a/ios/MullvadREST/shadowsocks-proxy/build.sh b/ios/MullvadREST/shadowsocks-proxy/build.sh
new file mode 100644
index 0000000000..7617bc91c8
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/build.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+if [ "$#" -ne 1 ]
+then
+ echo "Usage (note: only call inside xcode!):"
+ echo "compile-library.sh <FFI_TARGET> <buildvariant>"
+ exit 1
+fi
+
+# what to pass to cargo build -p, e.g. your_lib_ffi
+FFI_TARGET=$1
+
+RELFLAG=
+if [[ "$CONFIGURATION" -eq "Release" ]]; then
+ RELFLAG=--release
+fi
+
+set -euvx
+
+if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then
+ # Assume we're in Xcode, which means we're probably cross-compiling.
+ # In this case, we need to add an extra library search path for build scripts and proc-macros,
+ # which run on the host instead of the target.
+ # (macOS Big Sur does not have linkable libraries in /usr/lib/.)
+ export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}"
+fi
+
+IS_SIMULATOR=0
+if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then
+ IS_SIMULATOR=1
+fi
+
+for arch in $ARCHS; do
+ case "$arch" in
+ x86_64)
+ if [ $IS_SIMULATOR -eq 0 ]; then
+ echo "Building for x86_64, but not a simulator build. What's going on?" >&2
+ exit 2
+ fi
+
+ # Intel iOS simulator
+ export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios"
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib $RELFLAG --target x86_64-apple-ios
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib --target x86_64-apple-ios
+ ;;
+
+ arm64)
+ if [ $IS_SIMULATOR -eq 0 ]; then
+ # Hardware iOS targets
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib $RELFLAG --target aarch64-apple-ios
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib --target aarch64-apple-ios
+ else
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib $RELFLAG --target aarch64-apple-ios-sim
+ $HOME/.cargo/bin/cargo build -p $FFI_TARGET --lib --target aarch64-apple-ios-sim
+ fi
+ esac
+done
diff --git a/ios/MullvadREST/shadowsocks-proxy/include/shadowsocks.h b/ios/MullvadREST/shadowsocks-proxy/include/shadowsocks.h
new file mode 100644
index 0000000000..eecb240d67
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/include/shadowsocks.h
@@ -0,0 +1,20 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct ProxyHandle {
+ void *context;
+ uint16_t port;
+} ProxyHandle;
+
+int32_t start_shadowsocks_proxy(const uint8_t *addr,
+ uintptr_t addr_len,
+ uint16_t port,
+ const uint8_t *password,
+ uintptr_t password_len,
+ const uint8_t *cipher,
+ uintptr_t cipher_len,
+ struct ProxyHandle *proxy_config);
+
+int32_t stop_shadowsocks_proxy(struct ProxyHandle *proxy_config);
diff --git a/ios/MullvadREST/shadowsocks-proxy/src/bin/run.rs b/ios/MullvadREST/shadowsocks-proxy/src/bin/run.rs
new file mode 100644
index 0000000000..073bd9cc02
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/src/bin/run.rs
@@ -0,0 +1,17 @@
+use std::{net::SocketAddr, str::FromStr};
+
+fn main() {
+ let socketaddr = SocketAddr::from_str("185.65.135.117:443").unwrap();
+ let password = "mullvad";
+ let cipher = "aes-256-gcm";
+
+ let (port, handle) = shadowsocks_proxy::run_forwarding_proxy(socketaddr, password, cipher)
+ .expect("failed to start SOCKS proxy");
+
+ println!("Running proxy on port {port}");
+
+ let _ = std::io::stdin().read_line(&mut String::new());
+ println!("Stopping proxy");
+ handle.stop();
+ println!("Done");
+}
diff --git a/ios/MullvadREST/shadowsocks-proxy/src/bin/run_unsafe.rs b/ios/MullvadREST/shadowsocks-proxy/src/bin/run_unsafe.rs
new file mode 100644
index 0000000000..1a3be6d541
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/src/bin/run_unsafe.rs
@@ -0,0 +1,51 @@
+use std::{
+ net::{Ipv4Addr, SocketAddr},
+ str::FromStr,
+};
+
+fn main() {
+ let socketaddr = SocketAddr::from_str("185.65.135.117:443").unwrap();
+ let password = "mullvad";
+ let cipher = "aes-256-gcm";
+
+ let cipher_ptr = cipher.as_ptr();
+ let cipher_size = cipher.as_bytes().len();
+
+ let password_ptr = password.as_ptr();
+ let password_size = password.as_bytes().len();
+
+ let addr = Ipv4Addr::from_str("185.65.135.117").unwrap();
+ let addr_bytes = addr.octets();
+ let addr_ptr = addr_bytes.as_ptr();
+
+ let mut ctx = shadowsocks_proxy::ProxyHandle {
+ port: 0,
+ context: std::ptr::null_mut(),
+ };
+
+ let retval = unsafe {
+ shadowsocks_proxy::start_shadowsocks_proxy(
+ addr_ptr,
+ addr_bytes.len(),
+ socketaddr.port(),
+ password_ptr,
+ password_size,
+ cipher_ptr,
+ cipher_size,
+ &mut ctx as *mut _,
+ )
+ };
+ if retval != 0 {
+ println!("Failed to start proxy - {retval}");
+ return;
+ }
+
+ println!("Running proxy on port {}", ctx.port);
+ let _ = std::io::stdin().read_line(&mut String::new());
+ println!("Stopping proxy");
+ let retval = unsafe { shadowsocks_proxy::stop_shadowsocks_proxy(&mut ctx as *mut _) };
+ if retval != 0 {
+ println!("Failed to stop proxy");
+ }
+ println!("Done");
+}
diff --git a/ios/MullvadREST/shadowsocks-proxy/src/ffi.rs b/ios/MullvadREST/shadowsocks-proxy/src/ffi.rs
new file mode 100644
index 0000000000..d8c575ef7e
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/src/ffi.rs
@@ -0,0 +1,128 @@
+use super::{run_forwarding_proxy, ShadowsocksHandle};
+
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+use std::sync::Once;
+
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+static INIT_LOGGING: Once = Once::new();
+
+#[repr(C)]
+pub struct ProxyHandle {
+ pub context: *mut libc::c_void,
+ pub port: u16,
+}
+
+/// # Safety
+/// `addr`, `password`, `cipher` must be valid for the lifetime of this function call and they must
+/// be backed by the amount of bytes as stored in the respective `*_len` parameters.
+///
+/// `proxy_config` must be pointing to a valid memory region for the size of a `ProxyHandle`
+/// instance.
+#[no_mangle]
+pub unsafe extern "C" fn start_shadowsocks_proxy(
+ addr: *const u8,
+ addr_len: usize,
+ port: u16,
+ password: *const u8,
+ password_len: usize,
+ cipher: *const u8,
+ cipher_len: usize,
+ proxy_config: *mut ProxyHandle,
+) -> i32 {
+ #[cfg(any(target_os = "macos", target_os = "ios"))]
+ INIT_LOGGING.call_once(|| {
+ let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.ShadowSocks")
+ .level_filter(log::LevelFilter::Info)
+ .init();
+ });
+
+ let bridge_ip = if let Some(addr) = unsafe { parse_ip_addr(addr, addr_len) } {
+ addr
+ } else {
+ return -1;
+ };
+
+ let bridge_addr = SocketAddr::new(bridge_ip, port);
+
+ let password = if let Some(password) = unsafe { parse_str(password, password_len) } {
+ password
+ } else {
+ return -1;
+ };
+
+ let cipher = if let Some(cipher) = unsafe { parse_str(cipher, cipher_len) } {
+ cipher
+ } else {
+ return -1;
+ };
+
+ let (port, handle) = match run_forwarding_proxy(bridge_addr, &password, &cipher) {
+ Ok((port, handle)) => (port, handle),
+ Err(err) => {
+ log::error!("Failed to run HTTP proxy {}", err);
+ return err.raw_os_error().unwrap_or(-1);
+ }
+ };
+ let handle = Box::new(handle);
+
+ unsafe {
+ std::ptr::write(
+ proxy_config,
+ ProxyHandle {
+ port,
+ context: Box::into_raw(handle) as *mut _,
+ },
+ )
+ }
+
+ 0
+}
+/// # Safety
+/// `proxy_config` must be pointing to a valid instance of a `ProxyInstance`, as instantiated by
+/// `start_shadowsocks_proxy`.
+#[no_mangle]
+pub unsafe extern "C" fn stop_shadowsocks_proxy(proxy_config: *mut ProxyHandle) -> i32 {
+ let context_ptr = unsafe { (*proxy_config).context };
+ if context_ptr.is_null() {
+ return -1;
+ }
+
+ let proxy_handle: Box<ShadowsocksHandle> = unsafe { Box::from_raw(context_ptr as *mut _) };
+ proxy_handle.stop();
+ unsafe { (*proxy_config).context = std::ptr::null_mut() };
+ 0
+}
+/// Constructs a new IP address from a pointer containing bytes representing an IP address.
+///
+/// SAFETY: `addr` must be a pointer to at least `addr_len` bytes.
+unsafe fn parse_ip_addr(addr: *const u8, addr_len: usize) -> Option<IpAddr> {
+ match addr_len {
+ 4 => {
+ // SAFETY: addr pointer must point to at least addr_len bytes
+ let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
+ Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).into())
+ }
+ 16 => {
+ // SAFETY: addr pointer must point to at least addr_len bytes
+ let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
+ let mut addr_arr = [0u8; 16];
+ addr_arr.as_mut_slice().copy_from_slice(bytes);
+
+ Some(Ipv6Addr::from(addr_arr).into())
+ }
+ anything_else => {
+ log::error!("Bad IP address length {anything_else}");
+ None
+ }
+ }
+}
+
+/// Allocates a new string with the contents of `data` if it contains only valid UTF-8 bytes.
+///
+/// SAFETY: `data` must be a valid pointer to `len` amount of bytes
+unsafe fn parse_str(data: *const u8, len: usize) -> Option<String> {
+ // SAFETY: data pointer must be valid for the size of len
+ let bytes = unsafe { std::slice::from_raw_parts(data, len) };
+ String::from_utf8(bytes.to_vec()).ok()
+}
diff --git a/ios/MullvadREST/shadowsocks-proxy/src/lib.rs b/ios/MullvadREST/shadowsocks-proxy/src/lib.rs
new file mode 100644
index 0000000000..66398cb9eb
--- /dev/null
+++ b/ios/MullvadREST/shadowsocks-proxy/src/lib.rs
@@ -0,0 +1,160 @@
+use shadowsocks_service::{
+ config::{
+ Config, ConfigType, LocalConfig, LocalInstanceConfig, ProtocolType, ServerInstanceConfig,
+ },
+ local::Server,
+ shadowsocks::{config::ServerConfig, crypto::CipherKind},
+};
+use std::{
+ io,
+ net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},
+ str::FromStr,
+};
+use tokio::sync::oneshot;
+
+mod ffi;
+pub use ffi::{start_shadowsocks_proxy, stop_shadowsocks_proxy, ProxyHandle};
+
+pub fn run_forwarding_proxy(
+ bridge_addr: SocketAddr,
+ password: &str,
+ cipher: &str,
+) -> io::Result<(u16, ShadowsocksHandle)> {
+ let runtime = ShadowsocksRuntime::new(bridge_addr, password, cipher)?;
+ let port = runtime.port();
+ let handle = runtime.run()?;
+
+ Ok((port, handle))
+}
+
+struct ShadowsocksRuntime {
+ runtime: tokio::runtime::Runtime,
+ config: Config,
+ local_port: u16,
+}
+
+pub struct ShadowsocksHandle {
+ tx: oneshot::Sender<oneshot::Sender<()>>,
+}
+
+impl ShadowsocksHandle {
+ pub fn stop(self) {
+ let (shutdown_tx, shutdown_rx) = oneshot::channel();
+ let _ = self.tx.send(shutdown_tx);
+ let _ = shutdown_rx.blocking_recv();
+ }
+}
+
+impl ShadowsocksRuntime {
+ pub fn new(bridge_addr: SocketAddr, password: &str, cipher: &str) -> io::Result<Self> {
+ let runtime = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()?;
+
+ let (config, local_port) = Self::create_config(bridge_addr, password, cipher)?;
+ Ok(Self {
+ runtime,
+ config,
+ local_port,
+ })
+ }
+
+ pub fn port(&self) -> u16 {
+ self.local_port
+ }
+
+ pub fn run(self) -> io::Result<ShadowsocksHandle> {
+ let (tx, rx) = oneshot::channel();
+ let (shutdown_tx, shutdown_rx) = oneshot::channel();
+ let (startup_tx, startup_rx) = oneshot::channel();
+ std::thread::spawn(move || {
+ self.run_service_inner(rx, startup_tx);
+ });
+
+ match startup_rx.blocking_recv() {
+ Ok(Ok(_)) => Ok(ShadowsocksHandle { tx }),
+ Ok(Err(err)) => {
+ let _ = tx.send(shutdown_tx);
+ let _ = shutdown_rx.blocking_recv();
+ Err(err)
+ }
+ Err(_) => {
+ let _ = tx.send(shutdown_tx);
+ let _ = shutdown_rx.blocking_recv();
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Tokio runtime crashed",
+ ))
+ }
+ }
+ }
+
+ fn run_service_inner(
+ self,
+ shutdown_rx: oneshot::Receiver<oneshot::Sender<()>>,
+ startup_done_tx: oneshot::Sender<io::Result<()>>,
+ ) {
+ let Self {
+ config, runtime, ..
+ } = self;
+
+ std::thread::spawn(move || {
+ runtime.spawn(async move {
+ match Server::create(config).await {
+ Ok(server) => {
+ let _ = startup_done_tx.send(Ok(()));
+ let _ = server.wait_until_exit().await;
+ }
+ Err(err) => {
+ let _ = startup_done_tx.send(Err(err));
+ }
+ }
+ });
+ if let Ok(shutdown_tx) = runtime.block_on(shutdown_rx) {
+ std::mem::drop(runtime);
+ let _ = shutdown_tx.send(());
+ }
+ });
+ }
+
+ pub fn create_config(
+ bridge_addr: SocketAddr,
+ password: &str,
+ cipher: &str,
+ ) -> io::Result<(Config, u16)> {
+ let mut cfg = Config::new(ConfigType::Local);
+ let free_port = get_free_port()?;
+ let bind_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), free_port);
+
+ let mut local_config = LocalConfig::new_with_addr(bind_addr.into(), ProtocolType::Tunnel);
+ local_config.forward_addr =
+ // TODO: Remove hardcoded API address
+ Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(45, 83, 223, 196)), 443).into());
+ cfg.local = vec![LocalInstanceConfig::with_local_config(local_config)];
+
+ let cipher = match CipherKind::from_str(cipher) {
+ Ok(cipher) => cipher,
+ Err(err) => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("Invalid cipher specified: {}", err),
+ ));
+ }
+ };
+ let server_config = ServerInstanceConfig::with_server_config(ServerConfig::new(
+ bridge_addr,
+ password,
+ cipher,
+ ));
+
+ cfg.server = vec![server_config];
+
+ Ok((cfg, free_port))
+ }
+}
+
+fn get_free_port() -> io::Result<u16> {
+ let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
+ let port = TcpListener::bind(bind_addr)?.local_addr()?.port();
+ Ok(port)
+}
diff --git a/ios/MullvadRESTTests/TransportStrategyTests.swift b/ios/MullvadRESTTests/TransportStrategyTests.swift
new file mode 100644
index 0000000000..75ed74279d
--- /dev/null
+++ b/ios/MullvadRESTTests/TransportStrategyTests.swift
@@ -0,0 +1,40 @@
+//
+// TransportStrategyTests.swift
+// MullvadRESTTests
+//
+// Created by Marco Nikic on 2023-04-27.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import XCTest
+
+final class TransportStrategyTests: XCTestCase {
+ func testEveryThirdConnectionAttemptsIsDirect() {
+ var strategy = REST.TransportStrategy()
+
+ for index in 0 ... 12 {
+ let expectedResult: REST.TransportStrategy.Transport
+ expectedResult = index.isMultiple(of: 3) ? .useURLSession : .useShadowSocks
+ XCTAssertEqual(strategy.connectionTransport(), expectedResult)
+ strategy.didFail()
+ }
+ }
+
+ func testLoadingFromCacheDoesNotImpactStrategy() throws {
+ var strategy = REST.TransportStrategy()
+
+ // Fail twice, the next suggested transport mode should be via Shadowsocks proxy
+ strategy.didFail()
+ strategy.didFail()
+ XCTAssertEqual(strategy.connectionTransport(), .useShadowSocks)
+
+ // Serialize the strategy and reload it from memory to simulate an application restart
+ let encodedRawStrategy = try JSONEncoder().encode(strategy)
+ var reloadedStrategy = try JSONDecoder().decode(REST.TransportStrategy.self, from: encodedRawStrategy)
+
+ // This should be the third failure, the next suggested transport will be a direct one
+ reloadedStrategy.didFail()
+ XCTAssertEqual(reloadedStrategy.connectionTransport(), .useURLSession)
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 3bab2cdcf9..ae304ecf80 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 01F1FF1C29F06124007083C3 /* ShadowSocksProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F1FF1B29F06124007083C3 /* ShadowSocksProxy.swift */; };
+ 01F1FF1E29F0627D007083C3 /* libshadowsocks_proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01F1FF1D29F0627D007083C3 /* libshadowsocks_proxy.a */; };
062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 06799AB428F98CE700ACD94E /* le_root_cert.cer */; };
062B45AE28FD503000746E77 /* WireGuardKit in Frameworks */ = {isa = PBXBuildFile; productRef = 062B45AD28FD503000746E77 /* WireGuardKit */; };
062B45BC28FD8C3B00746E77 /* RESTDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B45BB28FD8C3B00746E77 /* RESTDefaults.swift */; };
@@ -134,6 +136,7 @@
586A950E290125F3007BAF2B /* ProductsRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */; };
586A950F29012BEE007BAF2B /* AddressCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114028F841390037AF9A /* AddressCacheTracker.swift */; };
586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */; };
+ 586F2BE229F6916F009E6924 /* shadowsocks.h in Headers */ = {isa = PBXBuildFile; fileRef = 586F2BE129F6916F009E6924 /* shadowsocks.h */; settings = {ATTRIBUTES = (Private, ); }; };
5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871167E2910035700D41AAC /* PreferencesInteractor.swift */; };
5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */; };
5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */; };
@@ -369,6 +372,10 @@
7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */; };
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; };
A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; };
+ A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */; };
+ A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; };
+ A92CAAC629F7D33C008ED922 /* TunnelTransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92CAAC529F7D33C008ED922 /* TunnelTransportProvider.swift */; };
+ A97FF54B2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54A2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift */; };
E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; };
E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; };
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; };
@@ -631,6 +638,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 01F1FF1B29F06124007083C3 /* ShadowSocksProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowSocksProxy.swift; sourceTree = "<group>"; };
+ 01F1FF1D29F0627D007083C3 /* libshadowsocks_proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libshadowsocks_proxy.a; path = ../target/debug/libshadowsocks_proxy.a; sourceTree = "<group>"; };
062B45BB28FD8C3B00746E77 /* RESTDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTDefaults.swift; sourceTree = "<group>"; };
063687AF28EB083800BE7161 /* ProxyURLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyURLRequest.swift; sourceTree = "<group>"; };
063687B928EB234F00BE7161 /* PacketTunnelTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelTransport.swift; sourceTree = "<group>"; };
@@ -777,6 +786,7 @@
586A95112901321B007BAF2B /* IPv6Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv6Endpoint.swift; sourceTree = "<group>"; };
586A951329013235007BAF2B /* AnyIPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyIPEndpoint.swift; sourceTree = "<group>"; };
586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTunnelProviderMessageOperation.swift; sourceTree = "<group>"; };
+ 586F2BE129F6916F009E6924 /* shadowsocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shadowsocks.h; path = "shadowsocks-proxy/include/shadowsocks.h"; sourceTree = "<group>"; };
5871167E2910035700D41AAC /* PreferencesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesInteractor.swift; sourceTree = "<group>"; };
5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLog.swift; sourceTree = "<group>"; };
5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+IPAddress.swift"; sourceTree = "<group>"; };
@@ -971,6 +981,10 @@
7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; };
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; };
+ A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStrategy.swift; sourceTree = "<group>"; };
+ A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = "<group>"; };
+ A92CAAC529F7D33C008ED922 /* TunnelTransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelTransportProvider.swift; sourceTree = "<group>"; };
+ A97FF54A2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelTransportProvider.swift; sourceTree = "<group>"; };
E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; };
E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; };
E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; };
@@ -992,6 +1006,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 01F1FF1E29F0627D007083C3 /* libshadowsocks_proxy.a in Frameworks */,
58D223BF294C8AE90029F5F8 /* Operations.framework in Frameworks */,
58D2241D294C91D20029F5F8 /* MullvadLogging.framework in Frameworks */,
58D223DC294C8EB90029F5F8 /* MullvadTypes.framework in Frameworks */,
@@ -1136,6 +1151,7 @@
582FFA82290A84E700895745 /* Info.plist */,
062B45A228FD4C0F00746E77 /* Assets */,
06799ABE28F98E1D00ACD94E /* MullvadREST.h */,
+ 586F2BE129F6916F009E6924 /* shadowsocks.h */,
06AC114128F8413A0037AF9A /* AddressCache.swift */,
06FAE67128F83CA40033DD93 /* HTTP.swift */,
06FAE67B28F83CA50033DD93 /* REST.swift */,
@@ -1159,10 +1175,12 @@
5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */,
06FAE67528F83CA40033DD93 /* RESTTaskIdentifier.swift */,
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */,
+ A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */,
06FAE66528F83CA30033DD93 /* RESTURLSession.swift */,
06FAE67728F83CA40033DD93 /* ServerRelaysResponse.swift */,
06FAE66B28F83CA30033DD93 /* SSLPinningURLSessionDelegate.swift */,
06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */,
+ 01F1FF1B29F06124007083C3 /* ShadowSocksProxy.swift */,
);
path = MullvadREST;
sourceTree = "<group>";
@@ -1477,6 +1495,7 @@
children = (
58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */,
587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */,
+ A97FF54A2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift */,
);
path = SimulatorTunnelProvider;
sourceTree = "<group>";
@@ -1546,6 +1565,7 @@
584F991F2902CBDD001F858D /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 01F1FF1D29F0627D007083C3 /* libshadowsocks_proxy.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -1825,6 +1845,7 @@
58E07298288031D5008902F8 /* WireGuardAdapterError+Localization.swift */,
58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */,
58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */,
+ A92CAAC529F7D33C008ED922 /* TunnelTransportProvider.swift */,
);
path = PacketTunnel;
sourceTree = "<group>";
@@ -1940,6 +1961,7 @@
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
58FBFBF0291630700020E046 /* DurationTests.swift */,
58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
+ A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */,
);
path = MullvadRESTTests;
sourceTree = "<group>";
@@ -1959,6 +1981,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 586F2BE229F6916F009E6924 /* shadowsocks.h in Headers */,
06799ACE28F98E1D00ACD94E /* MullvadREST.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2034,6 +2057,8 @@
isa = PBXNativeTarget;
buildConfigurationList = 06799AD328F98E1D00ACD94E /* Build configuration list for PBXNativeTarget "MullvadREST" */;
buildPhases = (
+ 588E4EB028FEF1CA008046E3 /* Run prebuild script */,
+ 01F1FF1F29F07D63007083C3 /* ShellScript */,
06799AB728F98E1D00ACD94E /* Headers */,
06799AB828F98E1D00ACD94E /* Sources */,
06799AB928F98E1D00ACD94E /* Frameworks */,
@@ -2477,6 +2502,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 01F1FF1F29F07D63007083C3 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nCARGO_TARGET_DIR=${PROJECT_DIR}/../target bash ${PROJECT_DIR}/MullvadREST/shadowsocks-proxy/build.sh shadowsocks-proxy\n";
+ };
063F028D2902BC8E001FA09F /* Run prebuild script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -2524,6 +2567,7 @@
06799AEA28F98E4800ACD94E /* RESTProxy.swift in Sources */,
06799ADD28F98E4800ACD94E /* RESTError.swift in Sources */,
06799ADB28F98E4800ACD94E /* RESTProxyFactory.swift in Sources */,
+ 01F1FF1C29F06124007083C3 /* ShadowSocksProxy.swift in Sources */,
06799AF228F98E4800ACD94E /* RESTAccessTokenManager.swift in Sources */,
06799AF328F98E4800ACD94E /* RESTAuthenticationProxy.swift in Sources */,
06799AE628F98E4800ACD94E /* ServerRelaysResponse.swift in Sources */,
@@ -2536,6 +2580,7 @@
06799ADF28F98E4800ACD94E /* RESTDevicesProxy.swift in Sources */,
06799ADA28F98E4800ACD94E /* RESTResponseHandler.swift in Sources */,
062B45BC28FD8C3B00746E77 /* RESTDefaults.swift in Sources */,
+ A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */,
06799AE428F98E4800ACD94E /* RESTAccountsProxy.swift in Sources */,
5897F1742913EAF800AF5695 /* ExponentialBackoff.swift in Sources */,
06799AE328F98E4800ACD94E /* RESTNetworkOperation.swift in Sources */,
@@ -2654,6 +2699,7 @@
587C93002986E2B600FB9664 /* TermsOfServiceCoordinator.swift in Sources */,
5864AF0929C78850005B0CD9 /* PreferencesCellFactory.swift in Sources */,
587B7536266528A200DEF7E9 /* NotificationManager.swift in Sources */,
+ A97FF54B2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift in Sources */,
5820EDA9288FE064006BF4E4 /* DeviceManagementInteractor.swift in Sources */,
58FB865A26EA214400F188BC /* RelayCacheTrackerObserver.swift in Sources */,
58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */,
@@ -2818,6 +2864,7 @@
583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */,
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
+ A92CAAC629F7D33C008ED922 /* TunnelTransportProvider.swift in Sources */,
58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2906,6 +2953,7 @@
buildActionMask = 2147483647;
files = (
A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */,
+ A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */,
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
58FBFBF1291630700020E046 /* DurationTests.swift in Sources */,
);
@@ -3164,6 +3212,10 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
+ "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/debug";
+ "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/debug";
+ "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/debug";
+ MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadREST/module.private.modulemap;
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadREST";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -3196,6 +3248,10 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
+ "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/release";
+ "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/release";
+ "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/release";
+ MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadREST/module.private.modulemap;
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadREST";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 3b4fa835a2..0000000000
--- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "swift-log",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-log.git",
- "state" : {
- "revision" : "173f567a2dfec11d74588eea82cecea555bdc0bc",
- "version" : "1.4.0"
- }
- },
- {
- "identity" : "wireguard-apple",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/mullvad/wireguard-apple.git",
- "state" : {
- "revision" : "6baeac49a14313a7b8b7a956f67f4a47a6ae7a41"
- }
- }
- ],
- "version" : 2
-}
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 26ac51cccf..69af7faa45 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -59,9 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
proxyFactory = REST.ProxyFactory.makeProxyFactory(
- transportProvider: { [weak self] in
- return self?.transportMonitor.transport
- },
+ transportProvider: { [weak self] in self?.transportMonitor },
addressCache: addressCache
)
@@ -70,6 +68,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
devicesProxy = proxyFactory.createDevicesProxy()
relayCacheTracker = RelayCacheTracker(application: application, apiProxy: apiProxy)
+
addressCacheTracker = AddressCacheTracker(
application: application,
apiProxy: apiProxy,
@@ -93,7 +92,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy: accountsProxy
)
- transportMonitor = TransportMonitor(tunnelManager: tunnelManager, tunnelStore: tunnelStore)
+ transportMonitor = TransportMonitor(
+ tunnelManager: tunnelManager,
+ tunnelStore: tunnelStore,
+ relayCacheTracker: relayCacheTracker
+ )
#if targetEnvironment(simulator)
// Configure mock tunnel provider on simulator
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index 7d2331b92d..2078646be2 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
@@ -21,15 +21,22 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
private var selectorResult: RelaySelectorResult?
private let urlRequestProxy: URLRequestProxy
private let relayCacheTracker: RelayCacheTracker
+ private let simulatorTransportProvider: SimulatorTunnelTransportProvider
private let providerLogger = Logger(label: "SimulatorTunnelProviderHost")
private let dispatchQueue = DispatchQueue(label: "SimulatorTunnelProviderHostQueue")
init(relayCacheTracker: RelayCacheTracker) {
self.relayCacheTracker = relayCacheTracker
+
+ let urlSession = REST.makeURLSession()
+ let urlSessionTransport = REST.URLSessionTransport(urlSession: urlSession)
+ let simulatorTransportProvider = SimulatorTunnelTransportProvider(urlSessionTransport: urlSessionTransport)
+ self.simulatorTransportProvider = simulatorTransportProvider
+
self.urlRequestProxy = URLRequestProxy(
- urlSession: REST.makeURLSession(),
- dispatchQueue: dispatchQueue
+ dispatchQueue: dispatchQueue,
+ transportProvider: { simulatorTransportProvider }
)
}
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelTransportProvider.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelTransportProvider.swift
new file mode 100644
index 0000000000..a571101e3f
--- /dev/null
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelTransportProvider.swift
@@ -0,0 +1,26 @@
+//
+// SimulatorTunnelTransportProvider.swift
+// MullvadVPN
+//
+// Created by Marco Nikic on 2023-05-10.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+
+final class SimulatorTunnelTransportProvider: RESTTransportProvider {
+ private let urlSessionTransport: REST.URLSessionTransport
+
+ init(urlSessionTransport: REST.URLSessionTransport) {
+ self.urlSessionTransport = urlSessionTransport
+ }
+
+ func transport() -> MullvadREST.RESTTransport? {
+ urlSessionTransport
+ }
+
+ func shadowSocksTransport() -> MullvadREST.RESTTransport? {
+ urlSessionTransport
+ }
+}
diff --git a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
index 4ee628751d..3314fc3823 100644
--- a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
+++ b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
@@ -18,16 +18,28 @@ struct PacketTunnelTransport: RESTTransport {
}
let tunnel: Tunnel
+ let useShadowsocksTransport: Bool
- init(tunnel: Tunnel) {
+ init(tunnel: Tunnel, useShadowsocksTransport: Bool) {
self.tunnel = tunnel
+ self.useShadowsocksTransport = useShadowsocksTransport
}
func sendRequest(
_ request: URLRequest,
completion: @escaping (Data?, URLResponse?, Error?) -> Void
- ) throws -> Cancellable {
- let proxyRequest = try ProxyURLRequest(id: UUID(), urlRequest: request)
+ ) -> Cancellable {
+ let proxyRequest = ProxyURLRequest(
+ id: UUID(),
+ urlRequest: request,
+ useShadowsocksTransport: useShadowsocksTransport
+ )
+
+ // If the URL provided to the proxy request was invalid, indicate failure via `.badURL` and return a no-op.
+ guard let proxyRequest = proxyRequest else {
+ completion(nil, nil, URLError(.badURL))
+ return AnyCancellable {}
+ }
return tunnel.sendRequest(proxyRequest) { result in
switch result {
diff --git a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
index 9f02429348..e37c416682 100644
--- a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
+++ b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
@@ -7,21 +7,83 @@
//
import Foundation
+import MullvadLogging
import MullvadREST
+import RelayCache
+import RelaySelector
-final class TransportMonitor {
+final class TransportMonitor: RESTTransportProvider {
private let tunnelManager: TunnelManager
private let tunnelStore: TunnelStore
private let urlSessionTransport: REST.URLSessionTransport
+ private let relayCacheTracker: RelayCacheTracker
+ private let logger = Logger(label: "TransportMonitor")
- init(tunnelManager: TunnelManager, tunnelStore: TunnelStore) {
+ // MARK: -
+
+ // MARK: Public API
+
+ init(tunnelManager: TunnelManager, tunnelStore: TunnelStore, relayCacheTracker: RelayCacheTracker) {
self.tunnelManager = tunnelManager
self.tunnelStore = tunnelStore
+ self.relayCacheTracker = relayCacheTracker
urlSessionTransport = REST.URLSessionTransport(urlSession: REST.makeURLSession())
}
- var transport: RESTTransport? {
+ public func transport() -> MullvadREST.RESTTransport? {
+ return selectTransport(urlSessionTransport, useShadowsocksTransport: false)
+ }
+
+ public func shadowSocksTransport() -> RESTTransport? {
+ let shadowSocksTransport: RESTTransport
+ do {
+ let cachedRelays = try relayCacheTracker.getCachedRelays()
+
+ let shadowSocksConfiguration = RelaySelector.getShadowsocksTCPBridge(relays: cachedRelays.relays)
+ let shadowSocksBridgeRelay = RelaySelector.getShadowSocksRelay(relays: cachedRelays.relays)
+
+ guard let shadowSocksConfiguration = shadowSocksConfiguration,
+ let shadowSocksBridgeRelay = shadowSocksBridgeRelay
+ else {
+ logger.error("Could not get shadow socks bridge information.")
+ return nil
+ }
+
+ let shadowSocksURLSession = urlSessionTransport.urlSession
+ let transport = REST.URLSessionShadowSocksTransport(
+ urlSession: shadowSocksURLSession,
+ shadowSocksConfiguration: shadowSocksConfiguration,
+ shadowSocksBridgeRelay: shadowSocksBridgeRelay
+ )
+
+ shadowSocksTransport = transport
+ } catch {
+ logger.error(
+ error: error,
+ message: "Could not create shadow socks transport."
+ )
+ return nil
+ }
+ return selectTransport(shadowSocksTransport, useShadowsocksTransport: true)
+ }
+
+ // MARK: -
+
+ // MARK: Private API
+
+ /// Selects a transport to use for sending an `URLRequest`
+ ///
+ /// This method returns the appropriate transport layer based on whether a tunnel is available, and whether it
+ /// should be bypassed
+ /// whenever a transport is requested.
+ ///
+ /// - Parameters:
+ /// - transport: The transport to use if there is no tunnel, or if it shouldn't be bypassed
+ /// - useShadowsocksTransport: A hint for enforcing a Shadowsocks transport when proxying a request via an
+ /// available `Tunnel`
+ /// - Returns: A transport to use for sending an `URLRequest`
+ private func selectTransport(_ transport: RESTTransport, useShadowsocksTransport: Bool) -> RESTTransport {
let tunnel = tunnelStore.getPersistentTunnels().first { tunnel in
return tunnel.status == .connecting ||
tunnel.status == .reasserting ||
@@ -29,10 +91,12 @@ final class TransportMonitor {
}
if let tunnel = tunnel, shouldByPassVPN(tunnel: tunnel) {
- return PacketTunnelTransport(tunnel: tunnel)
- } else {
- return urlSessionTransport
+ return PacketTunnelTransport(
+ tunnel: tunnel,
+ useShadowsocksTransport: useShadowsocksTransport
+ )
}
+ return transport
}
private func shouldByPassVPN(tunnel: Tunnel) -> Bool {
diff --git a/ios/MullvadVPNTests/RelaySelectorTests.swift b/ios/MullvadVPNTests/RelaySelectorTests.swift
index f0276f5ba0..f8f9eb60bc 100644
--- a/ios/MullvadVPNTests/RelaySelectorTests.swift
+++ b/ios/MullvadVPNTests/RelaySelectorTests.swift
@@ -152,5 +152,5 @@ private let sampleRelays = REST.ServerRelaysResponse(
),
]
),
- bridge: REST.ServerBridges(shadowsocks: [])
+ bridge: REST.ServerBridges(shadowsocks: [], relays: [])
)
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 4a0a341aba..fcae0e4ec4 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -152,14 +152,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
let urlSession = REST.makeURLSession()
let urlSessionTransport = REST.URLSessionTransport(urlSession: urlSession)
+ let transportProvider = TunnelTransportProvider(
+ urlSessionTransport: urlSessionTransport,
+ relayCache: relayCache
+ )
+
let proxyFactory = REST.ProxyFactory.makeProxyFactory(
- transportProvider: { () -> RESTTransport? in
- return urlSessionTransport
- },
+ transportProvider: { transportProvider },
addressCache: addressCache
)
- urlRequestProxy = URLRequestProxy(urlSession: urlSession, dispatchQueue: dispatchQueue)
+ urlRequestProxy = URLRequestProxy(
+ dispatchQueue: dispatchQueue,
+ transportProvider: { transportProvider }
+ )
accountsProxy = proxyFactory.createAccountsProxy()
devicesProxy = proxyFactory.createDevicesProxy()
diff --git a/ios/PacketTunnel/TunnelTransportProvider.swift b/ios/PacketTunnel/TunnelTransportProvider.swift
new file mode 100644
index 0000000000..85e0600c49
--- /dev/null
+++ b/ios/PacketTunnel/TunnelTransportProvider.swift
@@ -0,0 +1,55 @@
+//
+// TunnelTransportProvider.swift
+// PacketTunnel
+//
+// Created by Marco Nikic on 2023-04-25.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Logging
+import MullvadREST
+import RelayCache
+import RelaySelector
+
+final class TunnelTransportProvider: RESTTransportProvider {
+ private let urlSessionTransport: REST.URLSessionTransport
+ private let relayCache: RelayCache
+ private let logger = Logger(label: "TunnelTransportProvider")
+
+ init(urlSessionTransport: REST.URLSessionTransport, relayCache: RelayCache) {
+ self.urlSessionTransport = urlSessionTransport
+ self.relayCache = relayCache
+ }
+
+ func transport() -> MullvadREST.RESTTransport? {
+ urlSessionTransport
+ }
+
+ func shadowSocksTransport() -> MullvadREST.RESTTransport? {
+ do {
+ let cachedRelays = try relayCache.read()
+ let shadowSocksConfiguration = RelaySelector.getShadowsocksTCPBridge(relays: cachedRelays.relays)
+ let shadowSocksBridgeRelay = RelaySelector.getShadowSocksRelay(relays: cachedRelays.relays)
+
+ guard let shadowSocksConfiguration = shadowSocksConfiguration,
+ let shadowSocksBridgeRelay = shadowSocksBridgeRelay
+ else {
+ logger.error("Could not get shadow socks bridge information.")
+ return nil
+ }
+
+ let shadowSocksURLSession = urlSessionTransport.urlSession
+ let shadowSocksTransport = REST.URLSessionShadowSocksTransport(
+ urlSession: shadowSocksURLSession,
+ shadowSocksConfiguration: shadowSocksConfiguration,
+ shadowSocksBridgeRelay: shadowSocksBridgeRelay
+ )
+
+ return shadowSocksTransport
+ } catch {
+ logger.error(error: error)
+ }
+ return nil
+ }
+}
diff --git a/ios/RelaySelector/RelaySelector.swift b/ios/RelaySelector/RelaySelector.swift
index 0feed32cf4..11625b1ca1 100644
--- a/ios/RelaySelector/RelaySelector.swift
+++ b/ios/RelaySelector/RelaySelector.swift
@@ -20,6 +20,13 @@ public enum RelaySelector {
return relays.bridge.shadowsocks.filter { $0.protocol == "tcp" }.randomElement()
}
+ /// Return a random Shadowsocks bridge relay, or `nil` if no relay were found.
+ /// - Parameter relays: The list of relays to randomly select from
+ /// - Returns: A Shadowsocks relay or `nil` if no relay were found.
+ public static func getShadowSocksRelay(relays: REST.ServerRelaysResponse) -> REST.BridgeRelay? {
+ relays.bridge.relays.randomElement()
+ }
+
/**
Filters relay list using given constraints and selects random relay.
Throws an error if there are no relays satisfying the given constraints.
diff --git a/ios/TunnelProviderMessaging/ProxyURLRequest.swift b/ios/TunnelProviderMessaging/ProxyURLRequest.swift
index acc346f2fc..cf392eee12 100644
--- a/ios/TunnelProviderMessaging/ProxyURLRequest.swift
+++ b/ios/TunnelProviderMessaging/ProxyURLRequest.swift
@@ -15,6 +15,7 @@ public struct ProxyURLRequest: Codable {
public let method: String?
public let httpBody: Data?
public let httpHeaders: [String: String]?
+ public let useShadowsocksTransport: Bool
public var urlRequest: URLRequest {
var urlRequest = URLRequest(url: url)
@@ -24,21 +25,14 @@ public struct ProxyURLRequest: Codable {
return urlRequest
}
- public init(id: UUID, urlRequest: URLRequest) throws {
- guard let url = urlRequest.url else {
- throw InvalidURLRequestError()
- }
+ public init?(id: UUID, urlRequest: URLRequest, useShadowsocksTransport: Bool = false) {
+ guard let urlRequestUrl = urlRequest.url else { return nil }
self.id = id
- self.url = url
+ url = urlRequestUrl
method = urlRequest.httpMethod
httpBody = urlRequest.httpBody
httpHeaders = urlRequest.allHTTPHeaderFields
- }
-}
-
-public struct InvalidURLRequestError: LocalizedError {
- public var errorDescription: String? {
- return "Invalid URLRequest URL."
+ self.useShadowsocksTransport = useShadowsocksTransport
}
}
diff --git a/ios/TunnelProviderMessaging/URLRequestProxy.swift b/ios/TunnelProviderMessaging/URLRequestProxy.swift
index 8d66819ebb..931cb4a571 100644
--- a/ios/TunnelProviderMessaging/URLRequestProxy.swift
+++ b/ios/TunnelProviderMessaging/URLRequestProxy.swift
@@ -7,20 +7,24 @@
//
import Foundation
+import MullvadREST
+import MullvadTypes
public final class URLRequestProxy {
/// Serial queue used for synchronizing access to class members.
private let dispatchQueue: DispatchQueue
- /// URL session used for proxy requests.
- private let urlSession: URLSession
+ private let transportProvider: () -> RESTTransportProvider?
/// List of all proxied network requests bypassing VPN.
- private var proxiedRequests: [UUID: URLSessionDataTask] = [:]
+ private var proxiedRequests: [UUID: Cancellable] = [:]
- public init(urlSession: URLSession, dispatchQueue: DispatchQueue) {
- self.urlSession = urlSession
+ public init(
+ dispatchQueue: DispatchQueue,
+ transportProvider: @escaping () -> RESTTransportProvider?
+ ) {
self.dispatchQueue = dispatchQueue
+ self.transportProvider = transportProvider
}
public func sendRequest(
@@ -28,29 +32,28 @@ public final class URLRequestProxy {
completionHandler: @escaping (ProxyURLResponse) -> Void
) {
dispatchQueue.async {
- let task = self.urlSession
- .dataTask(with: proxyRequest.urlRequest) { [weak self] data, response, error in
- guard let self = self else { return }
-
- self.dispatchQueue.async {
- let response = ProxyURLResponse(
- data: data,
- response: response,
- error: error
- )
-
- _ = self.removeRequest(identifier: proxyRequest.id)
+ // Instruct the Packet Tunnel to try to reach the API via the local shadow socks proxy instance if needed
+ let transportProvider = self.transportProvider()
+ let transport = proxyRequest.useShadowsocksTransport
+ ? transportProvider?.shadowSocksTransport()
+ : transportProvider?.transport()
+ guard let transport = transport else { return }
+ // The task sent by `transport.sendRequest` comes in an already resumed state
+ let task = transport.sendRequest(proxyRequest.urlRequest) { [weak self] data, response, error in
+ guard let self = self else { return }
+ // However there is no guarantee about which queue the execution resumes on
+ // Use `dispatchQueue` to guarantee thread safe access to `proxiedRequests`
+ self.dispatchQueue.async {
+ let response = ProxyURLResponse(data: data, response: response, error: error)
+ _ = self.removeRequest(identifier: proxyRequest.id)
- completionHandler(response)
- }
+ completionHandler(response)
}
-
+ }
// All tasks should have unique identifiers, but if not, cancel the task scheduled
// earlier.
let oldTask = self.addRequest(identifier: proxyRequest.id, task: task)
oldTask?.cancel()
-
- task.resume()
}
}
@@ -62,11 +65,13 @@ public final class URLRequestProxy {
}
}
- private func addRequest(identifier: UUID, task: URLSessionDataTask) -> URLSessionDataTask? {
+ private func addRequest(identifier: UUID, task: Cancellable) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
return proxiedRequests.updateValue(task, forKey: identifier)
}
- private func removeRequest(identifier: UUID) -> URLSessionDataTask? {
+ private func removeRequest(identifier: UUID) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
return proxiedRequests.removeValue(forKey: identifier)
}
}
diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml
index e31d54c5e3..fb40f49022 100644
--- a/mullvad-api/Cargo.toml
+++ b/mullvad-api/Cargo.toml
@@ -32,4 +32,4 @@ mullvad-types = { path = "../mullvad-types" }
talpid-types = { path = "../talpid-types" }
talpid-time = { path = "../talpid-time" }
-shadowsocks = { version = "1.15.3", default-features = false, features = ["stream-cipher"] }
+shadowsocks = { git = "https://github.com/mullvad/shadowsocks-rust", rev = "8f6afd081a9440fff2dda565908eddc27a3295f1", features = [ "stream-cipher" ] }
diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs
index 73007f32f9..54dc98c9cd 100644
--- a/mullvad-management-interface/src/types/conversions/settings.rs
+++ b/mullvad-management-interface/src/types/conversions/settings.rs
@@ -161,7 +161,8 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings {
)?,
// NOTE: This field is meaningless when obtained from gRPC
wg_migration_rand_num: std::f32::NAN,
- // NOTE: This field is set based on mullvad-types. It's not based on the actual settings version.
+ // NOTE: This field is set based on mullvad-types. It's not based on the actual settings
+ // version.
settings_version: CURRENT_SETTINGS_VERSION,
})
}
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 3d38b35578..9a833353d3 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -37,7 +37,6 @@ rand = "0.8.5"
[target.'cfg(not(target_os="android"))'.dependencies]
byteorder = "1"
internet-checksum = "0.2"
-shadowsocks-service = { version = "1.15.3", default-features = false, features = ["local", "stream-cipher"] }
shell-escape = "0.1"
socket2 = { version = "0.4.2", features = ["all"] }
prost = "0.11"
diff --git a/talpid-openvpn/Cargo.toml b/talpid-openvpn/Cargo.toml
index e4fb576692..16fa1c7e8a 100644
--- a/talpid-openvpn/Cargo.toml
+++ b/talpid-openvpn/Cargo.toml
@@ -26,7 +26,7 @@ talpid-tunnel = { path = "../talpid-tunnel" }
talpid-types = { path = "../talpid-types" }
uuid = { version = "0.8", features = ["v4"] }
tokio = { version = "1.8", features = ["process", "rt-multi-thread", "fs"] }
-shadowsocks-service = { version = "1.15.3", default-features = false, features = ["local", "stream-cipher"] }
+shadowsocks-service = { git = "https://github.com/mullvad/shadowsocks-rust", rev = "8f6afd081a9440fff2dda565908eddc27a3295f1", features = [ "local", "stream-cipher" ] }
[target.'cfg(not(target_os="android"))'.dependencies]
byteorder = "1"