diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift index afd34cb..20aceb8 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift @@ -183,6 +183,8 @@ extension OpenVPNTunnelProvider { static let httpsProxy = "HTTPSProxy" + static let proxyAutoConfigurationURL = "ProxyAutoConfigurationURL" + static let proxyBypassDomains = "ProxyBypassDomains" static let routingPolicies = "RoutingPolicies" @@ -603,6 +605,9 @@ private extension OpenVPN.Configuration { if let httpsProxy = httpsProxy { dict[S.httpsProxy] = httpsProxy.rawValue } + if let proxyAutoConfigurationURL = proxyAutoConfigurationURL { + dict[S.proxyAutoConfigurationURL] = proxyAutoConfigurationURL.absoluteString + } if let proxyBypassDomains = proxyBypassDomains { dict[S.proxyBypassDomains] = proxyBypassDomains } @@ -681,6 +686,9 @@ private extension OpenVPN.Configuration { if let httpsProxy = httpsProxy { log.info("\tHTTPS proxy: \(httpsProxy.maskedDescription)") } + if let proxyAutoConfigurationURL = proxyAutoConfigurationURL { + log.info("\tPAC: \(proxyAutoConfigurationURL)") + } if let proxyBypassDomains = proxyBypassDomains { log.info("\tProxy bypass domains: \(proxyBypassDomains.maskedDescription)") } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift index 958632c..a9a7829 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift @@ -514,6 +514,9 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { if let proxy = options.httpsProxy { log.info("\t\tHTTPS: \(proxy.maskedDescription)") } + if let pacURL = options.proxyAutoConfigurationURL { + log.info("\t\tPAC: \(pacURL)") + } if let bypass = options.proxyBypassDomains { log.info("\t\tBypass domains: \(bypass.maskedDescription)") } @@ -677,6 +680,14 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { proxySettings?.httpEnabled = true log.info("Routing: Setting HTTP proxy \(httpProxy.address.maskedDescription):\(httpProxy.port)") } + if let pacURL = cfg.sessionConfiguration.proxyAutoConfigurationURL ?? options.proxyAutoConfigurationURL { + if proxySettings == nil { + proxySettings = NEProxySettings() + } + proxySettings?.proxyAutoConfigurationURL = pacURL + proxySettings?.autoProxyConfigurationEnabled = true + log.info("Routing: Setting PAC \(pacURL)") + } // only set if there is a proxy (proxySettings set to non-nil above) if let bypass = cfg.sessionConfiguration.proxyBypassDomains ?? options.proxyBypassDomains { diff --git a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift index 145ff0a..72923d4 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift @@ -247,9 +247,12 @@ extension OpenVPN { /// The search domain. public var searchDomain: String? + /// The Proxy Auto-Configuration (PAC) url. + public var proxyAutoConfigurationURL: URL? + /// The HTTP proxy. public var httpProxy: Proxy? - + /// The HTTPS proxy. public var httpsProxy: Proxy? @@ -295,6 +298,7 @@ extension OpenVPN { searchDomain: searchDomain, httpProxy: httpProxy, httpsProxy: httpsProxy, + proxyAutoConfigurationURL: proxyAutoConfigurationURL, proxyBypassDomains: proxyBypassDomains, routingPolicies: routingPolicies ) @@ -392,10 +396,13 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.httpProxy` public let httpProxy: Proxy? - + /// - Seealso: `ConfigurationBuilder.httpsProxy` public let httpsProxy: Proxy? + /// - Seealso: `ConfigurationBuilder.proxyAutoConfigurationURL` + public let proxyAutoConfigurationURL: URL? + /// - Seealso: `ConfigurationBuilder.proxyBypassDomains` public let proxyBypassDomains: [String]? @@ -457,6 +464,7 @@ extension OpenVPN.Configuration { builder.searchDomain = searchDomain builder.httpProxy = httpProxy builder.httpsProxy = httpsProxy + builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL builder.proxyBypassDomains = proxyBypassDomains builder.routingPolicies = routingPolicies return builder diff --git a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift index bf44310..184f752 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift @@ -96,7 +96,7 @@ extension OpenVPN { static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") - static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS?) +[^ ]+ +\\d+") + static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS? +[^ ]+ +\\d+|AUTO_CONFIG_URL +[^ ]+)") static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") @@ -228,6 +228,7 @@ extension OpenVPN { var optSearchDomain: String? var optHTTPProxy: Proxy? var optHTTPSProxy: Proxy? + var optProxyAutoConfigurationURL: URL? var optProxyBypass: [String]? var optRedirectGateway: Set? @@ -533,6 +534,15 @@ extension OpenVPN { optSearchDomain = $0[1] } Regex.proxy.enumerateArguments(in: line) { + if $0.count == 2 { + guard let url = URL(string: $0[1]) else { + unsupportedError = ConfigurationError.malformed(option: "dhcp-option PROXY_AUTO_CONFIG_URL has malformed URL") + return + } + optProxyAutoConfigurationURL = url + return + } + guard $0.count == 3, let port = UInt16($0[2]) else { return } @@ -542,7 +552,7 @@ extension OpenVPN { case "PROXY_HTTP": optHTTPProxy = Proxy($0[1], port) - + default: break } @@ -731,6 +741,7 @@ extension OpenVPN { sessionBuilder.searchDomain = optSearchDomain sessionBuilder.httpProxy = optHTTPProxy sessionBuilder.httpsProxy = optHTTPSProxy + sessionBuilder.proxyAutoConfigurationURL = optProxyAutoConfigurationURL sessionBuilder.proxyBypassDomains = optProxyBypass if let flags = optRedirectGateway { diff --git a/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift b/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift index 34fd0d0..0ae7845 100644 --- a/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift +++ b/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift @@ -56,6 +56,7 @@ class ConfigurationParserTests: XCTestCase { "dhcp-option DOMAIN example.com", "dhcp-option PROXY_HTTP 1.2.3.4 8081", "dhcp-option PROXY_HTTPS 7.8.9.10 8082", + "dhcp-option PROXY_AUTO_CONFIG_URL https://pac/", "dhcp-option PROXY_BYPASS foo.com bar.org net.chat" ] XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: lines)) @@ -67,6 +68,7 @@ class ConfigurationParserTests: XCTestCase { XCTAssertEqual(parsed.httpProxy?.port, 8081) XCTAssertEqual(parsed.httpsProxy?.address, "7.8.9.10") XCTAssertEqual(parsed.httpsProxy?.port, 8082) + XCTAssertEqual(parsed.proxyAutoConfigurationURL?.absoluteString, "https://pac/") XCTAssertEqual(parsed.proxyBypassDomains, ["foo.com", "bar.org", "net.chat"]) }