iOS Network Extension无远端VPN服务器实现全局自定义DNS问题
问题根源
你当前使用的NEVPNProtocolIPSec是标准IPSec VPN协议,必须与真实的IPSec服务端完成IKE握手、密钥协商等一系列交互才能建立连接,你填写本地地址时没有对应服务响应请求,必然会报「VPN服务器未响应」的错误,该方案完全不适合你的需求。
正确实现方案(兼容iOS12)
你要实现无需真实服务器的本地VPN覆写全局DNS,只能通过NEPacketTunnelProvider Network Extension实现,本质是创建一个本地虚拟VPN隧道,不需要与外部服务建立连接,仅通过隧道配置覆写系统DNS规则,流量无需经过隧道处理。
实现步骤
- 新增Network Extension Target
- 在主项目中新建Target,选择「Network Extension」,类型选择「Packet Tunnel Provider」,将该Extension的最低部署版本设置为与主项目一致的iOS12
- 为主项目和Extension Target同时开启「Personal VPN」、「Network Extensions」能力
- 主项目中配置VPN连接
替换你当前的IPSec协议代码,使用NETunnelProviderManager创建VPN配置:
import NetworkExtension let manager = NETunnelProviderManager() manager.localizedDescription = "自定义DNS服务" let proto = NETunnelProviderProtocol() // 这里填你新建的Packet Tunnel Provider Extension的Bundle ID proto.providerBundleIdentifier = "com.yourapp.bundle.tunnel" // serverAddress随便填写即可,不需要真实可用 proto.serverAddress = "LocalDNSProxy" manager.protocolConfiguration = proto manager.isEnabled = true // 保存配置到系统 manager.saveToPreferences { error in guard error == nil else { print("配置保存失败:\(error!)") return } // 保存成功后可以调用连接 do { try manager.connection.startVPNTunnel() } catch { print("VPN启动失败:\(error)") } }
- 实现Packet Tunnel Provider逻辑
在你新建的Extension Target的PacketTunnelProvider类中,重写startTunnel方法,配置DNS规则:
import NetworkExtension class PacketTunnelProvider: NEPacketTunnelProvider { override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { // 配置隧道网络参数 let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") // 配置自定义DNS服务器,替换为你需要的地址 let dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "1.1.1.1"]) // 匹配所有域名,保证全局DNS生效 dnsSettings.matchDomains = [""] settings.dnsSettings = dnsSettings // 配置虚拟网卡IP,随便填写私有段地址即可 let ipv4Settings = NEIPv4Settings(addresses: ["10.0.0.2"], subnetMasks: ["255.255.255.0"]) ipv4Settings.includedRoutes = [NEIPv4Route.default()] settings.ipv4Settings = ipv4Settings settings.mtu = 1500 // 应用隧道配置 setTunnelNetworkSettings(settings) { [weak self] error in guard error == nil else { completionHandler(error) return } // 不需要处理数据包,启动隧道后保持存活即可 completionHandler(nil) // 可选:监听数据包直接回传,避免系统判定隧道无响应 self?.handlePackets() } } private func handlePackets() { packetFlow.readPackets { [weak self] packets, protocols in self?.packetFlow.writePackets(packets, withProtocols: protocols) self?.handlePackets() } } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { completionHandler() } }
注意事项
- 首次保存VPN配置时会弹出用户授权弹窗,用户同意后才能正常使用
- 该方案所有逻辑都在本地运行,不需要任何外部服务器支持
- 应用退后台后VPN会保持正常运行,符合全局DNS的需求
内容的提问来源于stack exchange,提问作者superGokuN
相关产品推荐
相关产品推荐

