diff options
| author | Steffen Ernst <steffen.ernst@mullvad.net> | 2025-05-09 11:10:20 +0200 |
|---|---|---|
| committer | Steffen Ernst <steffen.ernst@mullvad.net> | 2025-05-20 15:46:45 +0200 |
| commit | 7d5a8f331556558727ec725bb35ac735a8d5d3f8 (patch) | |
| tree | 94296bca710251e24a3114175ae6e3388579b388 | |
| parent | d2e13c40a83889c470d6fbbcfc5b041696a21605 (diff) | |
| download | mullvadvpn-7d5a8f331556558727ec725bb35ac735a8d5d3f8.tar.xz mullvadvpn-7d5a8f331556558727ec725bb35ac735a8d5d3f8.zip | |
Handle port 443 removal and show blocked state banner
6 files changed, 113 insertions, 2 deletions
diff --git a/ios/MullvadREST/Relay/RelaySelectorWrapper.swift b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift index c52a0a2faf..feb1d1e890 100644 --- a/ios/MullvadREST/Relay/RelaySelectorWrapper.swift +++ b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift @@ -20,6 +20,8 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable { tunnelSettings: LatestTunnelSettings, connectionAttemptCount: UInt ) throws -> SelectedRelays { + try validateWireguardPort(tunnelSettings) + let obfuscation = try prepareObfuscation(for: tunnelSettings, connectionAttemptCount: connectionAttemptCount) return switch tunnelSettings.tunnelMultihopState { @@ -76,4 +78,29 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable { connectionAttemptCount: connectionAttemptCount ) } + + private func validateWireguardPort(_ tunnelSettings: LatestTunnelSettings) throws { + func isPortWithinValidWireGuardRanges(_ port: UInt16) throws -> Bool { + return try relayCache + .read().relays.wireguard.portRanges + .contains { range in + if let minPort = range.first, let maxPort = range.last { + return (minPort ... maxPort).contains(port) + } + + return false + } + } + + switch tunnelSettings.wireGuardObfuscation.state { + case .automatic, .off: + if case let .only(port) = tunnelSettings.relayConstraints.port { + guard try isPortWithinValidWireGuardRanges(port) else { + throw NoRelaysSatisfyingConstraintsError(.invalidPort) + } + } + case .on, .udpOverTcp, .quic, .shadowsocks: + break + } + } } diff --git a/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift index e0135cb44f..9325971fef 100644 --- a/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift @@ -248,6 +248,8 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific errorString = "No DAITA compatible servers match your location settings. Try changing location." case .noRelaysSatisfyingConstraints: errorString = "No servers match your settings, try changing server or other settings." + case .noRelaysSatisfyingPortConstraints: + errorString = "The selected WireGuard port is not supported, please change it under VPN settings." case .invalidAccount: errorString = "You are logged in with an invalid account number. Please log out and try another one." case .deviceLoggedOut: diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift index b84cb528b8..0302ad5380 100644 --- a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift +++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift @@ -120,4 +120,80 @@ class RelaySelectorWrapperTests: XCTestCase { let selectedRelays = try wrapper.selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) XCTAssertNotNil(selectedRelays.entry) } + + func testValidWireguardPortDoesNotThrow() throws { + let wrapper = RelaySelectorWrapper(relayCache: relayCache) + + let settings = LatestTunnelSettings( + relayConstraints: .init( + port: + .only( + ServerRelaysResponseStubs.sampleRelays.wireguard.portRanges.first!.first! + ) + ) + ) + + XCTAssertNoThrow( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + } + + func testInvalidWireguardPortThrows() throws { + let wrapper = RelaySelectorWrapper(relayCache: relayCache) + + var settings = LatestTunnelSettings( + relayConstraints: .init(port: .only(1)), + wireGuardObfuscation: .init(state: .automatic) + ) + + XCTAssertThrowsError( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + + settings = LatestTunnelSettings( + relayConstraints: .init(port: .only(1)), + wireGuardObfuscation: .init(state: .off) + ) + + XCTAssertThrowsError( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + } + + func testInvalidWireguardPortDoesNotThrowWhenObfuscated() throws { + let wrapper = RelaySelectorWrapper(relayCache: relayCache) + + var settings = LatestTunnelSettings( + relayConstraints: .init(port: .only(1)), + wireGuardObfuscation: .init(state: .quic) + ) + + XCTAssertNoThrow( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + + settings = LatestTunnelSettings( + relayConstraints: .init(port: .only(1)), + wireGuardObfuscation: .init(state: .udpOverTcp) + ) + + XCTAssertNoThrow( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + + settings = LatestTunnelSettings( + relayConstraints: .init(port: .only(1)), + wireGuardObfuscation: .init(state: .shadowsocks) + ) + + XCTAssertNoThrow( + try wrapper + .selectRelays(tunnelSettings: settings, connectionAttemptCount: 0) + ) + } } diff --git a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift index 219e7d518d..2383142ba1 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift @@ -58,6 +58,8 @@ public struct BlockedStateErrorMapper: BlockedStateErrorMapperProtocol { .noRelaysSatisfyingDaitaConstraints case .noObfuscatedRelaysFound: .noRelaysSatisfyingObfuscationSettings + case .invalidPort: + .noRelaysSatisfyingPortConstraints default: .noRelaysSatisfyingConstraints } diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index b42a52458e..2421f498a1 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -206,7 +206,8 @@ extension BlockedStateReason { case .noRelaysSatisfyingConstraints, .noRelaysSatisfyingFilterConstraints, .multihopEntryEqualsExit, .noRelaysSatisfyingObfuscationSettings, .noRelaysSatisfyingDaitaConstraints, .readSettings, .invalidAccount, .accountExpired, .deviceRevoked, - .unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey: + .unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey, + .noRelaysSatisfyingPortConstraints: return false } } diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index 3aac312490..6f837fc734 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -211,9 +211,12 @@ public enum BlockedStateReason: String, Codable, Equatable, Sendable { /// No relays satisfying DAITA constraints. case noRelaysSatisfyingDaitaConstraints - /// No relays satisfying DAITA constraints. + /// No relays satisfying obfuscation settings. case noRelaysSatisfyingObfuscationSettings + /// No relays satisfying port constraints. + case noRelaysSatisfyingPortConstraints + /// Any other failure when reading settings. case readSettings |
