How to implement Content Filter in macOS

I've implemented a content filter in iOS using Network Extension with help of NEFilterDataProvider, NEFilterPacketProvider. Which works fine, getting all requests and blocking based on the requirement.

But When I implement the same this in macOS, getting NEFilterManager.shared().isEnabled true means it's connected, still able to access all the sites.

I notice one thing that for macOS in NEFilterProviderConfiguration, "filterBrowsers is not supported on macOS" is deprecated What does it mean?

Replies

I've implemented a content filter in iOS using Network Extension with help of NEFilterDataProvider, NEFilterPacketProvider.

NEFilterPacketProvider on iOS? Did you mean NEFilterControlProvider?

Regarding:

I notice one thing that for macOS in NEFilterProviderConfiguration, "filterBrowsers is not supported on macOS" is deprecated What does it mean?

Right, as the property comments state, filterBrowsers is not supported on macOS. It will compile but will not work on macOS.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
  • Thanks for your feedback I really appreciate it.

    Yeah sorry, it is NEFilterControlProvider.

    If filterBrowsers is not supported on macOS, How do I implement a working demo? I'm building a mac application that blocks URLs based on categories. For eg. If a user tries to open vimeo.com, It must be blocked.

    Is there any alternative to achieve that?

Add a Comment

How do I implement a working demo? I'm building a mac application that blocks URLs based on categories. For eg. If a user tries to open vimeo.com, It must be blocked. Is there any alternative to achieve that?

Right, so on macOS with NEFilterDataProvider if you know the address or hostname and it's always going to be static then you can setup a default NEFilterRule for this traffic. For example:

let exampleRule = NENetworkRule(
	remoteNetwork: NWHostEndpoint(hostname: "example.com", port: "0"),
	remotePrefix: 0,
	localNetwork: nil,
	localPrefix: 0,
	protocol: .TCP,
	direction: .any
)
let filterRule = NEFilterRule(networkRule: exampleRule, action: .drop)

The rule above will drop all traffic to example.com if that is what you want. The same can be done to allow traffic. Now, if you need to filter traffic further, you would create a NENetworkRule that catches all TCP traffic with the action to filterData. This rule will give your provider all TCP traffic in handleNewFlow:

let exampleRule = NENetworkRule(
	remoteNetwork: nil,
	remotePrefix: 0,
	localNetwork: nil,
	localPrefix: 0,
	protocol: .TCP,
	direction: .any
)
let filterRule = NEFilterRule(networkRule: exampleRule, action: .filterData)

The gotcha here is that you will receive an address for the inbound or outbound traffic and it will be up to you to decide whether this is a known address to allow or if it should be investigated further.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
  • I tried your code this way, doesn't work, Is there anything wrong here?

    `class FilterDataProvider: NEFilterDataProvider {   override func startFilter(completionHandler: @escaping (Error?) -> Void) {     let exampleRule = NENetworkRule(       remoteNetwork: NWHostEndpoint(hostname: "https://www.vimeo.com/", port: "0"),       remotePrefix: 0,       localNetwork: nil,       localPrefix: 0,       protocol: .TCP,       direction: .any     )     let filterRule = NEFilterRule(networkRule: exampleRule, action: .drop)

        let filterSettings = NEFilterSettings(rules: [filterRule], defaultAction: .drop)     apply(filterSettings) { error in       if let applyError = error {         os_log("Failed to apply filter settings: %@", applyError.localizedDescription)       }       completionHandler(error)     }   }   override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {     completionHandler()   }

      override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {     return .drop()   } }`

  • Please post your code as a response to the thread and not in the comments.

Add a Comment

This is FilterDataProvider class. I tried https://www.vimeo.com & vimeo.com This is the URL of demo. https://github.com/anismansuri63/MacFilter

class FilterDataProvider: NEFilterDataProvider {
  override func startFilter(completionHandler: @escaping (Error?) -> Void) {
    let exampleRule = NENetworkRule(
      remoteNetwork: NWHostEndpoint(hostname: "https://www.vimeo.com/", port: "0"),
      remotePrefix: 0,
      localNetwork: nil,
      localPrefix: 0,
      protocol: .TCP,
      direction: .any
    )
    let filterRule = NEFilterRule(networkRule: exampleRule, action: .drop)

    let filterSettings = NEFilterSettings(rules: [filterRule], defaultAction: .drop)
    apply(filterSettings) { error in
      if let applyError = error {
        os_log("Failed to apply filter settings: %@", applyError.localizedDescription)
      }
      completionHandler(error)
    }
  }
  override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
    completionHandler()
  }

  override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
    return .drop()
  }
}

If your have tried with a remoteNetwork that contains a NWHostEndpoint that looks like this:

NWHostEndpoint(hostname: "example.com", port: "0")

Without https:// and it still does not work then try opening a TSI so someone in DTS can evaluate this further.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

So we can't use NEFilterControlProvider in MacOS to filter content at the flow layer as opposed to the packet layer?

So we can't use NEFilterControlProvider in macOS to filter content at the flow layer as opposed to the packet layer?

That’s incorrect. Lemme explain:

  • iOS supports filter control providers and filter data providers.

  • This separation exists to enforce a privacy policy. See the Overview section on this page.

  • macOS does not enforce this policy, so there is only the filter data provider. Like the iOS filter data provider, this works at the flow layer.

  • Separately, macOS also supports a filter packet provider.

  • macOS also supports a transparent proxy provider, which gives you even more control.

For an overview of NE provider types and their deployment options, see TN3134 Network Extension provider deployment.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Okay, thanks for clarifying. So if see that I can initialize a set of firewall filter rules using source/destination IPs + ports and Protocol like TCP or UDP directly in the filter data provider on MacOS. However, it looks like to dynamically update the rules after my firewall is running, I have to use func handleRulesChanged() but it's not supported on MacOS. Why is that? Does this mean that I won't be able to dynamically add/delete rules after I start my content filter application on MacOS?

Add a Comment