Stop and restart of NWListener fails on real devices

I would like to receive some data via UDP in an endless loop until the user cancels the data receiving. I created an NWListener, and set it up. It receives data as expected. However once I call .cancel() on my NWListener it changes the state to .cancelled but afterwards I have no chance to restart the listening process again. It blocks somehow the port. I would be able to restart it on another port, but this is not what I want to do. On the same port it ends up in this error message:


2020-02-20 23:00:28.957501+0100 networking[10942:7110395] [] nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error [48: Address already in use]
2020-02-20 23:00:28.957642+0100 networking[10942:7110395] [] nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
2020-02-20 23:00:28.957735+0100 networking[10942:7110395] [] nw_listener_start_locked [L2] nw_path_create_evaluator_for_listener failed
 NWListener Handler called
Listener: Failed POSIXErrorCode: Address already in use

The error only occurs on real devices (im simulator it works as expected), and only if the real device has either recieved at least one UDP message, or after starting the NWListener twice without .cancel() it before.


I created a dedicated small sample project in Xcode to nail the issue down and to ensure it has nothing to do with the rest of my actual project. This is the ViewController I created to show the problem, I just connected two buttons to start/stop UDP listening:



import UIKit
import Network

class ViewController: UIViewController {

    var udpListener:NWListener?
    var backgroundQueueUdpListener   = DispatchQueue(label: "udp-lis.bg.queue", attributes: [])
    var backgroundQueueUdpConnection = DispatchQueue(label: "udp-con.bg.queue", attributes: [])
            
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myOnButton(self)
    }
    
    @IBAction func myOnButton(_ sender: Any) {
        
        do {
            self.udpListener = try NWListener(using: .udp, on: 55555)
            self.udpListener?.stateUpdateHandler = { (listenerState) in
                print(" NWListener Handler called")
                switch listenerState {
                case .setup:
                    print("Listener: Setup")
                case .waiting(let error):
                    print("Listener: Waiting \(error)")
                case .ready:
                    print("Listener:  Ready and listens on port: \(self.udpListener?.port?.debugDescription ?? "-")")
                case .failed(let error):
                    print("Listener: Failed \(error)")
                case .cancelled:
                    print("Listener:    Cancelled by myOffButton")
                default:
                    break;
                    
                }
            }
            
            self.udpListener?.start(queue: backgroundQueueUdpListener)
            self.udpListener?.newConnectionHandler = { (incomingUdpConnection) in
                print(" NWConnection Handler called ")
                incomingUdpConnection.stateUpdateHandler = { (udpConnectionState) in
                    
                    switch udpConnectionState {
                    case .setup:
                        print("Connection:  setup")
                    case .waiting(let error):
                        print("Connection:  waiting: \(error)")
                    case .ready:
                        print("Connection:  ready")
                        self.processData(incomingUdpConnection)
                    case .failed(let error):
                        print("Connection:  failed: \(error)")
                    case .cancelled:
                        print("Connection:    cancelled")
                    default:
                        break
                    }
                }

                incomingUdpConnection.start(queue: self.backgroundQueueUdpConnection)
            }
            
        } catch {
            print("       CATCH")
        }
        
    }
    @IBAction func myOffButton(_ sender: Any) {
        udpListener?.cancel()
    }
  
    func processData(_ incomingUdpConnection :NWConnection) {
        
        incomingUdpConnection.receiveMessage(completion: {(data, context, isComplete, error) in
            
            if let data = data, !data.isEmpty {
                if let string = String(data: data, encoding: .ascii) {
                    print ("DATA       = \(string)")
                }
            }
            //print ("context    = \(context)")
            print ("isComplete = \(isComplete)")
            //print ("error      = \(error)")

            self.processData(incomingUdpConnection)
        })
        
    }
   
}

I hope someone can help me, I spent now several hours to solve this issue, but without any success so far.

To test incoming messages via UDP, I used this shell script:


#!/bin/zsh
while :
do
  echo "1234" | nc -cu -w 0 192.168.2.106 55555
  sleep 1
  echo "567890" | nc -cu -w 0 192.168.2.106 55555
  sleep 1
done

It’s probably best to simply not stop the listener but rather reject connections you don’t care about. However, if you’re determined to stop and then restart the listener, you need to learn about

SO_REUSEADDR
and
SO_REUSEPORT
(see the
setsockopt
man page and the BSD Sockets tutorial of your choice) and their equivalent in Network framework, namely
allowLocalEndpointReuse
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
As suggested by Quinn, I have tried to use allowLocalEndpointReuse with my TCP server but I can't make it work for some reason.

I'm using the following code to test it out:

Code Block swift
let parameters = NWParameters.tcp
parameters.allowLocalEndpointReuse = true
listener = try! NWListener(using: parameters, on: 15000)
listener?.newConnectionHandler = { conn in
// ...

  1. Run app

  2. Accept incoming TCP connection; all is well

  3. Stop and restart app

Expected behaviour: It rebinds, like SO_REUSEADDR would allow. (I use BSD sockets elsewhere in the same app and they have no problem rebinding to the same ports.)

Actual behaviour:
Code Block
Target[2669:2271422] [] nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error [48: Address already in use]
Target[2669:2271422] [] nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
Target[2669:2271422] [] nw_listener_start_locked [L1] nw_path_create_evaluator_for_listener failed

...but if I wait a minute before restarting the app then everything is fine.

Am I using allowLocalEndpointReuse wrong?
This is working for me. I tested it as follows:
  1. I created new Mac project and set up the listener using the code pasted in at the end of this response.

  2. I ran it on my Mac (running 10.15.5).

  3. I started the server.

  4. I used nc to connect to it.

  5. I stopped the server and then started it again.

  6. I used nc to connect to it again.

Below are the server and client transcripts:

Code Block
Enabled
will start
did start
waiting(POSIXErrorCode: Network is down)
ready
did receive connection, now: 2020-07-21 08:28:09 +0000
did close connection
will stop
did stop
will start
did start
waiting(POSIXErrorCode: Network is down)
ready
did receive connection, now: 2020-07-21 08:28:13 +0000
did close connection


.

Code Block
% nc 127.0.0.1 12345
Goodbye Cruel World!
2020-07-21 08:28:09 +0000
% nc 127.0.0.1 12345
Goodbye Cruel World!
2020-07-21 08:28:13 +0000


As you can see, the two responses received by the client come from different sides of the stop and start sequence.

Share and Enjoy

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



Code Block
var _listener: NWListener? = nil
func startStopListener() {
if let listener = self._listener {
self._listener = nil
self.stop(listener: listener)
} else {
self._listener = self.start()
}
}
func start() -> NWListener? {
print("will start")
let params = NWParameters.tcp
params.allowLocalEndpointReuse = true
let listener: NWListener
do {
listener = try NWListener(using: params, on: 12345)
} catch {
print("did not start, error: \(error)")
return nil
}
listener.stateUpdateHandler = { state in
print(state)
}
listener.newConnectionHandler = { connection in
let now = Date()
print("did receive connection, now: \(now)")
let brushoffMessage = Data("Goodbye Cruel World!\r\n\(now)\r\n".utf8)
connection.send(content: brushoffMessage, completion: .contentProcessed({ _ in
connection.cancel()
print("did close connection")
}))
connection.start(queue: .main)
}
listener.start(queue: .main)
print("did start")
return listener
}
func stop(listener: NWListener) {
print("will stop")
listener.stateUpdateHandler = nil
listener.newConnectionHandler = nil
listener.cancel()
print("did stop")
}

Hello, I have exactly the same problem!
It happens on iPhone 11 Pro iOS 13.5.1, but iPhone 11 with same iOS not.
I have screen and on present ConnectionListener is initialized and on dismiss it deinitialized.

First time open screen everything is ok:
Code Block
Listener stateUpdateHandler: waiting(POSIXErrorCode: Network is down)
Listener stateUpdateHandler: ready
"📞 New connection: 10.0.1.2:50655 establish"
"Connection stateUpdateHandler: preparing"
"Connection stateUpdateHandler: ready"

but for next n-tries only this messages:
Code Block
[] 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_locked [L3] nw_path_create_evaluator_for_listener failed
Listener stateUpdateHandler: waiting(POSIXErrorCode: Network is down)
Listener stateUpdateHandler: failed(POSIXErrorCode: Address already in use)

Here is my code for listening inbound connection:
Code Block
final class ConnectionListener {
        
  var dataReceivedHandler: ((Data) -> Void)?
   
  private let port: UInt16
  private let maxLength: Int
   
  private var listener: NWListener!
  private var connection: NWConnection!
   
  init(port: UInt16, maxLength: Int) {
    self.port = port
    self.maxLength = maxLength
  }
   
  deinit {
    print("❌ Deinitialize \(self)")
  }
   
  // MARK: Public API
   
  func startListening() {
    let parameters = NWParameters.tcp
    parameters.allowLocalEndpointReuse = true
    self.listener = try! NWListener(using: parameters, on: NWEndpoint.Port(integerLiteral: port))
    self.listener.stateUpdateHandler = { state in print("Listener stateUpdateHandler: \(state)") }
    self.listener.newConnectionHandler = { [weak self] in self?.establishNewConnection($0) }
    self.listener.start(queue: .main)
  }
   
  func stopListening() {
    listener.cancel()
    connection?.cancel()
  }
   
  // MARK: Private API
   
  private func establishNewConnection(_ newConnection: NWConnection) {
    connection = newConnection
    debugPrint("📞 New connection: \(String(describing: connection.endpoint)) establish")
    connection.stateUpdateHandler = { [weak self] state in
      guard let self = self else { return }
      debugPrint("Connection stateUpdateHandler: \(state)")
      switch state {
      case .ready:
        debugPrint("Connection: start receiving ✅")
        self.receive(on: self.connection)
      default: break
      }
    }
    self.connection.start(queue: .main)
  }
   
  private func receive(on connection: NWConnection) {
    connection.receive(minimumIncompleteLength: 1, maximumLength: maxLength, completion: { [weak self] content, context, isCompleted, error in
      guard let self = self else { return }
      if let frame = content {
        self.dataReceivedHandler?(frame)
      }
      self.receive(on: connection)
    })
  }
   
}


Maybe I have some problem in my Listener? Thanks!
I also create GitHub sample project, which is shown a problem

GitHub project
I am also facing the same issue. Any fixes?
Just test on my iPhone 11 Pro Max with iOS 13.6.1, the same issue still exists.
Have set the allowLocalEndpointReuse as true
I am also experiencing a similar issue.

Basically:

On macOS, macOS Playground, and iOS, allowLocalEndpointReuse always behaves as if it were false, even when set to true
On iOS Simulator and iOS Playground, allowLocalEndpointReuse always behaves as if it were true, even when set to false

I have submitted a radar against Network Framework (FB8658821) which also has a test project.
I have also posted the content of the radar to OpenRadar for others to see (search for FB8658821).

But basically, here is the output I get:

On macOS & iOS:
Running test with allowLocalEndpointReuse false

Code Block
[OK] Listener #2 not started. Did not allow endpoint reuse, as expected: POSIXErrorCode: Address already in use

Running test with allowLocalEndpointReuse true
Code Block
[FAIL] Failed to allow endpoint reuse despite being requested: POSIXErrorCode: Address already in use


On iOS Simulator & iOS Playground:
Running test with allowLocalEndpointReuse false
Code Block
[FAIL] Allowed endpoint reuse, despite asking to deny it

Running test with allowLocalEndpointReuse true
Code Block
[OK] Listener #2 started. Did allow endpoint reuse, as expected
The problem is the 55555 port, i have same problem .
We can't change the port, we have devices in production that use this port.
Any solution?

@eskimo

I'm got the above working but I also need to advertise a Bonjour service with a TXT record. When I add the following code the state changes to failed and the whole thing stops working :( Any hints?

listener.service = .init(
                name: "SETup",
                type: "_http._tcp.",
                domain: "local",
                txtRecord: NWTXTRecord([
                    "uuid": "..."
                ])
            )

Prints the following results:

Service started.
dnssd_clientstub ConnectToServer: connect() failed path:/var/run/mDNSResponder Socket:11 Err:-1 Errno:2 No such file or directory
waiting(POSIXErrorCode: Network is down)
ready
failed(-65563: ServiceNotRunning)

FYI, I have enabled incoming/outgoing connections and added bonjour plist key/value pair.

we can't have two applications listening to the same port at the same time

Well, that’s different from the original issue, which is about starting and stopping a listener in quick succession. However…

I took a look at thibault.ml’s bug (FB8658821) and there’s definitely a real issue here. The resulting changes are not in any currently shipping or seeded build. Moreover, I can’t guarantee these changes will resolve your issue. This is a tricky problem, and I won’t say anything definitive until we see what ships.

Share and Enjoy

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

Stop and restart of NWListener fails on real devices
 
 
Q