Skip to content

Commit

Permalink
show outgoing address on map view
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed Nov 14, 2023
1 parent c99d3c9 commit b90ae3c
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 16 deletions.
6 changes: 6 additions & 0 deletions ios/MullvadREST/RESTDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ extension REST {
/// Default API hostname.
public static let defaultAPIHostname = "api.mullvad.net"

/// Exit ipV4 API hostname.
public static let ipV4APIHostname = "ipv4.am.i.mullvad.net"

/// Exit ipV6 API hostname.
public static let ipV6APIHostname = "ipv6.am.i.mullvad.net"

/// Default API endpoint.
public static let defaultAPIEndpoint = AnyIPEndpoint(string: "45.83.223.196:443")!

Expand Down
48 changes: 44 additions & 4 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,15 @@
F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */; };
F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */; };
F09A29822A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297F2A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift */; };
F09D04B32AE919AC003D4F89 /* OutgoingConnectionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */; };
F09D04B52AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */; };
F09D04B72AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */; };
F09D04B92AE95111003D4F89 /* OutgoingConnectionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */; };
F09D04BB2AE95396003D4F89 /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */; };
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F09D04C02AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */; };
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F0B0E6972AFE6E7E001DC66B /* XCTest+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */; };
F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; };
F0C6A8432AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C6A8422AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift */; };
F0C6FA812A66E23300F521F0 /* DeleteAccountOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C6FA802A66E23300F521F0 /* DeleteAccountOperation.swift */; };
Expand Down Expand Up @@ -1636,6 +1645,13 @@
F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoucherTextField.swift; sourceTree = "<group>"; };
F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherContentView.swift; sourceTree = "<group>"; };
F09A297F2A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherInteractor.swift; sourceTree = "<group>"; };
F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxy.swift; sourceTree = "<group>"; };
F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OutgoingConnectionProxy+Stub.swift"; sourceTree = "<group>"; };
F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxyTests.swift; sourceTree = "<group>"; };
F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = "<group>"; };
F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionService.swift; sourceTree = "<group>"; };
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = "<group>"; };
F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; };
F0C6A8422AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewConfiguration.swift; sourceTree = "<group>"; };
F0C6FA802A66E23300F521F0 /* DeleteAccountOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountOperation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2128,14 +2144,15 @@
583FE01E29C197D5006E85F9 /* Tunnel */ = {
isa = PBXGroup;
children = (
58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */,
58C3F4F82964B08300D72515 /* MapViewController.swift */,
58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */,
58CCA00F224249A1004F3011 /* TunnelViewController.swift */,
5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */,
5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */,
58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */,
58C3F4F82964B08300D72515 /* MapViewController.swift */,
F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */,
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */,
58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */,
58CCA00F224249A1004F3011 /* TunnelViewController.swift */,
5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */,
);
path = Tunnel;
sourceTree = "<group>";
Expand Down Expand Up @@ -2493,6 +2510,10 @@
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */,
58C3FA652A38549D006A450A /* MockFileCache.swift */,
F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */,
F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */,
F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */,
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */,
A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */,
A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */,
584B26F3237434D00073B10E /* RelaySelectorTests.swift */,
Expand All @@ -2503,6 +2524,7 @@
A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */,
A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */,
);
path = MullvadVPNTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2737,6 +2759,7 @@
58C774C929AB543C003A1A56 /* Containers */,
58CAF9F22983D32200BE19F7 /* Coordinators */,
583FE02329C1AC9F006E85F9 /* Extensions */,
F09D04B82AE94F27003D4F89 /* GeneralAPIs */,
58B26E1F2943516500D5980C /* Notifications */,
586A950B2901250A007BAF2B /* Operations */,
5864859729A0D012006C5743 /* Presentation controllers */,
Expand Down Expand Up @@ -3018,6 +3041,14 @@
path = RedeemVoucher;
sourceTree = "<group>";
};
F09D04B82AE94F27003D4F89 /* GeneralAPIs */ = {
isa = PBXGroup;
children = (
F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */,
);
path = GeneralAPIs;
sourceTree = "<group>";
};
F0E361892A4ADCF500AEEF2B /* Welcome */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4100,10 +4131,13 @@
A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */,
A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */,
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */,
A9A5F9F12ACB05160083449F /* String+Split.swift in Sources */,
A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */,
A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */,
A9A5F9F52ACB05160083449F /* RegisteredDeviceInAppNotificationProvider.swift in Sources */,
F09D04B72AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift in Sources */,
F09D04B92AE95111003D4F89 /* OutgoingConnectionProxy.swift in Sources */,
A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */,
A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */,
A9A5F9F82ACB05160083449F /* NotificationProviderIdentifier.swift in Sources */,
Expand Down Expand Up @@ -4144,12 +4178,14 @@
A9A5FA142ACB05160083449F /* MapConnectionStatusOperation.swift in Sources */,
A9A5FA152ACB05160083449F /* RedeemVoucherOperation.swift in Sources */,
A9A5FA162ACB05160083449F /* RotateKeyOperation.swift in Sources */,
F09D04B52AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift in Sources */,
A9A5FA172ACB05160083449F /* SendTunnelProviderMessageOperation.swift in Sources */,
A9A5FA182ACB05160083449F /* SetAccountOperation.swift in Sources */,
A9A5FA192ACB05160083449F /* StartTunnelOperation.swift in Sources */,
A9A5FA1A2ACB05160083449F /* StopTunnelOperation.swift in Sources */,
A9A5FA1B2ACB05160083449F /* Tunnel.swift in Sources */,
A9A5FA1C2ACB05160083449F /* Tunnel+Messaging.swift in Sources */,
F09D04BB2AE95396003D4F89 /* MockURLProtocol.swift in Sources */,
A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */,
A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */,
A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */,
Expand All @@ -4159,6 +4195,7 @@
A9A5FA222ACB05160083449F /* TunnelObserver.swift in Sources */,
A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */,
A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */,
F09D04C02AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift in Sources */,
A9A5FA232ACB05160083449F /* TunnelState.swift in Sources */,
A9A5FA242ACB05160083449F /* TunnelStore.swift in Sources */,
A9A5FA252ACB05160083449F /* UpdateAccountDataOperation.swift in Sources */,
Expand All @@ -4174,6 +4211,7 @@
A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */,
A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */,
A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */,
F0B0E6972AFE6E7E001DC66B /* XCTest+Async.swift in Sources */,
A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */,
A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */,
A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */,
Expand Down Expand Up @@ -4485,6 +4523,7 @@
F0C6A8432AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift in Sources */,
7AF10EB42ADE85BC00C090B9 /* RelayFilterCoordinator.swift in Sources */,
58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */,
F09D04B32AE919AC003D4F89 /* OutgoingConnectionProxy.swift in Sources */,
58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */,
7AF10EB22ADE859200C090B9 /* AlertViewController.swift in Sources */,
587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */,
Expand All @@ -4502,6 +4541,7 @@
063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */,
A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */,
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */,
5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */,
7A9CCCC22A96302800DD6A34 /* SafariCoordinator.swift in Sources */,
Expand Down
8 changes: 7 additions & 1 deletion ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private let accountsProxy: RESTAccountHandling
private var tunnelObserver: TunnelObserver?
private var appPreferences: AppPreferencesDataSource
private var outgoingConnectionService: OutgoingConnectionServiceHandling

private var outOfTimeTimer: Timer?

Expand All @@ -91,6 +92,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
apiProxy: APIQuerying,
devicesProxy: DeviceHandling,
accountsProxy: RESTAccountHandling,
outgoingConnectionService: OutgoingConnectionServiceHandling,
appPreferences: AppPreferencesDataSource
) {
self.tunnelManager = tunnelManager
Expand All @@ -100,6 +102,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
self.devicesProxy = devicesProxy
self.accountsProxy = accountsProxy
self.appPreferences = appPreferences
self.outgoingConnectionService = outgoingConnectionService

super.init()

Expand Down Expand Up @@ -676,7 +679,10 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
}

private func makeTunnelCoordinator() -> TunnelCoordinator {
let tunnelCoordinator = TunnelCoordinator(tunnelManager: tunnelManager)
let tunnelCoordinator = TunnelCoordinator(
tunnelManager: tunnelManager,
outgoingConnectionService: outgoingConnectionService
)

tunnelCoordinator.showSelectLocationPicker = { [weak self] in
self?.router.present(.selectLocation, animated: true)
Expand Down
10 changes: 8 additions & 2 deletions ios/MullvadVPN/Coordinators/TunnelCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ class TunnelCoordinator: Coordinator, Presenting {

var showSelectLocationPicker: (() -> Void)?

init(tunnelManager: TunnelManager) {
init(
tunnelManager: TunnelManager,
outgoingConnectionService: OutgoingConnectionServiceHandling
) {
self.tunnelManager = tunnelManager

let interactor = TunnelViewControllerInteractor(tunnelManager: tunnelManager)
let interactor = TunnelViewControllerInteractor(
tunnelManager: tunnelManager,
outgoingConnectionService: outgoingConnectionService
)
controller = TunnelViewController(interactor: interactor)

super.init()
Expand Down
112 changes: 112 additions & 0 deletions ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// OutgoingConnectionProxy.swift
// MullvadREST
//
// Created by Mojgan on 2023-10-24.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadREST
import MullvadTypes
import Network

protocol OutgoingConnectionHandling {
func getIPV6(retryStrategy: REST.RetryStrategy) async throws -> OutgoingConnectionProxy.IPV6ConnectionData
func getIPV4(retryStrategy: REST.RetryStrategy) async throws -> OutgoingConnectionProxy.IPV4ConnectionData
}

final class OutgoingConnectionProxy: OutgoingConnectionHandling {
let urlSession: URLSession

init(urlSession: URLSession) {
self.urlSession = urlSession
}

func getIPV6(retryStrategy: REST.RetryStrategy) async throws -> IPV6ConnectionData {
try await perform(retryStrategy: retryStrategy, host: REST.ipV6APIHostname)
}

func getIPV4(retryStrategy: REST.RetryStrategy) async throws -> IPV4ConnectionData {
try await perform(retryStrategy: retryStrategy, host: REST.ipV4APIHostname)
}

private func perform<T: Decodable>(retryStrategy: REST.RetryStrategy, host: String) async throws -> T {
let delayIterator = retryStrategy.makeDelayIterator()
for _ in 0 ..< retryStrategy.maxRetryCount {
do {
return try await perform(host: host)
} catch {
// ignore if request is cancelled
if case URLError.cancelled = error {
throw error
} else {
// retry with the delay
guard let delay = delayIterator.next() else { throw error }
let mills = UInt64(max(0, delay.milliseconds))
let nanos = mills.saturatingMultiplication(1_000_000)
try await Task.sleep(nanoseconds: nanos)
}
}
}
return try await perform(host: host)
}

private func perform<T: Decodable>(host: String) async throws -> T {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = host
urlComponents.path = "/json"

guard let url = urlComponents.url else {
throw REST.Error.network(URLError(.badURL))
}
do {
let request = URLRequest(
url: url,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: REST.defaultAPINetworkTimeout.timeInterval
)
let (data, response) = try await urlSession.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw REST.Error.network(URLError(.badServerResponse))
}
let decoder = JSONDecoder()
guard (200 ..< 300).contains(httpResponse.statusCode) else {
throw REST.Error.unhandledResponse(
httpResponse.statusCode,
try? decoder.decode(
REST.ServerErrorResponse.self,
from: data
)
)
}
let connectionData = try decoder.decode(T.self, from: data)
return connectionData

} catch {
throw error
}
}
}

extension OutgoingConnectionProxy {
typealias IPV4ConnectionData = OutgoingConnectionData<IPv4Address>
typealias IPV6ConnectionData = OutgoingConnectionData<IPv6Address>
typealias IPAddressType = Codable & IPAddress

// MARK: - OutgoingConnectionData

struct OutgoingConnectionData<T: IPAddressType>: Codable, Equatable {
let ip: T
let exitIP: Bool

enum CodingKeys: String, CodingKey {
case ip, exitIP = "mullvad_exit_ip"
}

static func == (lhs: Self, rhs: Self) -> Bool {
lhs.ip.rawValue == rhs.ip.rawValue && lhs.exitIP == rhs.exitIP
}
}
}
3 changes: 3 additions & 0 deletions ios/MullvadVPN/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHand
apiProxy: appDelegate.apiProxy,
devicesProxy: appDelegate.devicesProxy,
accountsProxy: appDelegate.accountsProxy,
outgoingConnectionService: OutgoingConnectionService(
outgoingConnectionProxy: OutgoingConnectionProxy(urlSession: URLSession(configuration: .ephemeral))
),
appPreferences: AppPreferences()
)

Expand Down
5 changes: 5 additions & 0 deletions ios/MullvadVPN/UI appearance/UIMetrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ enum UIMetrics {
static let chipViewLayoutMargins = UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8)
static let chipViewLabelSpacing: CGFloat = 7
}

enum ConnectionPanelView {
static let inRowHeight: CGFloat = 22
static let outRowHeight: CGFloat = 44
}
}

extension UIMetrics {
Expand Down
Loading

0 comments on commit b90ae3c

Please sign in to comment.