I'm working on a Network Extension using NEDNSProxyProvider to inspect DNS traffic. However, I've run into a couple of issues:
DNS Proxy is not capturing traffic when a public DNS (like 8.8.8.8 or 1.1.1.1) is manually configured in the WiFi settings. It seems like the system bypasses the proxy in this case. Is this expected behavior? Is there a way to force DNS traffic through the proxy even if a public DNS is set?
Using DNS Proxy and DNS Settings simultaneously doesn't work. Is there a known limitation or a correct way to combine these?
How to set DNS or DNSSettings using DNSProxy?
import NetworkExtension import SystemExtensions import SwiftUI protocol DNSProxyManagerDelegate { func managerStateDidChange(_ manager: DNSProxyManager) } class DNSProxyManager: NSObject { private let manager = NEDNSProxyManager.shared() var delegate: DNSProxyManagerDelegate? private(set) var isEnabled: Bool = false { didSet { delegate?.managerStateDidChange(self) } } var completion: (() -> Void)? override init() { super.init() self.load() } func toggle() { isEnabled ? disable() : start() } private func start() { let request = OSSystemExtensionRequest .activationRequest(forExtensionWithIdentifier: Constants.extensionBundleID, queue: DispatchQueue.main) request.delegate = self OSSystemExtensionManager.shared.submitRequest(request) log.info("Submitted extension activation request") } private func enable() { update { self.manager.localizedDescription = "DNS Proxy" let proto = NEDNSProxyProviderProtocol() proto.providerBundleIdentifier = Constants.extensionBundleID self.manager.providerProtocol = proto self.manager.isEnabled = true } } private func disable() { update { self.manager.isEnabled = false } } private func remove() { update { self.manager.removeFromPreferences { _ in self.isEnabled = self.manager.isEnabled } } } private func update(_ body: @escaping () -> Void) { self.manager.loadFromPreferences { (error) in if let error = error { log.error("Failed to load DNS manager: \(error)") return } self.manager.saveToPreferences { (error) in if let error = error { return } log.info("Saved DNS manager") self.isEnabled = self.manager.isEnabled } } } private func load() { manager.loadFromPreferences { error in guard error == nil else { return } self.isEnabled = self.manager.isEnabled } } } extension DNSProxyManager: OSSystemExtensionRequestDelegate { func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { log.info("Extension activation request needs user approval") } func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { log.error("Extension activation request failed: \(error)") } func request(_ request: OSSystemExtensionRequest, foundProperties properties: [OSSystemExtensionProperties]) { log.info("Extension activation request found properties: \(properties)") } func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { guard result == .completed else { log.error("Unexpected result \(result.description) for system extension request") return } log.info("Extension activation request did finish with result: \(result.description)") enable() } func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { log.info("Existing extension willt be replaced: \(existing.bundleIdentifier) -> \(ext.bundleIdentifier)") return .replace } }
import NetworkExtension class DNSProxyProvider: NEDNSProxyProvider { var handlers: [String: FlowHandler] = [:] var isReady = false let queue = DispatchQueue(label: "DNSProxyProvider") override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) { completionHandler(nil) } override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { completionHandler() } override func handleNewUDPFlow(_ flow: NEAppProxyUDPFlow, initialRemoteEndpoint remoteEndpoint: NWEndpoint) -> Bool { let id = shortUUID() handlers[id] = FlowHandler(flow: flow, remoteEndpoint: remoteEndpoint, id: id, delegate: self) return true } override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { return false } }
class FlowHandler { let id: String let flow: NEAppProxyUDPFlow let remoteEndpoint: NWHostEndpoint let delegate: FlowHandlerDelegate private var connections: [String: RemoteConnection] = [:] private var pendingPacketsByDomain: [String: [(packet: Data, endpoint: NWEndpoint, uniqueID: String, timestamp: Date)]] = [:] private let packetQueue = DispatchQueue(label: "com.flowhandler.packetQueue") init(flow: NEAppProxyUDPFlow, remoteEndpoint: NWEndpoint, id: String, delegate: FlowHandlerDelegate) { log.info("Flow received for \(id) flow: \(String(describing: flow))") self.flow = flow self.remoteEndpoint = remoteEndpoint as! NWHostEndpoint self.id = id self.delegate = delegate defer { start() } } deinit { closeAll(nil) } func start() { flow.open(withLocalEndpoint: flow.localEndpoint as? NWHostEndpoint) { error in if let error = error { self.delegate.flowClosed(self) return } self.readFromFlow() } } func readFromFlow() { self.flow.readDatagrams { packets, endpoint, error in if let error = error { self.closeAll(error) return } guard let packets = packets, let endpoints = endpoint, !packets.isEmpty, !endpoints.isEmpty else { self.closeAll(nil) return } self.processFlowPackets(packets, endpoints) self.readFromFlow() } } }
Any insights or suggestions would be greatly appreciated. Thanks!
Yes. The system has a bias towards using secure DNS where possible, and those services provide secure DNS. I touch on this in this thread, with its reference to RFC 9462.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"