-
Notifications
You must be signed in to change notification settings - Fork 339
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update relay selector for shadowsocks obfuscation
- Loading branch information
Showing
15 changed files
with
685 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// UInt+Counting.swift | ||
// MullvadVPN | ||
// | ||
// Created by Jon Petersson on 2024-11-05. | ||
// Copyright © 2024 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
extension UInt { | ||
/// Determines whether a number has a specific order in a given set. | ||
/// Eg. `6.isOrdered(nth: 3, forEverySetOf: 4)` -> "Is a 6 ordered third in an arbitrary | ||
/// amount of sets of four?". The result of this is `true`, since in a range of eg. 0-7 a six | ||
/// would be considered third if the range was divided into sets of 4. | ||
public func isOrdered(nth: UInt, forEverySetOf set: UInt) -> Bool { | ||
guard nth > 0, set > 0 else { | ||
assertionFailure("Both 'nth' and 'set' must be positive") | ||
return false | ||
} | ||
|
||
return self % set == nth - 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// ObfuscationMethodSelector.swift | ||
// MullvadREST | ||
// | ||
// Created by Jon Petersson on 2024-11-01. | ||
// Copyright © 2024 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import MullvadSettings | ||
|
||
public struct ObfuscationMethodSelector { | ||
/// This retry logic used is explained at the following link: | ||
/// https://github.com/mullvad/mullvadvpn-app/blob/main/docs/relay-selector.md#default-constraints-for-tunnel-endpoints | ||
public static func obfuscationMethodBy( | ||
connectionAttemptCount: UInt, | ||
tunnelSettings: LatestTunnelSettings | ||
) -> WireGuardObfuscationState { | ||
if tunnelSettings.wireGuardObfuscation.state == .automatic { | ||
if connectionAttemptCount.isOrdered(nth: 3, forEverySetOf: 4) { | ||
.shadowsocks | ||
} else if connectionAttemptCount.isOrdered(nth: 4, forEverySetOf: 4) { | ||
.udpOverTcp | ||
} else { | ||
.off | ||
} | ||
} else { | ||
tunnelSettings.wireGuardObfuscation.state | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// | ||
// ObfuscatorPortSelector.swift | ||
// MullvadVPN | ||
// | ||
// Created by Jon Petersson on 2024-11-01. | ||
// Copyright © 2024 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import MullvadSettings | ||
import MullvadTypes | ||
|
||
struct ObfuscatorPortSelectorResult { | ||
let relays: REST.ServerRelaysResponse | ||
let port: RelayConstraint<UInt16> | ||
} | ||
|
||
struct ObfuscatorPortSelector { | ||
let relays: REST.ServerRelaysResponse | ||
|
||
func obfuscate( | ||
tunnelSettings: LatestTunnelSettings, | ||
connectionAttemptCount: UInt | ||
) throws -> ObfuscatorPortSelectorResult { | ||
var relays = relays | ||
var port = tunnelSettings.relayConstraints.port | ||
let obfuscationMethod = ObfuscationMethodSelector.obfuscationMethodBy( | ||
connectionAttemptCount: connectionAttemptCount, | ||
tunnelSettings: tunnelSettings | ||
) | ||
|
||
switch obfuscationMethod { | ||
case .udpOverTcp: | ||
port = obfuscateUdpOverTcpPort( | ||
tunnelSettings: tunnelSettings, | ||
connectionAttemptCount: connectionAttemptCount | ||
) | ||
case .shadowsocks: | ||
relays = obfuscateShadowsocksRelays(tunnelSettings: tunnelSettings) | ||
port = obfuscateShadowsocksPort( | ||
tunnelSettings: tunnelSettings, | ||
shadowsocksPortRanges: relays.wireguard.shadowsocksPortRanges | ||
) | ||
default: | ||
break | ||
} | ||
|
||
return ObfuscatorPortSelectorResult(relays: relays, port: port) | ||
} | ||
|
||
private func obfuscateShadowsocksRelays(tunnelSettings: LatestTunnelSettings) -> REST.ServerRelaysResponse { | ||
let relays = relays | ||
let wireGuardObfuscation = tunnelSettings.wireGuardObfuscation | ||
|
||
return wireGuardObfuscation.state == .shadowsocks | ||
? filterShadowsocksRelays(from: relays, for: wireGuardObfuscation.shadowsocksPort) | ||
: relays | ||
} | ||
|
||
private func filterShadowsocksRelays( | ||
from relays: REST.ServerRelaysResponse, | ||
for port: WireGuardObfuscationShadowsockPort | ||
) -> REST.ServerRelaysResponse { | ||
let portRanges = RelaySelector.parseRawPortRanges(relays.wireguard.shadowsocksPortRanges) | ||
|
||
// If the selected port is within the shadowsocks port ranges we can select from all relays. | ||
guard | ||
case let .custom(port) = port, | ||
!portRanges.contains(where: { $0.contains(port) }) | ||
else { | ||
return relays | ||
} | ||
|
||
let filteredRelays = relays.wireguard.relays.filter { relay in | ||
relay.shadowsocksExtraAddrIn != nil | ||
} | ||
|
||
return REST.ServerRelaysResponse( | ||
locations: relays.locations, | ||
wireguard: REST.ServerWireguardTunnels( | ||
ipv4Gateway: relays.wireguard.ipv4Gateway, | ||
ipv6Gateway: relays.wireguard.ipv6Gateway, | ||
portRanges: relays.wireguard.portRanges, | ||
relays: filteredRelays, | ||
shadowsocksPortRanges: relays.wireguard.shadowsocksPortRanges | ||
), | ||
bridge: relays.bridge | ||
) | ||
} | ||
|
||
private func obfuscateUdpOverTcpPort( | ||
tunnelSettings: LatestTunnelSettings, | ||
connectionAttemptCount: UInt | ||
) -> RelayConstraint<UInt16> { | ||
switch tunnelSettings.wireGuardObfuscation.udpOverTcpPort { | ||
case .automatic: | ||
return (connectionAttemptCount % 2 == 0) ? .only(80) : .only(5001) | ||
case .port5001: | ||
return .only(5001) | ||
case .port80: | ||
return .only(80) | ||
} | ||
} | ||
|
||
private func obfuscateShadowsocksPort( | ||
tunnelSettings: LatestTunnelSettings, | ||
shadowsocksPortRanges: [[UInt16]] | ||
) -> RelayConstraint<UInt16> { | ||
let wireGuardObfuscation = tunnelSettings.wireGuardObfuscation | ||
|
||
let shadowsockPort: () -> UInt16? = { | ||
switch wireGuardObfuscation.shadowsocksPort { | ||
case let .custom(port): | ||
port | ||
default: | ||
RelaySelector.pickRandomPort(rawPortRanges: shadowsocksPortRanges) | ||
} | ||
} | ||
|
||
guard | ||
wireGuardObfuscation.state == .shadowsocks, | ||
let port = shadowsockPort() | ||
else { | ||
return tunnelSettings.relayConstraints.port | ||
} | ||
|
||
return .only(port) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.