Issue with Multiple NWListeners on the Same Port in Network Framework

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:

  1. Is there a limitation in the Network framework regarding multiple listeners on the same port even with allowLocalEndpointReuse?

  2. Should I be using separate NWParameters for each listener, or is it acceptable to reuse them?

  3. 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?

  4. 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!

Answered by DTS Engineer in 809452022

I don’t think that you’re going to be able to make this work. Network framework’s UDP support is rather limited, something I called out in TN3151 Choosing the right networking API. In the specific case of an NWListener, the UDP use case it supports is kinda like a TCP listener. That is:

  1. You listen on a port.

  2. When a UDP packet comes in to that port, it spawns an NWConnection for that flow, where a flow is defined as a series of datagrams that all have the same local IP / local port / remote IP / remote port tuple.

This model isn’t compatible with multiple listeners, because it’s not obvious which listener would see the flow and then spawn the connection that ends up owning that flow.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I don’t think that you’re going to be able to make this work. Network framework’s UDP support is rather limited, something I called out in TN3151 Choosing the right networking API. In the specific case of an NWListener, the UDP use case it supports is kinda like a TCP listener. That is:

  1. You listen on a port.

  2. When a UDP packet comes in to that port, it spawns an NWConnection for that flow, where a flow is defined as a series of datagrams that all have the same local IP / local port / remote IP / remote port tuple.

This model isn’t compatible with multiple listeners, because it’s not obvious which listener would see the flow and then spawn the connection that ends up owning that flow.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@DTS Engineer Thanks,

Given your explanation about the limitations of the Network framework's UDP support, could you clarify in which scenarios the allowLocalEndpointReuse property would actually be effective? Additionally, I’m also trying to set TCP-specific options like noDelay and connectionTimeout on a NWConnection. How can I configure these options correctly?

I’ve noticed that even when creating NWParameters with an initializer that takes NWProtocolTCP.Options, it doesn’t seem to apply as expected. What should I do?

Thanks :)

could you clarify in which scenarios the allowLocalEndpointReuse property would actually be effective?

The obvious one is for TCP listeners, where in BSD Sockets you’d set SO_REUSEADDR to avoid the time-wait state delay.

I’m also trying to set TCP-specific options like noDelay and connectionTimeout on a NWConnection.

On an outgoing connection? Or on incoming connections spawned by the listener?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

On an outgoing connection? Or on incoming connections spawned by the listener?

On an outgoing TCP connection.

Accepted Answer

This works for me:

let endpoint: NWEndpoint = …
let tcp = NWProtocolTCP.Options()
tcp.noOptions = true
let parameters = NWParameters(tls: nil, tcp: tcp)
let conn = NWConnection(to: endpoint, using: parameters)

Specifically, if I don’t set noOptions the result SYN packets look like this:

… 192.168.1.171.51626 > 1.2.3.4.12345: Flags [S], seq 1774463392, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 836301535 ecr 0,sackOK,eol], length 0

But the above above generates a SYN like this:

… 192.168.1.171.51627 > 1.2.3.4.12345: Flags [S], seq 4012611142, win 65535, length 0

Be aware that the connection timeout doesn’t make a lot of sense given Network framework’s wait-for-connectivity approach.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the response @DTS Engineer, now if i set an option say "EnableFastOpen" on a NWConnection object. How can I check the values for it at a later point in time. Is there a way I get it from "params" property of a "NWConnection" instance?

Is there a way I get it from params property of a NWConnection instance?

Ah, um, yeah, via the parameters property:

let connection: NWConnection = …
print(connection.parameters.allowFastOpen)

I suspect that’ll only return the parameters that you applied; that is, it doesn’t reflect the actual parameters being used on the ‘wire’. For those you need to get the metadata:

guard let metadata = connection.metadata(definition: NWProtocolTCP.definition) as? NWProtocolTCP.Metadata else {
    … no TCP metadata …
}
… use NWProtocolTCP.Metadata properties …

but very few TCP options are reflected in the metadata.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

It’s better if you reply as a reply, rather than as a comment. See Quinn’s Top Ten DevForums Tips for this and other titbits.

Could you show me how can i check by reading the properties if my TCP-specific options, like noDelay, are set?

To do this, get the parameters from the connection and get the protocol options from there. For example:

let conn: NWConnection = …
let parameters = conn.parameters
guard let options = parameters.defaultProtocolStack.transportProtocol as? NWProtocolTCP.Options else {
    …
}
print(options.noDelay)

IMPORTANT This only works on macOS 15 and later. On earlier systems there was a bug in the Swift-to-C glue that caused this to fail from Swift (r. 99436602).

Keep in mind that this only represents the options you requested; there’s no guarantee that these options are in effect. For simple options like noDelay, failing to apply the option would most definitely be a bug. However, not all options are that cut’n’dry.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Issue with Multiple NWListeners on the Same Port in Network Framework
 
 
Q