Post

Replies

Boosts

Views

Activity

Xcode 16.4 and above build error with Network Extension and WireGuard library
I have added a Network Extension to my iOS project to use the WireGuard library. Everything was working fine up to Xcode 16, but after updating, I’m facing a build issue. The build fails with the following error: No such file or directory: '@rpath/WireGuardNetworkExtensioniOS.debug.dylib' I haven’t explicitly added any .dylib to my project. The Network Extension target builds and runs fine on Xcode 16.
0
0
13
1h
How to avoid my local server flows in Transparent App Proxy
I have written the Transparent App Proxy and can capture the network flow and send it to my local server. I want to avoid any processing on the traffic outgoing from my server and establish a connection with a remote server, but instead of connecting to the remote server, it again gets captured and sent back to my local server. I am not getting any clue on how to ignore these flows originating from my server. Any pointers, API, or mechanisms that will help me?
9
2
157
Apr ’25
Title: DNS Proxy Not Capturing Traffic When Public DNS Is Set in WiFi Settings
I'm working on a Network Extension using NEDNSProxyProvider to inspect DNS traffic. However, I've run into a couple of issues: DNS Proxy is not capturing traffic when a public DNS (like 8.8.8.8 or 1.1.1.1) is manually configured in the WiFi settings. It seems like the system bypasses the proxy in this case. Is this expected behavior? Is there a way to force DNS traffic through the proxy even if a public DNS is set? Using DNS Proxy and DNS Settings simultaneously doesn't work. Is there a known limitation or a correct way to combine these? How to set DNS or DNSSettings using DNSProxy? import NetworkExtension import SystemExtensions import SwiftUI protocol DNSProxyManagerDelegate { func managerStateDidChange(_ manager: DNSProxyManager) } class DNSProxyManager: NSObject { private let manager = NEDNSProxyManager.shared() var delegate: DNSProxyManagerDelegate? private(set) var isEnabled: Bool = false { didSet { delegate?.managerStateDidChange(self) } } var completion: (() -> Void)? override init() { super.init() self.load() } func toggle() { isEnabled ? disable() : start() } private func start() { let request = OSSystemExtensionRequest .activationRequest(forExtensionWithIdentifier: Constants.extensionBundleID, queue: DispatchQueue.main) request.delegate = self OSSystemExtensionManager.shared.submitRequest(request) log.info("Submitted extension activation request") } private func enable() { update { self.manager.localizedDescription = "DNS Proxy" let proto = NEDNSProxyProviderProtocol() proto.providerBundleIdentifier = Constants.extensionBundleID self.manager.providerProtocol = proto self.manager.isEnabled = true } } private func disable() { update { self.manager.isEnabled = false } } private func remove() { update { self.manager.removeFromPreferences { _ in self.isEnabled = self.manager.isEnabled } } } private func update(_ body: @escaping () -> Void) { self.manager.loadFromPreferences { (error) in if let error = error { log.error("Failed to load DNS manager: \(error)") return } self.manager.saveToPreferences { (error) in if let error = error { return } log.info("Saved DNS manager") self.isEnabled = self.manager.isEnabled } } } private func load() { manager.loadFromPreferences { error in guard error == nil else { return } self.isEnabled = self.manager.isEnabled } } } extension DNSProxyManager: OSSystemExtensionRequestDelegate { func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { log.info("Extension activation request needs user approval") } func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { log.error("Extension activation request failed: \(error)") } func request(_ request: OSSystemExtensionRequest, foundProperties properties: [OSSystemExtensionProperties]) { log.info("Extension activation request found properties: \(properties)") } func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { guard result == .completed else { log.error("Unexpected result \(result.description) for system extension request") return } log.info("Extension activation request did finish with result: \(result.description)") enable() } func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { log.info("Existing extension willt be replaced: \(existing.bundleIdentifier) -> \(ext.bundleIdentifier)") return .replace } } import NetworkExtension class DNSProxyProvider: NEDNSProxyProvider { var handlers: [String: FlowHandler] = [:] var isReady = false let queue = DispatchQueue(label: "DNSProxyProvider") override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) { completionHandler(nil) } override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { completionHandler() } override func handleNewUDPFlow(_ flow: NEAppProxyUDPFlow, initialRemoteEndpoint remoteEndpoint: NWEndpoint) -> Bool { let id = shortUUID() handlers[id] = FlowHandler(flow: flow, remoteEndpoint: remoteEndpoint, id: id, delegate: self) return true } override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { return false } } class FlowHandler { let id: String let flow: NEAppProxyUDPFlow let remoteEndpoint: NWHostEndpoint let delegate: FlowHandlerDelegate private var connections: [String: RemoteConnection] = [:] private var pendingPacketsByDomain: [String: [(packet: Data, endpoint: NWEndpoint, uniqueID: String, timestamp: Date)]] = [:] private let packetQueue = DispatchQueue(label: "com.flowhandler.packetQueue") init(flow: NEAppProxyUDPFlow, remoteEndpoint: NWEndpoint, id: String, delegate: FlowHandlerDelegate) { log.info("Flow received for \(id) flow: \(String(describing: flow))") self.flow = flow self.remoteEndpoint = remoteEndpoint as! NWHostEndpoint self.id = id self.delegate = delegate defer { start() } } deinit { closeAll(nil) } func start() { flow.open(withLocalEndpoint: flow.localEndpoint as? NWHostEndpoint) { error in if let error = error { self.delegate.flowClosed(self) return } self.readFromFlow() } } func readFromFlow() { self.flow.readDatagrams { packets, endpoint, error in if let error = error { self.closeAll(error) return } guard let packets = packets, let endpoints = endpoint, !packets.isEmpty, !endpoints.isEmpty else { self.closeAll(nil) return } self.processFlowPackets(packets, endpoints) self.readFromFlow() } } } Any insights or suggestions would be greatly appreciated. Thanks!
2
3
99
Apr ’25
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
331
Oct ’24
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
367
Oct ’24