General:
Forums subtopic: App & System Services > Networking
TN3151 Choosing the right networking API
Networking Overview document — Despite the fact that this is in the archive, this is still really useful.
TLS for App Developers forums post
Choosing a Network Debugging Tool documentation
WWDC 2019 Session 712 Advances in Networking, Part 1 — This explains the concept of constrained networking, which is Apple’s preferred solution to questions like How do I check whether I’m on Wi-Fi?
TN3135 Low-level networking on watchOS
TN3179 Understanding local network privacy
Adapt to changing network conditions tech talk
Understanding Also-Ran Connections forums post
Extra-ordinary Networking forums post
Foundation networking:
Forums tags: Foundation, CFNetwork
URL Loading System documentation — NSURLSession, or URLSession in Swift, is the recommended API for HTTP[S] on Apple platforms.
Moving to Fewer, Larger Transfers forums post
Testing Background Session Code forums post
Network framework:
Forums tag: Network
Network framework documentation — Network framework is the recommended API for TCP, UDP, and QUIC on Apple platforms.
Building a custom peer-to-peer protocol sample code (aka TicTacToe)
Implementing netcat with Network Framework sample code (aka nwcat)
Configuring a Wi-Fi accessory to join a network sample code
Moving from Multipeer Connectivity to Network Framework forums post
NWEndpoint History and Advice forums post
Network Extension (including Wi-Fi on iOS):
See Network Extension Resources
Wi-Fi Fundamentals
TN3111 iOS Wi-Fi API overview
Wi-Fi Aware framework documentation
Wi-Fi on macOS:
Forums tag: Core WLAN
Core WLAN framework documentation
Wi-Fi Fundamentals
Secure networking:
Forums tags: Security
Apple Platform Security support document
Preventing Insecure Network Connections documentation — This is all about App Transport Security (ATS).
WWDC 2017 Session 701 Your Apps and Evolving Network Security Standards [1] — This is generally interesting, but the section starting at 17:40 is, AFAIK, the best information from Apple about how certificate revocation works on modern systems.
Available trusted root certificates for Apple operating systems support article
Requirements for trusted certificates in iOS 13 and macOS 10.15 support article
About upcoming limits on trusted certificates support article
Apple’s Certificate Transparency policy support article
What’s new for enterprise in iOS 18 support article — This discusses new key usage requirements.
Technote 2232 HTTPS Server Trust Evaluation
Technote 2326 Creating Certificates for TLS Testing
QA1948 HTTPS and Test Servers
Miscellaneous:
More network-related forums tags: 5G, QUIC, Bonjour
On FTP forums post
Using the Multicast Networking Additional Capability forums post
Investigating Network Latency Problems forums post
WirelessInsights framework documentation
iOS Network Signal Strength forums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] This video is no longer available from Apple, but the URL should help you locate other sources of this info.
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
This issue has cropped up many times here on DevForums. Someone recently opened a DTS tech support incident about it, and I used that as an opportunity to post a definitive response here.
If you have questions or comments about this, start a new thread and tag it with Network so that I see it.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
iOS Network Signal Strength
The iOS SDK has no general-purpose API that returns Wi-Fi or cellular signal strength in real time. Given that this has been the case for more than 10 years, it’s safe to assume that it’s not an accidental omission but a deliberate design choice.
For information about the Wi-Fi APIs that are available on iOS, see TN3111 iOS Wi-Fi API overview.
Network performance
Most folks who ask about this are trying to use the signal strength to estimate network performance. This is a technique that I specifically recommend against. That’s because it produces both false positives and false negatives:
The network signal might be weak and yet your app has excellent connectivity. For example, an iOS device on stage at WWDC might have terrible WWAN and Wi-Fi signal but that doesn’t matter because it’s connected to the Ethernet.
The network signal might be strong and yet your app has very poor connectivity. For example, if you’re on a train, Wi-Fi signal might be strong in each carriage but the overall connection to the Internet is poor because it’s provided by a single over-stretched WWAN.
The only good way to determine whether connectivity is good is to run a network request and see how it performs. If you’re issuing a lot of requests, use the performance of those requests to build a running estimate of how well the network is doing. Indeed, Apple practices what we preach here: This is exactly how HTTP Live Streaming works.
Remember that network performance can change from moment to moment. The user’s train might enter or leave a tunnel, the user might step into a lift, and so on. If you build code to estimate the network performance, make sure it reacts to such changes.
Keeping all of the above in mind, iOS 26 beta has two new APIs related to this issue:
Network framework now offers a linkQuality property. See this post for my take on how to use this effectively.
The WirelessInsights framework can notify you of anticipated WWAN condition changes.
But what about this code I found on the ’net?
Over the years various folks have used various unsupported techniques to get around this limitation. If you find code on the ’net that, say, uses KVC to read undocumented properties, or grovels through system logs, or walks the view hierarchy of the status bar, don’t use it. Such techniques are unsupported and, assuming they haven’t broken yet, are likely to break in the future.
But what about Hotspot Helper?
Hotspot Helper does have an API to read Wi-Fi signal strength, namely, the signalStrength property. However, this is not a general-purpose API. Like the rest of Hotspot Helper, this is tied to the specific use case for which it was designed. This value only updates in real time for networks that your hotspot helper is managing, as indicated by the isChosenHelper property.
But what about MetricKit?
MetricKit is so cool. Amongst other things, it supports the MXCellularConditionMetric payload, which holds a summary of the cellular conditions while your app was running. However, this is not a real-time signal strength value.
But what if I’m working for a carrier?
This post is about APIs in the iOS SDK. If you’re working for a carrier, discuss your requirements with your carrier’s contact at Apple.
Revision History
2025-07-02 Updated to cover new features in the iOS 16 beta. Made other minor editorial changes.
2022-12-01 First posted.
Hi,
We're receiving data via centralManager.centralManager.scanForPeripherals, with no options or filtering (for now), and in the func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) callback, we get advertisementData for each bluetooth device found.
But, I know one of my BLE devices is sending an Eddystone TLM payload, which generally is received into the kCBAdvDataServiceData part of the advertisementData dictionary, but, it doesn't show up.
What is happening however (when comparing to other devices that do show that payload), is I've noticed the "isConnectable" part is false, and others have it true. Technically we're not "connecting" as such as we're simply reading passive advertisement data, but does that have any bearing on how CoreBluetooth decides to build up it's AdvertisementData response?
Example (with serviceData; and I know this has Eddystone TLM)
["kCBAdvDataLocalName": FSC-BP105N, "kCBAdvDataRxPrimaryPHY": 1, "kCBAdvDataServiceUUIDs": <__NSArrayM 0x300b71f80>(
FEAA,
FEF5
)
, "kCBAdvDataTimestamp": 773270526.26279, "kCBAdvDataServiceData": {
FFF0 = {length = 11, bytes = 0x36021892dc0d3015aeb164};
FEAA = {length = 14, bytes = 0x20000be680000339ffa229bbce8a};
}, "kCBAdvDataRxSecondaryPHY": 0, "kCBAdvDataIsConnectable": 1]
Vs
This also has Eddystone TLM configured
["kCBAdvDataLocalName": 100FA9FD-7000-1000, "kCBAdvDataIsConnectable": 0, "kCBAdvDataRxPrimaryPHY": 1, "kCBAdvDataRxSecondaryPHY": 0, "kCBAdvDataTimestamp": 773270918.97273]
Any insight would be great to understand if the presence of other flags drive the exposure of ServiceData or not...
I would like to test running some Thread Networking code on my MacOS machine:
import ThreadNetwork
let client = THClient()
let bIsPreferredAvailable = await client.isPreferredAvailable()
but I get some errors when trying to create an instance of the THClient class:
Client: -[THClient connectToXPCService]_block_invoke - CTCS XPC Client is interrupted.
Client: -[THClient getConnectionEntitlementValidity]_block_invoke - clientProxyWithErrorHandler Error: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.ThreadNetwork.xpc" UserInfo={NSDebugDescription=connection to service named com.apple.ThreadNetwork.xpc}
Client: -[THClient init] - XPC Client Init Failed
Invalidating XPC connection.
Client: -[THClient getConnectionEntitlementValidity]_block_invoke - clientProxyWithErrorHandler Error: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.ThreadNetwork.xpc" UserInfo={NSDebugDescription=connection to service named com.apple.ThreadNetwork.xpc}
How can I get the code to run?
From time to time the subject of NECP grows up, both here on DevForums and in DTS cases. I’ve posted about this before but I wanted to collect those tidbits into single coherent post.
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"
A Peek Behind the NECP Curtain
NECP stands for Network Extension Control Protocol. It’s a subsystem within the Apple networking stack that controls which programs have access to which network interfaces. It’s vitally important to the Network Extension subsystem, hence the name, but it’s used in many different places. Indeed, a very familiar example of its use is the Settings > Mobile Data [1] user interface on iOS.
NECP has no explicit API, although there are APIs that are offer some insight into its state. Continuing the Settings > Mobile Data example above, there is a little-known API, CTCellularData in the Core Telephony framework, that returns whether your app has access to WWAN.
Despite having no API, NECP is still relevant to developers. The Settings > Mobile Data example is one place where it affects app developers but it’s most important for Network Extension (NE) developers. A key use case for NECP is to prevent VPN loops. When starting an NE provider, the system configures the NECP policy for the NE provider’s process to prevent it from using a VPN interface. This means that you can safely open a network connection inside your VPN provider without having to worry about its traffic being accidentally routed back to you. This is why, for example, an NE packet tunnel provider can use any networking API it wants, including BSD Sockets, to run its connection without fear of creating a VPN loop [1].
One place that NECP shows up regularly is the system log. Next time you see a system log entry like this:
type: debug
time: 15:02:54.817903+0000
process: Mail
subsystem: com.apple.network
category: connection
message: nw_protocol_socket_set_necp_attributes [C723.1.1:1] setsockopt 39 SO_NECP_ATTRIBUTES
…
you’ll at least know what the necp means (-:
Finally, a lot of NECP infrastructure is in the Darwin open source. As with all things in Darwin, it’s fine to poke around and see how your favourite feature works, but do not incorporate any information you find into your product. Stuff you uncover by looking in Darwin is not considered API.
[1] Settings > Cellular Data if you speak American (-:
[2] Network Extension providers can call the createTCPConnection(to:enableTLS:tlsParameters:delegate:) method to create an NWTCPConnection [3] that doesn’t run through the tunnel. You can use that if it’s convenient but you don’t need to use it.
[3] NWTCPConnection is now deprecated, but there are non-deprecated equivalents. For the full story, see NWEndpoint History and Advice.
Revision History
2025-12-12 Replaced “macOS networking stack” with “Apple networking stack” to avoid giving the impression that this is all about macOS. Added a link to NWEndpoint History and Advice. Made other minor editorial changes.
2023-02-27 First posted.
IMPORTANT The resume rate limiter is now covered by the official documentation. See Use background sessions efficiently within Downloading files in the background. So, the following is here purely for historical perspective.
NSURLSession’s background session support on iOS includes a resume rate limiter. This limiter exists to prevent apps from abusing the background session support in order to run continuously in the background. It works as follows:
nsurlsessiond (the daemon that does all the background session work) maintains a delay value for your app.
It doubles that delay every time it resumes (or relaunches) your app.
It resets that delay to 0 when the user brings your app to the front.
It also resets the delay to 0 if the delay period elapses without it having resumed your app.
When your app creates a new task while it is in the background, the task does not start until that delay has expired.
To understand the impact of this, consider what happens when you download 10 resources. If you pass them to the background session all at once, you see something like this:
Your app creates tasks 1 through 10 in the background session.
nsurlsessiond starts working on the first few tasks.
As tasks complete, nsurlsessiond starts working on subsequent ones.
Eventually all the tasks complete and nsurlsessiond resumes your app.
Now consider what happens if you only schedule one task at a time:
Your app creates task 1.
nsurlsessiond starts working on it.
When it completes, nsurlsessiond resumes your app.
Your app creates task 2.
nsurlsessiond delays the start of task 2 a little bit.
nsurlsessiond starts working on task 2.
When it completes, nsurlsessiond resumes your app.
Your app creates task 3.
nsurlsessiond delays the start of task 3 by double the previous amount.
nsurlsessiond starts working on task 3.
When it completes, nsurlsessiond resumes your app.
Steps 8 through 11 repeat, and each time the delay doubles. Eventually the delay gets so large that it looks like your app has stopped making progress.
If you have a lot of tasks to run then you can mitigate this problem by starting tasks in batches. That is, rather than start just one task in step 1, you would start 100. This only helps up to a point. If you have thousands of tasks to run, you will eventually start seeing serious delays. In that case it’s much better to change your design to use fewer, larger transfers.
Note All of the above applies to iOS 8 and later. Things worked differently in iOS 7. There’s a post on DevForums that explains the older approach.
Finally, keep in mind that there may be other reasons for your task not starting. Specifically, if the task is flagged as discretionary (because you set the discretionary flag when creating the task’s session or because the task was started while your app was in the background), the task may be delayed for other reasons (low power, lack of Wi-Fi, and so on).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
(r. 22323366)
Transport Layer Security (TLS) is the most important security protocol on the Internet today. Most notably, TLS puts the S into HTTPS, adding security to the otherwise insecure HTTP protocol.
IMPORTANT TLS is the successor to the Secure Sockets Layer (SSL) protocol. SSL is no longer considered secure and it’s now rarely used in practice, although many folks still say SSL when they mean TLS.
TLS is a complex protocol. Much of that complexity is hidden from app developers but there are places where it’s important to understand specific details of the protocol in order to meet your requirements. This post explains the fundamentals of TLS, concentrating on the issues that most often confuse app developers.
Note The focus of this is TLS-PKI, where PKI stands for public key infrastructure. This is the standard TLS as deployed on the wider Internet. There’s another flavour of TLS, TLS-PSK, where PSK stands for pre-shared key. This has a variety of uses, but an Apple platforms we most commonly see it with local traffic, for example, to talk to a Wi-Fi based accessory. For more on how to use TLS, both TLS-PKI and TLS-PSK, in a local context, see TLS For Accessory Developers.
Server Certificates
For standard TLS to work the server must have a digital identity, that is, the combination of a certificate and the private key matching the public key embedded in that certificate. TLS Crypto Magic™ ensures that:
The client gets a copy of the server’s certificate.
The client knows that the server holds the private key matching the public key in that certificate.
In a typical TLS handshake the server passes the client a list of certificates, where item 0 is the server’s certificate (the leaf certificate), item N is (optionally) the certificate of the certificate authority that ultimately issued that certificate (the root certificate), and items 1 through N-1 are any intermediate certificates required to build a cryptographic chain of trust from 0 to N.
Note The cryptographic chain of trust is established by means of digital signatures. Certificate X in the chain is issued by certificate X+1. The owner of certificate X+1 uses their private key to digitally sign certificate X. The client verifies this signature using the public key embedded in certificate X+1. Eventually this chain terminates in a trusted anchor, that is, a certificate that the client trusts by default. Typically this anchor is a self-signed root certificate from a certificate authority.
Note Item N is optional for reasons I’ll explain below. Also, the list of intermediate certificates may be empty (in the case where the root certificate directly issued the leaf certificate) but that’s uncommon for servers in the real world.
Once the client gets the server’s certificate, it evaluates trust on that certificate to confirm that it’s talking to the right server. There are three levels of trust evaluation here:
Basic X.509 trust evaluation checks that there’s a cryptographic chain of trust from the leaf through the intermediates to a trusted root certificate. The client has a set of trusted root certificates built in (these are from well-known certificate authorities, or CAs), and a site admin can add more via a configuration profile.
This step also checks that none of the certificates have expired, and various other more technical criteria (like the Basic Constraints extension).
Note This explains why the server does not have to include the root certificate in the list of certificates it passes to the client; the client has to have the root certificate installed if trust evaluation is to succeed.
In addition, TLS trust evaluation (per RFC 2818) checks that the DNS name that you connected to matches the DNS name in the certificate. Specifically, the DNS name must be listed in the Subject Alternative Name extension.
Note The Subject Alternative Name extension can also contain IP addresses, although that’s a much less well-trodden path. Also, historically it was common to accept DNS names in the Common Name element of the Subject but that is no longer the case on Apple platforms.
App Transport Security (ATS) adds its own security checks.
Basic X.509 and TLS trust evaluation are done for all TLS connections. ATS is only done on TLS connections made by URLSession and things layered on top URLSession (like WKWebView). In many situations you can override trust evaluation; for details, see Technote 2232 HTTPS Server Trust Evaluation). Such overrides can either tighten or loosen security. For example:
You might tighten security by checking that the server certificate was issued by a specific CA. That way, if someone manages to convince a poorly-managed CA to issue them a certificate for your server, you can detect that and fail.
You might loosen security by adding your own CA’s root certificate as a trusted anchor.
IMPORTANT If you rely on loosened security you have to disable ATS. If you leave ATS enabled, it requires that the default server trust evaluation succeeds regardless of any customisations you do.
Mutual TLS
The previous section discusses server trust evaluation, which is required for all standard TLS connections. That process describes how the client decides whether to trust the server. Mutual TLS (mTLS) is the opposite of that, that is, it’s the process by which the server decides whether to trust the client.
Note mTLS is commonly called client certificate authentication. I avoid that term because of the ongoing industry-wide confusion between certificates and digital identities. While it’s true that, in mTLS, the server authenticates the client certificate, to set this up on the client you need a digital identity, not a certificate.
mTLS authentication is optional. The server must request a certificate from the client and the client may choose to supply one or not (although if the server requests a certificate and the client doesn’t supply one it’s likely that the server will then fail the connection).
At the TLS protocol level this works much like it does with the server certificate. For the client to provide this certificate it must apply a digital identity, known as the client identity, to the connection. TLS Crypto Magic™ assures the server that, if it gets a certificate from the client, the client holds the private key associated with that certificate.
Where things diverge is in trust evaluation. Trust evaluation of the client certificate is done on the server, and the server uses its own rules to decided whether to trust a specific client certificate. For example:
Some servers do basic X.509 trust evaluation and then check that the chain of trust leads to one specific root certificate; that is, a client is trusted if it holds a digital identity whose certificate was issued by a specific CA.
Some servers just check the certificate against a list of known trusted client certificates.
When the client sends its certificate to the server it actually sends a list of certificates, much as I’ve described above for the server’s certificates. In many cases the client only needs to send item 0, that is, its leaf certificate. That’s because:
The server already has the intermediate certificates required to build a chain of trust from that leaf to its root.
There’s no point sending the root, as I discussed above in the context of server trust evaluation.
However, there are no hard and fast rules here; the server does its client trust evaluation using its own internal logic, and it’s possible that this logic might require the client to present intermediates, or indeed present the root certificate even though it’s typically redundant. If you have problems with this, you’ll have to ask the folks running the server to explain its requirements.
Note If you need to send additional certificates to the server, pass them to the certificates parameter of the method you use to create your URLCredential (typically init(identity:certificates:persistence:)).
One thing that bears repeating is that trust evaluation of the client certificate is done on the server, not the client. The client doesn’t care whether the client certificate is trusted or not. Rather, it simply passes that certificate the server and it’s up to the server to make that decision.
When a server requests a certificate from the client, it may supply a list of acceptable certificate authorities [1]. Safari uses this to filter the list of client identities it presents to the user. If you are building an HTTPS server and find that Safari doesn’t show the expected client identity, make sure you have this configured correctly. If you’re building an iOS app and want to implement a filter like Safari’s, get this list using:
The distinguishedNames property, if you’re using URLSession
The sec_protocol_metadata_access_distinguished_names routine, if you’re using Network framework
[1] See the certificate_authorities field in Section 7.4.4 of RFC 5246, and equivalent features in other TLS versions.
Self-Signed Certificates
Self-signed certificates are an ongoing source of problems with TLS. There’s only one unequivocally correct place to use a self-signed certificate: the trusted anchor provided by a certificate authority.
One place where a self-signed certificate might make sense is in a local environment, that is, securing a connection between peers without any centralised infrastructure. However, depending on the specific circumstances there may be a better option. TLS For Accessory Developers discusses this topic in detail.
Finally, it’s common for folks to use self-signed certificates for testing. I’m not a fan of that approach. Rather, I recommend the approach described in QA1948 HTTPS and Test Servers. For advice on how to set that up using just your Mac, see TN2326 Creating Certificates for TLS Testing.
TLS Standards
RFC 6101 The Secure Sockets Layer (SSL) Protocol Version 3.0 (historic)
RFC 2246 The TLS Protocol Version 1.0
RFC 4346 The Transport Layer Security (TLS) Protocol Version 1.1
RFC 5246 The Transport Layer Security (TLS) Protocol Version 1.2
RFC 8446 The Transport Layer Security (TLS) Protocol Version 1.3
RFC 4347 Datagram Transport Layer Security
RFC 6347 Datagram Transport Layer Security Version 1.2
RFC 9147 The Datagram Transport Layer Security (DTLS) Protocol Version 1.3
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Revision History:
2025-11-21 Clearly defined the terms TLS-PKI and TLS-PSK.
2024-03-19 Adopted the term mutual TLS in preference to client certificate authentication throughout, because the latter feeds into the ongoing certificate versus digital identity confusion. Defined the term client identity. Added the Self-Signed Certificates section. Made other minor editorial changes.
2023-02-28 Added an explanation mTLS acceptable certificate authorities.
2022-12-02 Added links to the DTLS RFCs.
2022-08-24 Added links to the TLS RFCs. Made other minor editorial changes.
2022-06-03 Added a link to TLS For Accessory Developers.
2021-02-26 Fixed the formatting. Clarified that ATS only applies to URLSession. Minor editorial changes.
2020-04-17 Updated the discussion of Subject Alternative Name to account for changes in the 2019 OS releases. Minor editorial updates.
2018-10-29 Minor editorial updates.
2016-11-11 First posted.
Hi there,
We’re developing a companion app for a smart home product that communicates over the user’s local network.
To provision the device, it initially creates its own Wi-Fi network. The user joins this temporary network and enters their home Wi-Fi credentials via our app. The app then sends those credentials directly to the device, which stores them and connects to the local network for normal operation.
We’re using AccessorySetupKit to discover nearby devices (via SSID prefix) and NEHotspotManager to join the accessory’s Wi-Fi network once the user selects it. This workflow works well in general.
However, we’ve encountered a problem: if the user factory-resets the accessory, or needs to restart setup (for example, after entering the wrong Wi-Fi password), the device no longer appears in the accessory picker.
In iOS 18, we were able to work around this by calling removeAccessory() after the device is selected. This forces the picker to always display the accessory again. But in iOS 26, a new confirmation dialog now appears when calling removeAccessory(), which confuses users during setup.
We’re looking for a cleaner way to handle this scenario — ideally a way to make the accessory rediscoverable without prompting the user to confirm removal.
Thanks for your time and guidance.
Most apps perform ordinary network operations, like fetching an HTTP resource with URLSession and opening a TCP connection to a mail server with Network framework. These operations are not without their challenges, but they’re the well-trodden path.
If your app performs ordinary networking, see TN3151 Choosing the right networking API for recommendations as to where to start.
Some apps have extra-ordinary networking requirements. For example, apps that:
Help the user configure a Wi-Fi accessory
Require a connection to run over a specific interface
Listen for incoming connections
Building such an app is tricky because:
Networking is hard in general.
Apple devices support very dynamic networking, and your app has to work well in whatever environment it’s running in.
Documentation for the APIs you need is tucked away in man pages and doc comments.
In many cases you have to assemble these APIs in creative ways.
If you’re developing an app with extra-ordinary networking requirements, this post is for you.
Note If you have questions or comments about any of the topics discussed here, put them in a new thread here on DevForums. Make sure I see it by putting it in the App & System Services > Networking area. And feel free to add tags appropriate to the specific technology you’re using, like Foundation, CFNetwork, Network, or Network Extension.
Links, Links, and More Links
Each topic is covered in a separate post:
The iOS Wi-Fi Lifecycle describes how iOS joins and leaves Wi-Fi networks. Understanding this is especially important if you’re building an app that works with a Wi-Fi accessory.
Network Interface Concepts explains how Apple platforms manage network interfaces. If you’ve got this far, you definitely want to read this.
Network Interface Techniques offers a high-level overview of some of the more common techniques you need when working with network interfaces.
Network Interface APIs describes APIs and core techniques for working with network interfaces. It’s referenced by many other posts.
Running an HTTP Request over WWAN explains why most apps should not force an HTTP request to run over WWAN, what they should do instead, and what to do if you really need that behaviour.
If you’re building an iOS app with an embedded network server, see Showing Connection Information in an iOS Server for details on how to get the information to show to your user so they can connect to your server.
Many folks run into trouble when they try to find the device’s IP address, or other seemingly simple things, like the name of the Wi-Fi interface. Don’t Try to Get the Device’s IP Address explains why these problems are hard, and offers alternative approaches that function correctly in all network environments.
Similarly, folks also run into trouble when trying to get the host name. On Host Names explains why that’s more complex than you might think.
If you’re working with broadcasts or multicasts, see Broadcasts and Multicasts, Hints and Tips.
If you’re building an app that works with a Wi-Fi accessory, see Working with a Wi-Fi Accessory.
If you’re trying to gather network interface statistics, see Network Interface Statistics.
There are also some posts that are not part of this series but likely to be of interest if you’re working in this space:
TN3179 Understanding local network privacy discusses the local network privacy feature.
Calling BSD Sockets from Swift does what it says on the tin, that is, explains how to call BSD Sockets from Swift. When doing weird things with the network, you often find yourself having to use BSD Sockets, and that API is not easy to call from Swift. The code therein is primarily for the benefit of test projects, oh, and DevForums posts like these.
TN3111 iOS Wi-Fi API overview is a critical resource if you’re doing Wi-Fi specific stuff on iOS.
TLS For Accessory Developers tackles the tricky topic of how to communicate securely with a network-based accessory.
A Peek Behind the NECP Curtain discusses NECP, a subsystem that control which programs have access to which network interfaces.
Networking Resources has links to many other useful resources.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Revision History
2025-07-31 Added a link to A Peek Behind the NECP Curtain.
2025-03-28 Added a link to On Host Names.
2025-01-16 Added a link to Broadcasts and Multicasts, Hints and Tips. Updated the local network privacy link to point to TN3179. Made other minor editorial changes.
2024-04-30 Added a link to Network Interface Statistics.
2023-09-14 Added a link to TLS For Accessory Developers.
2023-07-23 First posted.
Hello,
Our app uses Network Extension / Packet Tunnel Provider to establish VPN connections on macOS and iOS.
We have observed that after creating a utun device and adding any IPv4 routes (NEPacketTunnelNetworkSettings.IPv4Settings), the OS automatically adds several host routes via utun to services such as Akamai, Apple Push, etc. These routes appear to correspond to TCP flows that were active at the moment the VPN connection was established. When a particular TCP flow ends, the corresponding host route is deleted. We understand this is likely intended to avoid breaking existing TCP connections.
However, we find the behavior of migrating existing TCP flows to the new utun interface simply because any IPv4 route is added somewhat questionable. This approach would make sense in a "full-tunnel" scenario — for example, when all IPv4 traffic (e.g., 0.0.0.0/0) is routed through the tunnel — but not necessarily in a "split-tunnel" configuration where only specific IPv4 routes are added.
Is there any way to control or influence this behavior?
Would it be possible for FlowDivert to differentiate between full-tunnel and split-tunnel cases, and only preserve existing TCP flows via utun in the full-tunnel scenario?
Thank you.
My external device can generate a fixed Wi-Fi network. When I connect to this Wi-Fi using my iPhone 17 Pro Max (iOS version 26.0.1), and my app tries to establish a connection using the following method, this method returns -1
int connect(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(connect);
However, when I use other phones, such as iPhone 12, iPhone 8, iPhone 11, etc., to connect to this external device, the above method always returns successfully, with the parameters passed to the method remaining the same.
I also tried resetting the network settings on the iPhone 17 Pro Max (iOS version 26.0.1), but it still cannot establish a connection.
Topic:
App & System Services
SubTopic:
Networking
How often do we see control filter start and stop?
I read somewhere that data filter is long lived and control Filter is short lived.
When does the operating system kills the control filter process?
Hey there
Are there any recommendations or guidance for apps on alternatives to certificate pinning to secure their device network traffic?
I want to move away from the overhead and risk associated with rotating certificates when using leaf pinning.
However, I also don't want people to be able to perform a MITM attack easily using something like Charles Proxy with a self‑signed certificate added to the trust store.
My understanding is that an app cannot distinguish between user‑trusted certificates and system‑trusted certificates in the trust store, so it cannot block traffic that uses user‑trusted certificates.
Topic:
App & System Services
SubTopic:
Networking
For important background information, read Extra-ordinary Networking before reading this.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Working with a Wi-Fi Accessory
Building an app that works with a Wi-Fi accessory presents specific challenges. This post discusses those challenges and some recommendations for how to address them.
Note While my focus here is iOS, much of the info in this post applies to all Apple platforms.
IMPORTANT iOS 18 introduced AccessorySetupKit, a framework to simplify the discovery and configuration of an accessory. I’m not fully up to speed on that framework myself, but I encourage you to watch WWDC 2024 Session 10203 Meet AccessorySetupKit and read the framework documentation.
IMPORTANT iOS 26 introduced WiFiAware, a framework for setting up communication with Wi-Fi Aware accessories. Wi-Fi Aware is an industry standard to securely discover, pair, and communicate with nearby devices. This is especially useful for stand-alone accessories (defined below). For more on this framework, watch WWDC 2025 Session 228 Supercharge device connectivity with Wi-Fi Aware and read the framework documentation. For information on how to create a Wi-Fi Aware accessory that works with iPhone, go to Developer > Accessories, download Accessory Design Guidelines for Apple Devices, and review the Wi-Fi Aware chapter.
Accessory Categories
I classify Wi-Fi accessories into three different categories.
A bound accessory is ultimately intended to join the user’s Wi-Fi network. It may publish its own Wi-Fi network during the setup process, but the goal of that process is to get the accessory on to the existing network. Once that’s done, your app interacts with the accessory using ordinary networking APIs.
An example of a bound accessory is a Wi-Fi capable printer.
A stand-alone accessory publishes a Wi-Fi network at all times. An iOS device joins that network so that your app can interact with it. The accessory never provides access to the wider Internet.
An example of a stand-alone accessory is a video camera that users take with them into the field. You might want to write an app that joins the camera’s network and downloads footage from it.
A gateway accessory is one that publishes a Wi-Fi network that provides access to the wider Internet. Your app might need to interact with the accessory during the setup process, but after that it’s useful as is.
An example of this is a Wi-Fi to WWAN gateway.
Not all accessories fall neatly into these categories. Indeed, some accessories might fit into multiple categories, or transition between categories. Still, I’ve found these categories to be helpful when discussing various accessory integration challenges.
Do You Control the Firmware?
The key question here is Do you control the accessory’s firmware? If so, you have a bunch of extra options that will make your life easier. If not, you have to adapt to whatever the accessory’s current firmware does.
Simple Improvements
If you do control the firmware, I strongly encourage you to:
Support IPv6
Implement Bonjour [1]
These two things are quite easy to do — most embedded platforms support them directly, so it’s just a question of turning them on — and they will make your life significantly easier:
Link-local addresses are intrinsic to IPv6, and IPv6 is intrinsic to Apple platforms. If your accessory supports IPv6, you’ll always be able to communicate with it, regardless of how messed up the IPv4 configuration gets.
Similarly, if you support Bonjour, you’ll always be able to find your accessory on the network.
[1] Bonjour is an Apple term for three Internet standards:
RFC 3927 Dynamic Configuration of IPv4 Link-Local Addresses
RFC 6762 Multicast DNS
RFC 6763 DNS-Based Service Discovery
WAC
For a bound accessory, support Wireless Accessory Configuration (WAC). This is a relatively big ask — supporting WAC requires you to join the MFi Program — but it has some huge benefits:
You don’t need to write an app to configure your accessory. The user will be able to do it directly from Settings.
If you do write an app, you can use the EAWiFiUnconfiguredAccessoryBrowser class to simplify your configuration process.
HomeKit
For a bound accessory that works in the user’s home, consider supporting HomeKit. This yields the same onboarding benefits as WAC, and many other benefits as well. Also, you can get started with the HomeKit Open Source Accessory Development Kit (ADK).
Bluetooth LE
If your accessory supports Bluetooth LE, think about how you can use that to improve your app’s user experience. For an example of that, see SSID Scanning, below.
Claiming the Default Route, Or Not?
If your accessory publishes a Wi-Fi network, a key design decision is whether to stand up enough infrastructure for an iOS device to make it the default route.
IMPORTANT To learn more about how iOS makes the decision to switch the default route, see The iOS Wi-Fi Lifecycle and Network Interface Concepts.
This decision has significant implications. If the accessory’s network becomes the default route, most network connections from iOS will be routed to your accessory. If it doesn’t provide a path to the wider Internet, those connections will fail. That includes connections made by your own app.
Note It’s possible to get around this by forcing your network connections to run over WWAN. See Binding to an Interface in Network Interface Techniques and Running an HTTP Request over WWAN. Of course, this only works if the user has WWAN. It won’t help most iPad users, for example.
OTOH, if your accessory’s network doesn’t become the default route, you’ll see other issues. iOS will not auto-join such a network so, if the user locks their device, they’ll have to manually join the network again.
In my experience a lot of accessories choose to become the default route in situations where they shouldn’t. For example, a bound accessory is never going to be able to provide a path to the wider Internet so it probably shouldn’t become the default route. However, there are cases where it absolutely makes sense, the most obvious being that of a gateway accessory.
Acting as a Captive Network, or Not?
If your accessory becomes the default route you must then decide whether to act like a captive network or not.
IMPORTANT To learn more about how iOS determines whether a network is captive, see The iOS Wi-Fi Lifecycle.
For bound and stand-alone accessories, becoming a captive network is generally a bad idea. When the user joins your network, the captive network UI comes up and they have to successfully complete it to stay on the network. If they cancel out, iOS will leave the network. That makes it hard for the user to run your app while their iOS device is on your accessory’s network.
In contrast, it’s more reasonable for a gateway accessory to act as a captive network.
SSID Scanning
Many developers think that TN3111 iOS Wi-Fi API overview is lying when it says:
iOS does not have a general-purpose API for Wi-Fi scanning
It is not.
Many developers think that the Hotspot Helper API is a panacea that will fix all their Wi-Fi accessory integration issues, if only they could get the entitlement to use it.
It will not.
Note this comment in the official docs:
NEHotspotHelper is only useful for hotspot integration. There are both technical and business restrictions that prevent it from being used for other tasks, such as accessory integration or Wi-Fi based location.
Even if you had the entitlement you would run into these technical restrictions. The API was specifically designed to support hotspot navigation — in this context hotspots are “Wi-Fi networks where the user must interact with the network to gain access to the wider Internet” — and it does not give you access to on-demand real-time Wi-Fi scan results.
Many developers look at another developer’s app, see that it’s displaying real-time Wi-Fi scan results, and think there’s some special deal with Apple that’ll make that work.
There is not.
In reality, Wi-Fi accessory developers have come up with a variety of creative approaches for this, including:
If you have a bound accessory, you might add WAC support, which makes this whole issue go away.
In many cases, you can avoid the need for Wi-Fi scan results by adopting AccessorySetupKit.
You might build your accessory with a barcode containing the info required to join its network, and scan that from your app. This is the premise behind the Configuring a Wi-Fi Accessory to Join the User’s Network sample code.
You might configure all your accessories to have a common SSID prefix, and then take advantage of the prefix support in NEHotspotConfigurationManager. See Programmatically Joining a Network, below.
You might have your app talk to your accessory via some other means, like Bluetooth LE, and have the accessory scan for Wi-Fi networks and return the results.
Programmatically Joining a Network
Network Extension framework has an API, NEHotspotConfigurationManager, to programmatically join a network, either temporarily or as a known network that supports auto-join. For the details, see Wi-Fi Configuration.
One feature that’s particularly useful is it’s prefix support, allowing you to create a configuration that’ll join any network with a specific prefix. See the init(ssidPrefix:) initialiser for the details.
For examples of how to use this API, see:
Configuring a Wi-Fi Accessory to Join the User’s Network — It shows all the steps for one approach for getting a non-WAC bound accessory on to the user’s network.
NEHotspotConfiguration Sample — Use this to explore the API in general.
Secure Communication
Users expect all network communication to be done securely. For some ideas on how to set up a secure connection to an accessory, see TLS For Accessory Developers.
Revision History
2025-11-05 Added a link to the Accessory Design Guidelines for Apple Devices.
2025-06-19 Added a preliminary discussion of Wi-Fi Aware.
2024-09-12 Improved the discussion of AccessorySetupKit.
2024-07-16 Added a preliminary discussion of AccessorySetupKit.
2023-10-11 Added the HomeKit section. Fixed the link in Secure Communication to point to TLS For Accessory Developers.
2023-07-23 First posted.
I see a lot of folks spend a lot of time trying to get Multipeer Connectivity to work for them. My experience is that the final result is often unsatisfactory. Instead, my medium-to-long term recommendation is to use Network framework instead. This post explains how you might move from Multipeer Connectivity to Network framework.
If you have questions or comments, put them in a new thread. Place it in the App & System Services > Networking topic area and tag it with Multipeer Connectivity and Network framework.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Moving from Multipeer Connectivity to Network Framework
Multipeer Connectivity has a number of drawbacks:
It has an opinionated networking model, where every participant in a session is a symmetric peer. Many apps work better with the traditional client/server model.
It offers good latency but poor throughput.
It doesn’t support flow control, aka back pressure, which severely constrains its utility for general-purpose networking.
It includes a number of UI components that are effectively obsolete.
It hasn’t evolved in recent years. For example, it relies on NSStream, which has been scheduled for deprecation as far as networking is concerned.
It always enables peer-to-peer Wi-Fi, something that’s not required for many apps and can impact the performance of the network (see Enable peer-to-peer Wi-Fi, below, for more about this).
Its security model requires the use of PKI — public key infrastructure, that is, digital identities and certificates — which are tricky to deploy in a peer-to-peer environment.
It has some gnarly bugs.
IMPORTANT Many folks use Multipeer Connectivity because they think it’s the only way to use peer-to-peer Wi-Fi. That’s not the case. Network framework has opt-in peer-to-peer Wi-Fi support. See Enable peer-to-peer Wi-Fi, below.
If Multipeer Connectivity is not working well for you, consider moving to Network framework. This post explains how to do that in 13 easy steps (-:
Plan for security
Select a network architecture
Create a peer identifier
Choose a protocol to match your send mode
Discover peers
Design for privacy
Configure your connections
Manage a listener
Manage a connection
Send and receive reliable messages
Send and receive best effort messages
Start a stream
Send a resource
Finally, at the end of the post you’ll find two appendices:
Final notes contains some general hints and tips.
Symbol cross reference maps symbols in the Multipeer Connectivity framework to sections of this post. Consult it if you’re not sure where to start with a specific Multipeer Connectivity construct.
Plan for security
The first thing you need to think about is security. Multipeer Connectivity offers three security models, expressed as choices in the MCEncryptionPreference enum:
.none for no security
.optional for optional security
.required for required security
For required security each peer must have a digital identity.
Optional security is largely pointless. It’s more complex than no security but doesn’t yield any benefits. So, in this post we’ll focus on the no security and required security models.
Your security choice affects the network protocols you can use:
QUIC is always secure.
WebSocket, TCP, and UDP can be used with and without TLS security.
QUIC security only supports PKI. TLS security supports both TLS-PKI and pre-shared key (PSK). You might find that TLS-PSK is easier to deploy in a peer-to-peer environment.
To configure the security of the QUIC protocol:
func quicParameters() -> NWParameters {
let quic = NWProtocolQUIC.Options(alpn: ["MyAPLN"])
let sec = quic.securityProtocolOptions
… configure `sec` here …
return NWParameters(quic: quic)
}
To enable TLS over TCP:
func tlsOverTCPParameters() -> NWParameters {
let tcp = NWProtocolTCP.Options()
let tls = NWProtocolTLS.Options()
let sec = tls.securityProtocolOptions
… configure `sec` here …
return NWParameters(tls: tls, tcp: tcp)
}
To enable TLS over UDP, also known as DTLS:
func dtlsOverUDPParameters() -> NWParameters {
let udp = NWProtocolUDP.Options()
let dtls = NWProtocolTLS.Options()
let sec = dtls.securityProtocolOptions
… configure `sec` here …
return NWParameters(dtls: dtls, udp: udp)
}
To configure TLS with a local digital identity and custom server trust evaluation:
func configureTLSPKI(sec: sec_protocol_options_t, identity: SecIdentity) {
let secIdentity = sec_identity_create(identity)!
sec_protocol_options_set_local_identity(sec, secIdentity)
if disableServerTrustEvaluation {
sec_protocol_options_set_verify_block(sec, { metadata, secTrust, completionHandler in
let trust = sec_trust_copy_ref(secTrust).takeRetainedValue()
… evaluate `trust` here …
completionHandler(true)
}, .main)
}
}
To configure TLS with a pre-shared key:
func configureTLSPSK(sec: sec_protocol_options_t, identity: Data, key: Data) {
let identityDD = identity.withUnsafeBytes { DispatchData(bytes: $0) }
let keyDD = identity.withUnsafeBytes { DispatchData(bytes: $0) }
sec_protocol_options_add_pre_shared_key(
sec,
keyDD as dispatch_data_t,
identityDD as dispatch_data_t
)
sec_protocol_options_append_tls_ciphersuite(
sec,
tls_ciphersuite_t(rawValue: TLS_PSK_WITH_AES_128_GCM_SHA256)!
)
}
Select a network architecture
Multipeer Connectivity uses a star network architecture. All peers are equal, and every peer is effectively connected to every peer. Many apps work better with the client/server model, where one peer acts on the server and all the others are clients. Network framework supports both models.
To implement a client/server network architecture with Network framework:
Designate one peer as the server and all the others as clients.
On the server, use NWListener to listen for incoming connections.
On each client, use NWConnection to made an outgoing connection to the server.
To implement a star network architecture with Network framework:
On each peer, start a listener.
And also start a connection to each of the other peers.
This is likely to generate a lot of redundant connections, as peer A connects to peer B and vice versa. You’ll need to a way to deduplicate those connections, which is the subject of the next section.
IMPORTANT While the star network architecture is more likely to create redundant connections, the client/server network architecture can generate redundant connections as well. The advice in the next section applies to both architectures.
Create a peer identifier
Multipeer Connectivity uses MCPeerID to uniquely identify each peer. There’s nothing particularly magic about MCPeerID; it’s effectively a wrapper around a large random number.
To identify each peer in Network framework, generate your own large random number. One good choice for a peer identifier is a locally generated UUID, created using the system UUID type.
Some Multipeer Connectivity apps persist their local MCPeerID value, taking advantage of its NSSecureCoding support. You can do the same with a UUID, using either its string representation or its Codable support.
IMPORTANT Before you decide to persist a peer identifier, think about the privacy implications. See Design for privacy below.
Avoid having multiple connections between peers; that’s both wasteful and potentially confusing. Use your peer identifier to deduplicate connections.
Deduplicating connections in a client/server network architecture is easy. Have each client check in with the server with its peer identifier. If the server already has a connection for that identifier, it can either close the old connection and keep the new connection, or vice versa.
Deduplicating connections in a star network architecture is a bit trickier. One option is to have each peer send its peer identifier to the other peer and then the peer with the ‘best’ identifier wins. For example, imagine that peer A makes an outgoing connection to peer B while peer B is simultaneously making an outgoing connection to peer A. When a peer receives a peer identifier from a connection, it checks for a duplicate. If it finds one, it compares the peer identifiers and then chooses a connection to drop based on that comparison:
if local peer identifier > remote peer identifier then
drop outgoing connection
else
drop incoming connection
end if
So, peer A drops its incoming connection and peer B drops its outgoing connection. Et voilà!
Choose a protocol to match your send mode
Multipeer Connectivity offers two send modes, expressed as choices in the MCSessionSendDataMode enum:
.reliable for reliable messages
.unreliable for best effort messages
Best effort is useful when sending latency-sensitive data, that is, data where retransmission is pointless because, by the retransmission arrives, the data will no longer be relevant. This is common in audio and video applications.
In Network framework, the send mode is set by the connection’s protocol:
A specific QUIC connection is either reliable or best effort.
WebSocket and TCP are reliable.
UDP is best effort.
Start with a reliable connection. In many cases you can stop there, because you never need a best effort connection.
If you’re not sure which reliable protocol to use, choose WebSocket. It has key advantages over other protocols:
It supports both security models: none and required. Moreover, its required security model supports both TLS-PKI and TLS PSK. In contrast, QUIC only supports the required security model, and within that model it only supports TLS-PKI.
It allows you to send messages over the connection. In contrast, TCP works in terms of bytes, meaning that you have to add your own framing.
If you need a best effort connection, get started with a reliable connection and use that connection to set up a parallel best effort connection. For example, you might have an exchange like this:
Peer A uses its reliable WebSocket connection to peer B to send a request for a parallel best effort UDP connection.
Peer B receives that, opens a UDP listener, and sends the UDP listener’s port number back to peer A.
Peer A opens its parallel UDP connection to that port on peer B.
Note For step 3, get peer B’s IP address from the currentPath property of the reliable WebSocket connection.
If you’re not sure which best effort protocol to use, use UDP. While it is possible to use QUIC in datagram mode, it has the same security complexities as QUIC in reliable mode.
Discover peers
Multipeer Connectivity has a types for advertising a peer’s session (MCAdvertiserAssistant) and a type for browsering for peer (MCNearbyServiceBrowser).
In Network framework, configure the listener to advertise its service by setting the service property of NWListener:
let listener: NWListener = …
listener.service = .init(type: "_example._tcp")
listener.serviceRegistrationUpdateHandler = { change in
switch change {
case .add(let endpoint):
… update UI for the added listener endpoint …
break
case .remove(let endpoint):
… update UI for the removed listener endpoint …
break
@unknown default:
break
}
}
listener.stateUpdateHandler = … handle state changes …
listener.newConnectionHandler = … handle the new connection …
listener.start(queue: .main)
This example also shows how to use the serviceRegistrationUpdateHandler to update your UI to reflect changes in the listener.
Note This example uses a service type of _example._tcp. See About service types, below, for more details on that.
To browse for services, use NWBrowser:
let browser = NWBrowser(for: .bonjour(type: "_example._tcp", domain: nil), using: .tcp)
browser.browseResultsChangedHandler = { latestResults, _ in
… update UI to show the latest results …
}
browser.stateUpdateHandler = … handle state changes …
browser.start(queue: .main)
This yields NWEndpoint values for each peer that it discovers. To connect to a given peer, create an NWConnection with that endpoint.
About service types
The examples in this post use _example._tcp for the service type. The first part, _example, is directly analogous to the serviceType value you supply when creating MCAdvertiserAssistant and MCNearbyServiceBrowser objects. The second part is either _tcp or _udp depending on the underlying transport protocol. For TCP and WebSocket, use _tcp. For UDP and QUIC, use _udp.
Service types are described in RFC 6335. If you deploy an app that uses a new service type, register that service type with IANA.
Discovery UI
Multipeer Connectivity also has UI components for advertising (MCNearbyServiceAdvertiser) and browsing (MCBrowserViewController). There’s no direct equivalent to this in Network framework. Instead, use your preferred UI framework to create a UI that best suits your requirements.
Note If you’re targeting Apple TV, check out the DeviceDiscoveryUI framework.
Discovery TXT records
The Bonjour service discovery protocol used by Network framework supports TXT records. Using these, a listener can associate metadata with its service and a browser can get that metadata for each discovered service.
To advertise a TXT record with your listener, include it it the service property value:
let listener: NWListener = …
let peerID: UUID = …
var txtRecord = NWTXTRecord()
txtRecord["peerID"] = peerID.uuidString
listener.service = .init(type: "_example._tcp", txtRecord: txtRecord.data)
To browse for services and their associated TXT records, use the .bonjourWithTXTRecord(…) descriptor:
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_example._tcp", domain: nil), using: .tcp)
browser.browseResultsChangedHandler = { latestResults, _ in
for result in latestResults {
guard
case .bonjour(let txtRecord) = result.metadata,
let peerID = txtRecord["peerID"]
else { continue }
// … examine `result` and `peerID` …
_ = peerID
}
}
This example includes the peer identifier in the TXT record with the goal of reducing the number of duplicate connections, but that’s just one potential use for TXT records.
Design for privacy
This section lists some privacy topics to consider as you implement your app. Obviously this isn’t an exhaustive list. For general advice on this topic, see Protecting the User’s Privacy.
There can be no privacy without security. If you didn’t opt in to security with Multipeer Connectivity because you didn’t want to deal with PKI, consider the TLS-PSK options offered by Network framework. For more on this topic, see Plan for security.
When you advertise a service, the default behaviour is to use the user-assigned device name as the service name. To override that, create a service with a custom name:
let listener: NWListener = …
let name: String = …
listener.service = .init(name: name, type: "_example._tcp")
It’s not uncommon for folks to use the peer identifier as the service name. Whether that’s a good option depends on the user experience of your product:
Some products present a list of remote peers and have the user choose from that list. In that case it’s best to stick with the user-assigned device name, because that’s what the user will recognise.
Some products automatically connect to services as they discover them. In that case it’s fine to use the peer identifier as the service name, because the user won’t see it anyway.
If you stick with the user-assigned device name, consider advertising the peer identifier in your TXT record. See Discovery TXT records.
IMPORTANT Using a peer identifier in your service name or TXT record is a heuristic to reduce the number of duplicate connections. Don’t rely on it for correctness. Rather, deduplicate connections using the process described in Create a peer identifier.
There are good reasons to persist your peer identifier, but doing so isn’t great for privacy. Persisting the identifier allows for tracking of your service over time and between networks. Consider whether you need a persistent peer identifier at all. If you do, consider whether it makes sense to rotate it over time.
A persistent peer identifier is especially worrying if you use it as your service name or put it in your TXT record.
Configure your connections
Multipeer Connectivity’s symmetric architecture means that it uses a single type, MCSession, to manage the connections to all peers.
In Network framework, that role is fulfilled by two types:
NWListener to listen for incoming connections.
NWConnection to make outgoing connections.
Both types require you to supply an NWParameters value that specifies the network protocol and options to use. In addition, when creating an NWConnection you pass in an NWEndpoint to tell it the service to connect to. For example, here’s how to configure a very simple listener for TCP:
let parameters = NWParameters.tcp
let listener = try NWListener(using: parameters)
… continue setting up the listener …
And here’s how you might configure an outgoing TCP connection:
let parameters = NWParameters.tcp
let endpoint = NWEndpoint.hostPort(host: "example.com", port: 80)
let connection = NWConnection.init(to: endpoint, using: parameters)
… continue setting up the connection …
NWParameters has properties to control exactly what protocol to use and what options to use with those protocols.
To work with QUIC connections, use code like that shown in the quicParameters() example from the Security section earlier in this post.
To work with TCP connections, use the NWParameters.tcp property as shown above.
To enable TLS on your TCP connections, use code like that shown in the tlsOverTCPParameters() example from the Security section earlier in this post.
To work with WebSocket connections, insert it into the application protocols array:
let parameters = NWParameters.tcp
let ws = NWProtocolWebSocket.Options(.version13)
parameters.defaultProtocolStack.applicationProtocols.insert(ws, at: 0)
To enable TLS on your WebSocket connections, use code like that shown in the tlsOverTCPParameters() example to create your base parameters and then add the WebSocket application protocol to that.
To work with UDP connections, use the NWParameters.udp property:
let parameters = NWParameters.udp
To enable TLS on your UDP connections, use code like that shown in the dtlsOverUDPParameters() example from the Security section earlier in this post.
Enable peer-to-peer Wi-Fi
By default, Network framework doesn’t use peer-to-peer Wi-Fi. To enable that, set the includePeerToPeer property on the parameters used to create your listener and connection objects.
parameters.includePeerToPeer = true
IMPORTANT Enabling peer-to-peer Wi-Fi can impact the performance of the network. Only opt into it if it’s a significant benefit to your app.
If you enable peer-to-peer Wi-Fi, it’s critical to stop network operations as soon as you’re done with them. For example, if you’re browsing for services with peer-to-peer Wi-Fi enabled and the user picks a service, stop the browse operation immediately. Otherwise, the ongoing browse operation might affect the performance of your connection.
Manage a listener
In Network framework, use NWListener to listen for incoming connections:
let parameters: NWParameters = .tcp
… configure parameters …
let listener = try NWListener(using: parameters)
listener.service = … service details …
listener.serviceRegistrationUpdateHandler = … handle service registration changes …
listener.stateUpdateHandler = { newState in
… handle state changes …
}
listener.newConnectionHandler = { newConnection in
… handle the new connection …
}
listener.start(queue: .main)
For details on how to set up parameters, see Configure your connections. For details on how to set up up service and serviceRegistrationUpdateHandler, see Discover peers.
Network framework calls your state update handler when the listener changes state:
let listener: NWListener = …
listener.stateUpdateHandler = { newState in
switch newState {
case .setup:
// The listener has not yet started.
…
case .waiting(let error):
// The listener tried to start and failed. It might recover in the
// future.
…
case .ready:
// The listener is running.
…
case .failed(let error):
// The listener tried to start and failed irrecoverably.
…
case .cancelled:
// The listener was cancelled by you.
…
@unknown default:
break
}
}
Network framework calls your new connection handler when a client connects to it:
var connections: [NWConnection] = []
let listener: NWListener = listener
listener.newConnectionHandler = { newConnection in
… configure the new connection …
newConnection.start(queue: .main)
connections.append(newConnection)
}
IMPORTANT Don’t forget to call start(queue:) on your connections.
In Multipeer Connectivity, the session (MCSession) keeps track of all the peers you’re communicating with. With Network framework, that responsibility falls on you. This example uses a simple connections array for that purpose. In your app you may or may not need a more complex data structure. For example:
In the client/server network architecture, the client only needs to manage the connections to a single peer, the server.
On the other hand, the server must managed the connections to all client peers.
In the star network architecture, every peer must maintain a listener and connections to each of the other peers.
Understand UDP flows
Network framework handles UDP using the same NWListener and NWConnection types as it uses for TCP. However, the underlying UDP protocol is not implemented in terms of listeners and connections. To resolve this, Network framework works in terms of UDP flows. A UDP flow is defined as a bidirectional sequence of UDP datagrams with the same 4 tuple (local IP address, local port, remote IP address, and remote port). In Network framework:
Each NWConnection object manages a single UDP flow.
If an NWListener receives a UDP datagram whose 4 tuple doesn’t match any known NWConnection, it creates a new NWConnection.
Manage a connection
In Network framework, use NWConnection to start an outgoing connection:
var connections: [NWConnection] = []
let parameters: NWParameters = …
let endpoint: NWEndpoint = …
let connection = NWConnection(to: endpoint, using: parameters)
connection.stateUpdateHandler = … handle state changes …
connection.viabilityUpdateHandler = … handle viability changes …
connection.pathUpdateHandler = … handle path changes …
connection.betterPathUpdateHandler = … handle better path notifications …
connection.start(queue: .main)
connections.append(connection)
As in the listener case, you’re responsible for keeping track of this connection.
Each connection supports four different handlers. Of these, the state and viability update handlers are the most important. For information about the path update and better path handlers, see the NWConnection documentation.
Network framework calls your state update handler when the connection changes state:
let connection: NWConnection = …
connection.stateUpdateHandler = { newState in
switch newState {
case .setup:
// The connection has not yet started.
…
case .preparing:
// The connection is starting.
…
case .waiting(let error):
// The connection tried to start and failed. It might recover in the
// future.
…
case .ready:
// The connection is running.
…
case .failed(let error):
// The connection tried to start and failed irrecoverably.
…
case .cancelled:
// The connection was cancelled by you.
…
@unknown default:
break
}
}
If you a connection is in the .waiting(_:) state and you want to force an immediate retry, call the restart() method.
Network framework calls your viability update handler when its viability changes:
let connection: NWConnection = …
connection.viabilityUpdateHandler = { isViable in
… react to viability changes …
}
A connection becomes inviable when a network resource that it depends on is unavailable. A good example of this is the network interface that the connection is running over. If you have a connection running over Wi-Fi, and the user turns off Wi-Fi or moves out of range of their Wi-Fi network, any connection running over Wi-Fi becomes inviable.
The inviable state is not necessarily permanent. To continue the above example, the user might re-enable Wi-Fi or move back into range of their Wi-Fi network. If the connection becomes viable again, Network framework calls your viability update handler with a true value.
It’s a good idea to debounce the viability handler. If the connection becomes inviable, don’t close it down immediately. Rather, wait for a short while to see if it becomes viable again.
If a connection has been inviable for a while, you get to choose as to how to respond. For example, you might close the connection down or inform the user.
To close a connection, call the cancel() method. This gracefully disconnects the underlying network connection. To close a connection immediately, call the forceCancel() method. This is not something you should do as a matter of course, but it does make sense in exceptional circumstances. For example, if you’ve determined that the remote peer has gone deaf, it makes sense to cancel it in this way.
Send and receive reliable messages
In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol.
The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for reliable messages is WebSocket. To send a message on a WebSocket connection:
let connection: NWConnection = …
let message: Data = …
let metadata = NWProtocolWebSocket.Metadata(opcode: .binary)
let context = NWConnection.ContentContext(identifier: "send", metadata: [metadata])
connection.send(content: message, contentContext: context, completion: .contentProcessed({ error in
// … check `error` …
_ = error
}))
In WebSocket, the content identifier is ignored. Using an arbitrary fixed value, like the send in this example, is just fine.
Multipeer Connectivity allows you to send a message to multiple peers in a single send call. In Network framework each send call targets a specific connection. To send a message to multiple peers, make a send call on the connection associated with each peer.
If your app needs to transfer arbitrary amounts of data on a connection, it must implement flow control. See Start a stream, below.
To receive messages on a WebSocket connection:
func startWebSocketReceive(on connection: NWConnection) {
connection.receiveMessage { message, _, _, error in
if let error {
… handle the error …
return
}
if let message {
… handle the incoming message …
}
startWebSocketReceive(on: connection)
}
}
IMPORTANT WebSocket preserves message boundaries, which is one of the reasons why it’s ideal for your reliable messaging connections. If you use a streaming protocol, like TCP or QUIC streams, you must do your own framing. A good way to do that is with NWProtocolFramer.
If you need the metadata associated with the message, get it from the context parameter:
connection.receiveMessage { message, context, _, error in
…
if let message,
let metadata = context?.protocolMetadata(definition: NWProtocolWebSocket.definition) as? NWProtocolWebSocket.Metadata
{
… handle the incoming message and its metadata …
}
…
}
Send and receive best effort messages
In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol.
The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for best effort messages is UDP. To send a message on a UDP connection:
let connection: NWConnection = …
let message: Data = …
connection.send(content: message, completion: .idempotent)
IMPORTANT UDP datagrams have a theoretical maximum size of just under 64 KiB. However, sending a large datagram results in IP fragmentation, which is very inefficient. For this reason, Network framework prevents you from sending UDP datagrams that will be fragmented. To find the maximum supported datagram size for a connection, gets its maximumDatagramSize property.
To receive messages on a UDP connection:
func startUDPReceive(on connection: NWConnection) {
connection.receiveMessage { message, _, _, error in
if let error {
… handle the error …
return
}
if let message {
… handle the incoming message …
}
startUDPReceive(on: connection)
}
}
This is exactly the same code as you’d use for WebSocket.
Start a stream
In Multipeer Connectivity, you can ask the session to start a stream to a specific peer. There are two ways to achieve this in Network framework:
If you’re using QUIC for your reliable connection, start a new QUIC stream over that connection. This is one place that QUIC shines. You can run an arbitrary number of QUIC connections over a single QUIC connection group, and QUIC manages flow control (see below) for each connection and for the group as a whole.
If you’re using some other protocol for your reliable connection, like WebSocket, you must start a new connection. You might use TCP for this new connection, but it’s not unreasonable to use WebSocket or QUIC.
If you need to open a new connection for your stream, you can manage that process over your reliable connection. Choose a protocol to match your send mode explains the general approach for this, although in that case it’s opening a parallel best effort UDP connection rather than a parallel stream connection.
The main reason to start a new stream is that you want to send a lot of data to the remote peer. In that case you need to worry about flow control. Flow control applies to both the send and receive side.
IMPORTANT Failing to implement flow control can result in unbounded memory growth in your app. This is particularly bad on iOS, where jetsam will terminate your app if it uses too much memory.
On the send side, implement flow control by waiting for the connection to call your completion handler before generating and sending more data. For example, on a TCP connection or QUIC stream you might have code like this:
func sendNextChunk(on connection: NWConnection) {
let chunk: Data = … read next chunk from disk …
connection.send(content: chunk, completion: .contentProcessed({ error in
if let error {
… handle error …
return
}
sendNextChunk(on: connection)
}))
}
This acts like an asynchronous loop. The first send call completes immediately because the connection just copies the data to its send buffer. In response, your app generates more data. This continues until the connection’s send buffer fills up, at which point it defers calling your completion handler. Eventually, the connection moves enough data across the network to free up space in its send buffer, and calls your completion handler. Your app generates another chunk of data
For best performance, use a chunk size of at least 64 KiB. If you’re expecting to run on a fast device with a fast network, a chunk size of 1 MiB is reasonable.
Receive-side flow control is a natural extension of the standard receive pattern. For example, on a TCP connection or QUIC stream you might have code like this:
func receiveNextChunk(on connection: NWConnection) {
let chunkSize = 64 * 1024
connection.receive(minimumIncompleteLength: chunkSize, maximumLength: chunkSize) { chunk, _, isComplete, error in
if let chunk {
… write chunk to disk …
}
if isComplete {
… close the file …
return
}
if let error {
… handle the error …
return
}
receiveNextChunk(on: connection)
}
}
IMPORTANT The above is cast in terms of writing the chunk to disk. That’s important, because it prevents unbounded memory growth. If, for example, you accumulated the chunks into an in-memory buffer, that buffer could grow without bound, which risks jetsam terminating your app.
The above assumes that you can read and write chunks of data synchronously and promptly, for example, reading and writing a file on a local disk. That’s not always the case. For example, you might be writing data to an accessory over a slow interface, like Bluetooth LE. In such cases you need to read and write each chunk asynchronously.
This results in a structure where you read from an asynchronous input and write to an asynchronous output. For an example of how you might approach this, albeit in a very different context, see Handling Flow Copying.
Send a resource
In Multipeer Connectivity, you can ask the session to send a complete resource, identified by either a file or HTTP URL, to a specific peer. Network framework has no equivalent support for this, but you can implement it on top of a stream:
To send, open a stream and then read chunks of data using URLSession and send them over that stream.
To receive, open a stream and then receive chunks of data from that stream and write those chunks to disk.
In this situation it’s critical to implement flow control, as described in the previous section.
Final notes
This section collects together some general hints and tips.
Concurrency
In Multipeer Connectivity, each MCSession has its own internal queue and calls delegate callbacks on that queue. In Network framework, you get to control the queue used by each object for its callbacks. A good pattern is to have a single serial queue for all networking, including your listener and all connections.
In a simple app it’s reasonable to use the main queue for networking. If you do this, be careful not to do CPU intensive work in your networking callbacks. For example, if you receive a message that holds JPEG data, don’t decode that data on the main queue.
Overriding protocol defaults
Many network protocols, most notably TCP and QUIC, are intended to be deployed at vast scale across the wider Internet. For that reason they use default options that aren’t optimised for local networking. Consider changing these defaults in your app.
TCP has the concept of a send timeout. If you send data on a TCP connection and TCP is unable to successfully transfer it to the remote peer within the send timeout, TCP will fail the connection.
The default send timeout is infinite. TCP just keeps trying. To change this, set the connectionDropTime property.
TCP has the concept of keepalives. If a connection is idle, TCP will send traffic on the connection for two reasons:
If the connection is running through a NAT, the keepalives prevent the NAT mapping from timing out.
If the remote peer is inaccessible, the keepalives fail, which in turn causes the connection to fail. This prevents idle but dead connections from lingering indefinitely.
TCP keepalives default to disabled. To enable and configure them, set the enableKeepalive property. To configure their behaviour, set the keepaliveIdle, keepaliveCount, and keepaliveInterval properties.
Symbol cross reference
If you’re not sure where to start with a specific Multipeer Connectivity construct, find it in the tables below and follow the link to the relevant section.
[Sorry for the poor formatting here. DevForums doesn’t support tables properly, so I’ve included the tables as preformatted text.]
| For symbol | See |
| ----------------------------------- | --------------------------- |
| `MCAdvertiserAssistant` | *Discover peers* |
| `MCAdvertiserAssistantDelegate` | *Discover peers* |
| `MCBrowserViewController` | *Discover peers* |
| `MCBrowserViewControllerDelegate` | *Discover peers* |
| `MCNearbyServiceAdvertiser` | *Discover peers* |
| `MCNearbyServiceAdvertiserDelegate` | *Discover peers* |
| `MCNearbyServiceBrowser` | *Discover peers* |
| `MCNearbyServiceBrowserDelegate` | *Discover peers* |
| `MCPeerID` | *Create a peer identifier* |
| `MCSession` | See below. |
| `MCSessionDelegate` | See below. |
Within MCSession:
| For symbol | See |
| --------------------------------------------------------- | ------------------------------------ |
| `cancelConnectPeer(_:)` | *Manage a connection* |
| `connectedPeers` | *Manage a listener* |
| `connectPeer(_:withNearbyConnectionData:)` | *Manage a connection* |
| `disconnect()` | *Manage a connection* |
| `encryptionPreference` | *Plan for security* |
| `myPeerID` | *Create a peer identifier* |
| `nearbyConnectionData(forPeer:withCompletionHandler:)` | *Discover peers* |
| `securityIdentity` | *Plan for security* |
| `send(_:toPeers:with:)` | *Send and receive reliable messages* |
| `sendResource(at:withName:toPeer:withCompletionHandler:)` | *Send a resource* |
| `startStream(withName:toPeer:)` | *Start a stream* |
Within MCSessionDelegate:
| For symbol | See |
| ---------------------------------------------------------------------- | ------------------------------------ |
| `session(_:didFinishReceivingResourceWithName:fromPeer:at:withError:)` | *Send a resource* |
| `session(_:didReceive:fromPeer:)` | *Send and receive reliable messages* |
| `session(_:didReceive:withName:fromPeer:)` | *Start a stream* |
| `session(_:didReceiveCertificate:fromPeer:certificateHandler:)` | *Plan for security* |
| `session(_:didStartReceivingResourceWithName:fromPeer:with:)` | *Send a resource* |
| `session(_:peer:didChange:)` | *Manage a connection* |
Revision History
2025-04-11 Added some advice as to whether to use the peer identifier in your service name. Expanded the discussion of how to deduplicate connections in a star network architecture.
2025-03-20 Added a link to the DeviceDiscoveryUI framework to the Discovery UI section. Made other minor editorial changes.
2025-03-11 Expanded the Enable peer-to-peer Wi-Fi section to stress the importance of stopping network operations once you’re done with them. Added a link to that section from the list of Multipeer Connectivity drawbacks.
2025-03-07 First posted.
Every now and again folks notice that Network framework seems to create an unexpected number of connections on the wire. This post explains why that happens and what you should do about it.
If you have questions or comments, put them in a new thread here on the forums. Use the App & System Services > Networking topic area and the Network tag.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Understanding Also-Ran Connections
Network framework implements the Happy Eyeballs algorithm. That might create more on-the-wire connections than you expect. There are two common places where folks notice this:
When looking at a packet trace
When implementing a listener
Imagine that you’ve implemented a TCP server using NWListener and you connect to it from a client using NWConnection. In many situations there are multiple network paths between the client and the server. For example, on a local network there’s always at least two paths: the link-local IPv6 path and either an infrastructure IPv4 path or the link-local IPv4 path.
When you start your NWConnection, Network framework’s Happy Eyeballs algorithm might [1] start a TCP connection for each of these paths. It then races those connections. The one that connects first is the ‘winner’, and Network framework uses that connection for your traffic. Once it has a winner, the other connections, the also-ran connections, are redundant, and Network framework just closes them.
You can observe this behaviour on the client side by looking in the system log. Many Network framework log entries (subsystem com.apple.network) contain a connection identifier. For example C8 is the eighth connection started by this process. Each connection may have child connections (C8.1, C8.2, …) and grandchild connections (C8.1.1, C8.1.2, …), and so on. You’ll see state transitions for these child connections occurring in parallel. For example, the following log entries show that C8 is racing the connection of two grandchild connections, C8.1.1 and C8.1.2:
type: debug
time: 12:22:26.825331+0100
process: TestAlsoRanConnections
subsystem: com.apple.network
category: connection
message: nw_socket_connect [C8.1.1:1] Calling connectx(…)
type: debug
time: 12:22:26.964150+0100
process: TestAlsoRanConnections
subsystem: com.apple.network
category: connection
message: nw_socket_connect [C8.1.2:1] Calling connectx(…)
Note For more information about accessing the system log, see Your Friend the System Log.
You also see this on the server side, but in this case each connection is visible to your code. When you connect from the client, Network framework calls your listener’s new connection handler with multiple connections. One of those is the winning connection and you’ll receive traffic on it. The others are the also-ran connections, and they close promptly.
IMPORTANT Depending on network conditions there may be no also-ran connections. Or there may be lots of them. If you want to test the also-ran connection case, use Network Link Conditioner to add a bunch of delay to your packets.
You don’t need to write special code to handle also-ran connections. From the perspective of your listener, these are simply connections that open and then immediately close. There’s no difference between an also-ran connection and, say, a connection from a client that immediately crashes. Or a connection generated by someone doing a port scan. Your server must be resilient to such things.
However, the presence of these also-ran connections can be confusing, especially if you’re just getting started with Network framework, and hence this post.
[1] This is “might” because the exact behaviour depends on network conditions. More on that below.
On "Accessory Interface Specification CarPlay Addendum R10", it says that it is recommended that the accessory uses a MIMO (2x2) hardware configuration, does this imply that WiFi 5 and SISO (1X1) will be phased out in the near future?
When will WiFi 6 MIMO (2x2) become mandatory?
On "Accessory Interface Specification CarPlay Addendum R10", it says that Spatial Audio is mandatory. However, for aftermarket in-vehicle infotainment (IVI) system due to the number of speakers are less than 6, is it allowed not to support spatial audio for this type of aftermarket IVI system?
Esim activation. Assuming I already have card data, I use the universal link https://esimsetup.apple.com/esim_qrcode_provisioning?carddata= to install it.
However, it always ends up in the system Settings app.
The flow: 1. Click the link -> 2. Redirect to Settings -> 3. Show activation dialog.
Is there anyway to make the activation flow stay within the app? I couldn't find any documentation for that.
This is an example from Revolut app, where the whole flow above happens without leaving the app.
For important background information, read Extra-ordinary Networking before reading this.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Network Interface APIs
Most developers don’t need to interact directly with network interfaces. If you do, read this post for a summary of the APIs available to you.
Before you read this, read Network Interface Concepts.
Interface List
The standard way to get a list of interfaces and their addresses is getifaddrs. To learn more about this API, see its man page.
A network interface has four fundamental attributes:
A set of flags — These are packed into a CUnsignedInt. The flags bits are declared in <net/if.h>, starting with IFF_UP.
An interface type — See Network Interface Type, below.
An interface index — Valid indexes are greater than 0.
A BSD interface name. For example, an Ethernet interface might be called en0. The interface name is shared between multiple network interfaces running over a given hardware interface. For example, IPv4 and IPv6 running over that Ethernet interface will both have the name en0.
WARNING BSD interface names are not considered API. There’s no guarantee, for example, that an iPhone’s Wi-Fi interface is en0.
You can map between the last two using if_indextoname and if_nametoindex. See the if_indextoname man page for details.
An interface may also have address information. If present, this always includes the interface address (ifa_addr) and the network mask (ifa_netmask). In addition:
Broadcast-capable interfaces (IFF_BROADCAST) have a broadcast address (ifa_broadaddr, which is an alias for ifa_dstaddr).
Point-to-point interfaces (IFF_POINTOPOINT) have a destination address (ifa_dstaddr).
Calling getifaddrs from Swift is a bit tricky. For an example of this, see QSocket: Interfaces.
IP Address List
Once you have getifaddrs working, it’s relatively easy to manipulate the results to build a list of just IP addresses, a list of IP addresses for each interface, and so on. QSocket: Interfaces has some Swift snippets that show this.
Interface List Updates
The interface list can change over time. Hardware interfaces can be added and removed, network interfaces come up and go down, and their addresses can change. It’s best to avoid caching information from getifaddrs. If thats unavoidable, use the kNotifySCNetworkChange Darwin notification to update your cache. For information about registering for Darwin notifications, see the notify man page (in section 3).
This notification just tells you that something has changed. It’s up to you to fetch the new interface list and adjust your cache accordingly.
You’ll find that this notification is sometimes posted numerous times in rapid succession. To avoid unnecessary thrashing, debounce it.
While the Darwin notification API is easy to call from Swift, Swift does not import kNotifySCNetworkChange. To fix that, define that value yourself, calling a C function to get the value:
var kNotifySCNetworkChange: UnsafePointer<CChar> {
networkChangeNotifyKey()
}
Here’s what that C function looks like:
extern const char * networkChangeNotifyKey(void) {
return kNotifySCNetworkChange;
}
Network Interface Type
There are two ways to think about a network interface’s type. Historically there were a wide variety of weird and wonderful types of network interfaces. The following code gets this legacy value for a specific BSD interface name:
func legacyTypeForInterfaceNamed(_ name: String) -> UInt8? {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
let err = getifaddrs(&addrList)
// In theory we could check `errno` here but, honestly, what are gonna
// do with that info?
guard
err >= 0,
let first = addrList
else { return nil }
defer { freeifaddrs(addrList) }
return sequence(first: first, next: { $0.pointee.ifa_next })
.compactMap { addr in
guard
let nameC = addr.pointee.ifa_name,
name == String(cString: nameC),
let sa = addr.pointee.ifa_addr,
sa.pointee.sa_family == AF_LINK,
let data = addr.pointee.ifa_data
else { return nil }
return data.assumingMemoryBound(to: if_data.self).pointee.ifi_type
}
.first
}
The values are defined in <net/if_types.h>, starting with IFT_OTHER.
However, this value is rarely useful because many interfaces ‘look like’ Ethernet and thus have a type of IFT_ETHER.
Network framework has the concept of an interface’s functional type. This is an indication of how the interface fits into the system. There are two ways to get an interface’s functional type:
If you’re using Network framework and have an NWInterface value, get the type property.
If not, call ioctl with a SIOCGIFFUNCTIONALTYPE request. The return values are defined in <net/if.h>, starting with IFRTYPE_FUNCTIONAL_UNKNOWN.
Swift does not import SIOCGIFFUNCTIONALTYPE, so it’s best to write this code in a C:
extern uint32_t functionalTypeForInterfaceNamed(const char * name) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) { return IFRTYPE_FUNCTIONAL_UNKNOWN; }
struct ifreq ifr = {};
strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
bool success = ioctl(fd, SIOCGIFFUNCTIONALTYPE, &ifr) >= 0;
int junk = close(fd);
assert(junk == 0);
if ( ! success ) { return IFRTYPE_FUNCTIONAL_UNKNOWN; }
return ifr.ifr_ifru.ifru_functional_type;
}
Finally, TN3158 Resolving Xcode 15 device connection issues documents the SIOCGIFDIRECTLINK flag as a specific way to identify the network interfaces uses by Xcode for device connection traffic.
Revision History
2025-12-10 Added info about SIOCGIFDIRECTLINK.
2023-07-19 First posted.
ios構成プロファイルの制限のallowCloudPrivateRelayのプライベートリレーの制御とRelayペイロードの機能は関係がありますか?
それとも別々の機能でしょうか?
↓
s there a relationship between the private relay control in the iOS configuration profile restriction allowCloudPrivateRelay and the functionality of the Relay payload?
Or are they separate features?
Topic:
App & System Services
SubTopic:
Networking
Starting in iOS 26, two notable changes have been made to CallKit, LiveCommunicationKit, and the PushToTalk framework:
As a diagnostic aid, we're introducing new dialogs to warn apps of voip push related issue, for example when they fail to report a call or when when voip push delivery stops. The specific details of that behavior are still being determined and are likely to change over time, however, the critical point here is that these alerts are only intended to help developers debug and improve their app. Because of that, they're specifically tied to development and TestFlight signed builds, so the alert dialogs will not appear for customers running app store builds. The existing termination/crashes will still occur, but the new warning alerts will not appear.
As PushToTalk developers have previously been warned, the last unrestricted PushKit entitlement ("com.apple.developer.pushkit.unrestricted-voip.ptt") has been disabled in the iOS 26 SDK. ALL apps that link against the iOS 26 SDK which receive a voip push through PushKit and which fail to report a call to CallKit will be now be terminated by the system, as the API contract has long specified.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware