From 8ab686c6223d3c1b4af41001c64b020d1cc2d913 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 30 Nov 2023 17:09:06 +0100 Subject: [PATCH] Show the correct port of the endpoint the device is connected to --- .../SimulatorTunnelProviderHost.swift | 3 +- .../Tunnel/TunnelControlView.swift | 13 +- .../Tunnel/TunnelControlViewModel.swift | 34 ++--- .../Actor/ObservedState.swift | 4 + .../Actor/PacketTunnelActor.swift | 122 ++++++------------ .../Actor/ProtocolObfuscator.swift | 8 +- ios/PacketTunnelCore/Actor/State.swift | 3 + .../Mocks/ProtocolObfuscationStub.swift | 2 + .../UDPOverTCPObfuscator.swift | 3 + 9 files changed, 80 insertions(+), 112 deletions(-) diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift index 6ceea58c18d9..86e4d83030b8 100644 --- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift @@ -185,7 +185,8 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { relayConstraints: try SettingsManager.readSettings().relayConstraints, networkReachability: .reachable, connectionAttemptCount: 0, - transportLayer: .udp + transportLayer: .udp, + remotePort: selectedRelay.endpoint.ipv4Relay.port ) ) } catch { diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 661b46fd3fa0..e1493f27fae7 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -145,7 +145,6 @@ final class TunnelControlView: UIView { func update(with model: TunnelControlViewModel) { viewModel = model let tunnelState = model.tunnelStatus.state - connectionPanel.dataSource = model.connectionPanel secureLabel.text = model.secureLabelText secureLabel.textColor = tunnelState.textColorForSecureLabel selectLocationButtonBlurView.isEnabled = model.enableButtons @@ -153,17 +152,7 @@ final class TunnelControlView: UIView { cityLabel.attributedText = attributedStringForLocation(string: model.city) countryLabel.attributedText = attributedStringForLocation(string: model.country) connectionPanel.connectedRelayName = model.connectedRelayName - - if let tunnelRelay = model.tunnelStatus.state.relay { - var protocolLayer = "" - if case let .connected(state) = model.tunnelStatus.observedState { - protocolLayer = state.transportLayer == .tcp ? "TCP" : "UDP" - } - connectionPanel.dataSource = ConnectionPanelData( - inAddress: "\(tunnelRelay.endpoint.ipv4Relay) \(protocolLayer)", - outAddress: model.connectionPanel.outAddress - ) - } + connectionPanel.dataSource = model.connectionPanel updateSecureLabel(tunnelState: tunnelState) updateActionButtons(tunnelState: tunnelState) diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift index 7453a6d81e62..2aa7e20fdc48 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift @@ -35,18 +35,6 @@ struct TunnelControlViewModel { self.connectedRelayName = connectedRelayName } - init(from other: TunnelControlViewModel) { - self.init( - tunnelStatus: other.tunnelStatus, - secureLabelText: other.secureLabelText, - connectionPanel: other.connectionPanel, - enableButtons: other.enableButtons, - city: other.city, - country: other.country, - connectedRelayName: other.connectedRelayName - ) - } - func update(status: TunnelStatus) -> TunnelControlViewModel { TunnelControlViewModel( tunnelStatus: status, @@ -60,13 +48,25 @@ struct TunnelControlViewModel { } func update(outgoingConnectionInfo: OutgoingConnectionInfo) -> TunnelControlViewModel { - TunnelControlViewModel( + let inPort = tunnelStatus.observedState.connectionState?.remotePort ?? 0 + + var connectionPanelData = ConnectionPanelData(inAddress: "") + if let tunnelRelay = tunnelStatus.state.relay { + var protocolLayer = "" + if case let .connected(state) = tunnelStatus.observedState { + protocolLayer = state.transportLayer == .tcp ? "TCP" : "UDP" + } + + connectionPanelData = ConnectionPanelData( + inAddress: "\(tunnelRelay.endpoint.ipv4Relay.ip):\(inPort) \(protocolLayer)", + outAddress: outgoingConnectionInfo.outAddress + ) + } + + return TunnelControlViewModel( tunnelStatus: tunnelStatus, secureLabelText: secureLabelText, - connectionPanel: ConnectionPanelData( - inAddress: "\(tunnelStatus.state.relay?.endpoint.ipv4Relay.description ?? "no info")", - outAddress: outgoingConnectionInfo.outAddress - ), + connectionPanel: connectionPanelData, enableButtons: enableButtons, city: city, country: country, diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index 1944e474f9f2..a6b7d741bc72 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -29,6 +29,7 @@ public struct ObservedConnectionState: Equatable, Codable { public var networkReachability: NetworkReachability public var connectionAttemptCount: UInt public var transportLayer: TransportLayer + public var remotePort: UInt16 public var lastKeyRotation: Date? public var isNetworkReachable: Bool { @@ -41,6 +42,7 @@ public struct ObservedConnectionState: Equatable, Codable { networkReachability: NetworkReachability, connectionAttemptCount: UInt, transportLayer: TransportLayer, + remotePort: UInt16, lastKeyRotation: Date? = nil ) { self.selectedRelay = selectedRelay @@ -48,6 +50,7 @@ public struct ObservedConnectionState: Equatable, Codable { self.networkReachability = networkReachability self.connectionAttemptCount = connectionAttemptCount self.transportLayer = transportLayer + self.remotePort = remotePort self.lastKeyRotation = lastKeyRotation } } @@ -89,6 +92,7 @@ extension ConnectionState { networkReachability: networkReachability, connectionAttemptCount: connectionAttemptCount, transportLayer: transportLayer, + remotePort: remotePort, lastKeyRotation: lastKeyRotation ) } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index e0a8208c0bdb..8d3372c6beb9 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -287,53 +287,56 @@ extension PacketTunnelActor { settings: Settings, reason: ReconnectReason ) throws -> ConnectionState? { - switch state { - case .initial: - return try makeConnectionStateInner( + var keyPolicy: KeyPolicy = .useCurrent + var networkReachability = defaultPathObserver.defaultPath?.networkReachability ?? .undetermined + var lastKeyRotation: Date? + + let callRelaySelector = { [self] maybeCurrentRelay, connectionCount in + try self.selectRelay( nextRelay: nextRelay, - settings: settings, - keyPolicy: .useCurrent, - networkReachability: defaultPathObserver.defaultPath?.networkReachability ?? .undetermined, - lastKeyRotation: nil + relayConstraints: settings.relayConstraints, + currentRelay: maybeCurrentRelay, + connectionAttemptCount: connectionCount ) + } - case var .connecting(connState), var .reconnecting(connState): - switch reason { - case .connectionLoss: - // Increment attempt counter when reconnection is requested due to connectivity loss. - connState.incrementAttemptCount() - case .userInitiated: - break + switch state { + case .initial: + break + case var .connecting(connectionState), var .reconnecting(connectionState): + if reason == .connectionLoss { + connectionState.incrementAttemptCount() } - // Explicit fallthrough fallthrough - - case var .connected(connState): - let relayConstraints = settings.relayConstraints - - connState.selectedRelay = try selectRelay( - nextRelay: nextRelay, - relayConstraints: relayConstraints, - currentRelay: connState.selectedRelay, - connectionAttemptCount: connState.connectionAttemptCount + case var .connected(connectionState): + let selectedRelay = try callRelaySelector( + connectionState.selectedRelay, + connectionState.connectionAttemptCount ) - connState.relayConstraints = relayConstraints - connState.currentKey = settings.privateKey - - return connState - + connectionState.selectedRelay = selectedRelay + connectionState.relayConstraints = settings.relayConstraints + connectionState.currentKey = settings.privateKey + return connectionState case let .error(blockedState): - return try makeConnectionStateInner( - nextRelay: nextRelay, - settings: settings, - keyPolicy: blockedState.keyPolicy, - networkReachability: blockedState.networkReachability, - lastKeyRotation: blockedState.lastKeyRotation - ) - + keyPolicy = blockedState.keyPolicy + lastKeyRotation = blockedState.lastKeyRotation + networkReachability = blockedState.networkReachability case .disconnecting, .disconnected: return nil } + let selectedRelay = try callRelaySelector(nil, 0) + return ConnectionState( + selectedRelay: selectedRelay, + relayConstraints: settings.relayConstraints, + currentKey: settings.privateKey, + keyPolicy: keyPolicy, + networkReachability: networkReachability, + connectionAttemptCount: 0, + lastKeyRotation: lastKeyRotation, + connectedEndpoint: selectedRelay.endpoint, + transportLayer: .udp, + remotePort: selectedRelay.endpoint.ipv4Relay.port + ) } private func obfuscateConnection( @@ -360,48 +363,8 @@ extension PacketTunnelActor { connectionAttemptCount: connectionState.connectionAttemptCount, lastKeyRotation: connectionState.lastKeyRotation, connectedEndpoint: obfuscatedEndpoint, - transportLayer: transportLayer - ) - } - - /** - Create a connection state when `State` is either `.inital` or `.error`. - - - Parameters: - - nextRelay: Next relay to connect to. - - settings: Current settings. - - keyPolicy: Current key that should be used by the tunnel. - - networkReachability: Network connectivity outside of tunnel. - - lastKeyRotation: Last time packet tunnel rotated the key. - - - Returns: New connection state, or `nil` if new relay cannot be selected. - */ - private func makeConnectionStateInner( - nextRelay: NextRelay, - settings: Settings, - keyPolicy: KeyPolicy, - networkReachability: NetworkReachability, - lastKeyRotation: Date? - ) throws -> ConnectionState? { - let relayConstraints = settings.relayConstraints - let privateKey = settings.privateKey - - let selectedRelay = try selectRelay( - nextRelay: nextRelay, - relayConstraints: relayConstraints, - currentRelay: nil, - connectionAttemptCount: 0 - ) - return ConnectionState( - selectedRelay: selectedRelay, - relayConstraints: relayConstraints, - currentKey: privateKey, - keyPolicy: keyPolicy, - networkReachability: networkReachability, - connectionAttemptCount: 0, - lastKeyRotation: lastKeyRotation, - connectedEndpoint: selectedRelay.endpoint, - transportLayer: .udp + transportLayer: transportLayer, + remotePort: protocolObfuscator.remotePort ) } @@ -444,5 +407,4 @@ extension PacketTunnelActor { } extension PacketTunnelActor: PacketTunnelActorProtocol {} - // swiftlint:disable:this file_length diff --git a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift index b9c5f20259b5..0b59e7a23ac2 100644 --- a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift +++ b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift @@ -13,6 +13,7 @@ import TunnelObfuscation public protocol ProtocolObfuscation { func obfuscate(_ endpoint: MullvadEndpoint, settings: Settings, retryAttempts: UInt) -> MullvadEndpoint var transportLayer: TransportLayer? { get } + var remotePort: UInt16 { get } } public class ProtocolObfuscator: ProtocolObfuscation { @@ -30,12 +31,14 @@ public class ProtocolObfuscator: ProtocolObfuscat /// - retryAttempts: The number of times a connection was attempted to `endpoint` /// - Returns: `endpoint` if obfuscation is disabled, or an obfuscated endpoint otherwise. public var transportLayer: TransportLayer? { - guard let tunnelObfuscator else { return nil } - return tunnelObfuscator.transportLayer + return tunnelObfuscator?.transportLayer } + private(set) public var remotePort: UInt16 = 0 + public func obfuscate(_ endpoint: MullvadEndpoint, settings: Settings, retryAttempts: UInt = 0) -> MullvadEndpoint { var obfuscatedEndpoint = endpoint + remotePort = endpoint.ipv4Relay.port let shouldObfuscate = switch settings.obfuscation.state { case .automatic: retryAttempts % 4 == 2 || retryAttempts % 4 == 3 @@ -57,6 +60,7 @@ public class ProtocolObfuscator: ProtocolObfuscat remoteAddress: obfuscatedEndpoint.ipv4Relay.ip, tcpPort: tcpPort.portValue ) + remotePort = tcpPort.portValue obfuscator.start() tunnelObfuscator = obfuscator diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index 36e9920410dd..3cca82d865d7 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -132,6 +132,9 @@ struct ConnectionState { public let connectedEndpoint: MullvadEndpoint /// Via which transport protocol was the connection made to the relay public let transportLayer: TransportLayer + + /// The remote port that was chosen to connect to `connectedEndpoint` + public let remotePort: UInt16 } /// Data associated with error state. diff --git a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift index 48c21c475d9d..acb69753f1ea 100644 --- a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift @@ -11,6 +11,8 @@ import Foundation @testable import PacketTunnelCore struct ProtocolObfuscationStub: ProtocolObfuscation { + var remotePort: UInt16 { 42 } + func obfuscate(_ endpoint: MullvadEndpoint, settings: Settings, retryAttempts: UInt) -> MullvadEndpoint { endpoint } diff --git a/ios/TunnelObfuscation/UDPOverTCPObfuscator.swift b/ios/TunnelObfuscation/UDPOverTCPObfuscator.swift index 806c51e4cb21..580c4937d785 100644 --- a/ios/TunnelObfuscation/UDPOverTCPObfuscator.swift +++ b/ios/TunnelObfuscation/UDPOverTCPObfuscator.swift @@ -16,6 +16,7 @@ public protocol TunnelObfuscation { func start() func stop() var localUdpPort: UInt16 { get } + var remotePort: UInt16 { get } var transportLayer: TransportLayer { get } } @@ -35,6 +36,8 @@ public final class UDPOverTCPObfuscator: TunnelObfuscation { return stateLock.withLock { proxyHandle.port } } + public var remotePort: UInt16 { tcpPort } + public var transportLayer: TransportLayer { .tcp } /// Initialize tunnel obfuscator with remote server address and TCP port where udp2tcp is running.