summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-12-04 12:13:13 +0100
committerAndrej Mihajlov <and@mullvad.net>2019-12-04 18:55:19 +0100
commitdd4ce2c1acd786bc896a32388062941d7007cd6d (patch)
tree73607dca29e2e1144ae248a13c196b715e778c34
parent0ad5a7b18853d0abfb873553082515a0c0e259ad (diff)
downloadmullvadvpn-dd4ce2c1acd786bc896a32388062941d7007cd6d.tar.xz
mullvadvpn-dd4ce2c1acd786bc896a32388062941d7007cd6d.zip
Add naive relay selector
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme52
-rw-r--r--ios/MullvadVPN/MullvadEndpoint.swift19
-rw-r--r--ios/MullvadVPN/RelaySelector+RelayCache.swift21
-rw-r--r--ios/MullvadVPN/RelaySelector.swift129
-rw-r--r--ios/MullvadVPNTests/Info.plist22
-rw-r--r--ios/MullvadVPNTests/RelaySelectorTests.swift122
6 files changed, 365 insertions, 0 deletions
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme
new file mode 100644
index 0000000000..0f05e5b7f1
--- /dev/null
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "1120"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "584B26F0237434D00073B10E"
+ BuildableName = "RelaySelectorTests.xctest"
+ BlueprintName = "RelaySelectorTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/ios/MullvadVPN/MullvadEndpoint.swift b/ios/MullvadVPN/MullvadEndpoint.swift
new file mode 100644
index 0000000000..e61fea7475
--- /dev/null
+++ b/ios/MullvadVPN/MullvadEndpoint.swift
@@ -0,0 +1,19 @@
+//
+// MullvadEndpoint.swift
+// MullvadVPN
+//
+// Created by pronebird on 12/06/2019.
+// Copyright © 2019 Amagicom AB. All rights reserved.
+//
+
+import Foundation
+import Network
+
+/// Contains server data needed to connect to a single mullvad endpoint
+struct MullvadEndpoint {
+ let ipv4Relay: NWEndpoint
+ let ipv6Relay: NWEndpoint?
+ let ipv4Gateway: IPv4Address
+ let ipv6Gateway: IPv6Address
+ let publicKey: Data
+}
diff --git a/ios/MullvadVPN/RelaySelector+RelayCache.swift b/ios/MullvadVPN/RelaySelector+RelayCache.swift
new file mode 100644
index 0000000000..b82507ba23
--- /dev/null
+++ b/ios/MullvadVPN/RelaySelector+RelayCache.swift
@@ -0,0 +1,21 @@
+//
+// RelaySelector+RelayCache.swift
+// MullvadVPN
+//
+// Created by pronebird on 07/11/2019.
+// Copyright © 2019 Amagicom AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+
+extension RelaySelector {
+
+ static func loadedFromRelayCache() -> AnyPublisher<RelaySelector, RelayCacheError> {
+ return RelayCache.withDefaultLocation().publisher
+ .flatMap { $0.read() }
+ .map { RelaySelector(relayList: $0.relayList) }
+ .eraseToAnyPublisher()
+ }
+
+}
diff --git a/ios/MullvadVPN/RelaySelector.swift b/ios/MullvadVPN/RelaySelector.swift
new file mode 100644
index 0000000000..255502446c
--- /dev/null
+++ b/ios/MullvadVPN/RelaySelector.swift
@@ -0,0 +1,129 @@
+//
+// RelaySelector.swift
+// PacketTunnel
+//
+// Created by pronebird on 11/06/2019.
+// Copyright © 2019 Amagicom AB. All rights reserved.
+//
+
+import Foundation
+import Network
+
+struct RelaySelectorResult {
+ var relay: RelayList.Hostname
+ var tunnel: RelayList.WireguardTunnel
+ var endpoint: MullvadEndpoint
+}
+
+struct RelaySelector {
+
+ private let relayList: RelayList
+
+ init(relayList: RelayList) {
+ self.relayList = relayList
+ }
+
+ /// Produce a `RelayList` satisfying the given constraints
+ private func applyConstraints(_ constraints: RelayConstraints) -> RelayList {
+ let filteredCountries = relayList.countries.filter { (country) -> Bool in
+ switch constraints.location {
+ case .any:
+ return true
+ case .only(let constraint):
+ switch constraint {
+ case .country(let countryCode):
+ return countryCode == country.code
+ case .city(let countryCode, _):
+ return countryCode == country.code
+ case .hostname(let countryCode, _, _):
+ return countryCode == country.code
+ }
+ }
+ }.map { (country) -> RelayList.Country in
+ var filteredCountry = country
+ filteredCountry.cities = country.cities.filter { (city) -> Bool in
+ switch constraints.location {
+ case .any:
+ return true
+ case .only(let constraint):
+ switch constraint {
+ case .country:
+ return true
+ case .city(_, let cityCode):
+ return cityCode == city.code
+ case .hostname(_, let cityCode, _):
+ return cityCode == city.code
+ }
+ }
+ }.map { (city) -> RelayList.City in
+ var filteredCity = city
+ filteredCity.relays = city.relays.filter { (relay) -> Bool in
+ switch constraints.location {
+ case .any:
+ return true
+ case .only(let constraint):
+ switch constraint {
+ case .country, .city:
+ return true
+ case .hostname(_, _, let hostname):
+ return hostname == relay.hostname
+ }
+ }
+ }
+ .map({ (relay) -> RelayList.Hostname in
+ var filteredRelay = relay
+ filteredRelay.tunnels?.wireguard = relay.tunnels?.wireguard?
+ .filter { !$0.portRanges.isEmpty }
+
+ return filteredRelay
+ }).filter { (relay) -> Bool in
+ guard let wireguardTunnels = relay.tunnels?.wireguard else { return false }
+
+ return relay.active && !wireguardTunnels.isEmpty
+ }
+
+ return filteredCity
+ }.filter({ (city) -> Bool in
+ return !city.relays.isEmpty
+ })
+
+ return filteredCountry
+ }.filter { (country) -> Bool in
+ return !country.cities.isEmpty
+ }
+
+ return RelayList(countries: filteredCountries)
+ }
+
+ func evaluate(with constraints: RelayConstraints) -> RelaySelectorResult? {
+ let filteredRelayList = applyConstraints(constraints)
+
+ guard let randomRelay = filteredRelayList.countries.randomElement()?
+ .cities.randomElement()?
+ .relays.randomElement() else {
+ return nil
+ }
+
+ guard let randomTunnel = randomRelay.tunnels?.wireguard?.randomElement() else {
+ return nil
+ }
+
+ guard let randomPort = randomTunnel.portRanges.randomElement()?.randomElement() else {
+ return nil
+ }
+
+ let networkPort = NWEndpoint.Port(integerLiteral: randomPort)
+ let ipv4Endpoint = NWEndpoint.hostPort(host: .ipv4(randomRelay.ipv4AddrIn), port: networkPort)
+
+ let endpoint = MullvadEndpoint(
+ ipv4Relay: ipv4Endpoint,
+ ipv6Relay: nil,
+ ipv4Gateway: randomTunnel.ipv4Gateway,
+ ipv6Gateway: randomTunnel.ipv6Gateway,
+ publicKey: randomTunnel.publicKey
+ )
+
+ return RelaySelectorResult(relay: randomRelay, tunnel: randomTunnel, endpoint: endpoint)
+ }
+
+}
diff --git a/ios/MullvadVPNTests/Info.plist b/ios/MullvadVPNTests/Info.plist
new file mode 100644
index 0000000000..64d65ca495
--- /dev/null
+++ b/ios/MullvadVPNTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/ios/MullvadVPNTests/RelaySelectorTests.swift b/ios/MullvadVPNTests/RelaySelectorTests.swift
new file mode 100644
index 0000000000..af83ad23eb
--- /dev/null
+++ b/ios/MullvadVPNTests/RelaySelectorTests.swift
@@ -0,0 +1,122 @@
+//
+// RelaySelectorTests.swift
+// RelaySelectorTests
+//
+// Created by pronebird on 07/11/2019.
+// Copyright © 2019 Amagicom AB. All rights reserved.
+//
+
+import XCTest
+import Network
+
+class RelaySelectorTests: XCTestCase {
+
+ func testCountryConstraint() {
+ let relaySelector = RelaySelector(relayList: sampleRelayList)
+ let constraints = RelayConstraints(location: .only(.country("es")))
+
+ let result = relaySelector.evaluate(with: constraints)
+
+ XCTAssertEqual(result?.relay.hostname, "es1-wireguard")
+ }
+
+ func testCityConstraint() {
+ let relaySelector = RelaySelector(relayList: sampleRelayList)
+ let constraints = RelayConstraints(location: .only(.city("se", "got")))
+
+ let result = relaySelector.evaluate(with: constraints)
+
+ XCTAssertEqual(result?.relay.hostname, "se10-wireguard")
+ }
+
+ func testHostnameConstraint() {
+ let relaySelector = RelaySelector(relayList: sampleRelayList)
+ let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
+
+ let result = relaySelector.evaluate(with: constraints)
+
+ XCTAssertEqual(result?.relay.hostname, "se6-wireguard")
+ }
+
+}
+
+private let sampleRelayList = RelayList(countries: [
+ .init(name: "Spain", code: "es", cities: [
+ .init(name: "Madrid",
+ code: "mad",
+ latitude: 40.408566,
+ longitude: -3.69222,
+ relays: [
+ .init(
+ hostname: "es1-wireguard",
+ ipv4AddrIn: .loopback,
+ includeInCountry: true,
+ active: true,
+ weight: 0,
+ tunnels: .init(wireguard: [
+ .init(
+ ipv4Gateway: .loopback,
+ ipv6Gateway: .loopback,
+ publicKey: .init(),
+ portRanges: [(7000...7100)]
+ )
+ ]))
+ ])
+ ]),
+ .init(name: "Sweden", code: "se", cities: [
+ .init(name: "Gothenburg",
+ code: "got",
+ latitude: 57.70887,
+ longitude: 11.97456,
+ relays: [
+ .init(
+ hostname: "se10-wireguard",
+ ipv4AddrIn: .loopback,
+ includeInCountry: true,
+ active: true,
+ weight: 0,
+ tunnels: .init(wireguard: [
+ .init(
+ ipv4Gateway: .loopback,
+ ipv6Gateway: .loopback,
+ publicKey: .init(),
+ portRanges: [(7000...7100)]
+ )
+ ]))
+ ]),
+ .init(name: "Stockholm",
+ code: "sto",
+ latitude: 59.3289,
+ longitude: 18.0649,
+ relays: [
+ .init(
+ hostname: "se2-wireguard",
+ ipv4AddrIn: .loopback,
+ includeInCountry: true,
+ active: true,
+ weight: 0,
+ tunnels: .init(wireguard: [
+ .init(
+ ipv4Gateway: .loopback,
+ ipv6Gateway: .loopback,
+ publicKey: .init(),
+ portRanges: [(8000...8100)]
+ )
+ ])),
+ .init(
+ hostname: "se6-wireguard",
+ ipv4AddrIn: IPv4Address.loopback,
+ includeInCountry: true,
+ active: true,
+ weight: 0,
+ tunnels: .init(wireguard: [
+ .init(
+ ipv4Gateway: .loopback,
+ ipv6Gateway: .loopback,
+ publicKey: .init(),
+ portRanges: [(8000...9000)]
+ )
+ ]))
+ ])
+ ])
+])