diff options
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme | 52 | ||||
| -rw-r--r-- | ios/MullvadVPN/MullvadEndpoint.swift | 19 | ||||
| -rw-r--r-- | ios/MullvadVPN/RelaySelector+RelayCache.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadVPN/RelaySelector.swift | 129 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/Info.plist | 22 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/RelaySelectorTests.swift | 122 |
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)] + ) + ])) + ]) + ]) +]) |
