NWListener seems to selectively ignore incoming data

So this is a bit of a weird one for me. I'm wanting to move my internal applications from CocoaAsync over the Network framework.

Communications is simple UDP and followed some of the advice from the forum as well as one of the WWDC videos talking about setting up NWConnection and NWListener.

I've created the classes and made a quick UI that sets up a sender and listener, with a couple of buttons that send two different UDP commands to my device.

Watching wire shark I see the data go out and the expected return message in WireShark, however, I only see the data come into the application from one type of message and not the other.

As you can see from the image above there are 4 packets (two sent, two received) but the program only seems to want to receive one return packet and not the other.

I double-checked and made sure that I can accept multiple receive messages and even tried sending from my iPad with its own message, and the message I was expecting the get from the other device. All seemed fine. I then checked the messaging in PacketSender and everything comes and goes as expected with messages.

This leads me back to a probable issue with how I'm implimenting the framework. Have I overlooked a parameter somewhere?

Sender Class

class UDPSender {
  var connection:NWConnection
   
  var connectionParams:NWParameters = .udp
   
   
  init() {
    connectionParams.allowLocalEndpointReuse = true
    connectionParams.requiredLocalEndpoint = NWEndpoint.hostPort(host: "10.0.1.122", port: 2353)
    connection = NWConnection(host: "10.0.1.161", port: 22202, using: connectionParams)
   
     
    connection.stateUpdateHandler = {(newState) in
      switch newState {
      case .ready:
        print("comms ready")
      case .failed(let error):
        print(error)
      default:
        break
      }
    }
   
    connection.start(queue: DispatchQueue(label: "UDPSender"))
  }
   
  func send(message:Data) {
    connection.send(content: message, completion: .contentProcessed({ (error) in
      if let error = error {
        print("Send error: \(error)")
      } else {
        print("Message Sent: \(message)")
      }
    }))
  }

} 

Listener Class

class UDPListener {

  var listener:NWListener
   
  var listenerParams:NWParameters = .udp
   
   
   
  init(onPort port:NWEndpoint.Port) {
     
    listenerParams.allowLocalEndpointReuse = true
    listenerParams.acceptLocalOnly = false

     
    listener = try! NWListener(using: listenerParams, on: port)
     
     
    listener.stateUpdateHandler = {(newState) in

       
      switch newState {
      case .ready:
        print("listener ready")
      case .failed(let error):
        print(error)
      default:
        break
      }
    }
   
   
    listener.newConnectionHandler = {[weak self] (newConnection) in
      if let strongSelf = self {
        newConnection.start(queue: .main)
        strongSelf.receive(on: newConnection)
      }
       
    }
     
    listener.start(queue: DispatchQueue(label: "Listener"))
  }
   
  func receive(on connection:NWConnection) {
   
    connection.receiveMessage { (content, context, isComplete, error) in
       
      print(isComplete)
       
      if error != nil {
      print("Listener Error: \(String(describing:error))")
      }
//      if let package = content {
        let str = String(decoding: content!, as: UTF8.self)
        print("\(connection.endpoint) :: \(str)")
//      }
       
       
      if error == nil {
        self.receive(on: connection)
      }
    }
  }
}

ContentView

struct ContentView: View {

   
  let listener = UDPListener(onPort:2353)
  let comms = UDPSender()
   

  var body: some View {
    HStack {
      Text("Press here")
       
      Button(action: {
        comms.send(message:"!nwd0#".data(using: .utf8)!)
         
      }, label: {
        Text("Send NWD")
      })
       
       
      Button(action: {
        comms.send(message: "!nsd10.0.1.122:2353:\"From Button Press\"#".data(using: .utf8)!)
      }, label: {
        Text("Send NSD")
      })
    }.padding(10)
  }
}

So, let me see if I have this straight:

  • 10.0.1.122 is the client IP.

  • And 2353 is its port.

  • 10.0.1.161 is the server IP.

  • 22202 is its port.

Right?

If what’s the deal with port 6454? ’cause that’s the source port of the second packet your trace.

Share and Enjoy

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

Hi Quinn,

You are correct:

10.0.1.122:2353 is my local machine- there's no rhyme or reason for the port selection, I've actually even tried a few different ones just to test.

10.0.1.161:22202 is the server/remote device. The device has 4 known ports to communicate on 22202 and 6454 are two of them. I tried the test again this morning and the first packet (!nw0#) is coming back on 22202, but the longer packet is now coming back on 6454. The results are the same though- the first packet response (coming back from 22202) is not recognized by the application, but the second one (coming back on 6454) I again sent a packet from my iPad (IP 10.0.1.27) and the application accepted it without issue.

When you use NWConnection with UDP the connection object represents a UDP flow, that is, a sequence of datagrams identified by the tuple local IP / local port / remote IP / remote port. If the remote peer sends you a datagram with a different remote port, that’s not part of the flow and thus won’t be received on your connection.

Share and Enjoy

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

My understanding (and this may be that I'm just used to BSD sockets) is that it shouldn't matter what the source port is of the remote device as long as the data is sent back to 2353 and thus being listened to by the NWListener.

Network framework and BSD Sockets have a very different model for UDP. An NWConnection is more like a connected UDP socket.

Share and Enjoy

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

NWListener seems to selectively ignore incoming data
 
 
Q