NEFilterDataProvider vs NEFilterPacketProvider

I am writing a firewall, and trying to choose between NEFilterDataProvider and NEFilterPacketProvider.


NEFilterDataProvider seems to contain a lot more information. For example, via NEFilterFlow's I can know very easily from which app the flow was originated.


However, I think NEFilterDataProvider only parses UDP and TCP connections ?


If that is correct, then it does not work for a firewall, because a malware could send data with any custom protocol.


Is there a good way to filter absolutely all network content, but still have access to NEFilterFlow's useful information in the subset of cases where it can be populated?


I am assuming that I should set NEFilterProviderConfiguration.filterPacket=true and NEFilterProviderConfiguration.filterSocket=true, but can I somehow use an NEFilterDataProvider that will look at strictly all the flow?

Accepted Reply

However, I think

NEFilterDataProvider
only parses UDP and TCP connections ?

Correct.

Is there a good way to filter absolutely all network content, but still have access to NEFilterFlow's useful information in the subset of cases where it can be populated?

You could implement both providers and have the packet filter ignore TCP and UDP traffic.

Share and Enjoy

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

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

Replies

However, I think

NEFilterDataProvider
only parses UDP and TCP connections ?

Correct.

Is there a good way to filter absolutely all network content, but still have access to NEFilterFlow's useful information in the subset of cases where it can be populated?

You could implement both providers and have the packet filter ignore TCP and UDP traffic.

Share and Enjoy

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

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

Thanks for that. That makes sense, I will do so.


Do you know if I am guaranteed that NEFilterPacketProvider will return absolutely all packet, for any protocol (for example ICMP, ARP, NAT, etc) ? In other words, am I guaranteed that any packet going through the network card will be realyed by NEFilterPacketProvider ?

And also another quick question:


I set up my app so that it now launches two system extensions: 1 for the NEFilterPacketProvider 1 for the NEFilterDataProvider.


It seems set up correctly in the sense that when I set :

providerConfiguration.filterSockets = false
providerConfiguration.filterPackets = true

Then only my system extension for NEFilterPacketProvider will launch and work correctly. The other one is not launched.


When I set

providerConfiguration.filterSockets = true
providerConfiguration.filterPackets = false


Then only the system extension for NEFilterDataProvider will launch and work correctly.


So this works correctly, and shows that my configuration is correct.


However, if I set both to true, only the extension for NEFilterDataProvider will launch (even though I do send the activation message correctly for both). Do I need to add something to the config to allow my main app to launch 2 system extensions?


Thanks!

am I guaranteed that any packet going through the network card will be [relayed] by

NEFilterPacketProvider
?

The packets passed to your packet filter include the link-layer header, so you can reasonably expect to see all packets, including non-IP packets like ARP.

However, if I set both to true, only the extension for

NEFilterDataProvider
will launch

That’s weird. Have you confirmed that both

filterDataProviderBundleIdentifier
and
filterPacketProviderBundleIdentifier
are set correctly?

Share and Enjoy

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

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

Yes, both

filterDataProviderBundleIdentifier
and
filterPacketProviderBundleIdentifier
are set correctly.


I tried two ways : having two different targets and bundles for each extension. This didn't work, as explained above.

I also tried having only 1 system extension, containing both the FilterDataProvider and FilterPacketProvider (in 2 different files). I also set up the Info.plist to look like this:


<dict>

<key>NEProviderClasses</key>

<dict>

<key>com.apple.networkextension.filter-packet</key>

<string>$(PRODUCT_MODULE_NAME).FilterPacketProvider</string>

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

<string>$(PRODUCT_MODULE_NAME).FilterDataProvider</string>

</dict>

</dict>

</plist>



However, it still didn't work. Do you know what approach is the correct one (2 targets versus 1 target) ? Do you know what else I could be doing wrong?

I realized that using 1 target per network extension (so 2 targets in total) is a non starter, because the main app can only have one NEFIlterManager, so couldn’t control both.


So I focused on the solution of having 1 system extension that cumulates the two Filter%Provider, with this Info.plist:


    NEProviderClasses
    
        com.apple.networkextension.filter-packet
        $(PRODUCT_MODULE_NAME).FilterPacketProvider
        com.apple.networkextension.filter-data
        $(PRODUCT_MODULE_NAME).FilterDataProvider
   


The extension loads fine, the problem is that only 1 FilterProvider receives the startFilter callback when I do this:


func loadFilterConfiguration(completionHandler: @escaping (Bool) -> Void) {
      
        NEFilterManager.shared().loadFromPreferences { loadError in
            DispatchQueue.main.async {
                var success = true
                if let error = loadError {
                    print("Failed to load the filter configuration: %@", error.localizedDescription)
                    success = false
                }
                completionHandler(success)
            }
        }
    }


loadFilterConfiguration { success in

            guard success else {
                print("Errrrror !")
                return
            }

            if (true) {
                let providerConfiguration = NEFilterProviderConfiguration()
               
                providerConfiguration.filterSockets = true
                providerConfiguration.filterPackets = true
                filterManager.providerConfiguration = providerConfiguration
                if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String {
                    filterManager.localizedDescription = appName
                }
            }
           
            filterManager.isEnabled = true
           
            filterManager.saveToPreferences { saveError in
                DispatchQueue.main.async {
                    if let error = saveError {
                        os_log("%@", error.localizedDescription)
                        return
                    }
                    os_log("%{public}s %{public}s", NEFilterManager.shared().providerConfiguration!.filterDataProviderBundleIdentifier!, NEFilterManager.shared().providerConfiguration!.filterPacketProviderBundleIdentifier! )
                }
            }
        }


Unless there is something I am doing wrong in the code above, this starts feeling like a bug in macOS. Both the filters should receive the startFilter callback, after this.

Note that when I keep only 1 FIlter%Provider (either one) in my extension, it works fine, indicating there is no issue in the Filter%Provider code themselves.

I do not, alas, have time to dig into the details of this here on DevForums. If you want to push this forward, my recommendation is that you open a DTS tech support incident and we can pick things up there.

Share and Enjoy

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

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

I understand, thanks for your help through this. I'll poke around a bit more, and I might open the ticket if I can't figure it out.

Absolutely am finding the same need to filter at the packet level (NEFilterPacketProvider) and at the flow level (NEFilterDataProvider). And, the coding approach of devfunshark seems appropriate, which I replicated and am having the same issue of the app only filtering by Packets if I set providerConfiguration.filterPackets = true and only by Flows if I set providerConfiguration.filterSockets = true. And again only by Flows if I set both filterPackets and filterSockets to true.

Was anybody successful in instantiating the filtering at the flow layer and the packet layer? If so, what was the approach to accomplish it?