diff --git a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift
index 29c6e2131..118545c2c 100644
--- a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift
+++ b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift
@@ -15,12 +15,6 @@ struct OpenVPNConfig: Decodable {
extension PacketTunnelProvider {
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
- // Reset session-derived state so reconnects never reuse stale gateway/address data.
- openVpnGatewayAddress = nil
- openVpnLocalAddress = nil
- openVpnLocalMask = nil
- lastOpenVPNSettings = nil
-
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
@@ -32,20 +26,6 @@ extension PacketTunnelProvider {
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
- splitTunnelType = openVPNConfig.splitTunnelType
- splitTunnelSites = openVPNConfig.splitTunnelSites
- openVpnDnsServers = Self.extractDnsServers(from: openVPNConfig.config)
- openVpnRemoteAddress = Self.extractRemoteHost(from: openVPNConfig.config)
- openVpnRedirectGatewayDef1 = Self.hasRedirectGatewayDef1(in: openVPNConfig.config)
- if let openVpnRemoteAddress {
- ovpnLog(.info, title: "Remote", message: "host=\(openVpnRemoteAddress)")
- }
- if !openVpnDnsServers.isEmpty {
- ovpnLog(.info, title: "DNS", message: "servers=\(openVpnDnsServers)")
- }
- if openVpnRedirectGatewayDef1 {
- ovpnLog(.info, title: "IPv4Routes", message: "redirect-gateway def1 detected")
- }
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
@@ -93,11 +73,6 @@ extension PacketTunnelProvider {
let digestString = digest.map { String(format: "%02x", $0) }.joined()
ovpnLog(.info, title: "ConfigDigest", message: digestString)
- let hasCertTag = configString.contains("") && configString.contains("")
- let hasKeyTag = configString.contains("") && configString.contains("")
- let hasAuthUserPass = configString.contains("auth-user-pass")
- ovpnLog(.info, title: "ConfigCreds", message: "inlineCert=\(hasCertTag) inlineKey=\(hasKeyTag) authUserPass=\(hasAuthUserPass)")
-
let hasTlsAuthOpen = configString.contains("")
let hasTlsAuthClose = configString.contains("")
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
@@ -108,98 +83,27 @@ extension PacketTunnelProvider {
ovpnLog(.debug, title: "ConfigHead", message: head)
ovpnLog(.debug, title: "ConfigTail", message: tail)
- if hasTlsAuthOpen && hasTlsAuthClose {
- ovpnLog(.info, title: "TLSAuthSanitized", message: "preserve original tls-auth block")
+ if let start = configString.range(of: ""),
+ let end = configString.range(of: "", range: start.upperBound.. Void
) {
- guard var effectiveSettings = networkSettings else {
- ovpnLog(.info, title: "SetTunnelNetworkSettings", message: "nil settings; skipping update")
- completionHandler(nil)
- return
- }
- let splitType = splitTunnelType ?? 0
-
- if let ipv4Settings = effectiveSettings.ipv4Settings {
- openVpnLocalAddress = ipv4Settings.addresses.first
- openVpnLocalMask = ipv4Settings.subnetMasks.first
- }
-
- let serverIP = openVPNAdapter.connectionInformation?.serverIP
- let configRemote = openVpnRemoteAddress
- let serverEndpoint: String? = {
- if let ip = serverIP, Self.isIPv4Address(ip) { return ip }
- if let ip = configRemote, Self.isIPv4Address(ip) { return ip }
- return effectiveSettings.tunnelRemoteAddress
- }()
-
- if let serverEndpoint,
- Self.isIPv4Address(serverEndpoint),
- effectiveSettings.tunnelRemoteAddress != serverEndpoint {
- let updatedSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: serverEndpoint)
- updatedSettings.ipv4Settings = effectiveSettings.ipv4Settings
- updatedSettings.ipv6Settings = effectiveSettings.ipv6Settings
- updatedSettings.dnsSettings = effectiveSettings.dnsSettings
- updatedSettings.proxySettings = effectiveSettings.proxySettings
- updatedSettings.mtu = effectiveSettings.mtu
- effectiveSettings = updatedSettings
- ovpnLog(.info, title: "Remote", message: "tunnelRemoteAddress set to server=\(serverEndpoint)")
- } else if let serverEndpoint, !Self.isIPv4Address(serverEndpoint) {
- ovpnLog(.info, title: "Remote", message: "skip tunnelRemoteAddress override; non-ip serverEndpoint=\(serverEndpoint)")
- }
-
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
- if let dnsSettings = effectiveSettings.dnsSettings {
- if dnsSettings.servers.isEmpty, !openVpnDnsServers.isEmpty {
- let newSettings = NEDNSSettings(servers: openVpnDnsServers)
- newSettings.matchDomains = dnsSettings.matchDomains
- effectiveSettings.dnsSettings = newSettings
- }
- } else if !openVpnDnsServers.isEmpty {
- let newSettings = NEDNSSettings(servers: openVpnDnsServers)
- effectiveSettings.dnsSettings = newSettings
- }
+ networkSettings?.dnsSettings?.matchDomains = [""]
- effectiveSettings.dnsSettings?.matchDomains = [""]
- if let dnsSettings = effectiveSettings.dnsSettings {
- let servers = dnsSettings.servers.joined(separator: ",")
- let domains = dnsSettings.matchDomains?.joined(separator: ",") ?? ""
- ovpnLog(.info, title: "DNS", message: "servers=[\(servers)] matchDomains=[\(domains)]")
- } else {
- ovpnLog(.error, title: "DNS", message: "dnsSettings is nil")
- }
-
- let tunnelRemote = effectiveSettings.tunnelRemoteAddress
- if !tunnelRemote.isEmpty {
- ovpnLog(.info, title: "Remote", message: "tunnelRemoteAddress=\(tunnelRemote)")
- } else if let remoteAddress = openVpnRemoteAddress {
- ovpnLog(.info, title: "Remote", message: "tunnelRemoteAddress is empty, configRemote=\(remoteAddress)")
- }
-
- if let ipv4Settings = effectiveSettings.ipv4Settings {
- let included = (ipv4Settings.includedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationSubnetMask)" }
- let excluded = (ipv4Settings.excludedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationSubnetMask)" }
- let addresses = ipv4Settings.addresses.joined(separator: ",")
- let masks = ipv4Settings.subnetMasks.joined(separator: ",")
- let router: String
-#if os(macOS)
- if #available(macOS 13.0, *) {
- router = ipv4Settings.router ?? ""
- } else {
- router = ""
- }
-#else
- router = ""
-#endif
- ovpnLog(.info, title: "IPv4RoutesPre", message: "addresses=[\(addresses)] masks=[\(masks)] router=\(router) included=\(included) excluded=\(excluded)")
- } else {
- ovpnLog(.error, title: "IPv4RoutesPre", message: "ipv4Settings is nil")
- }
-
- if let ipv6Settings = effectiveSettings.ipv6Settings {
- let included = (ipv6Settings.includedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationNetworkPrefixLength)" }
- let excluded = (ipv6Settings.excludedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationNetworkPrefixLength)" }
- let addresses = ipv6Settings.addresses.joined(separator: ",")
- let prefixes = ipv6Settings.networkPrefixLengths.map { "\($0)" }.joined(separator: ",")
- ovpnLog(.info, title: "IPv6RoutesPre", message: "addresses=[\(addresses)] prefixes=[\(prefixes)] included=\(included) excluded=\(excluded)")
- }
-
- if splitType == 1 {
+ if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]()
guard let splitTunnelSites else {
@@ -390,8 +195,9 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
}
}
- effectiveSettings.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
- } else if splitType == 2 {
+ networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
+ } else {
+ if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
@@ -419,418 +225,14 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
- effectiveSettings.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
- effectiveSettings.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
- effectiveSettings.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
- } else {
- // Full tunnel: rely on adapter-provided routes.
- }
-
- if let serverEndpoint,
- Self.isIPv4Address(serverEndpoint),
- let ipv4Settings = effectiveSettings.ipv4Settings {
- let hostMask = "255.255.255.255"
- var excluded = ipv4Settings.excludedRoutes ?? []
- let alreadyExcluded = excluded.contains {
- $0.destinationAddress == serverEndpoint && $0.destinationSubnetMask == hostMask
- }
- if !alreadyExcluded {
- excluded.append(NEIPv4Route(destinationAddress: serverEndpoint, subnetMask: hostMask))
- ipv4Settings.excludedRoutes = excluded
- ovpnLog(.info, title: "IPv4Routes", message: "excluded remoteAddress=\(serverEndpoint)")
- }
- } else if let serverEndpoint {
- ovpnLog(.info, title: "IPv4Routes", message: "skip explicit remote exclude; non-ip server=\(serverEndpoint)")
- }
-
- let localAddr = openVpnLocalAddress
- var net30Gateway: String?
- if let localAddr, let mask = openVpnLocalMask {
- net30Gateway = Self.net30Peer(for: localAddr, mask: mask)
- }
- var gateway = net30Gateway
- if let adapterGateway = openVPNAdapter.connectionInformation?.gatewayIPv4, !adapterGateway.isEmpty {
- if let localAddr, adapterGateway == localAddr {
- ovpnLog(.info, title: "IPv4Gateway", message: "ignore adapter gateway equal to local address=\(adapterGateway)")
- } else if let net30Gateway, net30Gateway != adapterGateway {
- ovpnLog(.info, title: "IPv4Gateway", message: "ignore mismatched adapter gateway=\(adapterGateway), using net30 peer=\(net30Gateway)")
- } else {
- gateway = adapterGateway
+ networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
+ networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
+ networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
- openVpnGatewayAddress = gateway
- if let gateway, !gateway.isEmpty {
- ovpnLog(.info, title: "IPv4Gateway", message: "gateway=\(gateway)")
- }
-#if os(macOS)
- if splitType == 0, let gateway, !gateway.isEmpty, effectiveSettings.tunnelRemoteAddress != gateway {
- let updatedSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: gateway)
- updatedSettings.ipv4Settings = effectiveSettings.ipv4Settings
- updatedSettings.ipv6Settings = effectiveSettings.ipv6Settings
- updatedSettings.dnsSettings = effectiveSettings.dnsSettings
- updatedSettings.proxySettings = effectiveSettings.proxySettings
- updatedSettings.mtu = effectiveSettings.mtu
- effectiveSettings = updatedSettings
- ovpnLog(.info, title: "Remote", message: "tunnelRemoteAddress set to gateway=\(gateway) on macOS full-tunnel")
- }
-#endif
-#if os(macOS)
- if var ipv4Settings = effectiveSettings.ipv4Settings {
- if splitType == 0 {
- let hasNet30Mask = ipv4Settings.subnetMasks.contains("255.255.255.252")
- if hasNet30Mask {
- let normalizedMasks = Array(repeating: "255.255.255.255",
- count: ipv4Settings.subnetMasks.count)
- let normalized = NEIPv4Settings(addresses: ipv4Settings.addresses,
- subnetMasks: normalizedMasks)
- normalized.includedRoutes = ipv4Settings.includedRoutes
- normalized.excludedRoutes = ipv4Settings.excludedRoutes
- if #available(macOS 13.0, *) {
- normalized.router = ipv4Settings.router
- }
- ipv4Settings = normalized
- ovpnLog(.info, title: "IPv4Routes", message: "normalized net30 /30 masks to /32 on macOS full-tunnel")
- }
-
- if let gateway, !gateway.isEmpty {
- if #available(macOS 13.0, *) {
- ipv4Settings.router = gateway
- ovpnLog(.info, title: "IPv4Routes", message: "set ipv4 router=\(gateway) on macOS full-tunnel")
- }
- }
-
- var included = ipv4Settings.includedRoutes ?? []
- let hasDefault = included.contains {
- $0.destinationAddress == "0.0.0.0" && $0.destinationSubnetMask == "0.0.0.0"
- }
- if hasDefault {
- included.removeAll {
- $0.destinationAddress == "0.0.0.0" && $0.destinationSubnetMask == "0.0.0.0"
- }
- }
- let hasDef1Low = included.contains {
- $0.destinationAddress == "0.0.0.0" && $0.destinationSubnetMask == "128.0.0.0"
- }
- let hasDef1High = included.contains {
- $0.destinationAddress == "128.0.0.0" && $0.destinationSubnetMask == "128.0.0.0"
- }
- if (hasDefault || openVpnRedirectGatewayDef1) && !(hasDef1Low && hasDef1High) {
- if !hasDef1Low {
- let route = NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "128.0.0.0")
- if let gateway, !gateway.isEmpty {
- route.gatewayAddress = gateway
- }
- included.append(route)
- }
- if !hasDef1High {
- let route = NEIPv4Route(destinationAddress: "128.0.0.0", subnetMask: "128.0.0.0")
- if let gateway, !gateway.isEmpty {
- route.gatewayAddress = gateway
- }
- included.append(route)
- }
- ovpnLog(.info, title: "IPv4Routes", message: "ensured def1 routes (/1 + /1) on macOS full-tunnel")
- }
- if let gateway, !gateway.isEmpty {
- included = included.map { route in
- let isDef1 =
- (route.destinationAddress == "0.0.0.0" && route.destinationSubnetMask == "128.0.0.0") ||
- (route.destinationAddress == "128.0.0.0" && route.destinationSubnetMask == "128.0.0.0")
- guard isDef1 else { return route }
- if route.gatewayAddress == gateway {
- return route
- }
- let updatedRoute = NEIPv4Route(destinationAddress: route.destinationAddress,
- subnetMask: route.destinationSubnetMask)
- updatedRoute.gatewayAddress = gateway
- return updatedRoute
- }
- ovpnLog(.info, title: "IPv4Routes", message: "set gateway=\(gateway) on macOS def1 routes")
- }
- ipv4Settings.includedRoutes = included
- effectiveSettings.ipv4Settings = ipv4Settings
- }
- }
-#endif
- if let ipv4Settings = effectiveSettings.ipv4Settings {
- let included = (ipv4Settings.includedRoutes ?? []).map {
- let gw = $0.gatewayAddress ?? ""
- return "\($0.destinationAddress)/\($0.destinationSubnetMask) gw=\(gw)"
- }
- let excluded = (ipv4Settings.excludedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationSubnetMask)" }
- let addresses = ipv4Settings.addresses.joined(separator: ",")
- let masks = ipv4Settings.subnetMasks.joined(separator: ",")
- let router: String
-#if os(macOS)
- if #available(macOS 13.0, *) {
- router = ipv4Settings.router ?? ""
- } else {
- router = ""
- }
-#else
- router = ""
-#endif
- ovpnLog(.info, title: "IPv4Routes", message: "addresses=[\(addresses)] masks=[\(masks)] router=\(router) included=\(included) excluded=\(excluded)")
- } else {
- ovpnLog(.error, title: "IPv4Routes", message: "ipv4Settings is nil")
- }
-
- if let ipv6Settings = effectiveSettings.ipv6Settings {
- let included = (ipv6Settings.includedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationNetworkPrefixLength)" }
- let excluded = (ipv6Settings.excludedRoutes ?? []).map { "\($0.destinationAddress)/\($0.destinationNetworkPrefixLength)" }
- let addresses = ipv6Settings.addresses.joined(separator: ",")
- let prefixes = ipv6Settings.networkPrefixLengths.map { "\($0)" }.joined(separator: ",")
- ovpnLog(.info, title: "IPv6Routes", message: "addresses=[\(addresses)] prefixes=[\(prefixes)] included=\(included) excluded=\(excluded)")
- }
-#if os(macOS)
- if effectiveSettings.ipv6Settings != nil {
- effectiveSettings.ipv6Settings = nil
- ovpnLog(.info, title: "IPv6", message: "cleared ipv6Settings on macOS")
- }
-#endif
-
- lastOpenVPNSettings = effectiveSettings
-
// Set the network settings for the current tunneling session.
- setTunnelNetworkSettings(effectiveSettings) { error in
- if let error {
- ovpnLog(.error, title: "SetTunnelNetworkSettings", message: error.localizedDescription)
- } else {
- ovpnLog(.info, title: "SetTunnelNetworkSettings", message: "ok")
- }
- completionHandler(error)
- }
- }
-
- private static func extractDnsServers(from config: String) -> [String] {
- let lines = config.split(whereSeparator: \.isNewline)
- var servers: [String] = []
- for line in lines {
- let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
- if trimmed.hasPrefix("dhcp-option DNS ") {
- let parts = trimmed.split(separator: " ")
- if let last = parts.last {
- servers.append(String(last))
- }
- }
- }
- return servers
- }
-
- private static func extractRemoteHost(from config: String) -> String? {
- let lines = config.split(whereSeparator: \.isNewline)
- for line in lines {
- let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
- if trimmed.hasPrefix("remote ") {
- let parts = trimmed.split(separator: " ")
- if parts.count >= 2 {
- return String(parts[1])
- }
- }
- }
- return nil
- }
-
- private static func hasRedirectGatewayDef1(in config: String) -> Bool {
- let lines = config.split(whereSeparator: \.isNewline)
- for line in lines {
- let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
- if trimmed.hasPrefix("redirect-gateway") {
- return trimmed.split(whereSeparator: { $0 == " " || $0 == "\t" }).contains("def1")
- }
- }
- return false
- }
-
- private static func net30Peer(for address: String, mask: String) -> String? {
- guard mask == "255.255.255.252" else { return nil }
- let parts = address.split(separator: ".")
- guard parts.count == 4 else { return nil }
- var octets: [Int] = []
- for part in parts {
- guard let num = Int(part), num >= 0 && num <= 255 else { return nil }
- octets.append(num)
- }
- let ip = (octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]
- let network = ip & ~3
- let host = ip - network
- let peerHost: Int
- switch host {
- case 1: peerHost = 2
- case 2: peerHost = 1
- default: return nil
- }
- let peerIP = network + peerHost
- return "\((peerIP >> 24) & 0xff).\((peerIP >> 16) & 0xff).\((peerIP >> 8) & 0xff).\(peerIP & 0xff)"
- }
-
- private func logOpenVPNConnectionInfo() {
- guard let info = ovpnAdapter?.connectionInformation else { return }
- let message = "vpnIPv4=\(info.vpnIPv4 ?? "") gatewayIPv4=\(info.gatewayIPv4 ?? "") serverIP=\(info.serverIP ?? "") tun=\(info.tunName ?? "")"
- ovpnLog(.info, title: "ConnInfo", message: message)
- }
-
- private static func normalizeInlineBlock(
- in config: String,
- tag: String,
- beginMarkers: [String],
- endMarkers: [String]
- ) -> String {
- guard !beginMarkers.isEmpty, !endMarkers.isEmpty else { return config }
-
- var normalizedConfig = config
- let openTag = "<\(tag)>"
- let closeTag = "\(tag)>"
- var searchStart = normalizedConfig.startIndex
-
- while let openRange = normalizedConfig.range(of: openTag, range: searchStart..= beginIndex {
- let extracted = lines[beginIndex...endIndex].joined(separator: "\n")
- let replacement = "<\(tag)>\n\(extracted)\n\(tag)>"
- normalizedConfig.replaceSubrange(openRange.lowerBound.. linesIn=\(lines.count) linesOut=\(endIndex - beginIndex + 1)")
- searchStart = normalizedConfig.index(openRange.lowerBound, offsetBy: replacement.count)
- } else {
- ovpnLog(.error, title: "ConfigInline", message: "tag=<\(tag)> missing markers, keeping original body")
- searchStart = closeRange.upperBound
- }
- }
-
- return normalizedConfig
- }
-
-
- private static func stripUnsupportedOptions(forOpenVPNAdapter config: String) -> String {
- let unsupportedTokens: Set = [
- "block-ipv6",
- "script-security",
- "up",
- "down",
- "resolv-retry",
- "persist-key",
- "persist-tun",
- "compat-mode",
- "disable-dco"
- ]
- let inlineBlockTags: Set = [
- "ca",
- "cert",
- "key",
- "pkcs12",
- "tls-auth",
- "tls-crypt",
- "tls-crypt-v2",
- "secret",
- "crl-verify",
- "extra-certs"
- ]
-
- var removed: [String: Int] = [:]
- var normalized: [String: Int] = [:]
- var output: [String] = []
- var activeInlineTag: String?
-
- for rawLine in config.split(whereSeparator: \.isNewline) {
- let line = String(rawLine)
- let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
- if trimmed.isEmpty {
- output.append(line)
- continue
- }
-
- let trimmedLowercased = trimmed.lowercased()
-
- if let currentInlineTag = activeInlineTag {
- output.append(line)
- if trimmedLowercased == "\(currentInlineTag)>" {
- activeInlineTag = nil
- }
- continue
- }
-
- if trimmedLowercased.hasPrefix("<"),
- trimmedLowercased.hasSuffix(">"),
- !trimmedLowercased.hasPrefix("") {
- let tagContent = String(trimmedLowercased.dropFirst().dropLast())
- let tagName = tagContent
- .split(whereSeparator: { $0 == " " || $0 == "\t" })
- .first
- .map(String.init) ?? ""
- if inlineBlockTags.contains(tagName) {
- activeInlineTag = tagName
- output.append(line)
- continue
- }
- }
-
- if trimmed.hasPrefix("#") || trimmed.hasPrefix(";") {
- output.append(line)
- continue
- }
-
- let parts = trimmed.split(whereSeparator: { $0 == " " || $0 == "\t" })
- let token = parts.first.map(String.init)?.lowercased() ?? ""
- if trimmedLowercased.hasPrefix("redirect-gateway") || token.hasPrefix("redirect-gateway") {
- let hasDef1 = parts.dropFirst().contains { String($0).lowercased().hasPrefix("def1") }
- if hasDef1 {
- output.append("redirect-gateway def1")
- normalized["redirect-gateway", default: 0] += 1
- } else {
- removed["redirect-gateway", default: 0] += 1
- }
- continue
- }
-
- if let matchedUnsupported = unsupportedTokens.first(where: { token.hasPrefix($0) }) {
- removed[matchedUnsupported, default: 0] += 1
- continue
- }
-
- output.append(line)
- }
-
- if !removed.isEmpty {
- let summary = removed.keys.sorted().map { "\($0)=\(removed[$0] ?? 0)" }.joined(separator: " ")
- ovpnLog(.info, title: "ConfigStrip", message: summary)
- }
- if !normalized.isEmpty {
- let summary = normalized.keys.sorted().map { "\($0)=\(normalized[$0] ?? 0)" }.joined(separator: " ")
- ovpnLog(.info, title: "ConfigNormalize", message: summary)
- }
-
- return output.joined(separator: "\n")
- }
-
- private static func isIPv4Address(_ value: String) -> Bool {
- let parts = value.split(separator: ".")
- if parts.count != 4 { return false }
- for part in parts {
- guard let num = Int(part), num >= 0 && num <= 255 else { return false }
- }
- return true
+ setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
@@ -848,9 +250,6 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
startHandler(nil)
self.startHandler = nil
-
- logOpenVPNConnectionInfo()
- refreshOpenVPNSettingsAfterConnect()
case .disconnected:
guard let stopHandler = stopHandler else { return }
@@ -893,41 +292,4 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// Handle log messages
ovpnLog(.info, message: logMessage)
}
-
- func openVPNAdapterDidReceiveClockTick(_ openVPNAdapter: OpenVPNAdapter) {
- let now = Date()
- if now.timeIntervalSince(lastOpenVPNStatsLogTime) < 5 {
- return
- }
- lastOpenVPNStatsLogTime = now
-
- let transport = openVPNAdapter.transportStatistics
- let iface = openVPNAdapter.interfaceStatistics
- let transportLine = "transport bytesIn=\(transport.bytesIn) bytesOut=\(transport.bytesOut) packetsIn=\(transport.packetsIn) packetsOut=\(transport.packetsOut)"
- let ifaceLine = "iface bytesIn=\(iface.bytesIn) bytesOut=\(iface.bytesOut) packetsIn=\(iface.packetsIn) packetsOut=\(iface.packetsOut) errorsIn=\(iface.errorsIn) errorsOut=\(iface.errorsOut)"
- ovpnLog(.info, title: "Stats", message: "\(transportLine) | \(ifaceLine)")
- }
-
- private func refreshOpenVPNSettingsAfterConnect() {
- let localAddr = openVpnLocalAddress
- var net30Gateway: String?
- if let localAddr, let mask = openVpnLocalMask {
- net30Gateway = Self.net30Peer(for: localAddr, mask: mask)
- }
- var gateway = net30Gateway
- if let adapterGateway = ovpnAdapter?.connectionInformation?.gatewayIPv4, !adapterGateway.isEmpty {
- if let localAddr, adapterGateway == localAddr {
- ovpnLog(.info, title: "IPv4Gateway", message: "post-connect ignoring adapter gateway equal to local address=\(adapterGateway)")
- } else if let net30Gateway, net30Gateway != adapterGateway {
- ovpnLog(.info, title: "IPv4Gateway", message: "post-connect keeping net30 peer=\(net30Gateway), adapter gateway=\(adapterGateway)")
- } else {
- gateway = adapterGateway
- }
- }
-
- guard let gateway, !gateway.isEmpty else { return }
- openVpnGatewayAddress = gateway
- ovpnLog(.info, title: "IPv4Gateway", message: "post-connect gateway=\(gateway)")
- }
-
}
diff --git a/client/platforms/ios/PacketTunnelProvider.swift b/client/platforms/ios/PacketTunnelProvider.swift
index 7b5f3d899..8a6784133 100644
--- a/client/platforms/ios/PacketTunnelProvider.swift
+++ b/client/platforms/ios/PacketTunnelProvider.swift
@@ -48,14 +48,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var splitTunnelType: Int?
var splitTunnelSites: [String]?
- var openVpnDnsServers: [String] = []
- var openVpnRemoteAddress: String?
- var openVpnRedirectGatewayDef1 = false
- var openVpnLocalAddress: String?
- var openVpnLocalMask: String?
- var openVpnGatewayAddress: String?
- var lastOpenVPNSettings: NEPacketTunnelNetworkSettings?
- var lastOpenVPNStatsLogTime = Date.distantPast
let vpnReachability = OpenVPNReachability()
@@ -86,8 +78,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard hasMeaningfulChange, let proto = self.protoType else { return }
- // WireGuard/AWG and OpenVPN manage network changes internally; avoid restarting the tunnel here.
- if proto == .wireguard || proto == .openvpn {
+ // WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
+ if proto == .wireguard {
return
}
@@ -187,15 +179,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
neLog(.info, message: "Start tunnel")
- if let vpnProto = protocolConfiguration as? NEVPNProtocol {
- if #available(iOS 14.0, macOS 11.0, *) {
- var details = "includeAllNetworks=\(vpnProto.includeAllNetworks)"
- if #available(iOS 14.2, macOS 11.0, *) {
- details += " excludeLocalNetworks=\(vpnProto.excludeLocalNetworks)"
- }
- neLog(.info, title: "Protocol", message: details)
- }
- }
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
let providerConfiguration = protocolConfiguration.providerConfiguration
@@ -340,8 +323,6 @@ extension WireGuardLogLevel {
final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
private let flow: NEPacketTunnelFlow
- private var readLogCounter = 0
- private var writeLogCounter = 0
init(flow: NEPacketTunnelFlow) {
self.flow = flow
@@ -350,98 +331,15 @@ final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
@objc(readPacketsWithCompletionHandler:)
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
- flow.readPackets { packets, protocols in
-#if os(macOS)
- if self.readLogCounter < 20, let firstPacket = packets.first, let firstProtocol = protocols.first {
- let prefix = firstPacket.prefix(12).map { String(format: "%02x", $0) }.joined()
- let header = Self.describePacketHeader(firstPacket)
- ovpnLog(.info, title: "FlowRead", message: "count=\(packets.count) proto0=\(firstProtocol) len0=\(firstPacket.count) prefix=\(prefix) \(header)")
- self.readLogCounter += 1
- }
-#endif
- completionHandler(packets, protocols)
- }
+ flow.readPackets(completionHandler: completionHandler)
}
@objc(writePackets:withProtocols:)
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
-#if os(macOS)
- if writeLogCounter < 20, let firstPacket = packets.first, let firstProtocol = protocols.first {
- let prefix = firstPacket.prefix(12).map { String(format: "%02x", $0) }.joined()
- let header = Self.describePacketHeader(firstPacket)
- ovpnLog(.info, title: "FlowWrite", message: "count=\(packets.count) proto0=\(firstProtocol) len0=\(firstPacket.count) prefix=\(prefix) \(header)")
- writeLogCounter += 1
- }
-#endif
- return flow.writePackets(packets, withProtocols: protocols)
- }
-
- private static func describePacketHeader(_ packet: Data) -> String {
- guard let versionNibble = packet.first.map({ Int($0 >> 4) }) else {
- return "ip=unknown"
- }
-
- if versionNibble == 4, packet.count >= 20 {
- let ihl = Int(packet[0] & 0x0f) * 4
- guard ihl >= 20, packet.count >= ihl else {
- return "ip=ipv4 malformed"
- }
-
- let proto = packet[9]
- let src = "\(packet[12]).\(packet[13]).\(packet[14]).\(packet[15])"
- let dst = "\(packet[16]).\(packet[17]).\(packet[18]).\(packet[19])"
- let l4Offset = ihl
- let ports: String
- if (proto == 6 || proto == 17) && packet.count >= l4Offset + 4 {
- let srcPort = (UInt16(packet[l4Offset]) << 8) | UInt16(packet[l4Offset + 1])
- let dstPort = (UInt16(packet[l4Offset + 2]) << 8) | UInt16(packet[l4Offset + 3])
- ports = "sport=\(srcPort) dport=\(dstPort)"
- } else {
- ports = "sport=- dport=-"
- }
- let protoName: String
- switch proto {
- case 1: protoName = "ICMP"
- case 6: protoName = "TCP"
- case 17: protoName = "UDP"
- default: protoName = "P\(proto)"
- }
- return "ip=ipv4 src=\(src) dst=\(dst) proto=\(protoName) \(ports)"
- }
-
- if versionNibble == 6, packet.count >= 40 {
- let proto = packet[6]
- func hex16(_ start: Int) -> String {
- let value = (UInt16(packet[start]) << 8) | UInt16(packet[start + 1])
- return String(format: "%x", value)
- }
- let src = stride(from: 8, to: 24, by: 2).map(hex16).joined(separator: ":")
- let dst = stride(from: 24, to: 40, by: 2).map(hex16).joined(separator: ":")
- let l4Offset = 40
- let ports: String
- if (proto == 6 || proto == 17) && packet.count >= l4Offset + 4 {
- let srcPort = (UInt16(packet[l4Offset]) << 8) | UInt16(packet[l4Offset + 1])
- let dstPort = (UInt16(packet[l4Offset + 2]) << 8) | UInt16(packet[l4Offset + 3])
- ports = "sport=\(srcPort) dport=\(dstPort)"
- } else {
- ports = "sport=- dport=-"
- }
- let protoName: String
- switch proto {
- case 58: protoName = "ICMPv6"
- case 6: protoName = "TCP"
- case 17: protoName = "UDP"
- default: protoName = "P\(proto)"
- }
- return "ip=ipv6 src=\(src) dst=\(dst) proto=\(protoName) \(ports)"
- }
-
- return "ip=v\(versionNibble) len=\(packet.count)"
+ flow.writePackets(packets, withProtocols: protocols)
}
}
-extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
-
extension NEProviderStopReason {
var amneziaDescription: String {
switch self {
diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm
index 3a04e238f..9302680bd 100644
--- a/client/platforms/ios/ios_controller.mm
+++ b/client/platforms/ios/ios_controller.mm
@@ -785,38 +785,8 @@ bool IosController::startOpenVPN(const QString &config)
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
- QByteArray configUtf8 = config.toUtf8();
- NSData *ovpnConfigData = [NSData dataWithBytes:configUtf8.constData() length:configUtf8.size()];
- tunnelProtocol.providerConfiguration = @{@"ovpn": ovpnConfigData};
+ tunnelProtocol.providerConfiguration = @{@"ovpn": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
- if (@available(iOS 14.0, macOS 11.0, *)) {
- int splitTunnelType = 0;
- QJsonParseError parseError;
- QJsonDocument doc = QJsonDocument::fromJson(config.toUtf8(), &parseError);
- if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
- QJsonObject obj = doc.object();
- splitTunnelType = obj.value(config_key::splitTunnelType).toInt(0);
- }
-#if defined(MACOS_NE)
- // On macOS NE use route-based full tunnel. includeAllNetworks enables
- // policy-based drop-all mode and causes enforceRoutes to be ignored.
- tunnelProtocol.includeAllNetworks = NO;
- if (splitTunnelType == 0) {
- tunnelProtocol.enforceRoutes = YES;
- if (@available(iOS 14.2, macOS 11.0, *)) {
- tunnelProtocol.excludeLocalNetworks = YES;
- }
- }
-#else
- tunnelProtocol.includeAllNetworks = (splitTunnelType == 0);
- if (@available(iOS 14.2, macOS 11.0, *)) {
- // Keep existing iOS behavior.
- if (splitTunnelType == 0) {
- tunnelProtocol.excludeLocalNetworks = NO;
- }
- }
-#endif
- }
m_currentTunnel.protocolConfiguration = tunnelProtocol;
@@ -829,9 +799,7 @@ bool IosController::startWireGuard(const QString &config)
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
- QByteArray configUtf8 = config.toUtf8();
- NSData *wgConfigData = [NSData dataWithBytes:configUtf8.constData() length:configUtf8.size()];
- tunnelProtocol.providerConfiguration = @{@"wireguard": wgConfigData};
+ tunnelProtocol.providerConfiguration = @{@"wireguard": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
m_currentTunnel.protocolConfiguration = tunnelProtocol;
@@ -845,9 +813,7 @@ bool IosController::startXray(const QString &config)
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
- QByteArray configUtf8 = config.toUtf8();
- NSData *xrayConfigData = [NSData dataWithBytes:configUtf8.constData() length:configUtf8.size()];
- tunnelProtocol.providerConfiguration = @{@"xray": xrayConfigData};
+ tunnelProtocol.providerConfiguration = @{@"xray": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
m_currentTunnel.protocolConfiguration = tunnelProtocol;