16

On the networking side, DNS Proxy is one of the biggest features of iOS 11. But they haven't provided much documentation or samples regarding it. There's a talk on it as well where they have just given a description of what is possible with DNS Proxy.

I want to create a working sample of it but didn't get success till now. So I have created a Network Extension with DNS Proxy entitlements and added a DNS Proxy Provider. Here's the code:

class DNSProxyProvider: NEDNSProxyProvider {
    let defaults = UserDefaults(suiteName: "group.com.securly.dnsProxy")

    override init() {
        NSLog("QNEDNSProxy.Provider: init")
        super.init()
        // +++ might want to set up KVO on `systemDNSSettings`
    }

    override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
        NSLog("QNEDNSProxy.Provider: start")
        // self.defaults?.set("DidStart", forKey: "DidStart")
        completionHandler(nil)
    }

    override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        NSLog("QNEDNSProxy.Provider: stop")
        completionHandler()
    }

    override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
        NSLog("QNEDNSProxy.Provider: new flow (denied)")
        // self.defaults?.set("DidHandleNewFlow", forKey: "DidHandleNewFlow")
        return true
    }

}

Then in AppDelegate, I declare a NEDNSProxyManager and use it as:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let manager = NEDNSProxyManager.shared()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        self.enable()
        return true
    }

    private func enable() {
        self.update {
            self.manager.localizedDescription = "DNSProxySample"
            let proto = NEDNSProxyProviderProtocol()
            // proto.providerConfiguration = +++
            proto.providerBundleIdentifier = "com.securly.dnsProxy"
            self.manager.providerProtocol = proto
            self.manager.isEnabled = true
        }
    }

    private func disable() {
        self.update {
            self.manager.isEnabled = false
        }
    }

    private func update(_ body: @escaping () -> Void) {
        self.manager.loadFromPreferences { (error) in
            guard error == nil else {
                NSLog("DNSProxySample.App: load error")
                return
            }
            body()
            self.manager.saveToPreferences { (error) in
                guard error == nil else {
                    NSLog("DNSProxySample.App: save error")
                    return
                }
                NSLog("DNSProxySample.App: saved")
            }
        }
    }
}

Questions/Issues:

  1. Why isn't startProxy or handleNewFlow called? Is there anything wrong in the setup?
  2. How do I mention custom DNS address?
Pang
  • 9,564
  • 146
  • 81
  • 122
mayuur
  • 4,736
  • 4
  • 30
  • 65

1 Answers1

7

I managed to trigger startProxy and handleFlow on DNSProxyProvider by system. My configurations are like this:

  1. Entitlements on app target enter image description here
  2. Entitlements on DNSProxy Extension enter image description here Red line is something similar to: group.com.xzy.project_name

  3. Info.plist file on Extension enter image description here

  4. AppDelegate

    import UIKit
    import NetworkExtension
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
        let manager = NEDNSProxyManager.shared()
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.enable()
            return true
        }
    
        private func enable() {
            self.update {
                self.manager.localizedDescription = "DNS"
                let proto = NEDNSProxyProviderProtocol()
                proto.providerBundleIdentifier = "EXTENSION_BUNDLE_IDENTIFIER_WHICH_HAS_DNS_PROXY"
                self.manager.providerProtocol = proto
                self.manager.isEnabled = true
            }
        }
    
        private func disable() {
            self.update {
                self.manager.isEnabled = false
            }
        }
    
        private func update(_ body: @escaping () -> Void) {
            self.manager.loadFromPreferences { (error) in
                guard error == nil else {
                    NSLog("DNS Test App: load error")
                    return
                }
                body()
                self.manager.saveToPreferences { (error) in
                    guard error == nil else {
                        NSLog("DNS Test App: save error")
                        return
                    }
                    NSLog("DNS Test App: saved")
                }
            }
        }
    }
    

DO NOT FORGET TO CHANGE BUNDLE IDENTIFIER at here proto.providerBundleIdentifier = "EXTENSION_BUNDLE_IDENTIFIER_WHICH_HAS_DNS_PROXY"

  1. DNSProxyProvider

    import NetworkExtension
    
    class DNSProxyProvider: NEDNSProxyProvider {
    
        override init() {
            NSLog("DNSProxyProvider: init")
            super.init()
        }
    
        override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
            NSLog("DNSProxyProvider: startProxy")
            completionHandler(nil)
        }
    
        override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
            NSLog("DNSProxyProvider: stopProxy")
            completionHandler()
        }
    
        override func sleep(completionHandler: @escaping () -> Void) {
            NSLog("DNSProxyProvider: sleep")
            completionHandler()
        }
    
        override func wake() {
            NSLog("DNSProxyProvider: wake")
        }
    
        override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
            NSLog("DNSProxyProvider: handleFlow")
            if let tcpFlow = flow as? NEAppProxyTCPFlow {
                let remoteHost = (tcpFlow.remoteEndpoint as! NWHostEndpoint).hostname
                let remotePort = (tcpFlow.remoteEndpoint as! NWHostEndpoint).port
                NSLog("DNSProxyProvider TCP HOST : \(remoteHost)")
                NSLog("DNSProxyProvider TCP PORT : \(remotePort)")
            } else if let udpFlow = flow as? NEAppProxyUDPFlow {
                let localHost = (udpFlow.localEndpoint as! NWHostEndpoint).hostname
                let localPort = (udpFlow.localEndpoint as! NWHostEndpoint).port
                NSLog("DNSProxyProvider UDP HOST : \(localHost)")
                NSLog("DNSProxyProvider UDP PORT : \(localPort)")
            }
            return true
        }
    
    }
    
  2. As a last step run the app on a real iOS Device.

  3. If you want to display extension logs open Console.app from your Mac.

  4. To debug the extension: Your main app should be selected from run menu. Select Attach to Process by PID or Name... from Xcode's Debug menu and then type your extension's name, press Attach button. After you see the Waiting to attach to EXTENSION_NAME on XYZ's iPhone. Run your app target on a iOS device.

abdullahselek
  • 7,893
  • 3
  • 50
  • 40
  • Are you using an actual device with supervisor or managed mode? Your instructions don't work in my actual iPhone, I see in the console this "Warning: allowing creation/modification of a DNS proxy configuration on non-supervised device because the requesting app (XXXX) is a development version. This will not be allowed for the production version of XXXX". In any case, even with that, it should work in dev mode, but nope. Thanks for your help. – Ricardo May 21 '18 at 15:09
  • 1
    You can use these instructions with a `Debug` build on a real device without managing the device. If you want to use a `Managed` device you have to install a `.mobileconfig` file specific to your app. – abdullahselek May 21 '18 at 15:23
  • How should I configure the handleNewFlow function? I want to be able to see which url is handled. Is this possible in this function? – Robert Veringa Dec 04 '18 at 14:47
  • Hello @abdullahselek, thanks for your answer. I can see the logs come through in the Console app, but I only see the IP addresses. Is it also possible to see the website (like www.stackoverflow.com)? Also when I browse to a website it does not load if the extension is running. – Robert Veringa Dec 05 '18 at 09:45
  • For getting the url you need to create a UDP session and with in this session you can write to continue flow and read from flow for packet details. You can inspired from this [repository](https://github.com/zhuhaow/NEKit). – abdullahselek Dec 05 '18 at 11:00
  • Where do we add the custom DNSEndpoints? – Sidharth J Dev Apr 11 '22 at 11:35