From a463b54c05769fa98ea64e4842af942eae961004 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 1 Apr 2026 12:28:56 +0200 Subject: [PATCH 1/6] fix: update VPNServer.swift --- IVPNClient/Models/Host.swift | 1 + IVPNClient/Models/VPNServer.swift | 39 ++++++++++++++----- IVPNClient/Models/VPNServerList.swift | 1 + .../ServerViewController.swift | 7 ++-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/IVPNClient/Models/Host.swift b/IVPNClient/Models/Host.swift index 40ab6ffe8..b8f6dbd69 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 e8ea0cea9..8d7cb4e23 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,24 @@ class VPNServer { return true } + + static func validHostMultiHopISP(_ first: VPNServer, _ second: VPNServer, _ firstHost: Host?, _ secondHost: Host?, ignoreSettings: Bool = false) -> Bool { + guard UserDefaults.shared.isMultiHop else { + return true + } + guard UserDefaults.standard.preventSameISPMultiHop || ignoreSettings else { + return true + } + + if let firstHost = firstHost, let secondHost = secondHost { + guard firstHost.host != secondHost.host else { + return false + } + } else if first.isp == second.isp { + return false + } + + return true + } } diff --git a/IVPNClient/Models/VPNServerList.swift b/IVPNClient/Models/VPNServerList.swift index f8e09511e..d7c9608f6 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/ViewControllers/ServerViewController.swift b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift index 406afda91..efe1b7845 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,7 @@ class ServerViewController: UITableViewController { return false } - guard VPNServer.validMultiHopISP(server, secondServer) else { + guard VPNServer.validHostMultiHopISP(server, secondServer, host, secondHost) else { showActionAlert(title: VPNServer.validMultiHopISPTitle, message: VPNServer.validMultiHopISPMessage, action: "Continue", cancel: "Cancel", actionHandler: { [self] _ in selected(indexPath: indexPath, force: true) }) @@ -371,7 +372,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 } From 0f9986a0e1b1973370a2b541376ab8f7790e8624 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 1 Apr 2026 15:32:14 +0200 Subject: [PATCH 2/6] fix: update ServerTableViewCell.swift --- IVPNClient/Models/VPNServer.swift | 8 +++++--- .../Scenes/TableCells/ServerTableViewCell.swift | 5 ++++- .../ViewControllers/ServerViewController.swift | 13 ++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/IVPNClient/Models/VPNServer.swift b/IVPNClient/Models/VPNServer.swift index 8d7cb4e23..13d8f0d44 100644 --- a/IVPNClient/Models/VPNServer.swift +++ b/IVPNClient/Models/VPNServer.swift @@ -192,11 +192,13 @@ class VPNServer { } if let firstHost = firstHost, let secondHost = secondHost { - guard firstHost.host != secondHost.host else { + guard firstHost.isp != secondHost.isp else { + return false + } + } else { + guard first.isp != second.isp else { return false } - } else if first.isp == second.isp { - return false } return true diff --git a/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift b/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift index 964863fec..0b89f87c7 100644 --- a/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift +++ b/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift @@ -57,7 +57,7 @@ class ServerTableViewCell: UITableViewCell { contentView.alpha = 0.55 return } - guard VPNServer.validMultiHopISP(viewModel.server, serverToValidate) else { + guard VPNServer.validHostMultiHopISP(viewModel.server, serverToValidate, host, hostToValidate) else { contentView.alpha = 0.55 return } @@ -70,6 +70,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 efe1b7845..ae1b7145f 100644 --- a/IVPNClient/Scenes/ViewControllers/ServerViewController.swift +++ b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift @@ -409,9 +409,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 } From b9887827652f5bcf63c25604b4e7f49bba24c54c Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 2 Apr 2026 10:54:35 +0200 Subject: [PATCH 3/6] fix: update VPNServer.swift --- IVPNClient/Models/VPNServer.swift | 30 +++++++++++++++++-- .../TableCells/ServerTableViewCell.swift | 8 +++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/IVPNClient/Models/VPNServer.swift b/IVPNClient/Models/VPNServer.swift index 13d8f0d44..0e9c3a974 100644 --- a/IVPNClient/Models/VPNServer.swift +++ b/IVPNClient/Models/VPNServer.swift @@ -183,11 +183,11 @@ class VPNServer { return true } - static func validHostMultiHopISP(_ first: VPNServer, _ second: VPNServer, _ firstHost: Host?, _ secondHost: Host?, ignoreSettings: Bool = false) -> Bool { + static func validHostMultiHopISP(_ first: VPNServer, _ second: VPNServer, _ firstHost: Host?, _ secondHost: Host?) -> Bool { guard UserDefaults.shared.isMultiHop else { return true } - guard UserDefaults.standard.preventSameISPMultiHop || ignoreSettings else { + guard UserDefaults.standard.preventSameISPMultiHop else { return true } @@ -203,5 +203,31 @@ class VPNServer { 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/Scenes/TableCells/ServerTableViewCell.swift b/IVPNClient/Scenes/TableCells/ServerTableViewCell.swift index 0b89f87c7..ade3077e6 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.validHostMultiHopISP(viewModel.server, serverToValidate, host, hostToValidate) 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 } From 2f09baa13a2b4c115db0d36af8cb835cc6e8f76d Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 2 Apr 2026 11:23:13 +0200 Subject: [PATCH 4/6] fix: update ServerViewController.swift --- .../Scenes/ViewControllers/ServerViewController.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/IVPNClient/Scenes/ViewControllers/ServerViewController.swift b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift index ae1b7145f..ed0e40757 100644 --- a/IVPNClient/Scenes/ViewControllers/ServerViewController.swift +++ b/IVPNClient/Scenes/ViewControllers/ServerViewController.swift @@ -296,7 +296,15 @@ class ServerViewController: UITableViewController { return false } - guard VPNServer.validHostMultiHopISP(server, secondServer, host, secondHost) 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) }) From ea6b959184f4519fe9b384d4c4c6769726dccb85 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 2 Apr 2026 12:53:05 +0200 Subject: [PATCH 5/6] feat: update NETunnelProviderProtocol+Ext.swift --- .../NETunnelProviderProtocol+Ext.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift index 641dcaca2..7a1b9f91d 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 allHosts = Application.shared.settings.selectedServer.hosts + let secondServer = Application.shared.settings.selectedExitServer + var filteredHosts: [Host] = [] + + for host in allHosts { + 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 allHosts = Application.shared.settings.selectedExitServer.hosts + let secondServer = Application.shared.settings.selectedServer + var filteredHosts: [Host] = [] + + for host in allHosts { + 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 } From 5e0ef5acb713b468aeea67588d6d6acee9922109 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 2 Apr 2026 13:32:38 +0200 Subject: [PATCH 6/6] feat: update NETunnelProviderProtocol+Ext.swift --- .../Extensions/NETunnelProviderProtocol+Ext.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift index 7a1b9f91d..250e4c94b 100644 --- a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift +++ b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift @@ -183,11 +183,11 @@ extension NETunnelProviderProtocol { } if UserDefaults.shared.isMultiHop, UserDefaults.standard.preventSameISPMultiHop { - let allHosts = Application.shared.settings.selectedServer.hosts + let hosts = Application.shared.settings.selectedServer.hosts let secondServer = Application.shared.settings.selectedExitServer var filteredHosts: [Host] = [] - for host in allHosts { + for host in hosts { if host.isp != secondServer.isp { filteredHosts.append(host) } @@ -211,11 +211,11 @@ extension NETunnelProviderProtocol { } if UserDefaults.shared.isMultiHop, UserDefaults.standard.preventSameISPMultiHop { - let allHosts = Application.shared.settings.selectedExitServer.hosts + let hosts = Application.shared.settings.selectedExitServer.hosts let secondServer = Application.shared.settings.selectedServer var filteredHosts: [Host] = [] - for host in allHosts { + for host in hosts { if host.isp != secondServer.isp { filteredHosts.append(host) }