Hi Everyone,
I’m working on a communication system for my app using NWConnection with the UDP protocol. The connection is registered to a custom serial dispatch queue. However, I’m trying to understand what the behavior will be in a scenario where the connection is canceled while there are still pending receive operations in progress.
Scenario Overview:
The sender is transmitting n = 100 packets to the receiver, out of which 40 packets have already been sent (i.e., delivered to the Receiver).
The receiver has posted m = 20 pending receive operations, where each receive operation is responsible for handling one packet.
The receiver has already successfully processed x = 10 packets.
At the time of cancellation, the receiver’s buffer still holds m = 20 packets that are pending for processing, and k = 10 pending receive callbacks are in the dispatch queue, waiting to be executed.
At same time when the 10th packet was processed another thread triggers .cancel() on this accepted NWConnection (on the receiver side), I need to understand the impact on the pending receive operations and their associated callbacks.
My Questions:
What happens to the k = 10 pending receive callbacks that are in the dispatch queue waiting to be triggered when the connection is canceled? Will these callbacks complete successfully and process the data? Or, because the connection is canceled, will they complete with failure?
What happens to the remaining pending receive operations that were initiated but have not yet been scheduled in the dispatch queue? For the pending receive operations that were already initiated (i.e., the network stack is waiting to receive the data, but the callback hasn’t been scheduled yet), will they fail immediately when the connection is canceled? Or is there any chance that the framework might still process these receives before the cancellation fully takes effect?
Post
Replies
Boosts
Views
Activity
Hi,
I have been reviewing some previous discussions around networking in macOS, and I’d like to clarify my understanding of the differences between the kernel-space network stack and user-space network stack and validate this understanding based on the information shared in earlier threads.
I’m also curious about how these differences impact macOS applications, particularly those requiring maintaining many simultaneous network connections.
Understanding Kernel-Space vs User-Space Network Stack
Kernel-Space Network Stack (BSD Sockets):
The kernel-space networking stack refers to the traditional networking layer that runs in the kernel and handles network communication via BSD sockets. This stack is lower-level and interacts directly with the operating system's networking drivers and hardware. All network connections managed through this stack require a socket (essentially a file descriptor) to be opened, which places limits on the number of file descriptors that can be used (for example, the default 64K limit for sockets). The kernel network stack is traditionally used on macOS (and other UNIX-based systems) for networking tasks, such as when you use system APIs like BSD sockets.
User-Space Network Stack (Network Framework): The user-space network stack in macOS (via the Network framework) allows applications to handle networking tasks without directly using the kernel. This provides more flexibility and performance benefits for certain types of network operations, as the networking stack is managed in user space rather than kernel space. This approach reduces overhead and allows more control over networking configurations. In theory, with user-space networking, the application wouldn't be bound by kernel-level socket limits, and it could handle many more simultaneous connections efficiently.
In previous posts on that thread, Quinn mentioned that the Network framework in macOS can rely on the user-space stack (by default) for network operations, but there are still cases where macOS falls back to using the kernel stack (i.e., BSD sockets) under certain conditions. One key example is when the built-in firewall is enabled. This prevents user-space networking from functioning as expected, and the system defaults to using the kernel's BSD sockets for network communication.
In the same discussion, it was also highlighted that NECP (Network Extension Control Plane) could place further limitations on user-space networking, and eventually, systems may run into issues like ENOSPC errors due to excessive simultaneous network flows. This suggests that while user-space networking can offer more flexibility, it's not immune to limits imposed by other system resources or configurations.
Given the information above, I wanted to confirm:
Is the above understanding correct and does the macOS Network framework still use the user-space networking stack in macOS 14 and beyond?
Under what conditions would the system fall back to using the kernel stack (BSD sockets) instead of the user-space stack? For example, does enabling the firewall still disable user-space networking?
What is the practical impact of this fallback on applications that require many simultaneous network connections? Specifically, are there any limitations like the 64K socket limit that developers should be aware of when the system uses the user space stack, and what are the best practices to manage large numbers of connections?
Hi everyone,
I’m working with the DNSServiceGetAddrInfo API and came across the following statement in the documentation:
If the call succeeds then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the query begins and will last indefinitely until the client terminates the query by passing this DNSServiceRef to DNSServiceRefDeallocate(_)
I’m trying to understand exactly what this means in practice. Specifically, after receiving a response with kDNSServiceFlagsMoreComing, being set to 0 does it imply that the OS itself continues querying the DNS periodically or indefinitely, even after we've already received some results? Or does it only continue fetching additional results related to the initial query until we explicitly terminate it?
Any clarification on the behavior of this query would be greatly appreciated!
Thanks in advance!
Hi everyone,
I'm currently working on a project where I need to send multicast packets across all available network interfaces using Apple Network Framework's NWConnectionGroup. Specifically, the MacBook (device I am using for sending multicast requests, MacOS: 15.1) is connected to two networks: Wi-Fi (Network 1) and Ethernet (Network 2), and I need to send multicast requests over both interfaces.
I tried using the .requiredInterface property as suggested by Eskimo in this post, but I’m running into issues.
It seems like I can't create an NWInterface object because it doesn't have any initializers.
Here is the code which I wrote:
var multicast_group_descriptor : NWMulticastGroup
var multicast_endpoint : NWEndpoint
multicast_endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("234.0.0.1"), port: NWEndpoint.Port(rawValue: 49154)!)
var connection_group : NWConnectionGroup
var multicast_params : NWParameters
multicast_params = NWParameters.udp
var interface = NWInterface(NWInterface.InterfaceType.wiredEthernet)
I get following error:
'NWInterface' cannot be constructed because it has no accessible initializers
I also experimented with the .requiredInterfaceType property. Even when I set it to .wiredEthernet and then change it to .wifi, I am still unable to send requests over the Wi-Fi network.
Here is the code I wrote:
var multicast_params : NWParameters
multicast_params = NWParameters.udp
multicast_params.allowLocalEndpointReuse = true
multicast_params.requiredInterfaceType = .wiredEthernet
var ip = multicast_params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
ip.disableMulticastLoopback = true
connection_group = NWConnectionGroup(with: multicast_group_descriptor, using: multicast_params)
connection_group.stateUpdateHandler = { state in
print(state)
if state == .ready {
connection_group.send(content: "Hello from machine on 15".data(using: .utf8)) { error in
print("Send to mg1 completed on wired Ethernet with error \(error?.errorCode)")
var params = connection_group.parameters
params.requiredInterfaceType = .wifi
connection_group.send(content: "Hello from machine on 15 P2 on Wi-Fi".data(using: .utf8)) { error in
print("Send to mg1 completed on Wi-Fi with error \(error?.errorCode)")
}
}
}
}
Is this expected behavior when using NWConnectionGroup? Or is there a different approach I should take to ensure multicast requests are sent over both interfaces simultaneously?
Any insights or suggestions would be greatly appreciated!
Thanks in advance,
Harshal
Hello Everyone,
I'm currently working on a cross-platform application that uses IP-based multicast for device discovery across both Apple and non-Apple devices running the same app. All devices join a multicast group "X.X.X.X" on port Y.
For Apple devices, I am using NWConnectionGroup for multicast discovery, while for non-Apple devices, I am using BSD sockets.
The issue arises when I attempt to send a multicast message to the group using NWConnectionGroup. The message is sent from a separate ephemeral port rather than the multicast port Y. As a result, all Apple processes that are using NWConnectionGroup can successfully receive the multicast message. However, the processes running on the non-Apple devices (using BSD sockets) do not receive the message.
My Questions:
Is there a way to configure NWConnectionGroup to send multicast messages from the same multicast port Y rather than an ephemeral port?
Is there any known behavior or limitation in how NWConnectionGroup handles multicast that could explain why non-Apple devices using BSD sockets cannot receive the message?
How can I ensure cross-platform multicast compatibility between Apple devices using NWConnectionGroup and non-Apple devices using BSD sockets?
Any guidance or suggestions would be greatly appreciated!
Thanks,
Harshal
Hello Everyone,
I’m working on a project that involves multicast communication between processes running on different devices within the same network. For all my Apple devices (macOS, iOS, etc.), I am using NWConnectionGroup, which listens on a multicast address "XX.XX.XX.XX" and a specific multicast port.
The issue occurs when a requestor (such as a non-Apple process) sends a multicast request, and the server, which is a process running on an Apple device using NWConnectionGroup (the responder), attempts to reply. The problem is that the response is sent from a different ephemeral port rather than the port on which the multicast request was received.
If the client is behind a firewall that blocks unsolicited traffic, the firewall only allows incoming packets on the same multicast port used for the initial request. Since the multicast response is sent from a different ephemeral port, the firewall blocks this response, preventing the requestor from receiving it.
Questions:
Is there a recommended approach within the NWConnectionGroup or Network.framework to ensure that responses to multicast requests are sent from the same port used for the request?
Are there any best practices for handling multicast responses in scenarios where the requestor is behind a restrictive firewall?
Any insights or suggestions on how to account for this behavior and ensure reliable multicast communication in such environments would be greatly appreciated.
Thanks,
Harshal
Hello everyone,
We have a use case where we need to disable the sending and receiving of fragmented packets on the network while using NWConnection.
However, even after setting the disableFragmentation flag to true, the connection still sends fragmented packets.We’ve tried setting the flag as follows, but the packets are still being fragmented:
var connection : NWConnection
var udp_options : NWProtocolUDP.Optionsudp_options = NWProtocolUDP.Options()
var connection_parameters = NWParameters(dtls: nil, udp: udp_options)
let ip_options = connection_parameters.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
ip_options.disableFragmentation = true
connection = NWConnection (host: "XX.XX.XX.***", port: NWEndpoint.Port(25000), using: connection_parameters)
The issue we are encountering is that even though we’ve set disableFragmentation to true on the sender, the receiver still receives fragmented UDP packets. This can be observed using Wireshark, where we are sending a 10k byte data from the sender and receiving the fragmented datagram packets on the receiver end while both the devices are on the same WiFi network. Additionally, Wireshark shows that the packet has the "DF" bit set to '0', indicating that fragmentation is allowed.
What is exactly expected from the disableFragmentation flag? Are we misunderstanding how this flag works? Or is there something else we should be doing to ensure that fragmentation is completely disabled?
Looking forward to your insights!
Hello everyone,
I have a question regarding the behavior of network listeners in my application. Here's the scenario I'm seeing:
When I open a .v6 listener, it accepts both IPv4 and IPv6 traffic. However, when I run the netstat -tln command, the socket is shown as udp6.
When I open a NWListener with the IP version set to .any, I receive both IPv4 and IPv6 traffic on the listener. In this case, running netstat -tln shows a udp46 socket.
My understanding is that if I create a socket with .v6, it should only accept IPv6 connections, not both IPv4 and IPv6. However, the .v6 listener appears to be accepting both types of traffic, which is causing some confusion.
Additionally, I am seeking to understand the difference between a udp6 socket and a udp46 socket, and also the difference between sockets created using .v6 and .any. What exactly does udp46 represent, and how is it different from udp6 in terms of accepting traffic?
Is this expected behavior, or is there something I am missing in how the listeners are set up?
Looking forward to hearing your insights!
Hello everyone,
I’m currently working on a Swift project using the Network framework to create a multicast-based communication system. Specifically, I’m implementing both a multicast receiver and a sender that join the same multicast group for communication. However, I’ve run into some challenges with the connection management, replying to multicast messages, and handling state updates for both connections and connection groups.
Below is a breakdown of my setup and the specific issues I’ve encountered.
I have two main parts in the implementation: the multicast receiver and the multicast sender. The goal is for the receiver to join the multicast group, receive messages from the sender, and send a reply back to the sender using a direct connection.
Multicast Receiver Code:
import Network
import Foundation
func setupMulticastGroup() -> NWConnectionGroup? {
let multicastEndpoint1 = NWEndpoint.hostPort(host: NWEndpoint.Host("224.0.0.1"), port: NWEndpoint.Port(rawValue: 45000)!)
let multicastParameters = NWParameters.udp
multicastParameters.multipathServiceType = .aggregate
do {
let multicastGroup = try NWMulticastGroup(for: [multicastEndpoint1], from: nil, disableUnicast: false)
let multicastConnections = NWConnectionGroup(with: multicastGroup, using: multicastParameters)
multicastConnections.stateUpdateHandler = InternalConnectionStateUpdateHandler
multicastConnections.setReceiveHandler(maximumMessageSize: 16384, rejectOversizedMessages: false, handler: receiveHandler)
multicastConnections.newConnectionHandler = newConnectionHandler
multicastConnections.start(queue: .global())
return multicastConnections
} catch {
return nil
}
}
func receiveHandler(message: NWConnectionGroup.Message, content: Data?, isComplete: Bool) {
print("Received message from \(String(describing: message.remoteEndpoint))")
if let content = content, let messageString = String(data: content, encoding: .utf8) {
print("Received Message: \(messageString)")
}
let remoteEndpoint = message.remoteEndpoint
message.reply(content: "Multicast group on 144 machine ACK from recv handler".data(using: .utf8))
if let connection = multicastConnections?.extract(connectionTo: remoteEndpoint) {
connection.stateUpdateHandler = InternalConnectionRecvStateUpdateHandler
connection.start(queue: .global())
connection.send(content: "Multicast group on 144 machine ACK from recv handler".data(using: .utf8), completion: NWConnection.SendCompletion.contentProcessed({ error in
print("Error code: \(error?.errorCode ?? 0)")
print("Ack sent to \(connection.endpoint)")
}))
}
}
func newConnectionHandler(connection: NWConnection) {
connection.start(queue: .global())
connection.send(content: "Multicast group on 144 machine ACK".data(using: .utf8), completion: NWConnection.SendCompletion.contentProcessed({ error in
print("Error code: \(error?.errorCode ?? 0)")
print("Ack sent to \(connection.endpoint)")
}))
}
func InternalConnectionRecvStateUpdateHandler(_ pState: NWConnection.State) {
switch pState {
case .setup:
NSLog("The connection has been initialized but not started")
case .preparing:
NSLog("The connection is preparing")
case .waiting(let error):
NSLog("The connection is waiting for a network path change. Error: \(error)")
case .ready:
NSLog("The connection is established and ready to send and receive data.")
case .failed(let error):
NSLog("The connection has disconnected or encountered an error. Error: \(error)")
case .cancelled:
NSLog("The connection has been canceled.")
default:
NSLog("Unknown NWConnection.State.")
}
}
func InternalConnectionStateUpdateHandler(_ pState: NWConnectionGroup.State) {
switch pState {
case .setup:
NSLog("The connection has been initialized but not started")
case .waiting(let error):
NSLog("The connection is waiting for a network path change. Error: \(error)")
case .ready:
NSLog("The connection is established and ready to send and receive data.")
case .failed(let error):
NSLog("The connection has disconnected or encountered an error. Error: \(error)")
case .cancelled:
NSLog("The connection has been canceled.")
default:
NSLog("Unknown NWConnection.State.")
}
}
let multicastConnections = setupMulticastGroup()
RunLoop.main.run()
Multicast Sender Code:
import Foundation
import Network
func setupConnection() -> NWConnection {
let params = NWParameters.udp
params.allowLocalEndpointReuse = true
return NWConnection(to: NWEndpoint.hostPort(host: NWEndpoint.Host("224.0.0.1"), port: NWEndpoint.Port(rawValue: 45000)!), using: params)
}
func sendData(using connection: NWConnection, data: Data) {
connection.send(content: data, completion: .contentProcessed { nwError in
if let error = nwError {
print("Failed to send message with error: \(error)")
} else {
print("Message sent successfully")
}
})
}
func setupReceiveHandler(for connection: NWConnection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65000) { content, contentContext, isComplete, error in
print("Received data:")
print(content as Any)
print(contentContext as Any)
print(error as Any)
setupReceiveHandler(for: connection)
}
}
let connectionSender = setupConnection()
connectionSender.stateUpdateHandler = internalConnectionStateUpdateHandler
connectionSender.start(queue: .global())
let sendingData = "Hello, this is a multicast message from the process on mac machine 144".data(using: .utf8)!
sendData(using: connectionSender, data: sendingData)
setupReceiveHandler(for: connectionSender)
RunLoop.main.run()
Issues Encountered:
Error Code 0 Even When Connection Refused:
On the receiver side, I encountered this log:
nw_socket_get_input_frames [C1.1.1:1] recvmsg(fd 8, 9216 bytes) [61: Connection refused]
Error code: 0
Ack sent to 10.20.16.144:62707
Questions:
how do I reply to the message if above usage pattern is wrong?
how do I get a NWConnection from the received message to create a separate connection for communication with the sender.
Any insights or suggestions on resolving these issues or improving my multicast communication setup would be greatly appreciated.
Thanks :)
Hello,
I'm working with the Network framework in Swift and have encountered an issue when attempting to create multiple NWListener instances on the same port. I am specifically trying to set the allowLocalEndpointReuse property on the NWParameters used for both listeners, but it seems that even with this property set, the second listener fails to start.
Here’s a simplified version of my implementation:
import Foundation
import Network
class UDPServer {
private var listener1: NWListener?
private var listener2: NWListener?
private let port: NWEndpoint.Port
init(port: UInt16) {
self.port = NWEndpoint.Port(rawValue: port) ?? NWEndpoint.Port(45000)
startListeners()
}
private func startListeners() {
let udpOptions = NWProtocolUDP.Options()
let params = NWParameters(udp: udpOptions)
params.allowLocalEndpointReuse = true
// Create first listener
do {
listener1 = try NWListener(using: params, on: port)
listener1?.start(queue: .global())
} catch {
print("Failed to create Listener 1: \(error)")
}
// Create second listener
do {
listener2 = try NWListener(using: params, on: port)
listener2?.start(queue: .global())
} catch {
print("Failed to create Listener 2: \(error)")
}
}
}
// Usage example
let udpServer = UDPServer(port: 45000)
RunLoop.main.run()
Observations:
I expect both listeners to operate without issues since I set allowLocalEndpointReuse to true.
However, when I attempt to start the second listener on the same port, it fails with an error.
output
nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error [48: Address already in use]
nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
nw_listener_start_on_queue [L2] nw_path_create_evaluator_for_listener failed
Listener 1 ready on port 45000
Listener 2 failed: POSIXErrorCode(rawValue: 48): Address already in use
Listener 2 cancelled
Questions:
Is there a limitation in the Network framework regarding multiple listeners on the same port even with allowLocalEndpointReuse?
Should I be using separate NWParameters for each listener, or is it acceptable to reuse them?
Even when trying to initialize NWParameters with NWProtocolUDP.Options, it doesn't seem to change anything. What steps should I take to modify these properties effectively?
If I wanted to set the noDelay option for TCP, how would I do that? Even when initializing NWParameters with init(.tls: , .tcp:), it doesn't seem to have any effect.
Any insights or recommendations would be greatly appreciated!
Thank you!
Hello Everyone,
I have a use case where I wanted to interpret the "Data" object received as a part of my NWConnection's recv call. I have my interpretation logic in cpp so in swift I extract the pointer to the raw bytes from Data and pass it to cpp as a UnsafeMutableRawPointer.
In cpp it is received as a void * where I typecast it to char * to read data byte by byte before framing a response.
I am able to get the pointer of the bytes by using
// Swift Code
// pContent is the received Data
if let content = pContent, !content.isEmpty {
bytes = content.withUnsafeBytes { rawBufferPointer in
guard let buffer = rawBufferPointer.baseAddress else {
// return with null data.
}
// invoke cpp method to interpret data and trigger response.
}
// Cpp Code
void InterpretResponse (void * pDataPointer, int pDataLength) {
char * data = (char *) pDataPointer;
for (int iterator = 0; iterator < pDataLength; ++iterator )
{
std::cout << data<< std::endl;
data++;
}
}
When I pass this buffer to cpp, I am unable to interpret it properly.
Can someone help me out here?
Thanks :)
Harshal
Hello,
I am using the withUnsafePointer API in Swift and have a question regarding the validity of the pointer returned by this API. Specifically, I want to understand if the pointer remains valid if the CPU performs a context switch due to its time-slicing mechanism while the closure is executing.
Is the pointer returned by withUnsafePointer guaranteed to be valid throughout the entire execution of the closure, even if a CPU context switch occurs as part of time slicing?
Hi everyone,
I’m working on an app where I need to separate IPv4 and IPv6 traffic on a specific port, let's say "X", using the Network Framework. However, I’ve run into a problem: it appears that I'm only able to open a single NWListener for a given port number. I was under the impression that I should be able to create distinct IPv4 and IPv6 listeners for the same port "X".
Here’s the sample code I’ve written:
var params: NWParameters
var l1: NWListener
var l2: NWListener
params = NWParameters.udp
let protocolOptions = params.defaultProtocolStack.internetProtocol! as NWProtocolOptions
let ipOptions = protocolOptions as! NWProtocolIP.Options
ipOptions.version = .v6
l1 = try NWListener(using: params, on: NWEndpoint.Port(rawValue: 54192)!)
l1.stateUpdateHandler = InternalListenerStateHandler
l1.newConnectionHandler = InternalNewConnectionHandler
l1.start(queue: .global())
ipOptions.version = .v4
l2 = try NWListener(using: params, on: NWEndpoint.Port(rawValue: 54192)!)
l2.stateUpdateHandler = InternalListenerStateHandler
l2.newConnectionHandler = InternalNewConnectionHandler
l2.start(queue: .global())
I’m trying to figure out why this approach isn’t working. Is there a way to manage both IPv4 and IPv6 traffic on the same port using the Network Framework, or is there something I’m overlooking in my setup?
Additionally, when I switch to the BSD framework, I can successfully open two sockets on the same port by setting the "IPV6_ONLY" property on the IPv6 socket.
Any insights or advice would be greatly appreciated!
Thanks,
Harshal
Hello everyone,
I'm currently developing an application that involves network communication on an Apple Watch.
I'm using Apple's network framework for communication and for validation purpose I need to establish communication between an Apple Watch simulator and an external command-line tool (nc). To send the connection requests, I require the IP address of the Apple Watch simulator.
I've been unable to locate the IP address of the Apple Watch simulator after searching is settings everywhere. This IP address is required for setting up network requests and ensuring effective testing and integration of Apple's network framework.
Could someone please provide guidance on how to obtain the IP address of the Apple Watch simulator? Specifically, I need to know how to retrieve this IP address so that I can configure my external command-line application (nc) to send connection requests to the simulator.
Thank you for your assistance and insights!
Regards,
Harshal.
Hello everyone,
I'm encountering an issue with Swift and C++ interoperability when passing a void pointer between Swift and C++ functions. When I pass pMessageBuffer (an UnsafeMutableRawPointer) from Swift to MyCppClass.NFCompletion (a static c++ function), which expects a reference to void pointer, Swift throws an error "Cannot convert value of type 'UnsafeMutableRawPointer' to expected argument type 'Optional' ".
Here is a sample code to help in better visualization of the usecase.
Cpp Code
class MyCppClass {
public:
static void SendData(void *pMessage, TUInt16 pMessageLength) {
// Assume vSocket is my Swift object held in C++.
vSocket.Send(pMessage, pMessageLength);
}
static void NFCompletion(void * & pBuffer, TInt64 pErrorCode, VPtr pCompletionFunction) {
// Process the buffer.
}
};
Swift Code:
public class MySwiftClass {
var vConnection: NWConnection?
public init() {}
public func Send(_ pMessageBuffer: UnsafeMutableRawPointer, _ pMessageLength: TSUInt16) {
let messageData = Data(bytesNoCopy: pMessageBuffer, count: Int(pMessageLength), deallocator: .none)
self.vConnection?.send(content: messageData, completion: .contentProcessed { nw_error in
var error_code: TSInt64 = 0
if let nw_error = nw_error {
error_code = self.InternalGetNetworkErrorCode(nw_error)
}
// Here's where the issue arises:
MyCppClass.NFCompletion(pMessageBuffer, TInt64(error_code), self.uCompletionHandler)
})
}
// Example function to handle network errors
private func InternalGetNetworkErrorCode(_ error: Error) -> TSInt64 {
// Implementation to convert nw_error to TSInt64 error code
return 0 // Placeholder return value
}
}
Could someone please help me understand why this conversion error occurs? How should I correctly handle passing a void pointer between Swift and C++ functions, ensuring compatibility and proper memory management?
Note: TSInt64 is typealias for swift Int and TInt64 is alias of c++ int_64t.
Thank you in advance for your assistance!
Regards,
Harshal