Skip to content

Commit

Permalink
Update data structure to support new obfuscation selection
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Nov 1, 2024
1 parent 114d191 commit 8d8a530
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 56 deletions.
157 changes: 128 additions & 29 deletions ios/MullvadSettings/WireGuardObfuscationSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,153 @@

import Foundation

/// Whether UDP-over-TCP obfuscation is enabled
/// Whether obfuscation is enabled and which method is used
///
/// `.automatic` means an algorithm will decide whether to use it or not.
public enum WireGuardObfuscationState: Codable {
case automatic
@available(*, deprecated, renamed: "udpTcp")
case on

case automatic
case udpOverTcp
case shadowsocks
case off

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

var allKeys = ArraySlice(container.allKeys)
guard let key = allKeys.popFirst(), allKeys.isEmpty else {
throw DecodingError.typeMismatch(
WireGuardObfuscationState.self,
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.",
underlyingError: nil
)
)
}

switch key {
case .automatic:
self = .automatic
case .on, .udpOverTcp:
self = .udpOverTcp
case .shadowsocks:
self = .shadowsocks
case .off:
self = .off
}
}
}

/// The port to select when using UDP-over-TCP obfuscation
///
/// `.automatic` means an algorith will decide between using `port80` or `port5001`
public enum WireGuardObfuscationPort: UInt16, Codable, CaseIterable {
case automatic = 0
case port80 = 80
case port5001 = 5001

/// The `UInt16` representation of the port.
/// - Returns: `0` if `.automatic`, `80` or `5001` otherwise.
public var portValue: UInt16 {
self == .automatic ? 0 : rawValue
public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible {
case automatic
case port80
case port5001

public var portValue: UInt16? {
switch self {
case .automatic:
nil
case .port80:
80
case .port5001:
5001
}
}

public init?(rawValue: UInt16) {
switch rawValue {
case 80:
self = .port80
case 5001:
self = .port5001
default: self = .automatic
public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_UDP_TCP_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
case .port80:
"80"
case .port5001:
"5001"
}
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedValue = try? container.decode(UInt16.self)
public enum WireGuardObfuscationShadowsockPort: Codable, Equatable, CustomStringConvertible {
case automatic
case custom(UInt16)

public var portValue: UInt16? {
switch self {
case .automatic:
nil
case .custom(let port):
port
}
}

let port = WireGuardObfuscationPort.allCases.first(where: { $0.rawValue == decodedValue })
self = port ?? .automatic
public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
case .custom(let port):
String(port)
}
}
}

private enum WireGuardObfuscationPort: UInt16, Codable {
case automatic
case port80
case port5001
}

public struct WireGuardObfuscationSettings: Codable, Equatable {
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
private var port: WireGuardObfuscationPort = .automatic

public let state: WireGuardObfuscationState
public let port: WireGuardObfuscationPort
public let udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort
public let shadowsocksPort: WireGuardObfuscationShadowsockPort

public init(state: WireGuardObfuscationState = .automatic, port: WireGuardObfuscationPort = .automatic) {
public init(
state: WireGuardObfuscationState = .automatic,
udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort = .automatic,
shadowsocksPort: WireGuardObfuscationShadowsockPort = .automatic
) {
self.state = state
self.port = port
self.udpOverTcpPort = udpOverTcpPort
self.shadowsocksPort = shadowsocksPort
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

state = try container.decode(WireGuardObfuscationState.self, forKey: .state)
shadowsocksPort = try container.decodeIfPresent(
WireGuardObfuscationShadowsockPort.self,
forKey: .shadowsocksPort
) ?? .automatic

if let port = try? container.decodeIfPresent(WireGuardObfuscationUdpOverTcpPort.self, forKey: .udpOverTcpPort) {
udpOverTcpPort = port
} else if let port = try? container.decodeIfPresent(WireGuardObfuscationPort.self, forKey: .port) {
switch port {
case .automatic:
udpOverTcpPort = .automatic
case .port80:
udpOverTcpPort = .port80
case .port5001:
udpOverTcpPort = .port5001
}
} else {
udpOverTcpPort = .automatic
}
}
}
1 change: 1 addition & 0 deletions ios/MullvadVPN/UI appearance/UIMetrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ enum UIMetrics {
static let layoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 12)
static let inputCellTextFieldLayoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
static let selectableSettingsCellLeftViewSpacing: CGFloat = 12
static let settingsCellRightViewSpacing: CGFloat = 12
static let checkableSettingsCellLeftViewSpacing: CGFloat = 20

/// Cell layout margins used in table views that use inset style.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ class AccountViewController: UIViewController {
purchaseButton.isEnabled = productState.isReceived && isInteractionEnabled
contentView.accountDeviceRow.setButtons(enabled: isInteractionEnabled)
contentView.accountTokenRowView.setButtons(enabled: isInteractionEnabled)
contentView.restorePurchasesView.setButtons(enabled: isInteractionEnabled)
contentView.logoutButton.isEnabled = isInteractionEnabled
contentView.restorePurchasesView.setButtons(enabled: false)
contentView.logoutButton.isEnabled = false
contentView.redeemVoucherButton.isEnabled = isInteractionEnabled
contentView.deleteButton.isEnabled = isInteractionEnabled
navigationItem.rightBarButtonItem?.isEnabled = isInteractionEnabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,16 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
value: "UDP-over-TCP",
comment: ""
)

#if DEBUG
cell.detailTitleLabel.text = String(format: NSLocalizedString(
"WIREGUARD_OBFUSCATION_UDP_TCP_PORT",
tableName: "VPNSettings",
value: "Port: %d",
value: "Port: %@",
comment: ""
), viewModel.obfuscationPort.portValue)
), viewModel.obfuscationUpdOverTcpPort.description)
#endif

cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()

Expand All @@ -169,14 +171,16 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
value: "Shadowsocks",
comment: ""
)

#if DEBUG
cell.detailTitleLabel.text = String(format: NSLocalizedString(
"WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT",
tableName: "VPNSettings",
value: "Port: %d",
value: "Port: %@",
comment: ""
), viewModel.obfuscationPort.portValue)
), viewModel.obfuscationShadowsocksPort.description)
#endif

cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()

Expand All @@ -199,7 +203,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
case let .wireGuardObfuscationPort(port):
guard let cell = cell as? SelectableSettingsCell else { return }

let portString = port == 0 ? "Automatic" : "\(port)"
let portString = port.description
cell.titleLabel.text = NSLocalizedString(
"WIREGUARD_OBFUSCATION_PORT_LABEL",
tableName: "VPNSettings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscationUdpOverTcp
case wireGuardObfuscationShadowsocks
case wireGuardObfuscationOff
case wireGuardObfuscationPort(_ port: UInt16)
case wireGuardObfuscationPort(_ port: WireGuardObfuscationUdpOverTcpPort)
case quantumResistanceAutomatic
case quantumResistanceOn
case quantumResistanceOff
Expand Down Expand Up @@ -107,7 +107,11 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}

static var wireGuardObfuscationPort: [Item] {
[.wireGuardObfuscationPort(0), .wireGuardObfuscationPort(80), .wireGuardObfuscationPort(5001)]
[
.wireGuardObfuscationPort(.automatic),
.wireGuardObfuscationPort(.port80),
.wireGuardObfuscationPort(.port5001)
]
}

static var quantumResistance: [Item] {
Expand Down Expand Up @@ -178,7 +182,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
private var obfuscationSettings: WireGuardObfuscationSettings {
WireGuardObfuscationSettings(
state: viewModel.obfuscationState,
port: viewModel.obfuscationPort
udpOverTcpPort: viewModel.obfuscationUpdOverTcpPort,
shadowsocksPort: viewModel.obfuscationShadowsocksPort
)
}

Expand All @@ -192,7 +197,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
let obfuscationStateItem: Item = switch viewModel.obfuscationState {
case .automatic: .wireGuardObfuscationAutomatic
case .off: .wireGuardObfuscationOff
case .on: .wireGuardObfuscationUdpOverTcp
case .on, .udpOverTcp: .wireGuardObfuscationUdpOverTcp
case .shadowsocks: .wireGuardObfuscationShadowsocks
}

let quantumResistanceItem: Item = switch viewModel.quantumResistance {
Expand All @@ -201,7 +207,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .on: .quantumResistanceOn
}

let obfuscationPortItem: Item = .wireGuardObfuscationPort(viewModel.obfuscationPort.portValue)
let obfuscationPortItem: Item = .wireGuardObfuscationPort(viewModel.obfuscationUpdOverTcpPort)

return [
wireGuardPortItem,
Expand Down Expand Up @@ -308,13 +314,13 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
selectObfuscationState(.automatic)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
case .wireGuardObfuscationUdpOverTcp:
selectObfuscationState(.on)
selectObfuscationState(.udpOverTcp)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
// TODO: When ready, add implementation for selected obfuscation.
// TODO: When ready, add implementation for selected obfuscation (navigate to new view etc).
case .wireGuardObfuscationShadowsocks:
selectObfuscationState(.on)
selectObfuscationState(.shadowsocks)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
// TODO: When ready, add implementation for selected obfuscation.
// TODO: When ready, add implementation for selected obfuscation (navigate to new view etc).
case .wireGuardObfuscationOff:
selectObfuscationState(.off)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
Expand Down Expand Up @@ -656,9 +662,8 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
viewModel.setWireGuardObfuscationState(state)
}

func selectObfuscationPort(_ port: UInt16) {
let selectedPort = WireGuardObfuscationPort(rawValue: port)!
viewModel.setWireGuardObfuscationPort(selectedPort)
func selectObfuscationPort(_ port: WireGuardObfuscationUdpOverTcpPort) {
viewModel.setWireGuardObfuscationUdpOverTcpPort(port)
}

func selectQuantumResistance(_ state: TunnelQuantumResistance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ struct VPNSettingsViewModel: Equatable {
var availableWireGuardPortRanges: [[UInt16]] = []

private(set) var obfuscationState: WireGuardObfuscationState
private(set) var obfuscationPort: WireGuardObfuscationPort
private(set) var obfuscationUpdOverTcpPort: WireGuardObfuscationUdpOverTcpPort
private(set) var obfuscationShadowsocksPort: WireGuardObfuscationShadowsockPort

private(set) var quantumResistance: TunnelQuantumResistance
private(set) var multihopState: MultihopState
Expand Down Expand Up @@ -178,8 +179,12 @@ struct VPNSettingsViewModel: Equatable {
obfuscationState = newState
}

mutating func setWireGuardObfuscationPort(_ newPort: WireGuardObfuscationPort) {
obfuscationPort = newPort
mutating func setWireGuardObfuscationShadowsockPort(_ newPort: WireGuardObfuscationShadowsockPort) {
obfuscationShadowsocksPort = newPort
}

mutating func setWireGuardObfuscationUdpOverTcpPort(_ newPort: WireGuardObfuscationUdpOverTcpPort) {
obfuscationUpdOverTcpPort = newPort
}

mutating func setQuantumResistance(_ newState: TunnelQuantumResistance) {
Expand Down Expand Up @@ -242,7 +247,8 @@ struct VPNSettingsViewModel: Equatable {
wireGuardPort = tunnelSettings.relayConstraints.port.value

obfuscationState = tunnelSettings.wireGuardObfuscation.state
obfuscationPort = tunnelSettings.wireGuardObfuscation.port
obfuscationUpdOverTcpPort = tunnelSettings.wireGuardObfuscation.udpOverTcpPort
obfuscationShadowsocksPort = tunnelSettings.wireGuardObfuscation.shadowsocksPort

quantumResistance = tunnelSettings.tunnelQuantumResistance
multihopState = tunnelSettings.tunnelMultihopState
Expand Down
8 changes: 4 additions & 4 deletions ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
let shouldObfuscate = switch settings.obfuscation.state {
case .automatic:
retryAttempts % 4 == 2 || retryAttempts % 4 == 3
case .on:
case .on, .udpOverTcp, .shadowsocks:
true
case .off:
false
Expand All @@ -52,15 +52,15 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
tunnelObfuscator = nil
return endpoint
}
var tcpPort = settings.obfuscation.port
var tcpPort = settings.obfuscation.udpOverTcpPort
if tcpPort == .automatic {
tcpPort = retryAttempts % 2 == 0 ? .port80 : .port5001
}
let obfuscator = Obfuscator(
remoteAddress: obfuscatedEndpoint.ipv4Relay.ip,
tcpPort: tcpPort.portValue
tcpPort: tcpPort.portValue ?? 0
)
remotePort = tcpPort.portValue
remotePort = tcpPort.portValue ?? 0
obfuscator.start()
tunnelObfuscator = obfuscator

Expand Down

0 comments on commit 8d8a530

Please sign in to comment.