Post

Replies

Boosts

Views

Activity

A server with the specified hostname could not be found exception
Hi, I have been working on the app that implements DNS Proxy Extension for a while now, and after a couple builds to TestFlight I noticed that I got a couple crashes that seem to be triggered by EXC_BREAKPOINT (SIGTRAP) After some investigation, it was found that crashes are connected to CFNetwork framework. So, I decided to additionally look into memory issues, but I found the app has no obvious memory leaks, no memory regression (within recommended 25%, actual value is at 20% as of right now), but the app still uses 11mb of memory footprint and most of it (6.5 mb is Swift metadata). At this point, not sure what's triggering those crashes, but I noticed that sometimes app will return message like this to the console (this example is for PostHog api that I use in the app): Task <0ABDCF4A-9653-4583-9150-EC11D852CA9E>.<1> finished with error [18 446 744 073 709 550 613] Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo={_kCFStreamErrorCodeKey=8, NSUnderlyingError=0x1072df0f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1003 "(null)" UserInfo={_kCFStreamErrorDomainKey=12, _kCFStreamErrorCodeKey=8, _NSURLErrorNWResolutionReportKey=Resolved 0 endpoints in 2ms using unknown from cache, _NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, uses wifi}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalUploadTask <0ABDCF4A-9653-4583-9150-EC11D852CA9E>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalUploadTask <0ABDCF4A-9653-4583-9150-EC11D852CA9E>.<1>" ), NSLocalizedDescription=A server with the specified hostname could not be found., NSErrorFailingURLStringKey=https://us.i.posthog.com/batch, NSErrorFailingURLKey=https://us.i.posthog.com/batch, _kCFStreamErrorDomainKey=12} If DNS Proxy Provider uses custom DoH server for resolving packets, could the cache policy for URLSession be a reason? I had a couple other ideas (HTTP3 failure, CFNetwork core issues like described here) but not sure if they are valid Would be grateful if someone could give me a hint of what I should look at
18
0
784
Sep ’24
Traffic logging with UI output using Content Filter Providers
Hey, I have been working on the app that implements both Content Filter Providers and DNS Proxy for custom network security app. However, I would like to display traffic logs from Content Filter. What's the best way to do that? I know that it works with UserDefaults under shared container with App Group. But I am not sure that it's the best approach for storing data that is constantly changing. I also tried to use CoreData with: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) But I receive error that "The file couldn’t be saved because you don’t have permission., ["reason": No permissions to create file; code = 1]" because my FilterDataProvider has access to CoreData model.
7
0
510
Aug ’24
DNS Proxy Invalidation in inactive state
Hi, I have been working on the app with NE Filter Providers for a while now and it seems to work well. However, unlike Content Filter Providers, DNS Proxy is invalidated when device is inactive state. It shows status "Invalid" for just a couple seconds before to changes to "Starting" and eventually "Running". That's not a major issue, but I would like to know what's causing this behaviour and if there is a way to fix it. I am using custom DoH in my DNS Proxy for flows proxying. And if the server times out to respond, app sends rcode 5 (Refused) for requested flow. At the same time, app shouldn't crash because all errors are handled appropriately. Would be very grateful for any thoughts, thank you!
0
0
396
Aug ’24
DNS Proxy invalidation in inactive state
Hi, I have been working on the app with NE Filter Providers for a while now and it seems to work well. However, unlike Content Filter Providers, DNS Proxy is invalidated when device is inactive state. It shows status "Invalid" for just a couple seconds before to changes to "Starting" and eventually "Running". That's not a major issue, but I would like to know what's causing this behaviour and if there is a way to fix it. I am using custom DoH in my DNS Proxy for flows proxying. And if the server times out to respond, app sends rcode 5 (Refused) for requested flow. At the same time, app shouldn't crash because all errors are handled appropriately. Would be very grateful for any thoughts, thank you!
0
0
413
Aug ’24
Webkit generated flow management using DNSProxy
Hi! I have been working on one idea for a while but can't figure out the proper way to do that. My app includes Content Filter and DNSProxy providers for filtering logic. And for the NEFilterSocketFlow everything works well, because the connection is first handled from DNSProxy and if it's blocked, NEFilterDataProvider returns datagrams that I wrote from DNSProxy (I return nxdomain). However, for NEFilterBrowserFlow it doesn't work, because webkit generated flows are for some reason intercepted by Content Filter first and at the time when the flow is checked for rules, there're none yet as DNSProxy didn't handle connection yet. So the app returns the following behaviour: In case the requested domain is not filtered by DNSProxy, the user is able to visit requested page, but if it's filtered, the flow just freezes and the page will never load for user. But I wanted to add proper handling and display block page. In case I am using some third-party apps for testing like ICS Dig, filtered domains return nxdomain properly. Not sure if there's a way to achieve desired result, but would be very grateful for any suggestions
8
0
524
Jun ’24
Custom proxying with NEDNSProxyProvider
On the [documentation page](Implement a completely custom DNS proxying protocol) it says For example, a DNS proxy provider might: Implement a completely custom DNS proxying protocol I would like to add some filtering logic to the NEDNSProxyProvider (for example, return nxdomain if the flow is not passing the filtering process). Is it possible to implement with NEDNSProxyProvider? It also says that func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool from NEDNSProxyProvider returns a Boolean value set to true if the proxy implementation decides to handle the flow, or false if it instead decides to terminate the flow link. Does it mean that the filtering logic could be added here by just returning false for the flows that are not matching the rules? Because I first tried to handle UDP flows like this in handleNewFlow(_ flow: NEAppProxyUDPFlow) function and form my own packets in connection.transferData, by first passing empty Data object and then by setting RCODE to 3, which is supposedly a nxdomain response code. However, both implementations didn't work: even though I was getting logs about handling failure, the flow was still able to go through. try await flow.open(withLocalEndpoint: flow.localEndpoint as? NWHostEndpoint) let datagrams = try await flow.readDatagrams() let results = try await datagrams.parallelMap { let connection = try DatagramConnection($0) return try await connection.transferData() } try await flow.writeDatagrams(results) flow.closeReadWithError(nil) flow.closeWriteWithError(nil) I am new to NEDNSProxyProvider and my networking knowledge is on a pretty basic level, so I would be very grateful to hear any suggestions. Thank you!
2
0
496
Apr ’24
Settings Bundle observing in NE targets
Hi, I have been trying to add Settings Bundle to my app that utilizes DNS Proxy and Content Filter, however, I noticed some weird behavior. I worked with Settings Bundle before, but the initial implementation didn't work for some reason. So, I simplified it to just one toggle switch and tried again. Initially, I had an observer adding in the init of my SettingsBundleService class and I was using a shared instance in DNS Proxy target, as a result, SettingsBundleService was observing changes but would always return false. But when I tried to use a shared instance from the main target, it worked just fine. public final class SettingsBundleService { static let shared = SettingsBundleService() private (set) var test: Bool = false init() { registerSettingsBundle() NotificationCenter.default.addObserver(self, selector: #selector(defaultsChanged), name: UserDefaults.didChangeNotification, object: nil) defaultsChanged() Logger.statistics.log("[SettingsBundleService] – added Settings Bundle observing") } private func registerSettingsBundle(){ let appDefaults = [String: AnyObject]() UserDefaults.standard.register(defaults: appDefaults) } @objc private func defaultsChanged(){ self.test = UserDefaults.standard.bool(forKey: "enable_feature_preference") Logger.statistics.log("[SettingsBundleService] – changed: \(test)") } deinit { NotificationCenter.default.removeObserver(self) Logger.statistics.log("[SettingsBundleService] – removed Settings Bundle observing") } Could somebody advise me on what I am doing wrong here?
2
0
519
May ’24
Data storage for Network Extension
Hi, I have been working on some kind of network filtering app for iOS using Content Filter Provider. And I have stored rules for each domain. As of right now, I use UserDefaults with my app's bundle suite to store and observe rules. I have also read this documentation page for UserDefaults link. Is it okay to use UserDefaults in my case, if I have rules added/modified dynamically as the flow is intercepted, or should I pick some other approach like Core Data, SwiftData, etc.? Thank you!
15
0
1.2k
Apr ’24
DNSProxy with configuration profile & MDM
I am trying to add DNSProxy configuration using .mobileconfig and MDM on supervised device. I have Content Filter payload in the same configuration file that works as expected, however I was unable to start my DNSProxy. My app has 3 extension targets for Filter Data/Control Providers and DNSProxy extension. Here is my DNSProxy payload: &lt;dict&gt; &lt;key&gt;AppBundleIdentifier&lt;/key&gt; &lt;string&gt;my.app.bundle.id&lt;/string&gt; &lt;key&gt;PayloadDescription&lt;/key&gt; &lt;string&gt;Configures DNS proxy network extension&lt;/string&gt; &lt;key&gt;PayloadDisplayName&lt;/key&gt; &lt;string&gt;DNS Proxy&lt;/string&gt; &lt;key&gt;PayloadIdentifier&lt;/key&gt; &lt;string&gt;com.apple.dnsProxy.managed.AEE249BB-4F44-4ED9-912B-6A70CC0E01B6&lt;/string&gt; &lt;key&gt;PayloadType&lt;/key&gt; &lt;string&gt;com.apple.dnsProxy.managed&lt;/string&gt; &lt;key&gt;PayloadUUID&lt;/key&gt; &lt;string&gt;AEE249BB-4F44-4ED9-912B-6A70CC0E01B6&lt;/string&gt; &lt;key&gt;PayloadVersion&lt;/key&gt; &lt;integer&gt;1&lt;/integer&gt; &lt;key&gt;ProviderBundleIdentifier&lt;/key&gt; &lt;string&gt;my.app.bundle.id.DNS-Proxy-Extension&lt;/string&gt; &lt;/dict&gt; Any thoughts on what I might be doing wrong?
8
0
965
Apr ’24
Async operations with Network Extension
Hi, I was working on some new filtering logic for my Content Filter that I would like to add. It involves making requests to remote DNS resolvers. Is it possible to use it within sync override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict of the NEFilterDataProvider? As of right now, I have a concept working in Command Line Tool and playground, however, when I try to add working module to the main project, it's not working (connections are not loading). Function that makes requests to the servers: In this function I use DispatchGroup and notify for non-main queue @available(iOS 12, *) public class NetworkService { private let nonMainQueue: DispatchQueue = DispatchQueue(label: "non-main-queue") func isBlocked(hostname: String, completion: @escaping (Bool) -> Void) { var isAnyBlocked = false let group = DispatchGroup() for server in servers { group.enter() let endpoint = NWEndpoint.Host(server) query(host: endpoint, domain: hostname, queue: .global()) { response, error in defer { group.leave() } /* * some code that determines the filtering logic * if condition is true => isAnyBlocked = true & return */ } } group.notify(queue: nonMainQueue) { completion(isAnyBlocked) } } } And, for example, in playground Semaphores make it work as expected, but the same approach doesn't work with the NEFilterDataProvider playground code sample let hostname = "google.com" func returnResponse() -> String { var result = "" let semaphore = DispatchSemaphore(value: 0) DispatchQueue.global().async { NetworkService.isBlocked(hostname: hostname) { isBlocked in result = isBlocked ? "blocked" : "allowed" semaphore.signal() } } semaphore.wait() return result } print(returnResponse()) Output: allowed
1
0
406
Apr ’24
Unable to start NEContentFilter on iOS
Previously, I added a post about the problem with NEFilterManager configuration. Since then, I explored the SimpleTunnel example project and I changed NEFilterManager setup to my own and it still worked well. Now, I simplified the code to just test that Content Filter is starting, but unfortunately it's displayed as 'Invalid' in System Settings. Here are the samples of my code, but I still don't understand what I am doing wrong here. I would be very grateful for any help. Test View struct ContentFilterView: View { @ObservedObject var vm = FilterManager.shared @State private var toggleState = false var body: some View { VStack { Toggle("Filter Status", isOn: $toggleState) .padding() .onChange(of: toggleState) { status in vm.setupFilter(with: status) } } .onAppear { vm.loadFilterConfiguration { success in if success { print("loadFilterConfiguration is successful") toggleState = vm.isEnabled ?? false print("NEFilterManager config: \(String(describing: NEFilterManager.shared().providerConfiguration?.organization))") } else { print("loadFilterConfiguration failed") toggleState = false } } } } } FilterManager class FilterManager: ObservableObject { @Published private(set) var isEnabled: Bool? = nil // MARK: - Properties private let manager = NEFilterManager.shared() private var subs = Set<AnyCancellable>() static let shared = FilterManager() private init() { manager.isEnabledPublisher() .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] isEnabled in self?.setIsEnabled(isEnabled) }) .store(in: &subs) } public func setupFilter(with status: Bool) { if status && manager.providerConfiguration == nil { let newConfiguration = NEFilterProviderConfiguration() newConfiguration.username = "TestUser" newConfiguration.organization = "Test Inc." newConfiguration.filterBrowsers = true newConfiguration.filterSockets = true manager.providerConfiguration = newConfiguration print("manager configuration saved successfully: \(String(describing: manager.providerConfiguration?.organization))") } manager.isEnabled = status manager.saveToPreferences { [weak self] error in if let error { print("Failed to save the filter configuration: \(error.localizedDescription)") self?.isEnabled = false return } } } public func loadFilterConfiguration(withCompletion completion: @escaping (Bool) -> Void) { manager.loadFromPreferences { error in if let loadError = error { print("Failed to load the filter configuration: \(loadError)") completion(false) } else { completion(true) } } } private func setIsEnabled(_ isEnabled: Bool) { guard self.isEnabled != isEnabled else { return } self.isEnabled = isEnabled print("NEFilter \(isEnabled ? "enabled" : "disabled")") } } extension NEFilterManager { // MARK: - Publisher enabling func isEnabledPublisher() -> AnyPublisher<Bool, Never> { NotificationCenter.default .publisher(for: NSNotification.Name.NEFilterConfigurationDidChange) .compactMap { [weak self] notification in guard let self else { return nil } return self.isEnabled } .eraseToAnyPublisher() } } NEFilterDataProvider class FilterDataProvider: NEFilterDataProvider { // MARK: - Properties /// A record of where in a particular flow the filter is looking. var flowOffSetMapping = [URL: Int]() /// The list of flows that should be blocked after fetching new rules. var blockNeedRules = [String]() /// The list of flows that should be allowed after fetching new rules. var allowNeedRules = [String]() override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { Log("Will handle filter flow \(flow)", prefix: "[Filter Data]") return .drop() } } NEFilterControlProvider is the same as SimpleTunnel example project NEFilterControlProvider implementation. I also followed suggested steps mentioned in this post but it didn't seem to help.
8
0
855
Apr ’24
NEFilterManager and NEDNSProxyManager
Hi, I am working on the app for some basic concept, I would like to intercept both DNS and IP connections. I succeeded in intercepting DNS using NEDNSProxyProvider, however I seem to have some troubles with IPConnections using NEFilterDataProvider. First thing, I have three targets in my app. For some reason, when I run DNS Proxy Extension target it doesn't ask me to choose the app for target run, and after the application if launched, it correctly intercepts DNS traffic and inits NEDNSProxyManager ps: all logs are correctly displayed for NEFilterDataProvider However, when I try to run Filter Data Extension target with Content Filter capability, it asks me to choose the app for run. Even tho I checked the Build Settings and those are identical to DNS Proxy Extension target. And finally, when I run main target it still inits NEDNSProxyManager properly and the NEFilterManager returns this warning -[NEFilterManager saveToPreferencesWithCompletionHandler:]_block_invoke_3: failed to save the new configuration: (null) I tried to log the configuration and compared to some code samples, but I can't identify the problem. I'd very grateful if somebody could suggest where the problems might be (targets builds difference & NEFilterManager config) I will attach a sample of code where I add configuration to my NEFilterManager // MARK: - FilterDataManager final class FilterDataManager: NSObject, ObservableObject { // MARK: - Properties private let manager = NEFilterManager.shared() private let filterName = "Data Filter" @Published private(set) var isEnabled: Bool? = nil // MARK: - Singleton static let shared = FilterDataManager() // Cancellables set private var subs: Set<AnyCancellable> = [] private override init() { super.init() enable() manager.isEnabledPublisher() .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] isEnabled in self?.setIsEnabled(isEnabled) }) .store(in: &subs) } // MARK: - Filter Configurations func enable() { manager.updateConfiguration { [unowned self] manager in manager.localizedDescription = filterName manager.providerConfiguration = createFilterProviderConfiguration() manager.isEnabled = true } completion: { result in guard case let .failure(error) = result else { return } Log("Filter enable failed: \(error)", prefix: "[Filter]") } } private func createFilterProviderConfiguration() -> NEFilterProviderConfiguration { let configuration = NEFilterProviderConfiguration() configuration.organization = "***" configuration.filterBrowsers = true configuration.filterSockets = true return configuration } func disable() { Log("Will disable filter", prefix: "[Filter]") manager.updateConfiguration { manager in manager.isEnabled = false } completion: { result in guard case let .failure(error) = result else { return } Log("Filter enable failed: \(error)") } } private func setIsEnabled(_ isEnabled: Bool) { guard self.isEnabled != isEnabled else { return } self.isEnabled = isEnabled Log("Filter \(isEnabled ? "enabled" : "disabled")", prefix: "[Filter]") } } ```Swift extension NEFilterManager { // MARK: - NEFilterManager config update func updateConfiguration(_ body: @escaping (NEFilterManager) -> Void, completion: @escaping (Result<Void, Error>) -> Void) { loadFromPreferences { [unowned self] error in if let error, let filterError = FilterError(error) { completion(.failure(filterError)) return } body(self) saveToPreferences { (error) in if let error, let filterError = FilterError(error) { completion(.failure(filterError)) return } completion(.success(())) } } } // MARK: - Publisher enabling func isEnabledPublisher() -> AnyPublisher<Bool, Never> { NotificationCenter.default .publisher(for: NSNotification.Name.NEFilterConfigurationDidChange) .compactMap { [weak self] notification in guard let self else { return nil } return self.isEnabled } .eraseToAnyPublisher() } } // MARK: - FilterError @available(iOS 8.0, *) enum FilterError: Error { /// The Filter configuration is invalid case configurationInvalid /// The Filter configuration is not enabled. case configurationDisabled /// The Filter configuration needs to be loaded. case configurationStale /// The Filter configuration cannot be removed. case configurationCannotBeRemoved /// Permission denied to modify the configuration case configurationPermissionDenied /// Internal error occurred while managing the configuration case configurationInternalError case unknown init?(_ error: Error) { switch error { case let error as NSError: switch NEFilterManagerError(rawValue: error.code) { case .configurationInvalid: self = .configurationInvalid return case .configurationDisabled: self = .configurationDisabled return case .configurationStale: self = .configurationStale return case .configurationCannotBeRemoved: self = .configurationCannotBeRemoved return case .some(.configurationPermissionDenied): self = .configurationPermissionDenied return case .some(.configurationInternalError): self = .configurationInternalError return case .none: return nil @unknown default: break } default: break } assertionFailure("Invalid error \(error)") return nil } }
3
0
595
Mar ’24