Networking

RSS for tag

Explore the networking protocols and technologies used by the device to connect to Wi-Fi networks, Bluetooth devices, and cellular data services.

Networking Documentation

Posts under Networking subtopic

Post

Replies

Boosts

Views

Activity

Expected behavior of searchDomains
Based on https://developer.apple.com/documentation/networkextension/nednssettings/searchdomains , we expect the values mentioned in searchDomains to be appended to a single label DNS query. However, we are not seeing this behavior. We have a packetTunnelProvider VPN, where we set searchDomains to a dns suffix (for ex: test.com) and we set matchDomains to applications and suffix (for ex: abc.com and test.com) . When a user tries to access https://myapp , we expect to see a DNS query packet for myapp.test.com . However, this is not happening when matchDomainsNoSearch is set to true. https://developer.apple.com/documentation/networkextension/nednssettings/matchdomainsnosearch When matchDomainsNoSearch is set to false, we see dns queries for myapp.test.com and myapp.abc.com. What is the expected behavior of searchDomains?
6
0
224
Feb ’26
Content filtering
Hello team, Would this mean that content filters intended for all browsing can only be implemented for managed devices using MDM? My goal would be to create a content filtering app for all users, regardless of if their device is managed/supervised. thanks.
1
0
103
Jan ’26
Wi-Fi Aware Paring Flow
Hello, I understand that to discover and pair a device or accessory with Wi-Fi Aware, we can use either the DeviceDiscoveryUI or AccessorySetupKitUI frameworks. During the pairing process, both frameworks prompt the user to enter a pairing code. Is this step mandatory? What alternatives exist for devices or accessories that don't have a way to communicate a pairing code to the user (for example, devices or accessories without a display or voice capability)? Best regards, Gishan
0
0
334
Nov ’25
iOS26 captive portal detection changes?
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.
0
0
341
Oct ’25
On FTP
Questions about FTP crop up from time-to-time here on DevForums. In most cases I write a general “don’t use FTP” response, but I don’t have time to go into all the details. I’ve created this post as a place to collect all of those details, so I can reference them in other threads. IMPORTANT Apple’s official position on FTP is: All our FTP APIs have been deprecated, and you should avoid using deprecated APIs. Apple has been slowly removing FTP support from the user-facing parts of our system. The most recent example of this is that we removed the ftp command-line tool in macOS 10.13. You should avoid the FTP protocol and look to adopt more modern alternatives. The rest of this post is an informational explanation of the overall FTP picture. This post is locked so I can keep it focused. If you have questions or comments, please do create a new thread in the App & System Services > Networking subtopic and I’ll respond there. Don’t Use FTP FTP is a very old and very crufty protocol. Certain things that seem obvious to us now — like being able to create a GUI client that reliably shows a directory listing in a platform-independent manner — aren’t possible to do in FTP. However, by far the biggest problem with FTP is that it provides no security [1]. Specifically, the FTP protocol: Provides no on-the-wire privacy, so anyone can see the data you transfer Provides no client-authenticates-server authentication, so you have no idea whether you’re talking to the right server Provides no data integrity, allowing an attacker to munge your data in transit Transfers user names and passwords in the clear Using FTP for anonymous downloads may be acceptable (see the explanation below) but most other uses of FTP are completely inappropriate for the modern Internet. IMPORTANT You should only use FTP for anonymous downloads if you have an independent way to check the integrity of the data you’ve downloaded. For example, if you’re downloading a software update, you could use code signing to check its integrity. If you don’t check the integrity of the data you’ve downloaded, an attacker could substitute a malicious download instead. This would be especially bad in, say, the software update case. These fundamental problems with the FTP protocol mean that it’s not a priority for Apple. This is reflected in the available APIs, which is the subject of the next section. FTP APIs Apple provides two FTP APIs: All Apple platforms provide FTP downloads via URLSession. Most Apple platforms (everything except watchOS) support CFFTPStream, which allows for directory listings, downloads, uploads, and directory creation. All of these FTP APIs are now deprecated: URLSession was deprecated for the purposes of FTP in the 2022 SDKs (macOS 13, iOS 16, iPadOS 16, tvOS 16, watchOS 9) [2]. CFFTPStream was deprecated in the 2016 SDKs (macOS 10.11, iOS 9, iPadOS 9, tvOS 9). CFFTPStream still works about as well as it ever did, which is not particularly well. Specifically: There is at least one known crashing bug (r. 35745763), albeit one that occurs quite infrequently. There are clear implementation limitations — like the fact that CFFTPCreateParsedResourceListing assumes a MacRoman text encoding (r. 7420589) — that won’t be fixed. If you’re looking for an example of how to use these APIs, check out SimpleFTPSample. Note This sample hasn’t been updated since 2013 and is unlikely to ever be updated given Apple’s position on FTP. The FTP support in URLSession has significant limitations: It only supports FTP downloads; there’s no support for uploads or any other FTP operations. It doesn’t support resumable FTP downloads [3]. It doesn’t work in background sessions. That prevents it from running FTP downloads in the background on iOS. It’s only supported in classic loading mode. See the usesClassicLoadingMode property and the doc comments in <Foundation/NSURLSession.h>. If Apple’s FTP APIs are insufficient for your needs, you’ll need to write or acquire your own FTP library. Before you do that, however, consider switching to an alternative protocol. After all, if you’re going to go to the trouble of importing a large FTP library into your code base, you might as well import a library for a better protocol. The next section discusses some options in this space. Alternative Protocols There are numerous better alternatives to FTP: HTTPS is by far the best alternative to FTP, offering good security, good APIs on Apple platforms, good server support, and good network compatibility. Implementing traditional FTP operations over HTTPS can be a bit tricky. One possible way forward is to enable DAV extensions on the server. FTPS is FTP over TLS (aka SSL). While FTPS adds security to the protocol, which is very important, it still inherits many of FTP’s other problems. Personally I try to avoid this protocol. SFTP is a file transfer protocol that’s completely unrelated to FTP. It runs over SSH, making it a great alternative in many of the ad hoc setups that traditionally use FTP. Apple doesn’t have an API for either FTPS or SFTP, although on macOS you may be able to make some headway by invoking the sftp command-line tool. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] In another thread someone asked me about FTP’s other problems, those not related to security, so let’s talk about that. One of FTP’s implicit design goals was to provide cross-platform support that exposes the target platform. You can think of FTP as being kinda like telnet. When you telnet from Unix to VMS, it doesn’t aim to abstract away VMS commands, so that you can type Unix commands at the VMS prompt. Rather, you’re expected to run VMS commands. FTP is (a bit) like that. This choice made sense back when the FTP protocol was invented. Folks were expecting to use FTP via a command-line client, so there was a human in the loop. If they ran a command and it produced VMS-like output, that was fine because they knew that they were FTPing into a VMS machine. However, most users today are using GUI clients, and this design choice makes it very hard to create a general GUI client for FTP. Let’s consider the simple problem of getting the contents of a directory. When you send an FTP LIST command, the server would historically run the platform native directory list command and pipe the results back to you. To create a GUI client you have to parse that data to extract the file names. Doing that is a serious challenge. Indeed, just the first step, working out the text encoding, is a challenge. Many FTP servers use UTF-8, but some use ISO-Latin-1, some use other standard encodings, some use Windows code pages, and so on. I say “historically” above because there have been various efforts to standardise this stuff, both in the RFCs and in individual server implementations. However, if you’re building a general client you can’t rely on these efforts. After all, the reason why folks continue to use FTP is because of it widespread support. [2] To quote the macOS 13 Ventura Release Notes: FTP is deprecated for URLSession and related APIs. Please adopt modern secure networking protocols such as HTTPS. (92623659) [3] Although you can implement resumable downloads using the lower-level CFFTPStream API, courtesy of the kCFStreamPropertyFTPFileTransferOffset property. Revision History 2025-10-06 Explained that URLSession only supports FTP in classic loading mode. Made other minor editorial changes. 2024-04-15 Added a footnote about FTP’s other problems. Made other minor editorial changes. 2022-08-09 Noted that the FTP support in URLSession is now deprecated. Made other minor editorial changes. 2021-04-06 Fixed the formatting. Fixed some links. 2018-02-23 First posted.
0
0
5.8k
Oct ’25
Connecting to a service found by Bonjour isn't working.
I'm using NWBrowser to search for a server that I hosted. The browser does find my service but when it tries to connect to it, it gets stuck in the preparing phase in NWConnection.stateUpdateHandler. When I hardcode the local IP address of my computer (where the server is hosted) into NWConnection it works perfectly fine and is able to connect. When it gets stuck in the preparing phase, it gives me the warnings and error messages in the image below. You can also see that the service name is correct and it is found. I have tried _http._tcp and _ssh._tcp types and neither work. This is what my code looks like: func findServerAndConnect(port: UInt16) { print("Searching for server...") let browser = NWBrowser(for: .bonjour(type: "_ssh._tcp", domain: "local."), using: .tcp) browser.browseResultsChangedHandler = { results, _ in print("Found results: \(results)") for result in results { if case let NWEndpoint.service(name, type_, domain, interface) = result.endpoint { if name == "PocketPadServer" { print("Found service: \(name) of type \(type_) in domain \(domain) on interface \(interface)") // Construct the full service name, including type and domain let fullServiceName = "\(name).\(type_).\(domain)" print("Full service name: \(fullServiceName), \(result.endpoint)") self.connect(to: result.endpoint, port: port) browser.cancel() break } } } } browser.start(queue: .main) } func connect(to endpoint: NWEndpoint, port: UInt16) { print("Connecting to \(endpoint) on port \(port)...") // endpoint = NWEndpoint( let tcpParams = NWProtocolTCP.Options() tcpParams.enableFastOpen = true tcpParams.keepaliveIdle = 2 let params = NWParameters(tls: nil, tcp: tcpParams) params.includePeerToPeer = true // connection = NWConnection(host: NWEndpoint.Host("xx.xxx.xxx.xxx"), port: NWEndpoint.Port(3000), using: params) connection = NWConnection(to: endpoint, using: params) connection?.pathUpdateHandler = { path in print("Connection path update: \(path)") if path.status == .satisfied { print("Connection path is satisfied") } else { print("Connection path is not satisfied: \(path.status)") } } connection?.stateUpdateHandler = { newState in DispatchQueue.main.async { switch newState { case .ready: print("Connected to server") self.pairing = true self.receiveMessage() case .failed(let error): print("Connection failed: \(error)") self.isConnected = false case .waiting(let error): print("Waiting for connection... \(error)") self.isConnected = false case .cancelled: print("Connection cancelled") self.isConnected = false case .preparing: print("Preparing connection...") self.isConnected = false default: print("Connection state changed: \(newState)") break } } } connection?.start(queue: .main) }
4
0
159
Apr ’25
New PushKit delegate in iOS 26.4
Starting in iOS 26.4, PushKit has introduced a new "didReceiveIncomingVoIPPushWithPayload" delegate, making it explicit whether or not an app is required to report a call for any given push. The new delegate passes in a PKVoIPPushMetadata object which includes a "mustReport" property. We have not documented the exact criteria that will cause a mustReport to return false, but those criteria currently include: The app being in the foreground at the point the push is received. The app being on an active call at the point the push is received. The system determines that delivery delays have made the call old enough that it may no longer be viable. When mustReport is false, apps should call the PushKit completion handler (as they previously have) but are otherwise not required to take any other action. __ Kevin Elliott DTS Engineer, CoreOS/Hardware
0
0
142
2w
iPhone App 进行网络请求的时候出错,报错信息为:未能完成该操作。设备上无剩余空间
1、已经检查过手机的存储空间,还有一百多G的空间。app端进行网络接口情况的时候报错了,报错信息如下: Error : Error Domain=NSPOSIXErrorDomain Code=28 "No space left on device" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <7DB1CBFD-B9BE-422D-9C9A-78D8FC04DC1B>.<76>, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=28, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <7DB1CBFD-B9BE-422D-9C9A-78D8FC04DC1B>.<76>" ), _NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: pdp_ip0[lte], ipv4, ipv6, dns, expensive, estimated upload: 65536Bps, uses cell} 2、手机型号是iPhone 15 Plus,iOS 17.6.1
2
0
390
Jun ’25
Unable to detect Network Extension configuration change while pushing MDM profile
My team is developing an enterprise VPN application that needs to respond to Mobile Device Management (MDM) profile installations and removals in real-time. Our app uses the NetworkExtension framework and needs to update the UI immediately when VPN configurations are added or removed via MDM. We are currently observing NEVPNConfigurationChangeNotification to detect VPN configuration changes: While NEVPNConfigurationChangeNotification fires reliably when users manually remove VPN profiles through Settings > General > VPN & Device Management, it appears to have inconsistent behavior when MDM profiles containing VPN configurations are installed programmatically via MDM systems. STEPS TO REPRODUCE From MDM Admin Console: Deploy a new VPN profile to the test device On Device: Wait for MDM profile installation (usually silent, no user interaction required) Check Device Settings: Go to Settings > General > VPN & Device Management to confirm profile is installed Return to App: Check if the UI shows the new VPN profile
1
0
106
3w
WiFi Aware connection cannot be established when both peers publish and subscribe
It works when one device is only a publisher and the other is only a subscriber. However, when both devices act as both publisher and subscriber simultaneously—which Apple’s documentation (https://developer.apple.com/documentation/wifiaware/adopting-wi-fi-aware#Declare-services) indicates is valid—the connection never establishes. After timing out, both NetworkListener and NetworkBrowser transition to the failed state. This appears to be a race condition in Network framework. Task.detached { try await NetworkListener( for: .wifiAware( .connecting( to: .myService, from: .allPairedDevices, datapath: .defaults ) ), using: .parameters { Coder( sending: ..., receiving: ..., using: NetworkJSONCoder() ) { TCP() } } ).run { connection in await self.add(connection: connection) } } Task.detached { try await NetworkBrowser( for: .wifiAware( .connecting( to: .allPairedDevices, from: .myService ) ), using: .tcp ).run { endpoints in for endpoint in endpoints { await self.connect(to: endpoint) } } }
1
0
118
Jan ’26
URL Session randomly returns requests extremely slowly!
Hi, I'm experiencing intermittent delays with URLSession where requests take 3-4 seconds to be sent, even though the actual server processing is fast. This happens randomly, maybe 10-20% of requests. The pattern I've noticed is I create my request I send off my request using try await urlSession.data(for: request) My middleware ends up receiving this request 4-7s after its been fired from the client-side The round trip ends up taking 4-7s! This hasn't been reproducible consistently at all on my end. I've also tried ephemeral URLSessions (so recreating the session instead of using .shared so no dead connections, but this doesn't seem to help at all) Completely lost on what to do. Please help!
5
0
317
Nov ’25
Crash: Fatal Exception: NSInvalidArgumentException -[NWConcrete_nw_protocol_options copyWithZone:]: unrecognized selector sent to instance
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
2
0
649
Mar ’25
sendto() system call doesn't return an error even when there is one
Please consider this very trivial C code, which was run on 15.3.1 of macos: #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include "sys/socket.h" #include <string.h> #include <unistd.h> #include <ifaddrs.h> #include <net/if.h> // prints out the sockaddr_in6 void print_addr(const char *msg_prefix, struct sockaddr_in6 sa6) { char addr_text[INET6_ADDRSTRLEN] = {0}; printf("%s%s:%d, addr family=%u\n", msg_prefix, inet_ntop(AF_INET6, &sa6.sin6_addr, (char *) &addr_text, INET6_ADDRSTRLEN), sa6.sin6_port, sa6.sin6_family); } // creates a datagram socket int create_dgram_socket() { const int fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { perror("Socket creation failed"); return -1; } return fd; } int main() { printf("current process id:%ld parent process id: %ld\n", (long) getpid(), (long) getppid()); // // hardcode a link-local IPv6 address of a interface which is down // ifconfig: // ,,, // awdl0: flags=8822<BROADCAST,SMART,SIMPLEX,MULTICAST> mtu 1500 // options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM> // ... // inet6 fe80::34be:50ff:fe14:ecd7%awdl0 prefixlen 64 scopeid 0x10 // nd6 options=201<PERFORMNUD,DAD> // media: autoselect (<unknown type>) // status: inactive // const char *ip6_addr_str = "fe80::34be:50ff:fe14:ecd7"; // link-local ipv6 address from above ifconfig output // parse the string literal to in6_addr struct in6_addr ip6_addr; int rv = inet_pton(AF_INET6, ip6_addr_str, &ip6_addr); if (rv != 1) { fprintf(stderr, "failed to parse ipv6 addr %s\n", ip6_addr_str); exit(EXIT_FAILURE); } // create a AF_INET6 SOCK_DGRAM socket const int sock_fd = create_dgram_socket(); if (sock_fd < 0) { exit(EXIT_FAILURE); } printf("created a socket, descriptor=%d\n", sock_fd); // create a destination sockaddr which points to the above // ipv6 link-local address and an arbitrary port const int dest_port = 12345; struct sockaddr_in6 dest_sock_addr; memset((char *) &dest_sock_addr, 0, sizeof(struct sockaddr_in6)); dest_sock_addr.sin6_addr = ip6_addr; dest_sock_addr.sin6_port = htons(dest_port); dest_sock_addr.sin6_family = AF_INET6; dest_sock_addr.sin6_scope_id = 0x10; // scopeid from the above ifconfig output // now sendto() to that address, whose network interface is down. // we expect sendto() to return an error print_addr("sendto() to ", dest_sock_addr); const char *msg = "hello"; const size_t msg_len = strlen(msg) + 1; rv = sendto(sock_fd, msg, msg_len, 0, (struct sockaddr *) &dest_sock_addr, sizeof(dest_sock_addr)); if (rv == -1) { perror("sendto() expectedly failed"); close(sock_fd); exit(EXIT_FAILURE); } printf("sendto() unexpectedly succeeded\n"); // should not reach here, we expect sendto() to return an error return 0; } It creates a SOCK_DGRAM socket and attempts to sendto() to a link-local IPv6 address of a local network interface which is not UP. The sendto() is expected to fail with a "network is down" (or at least fail with some error). Let's see how it behaves. Copy that code to a file called netdown.c and compile it as follows: clang netdown.c Now run the program: ./a.out That results in the following output: current process id:29290 parent process id: 21614 created a socket, descriptor=3 sendto() to fe80::34be:50ff:fe14:ecd7:14640, addr family=30 sendto() unexpectedly succeeded (To reproduce this locally, replace the IPv6 address in that code with a link-local IPv6 address of an interface that is not UP on your system) Notice how the sendto() returned successfully without any error giving an impression to the application code that the message has been sent. In reality, the message isn't really sent. Here's the system logs from that run: PID Type Date & Time Process Message debug 2025-03-13 23:36:36.830147 +0530 kernel Process (a.out) allowed via dev tool environment (/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal) debug 2025-03-13 23:36:36.833054 +0530 kernel [SPI][HIDSPI] TX: 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 RX: 20 02 00 00 00 00 38 00 10 02 00 17 00 00 2E 00 26700 error 2025-03-13 23:36:36.838607 +0530 nehelper Failed to get the signing identifier for 29290: No such process 26700 error 2025-03-13 23:36:36.838608 +0530 nehelper Failed to get the code directory hash for 29290: No such process default 2025-03-13 23:36:36.840070 +0530 kernel cfil_dispatch_attach_event:3507 CFIL: Failed to get effective audit token for <sockID 22289651233205710 <4f3051d7ec2dce>> 26700 error 2025-03-13 23:36:36.840678 +0530 nehelper Failed to get the signing identifier for 29290: No such process 26700 error 2025-03-13 23:36:36.840679 +0530 nehelper Failed to get the code directory hash for 29290: No such process default 2025-03-13 23:36:36.841742 +0530 kernel cfil_hash_entry_log:6082 <CFIL: Error: sosend_reinject() failed>: [29290 ] <UDP(17) out so 891be95f39bd0385 22289651233205710 22289651233205710 age 0> lport 60244 fport 12345 laddr fe80::34be:50ff:fe14:ecd7 faddr fe80::34be:50ff:fe14:ecd7 hash D7EC2DCE default 2025-03-13 23:36:36.841756 +0530 kernel cfil_service_inject_queue:4466 CFIL: sosend() failed 50 Notice the last line where it states the sosend() (and internal impl detail of macos) failed with error code 50, which corresponds to ENETDOWN ("Network is down"). However, like I noted, this error was never propagated back to the application from the sendto() system call. The documentation of sendto() system call states: man sendto ... Locally detected errors are indicated by a return value of -1. ... RETURN VALUES Upon successful completion, the number of bytes which were sent is returned. Otherwise, -1 is returned and the global variable errno is set to indicate the error. So I would expect sendto() to return -1, which it isn't. The 15.3.1 source of xnu hasn't yet been published but there is the 15.3 version here https://github.com/apple-oss-distributions/xnu/tree/xnu-11215.81.4 and looking at the corresponding function cfil_service_inject_queue, line 4466 (the one which is reported in the logs) https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/net/content_filter.c#L4466, the code there logs this error and the cfil_service_inject_queue function then returns back the error. However, looking at the call sites of the call to cfil_service_inject_queue(...), there are several places within that file which don't track the return value (representing an error value) and just ignore it. Is that intentional and does that explain this issue? Does this deserve to be reported as a bug through feedback assistant?
2
0
456
Mar ’25
Does app launch recency affect NEPacketTunnelProvider, HotspotHelper, or NEHotspotManager functionality?
We are assisting a client with their app integration. The client believes that NEPacketTunnelProvider, NEHotspotHelper, and NEHotspotManager extensions stop functioning if the containing app hasn't been launched by the user within some recent window (e.g. 30, 60, or 90 days). We haven't been able to find any documentation supporting this claim. Specifically, we'd like to know: Is there any app launch recency requirement that would cause iOS to stop invoking a registered NEHotspotHelper or NEHotspotManager configuration? Is there any app launch recency requirement that would cause iOS to tear down or prevent activation of a NEPacketTunnelProvider? More generally, does iOS enforce any kind of "staleness" check on apps that provide Network Extension or Hotspot-related functionality, where not being foregrounded for some period causes the system to stop honoring their registrations? If such a mechanism exists, we'd appreciate any pointers to documentation or technical notes describing the behavior and timeframes involved. If it doesn't exist, confirmation would help us guide our client's debugging in the right direction. Thank you.
1
0
34
1d
sendto() system call - Nondeterministic "No route to host" due to local network restrictions
Please consider this trivial C code which deals with BSD sockets. This will illustrate an issue with sendto() which seems to be impacted by the recent "Local Network" restrictions on 15.3.1 macos. #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include "sys/socket.h" #include <string.h> #include <unistd.h> #include <ifaddrs.h> #include <net/if.h> // prints out the sockaddr_in6 void print_addr(const char *msg_prefix, struct sockaddr_in6 sa6) { char addr_text[INET6_ADDRSTRLEN] = {0}; printf("%s%s:%d, addr family=%u\n", msg_prefix, inet_ntop(AF_INET6, &sa6.sin6_addr, (char *) &addr_text, INET6_ADDRSTRLEN), sa6.sin6_port, sa6.sin6_family); } // creates a datagram socket int create_dgram_socket() { const int fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { perror("Socket creation failed"); return -1; } return fd; } // returns a string representing the current local time char *current_time() { time_t seconds_since_epoch; time(&seconds_since_epoch); char *res = ctime(&seconds_since_epoch); const size_t len = strlen(res); // strip off the newline character that's at the end of the ctime() output res[len - 1] = '\0'; return res; } // Creates a datagram socket and then sends a messages (through sendto()) to a valid // multicast address. This it does two times, to the exact same destination address from // the exact same socket. // // Between the first and the second attempt to sendto(), there is // a sleep of 1 second. // // The first time, the sendto() succeeds and claims to have sent the expected number of bytes. // However system logs (generated through "log collect") seem to indicate that the message isn't // actually sent (there's a "cfil_service_inject_queue:4466 CFIL: sosend() failed 65" in the logs). // // The second time the sendto() returns a EHOSTUNREACH ("No route to host") error. // // If the sleep between these two sendto() attempts is removed then both the attempts "succeed". // However, the system logs still suggest that the message isn't actually sent. int main() { printf("current process id:%ld parent process id: %ld\n", (long) getpid(), (long) getppid()); // valid multicast address as specified in // https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml const char *ip6_addr_str = "ff01::1"; struct in6_addr ip6_addr; int rv = inet_pton(AF_INET6, ip6_addr_str, &ip6_addr); if (rv != 1) { fprintf(stderr, "failed to parse ipv6 addr %s\n", ip6_addr_str); exit(EXIT_FAILURE); } // create a AF_INET6 SOCK_DGRAM socket const int sock_fd = create_dgram_socket(); if (sock_fd < 0) { exit(EXIT_FAILURE); } printf("created a socket, descriptor=%d\n", sock_fd); const int dest_port = 12345; // arbitrary port struct sockaddr_in6 dest_sock_addr; memset((char *) &dest_sock_addr, 0, sizeof(struct sockaddr_in6)); dest_sock_addr.sin6_addr = ip6_addr; // the target multicast address dest_sock_addr.sin6_port = htons(dest_port); dest_sock_addr.sin6_family = AF_INET6; print_addr("test will attempt to sendto() to destination host:port -> ", dest_sock_addr); const char *msg = "hello"; const size_t msg_len = strlen(msg) + 1; for (int i = 1; i <= 2; i++) { if (i != 1) { // if not the first attempt, then sleep a while before attempting to sendto() again int num_sleep_seconds = 1; printf("sleeping for %d second(s) before calling sendto()\n", num_sleep_seconds); sleep(num_sleep_seconds); } printf("%s attempt %d to sendto() %lu bytes\n", current_time(), i, msg_len); const size_t num_sent = sendto(sock_fd, msg, msg_len, 0, (struct sockaddr *) &dest_sock_addr, sizeof(dest_sock_addr)); if (num_sent == -1) { fprintf(stderr, "%s ", current_time()); perror("sendto() failed"); close(sock_fd); exit(EXIT_FAILURE); } printf("%s attempt %d of sendto() succeeded, sent %lu bytes\n", current_time(), i, num_sent); } return 0; } What this program does is, it uses the sendto() system call to send a message over a datagram socket to a (valid) multicast address. It does this twice, from the same socket to the same target address. There is a sleep() of 1 second between these two sendto() attempts. Copy that code into noroutetohost.c and compile: clang noroutetohost.c Then run: ./a.out This generates the following output: current process id:58597 parent process id: 21614 created a socket, descriptor=3 test will attempt to sendto() to destination host:port ->ff01::1:14640, addr family=30 Fri Mar 14 20:34:09 2025 attempt 1 to sendto() 6 bytes Fri Mar 14 20:34:09 2025 attempt 1 of sendto() succeeded, sent 6 bytes sleeping for 1 second(s) before calling sendto() Fri Mar 14 20:34:10 2025 attempt 2 to sendto() 6 bytes Fri Mar 14 20:34:10 2025 sendto() failed: No route to host Notice how the first call to sendto() "succeeds", even the return value (that represents the number of bytes sent) matches the number of bytes that were supposed to be sent. Then notice how the second attempt fails with a EHOSTUNREACH ("No route to host") error. Looking through the system logs, it appears that the first attempt itself has failed: 2025-03-14 20:34:09.474797 default kernel cfil_hash_entry_log:6082 <CFIL: Error: sosend_reinject() failed>: [58597 a.out] <UDP(17) out so 891be95f3a70c605 22558774573152560 22558774573152560 age 0> lport 0 fport 12345 laddr :: faddr ff01::1 hash 1003930 2025-03-14 20:34:09.474806 default kernel cfil_service_inject_queue:4466 CFIL: sosend() failed 65 (notice the time on that log messages, they match the first attempt from the program's output log) So even though the first attempt failed, it never got reported back to the application. Then after sleeping for (an arbitrary amount of) 1 second, the second call fails with the EHOSTUNREACH. The system logs don't show any error (at least not the one similar to that previous one) for the second call. If I remove that sleep() between those two attempts, then both the sendto() calls "succeed" (and return the expected value for the number of bytes sent). However, the system logs show that the first call (and very likely even the second) has failed with the exact same log message from the kernel like before. If I'm not wrong then this appears to be some kind of a bug in the "local network" restrictions. Should this be reported? I can share the captured logs but I would prefer to do it privately for this one. Another interesting thing in all this is that there's absolutely no notification to the end user (I ran this program from the Terminal) about any of the "Local Network" restrictions.
4
0
499
Mar ’25
Question Regarding peekOutboundBytes Limit in NEFilterDataProvider When Using SMB
Dear Apple Developer Technical Support, I am currently developing a macOS network filtering solution using NetworkExtension with NEFilterDataProvider. During implementation of the handleOutboundData logic, we are using the following verdict: NEFilterNewFlowVerdict.filterDataVerdict( withFilterInbound: true, peekInboundBytes: InboundPeekBytes, filterOutbound: true, peekOutboundBytes: OutboundPeekBytes ) However, we have encountered an issue when SMB traffic is involved. When SMB protocol communication occurs, the network connection occasionally becomes unresponsive or appears to stall when peekOutboundBytes is set to a large value. Through testing, we observed the following behavior: On some systems, reducing the peekOutboundBytes value allows SMB communication to proceed normally. On other systems, even relatively small values can still cause the SMB connection to stall. This behavior appears inconsistent across different macOS environments. Because of this, we would like to clarify the following: Is there a documented or recommended maximum value for peekOutboundBytes when using NEFilterNewFlowVerdict.filterDataVerdict? Are there any internal limits or constraints within NetworkExtension that could cause SMB traffic to stall when the peek buffer size is too large? Are there best practices for selecting appropriate peekInboundBytes / peekOutboundBytes values when filtering high-throughput protocols such as SMB? If necessary, we can provide additional information such as macOS version, test environment details, and logs. Thank you for your assistance. Best regards, sangho
1
0
60
1w
iOS UDP Multicast: Receiving works but sending silently fails
Hi everyone, I’m working with UDP Multicasting on iOS (iOS 15+) using Network.framework and facing a confusing issue. Setup: Multicast IP: 239.255.0.1 Port: 45454 Using NWConnectionGroup / NWMulticastGroup NSLocalNetworkUsageDescription is present in Info.plist Devices are on the same Wi-Fi network Problem: Receiving multicast packets works perfectly Sending multicast packets does NOT work No errors are thrown send() completion handler reports success stateUpdateHandler sometimes doesn’t transition to .ready No packets are actually transmitted on the network Observations: The app can receive data from other multicast senders Sending appears to be silently blocked Reinstalling the app fixes the issue This points to a Local Network permission problem If permission was denied once, iOS does not re-prompt Inbound multicast works, outbound multicast is blocked Questions: Is it expected on iOS that receiving multicast works even when sending is blocked? Is reinstalling the app the only way to recover if Local Network permission was denied? Is there any reliable runtime way to detect that outbound multicast is blocked? Is NWConnectionGroup the correct and only supported way to send multicast on iOS? Any clarification or official guidance would really help. Thanks in advance!
1
0
136
4w