diff --git a/IVPNClient/Models/Host.swift b/IVPNClient/Models/Host.swift index 40ab6ffe..b8f6dbd6 100644 --- a/IVPNClient/Models/Host.swift +++ b/IVPNClient/Models/Host.swift @@ -35,6 +35,7 @@ struct Host: Codable { var publicKey: String var localIP: String var ipv6: IPv6? + var isp: String var multihopPort: Int var load: Double var v2ray: String diff --git a/IVPNClient/Models/VPNServer.swift b/IVPNClient/Models/VPNServer.swift index e8ea0cea..0e9c3a97 100644 --- a/IVPNClient/Models/VPNServer.swift +++ b/IVPNClient/Models/VPNServer.swift @@ -91,16 +91,16 @@ class VPNServer { return gateway.components(separatedBy: CharacterSet.decimalDigits).joined() } - private (set) var gateway: String - private (set) var countryCode: String - private (set) var country: String - private (set) var city: String - private (set) var latitude: Double - private (set) var longitude: Double - private (set) var isp: String - private (set) var hosts: [Host] - private (set) var load: Double? - private (set) var ipv6: IPv6? + private(set) var gateway: String + private(set) var countryCode: String + private(set) var country: String + private(set) var city: String + private(set) var latitude: Double + private(set) var longitude: Double + private(set) var isp: String + private(set) var hosts: [Host] + private(set) var load: Double? + private(set) var ipv6: IPv6? var dnsName: String? // MARK: - Initialize - @@ -182,5 +182,52 @@ class VPNServer { return true } + + static func validHostMultiHopISP(_ first: VPNServer, _ second: VPNServer, _ firstHost: Host?, _ secondHost: Host?) -> Bool { + guard UserDefaults.shared.isMultiHop else { + return true + } + guard UserDefaults.standard.preventSameISPMultiHop else { + return true + } + + if let firstHost = firstHost, let secondHost = secondHost { + guard firstHost.isp != secondHost.isp else { + return false + } + } else { + guard first.isp != second.isp else { + return false + } + } + + return true + } + + static func validHostsMultiHopISP(_ first: VPNServer, _ second: VPNServer, _ hosts: [Host]) -> Bool { + guard UserDefaults.shared.isMultiHop else { + return true + } + guard UserDefaults.standard.preventSameISPMultiHop else { + return true + } + guard hasHostWithDifferentISP(second, hosts) else { + return false + } + + return true + } + + private static func hasHostWithDifferentISP(_ server: VPNServer, _ hosts: [Host]) -> Bool { + for host in hosts { + guard host.isp != server.isp else { + continue + } + + return true + } + + return false + } } diff --git a/IVPNClient/Models/VPNServerList.swift b/IVPNClient/Models/VPNServerList.swift index f8e09511..d7c9608f 100644 --- a/IVPNClient/Models/VPNServerList.swift +++ b/IVPNClient/Models/VPNServerList.swift @@ -404,6 +404,7 @@ class VPNServerList { dnsName: host["dns_name"] as? String ?? "", publicKey: host["public_key"] as? String ?? "", localIP: host["local_ip"] as? String ?? "", + isp: host["isp"] as? String ?? "", multihopPort: host["multihop_port"] as? Int ?? 0, load: host["load"] as? Double ?? 0, v2ray: host["v2ray"] as? String ?? "" diff --git a/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift b/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift index 964863fe..ade3077e 100644 --- a/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift +++ b/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift @@ -53,11 +53,15 @@ class ServerTableViewCell: UITableViewCell { var serverToValidate: VPNServer! { didSet { - guard VPNServer.validMultiHopCountry(viewModel.server, serverToValidate) else { + if !VPNServer.validMultiHopCountry(viewModel.server, serverToValidate) { contentView.alpha = 0.55 return } - guard VPNServer.validMultiHopISP(viewModel.server, serverToValidate) else { + if !viewModel.server.isHost, !VPNServer.validHostsMultiHopISP(viewModel.server, serverToValidate, viewModel.server.hosts) { + contentView.alpha = 0.55 + return + } + if viewModel.server.isHost, !VPNServer.validHostMultiHopISP(viewModel.server, serverToValidate, host, hostToValidate) { contentView.alpha = 0.55 return } @@ -70,6 +74,9 @@ class ServerTableViewCell: UITableViewCell { } } + var host: Host? + var hostToValidate: Host? + var indexPath: IndexPath! { didSet { if indexPath.row == 0 || !isMultiHop && indexPath.row == 1 { diff --git a/IVPNClient/Scenes/ViewControllers/ServerViewController.swift b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift index 406afda9..ed0e4075 100644 --- a/IVPNClient/Scenes/ViewControllers/ServerViewController.swift +++ b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift @@ -274,12 +274,13 @@ class ServerViewController: UITableViewController { return false } - private func validMultiHop(server: VPNServer, indexPath: IndexPath, force: Bool) -> Bool { + private func validMultiHop(server: VPNServer, host: Host?, indexPath: IndexPath, force: Bool) -> Bool { guard !force else { return true } let secondServer = isExitServer ? Application.shared.settings.selectedServer : Application.shared.settings.selectedExitServer + let secondHost = isExitServer ? Application.shared.settings.selectedHost : Application.shared.settings.selectedExitHost guard VPNServer.validMultiHop(server, secondServer) else { showAlert(title: "Entry and exit servers are the same", message: "Please select a different entry or exit server.") @@ -295,7 +296,15 @@ class ServerViewController: UITableViewController { return false } - guard VPNServer.validMultiHopISP(server, secondServer) else { + if host == nil, !VPNServer.validHostsMultiHopISP(server, secondServer, server.hosts) { + showActionAlert(title: VPNServer.validMultiHopISPTitle, message: VPNServer.validMultiHopISPMessage, action: "Continue", cancel: "Cancel", actionHandler: { [self] _ in + selected(indexPath: indexPath, force: true) + }) + tableView.deselectRow(at: indexPath, animated: true) + return false + } + + if host != nil, !VPNServer.validHostMultiHopISP(server, secondServer, host, secondHost) { showActionAlert(title: VPNServer.validMultiHopISPTitle, message: VPNServer.validMultiHopISPMessage, action: "Continue", cancel: "Cancel", actionHandler: { [self] _ in selected(indexPath: indexPath, force: true) }) @@ -371,7 +380,7 @@ class ServerViewController: UITableViewController { } } - guard validMultiHop(server: selectedServer, indexPath: indexPath, force: force) else { + guard validMultiHop(server: selectedServer, host: selectedHost, indexPath: indexPath, force: force) else { return } @@ -408,9 +417,20 @@ extension ServerViewController { cell.isFavorite = isFavorite cell.indexPath = indexPath cell.viewModel = VPNServerViewModel(server: server) - cell.serverToValidate = isExitServer ? Application.shared.settings.selectedServer : Application.shared.settings.selectedExitServer cell.expandedGateways = expandedGateways + if server.isHost { + if let serverByCity = Application.shared.serverList.getServer(byCity: server.city) { + cell.host = serverByCity.getHost(hostName: server.gateway) + cell.hostToValidate = isExitServer ? Application.shared.settings.selectedHost : Application.shared.settings.selectedExitHost + } + } else { + cell.host = nil + cell.hostToValidate = nil + } + + cell.serverToValidate = isExitServer ? Application.shared.settings.selectedServer : Application.shared.settings.selectedExitServer + return cell } diff --git a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift index 641dcaca..250e4c94 100644 --- a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift +++ b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift @@ -182,6 +182,22 @@ extension NETunnelProviderProtocol { return selectedHost } + if UserDefaults.shared.isMultiHop, UserDefaults.standard.preventSameISPMultiHop { + let hosts = Application.shared.settings.selectedServer.hosts + let secondServer = Application.shared.settings.selectedExitServer + var filteredHosts: [Host] = [] + + for host in hosts { + if host.isp != secondServer.isp { + filteredHosts.append(host) + } + } + + if let firstHost = filteredHosts.first { + return firstHost + } + } + if let firstHost = Application.shared.settings.selectedServer.hosts.first { return firstHost } @@ -194,6 +210,22 @@ extension NETunnelProviderProtocol { return selectedHost } + if UserDefaults.shared.isMultiHop, UserDefaults.standard.preventSameISPMultiHop { + let hosts = Application.shared.settings.selectedExitServer.hosts + let secondServer = Application.shared.settings.selectedServer + var filteredHosts: [Host] = [] + + for host in hosts { + if host.isp != secondServer.isp { + filteredHosts.append(host) + } + } + + if let firstHost = filteredHosts.first { + return firstHost + } + } + if let firstHost = Application.shared.settings.selectedExitServer.hosts.first { return firstHost }