We have developed a DNS filter extension that works for most applications, but it does not receive all DNS queries.
In particular, if we have our extension installed and enabled, we see Safari browsing cause local DNS servers to be used instead of going through our extension.
What is the logic for how DNS servers vs. extensions are chosen to resolve DNS queries?
Networking
RSS for tagExplore the networking protocols and technologies used by the device to connect to Wi-Fi networks, Bluetooth devices, and cellular data services.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
We are experiencing an issue where our iOS app’s network extension (acting as a VPN) is being unexpectedly terminated by the operating system. The termination appears identical to a user-initiated stop, as the extension receives the following call: NEProviderStopReasonUserInitiated.
The issue occurs sporadically but can happen 10–20 times per day on devices with less than 10% free storage.
On one affected device, opening the Camera app (or using the camera within another app like WhatsApp) consistently triggers the issue, making it easily reproducible.
Memory consumption does not seem to be the cause—the extension is stopped while using only ~10MB of memory, well below the 50MB limit.
We noticed a pattern related to swap usage:
• On affected devices, the “Swap Used” column shows very low values (a few MB).
• On unaffected devices, swap usage is significantly higher (hundreds of MB).
• This is the only clear difference we’ve observed.
The issue occurs across different device models and iOS versions (18.2.1 and 17.6.1).
It also happens across different app builds (compiled with Xcode 15.x and Xcode 16.x).
We found a similar report on the Apple Developer Forums:
🔗 https://developer.apple.com/forums/thread/108149
Has anyone else encountered this behavior with Network Extensions? Could low swap usage or system resource constraints be a factor? Any suggestions for debugging or potential workarounds would be greatly appreciated.
Hi there,
We are facing some issues regarding TLS connectivity:
Starting with iOS 26, the operating system refuses to open TLS sockets to local devices with self-signed certificates over Wi-Fi. In this situation, connection is no longer possible, even if the device is detected on the network with Bonjour.
We have not found a workaround for this problem.
We've tryied those solutions without success:
Added the 'NSAppTransportSecurity' key to the info.plist file, testing all its items, such as "NSAllowsLocalNetworking", "NSExceptionDomains", etc.
Various code changes to use properties such as "sec_protocol_options_set_local_identity" and "sec_protocol_options_set_tls_server_name" to no avail.
Brutally import the certificate files into the project and load them via, for example, "Bundle.main.url(forResource: "nice_INTERFACE_server_cert", withExtension: "crt")", using methods such as sec_trust_copy_ref and SecCertificateCopyData.
Download the .pem or .crt files to the iPhone, install them (now visible under "VPN & Device Management"), and then flag them as trusted by going to "Settings -> General -> Info -> Trust". certificates"
The most critical part seems to be the line
sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { $2(true) }, queue)
whose purpose is to bypass certificate checks and validate all of them (as apps already do). However, on iOS26, if I set a breakpoint on leg$2(true),` it never gets there, while on iOS 18, it does.
I'll leave as example the part of the code that was tested the most below. Currently, on iOS26, the handler systematically falls back to .cancelled:
func startConnection(host: String, port: UInt16) {
self.queue = DispatchQueue(label: "socketQueue")
let tlsOptions = NWProtocolTLS.Options()
sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { $2(true) }, queue)
let parameters = NWParameters(tls: tlsOptions)
self.nwConnection = NWConnection(host: .init(host), port: .init(rawValue: port)!, using: parameters)
self.nwConnection.stateUpdateHandler = { [weak self] state in
switch state {
case .setup:
break
case .waiting(let error):
self?.connectionDidFail(error: error)
case .preparing:
break
case .ready:
self?.didConnectSubject.onNext(Void())
case .failed(let error):
self?.connectionDidFail(error: error)
case .cancelled:
self?.didDisconnectSubject.onNext(nil)
@unknown default:
break
}
}
self.setupReceive()
self.nwConnection.start(queue: queue)
}
These are the prints made during the procedure. The ones with the dot are from the app, while the ones without are warnings/info from Xcode:
🔵 INFO WifiNetworkManager.connect():52 - Try to connect onto the interface access point with ssid NiceProView4A9151_AP
🔵 INFO WifiNetworkManager.connect():68 - Connected to NiceProView4A9151_AP
tcp_output [C13:2] flags=[R.] seq=215593821, ack=430284980, win=4096 state=CLOSED rcv_nxt=430284980, snd_una=215593821
nw_endpoint_flow_failed_with_error [C13 192.168.0.1:443 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], dns, uses wifi, LQM: unknown)] already failing, returning
nw_connection_copy_protocol_metadata_internal_block_invoke [C13] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C13] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_connected_local_endpoint_block_invoke [C13] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C13] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C14] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C14] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_connected_local_endpoint_block_invoke [C14] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C14] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
[C14 192.168.0.1:443 tcp, tls, attribution: developer] is already cancelled, ignoring cancel
[C14 192.168.0.1:443 tcp, tls, attribution: developer] is already cancelled, ignoring cancel
nw_connection_copy_protocol_metadata_internal_block_invoke [C15] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C15] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_connected_local_endpoint_block_invoke [C15] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C15] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C16] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C16] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
nw_connection_copy_connected_local_endpoint_block_invoke [C16] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C16] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
[C16 192.168.0.1:443 tcp, tls, attribution: developer] is already cancelled, ignoring cancel
[C16 192.168.0.1:443 tcp, tls, attribution: developer] is already cancelled, ignoring cancel
🔴 ERROR InterfaceDisconnectedViewModel.connect():51 - Sequence timeout.
Topic:
App & System Services
SubTopic:
Networking
Tags:
Foundation
Developer Tools
Nearby Interaction
iOS
Hi all,
I work on a smart product that, for setup, uses a captive portal to allow users to connect and configure the device.
It emits a WiFi network and runs a captive portal - an HTTP server operates at 10.0.0.1, and a DNS server responds to all requests with 10.0.0.1 to direct "any and all" request to the server.
When iOS devices connect, they send a request to captive.apple.com/hotspot-detect.html; if it returns success, that means they're on the internet; if not, the typical behavior in the past has been to assume you're connected to a captive portal and display what's being served.
I serve any requests to /hotspot-detect.html with my captive portal page (index.html).
This has worked reliably on iOS18 for a long time (user selects my products WiFi network, iOS detects portal and opens it).
But almost everyone who's now trying with iOS26 is having the "automatic pop up" behavior fail - usually it says "Error opening page - Hotspot login cannot open the page because the network connection was lost." However, if opening safari and navigating to any URL (or 10.0.0.1) the portal loads - it's just the iOS auto-detect and open that's not working
iOS18 always succeeds; iOS26 always fails.
Anybody have any idea what changes may have been introduced in iOS26 on this front, or anything I can do to help prompt or coax iOS26 into loading the portal? It typically starts reading, but then stops mid-read.
Topic:
App & System Services
SubTopic:
Networking
Hi there,
can some one help how to debug this crashes? where I can start to find root causes of this crashes. I've got lot of these NSInvalidArgumentException crashes in myapp last version
I have no idea how to reproduce these issues since it doesn't point to any specific code on myapp, so I don't know how to start
Fatal Exception: NSInvalidArgumentException
-[NWConcrete_nw_protocol_options copyWithZone:]: unrecognized selector sent to instance 0x283391d60
Fatal Exception: NSInvalidArgumentException
-[NSConcreteHashTable lengthOfBytesUsingEncoding:]: unrecognized selector sent to instance 0x281d4cbe0
Fatal Exception: NSInvalidArgumentException
-[_NSXPCConnectionExportedObjectTable lengthOfBytesUsingEncoding:]: unrecognized selector sent to instance 0x2829d11d0
Fatal Exception: NSInvalidArgumentException
-[OS_dispatch_group lengthOfBytesUsingEncoding:]: unrecognized selector sent to instance 0x281a11900
Fatal Exception: NSInvalidArgumentException
-[__NSCFData getBytes:maxLength:usedLength:encoding:options:range:remainingRange:]: unrecognized selector sent to instance 0x28210e440
Fatal Exception: NSInvalidArgumentException
-[_NSCoreTypesetterLayoutCache copyWithZone:]: unrecognized selector sent to instance 0x283bbc730
Thanks
com.kitabisa.ios_issue_dd3c71c96cddb5bb99874640746439d6_crash_session_de9bb41c2b7e43fa9ccfc42e0f649aa3_DNE_0_v2_stacktrace.txt
Hello to all
I have coded in swift a headless app, that launches 3 go microservices and itself. The app listens via unix domain sockets for commands from the microservices and executes different VPN related operations, using the NEVPNManager extension. Because there are certificates and VPN operations, the headless app and two Go microservices must run as root.
The app and microservices run perfectly when I run in Xcode launching the swift app as root. However, I have been trying for some weeks already to modify the application so at startup it requests the password and runs as root or something similar, so all forked apps also run as root. I have not succeeded. I have tried many things, the last one was using SMApp but as the swift app is a headless app and not a CLI command app it can not be embedded. And CLI apps can not get the VPN entitlements. Can anybody please give me some pointers how can I launch the app so it requests the password and runs as root in background or what is the ideal framework here? thank you again.
Hello all,
Does anyone know how long it will take Apple to approve multicast entitlement approval after the Apple form is submitted?
Any input would be appreciated.
Thank you
Allyson
Hi, when I perform an overlay installation via a PKG on macOS for an application containing the NEFilterDataProvider functionality, there is a chance that the entire system network becomes unreachable. Disabling the corresponding Content Filter in "System Settings > Network > Filters" immediately restores network connectivity. This issue does not occur every time, with a frequency of approximately 1 in 20 installation attempts.
The following details may help identify the problem:
The Filter.app containing the NEFilterDataProvider resides within the main app's Resources directory, e.g., /Applications/Main.app/Contents/Resources/Filter.app
Main.app is installed via a PKG; the issue typically occurs during an overlay installation of Main.app.
The NEFilterDataProvider operates as a System Extension.
The func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {} returns .allow.
Wireshark packet captures show TCP packets but no UDP packets; TCP handshakes cannot complete.
Disabling the corresponding content filter in "System Settings > Network > Filters" restores the network; re-enabling it breaks connectivity again.
After waiting for a period, approximately 30-60 minutes, network connectivity can recover automatically.
What causes this and how can it be fixed? Any workarounds?
Hi,
I have created an application for NFC tag scanning and read the tag data. For that,
i enabled the capability: NearField Communication Tag reading.
Then I added 2 tag formats in the entitlement
then i added info.plist:
NFCReaderUsageDescription
We need to use NFC
com.apple.developer.nfc.readersession.felica.systemcodes
8005
8008
0003
fe00
90b7
927a
12FC
86a7
com.apple.developer.nfc.readersession.iso7816.select-identifiers
D2760000850100
D2760000850101
but even though when i run the app and tap the nfc card im getting some error:
NFCTag didBecomeActive
2025-08-29 19:08:12.272278+0530 SAFRAN_NFC[894:113090] NFCTag didDetectTags
2025-08-29 19:08:12.282869+0530 SAFRAN_NFC[894:113520] [CoreNFC] -[NFCTagReaderSession _connectTag:error:]:730 Error Domain=NFCError Code=2 "Missing required entitlement" UserInfo={NSLocalizedDescription=Missing required entitlement}
2025-08-29 19:08:12.284044+0530 SAFRAN_NFC[894:113090] NFCTag restarting polling
2025-08-29 19:08:12.372116+0530 SAFRAN_NFC[894:113090] NFCTag didDetectTags
2025-08-29 19:08:12.381535+0530 SAFRAN_NFC[894:113378] [CoreNFC] -[NFCTagReaderSession _connectTag:error:]:730 Error Domain=NFCError Code=2 "Missing required entitlement" UserInfo={NSLocalizedDescription=Missing required entitlement}
2025-08-29 19:08:12.382246+0530 SAFRAN_NFC[894:113090] NFCTag restarting polling
2025-08-29 19:08:12.470667+0530 SAFRAN_NFC[894:113090] NFCTag didDetectTags
2025-08-29 19:08:12.479336+0530 SAFRAN_NFC[894:113378] [CoreNFC] -[NFCTagReaderSession _connectTag:error:]:730 Error Domain=NFCError Code=2 "Missing required entitlement" UserInfo={NSLocalizedDescription=Missing required entitlement}
2025-08-29 19:08:12.480101+0530 SAFRAN_NFC[894:113090] NFCTag restarting polling
Could you please help me wha tis the issue and give solution for that?
Development environment
Xcode 26.0 Beta 6
iOS 26 Simulator
macOS 15.6.1
To verify TLS 1.3 session resumption behavior in URLSession, I configured URLSessionConfiguration as follows and sent an HTTP GET request:
let config = URLSessionConfiguration.ephemeral
config.tlsMinimumSupportedProtocolVersion = .TLSv13
config.tlsMaximumSupportedProtocolVersion = .TLSv13
config.httpMaximumConnectionsPerHost = 1
config.httpAdditionalHeaders = ["Connection": "close"]
config.enablesEarlyData = true
let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
let url = URL(string: "https://www.google.com")!
var request = URLRequest(url: url)
request.assumesHTTP3Capable = true
request.httpMethod = "GET"
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
print("Error during URLSession data task: \(error)")
return
}
if let data = data, let responseString = String(data: data, encoding: .utf8) {
print("Received data via URLSession: \(responseString)")
} else {
print("No data received or data is not UTF-8 encoded")
}
}
task.resume()
However, after capturing the packets, I found that the ClientHello packet did not include the early_data extension.
It seems that enablesEarlyData on URLSessionConfiguration is not being applied.
How can I make this work properly?
Hi Team,
I have a Network Extension application and UI frontend for it.
The UI frontend talks to the Network Extension using XPC, as provided by NEMachServiceName.
On M2 machine,
The application and XPC connection works fine on clean installation.
But, when the application is upgraded, the XPC connection keeps failing.
Upgrade steps:
PreInstall script kills the running processes, both UI and Network Extension
Let installation continue
PostInstall script to launch the application after installation complete.
Following code is successful to the point of resume from UI application
NSXPCInterface *exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(IPCUIObject)];
newConnection.exportedInterface = exportedInterface;
newConnection.exportedObject = delegate;
NSXPCInterface *remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(IPCExtObject)];
newConnection.remoteObjectInterface = remoteObjectInterface;
self.currentConnection = newConnection;
[newConnection resume];
But it fails to get the object
id<IPCExtObject> providerProxy = [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *registerError) {
}];
Please note, this only fails for M2. For M1, this exact code is running fine.
Additionally, if I uninstall the application by dropping it in Trash and then installing the newer version, then too, the application works fine.
This is a topic that’s come up a few times on the forums, so I thought I’d write up a summary of the issues I’m aware of. If you have questions or comments, start a new thread in the App & System Services > Networking subtopic and tag it with Network Extension. That way I’ll be sure to see it go by.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Network Extension Provider Packaging
There are two ways to package a network extension provider:
App extension ( appex )
System extension ( sysex )
Different provider types support different packaging on different platforms. See TN3134 Network Extension provider deployment for the details.
Some providers, most notably packet tunnel providers on macOS, support both appex and sysex packaging. Sysex packaging has a number of advantages:
It supports direct distribution, using Developer ID signing.
It better matches the networking stack on macOS. An appex is tied to the logged in user, whereas a sysex, and the networking stack itself, is global to the system as a whole.
Given that, it generally makes sense to package your Network Extension (NE) provider as a sysex on macOS. If you’re creating a new product that’s fine, but if you have an existing iOS product that you want to bring to macOS, you have to account for the differences brought on by the move to sysex packaging. Similarly, if you have an existing sysex product on macOS that you want to bring to iOS, you have to account for the appex packaging. This post summarises those changes.
Keep the following in mind while reading this post:
The information here applies to all NE providers that can be packaged as either an appex or a sysex. When this post uses a specific provider type in an example, it’s just an example.
Unless otherwise noted, any information about iOS also applies to iPadOS, tvOS, and visionOS.
Process Lifecycle
With appex packaging, the system typically starts a new process for each instance of your NE provider. For example, with a packet tunnel provider:
When the users starts the VPN, the system creates a process and then instantiates and starts the NE provider in that process.
When the user stops the VPN, the system stops the NE provider and then terminates the process running it.
If the user starts the VPN again, the system creates an entirely new process and instantiates and starts the NE provider in that.
In contrast, with sysex packaging there’s typically a single process that runs all off the sysex’s NE providers. Returning to the packet tunnel provider example:
When the users starts the VPN, the system instantiates and starts the NE provider in the sysex process.
When the user stops the VPN, the system stops and deallocates the NE provider instances, but leaves the sysex process running.
If the user starts the VPN again, the system instantiates and starts a new instances of the NE provider in the sysex process.
This lifecycle reflects how the system runs the NE provider, which in turn has important consequences on what the NE provider can do:
An appex acts like a launchd agent [1], in that it runs in a user context and has access to that user’s state.
A sysex is effectively a launchd daemon. It runs in a context that’s global to the system as a whole. It does not have access to any single user’s state. Indeed, there might be no user logged in, or multiple users logged in.
The following sections explore some consequences of the NE provider lifecycle.
[1] It’s not actually run as a launchd agent. Rather, there’s a system launchd agent that acts as the host for the app extension.
App Groups
With an app extension, the app extension and its container app run as the same user. Thus it’s trivial to share state between them using an app group container.
Note When talking about extensions on Apple platforms, the container app is the app in which the extension is embedded and the host app is the app using the extension. For network extensions the host app is the system itself.
That’s not the case with a system extension. The system extension runs as root whereas the container app runs an the user who launched it. While both programs can claim access to the same app group, the app group container location they receive will be different. For the system extension that location will be inside the home directory for the root user. For the container app the location will be inside the home directory of the user who launched it.
This does not mean that app groups are useless in a Network Extension app. App groups are also a factor in communicating between the container app and its extensions, the subject of the next section.
IMPORTANT App groups have a long and complex history on macOS. For the full story, see App Groups: macOS vs iOS: Working Towards Harmony.
Communicating with Extensions
With an app extension there are two communication options:
App-provider messages
App groups
App-provider messages are supported by NE directly. In the container app, send a message to the provider by calling sendProviderMessage(_:responseHandler:) method. In the appex, receive that message by overriding the handleAppMessage(_:completionHandler:) method.
An appex can also implement inter-process communication (IPC) using various system IPC primitives. Both the container app and the appex claim access to the app group via the com.apple.security.application-groups entitlement. They can then set up IPC using various APIs, as explain in the documentation for that entitlement.
With a system extension the story is very different. App-provider messages are supported, but they are rarely used. Rather, most products use XPC for their communication. In the sysex, publish a named XPC endpoint by setting the NEMachServiceName property in its Info.plist. Listen for XPC connections on that endpoint using the XPC API of your choice.
Note For more information about the available XPC APIs, see XPC Resources.
In the container app, connect to that named XPC endpoint using the XPC Mach service name API. For example, with NSXPCConnection, initialise the connection with init(machServiceName:options:), passing in the string from NEMachServiceName. To maximise security, set the .privileged flag.
Note XPC Resources has a link to a post that explains why this flag is important.
If the container app is sandboxed — necessary if you ship on the Mac App Store — then the endpoint name must be prefixed by an app group ID that’s accessible to that app, lest the App Sandbox deny the connection. See the app groups documentation for the specifics.
When implementing an XPC listener in your sysex, keep in mind that:
Your sysex’s named XPC endpoint is registered in the global namespace. Any process on the system can open a connection to it [1]. Your XPC listener must be prepared for this. If you want to restrict connections to just your container app, see XPC Resources for a link to a post that explains how to do that.
Even if you restrict access in that way, it’s still possible for multiple instances of your container app to be running simultaneously, each with its own connection to your sysex. This happens, for example, if there are multiple GUI users logged in and different users run your container app. Design your XPC protocol with this in mind.
Your sysex only gets one named XPC endpoint, and thus one XPC listener. If your sysex includes multiple NE providers, take that into account when you design your XPC protocol.
[1] Assuming that connection isn’t blocked by some other mechanism, like the App Sandbox.
Inter-provider Communication
A sysex can include multiple types of NE providers. For example, a single sysex might include a content filter and a DNS proxy provider. In that case the system instantiates all of the NE providers in the same sysex process. These instances can communicate without using IPC, for example, by storing shared state in global variables (with suitable locking, of course).
It’s also possible for a single container app to contain multiple sysexen, each including a single NE provider. In that case the system instantiates the NE providers in separate processes, one for each sysex. If these providers need to communicate, they have to use IPC.
In the appex case, the system instantiates each provider in its own process. If two providers need to communicate, they have to use IPC.
Managing Secrets
An appex runs in a user context and thus can store secrets, like VPN credentials, in the keychain. On macOS this includes both the data protection keychain and the file-based keychain. It can also use a keychain access group to share secrets with its container app. See Sharing access to keychain items among a collection of apps.
Note If you’re not familiar with the different types of keychain available on macOS, see TN3137 On Mac keychain APIs and implementations.
A sysex runs in the global context and thus doesn’t have access to user state. It also doesn’t have access to the data protection keychain. It must use the file-based keychain, and specifically the System keychain. That means there’s no good way to share secrets with the container app.
Instead, do all your keychain operations in the sysex. If the container app needs to work with a secret, have it pass that request to the sysex via IPC. For example, if the user wants to use a digital identity as a VPN credential, have the container app get the PKCS#12 data and password and then pass that to the sysex so that it can import the digital identity into the keychain.
Memory Limits
iOS imposes strict memory limits an NE provider appexen [1]. macOS imposes no memory limits on NE provider appexen or sysexen.
[1] While these limits are not documented officially, you can get a rough handle on the current limits by reading the posts in this thread.
Frameworks
If you want to share code between a Mac app and its embedded appex, use a structure like this:
MyApp.app/
Contents/
MacOS/
MyApp
PlugIns/
MyExtension.appex/
Contents/
MacOS/
MyExtension
…
Frameworks/
MyFramework.framework/
…
There’s one copy of the framework, in the app’s Frameworks directory, and both the app and the appex reference it.
This approach works for an appex because the system always loads the appex from your app’s bundle. It does not work for a sysex. When you activate a sysex, the system copies it to a protected location. If that sysex references a framework in its container app, it will fail to start because that framework isn’t copied along with the sysex.
The solution is to structure your app like this:
MyApp.app/
Contents/
MacOS/
MyApp
Library/
SystemExtensions/
MyExtension.systemextension/
Contents/
MacOS/
MyExtension
Frameworks/
MyFramework.framework/
…
…
That is, have both the app and the sysex load the framework from the sysex’s Frameworks directory. When the system copies the sysex to its protected location, it’ll also copy the framework, allowing the sysex to load it.
To make this work you have to change the default rpath configuration set up by Xcode. Read Dynamic Library Standard Setup for Apps to learn how that works and then tweak things so that:
The framework is embedded in the sysex, not the container app.
The container app has an additional LC_RPATH load command for the sysex’s Frameworks directory (@executable_path/../Library/SystemExtensions/MyExtension.systemextension/Contents/Frameworks).
The sysex’s LC_RPATH load command doesn’t reference the container app’s Frameworks directory (@executable_path/../../../../Frameworks) but instead points to the sysex’s Framweorks directory (@executable_path/../Frameworks).
Entitlements
When you build an app with an embedded NE extension, both the app and the extension must be signed with the com.apple.developer.networking.networkextension entitlement. This is a restricted entitlement, that is, it must be authorised by a provisioning profile.
The value of this entitlement is an array, and the values in that array differ depend on your distribution channel:
If you distribute your app directly with Developer ID signing, use the values with the -systemextension suffix.
Otherwise — including when you distribute the app on the App Store and when signing for development — use the values without that suffix.
Make sure you authorise these values with your provisioning profile. If, for example, you use an App Store distribution profile with a Developer ID signed app, things won’t work because the profile doesn’t authorise the right values.
In general, the easiest option is to use Xcode’s automatic code signing. However, watch out for the pitfall described in Exporting a Developer ID Network Extension.
Revision History
2025-11-06 Added the Entitlements section. Explained that, with sysex packaging, multiple instances of your container app might connect simultaneously with your sysex.
2025-09-17 First posted.
My application advertises service uuid FC66 and 00410b66-2553-48d7-cf18-000000002154 in advertising data, and "loading" as local name in response data in foreground.
But iOS cut local name to "loadi" and add Hashed UUID data 0100000000000000000000000600000000 to response data.
Why iOS add hashed uuid data? Is it because my 128-bit uuid format is wrong?
We want to advertise the complete local name data.
How to avoid this problem?
Hi everyone,
I’m currently developing a macOS app that is distributed via a DMG file on our website. The app includes an App Extension (appex) for Network Extension functionality.
I’m wondering if distributing via DMG on the web requires the app extension to be implemented as a System Extension instead of an App Extension. Is it necessary to migrate to System Extension for web-based DMG distribution, or can I continue using App Extension as is?
Any insights or recommendations would be greatly appreciated.
Thank you!
We are developing a client server application using TCP bsd sockets.
When our client is connected to the server, copying another client .app bundle from a file server on the same machine (using Finder or terminal using cp), occasionally causes the first client to disconnect. The client receives an EBROKENPIPE error when attempting to write to its socket.
In the Console, the following message appears just before the disconnection:
necp_socket_find_policy_match: Marking socket in state 258 as defunct
This issue seems to occur only when copying an .app bundle signed with the same TeamIdentifier as the running client. Copying arbitrary files or bundles with a different TeamIdentifier does not trigger the problem.
We are running on macOS 15.5. The issue appears specific to macOS 15 and was not observed on earlier versions.
Any help or pointers would be greatly appreciated!
Topic:
App & System Services
SubTopic:
Networking
After dropping an A-record TTL to 60 secs (it was previously no higher than 600 secs for several weeks) and making an IP change for a small business website on Monday, I took down the old web service just over 24 hours later on Tuesday evening. We then had reports of some customers not being able to access the website on Wednesday morning. On investigation using my iPhone it would appear that Apple Private Relay is still directing clients to the old IP address.
It's just as well I have iCloud+ as I would never have seen this issue otherwise and would have been none the wiser as to why some customers were having problems.
Has anyone else seen this and/or have a fix other than waiting longer? Do you know how long it takes for Apple Private Relay to update? This isn't expected behaviour of DNS?
I spoke to someone at Apple yesterday and there wasn't much they can do. I hope they're escalating internally as almost 3 days later it's still pointing users to the old IP address despite having ample time for proper DNS propagation.
Topic:
App & System Services
SubTopic:
Networking
If I run an app with a Message Filter Extension on a handset with iOS 18.2 then it runs fine, however if I run the exact same app with no changes on a different phone which has iOS 17.6.1 installed then the following error occurs when the extension is enabled within Settings:
dyld[631]: Symbol not found: _$sSo40ILMessageFilterCapabilitiesQueryResponseC14IdentityLookupE21promotionalSubActionsSaySo0abI6ActionVGvs
I'm trying to use NEHotspotNetwork to configure an IoT. I've read all the issues that have plagued other developers when using this framework, and I was under the impression that bugs were filed and fixed.
Here are my issues in hopes that someone can catch my bug, or has finally figured this out and it's not a bug in the framework with no immediate fix on the horizon.
If I use the following code:
let config = NEHotspotConfiguration(ssid: ssid)
config.joinOnce = true
KiniStatusBanner.shared.show(text: "Connecting to Kini", in: presentingVC.view)
NEHotspotConfigurationManager.shared.apply(config) { error in
DispatchQueue.main.async {
if let nsError = error as NSError?,
nsError.domain == NEHotspotConfigurationErrorDomain,
nsError.code == NEHotspotConfigurationError.alreadyAssociated.rawValue {
print("Already connected to \(self.ssid)")
KiniStatusBanner.shared.dismiss()
self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
} else if let error = error {
// This doesn't happen
print("❌ Failed to connect: \(error.localizedDescription)")
KiniStatusBanner.shared.update(text: "Failed to Connect to Kini. Try again later.")
KiniStatusBanner.shared.dismiss(after: 2.5)
} else {
// !!!! Most often, this is the path the code takes
NEHotspotNetwork.fetchCurrent { current in
if let ssid = current?.ssid, ssid == self.ssid {
log("✅✅ 1st attempt: connected to \(self.ssid)")
KiniStatusBanner.shared.dismiss()
self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
} else {
// Dev forums talked about giving things a bit of time to settle and then try again
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
NEHotspotNetwork.fetchCurrent { current in
if let ssid = current?.ssid, ssid == self.ssid {
log("✅✅✅ 2nd attempt: connected to \(self.ssid)")
KiniStatusBanner.shared.dismiss()
self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
} else {
log("❌❌❌ 2nd attempt: Failed to connect: \(self.ssid)")
KiniStatusBanner.shared.update(text: "Could not join Kini network. Try again.")
KiniStatusBanner.shared.dismiss(after: 2.5)
self.cleanupHotspot()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("cleanup again")
self.cleanupHotspot()
}
}
}
}
log("❌❌ 1st attempt: Failed to connect: \(self.ssid)")
KiniStatusBanner.shared.update(text: "Could not join Kini network. Try again.")
KiniStatusBanner.shared.dismiss(after: 2.5)
self.cleanupHotspot()
}
As you can see, one can't just use NEHotspotConfigurationManager.shared.apply and has to double-check to make sure that it actually succeeds, by checking to see if the SSID desired, matches the one that the device is using.
Ok, but about 50% of the time, the call to NEHotspotNetwork.fetchCurrent gives me this error:
NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request
Well, there is a workaround for that randomness too. At some point before calling this code, one can:
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
That eliminates the NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request
BUT... three issues.
The user is presented with an authorization alert: Allow "Kini" to use your location? This app needs access to you Wi-Fi name to connect to your Kini device. Along with a map with a location pin on it. This gives my users a completely wrong impression, especially for a device/app where we promise users not to track their location. They actually see a map with their location pinned on it, implying something that would freak out anyone who was expecting no tracking. I understand why an authorization is normally required, but since all we are getting is our own IoT's SSID, there should be no need for an authorization for this, and no map associated with the request. Again, they are accessing my IoT's network, NOT their home/location Wi-Fi SSID. My app already knows and specifies that network, and all I am trying to do is to work around a bug that makes it look like I have a successful return from NEHotspotConfigurationManager.shared.apply() when in fact the network I was looking for wasn't even on.
Not only do I get instances where the network doesn't connect, and result codes show no errors, but I also get instances where I get an alert that says that the network is unreachable, yet my IoT shows that the app is connected to its Wi-Fi. On the iOS device, I go to the Wi-Fi settings, and see that I am on the IoT's network. So basically, sometimes I connect, but the frameworks says that there is no connection, and sometimes it reports a connection when there is none.
As you can see in the code, I call cleanupHotspot() to make the iOS device get off of my temp Wi-Fi SSID. This is the code:
func cleanupHotspot() {
NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid)
}
That code gets called by the above code when things aren't as I expect and need to cleanup. And I also call it when the user dismisses the viewcontroller that is attempting to make the connection.
It doesn't always work. I get stuck on the tempo SSID, unless I go through this whole thing again: try to make the connection again, this time it succeeds quickly, and then I can disconnect.
Any ideas?
I'm on iOS18.5, and have tried this on multiple iPhones including 11, 13 and 16.
We are using PacketTunnel as system extension to establish vpn tunnel. The flow is like:
Create a PacketTunnelProvide to establish vpn
When tunnel gets connected add excludedRoutes by calling setTunnelNetworkSettings().
Result: The routing table is not getting updated with new excludeRoutes entries.
As per setTunnelNetworkSettings() documentation:
"This function is called by tunnel provider implementations to set the network settings of the tunnel, including IP routes, DNS servers, and virtual interface addresses depending on the tunnel type. Subclasses should not override this method. This method can be called multiple times during the lifetime of a particular tunnel. It is not necessary to call this function with nil to clear out the existing settings before calling this function with a non-nil configuration."
So we believe setTunnelNetworkSettings() should be able to set new excludeRoutes. We could see we are passing correct entries to setTunnelNetworkSettings():
{
tunnelRemoteAddress = 10.192.229.240
DNSSettings = {
protocol = cleartext
server = (
10.192.230.211,
192.168.180.15,
)
matchDomains = (
,
)
matchDomainsNoSearch = NO
}
IPv4Settings = {
configMethod = manual
addresses = (
100.100.100.17,
)
subnetMasks = (
255.255.255.255,
)
includedRoutes = (
{
destinationAddress = 1.1.1.1
destinationSubnetMask = 255.255.255.255
gatewayAddress = 100.100.100.17
},
{
destinationAddress = 2.2.2.0
destinationSubnetMask = 255.255.255.255
gatewayAddress = 100.100.100.17
},
{
destinationAddress = 11.11.11.0
destinationSubnetMask = 255.255.255.0
gatewayAddress = 100.100.100.17
},
)
excludedRoutes = (
{
destinationAddress = 170.114.52.2
destinationSubnetMask = 255.255.255.255
},
)
overridePrimary = NO
}
MTU = 1298
}
The problem is present on macOS Sequoia 15.2.
Is it a known issue? Did anyone else faced this issue?
Problem description
Since macOS Sequoia, our users have experienced issues with multicast traffic in our macOS app. Regularly, the app starts but cannot receive multicast, or multicast eventually stops mid-execution. The app sometimes asks again for Local Network permission, while it was already allowed so. Several versions of our app on a single machine are sometimes (but not always) shown as different instances in the System Settings > Privacy & Security > Local Network list. And when several instances are shown in that list, disabling one disables all of them, but it does not actually forbids the app from receiving multicast traffic. All of those issues are experienced by an increasing number of users after they update their system from macOS 14 to macOS 15 or 26, and many of them have reported networking issues during production-critical moments.
We haven't been able to find the root cause of those issues, so we built a simple test app, called "FM Mac App Test", that can reproduce multicast issues. This app creates a GCDAsyncUdpSocket socket to receive multicast packets from a piece of hardware we also develop, and displays a simple UI showing if such packets are received. The app is entitled with "Custom Network Protocol", is built against x86_64 and arm64, and is archived (signed and notarized). We can share the source code if requested.
Out of the many issues our main app exhibits, the test app showcases some:
The app asks several times for Local Network permission, even after being allowed so previously. After allowing the app's Local Network and rebooting the machine, the System Settings > Privacy & Security > Local Network does not show the app, and the app asks again for Local Network access.
The app shows a different Local Network Usage Description than in the project's plist.
Several versions of the app appear as different instances in the Privacy list, and behave strangely. Toggling on or off one instance toggles the others. Only one version of the app seems affected by the setting, the other versions always seem to have access to Local Network even when the toggle is set to off.
We even did see messages from different app versions in different user accounts. This seems to contradicts Apple's documentation that states user accounts have independent Privacy settings.
Can you help us understand what we are missing (in terms of build settings, entitlements, proper archiving...) so our app conforms to what macOS expects for proper Local Network behavior?
Related material
Local Network Privacy breaks Application: this issue seemed related to ours, but the fix was to ensure different versions of the app have different UUIDs. We ensured that ourselves, to no improvement.
Local Network FAQ
Technote TN3179
Steps to Reproduce
Test App is developed on Xcode 15.4 (15F31d) on macOS 14.5 (23F79), and runs on macOS 26.0.1 (25A362). We can share the source code if requested.
On a clean install of macOS Tahoe (our test setup used macOS 26.0.1 on a Mac mini M2 8GB), we upload the app (version 5.1).
We run the app, make sure the selected NIC is the proper one, and open the multicast socket. The app asks us to allow Local Network, we allow it. The alert shows a different Local Network Usage Description than the one we set in our project's plist.
The app properly shows packets are received from the console on our LAN.
We check the list in System Settings > Privacy & Security > Local Network, it includes our app properly allowed.
We then reboot the machine. After reboot, the same list does not show the app anymore.
We run the app, it asks again about Local Network access (still with incorrect Usage Description). We allow it again, but no console packet is received yet. Only after closing and reopening the socket are the console packets received.
After a 2nd reboot, the System Settings > Privacy & Security > Local Network list shows correctly the app. The app seems to now run fine.
We then upload an updated version of the same app (5.2), also built and notarized. The 2nd version is simulating when we send different versions of our main app to our users. The updated version has a different UUID than the 1st version.
The updated version also asks for Local Network access, this time with proper Usage Description.
A 3rd updated version of the app (5.3, also with unique UUID) behaves the same. The System Settings > Privacy & Security > Local Network list shows three instances of the app.
We toggle off one of the app, all of them toggle off. The 1st version of the app (5.1) does not have local network access anymore, but both 2nd and 3rd versions do, while their toggle button seems off.
We toggle on one of the app, all of them toggle on. All 3 versions have local network access.