Randomize provider server (#263)

* Pick random server within location

* Add toggle to provider section in profile
This commit is contained in:
Davide De Rosa 2023-03-19 08:19:32 +01:00 committed by GitHub
parent c85f3d894e
commit 17ae9793df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 125 additions and 64 deletions

View File

@ -77,7 +77,7 @@ class DefaultLightProfileManager: LightProfileManager {
$0.header < $1.header $0.header < $1.header
}.map { }.map {
let server: ProviderServer? let server: ProviderServer?
if let serverId = $0.providerServerId() { if let serverId = $0.providerServerId {
server = providerManager.server(withId: serverId) server = providerManager.server(withId: serverId)
} else { } else {
server = nil server = nil

View File

@ -92,13 +92,13 @@ extension EndpointView {
} }
_customEndpoint = .init { _customEndpoint = .init {
if currentProfile.value.isProvider { if currentProfile.value.isProvider {
return currentProfile.value.providerCustomEndpoint() return currentProfile.value.providerCustomEndpoint
} else { } else {
return currentProfile.value.hostOpenVPNSettings?.customEndpoint return currentProfile.value.hostOpenVPNSettings?.customEndpoint
} }
} set: { } set: {
if currentProfile.value.isProvider { if currentProfile.value.isProvider {
currentProfile.value.setProviderCustomEndpoint($0) currentProfile.value.providerCustomEndpoint = $0
} else { } else {
currentProfile.value.hostOpenVPNSettings?.customEndpoint = $0 currentProfile.value.hostOpenVPNSettings?.customEndpoint = $0
} }

View File

@ -35,16 +35,6 @@ extension ProfileView {
} }
var body: some View { var body: some View {
if currentProfile.value.isProvider {
Section {
Toggle(
L10n.Profile.Items.VpnResolvesHostname.caption,
isOn: $currentProfile.value.networkSettings.resolvesHostname
)
} footer: {
Text(L10n.Profile.Sections.VpnResolvesHostname.footer)
}
}
Section { Section {
Toggle( Toggle(
L10n.Profile.Items.VpnSurvivesSleep.caption, L10n.Profile.Items.VpnSurvivesSleep.caption,

View File

@ -77,6 +77,18 @@ extension ProfileView {
} footer: { } footer: {
currentProviderServerDescription.map(Text.init) currentProviderServerDescription.map(Text.init)
} }
Section {
Toggle(
L10n.Profile.Items.RandomizesServer.caption,
isOn: $currentProfile.value.providerRandomizesServer ?? false
)
Toggle(
L10n.Profile.Items.VpnResolvesHostname.caption,
isOn: $currentProfile.value.networkSettings.resolvesHostname
)
} footer: {
Text(L10n.Profile.Sections.VpnResolvesHostname.footer)
}
Section { Section {
NavigationLink { NavigationLink {
ProviderPresetView(currentProfile: currentProfile) ProviderPresetView(currentProfile: currentProfile)
@ -107,7 +119,14 @@ extension ProfileView {
} }
private var currentProviderServerDescription: String? { private var currentProviderServerDescription: String? {
profile.providerServer(providerManager)?.localizedLongDescription(withCategory: true) guard let server = profile.providerServer(providerManager) else {
return nil
}
if currentProfile.value.providerRandomizesServer ?? false {
return server.localizedCountry(withCategory: true)
} else {
return server.localizedLongDescription(withCategory: true)
}
} }
private var currentProviderCountryImage: Image? { private var currentProviderCountryImage: Image? {

View File

@ -71,7 +71,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
self.isEditable = isEditable self.isEditable = isEditable
_selectedServer = .init { _selectedServer = .init {
guard let serverId = currentProfile.value.providerServerId() else { guard let serverId = currentProfile.value.providerServerId else {
return nil return nil
} }
return providerManager.server(withId: serverId) return providerManager.server(withId: serverId)
@ -84,9 +84,9 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
isPresented.wrappedValue = false isPresented.wrappedValue = false
} }
_favoriteLocationIds = .init { _favoriteLocationIds = .init {
currentProfile.value.providerFavoriteLocationIds() currentProfile.value.providerFavoriteLocationIds
} set: { } set: {
currentProfile.value.setProviderFavoriteLocationIds($0) currentProfile.value.providerFavoriteLocationIds = $0
} }
} }
@ -151,6 +151,8 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
private func locationRow(_ location: ProviderLocation) -> some View { private func locationRow(_ location: ProviderLocation) -> some View {
if let onlyServer = location.onlyServer { if let onlyServer = location.onlyServer {
singleServerRow(location, onlyServer) singleServerRow(location, onlyServer)
} else if profile.providerRandomizesServer ?? false {
singleServerRow(location, nil)
} else { } else {
multipleServersRow(location) multipleServersRow(location)
} }
@ -170,9 +172,9 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
}) })
} }
private func singleServerRow(_ location: ProviderLocation, _ server: ProviderServer) -> some View { private func singleServerRow(_ location: ProviderLocation, _ server: ProviderServer?) -> some View {
Button { Button {
selectedServer = server selectedServer = server ?? location.servers?.randomElement()
} label: { } label: {
LocationRow( LocationRow(
location: location, location: location,

View File

@ -46,7 +46,7 @@ struct ProviderPresetView: View {
server = currentProfile.value.providerServer(providerManager) server = currentProfile.value.providerServer(providerManager)
_selectedPreset = .init { _selectedPreset = .init {
guard let serverId = currentProfile.value.providerServerId() else { guard let serverId = currentProfile.value.providerServerId else {
return nil return nil
} }
guard let server = providerManager.server(withId: serverId) else { guard let server = providerManager.server(withId: serverId) else {

View File

@ -795,6 +795,10 @@ internal enum L10n {
internal static let caption = L10n.tr("Localizable", "profile.items.provider.refresh.caption", fallback: "Refresh infrastructure") internal static let caption = L10n.tr("Localizable", "profile.items.provider.refresh.caption", fallback: "Refresh infrastructure")
} }
} }
internal enum RandomizesServer {
/// Randomize server
internal static let caption = L10n.tr("Localizable", "profile.items.randomizes_server.caption", fallback: "Randomize server")
}
internal enum UseProfile { internal enum UseProfile {
/// Use this profile /// Use this profile
internal static let caption = L10n.tr("Localizable", "profile.items.use_profile.caption", fallback: "Use this profile") internal static let caption = L10n.tr("Localizable", "profile.items.use_profile.caption", fallback: "Use this profile")

View File

@ -70,6 +70,14 @@ extension ProviderServer {
countryCode.localizedAsCountryCode countryCode.localizedAsCountryCode
} }
func localizedCountry(withCategory: Bool) -> String {
let desc = localizedCountry
if withCategory, !categoryName.isEmpty {
return "\(categoryName.uppercased()): \(desc)"
}
return desc
}
var localizedShortDescription: String? { var localizedShortDescription: String? {
var comps = localizedName.map { [$0] } ?? [] var comps = localizedName.map { [$0] } ?? []
if let serverIndex = serverIndex { if let serverIndex = serverIndex {

View File

@ -152,6 +152,7 @@
"profile.items.vpn.turn_off.caption" = "Disable VPN"; "profile.items.vpn.turn_off.caption" = "Disable VPN";
"profile.items.connection_status.caption" = "Status"; "profile.items.connection_status.caption" = "Status";
"profile.items.data_count.caption" = "Exchanged data"; "profile.items.data_count.caption" = "Exchanged data";
"profile.items.randomizes_server.caption" = "Randomize server";
"profile.items.provider.refresh.caption" = "Refresh infrastructure"; "profile.items.provider.refresh.caption" = "Refresh infrastructure";
"profile.items.category.caption" = "Category"; "profile.items.category.caption" = "Category";
"profile.items.only_shows_favorites.caption" = "Only show favorite locations"; "profile.items.only_shows_favorites.caption" = "Only show favorite locations";

View File

@ -27,7 +27,8 @@ import Foundation
import TunnelKitCore import TunnelKitCore
extension Profile { extension Profile {
public func hostAccount() -> Profile.Account? { public var hostAccount: Profile.Account? {
get {
switch currentVPNProtocol { switch currentVPNProtocol {
case .openVPN: case .openVPN:
return host?.ovpnSettings?.account return host?.ovpnSettings?.account
@ -36,16 +37,16 @@ extension Profile {
return nil return nil
} }
} }
set {
public mutating func setHostAccount(_ account: Profile.Account?) {
switch currentVPNProtocol { switch currentVPNProtocol {
case .openVPN: case .openVPN:
host?.ovpnSettings?.account = account host?.ovpnSettings?.account = newValue
case .wireGuard: case .wireGuard:
break break
} }
} }
}
public var hostOpenVPNSettings: OpenVPNSettings? { public var hostOpenVPNSettings: OpenVPNSettings? {
get { get {

View File

@ -41,16 +41,16 @@ extension Profile {
public var account: Profile.Account { public var account: Profile.Account {
get { get {
if isProvider { if isProvider {
return providerAccount() ?? .init() return providerAccount ?? .init()
} else { } else {
return hostAccount() ?? .init() return hostAccount ?? .init()
} }
} }
set { set {
if isProvider { if isProvider {
setProviderAccount(newValue) providerAccount = newValue
} else { } else {
setHostAccount(newValue) hostAccount = newValue
} }
} }
} }

View File

@ -45,7 +45,7 @@ extension Profile {
provider?.name provider?.name
} }
public func providerServerId() -> String? { public var providerServerId: String? {
provider?.vpnSettings[currentVPNProtocol]?.serverId provider?.vpnSettings[currentVPNProtocol]?.serverId
} }
@ -71,28 +71,40 @@ extension Profile {
provider?.vpnSettings[currentVPNProtocol]?.presetId = preset.id provider?.vpnSettings[currentVPNProtocol]?.presetId = preset.id
} }
public func providerFavoriteLocationIds() -> Set<String>? { public var providerFavoriteLocationIds: Set<String>? {
get {
provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds
} }
set {
public mutating func setProviderFavoriteLocationIds(_ ids: Set<String>?) { provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds = newValue
provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds = ids }
} }
public func providerCustomEndpoint() -> Endpoint? { public var providerCustomEndpoint: Endpoint? {
get {
provider?.vpnSettings[currentVPNProtocol]?.customEndpoint provider?.vpnSettings[currentVPNProtocol]?.customEndpoint
} }
set {
public mutating func setProviderCustomEndpoint(_ endpoint: Endpoint?) { provider?.vpnSettings[currentVPNProtocol]?.customEndpoint = newValue
provider?.vpnSettings[currentVPNProtocol]?.customEndpoint = endpoint }
} }
public func providerAccount() -> Profile.Account? { public var providerAccount: Profile.Account? {
get {
provider?.vpnSettings[currentVPNProtocol]?.account provider?.vpnSettings[currentVPNProtocol]?.account
} }
set {
provider?.vpnSettings[currentVPNProtocol]?.account = newValue
}
}
public mutating func setProviderAccount(_ account: Profile.Account?) { public var providerRandomizesServer: Bool? {
provider?.vpnSettings[currentVPNProtocol]?.account = account get {
provider?.randomizesServer
}
set {
provider?.randomizesServer = newValue
}
} }
} }

View File

@ -47,6 +47,8 @@ extension Profile {
public var vpnSettings: [VPNProtocolType: Settings] = [:] public var vpnSettings: [VPNProtocolType: Settings] = [:]
public var randomizesServer: Bool?
public init(_ name: ProviderName) { public init(_ name: ProviderName) {
self.name = name self.name = name
} }

View File

@ -39,7 +39,7 @@ extension Profile {
extension Profile { extension Profile {
public func providerServer(_ providerManager: ProviderManager) -> ProviderServer? { public func providerServer(_ providerManager: ProviderManager) -> ProviderServer? {
guard let serverId = providerServerId() else { guard let serverId = providerServerId else {
return nil return nil
} }
return providerManager.server(withId: serverId) return providerManager.server(withId: serverId)
@ -51,9 +51,21 @@ extension Profile {
} }
// infer remotes from preset + server // infer remotes from preset + server
guard let server = providerServer(providerManager) else { guard let selectedServer = providerServer(providerManager) else {
throw PassepartoutError.missingProviderServer throw PassepartoutError.missingProviderServer
} }
let server: ProviderServer
if providerRandomizesServer ?? false {
let location = selectedServer.location(withVPNProtocol: currentVPNProtocol)
let servers = providerManager.servers(forLocation: location)
guard let randomServerId = servers.randomElement()?.id,
let randomServer = providerManager.server(withId: randomServerId) else {
throw PassepartoutError.missingProviderServer
}
server = randomServer
} else {
server = selectedServer
}
guard let preset = providerPreset(server) else { guard let preset = providerPreset(server) else {
throw PassepartoutError.missingProviderPreset throw PassepartoutError.missingProviderPreset
} }
@ -68,8 +80,8 @@ extension Profile {
// apply provider settings (username, custom endpoint) // apply provider settings (username, custom endpoint)
let cfg = builder.build() let cfg = builder.build()
var settings = OpenVPNSettings(configuration: cfg) var settings = OpenVPNSettings(configuration: cfg)
settings.account = providerAccount() settings.account = providerAccount
settings.customEndpoint = providerCustomEndpoint() settings.customEndpoint = providerCustomEndpoint
return settings return settings
} }

View File

@ -45,6 +45,16 @@ extension ProviderServer {
public var locationId: String { public var locationId: String {
"\(providerMetadata.name):\(categoryName):\(countryCode)" "\(providerMetadata.name):\(categoryName):\(countryCode)"
} }
public func location(withVPNProtocol vpnProtocol: VPNProtocolType) -> ProviderLocation {
ProviderLocation(
providerMetadata: providerMetadata,
vpnProtocol: vpnProtocol,
categoryName: categoryName,
countryCode: countryCode,
servers: nil
)
}
} }
extension ProviderServer { extension ProviderServer {

View File

@ -85,7 +85,7 @@ extension VPNManager {
try await profileManager.makeProfileReady(profile) try await profileManager.makeProfileReady(profile)
} }
let oldServerId = profile.providerServerId() let oldServerId = profile.providerServerId
guard let newServer = providerManager.server(withId: newServerId) else { guard let newServer = providerManager.server(withId: newServerId) else {
pp_log.warning("Server \(newServerId) not found") pp_log.warning("Server \(newServerId) not found")
throw PassepartoutError.missingProviderServer throw PassepartoutError.missingProviderServer

View File

@ -206,15 +206,15 @@ extension VPNManager {
if newProfile.isProvider { if newProfile.isProvider {
// server changed? // server changed?
if newProfile.providerServerId() != lastProfile.providerServerId() { if newProfile.providerServerId != lastProfile.providerServerId {
pp_log.info("Provider server changed: \(newProfile.providerServerId()?.description ?? "nil")") pp_log.info("Provider server changed: \(newProfile.providerServerId?.description ?? "nil")")
isHandled = true isHandled = true
shouldReconnect = notDisconnected shouldReconnect = notDisconnected
} }
// endpoint changed? // endpoint changed?
else if newProfile.providerCustomEndpoint() != lastProfile.providerCustomEndpoint() { else if newProfile.providerCustomEndpoint != lastProfile.providerCustomEndpoint {
pp_log.info("Provider endpoint changed: \(newProfile.providerCustomEndpoint()?.description ?? "automatic")") pp_log.info("Provider endpoint changed: \(newProfile.providerCustomEndpoint?.description ?? "automatic")")
isHandled = true isHandled = true
shouldReconnect = notDisconnected shouldReconnect = notDisconnected
} }