Implement web filter in macOS with NetworkExtension API

796 views Asked by At

I'm working on a web filtering app for macOS for personal use. The app is supposed to capture all outbound HTTP requests produced by web browser apps on system and filter (allow or drop) them by finding their URL in a local DB.

For this, I'm following sample code of SimpleFirewall app provided as part of WWDC 2019 session "Network Extensions for Modern Mac".

Following is my subclass of NEFilterDataProvider:

import NetworkExtension
import os.log

class FilterDataProvider: NEFilterDataProvider {

    override func startFilter(completionHandler: @escaping (Error?) -> Void) {
        let filterSettings = NEFilterSettings(rules: [NEFilterRule(networkRule: NENetworkRule(
            remoteNetwork: nil,
            remotePrefix: 0,
            localNetwork: nil,
            localPrefix: 0,
            protocol: .TCP,
            direction: .outbound
        ), action: .filterData)], defaultAction: .allow)
        apply(filterSettings) { error in
            if let applyError = error {
                os_log("Failed to apply filter settings: %@", applyError.localizedDescription)
            }
            completionHandler(error)
        }
    }

    override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
        guard let socketFlow = flow as? NEFilterSocketFlow,
              let url = socketFlow.url else {
                return .allow()
        }

        /* .drop() if url found in local DB */

        return .allow()
    }
}

Although, I'm able to capture url in handleNewFlow but this does not seem like an elegant or optimal solution possible. I'm really concerned about the performance as this captures all the TCP outbound traffic generated by any app on the system not limited to just HTTP outbound traffic from web browser apps.

I can think of possible solutions but I'm unable to find the APIs available for implementing that on macOS:

  1. How do you get flow as Browser Flow, something like NEFilterBrowserFlow but not just for WebKit-based browsers but for all browsers?
  2. If #1 not possible, then how do you get something like sourceAppIdentifier to match against bundle identifiers of browser apps?
  3. If possible, how do you filter only HTTP traffic?
1

There are 1 answers

0
Stuart Welch On

I realise this is quite a late reply but I came across this post while looking at Content Filter Providers myself. I can see you already have logic to cast to a NEFilterSocketFlow. Did you try also casting to NEFilterBrowserFlow? This should give you the WebKit browser flow. As for handling other browsers, they all use WebKit under the hood so it should therefore cover all browsers.