NEFilterDataProvider debugging issue

I want to filter users browsing activity based on response content.

I started by implementing a simple NEFIlterDataProvider extension based on docs and official iOS-samples

After that, I tried everything I could find here and in SO threads, but I can't make breakpoints or output work.

https://forums.developer.apple.com/thread/11241

https://forums.developer.apple.com/thread/9775

I have tried all the debugging methods mentioned in the topics above, but I don't see any triggered breakpoints or any output in the device console


Here is the simplest extension code, but I can’t debug it anyway


import NetworkExtension
import os.log

class FilterDataProvider: NEFilterDataProvider {

    override func startFilter(completionHandler: @escaping (Error?) -> Void) {
        // Add code to initialize the filter.
        os_log("---------------------FOR LOGGING----------------------")
        completionHandler(nil)
    }
   
    override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        // Add code to clean up filter resources.
        os_log("---------------------FOR LOGGING----------------------")
        completionHandler()
    }
   
    override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
        print("new flow: \(flow)")
        os_log("---------------------FOR LOGGING----------------------")
        // Add code to determine if the flow should be dropped or not, downloading new rules if required.
        return .drop()
    }
}


Even with .drop() response handling I still can browse freely so looks like it doesn’t work for me.

Here are my entitlements and a part of info.plist.


<dict>

<key>com.apple.developer.networking.networkextension</key>

<array>

<string>app-proxy-provider</string>

<string>content-filter-provider</string>

<string>packet-tunnel-provider</string>

</array>

<key>com.apple.security.application-groups</key>

<array>

<string>group.ArunBuduri.urlFetcher</string>

</array>

</dict>

</plist>


Info.plist

<key>NSExtension</key>

<dict>

<key>NSExtensionPointIdentifier</key>

<string>com.apple.networkextension.filter-data</string>

<key>NSExtensionPrincipalClass</key>

<string>FilterDataProvider</string>

</dict>



When I try to connect XCode to a running extension, I always have "waiting to attach to "extension name' on device" message.

I have a supervised device but I didn't do any additional profile customization or something like that.

I'm working with 10.2.1 XCode, but I tried to debug it on different versions including 8x.
My host app is just a VC with a link to a site to test the extension. I tried to connect extension to my host application and browsers, filter extension is added to the Embed Binaries, and I have no idea what I'm doing wrong. Maybe I need to extend my host application functionality or do some work with my device's profile to enable content filter?
I will be glad to any comment or advice, thanks.

Replies

I have two suggestions on this front. First, add a log statement to your provider’s

-init
method:
override init() {
    NSLog("***")
    super.init()
}

where

***
is something unique. You can then use Console to confirm that your code started running. A lot of provider problems are caused by setup bugs that prevent the provider from loading, and there’s no point trying to debug your code until you’re sure it’s actually running.

Secondly, you wrote:

I always have "waiting to attach to "extension name' on device" message

I don’t think waiting to attached works for NE providers. I always attach explicitly using Debug > Attach to Process. The provider process will only show up in this list if the provider is running, which is why my first check is so important.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your answer!



"First, add a log statement to your provider’s

-init
method"


I overrode extension class with NSLog and tried to catch it with every possible scheme order combination on both physical device and simulator, but found nothing in the console or system log. It also does not appear in the "Attach to process" list.

I've raised a new project from scratch with an empty host application and NEFilterDataProvider extension, following the instructions in the documentation carefully, but to no avail.
So I'm probably missing some very important point, but it's obvious to everyone but me, such as
Do I need some functionality in the host app, or can an extension work on any of them?

First up, don’t try to run this on the simulator. NetworkExtension providers are not supported there.

Next, how are you configuring your provider? For testing, you generally configure a filter from your host app using the

NEFilterManager
class.

IMPORTANT This only works for testing. In production you must configure the provider via a configuration profile, something that makes sense when you consider that content filters are only supported on supervised devices.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I've tried both ways to configure my provider (via NEFilterManager or setting up a configuration profile on a supervised device), but it was not successful. I've spent much time creating profiles (related to Reference), and that's works well with BuiltIn profiles but not with the Plugin ones.

When I set up development settings, I tried to do that like so:

let config = NEFilterProviderConfiguration()
        config.username = "User"
        config.organization = "Atomic"
        config.filterBrowsers = true
        config.filterSockets = false
        config.serverAddress = "testing.t-atomic.com"
        NEFilterManager.shared().providerConfiguration = config
        NEFilterManager.shared().isEnabled = true
        NEFilterManager.shared().saveToPreferences { error in
            if let err = error {
                print("Failed to save the filter configuration: \(err)")
                return
            }
        }


Now I have a goal just to make sure that extensions are started and I can capture all browser traffic.

Now I'm getting

Error Domain=NEFilterErrorDomain Code=3 "(null)"

when trying to save manager instance.


I successfully ran it once (I was asked to allow the use of the filter) before the error occurred, but my extensions did not start then.

It sounds like you have a packaging problem in your extension. To debug this, you’ll need to look at your built binary, not at your source code. Pasted in below are some commands that I typically use for this. You want to check for consistency across the app and the app extensions, including:

  • The Team ID

  • The App ID prefix — For new projects this should match the Team ID.

  • The bundle IDs — The exact identifiers don’t matter, but it’s important that the identifiers for the app extensions be ‘children’ of the app’s bundle ID.

  • The deployment target

  • The entitlements — All three products need the

    content-filter-provider
    entitlement. They should also have the
    get-task-allow
    entitlement, because that’s what allows you to test your code on a non-supervised device.
  • The Mach-O architectures — In my example, I’m targeting iOS 9, so I need both 64- and 32-bit Arm code.

Also, you set the

NSExtensionPrincipalClass
property depending on your build language:
  • For Objective-C, this is just the class name.

  • For Swift, this is the module name, followed by a dot, followed by the class name.

If you can’t get your extension at least loading based on the above, my recommendation is that you open a DTS tech support incident so that I can look at your project in more detail.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
$ alias plcat='plutil -convert xml1 -o /dev/stdout'
$ plcat QNEFilter.app/Info.plist 
…
<dict>
    …
    <key>CFBundleIdentifier</key>
    <string>com.example.apple-samplecode.QNEFilter-iOS</string>
    …
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>MinimumOSVersion</key>
    <string>9.0</string>
    …
</dict>
</plist>
$ plcat QNEFilter.app/PlugIns/QNEFilterControlProvider.appex/Info.plist 
…
<dict>
    …
    <key>CFBundleIdentifier</key>
    <string>com.example.apple-samplecode.QNEFilter-iOS.ControlProvider</string>
    …
    <key>MinimumOSVersion</key>
    <string>9.0</string>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.networkextension.filter-control</string>
        <key>NSExtensionPrincipalClass</key>
        <string>QNEFilterControlProvider.ControlProvider</string>
    </dict>
    …
</dict>
</plist>
$ plcat QNEFilter.app/PlugIns/QNEFilterDataProvider.appex/Info.plist 
…
<dict>
    …
    <key>CFBundleIdentifier</key>
    <string>com.example.apple-samplecode.QNEFilter-iOS.DataProvider</string>
    …
    <key>MinimumOSVersion</key>
    <string>9.0</string>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.networkextension.filter-data</string>
        <key>NSExtensionPrincipalClass</key>
        <string>QNEFilterDataProvider.DataProvider</string>
    </dict>
    …
</dict>
</plist>
$ codesign -d -v --entitlements :- QNEFilter.app
Executable=…/QNEFilter.app/QNEFilter
Identifier=com.example.apple-samplecode.QNEFilter-iOS
Format=app bundle with Mach-O universal (armv7 arm64)
CodeDirectory v=20400 size=2382 flags=0x0(none) hashes=65+5 location=embedded
Signature size=4793
Signed Time=14 May 2019 at 10:00:24
Info.plist entries=26
TeamIdentifier=SKMME9E2Y8
Sealed Resources version=2 rules=10 files=31
Internal requirements count=1 size=204
<?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>SKMME9E2Y8.com.example.apple-samplecode.QNEFilter-iOS</string>
    <key>com.apple.developer.networking.networkextension</key>
    <array>
        <string>content-filter-provider</string>
    </array>
    <key>com.apple.developer.team-identifier</key>
    <string>SKMME9E2Y8</string>
    <key>get-task-allow</key>
    <true/>
</dict>
</plist>
$ codesign -d -v --entitlements :- QNEFilter.app/PlugIns/QNEFilterControlProvider.appex
…
Identifier=com.example.apple-samplecode.QNEFilter-iOS.ControlProvider
Format=bundle with Mach-O universal (armv7 arm64)
…
TeamIdentifier=SKMME9E2Y8
…
<dict>
    <key>application-identifier</key>
    <string>SKMME9E2Y8.com.example.apple-samplecode.QNEFilter-iOS.ControlProvider</string>
    <key>com.apple.developer.networking.networkextension</key>
    <array>
        <string>content-filter-provider</string>
    </array>
    <key>com.apple.developer.team-identifier</key>
    <string>SKMME9E2Y8</string>
    <key>get-task-allow</key>
    <true/>
</dict>
</plist>
$ codesign -d -v --entitlements :- QNEFilter.app/PlugIns/QNEFilterDataProvider.appex
…
Identifier=com.example.apple-samplecode.QNEFilter-iOS.DataProvider
Format=bundle with Mach-O universal (armv7 arm64)
…
TeamIdentifier=SKMME9E2Y8
…
<dict>
    <key>application-identifier</key>
    <string>SKMME9E2Y8.com.example.apple-samplecode.QNEFilter-iOS.DataProvider</string>
    <key>com.apple.developer.networking.networkextension</key>
    <array>
        <string>content-filter-provider</string>
    </array>
    <key>com.apple.developer.team-identifier</key>
    <string>SKMME9E2Y8</string>
    <key>get-task-allow</key>
    <true/>
</dict>
</plist>