I'm trying to get a DNS Proxy working in iOS 11.0 beta 2. I have the entitlements, and I have enabled the DNS proxy through the manager but it isn't calling startProxy in my Proxy Provider. I'm able to loadFromPreferences() so I think the entitlements are correct and isEnabled shows true when I move the app to the background and back to the foreground. Any help would be great.
In my main app:
func applicationDidBecomeActive(_ application: UIApplication) {
let manager = NEDNSProxyManager.shared()
if manager.isEnabled == true {
NSLog("enabled")
} else {
manager.localizedDescription = "DNS Proxy"
manager.loadFromPreferences { error in
if (error != nil) {
NSLog("Load error: \(String(describing: error?.localizedDescription))");
} else {
NSLog("loaded preferences");
let dict = ["foo": "bar"]
let proto = NEDNSProxyProviderProtocol()
proto.providerConfiguration = dict
proto.providerBundleIdentifier = "com.bangj.DNS.DNS-Proxy"
manager.providerProtocol = proto
manager.isEnabled = true
}
}
}
}
My Proxy Provider:
class DNSProxyProvider: NEDNSProxyProvider {
override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
NSLog("startproxy")
/
completionHandler(nil)
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
NSLog("stopproxy")
/
completionHandler()
}
override func sleep(completionHandler: @escaping () -> Void) {
NSLog("sleep")
/
completionHandler()
}
override func wake() {
NSLog("wake")
/
}
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("handleNewFlow")
/
return false
}
}
I think my entitlements look good:
butte% codesign -d --entitlements :- DNS-Proxy.appex
Executable=/Users/pusateri/Library/Developer/Xcode/DerivedData/DNS-grgoqerfsmhphqfyrpxzgzxiqdkd/Build/Products/Debug-iphoneos/DNS-Proxy.appex/DNS-Proxy
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-/
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>XXXXXXXXXX.com.bangj.DNS.DNS-Proxy</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>XXXXXXXXXX</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.bangj.DNS</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
% codesign -d --entitlements :- DNS.app
Executable=/Users/pusateri/Library/Developer/Xcode/DerivedData/DNS-grgoqerfsmhphqfyrpxzgzxiqdkd/Build/Products/Debug-iphoneos/DNS.app/DNS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-/
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>XXXXXXXXXX.com.bangj.DNS</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>XXXXXXXXXX</string>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
One thing that is confusing is that in NEDNSProxyManager.h, enabled is the property but I can't set enabled in Xcode. I have to use isEnabled even though it's just the getter (and not the setter). I'm new to Swift so maybe I don't understand how setters work in Swift. setEnabled doesn't work either.
/!
* @property enabled
* @discussion Toggles the enabled status of the DNS proxy. Setting this property will disable DNS proxy configurations of other apps. This property will be set to NO when other DNS proxy configurations are enabled.
*/
@property (getter=isEnabled) BOOL enabled NS_AVAILABLE(NA, 11_0);
One more thing, this is probably a beta thing but NEDNS Proxy doesn't seem to be available for macos, just iOS.
I'm running this on a real phone. No configuration profile. And, yes, wifi debugging is AWESOME!
I only had a short amount of time to play with this today but I did manage to get the provider loading. I’m going to address some specific points in your original post and then describe what I did.
You wrote:
I'm able to loadFromPreferences() …
Did you actually call
saveToPreferences(completionHandler:)
? I don’t see that anywhere in the snippets you posted.
I think my entitlements look good:
You should dump the entitlements of the
.appex
inside your
.app
, rather than the one at the top level of your build results folder. That ensures that you’re dumping the entitlements that are actually being looked at by iOS.
You should also dump your provisioning profiles to ensure that they whitelist the entitlements you use (specifically
com.apple.developer.networking.networkextension
).
One thing that is confusing is that in NEDNSProxyManager.h,
is the property but I can't setenabled
in Xcode.enabled
Right. That’s a Swift thing. In Objective-C you have the
isEnabled
getter paired to the
enabled
setter, but Swift exposes both as
isEnabled
so as to conform to standard Swift style.
NEDNS Proxy doesn't seem to be available for macos, just iOS.
Correct.
So, I sat down to play with this API today and managed to get it to the point where the provider actually loads and
startProxy(options:completionHandler:)
is called. My provider looks very much like yours. The only change I made was to delete extraneous stuff.
import NetworkExtension
class DNSProxyProvider: NEDNSProxyProvider {
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")
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)")
return false
}
}
Note that all my log messages include QNEDNSProxy, which helps when it comes to debugging (more on this below).
My loading code is also very similar to yours:
private func enable() {
self.update {
self.manager.localizedDescription = "QNEDNSProxy Test"
let proto = NEDNSProxyProviderProtocol()
// proto.providerConfiguration = +++
proto.providerBundleIdentifier = "com.example.apple-samplecode.QNE-iOS.DNSProxy.Provider"
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("QNEDNSProxy.App: load error")
return
}
body()
self.manager.saveToPreferences { (error) in
guard error == nil else {
NSLog("QNEDNSProxy.App: save error")
return
}
NSLog("QNEDNSProxy.App: saved")
}
}
}
Note that I’m calling
saveToPreferences(completionHandler:)
, which is required per my comments above.
Beyond that it was just a question getting the provisioning and entitlements correct. I’ve pasted in dumps of those at the end of this email.
IMPORTANT The app group is not needed for this minimal example. It was inherited from the Network Extension template, which includes it because it’s probably going to be useful in the long term.
Finally, some debugging hints. I wired up the
enable()
and
disable()
methods to buttons in my test app. I then ran the Console utility on my Mac and selected my iPhone on the left. I then entered
QNEDNSProxy into the search field so that I see all messages related to be app and its provider. At that point I could see the system make progress as it tried to load my provider. When things started to go wrong — for example, when I saw messages about the state changing from ‘starting’ to ‘stopping’ — I removed the
QNEDNSProxy search term and started looking at all the messages that were likely to be related to this task. I eventually found a log message from
pkd
(the daemon responsible for PlugInKit, the infrastructure used by app extensions) which indicated that I had set
providerBundleIdentifier
incorrectly.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
$ codesign -d --entitlements :- QNEDNSProxy.app/PlugIns/Provider.appex
Executable=/Users/quinn/DTS Work iOS/Samples and Tests/QNE/QNEDNSProxy-GIT/build/Debug-iphoneos/QNEDNSProxy.app/PlugIns/Provider.appex/Provider
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>VR9NTVC6BB.com.example.apple-samplecode.QNE-iOS.DNSProxy.Provider</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>VR9NTVC6BB</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.example.apple-samplecode.QNE-iOS</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
$
$ codesign -d --entitlements :- QNEDNSProxy.app/PlugIns/Provider.appex
Executable=/Users/quinn/DTS Work iOS/Samples and Tests/QNE/QNEDNSProxy-GIT/build/Debug-iphoneos/QNEDNSProxy.app/PlugIns/Provider.appex/Provider
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>VR9NTVC6BB.com.example.apple-samplecode.QNE-iOS.DNSProxy.Provider</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>VR9NTVC6BB</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.example.apple-samplecode.QNE-iOS</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
$
$ security cms -D -i QNEDNSProxy.app/embedded.mobileprovision
…
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppIDName</key>
<string>QNE iOS DNSProxy</string>
<key>ApplicationIdentifierPrefix</key>
<array>
<string>VR9NTVC6BB</string>
</array>
<key>CreationDate</key>
<date>2017-07-19T11:01:01Z</date>
<key>Platform</key>
<array>
<string>iOS</string>
</array>
<key>DeveloperCertificates</key>
<array>
…
</array>
<key>Entitlements</key>
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>content-filter-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>VR9NTVC6BB.*</string>
</array>
<key>get-task-allow</key>
<true/>
<key>application-identifier</key>
<string>VR9NTVC6BB.com.example.apple-samplecode.QNE-iOS.DNSProxy</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.example.apple-samplecode.QNE-iOS</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>VR9NTVC6BB</string>
</dict>
<key>ExpirationDate</key>
<date>2018-07-19T11:01:01Z</date>
<key>Name</key>
<string>QNE iOS DNSProxy</string>
<key>ProvisionedDevices</key>
<array>
…
</array>
<key>TeamIdentifier</key>
<array>
<string>VR9NTVC6BB</string>
</array>
<key>TeamName</key>
<string>Apple Inc. - DTS VPN</string>
<key>TimeToLive</key>
<integer>365</integer>
<key>UUID</key>
<string>9237134a-dfad-4e70-b417-17a64ee86378</string>
<key>Version</key>
<integer>1</integer>
</dict>
</plist>
$
$ security cms -D -i QNEDNSProxy.app/PlugIns/Provider.appex/embedded.mobileprovision
…
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppIDName</key>
<string>QNE iOS DNSProxy Provider</string>
<key>ApplicationIdentifierPrefix</key>
<array>
<string>VR9NTVC6BB</string>
</array>
<key>CreationDate</key>
<date>2017-07-19T11:01:24Z</date>
<key>Platform</key>
<array>
<string>iOS</string>
</array>
<key>DeveloperCertificates</key>
<array>
…
</array>
<key>Entitlements</key>
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>content-filter-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>VR9NTVC6BB.*</string>
</array>
<key>get-task-allow</key>
<true/>
<key>application-identifier</key>
<string>VR9NTVC6BB.com.example.apple-samplecode.QNE-iOS.DNSProxy.Provider</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.example.apple-samplecode.QNE-iOS</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>VR9NTVC6BB</string>
</dict>
<key>ExpirationDate</key>
<date>2018-07-19T11:01:24Z</date>
<key>Name</key>
<string>QNE iOS DNSProxy Provider</string>
<key>ProvisionedDevices</key>
<array>
…
</array>
<key>TeamIdentifier</key>
<array>
<string>VR9NTVC6BB</string>
</array>
<key>TeamName</key>
<string>Apple Inc. - DTS VPN</string>
<key>TimeToLive</key>
<integer>365</integer>
<key>UUID</key>
<string>c3db3b50-8db4-4cc6-94b4-84f53da9fea4</string>
<key>Version</key>
<integer>1</integer>
</dict>
</plist>