Networking

RSS for tag

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

Networking Documentation

Posts under Networking subtopic

Post

Replies

Boosts

Views

Activity

Networking Resources
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.
0
0
3.6k
1w
My app attempts to use a socket to establish a connection with my external device, but it fails
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.
0
0
28
Oct ’25
On Host Names
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" On Host Names I commonly see questions like How do I get the device’s host name? This question doesn’t make sense without more context. Apple systems have a variety of things that you might consider to be the host name: The user-assigned device name — This is a user-visible value, for example, Guy Smiley. People set this in Settings > General > About > Name. The local host name — This is a DNS name used by Bonjour, for example, guy-smiley.local. By default this is algorithmically derived from the user-assigned device name. On macOS, people can override this in Settings > General > Sharing > Local hostname. The reverse DNS name associated with the various IP addresses assigned to the device’s various network interfaces That last one is pretty much useless. You can’t get a single host name because there isn’t a single IP address. For more on that, see Don’t Try to Get the Device’s IP Address. The other two have well-defined answers, although those answers vary by platform. I’ll talk more about that below. Before getting to that, however, let’s look at the big picture. Big Picture The use cases for the user-assigned device name are pretty clear. I rarely see folks confused about that. Another use case for this stuff is that you’ve started a server and you want to tell the user how to connect to it. I discuss this in detail in Showing Connection Information in an iOS Server. However, most folks who run into problems like this do so because they’re suffering from one of the following misconceptions: The device has a DNS name. Its DNS name is unique. Its DNS name doesn’t change. Its DNS name is in some way useful for networking. Some of these may be true in some specific circumstances, but none of them are true in all circumstances. These issues are not unique to Apple platforms — if you look at the Posix spec for gethostname, it says nothing about DNS! — but folks tend to notice these problems more on Apple platforms because Apple devices are often deployed to highly dynamic network environments. So, before you start using the APIs discussed in this post, think carefully about your assumptions. And if you actually do want to work with DNS, there are two cases to consider: If you’re looking for the local host name, use the APIs discussed above. In other cases, it’s likely that the APIs in this post will not be helpful and you’d be better off focusing on DNS APIs [1]. [1] The API I recommend for this is DNS-SD. See the DNS section in TN3151 Choosing the right networking API. macOS To get the user-assigned device name, call the SCDynamicStoreCopyComputerName(_:_:) function. For example: let userAssignedDeviceName = SCDynamicStoreCopyComputerName(nil, nil) as String? To get the local host name, call the SCDynamicStoreCopyLocalHostName(_:) function. For example: let localHostName = SCDynamicStoreCopyLocalHostName(nil) as String? IMPORTANT This returns just the name label. To form a local host name, append .local.. Both routines return an optional result; code defensively! If you’re displaying these values to the user, use the System Configuration framework dynamic store notification mechanism to keep your UI up to date. iOS and Friends On iOS, iPadOS, tvOS, and visionOS, get the user-assigned device name from the name property on UIDevice. IMPORTANT Access to this is now restricted. For more on that, see the documentation for the com.apple.developer.device-information.user-assigned-device-name entitlement. There is no direct mechanism to get the local host name. Other APIs There are a wide variety of other APIs that purport to return the host name. These include: gethostname The name property on NSHost [1] The hostName property on NSProcessInfo (ProcessInfo in Swift) These are problematic for a number of reasons: They have a complex implementation that makes it hard to predict what value you’ll get back. They might end up trying to infer the host name from the network environment. The existing behaviour is hard to change due to compatibility concerns. Some of them are marked as to-be-deprecated. IMPORTANT The second issue is particularly problematic, because it involves synchronous DNS requests [2]. That’s slow in general. Worse yet, if the network environment is restricted in some way, these calls can be very slow, taking about 30 seconds to time out. Given these problems, it’s generally best to avoid calling these routines at all. [1] It also has a names property, which is a little closer to reality but still not particularly useful. [2] Actually, that’s not true for gethostname. Rather, that call just returns whatever was last set by sethostname. This is always fast. The System Configuration framework infrastructure calls sethostname to update the host name as the system state changes.
0
0
178
Mar ’25
Network Extension Resources
General: Forums subtopic: App & System Services > Networking DevForums tag: Network Extension Network Extension framework documentation Routing your VPN network traffic article Filtering traffic by URL sample code Filtering Network Traffic sample code TN3120 Expected use cases for Network Extension packet tunnel providers technote TN3134 Network Extension provider deployment technote TN3165 Packet Filter is not API technote Network Extension and VPN Glossary forums post Debugging a Network Extension Provider forums post Exporting a Developer ID Network Extension forums post Network Extension vs ad hoc techniques on macOS forums post Network Extension Provider Packaging forums post NWEndpoint History and Advice forums post Extra-ordinary Networking forums post Wi-Fi management: Wi-Fi Fundamentals forums post TN3111 iOS Wi-Fi API overview technote How to modernize your captive network developer news post iOS Network Signal Strength forums post See also Networking Resources. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
2.9k
Nov ’25
setTunnelNetworkSettings() is not setting excludedRoutes
We are using PacketTunnel as system extension to establish vpn tunnel. The flow is like: Create a PacketTunnelProvide to establish vpn When tunnel gets connected add excludedRoutes by calling setTunnelNetworkSettings(). Result: The routing table is not getting updated with new excludeRoutes entries. As per setTunnelNetworkSettings() documentation: "This function is called by tunnel provider implementations to set the network settings of the tunnel, including IP routes, DNS servers, and virtual interface addresses depending on the tunnel type. Subclasses should not override this method. This method can be called multiple times during the lifetime of a particular tunnel. It is not necessary to call this function with nil to clear out the existing settings before calling this function with a non-nil configuration." So we believe setTunnelNetworkSettings() should be able to set new excludeRoutes. We could see we are passing correct entries to setTunnelNetworkSettings(): { tunnelRemoteAddress = 10.192.229.240 DNSSettings = { protocol = cleartext server = ( 10.192.230.211, 192.168.180.15, ) matchDomains = ( , ) matchDomainsNoSearch = NO } IPv4Settings = { configMethod = manual addresses = ( 100.100.100.17, ) subnetMasks = ( 255.255.255.255, ) includedRoutes = ( { destinationAddress = 1.1.1.1 destinationSubnetMask = 255.255.255.255 gatewayAddress = 100.100.100.17 }, { destinationAddress = 2.2.2.0 destinationSubnetMask = 255.255.255.255 gatewayAddress = 100.100.100.17 }, { destinationAddress = 11.11.11.0 destinationSubnetMask = 255.255.255.0 gatewayAddress = 100.100.100.17 }, ) excludedRoutes = ( { destinationAddress = 170.114.52.2 destinationSubnetMask = 255.255.255.255 }, ) overridePrimary = NO } MTU = 1298 } The problem is present on macOS Sequoia 15.2. Is it a known issue? Did anyone else faced this issue?
0
0
490
Dec ’24
CallKit and PushToTalk related changes in iOS 26
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
0
0
706
Jun ’25
Understanding Also-Ran Connections
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.
0
0
127
Apr ’25
NSURLSession’s Resume Rate Limiter
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)
0
0
13k
Jul ’25
NWEndpoint History and Advice
The path from Network Extension’s in-provider networking APIs to Network framework has been long and somewhat rocky. The most common cause of confusion is NWEndpoint, where the same name can refer to two completely different types. I’ve helped a bunch of folks with this over the years, and I’ve decided to create this post to collect together all of those titbits. If you have questions or comments, please put them in a new thread. Put it 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" NWEndpoint History and Advice A tale that spans three APIs, two languages, and ten years. The NWEndpoint type has a long and complex history, and if you’re not aware of that history you can bump into weird problems. The goal of this post is to explain the history and then offer advice on how to get around specific problems. IMPORTANT This post focuses on NWEndpoint, because that’s the type that causes the most problems, but there’s a similar situation with NWPath. The History In iOS 9 Apple introduced the Network Extension (NE) framework, which offers a convenient way for developers to create a custom VPN transport. Network Extension types all have the NE prefix. Note I’m gonna use iOS versions here, just to keep the text simple. If you’re targeting some other platform, use this handy conversion table: iOS | macOS | tvOS | watchOS | visionOS --- + ----- + ---- + ------- + -------- 9 | 10.11 | 9 | 2 | - 12 | 10.14 | 12 | 5 | - 18 | 15 | 18 | 11 | 2 At that time we also introduced in-provider networking APIs. The idea was that an NE provider could uses these Objective-C APIs to communicate with its VPN server, and thereby avoiding a bunch of ugly BSD Sockets code. The in-provider networking APIs were limited to NE providers. Specifically, the APIs to construct an in-provider connection were placed on types that were only usable within an NE provider. For example, a packet tunnel provider could create a NWTCPConnection object by calling -createTCPConnectionToEndpoint:enableTLS:TLSParameters:delegate:] and -createTCPConnectionThroughTunnelToEndpoint:enableTLS:TLSParameters:delegate:, which are both methods on NEPacketTunnelProvider. These in-provider networking APIs came with a number of ancillary types, including NWEndpoint and NWPath. At the time we thought that we might promote these in-provider networking APIs to general-purpose networking APIs. That’s why the APIs use the NW prefix. For example, it’s NWTCPConnection, not NETCPConnection. However, plans changed. In iOS 12 Apple shipped Network framework as our recommended general-purpose networking API. This actually includes two APIs: A Swift API that follows Swift conventions, for example, the connection type is called NWConnection A C API that follows C conventions, for example, the connection type is called nw_connection_t These APIs follow similar design patterns to the in-provider networking API, and thus have similar ancillary types. Specifically, there are an NWEndpoint and nw_endpoint_t types, both of which perform a similar role to the NWEndpoint type in the in-provider networking API. This was a source of some confusion in Swift, because the name NWEndpoint could refer to either the Network framework type or the Network Extension framework type, depending on what you’d included. Fortunately you could get around this by qualifying the type as either Network.NWEndpoint or NetworkExtension.NWEndpoint. The arrival of Network framework meant that it no longer made sense to promote the in-provider networking APIs to general-purposes networking APIs. The in-provider networking APIs were on the path to deprecation. However, deprecating these APIs was actually quite tricky. Network Extension framework uses these APIs in a number of interesting ways, and so deprecating them required adding replacements. In addition, we’d needed different replacements for Swift and Objective-C, because Network framework has separate APIs for Swift and C-based languages. In iOS 18 we tackled that problem head on. To continue the NWTCPConnection example above, we replaced: -createTCPConnectionToEndpoint:enableTLS:TLSParameters:delegate:] with nw_connection_t -createTCPConnectionThroughTunnelToEndpoint:enableTLS:TLSParameters:delegate: with nw_connection_t combined with a new virtualInterface property on NEPacketTunnelProvider Of course that’s the Objective-C side of things. In Swift, the replacement is NWConnection rather than nw_connection_t, and the type of the virtualInterface property is NWInterface rather than nw_interface_t. But that’s not the full story. For the two types that use the same name in both frameworks, NWEndpoint and NWPath, we decided to use this opportunity to sort out that confusion. To see how we did that, check out the <NetworkExtension/NetworkExtension.apinotes> file in the SDK. Focusing on NWEndpoint for the moment, you’ll find two entries: … - Name: NWEndpoint SwiftPrivate: true … SwiftVersions: - Version: 5.0 … - Name: NWEndpoint SwiftPrivate: false … The first entry applies when you’re building with the Swift 6 language mode. This marks the type as SwiftPrivate, which means that Swift imports it as __NWEndpoint. That frees up the NWEndpoint name to refer exclusively to the Network framework type. The second entry applies when you’re building with the Swift 5 language mode. It marks the type as not SwiftPrivate. This is a compatible measure to ensure that code written for Swift 5 continues to build. The Advice This sections discusses specific cases in this transition. NWEndpoint and NWPath In Swift 5 language mode, NWEndpoint and NWPath might refer to either framework, depending on what you’ve imported. Add a qualifier if there’s any ambiguity, for example, Network.NWEndpoint or NetworkExtension.NWEndpoint. In Swift 6 language mode, NWEndpoint and NWPath always refer to the Network framework type. Add a __ prefix to get to the Network Extension type. For example, use NWEndpoint for the Network framework type and __NWEndpoint for the Network Extension type. Direct and Through-Tunnel TCP Connections in Swift To create a connection directly, simply create an NWConnection. This support both TCP and UDP, with or without TLS. To create a connection through the tunnel, replace code like this: let c = self.createTCPConnectionThroughTunnel(…) with code like this: let params = NWParameters.tcp params.requiredInterface = self.virtualInterface let c = NWConnection(to: …, using: params) This is for TCP but the same basic process applies to UDP. UDP and App Proxies in Swift If you’re building an app proxy, transparent proxy, or DNS proxy in Swift and need to handle UDP flows using the new API, adopt the NEAppProxyUDPFlowHandling protocol. So, replace code like this: class AppProxyProvider: NEAppProxyProvider { … override func handleNewUDPFlow(_ flow: NEAppProxyUDPFlow, initialRemoteEndpoint remoteEndpoint: NWEndpoint) -> Bool { … } } with this: class AppProxyProvider: NEAppProxyProvider, NEAppProxyUDPFlowHandling { … func handleNewUDPFlow(_ flow: NEAppProxyUDPFlow, initialRemoteFlowEndpoint remoteEndpoint: NWEndpoint) -> Bool { … } } Creating a Network Rule To create an NWHostEndpoint, replace code like this: let ep = NWHostEndpoint(hostname: "1.2.3.4", port: "12345") let r = NENetworkRule(destinationHost: ep, protocol: .TCP) with this: let ep = NWEndpoint.hostPort(host: "1.2.3.4", port: 12345) let r = NENetworkRule(destinationHostEndpoint: ep, protocol: .TCP) Note how the first label of the initialiser has changed from destinationHost to destinationHostEndpoint.
0
0
189
Jul ’25
Broadcasts and Multicasts, Hints and Tips
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" Broadcasts and Multicasts, Hints and Tips I regularly see folks struggle with broadcasts and multicasts on Apple platforms. This post is my attempt to clear up some of the confusion. This post covers both IPv4 and IPv6. There is, however, a key difference. In IPv4, broadcasts and multicasts are distinct concepts. In contrast, IPv6 doesn’t support broadcast as such; rather, it treats broadcasts as a special case of multicasts. IPv6 does have an all nodes multicast address, but it’s rarely used. Before reading this post, I suggest you familiarise yourself with IP addresses in general. A good place to start is The Fount of All Knowledge™. Service Discovery A lot of broadcast and multicast questions come from folks implementing their own service discovery protocol. I generally recommend against doing that, for the reasons outlined in the Service Discovery section of Don’t Try to Get the Device’s IP Address. There are, however, some good reasons to implement a custom service discovery protocol. For example, you might be working with an accessory that only supports this custom protocol [1]. If you must implement your own service discovery protocol, read this post and also read the advice in Don’t Try to Get the Device’s IP Address. IMPORTANT Sometimes I see folks implementing their own version of mDNS. This is almost always a mistake: If you’re using third-party tooling that includes its own mDNS implementation, it’s likely that this tooling allows you to disable that implementation and instead rely on the Bonjour support that’s built-in to all Apple platforms. If you’re doing some weird low-level thing with mDNS or DNS-SD, it’s likely that you can do that with the low-level DNS-SD API. [1] And whose firmware you can’t change! I talk more about this in Working with a Wi-Fi Accessory. API Choice Broadcasts and multicasts typically use UDP [1]. TN3151 Choosing the right networking API describes two recommended UDP APIs: Network framework BSD Sockets Our general advice is to prefer Network framework over BSD Sockets, but UDP broadcasts and multicasts are an exception to that rule. Network framework has very limited UDP broadcast support. And while it’s support for UDP multicasts is less limited, it’s still not sufficient for all UDP applications. In cases where Network framework is not sufficient, BSD Sockets is your only option. [1] It is possible to broadcast and multicast at the Ethernet level, but I almost never see questions about that. UDP Broadcasts in Network Framework Historically I’ve claimed that Network framework was useful for UDP broadcasts is very limited circumstances (for example, in the footnote on this post). I’ve since learnt that this isn’t the case. Or, more accurately, this support is so limited (r. 122924701) as to be useless in practice. For the moment, if you want to work with UDP broadcasts, your only option is BSD Sockets. UDP Multicasts in Network Framework Network framework supports UDP multicast using the NWConnectionGroup class with the NWMulticastGroup group descriptor. This support has limits. The most significant limit is that it doesn’t support broadcasts; it’s for multicasts only. Note This only relevant to IPv4. Remember that IPv6 doesn’t support broadcasts as a separate concept. There are other limitations, but I don’t have a good feel for them. I’ll update this post as I encounter issues. Local Network Privacy Some Apple platforms support local network privacy. This impacts broadcasts and multicasts in two ways: Broadcasts and multicasts require local network access, something that’s typically granted by the user. Broadcasts and multicasts are limited by a managed entitlement (except on macOS). TN3179 Understanding local network privacy has lots of additional info on this topic, including the list of platforms to which it applies. Send, Receive, and Interfaces When you broadcast or multicast, there’s a fundamental asymmetry between send and receive: You can reasonable receive datagrams on all broadcast-capable interfaces. But when you send a datagram, it has to target a specific interface. The sending behaviour is the source of many weird problems. Consider the IPv4 case. If you send a directed broadcast, you can reasonably assume it’ll be routed to the correct interface based on the network prefix. But folks commonly send an all-hosts broadcast (255.255.255.255), and it’s not obvious what happens in that case. Note If you’re unfamiliar with the terms directed broadcast and all-hosts broadcast, see IP address. The exact rules for this are complex, vary by platform, and can change over time. For that reason, it’s best to write your broadcast code to be interface specific. That is: Identify the interfaces on which you want to work. Create a socket per interface. Bind that socket to that interface. Note Use the IP_BOUND_IF (IPv4) or IPV6_BOUND_IF (IPv6) socket options rather than binding to the interface address, because the interface address can change over time. Extra-ordinary Networking has links to other posts which discuss these concepts and the specific APIs in more detail. Miscellaneous Gotchas A common cause of mysterious broadcast and multicast problems is folks who hard code BSD interface names, like en0. Doing that might work for the vast majority of users but then fail in some obscure scenarios. BSD interface names are not considered API and you must not hard code them. Extra-ordinary Networking has links to posts that describe how to enumerate the interface list and identify interfaces of a specific type. Don’t assume that there’ll be only one interface of a given type. This might seem obviously true, but it’s not. For example, our platforms support peer-to-peer Wi-Fi, so each device has multiple Wi-Fi interfaces. When sending a broadcast, don’t forget to enable the SO_BROADCAST socket option. If you’re building a sandboxed app on the Mac, working with UDP requires both the com.apple.security.network.client and com.apple.security.network.server entitlements. Some folks reach for broadcasts or multicasts because they’re sending the same content to multiple devices and they believe that it’ll be faster than unicasts. That’s not true in many cases, especially on Wi-Fi. For more on this, see the Broadcasts section of Wi-Fi Fundamentals. Snippets To send a UDP broadcast: func broadcast(message: Data, to interfaceName: String) throws { let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0) defer { try! fd.close() } try fd.setSocketOption(SOL_SOCKET, SO_BROADCAST, 1 as CInt) let interfaceIndex = if_nametoindex(interfaceName) guard interfaceIndex > 0 else { throw … } try fd.setSocketOption(IPPROTO_IP, IP_BOUND_IF, interfaceIndex) try fd.send(data: message, to: ("255.255.255.255", 2222)) } Note These snippet uses the helpers from Calling BSD Sockets from Swift. To receive UDP broadcasts: func receiveBroadcasts(from interfaceName: String) throws { let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0) defer { try! fd.close() } let interfaceIndex = if_nametoindex(interfaceName) guard interfaceIndex > 0 else { fatalError() } try fd.setSocketOption(IPPROTO_IP, IP_BOUND_IF, interfaceIndex) try fd.setSocketOption(SOL_SOCKET, SO_REUSEADDR, 1 as CInt) try fd.setSocketOption(SOL_SOCKET, SO_REUSEPORT, 1 as CInt) try fd.bind("0.0.0.0", 2222) while true { let (data, (sender, port)) = try fd.receiveFrom() … } } IMPORTANT This code runs synchronously, which is less than ideal. In a real app you’d run the receive asynchronously, for example, using a Dispatch read source. For an example of how to do that, see this post. If you need similar snippets for multicast, lemme know. I’ve got them lurking on my hard disk somewhere (-: Other Resources Apple’s official documentation for BSD Sockets is in the man pages. See Reading UNIX Manual Pages. Of particular interest are: setsockopt man page ip man page ip6 man page If you’re not familiar with BSD Sockets, I strongly recommend that you consult third-party documentation for it. BSD Sockets is one of those APIs that looks simple but, in reality, is ridiculously complicated. That’s especially true if you’re trying to write code that works on BSD-based platforms, like all of Apple’s platforms, and non-BSD-based platforms, like Linux. I specifically recommend UNIX Network Programming, by Stevens et al, but there are lots of good alternatives. https://unpbook.com Revision History 2025-09-01 Fixed a broken link. 2025-01-16 First posted.
0
0
555
Sep ’25
Alternative for deprecated dns_parse_packet
I'm developing in Swift and working on parsing DNS queries. I'm considering using dns_parse_packet, but I noticed that dns_util is deprecated (although it still seems to work in my limited testing). As far as I know, there isn’t a built-in replacement for this. Is that correct? On a related note, are there any libraries available for parsing TLS packets—specifically the ClientHello message to extract the Server Name Indication (SNI)—instead of relying on my own implementation? Related to this post.
0
1
285
Dec ’24
NSPOSIXErrorDomain Code=65 iOS18 Xcode16
Hi, I have a problem about "NSPOSIXErrorDomain Code=65 & iOS18 & Xcode 16". I used 'CocoaAsyncSocket', '~> 7.6.5'. It works fine on iOS 15.2, But it's worried on iOS 18.3. Before this, broadcasts can be obtained normally。 I had get socket Multicast Networking. Please help me .
0
0
281
Dec ’24
Remove Weak cipher from the iOS cipher suite
Hi everyone, is there any ways we can remove the weak ciphers as part of TLS handshake (TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) I checked here but still do not see anyways to print out and change the ciphers suite we want to use https://forums.developer.apple.com/forums/thread/43230 https://forums.developer.apple.com/forums/thread/700406?answerId=706382022#706382022
0
0
331
Dec ’24
Extra-ordinary Networking
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.
0
0
5.4k
Jul ’25
Wi-Fi Aware Paring Flow
Hello, I understand that to discover and pair a device or accessory with Wi-Fi Aware, we can use either the DeviceDiscoveryUI or AccessorySetupKitUI frameworks. During the pairing process, both frameworks prompt the user to enter a pairing code. Is this step mandatory? What alternatives exist for devices or accessories that don't have a way to communicate a pairing code to the user (for example, devices or accessories without a display or voice capability)? Best regards, Gishan
0
0
163
Nov ’25
Don’t Try to Get the Device’s IP Address
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" Don’t Try to Get the Device’s IP Address I regularly see questions like: How do I find the IP address of the device? How do I find the IP address of the Wi-Fi interface? How do I identify the Wi-Fi interface? I also see a lot of really bad answers to these questions. That’s understandable, because the questions themselves don’t make sense. Networking on Apple platforms is complicated and many of the things that are ‘obviously’ true are, in fact, not true at all. For example: There’s no single IP address that represents the device, or an interface. A device can have 0 or more interfaces, each of which can have 0 or more IP addresses, each of which can be IPv4 and IPv6. A device can have multiple interfaces of a given type. It’s common for iPhones to have multiple WWAN interfaces, for example. It’s not possible to give a simple answer to any of these questions, because the correct answer depends on the context. Why do you need this particular information? What are you planning to do with it? This post describes the scenarios I most commonly encounter, with my advice on how to handle each scenario. IMPORTANT BSD interface names, like en0, are not considered API. There’s no guarantee, for example, that an iPhone’s Wi-Fi interface is en0. If you write code that relies on a hard-coded interface name, it will fail in some situations. Service Discovery Some folks want to identify the Wi-Fi interface so that they can run a custom service discovery protocol over it. Before you do that, I strongly recommend that you look at Bonjour. This has a bunch of advantages: It’s an industry standard [1]. It’s going to be more efficient on the ‘wire’. You don’t have to implement it yourself, you can just call an API [2]. For information about the APIs available, see TN3151 Choosing the right networking API. If you must implement your own service discovery protocol, don’t think in terms of finding the Wi-Fi interface. Rather, write your code to work with all Wi-Fi interfaces, or perhaps even all Ethernet-like interfaces. That’s what Apple’s Bonjour implementation does, and it means that things will work in odd situations [3]. To find all Wi-Fi interfaces, get the interface list and filter it for ones with the Wi-Fi functional type. To find all broadcast-capable interfaces, get the interface list and filter it for interfaces with the IFF_BROADCAST flag set. If the service you’re trying to discover only supports IPv4, filter out any IPv6-only interfaces. For advice on how to do this, see Interface List and Network Interface Type in Network Interface APIs. When working with multiple interfaces, it’s generally a good idea to create a socket per interface and then bind that socket to the interface. That ensures that, when you send a packet, it’ll definitely go out the interface you expect. For more information on how to implement broadcasts correctly, see Broadcasts and Multicasts, Hints and Tips. [1] Bonjour is an Apple term for: RFC 3927 Dynamic Configuration of IPv4 Link-Local Addresses RFC 6762 Multicast DNS RFC 6763 DNS-Based Service Discovery [2] That’s true even on non-Apple platforms. It’s even true on most embedded platforms. If you’re talking to a Wi-Fi accessory, see Working with a Wi-Fi Accessory. [3] Even if the service you’re trying to discover can only be found on Wi-Fi, it’s possible for a user to have their iPhone on an Ethernet that’s bridged to a Wi-Fi. Why on earth would they do that? Well, security, of course. Some organisations forbid their staff from using Wi-Fi. Logging and Diagnostics Some folks want to log the IP address of the Wi-Fi interface, or the WWAN, or both for diagnostic purposes. This is quite feasible, with the only caveat being there may be multiple interfaces of each type. To find all interfaces of a particular type, get the interface list and filter it for interfaces with that functional type. See Interface List and Network Interface Type in Network Interface APIs. Interface for an Outgoing Connection There are situations where you need to get the interface used by a particular connection. A classic example of that is FTP. When you set up a transfer in FTP, you start with a control connection to the FTP server. You then open a listener and send its IP address and port to the FTP server over your control connection. What IP address should you use? There’s an easy answer here: Use the local IP address for the control connection. That’s the one that the server is most likely to be able to connect to. To get the local address of a connection: In Network framework, first get the currentPath property and then get its localEndpoint property. In BSD Sockets, use getsockname. See its man page for details. Now, this isn’t a particularly realistic example. Most folks don’t use FTP these days [1] but, even if they do, they use FTP passive mode, which avoids the need for this technique. However, this sort of thing still does come up in practice. I recently encountered two different variants of the same problem: One developer was implementing VoIP software and needed to pass the devices IP address to their VoIP stack. The best IP address to use was the local IP address of their control connection to the VoIP server. A different developer was upgrading the firmware of an accessory. They do this by starting a server within their app and sending a command to the accessory to download the firmware from that server. Again, the best IP address to use is the local address of the control connection. [1] See the discussion in TN3151 Choosing the right networking API. Listening for Connections If you’re listening for incoming network connections, you don’t need to bind to a specific address. Rather, listen on all local addresses. In Network framework, this is the default for NWListener. In BSD Sockets, set the address to INADDR_ANY (IPv4) or in6addr_any (IPv6). If you only want to listen on a specific interface, don’t try to bind to that interface’s IP address. If you do that, things will go wrong if the interface’s IP address changes. Rather, bind to the interface itself: In Network framework, set either the requiredInterfaceType property or the requiredInterface property on the NWParameters you use to create your NWListener. In BSD Sockets, set the IP_BOUND_IF (IPv4) or IPV6_BOUND_IF (IPv6) socket option. How do you work out what interface to use? The standard technique is to get the interface list and filter it for interfaces with the desired functional type. See Interface List and Network Interface Type in Network Interface APIs. Remember that their may be multiple interfaces of a given type. If you’re using BSD Sockets, where you can only bind to a single interface, you’ll need to create multiple listeners, one for each interface. Listener UI Some apps have an embedded network server and they want to populate a UI with information on how to connect to that server. This is a surprisingly tricky task to do correctly. For the details, see Showing Connection Information for a Local Server. Outgoing Connections In some situations you might want to force an outgoing connection to run over a specific interface. There are four common cases here: Set the local address of a connection [1]. Force a connection to run over a specific interface. Force a connection to run over a type of interface. Force a connection to run over an interface with specific characteristics. For example, you want to download some large resource without exhausting the user’s cellular data allowance. The last case should be the most common — see the Constraints section of Network Interface Techniques — but all four are useful in specific circumstances. The following sections explain how to tackle these tasks in the most common networking APIs. [1] This implicitly forces the connection to use the interface with that address. For an explanation as to why, see the discussion of scoped routing in Network Interface Techniques. Network Framework Network framework has good support for all of these cases. Set one or more of the following properties on the NWParameters object you use to create your NWConnection: requiredLocalEndpoint property requiredInterface property prohibitedInterfaces property requiredInterfaceType property prohibitedInterfaceTypes property prohibitConstrainedPaths property prohibitExpensivePaths property Foundation URL Loading System URLSession has fewer options than Network framework but they work in a similar way: Set one or more of the following properties on the URLSessionConfiguration object you use to create your session: allowsCellularAccess property allowsConstrainedNetworkAccess property allowsExpensiveNetworkAccess property Note While these session configuration properties are also available on URLRequest, it’s better to configure this on the session. There’s no option that forces a connection to run over a specific interface. In most cases you don’t need this — it’s better to use the allowsConstrainedNetworkAccess and allowsExpensiveNetworkAccess properties — but there are some situations where that’s necessary. For advice on this front, see Running an HTTP Request over WWAN. BSD Sockets BSD Sockets has very few options in this space. One thing that’s easy and obvious is setting the local address of a connection: Do that by passing the address to bind. Alternatively, to force a connection to run over a specific interface, set the IP_BOUND_IF (IPv4) or IPV6_BOUND_IF (IPv6) socket options. Revision History 2025-01-21 Added a link to Broadcasts and Multicasts, Hints and Tips. Made other minor editorial changes. 2023-07-18 First posted.
0
0
2.5k
Jan ’25
iOS26 captive portal detection changes?
Hi all, I work on a smart product that, for setup, uses a captive portal to allow users to connect and configure the device. It emits a WiFi network and runs a captive portal - an HTTP server operates at 10.0.0.1, and a DNS server responds to all requests with 10.0.0.1 to direct "any and all" request to the server. When iOS devices connect, they send a request to captive.apple.com/hotspot-detect.html; if it returns success, that means they're on the internet; if not, the typical behavior in the past has been to assume you're connected to a captive portal and display what's being served. I serve any requests to /hotspot-detect.html with my captive portal page (index.html). This has worked reliably on iOS18 for a long time (user selects my products WiFi network, iOS detects portal and opens it). But almost everyone who's now trying with iOS26 is having the "automatic pop up" behavior fail - usually it says "Error opening page - Hotspot login cannot open the page because the network connection was lost." However, if opening safari and navigating to any URL (or 10.0.0.1) the portal loads - it's just the iOS auto-detect and open that's not working iOS18 always succeeds; iOS26 always fails. Anybody have any idea what changes may have been introduced in iOS26 on this front, or anything I can do to help prompt or coax iOS26 into loading the portal? It typically starts reading, but then stops mid-read.
0
0
221
Oct ’25
Moving from Multipeer Connectivity to Network Framework
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.
0
0
1.3k
Apr ’25
Content filtering
Hello team, Would this mean that content filters intended for all browsing can only be implemented for managed devices using MDM? My goal would be to create a content filtering app for all users, regardless of if their device is managed/supervised. thanks.
0
0
11
23h