Post

Replies

Boosts

Views

Activity

Issue with Network Flow Detection using NEFilterDataProvider in iOS Network Extension.
PLATFORM AND VERSION iOS Development environment: Xcode 16.0, macOS 15.0.1 Run-time configuration: iOS 17.5.1 DESCRIPTION OF PROBLEM We are working on an iOS application that utilizes the NEFilterDataProvider class from the Network Extension framework to control network flows. However, we are encountering an issue where network flows are not being detected as expected. Here are the details of our setup: We are using the NEFilterDataProvider class to filter network traffic in our app. The filtering setup works well for certain flows/apps, but we cannot detect Facebook network flows as intended. The app is correctly configured with the necessary entitlements, and we have set up the required App Groups and Network Extension capabilities. We would like to request guidance on how to troubleshoot or resolve this issue. Could you provide insights on: Whether there are any known limitations or conditions under which network flows may not be detected by NEFilterDataProvider. Recommendations for additional debugging techniques to better understand why some flows might not be captured. Recommendations for additional code to be added to detect some flows that might not be captured. Any specific scenarios or configurations that might be causing this issue in iOS. STEPS TO CHECK Replace below code in FilterDataProvider. Try running the app and set debugger in FilterDataProvider. Launch Facebook app. You will observe that no NEFilterFlow is detected in handleNewFlow for actions such as posts, reels, etc. import NetworkExtension class FilterDataProvider: NEFilterDataProvider { let blockedDomains = [ "facebook.com" ] override func startFilter(completionHandler: @escaping (Error?) -> Void) { // Perform any necessary setup here. DNSLogger.shared.log(message: "Filter started") completionHandler(nil) } override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { // Perform any necessary cleanup here. DNSLogger.shared.log(message: "Filter stopped with reason: \(reason)") completionHandler() } override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { var url: URL? if let urlFlow = flow as? NEFilterBrowserFlow { url = urlFlow.url } else { let urlFlow = flow as? NEFilterSocketFlow url = urlFlow?.url } guard let hostName = url?.host else { return .allow() } DNSLogger.shared.log(message: "Domain reveived: \(hostName)") return .allow() } // Handle inbound data (data received from the network) override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { DNSLogger.shared.log(message: "Inbound data: \(readBytes)") return .needRules() } // Handle outbound data (data sent to the network) override func handleOutboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { // Inspect or modify outbound data if needed // For example, you could log the data or modify it before sending DNSLogger.shared.log(message: "Outbound data: \(readBytes)") return .needRules() } override func handleRemediation(for flow: NEFilterFlow) -> NEFilterRemediationVerdict { return .needRules() } override func handleRulesChanged() { // Handle any changes to the rules } }
1
4
171
4w
Query Regarding NEFilterDataProvider's Hostname Resolution Across Different Browsers
PLATFORM AND VERSION macOS Development environment: Xcode 15.0, macOS 15.0.1 Run-time configuration: macOS 15.0.1 DESCRIPTION OF PROBLEM We are currently developing a macOS app using the NEFilterDataProvider in the Network Extension framework, and we've encountered an issue regarding hostname resolution that we would like your guidance on. In our implementation, we need to drop network flows based on the hostname. The app successfully receives the remoteHostname or remoteEndpoint.hostname for browsers such as Safari and Mozilla Firefox. However, for other browsers like Chrome, Opera Mini, Arc, Brave, and Edge, we only receive the IP address instead of the hostname. We are particularly looking for a way to retrieve the hostname for all browsers to apply our filtering logic consistently. Could you please advise whether there is any additional configuration or API we can use to ensure that we receive hostnames for these browsers as well? Alternatively, is this a limitation of the browsers themselves, and should we expect to only receive IP addresses for certain cases? STEPS TO REPRODUCE For Chrome, Brave, Edge, and Arc browsers you won't receive the hostname in NEFilterFlow. Using the same sample project provided in WWDC 2019 https://developer.apple.com/documentation/networkextension/filtering_network_traffic import NetworkExtension import os.log import Network /** The FilterDataProvider class handles connections that match the installed rules by prompting the user to allow or deny the connections. */ class FilterDataProvider: NEFilterDataProvider { // MARK: NEFilterDataProvider override func startFilter(completionHandler: @escaping (Error?) -> Void) { completionHandler(nil) } override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { completionHandler() } override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { guard let socketFlow = flow as? NEFilterSocketFlow, let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint, let localEndpoint = socketFlow.localEndpoint as? NWHostEndpoint else { return .allow() } var hostName: String? = nil // Attempt to use the URL host for native apps (e.g., Safari) if let url = socketFlow.url { hostName = url.host os_log("URL-based Host: %@", hostName ?? "No host found") } // Fallback: Use remote hostname for third-party browsers like Chrome if hostName == nil { if #available(macOS 11.0, *), let remoteHostname = socketFlow.remoteHostname { hostName = remoteHostname os_log("Remote Hostname: %@", hostName ?? "No hostname found") } else { hostName = remoteEndpoint.hostname os_log("IP-based Hostname: %@", hostName ?? "No hostname found") } } let flowInfo = [ FlowInfoKey.localPort.rawValue: localEndpoint.port, FlowInfoKey.remoteAddress.rawValue: remoteEndpoint.hostname, FlowInfoKey.hostName.rawValue: hostName ?? "No host found" ] // Ask the app to prompt the user let prompted = IPCConnection.shared.promptUser(aboutFlow: flowInfo, rawFlow: flow) { allow in let userVerdict: NEFilterNewFlowVerdict = allow ? .allow() : .drop() self.resumeFlow(flow, with: userVerdict) } guard prompted else { return .allow() } return .pause() } // Helper function to check if a string is an IP address func isIPAddress(_ hostName: String) -> Bool { var sin = sockaddr_in() var sin6 = sockaddr_in6() if hostName.withCString({ inet_pton(AF_INET, $0, &sin.sin_addr) }) == 1 { return true } else if hostName.withCString({ inet_pton(AF_INET6, $0, &sin6.sin6_addr) }) == 1 { return true } return false } }
1
3
156
4w