Post

Replies

Boosts

Views

Created

NEPacketTunnelProvider performance issues
Following previous question here :https://developer.apple.com/forums/thread/801397, I've decided to move my VPN implementation using NEPacketTunnelProvider on a dedicated networkExtension. My extension receives packets using readPacketsWithCompletionHandler and forwards them immediately to a daemon through a shared memory ring buffer with Mach port signaling. The daemon then encapsulates the packets with our VPN protocol and sends them over a UDP socket. I'm seeing significant throughput degradation, much higher than the tunnel overhead itself. On our side, the IPC path supports parallel handling, but I'm not not sure whether the provider has any internal limitation that prevents packets from being processed in parallel. The tunnel protocol requires packet ordering, but preparation can be done in parallel if the provider allows it. Is there any inherent constraint in NEPacketTunnelProvider that prevents concurrent packet handling, or any recommended approach to improve throughput in this model? For comparison, when I create a utun interface manually with ifconfig and route traffic through it, I observe performance that is about four times faster.
1
0
78
4w
Creating machine identifier to be used by daemon based app
I am developing a daemon-based product that needs a cryptographic, non-spoofable proof of machine identity so a remote management server can grant permissions based on the physical machine. I was thinking to create a signing key in the Secure Enclave and use a certificate signed by that key as the machine identity. The problem is that the Secure Enclave key I can create is only accessible from user context, while my product runs as a system daemon and must not rely on user processes or launchAgents. Could you please advise on the recommended Apple-supported approaches for this use case ? Specifically, Is there a supported way for a system daemon to generate and use an unremovable Secure Enclave key during phases like the pre-logon, that doesn't have non user context (only the my application which created this key/certificate will have permission to use/delete it) If Secure Enclave access from a daemon is not supported, what Apple-recommended alternatives exist for providing a hardware-backed machine identity for system daemons? I'd rather avoid using system keychain, as its contents may be removed or used by root privileged users. The ideal solution would be that each Apple product, would come out with a non removable signing certificate, that represent the machine itself (lets say that the cetificate name use to represent the machine ID), and can be validated by verify that the root signer is "Apple Root CA"
3
0
563
Oct ’25
SwiftUI based application gets stuck on deadlock
Hi, I get a weird deadlock in my swiftUI based application where com.apple.libtrace.state.block-list is waiting on com.apple.main-thread any idea what can lead to this deadlock ? also, what is com.apple.libtrace.state.block-list used for ? When deadlock occurs, the main thread it stucks on publishing property change that triggers callback function. for example : # definition @Published var event: eventType = .evtTypeWhatever ... # setting that may linked to the deadlock : self.handler.event = eventType.evtSomething; # callback definition .onReceive(cbhandler.$event, perform: eventReceived) # implementation : private func eventReceived(_ type:eventType) { switch type { case .evtSomething: # do something Here's the relevant callstack. We can see that It even didn’t get to the callback. The deadlock is probably in publishing mechanism itself 2480 Thread_87233850 DispatchQueue_1: com.apple.main-thread (serial) + 2480 start (in dyld) + 6076 [0x197c92b98] + 2480 __debug_main_executable_dylib_entry_point (in myAgent.debug.dylib) + 12 [0x10402fc38] myApp.swift:0 + 2480 static networkWrapperAppApp.$main() (in myAgent.debug.dylib) + 40 [0x10402fbf8] /<compiler-generated>:0 + 2480 static App.main() (in SwiftUI) + 224 [0x1c8b2a5c0] + 2480 runApp<A>(_:) (in SwiftUI) + 108 [0x1c87c9658] + 2480 specialized runApp(_:) (in SwiftUI) + 160 [0x1c836b878] + 2480 NSApplicationMain (in AppKit) + 880 [0x19c00d35c] + 2480 -[NSApplication run] (in AppKit) + 480 [0x19c036c64] + 2480 -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (in AppKit) + 688 [0x19c9e25b0] + 2480 _DPSNextEvent (in AppKit) + 684 [0x19c043ab4] + 2480 _BlockUntilNextEventMatchingListInModeWithFilter (in HIToolbox) + 76 [0x1a3d3e484] + 2480 ReceiveNextEventCommon (in HIToolbox) + 676 [0x1a3bb34e8] + 2480 RunCurrentEventLoopInMode (in HIToolbox) + 324 [0x1a3bb027c] + 2480 CFRunLoopRunSpecific (in CoreFoundation) + 572 [0x19811bc58] + 2480 __CFRunLoopRun (in CoreFoundation) + 1980 [0x19811ca9c] + 2480 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ (in CoreFoundation) + 16 [0x19815bda4] + 2480 _dispatch_main_queue_callback_4CF (in libdispatch.dylib) + 44 [0x197e89cec] + 2480 _dispatch_main_queue_drain (in libdispatch.dylib) + 180 [0x197e89db0] + 2480 _dispatch_main_queue_drain.cold.5 (in libdispatch.dylib) + 812 [0x197eb1b58] + 2480 _dispatch_client_callout (in libdispatch.dylib) + 16 [0x197e9485c] + 2480 _dispatch_call_block_and_release (in libdispatch.dylib) + 32 [0x197e7ab2c] + 2480 thunk for @escaping @callee_guaranteed () -> () (in myAgent.debug.dylib) + 48 [0x103f44a3c] /<compiler-generated>:0 + 2480 closure #1 in closure #9 in AppDelegate.applicationDidFinishLaunching(_:) (in myAgent.debug.dylib) + 24508 [0x10401625c] /<compiler-generated>:0 + 2480 callbackHandler.currPage.modify (in myAgent.debug.dylib) + 44 [0x103ff84ec] /<compiler-generated>:0 + 2480 callbackHandler.currPage.setter (in myAgent.debug.dylib) + 204 [0x103ff8470] myApp.swift:81 + 2480 callbackHandler.currentPage.setter (in myAgent.debug.dylib) + 120 [0x103ff7850] myApp.swift:0 + 2480 static Published.subscript.setter (in Combine) + 84 [0x1abbe3500] + 2480 specialized static Published.subscript.setter (in Combine) + 60 [0x1abbe4648] + 2480 specialized static Published.withMutation<A>(of:keyPath:storage:apply:) (in Combine) + 556 [0x1abbe45b0] + 2480 closure #1 in static Published.subscript.setter (in Combine) + 428 [0x1abbe3724] + 2480 PublishedSubject.send(_:) (in Combine) + 192 [0x1abbbc558] + 2480 ObservableObjectPublisher.send() (in Combine) + 636 [0x1abbd18fc] + 2480 ObservableObjectPublisher.Inner.send() (in Combine) + 176 [0x1abbd2244] + 2480 _os_unfair_lock_lock_slow (in libsystem_platform.dylib) + 176 [0x19806a324] + 2480 __ulock_wait2 (in libsystem_kernel.dylib) + 8 [0x197ffea54] 2480 Thread_87263447 DispatchQueue_22: com.apple.libtrace.state.block-list (serial) + 2480 start_wqthread (in libsystem_pthread.dylib) + 8 [0x19802db74] + 2480 _pthread_wqthread (in libsystem_pthread.dylib) + 292 [0x19802ee64] + 2480 _dispatch_workloop_worker_thread (in libdispatch.dylib) + 540 [0x197e8dae8] + 2480 _dispatch_root_queue_drain_deferred_wlh (in libdispatch.dylib) + 292 [0x197e8e264] + 2480 _dispatch_lane_invoke (in libdispatch.dylib) + 440 [0x197e83e60] + 2480 _dispatch_lane_serial_drain (in libdispatch.dylib) + 740 [0x197e83350] + 2480 _dispatch_client_callout (in libdispatch.dylib) + 16 [0x197e9485c] + 2480 _dispatch_call_block_and_release (in libdispatch.dylib) + 32 [0x197e7ab2c] + 2480 ___os_state_request_for_self_block_invoke (in libsystem_trace.dylib) + 372 [0x197d827a8] + 2480 _dispatch_sync_f_slow (in libdispatch.dylib) + 148 [0x197e8a640] + 2480 __DISPATCH_WAIT_FOR_QUEUE__ (in libdispatch.dylib) + 368 [0x197e8aa88] + 2480 _dispatch_thread_event_wait_slow (in libdispatch.dylib) + 56 [0x197e7cadc] + 2480 _dlock_wait (in libdispatch.dylib) + 56 [0x197e7ccbc] + 2480 __ulock_wait (in libsystem_kernel.dylib) + 8 [0x197ff29b8]
Topic: UI Frameworks SubTopic: SwiftUI
2
0
100
Oct ’25
How to restore macOS routing table after VPN crash or routing changes?
Hi, I have a VPN product for macOS. When activated, it creates a virtual interface that capture all outgoing traffic for the VPN. the VPN encrypt it, and send it to the tunnel gateway. The gateway then decapsulates the packet and forwards it to the original destination. To achieve this, The vpn modifies the routing table with the following commands: # after packets were encoded with the vpn protocol, re-send them through # the physical interface /sbin/route add -host <tunnel_gateway_address_in_physical_subnet> <default_gateway> -ifp en0 > /dev/null 2>&1 # remove the default rule for en0 and replace it with scoped rule /sbin/route delete default <default_gateway> -ifp en0 > /dev/null 2>&1 /sbin/route add default <default_gateway> -ifscope en0 > /dev/null 2>&1 # create new rule for the virtual interface that will catch all packets # for the vpn /sbin/route add default <tunnel_gateway_address_in_tunnel_subnet> -ifp utunX > /dev/null 2>&1 This works in most cases. However, there are scenarios where the VPN process may crash, stop responding, or another VPN product may alter the routing table. When that happens, packets may no longer go out through the correct interface. Question: Is there a way to reliably reconstruct the routing table from scratch in such scenarios? Ideally, I would like to rebuild the baseline rules for the physical interface (e.g., en0) and then reapply the VPN-specific rules on top. Are there APIs, system utilities, or best practices in macOS for restoring the original routing configuration before reapplying custom VPN routes? Thanks
5
0
357
Sep ’25
Excessive batter drain in macOS during sleep mode.
We are experiencing abnormal battery drain during sleep on several machines that installed our product. The affected devices appear to enter and exit sleep repeatedly every few seconds, even though the system logs show no new wake request reasons or changes in wake timers. Symptoms: Battery drops ~1% every ~15–20 minutes overnight. pmset -g log shows repeated "Entering Sleep" and "Wake Requests" events every few seconds. Wake requests remain unchanged between cycles and are scheduled far into the future (i.e. 20+ minutes later), yet the log lines keep repeating. On healthy machines, the same wake request entries appear only once every 20–30 minutes as expected, with minimal battery drop during sleep (~1% in 9 hours). What we've checked: No user activity (system lid closed, device idle). No significant pmset -g assertions; only powerd and bluetoothd are holding expected PreventUserIdleSystemSleep. pmset -g on affected machines shows sleep set to 0, likely due to sleep prevented by powerd, bluetoothd. No third-party daemons are holding assertions or logging excessive activity. Sample Logs from Affected Machine: 2025-06-28 21:57:29 Sleep Entering Sleep state due to 'Maintenance Sleep':TCPKeepAlive=active Using Batt (Charge:76%) 3 secs 2025-06-28 21:57:31 Wake Requests [process=mDNSResponder request=Maintenance deltaSecs=7198 wakeAt=2025-06-28 23:57:29 ...] 2025-06-28 21:57:38 Sleep Entering Sleep state due to 'Maintenance Sleep':TCPKeepAlive=active Using Batt (Charge:76%) 3 secs 2025-06-28 21:57:40 Wake Requests [process=mDNSResponder request=Maintenance deltaSecs=7198 wakeAt=2025-06-28 23:57:38 ...] 2025-06-28 21:57:47 Sleep Entering Sleep state due to 'Maintenance Sleep':TCPKeepAlive=active Using Batt (Charge:75%) 3 secs 2025-06-28 21:57:49 Wake Requests [process=mDNSResponder request=Maintenance deltaSecs=7198 wakeAt=2025-06-28 23:57:47 ...] The only change in logs is the wakeAt timestamp being slightly updated . The wake requests themselves (process, type, deltaSecs) remain identical. Yet, the system keeps entering/exiting sleep every few seconds, which leads to power drain. We would appreciate your help in identifying: Why the sleep/wake cycles are repeating every few seconds on these machines. Whether this behavior is expected under certain conditions or indicates a regression or misbehavior in power management. How we can trace what exactly is triggering the repeated wake (e.g., a subsystem, implicit assertion, etc.). Whether there are unified log predicates or private logging options to further trace the root cause (e.g., process holding IO or waking CPU without explicit assertion). We can provide access to full logs, configuration profiles, and system diagnostics if needed.
4
0
195
Jul ’25
Don't fragment bit doesn't get set in Sequoia
Hi, I've noticed a weird behavior happening on Sequoia with DF bit: On machine where SIP is disabled, when I do /sbin/ping -D -s 1400 8.8.8.8 I do see the DF bit in wireshark On machine where SIP is enabled, when I do /sbin/ping -D -s 1400 8.8.8.8 I do not see the DF bit in wireshark The -D flag should set the DF bit but for some reason it doesn’t if the SIP is enabled. Perhaps there was any change in permission/entitlements mechanism in Sequoia that can explain it ? I'm using the built-in ping command so maybe it should be signed with more entitlements ?
3
0
369
Feb ’25
Getting connection settings from method handleNewUDPFlow
I'm using NETransparentProxyProvider to intercept udp sockets using the method handleNewUDPFlow. An application may create a UDP socket and set the DONTFRAG using setsockopt method setsockopt(s, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val)) In this case, do I have option in this case, to get the connection settings inside the callback (void)handleNewUDPFlow:(NEAppProxyUDPFlow *)flow initialRemoteEndpoint:(NWEndpoint *)remoteEndpoint; So in this case, I would be able to create the outgoing socket with the exact same characteristics, after the original app socket got intercepted by my proxy provider ?
1
0
371
Feb ’25
Xcconfig variables doesn't get loaded automatically in project settings
Hi, I am using xcode build that receive it's configuration using xcconfig files, those add some new definitions to the project, like the location of openssl library. If xcode environment variable include prefix that matches one of the fields in the project settings, it is automatically referred to as if you added it to that field. for example : the var HEADER_SEARCH_PATHS_openssl_libopenssl has value (openssl headers' path) that should be automatically added to the field Headers Search Paths under project settings. For some reason it stopped working for me and i'm not sure why (i've tried to release the xcconfig files). any idea why ? Thanks !
0
1
341
Jan ’25
Load network/security extension skip the finish callback
Hi, i'm working on an endpoint security extension loader and implement several callbacks from delegate object OSSystemExtensionRequestDelegate the callback i'm interested in is : public func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result); public func requestNeedsUserApproval(_ request: OSSystemExtensionRequest); I've noticed that if I manually approve the extension long time after it was activated, the extension process goes up, but the callback isn't being called. The requestNeedUserApproval callback always gets called. I see in the unified logs that when the extension goes from activated_waiting_for_user -> activated_enabling -> activated_enabled Than the request callback doesn't get called. But whenever the extension goes activated_waiting_for_user -> activated_disabled The request callback gets called. (this is counter intuitive since we expected the state activated_disabled may hint that the extension failed to be activated somehow) Any Idea why my callback doesn't gets called if the extension gets approved long after it was activated ?
1
0
424
Nov ’24
How to make my daemon run only in pre login mode.
Hi, I'd like to be able to run my daemon process only in pre-logon mode that can be reach by either reboot the machine prior to provide user credentials, or by log out from current user. So far I couldn't find any useful configuration in the plist file under /Library/LaunchDaemon. Perhaps there's a way to get notification programmatically for when the system enter/exit pre-login mode ? Thanks
1
0
512
Sep ’24
Application signed with ability to load system extensions started failing due to signature issue
Hi, I've developed an application which reside under /Applications. Inside the main application bundle (/Applications/mainApp.app) there are sub-app that contain security extension. Here's the relevant path /Applications/mainApp.app/Contents/Helpers/subApp.app/Contents/Library/SystemExtensions/com.myComp.type.systemextension/ So far I could load the extension by running the subApp and make sure it calls the extension activation API. but seems like starting from Sonoma (i'm using version 14.6.1 )it stopped working, and I get crash dump on signature failure which trying to open the subApp.app. in the crash log I get reason of invalid code sign. I also get the following hints Binary Images: 0x1050a0000 - 0x10512bfff dyld_path_missing (*) <f635824e-318b-3f0c-842c-c369737f2b68> /dyld_path_missing 0x104d9c000 - 0x104d9ffff main_executable_path_missing (*) <1df5f408-cb16-304f-8b38-226e29361161> /main_executable_path_missing Is it possible that new OS version have new validation rule that enforce something about the location of the app that can start extensions ?
2
0
809
Sep ’24
Detect and thwart file copy operation using securityExtension.
For a security product, I wonder if security extension has a capability to catch a file during copy operation (I guess it's composed out of multiple basic ops like file read and file write). I'd like to store the file in some quarantined temporal (let's say when someone copy file from external file system like usb/network location and copy it back once the file has properly scanned. So far, i've used the authorization capabilities of the security extension. I wonder if there's also an option to change the target location of a file being copied ? Thanks.
3
0
877
Feb ’24
Using SimplePing example to send ICMP with DF flag set
Hi, I've tried to modify the simplePing example from here https://developer.apple.com/library/archive/samplecode/SimplePing/ and set the DF flag on. In my attempt, I've used setsockopt right after socket was created : fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); int val = 1; setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val)); However, from wireshark I could clearly see that the icmp packet had the DF bit unset ... Please help me figure out what's wrong in my code. Thanks !
4
0
1.7k
Aug ’23
MTU cache doesn't gets updated when PMTU is set.
HI, I've created a virtual interface that used to get all outgoing packets, encapsulate with some VPN header, before resend them to their final destination through the physical adapter. In order to choose the optimal MTU size that won't trigger fragmentation, I'd like to calculate the PMTU between the physical interface and the destination, subtracted by the encapsulation header size. Then I'll set the virtual adapter's MTU with this result. In order to do so, I poll the overall MTU cache using sysctl on macOS. First, I verified that path mtu discovery is set sysctl net.inet.tcp.path_mtu_discovery net.inet.tcp.path_mtu_discovery: 1 Then, I tried to extract the cached pmtu for the gateway from the other size of the tunnel using the routing table . static constexpr auto kSysctlMibLength = 6; void get_pmtu_cache() { std::map<std::string, std::uint32_t> res; size_t size_needed = 0; std::vector<char> route_table; std::array<int, kSysctlMibLength> mib; char *next = nullptr; char *lim = nullptr; struct rt_msghdr *rtm = nullptr; struct sockaddr *saddr = nullptr; struct sockaddr_in *sockin = nullptr; char dest_ip_address[INET6_ADDRSTRLEN]; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = 0; mib[4] = NET_RT_DUMP; mib[5] = 0; // stage 1 : get the routing table // get routing table size if (sysctl(mib.data(), kSysctlMibLength, nullptr, &size_needed, nullptr, 0) < 0) { return; } // allocate local var according to size route_table.reserve(size_needed); // get routing table contents if (sysctl(mib.data(), kSysctlMibLength, route_table.data(), &size_needed, nullptr, 0) < 0) { return; } In the next step, I simple iterate the routing table elements and extract the following field for each destination : rt_msghdr.rt_metrics.rmx_mtu which is the path MTU from current endpoint to dest address. lim = route_table.data() + size_needed; for (next = route_table.data(); next < lim; next += rtm->rtm_msglen) { rtm = reinterpret_cast<struct rt_msghdr *>(next); saddr = reinterpret_cast<struct sockaddr *>(rtm + 1); if ((rtm->rtm_addrs & RTA_DST) != 0) { sockin = reinterpret_cast<struct sockaddr_in *>(saddr); if (nullptr == inet_ntop(saddr->sa_family, &sockin->sin_addr.s_addr, dest_ip_address,INET6_ADDRSTRLEN)) { continue; } const std::string dest_ip_address_str(dest_ip_address, strlen(dest_ip_address)); auto iter = res.find(dest_ip_address_str); if (iter == res.end() || iter->second > rtm->rtm_rmx.rmx_mtu) { res.insert_or_assign(dest_ip_address_str, rtm->rtm_rmx.rmx_mtu); } } } when I finally print all the values in res I see that my pmtu to my VPN server is 1500, even-though I've set the server's mtu size to 1000, and I check that ping -D -s 1500 <server> doesn't work since packets from size 1500 that cannot be fragmanted won't work. auto item = res.find(vpn_server_address); if (item == res.cend()) { printf("no pmtu found to %s\n", vpn_server_address ); return; } ret = item->second; I've tried to trigger the pmtu discovery to the VPN server using nscurl by sending https requests there, but the pmtu still remained on 1500. Perhaps I'm missing something ? do I extract the pmtu correctly ? do I trigger the pmtu discovery by sending https messages using nscurl which is based on NSURLSession ? Thanks !
2
0
987
Aug ’23