Issue with Network Framework and UDP Broadcast

Hello, I've implemented UDP broadcast as follows and have the com.apple.developer.networking.multicast
entitlement. This code works on the iOS simulator, but not a physical iOS device. The error I get is
Code Block language
2020-12-14 12:59:48.489322-0700 CBWMobile[681:49802] [] nw_socket_connect connectx failed [48: Address already in use]


Code Block swift
import Foundation
import Network
protocol BroadcastResponseHandler {
  func handleResponse(_ client: UDPBroadcaster, data: Data)
}
class DiscoveredLocalDevices: BroadcastResponseHandler {
  private let internalQueue = DispatchQueue(label: "com.discoveredDevices.queue", qos: .default, attributes: .concurrent)
  private var _devices = Set<Device>()
  public var devices: Set<Device> {
    get {
      return internalQueue.sync {
        _devices
      }
    }
    set(newState) {
      internalQueue.async {
        self._devices = newState
      }
    }
  }
  func handleResponse(_ client: UDPBroadcaster, data: Data) {
    if data.count == 61 {
      if let device = Device(udpBroadcastResponse: data) {
        var previousDevices = devices
        previousDevices.insert(device)
        devices = previousDevices
      }
    }
  }
}
class UDPBroadcaster {
  private let udpBroadcastPort: NWEndpoint.Port = 65431
  private let outHost: NWEndpoint.Host = "255.255.255.255"
  private var outConnection: NWConnection?
  private var listener: NWListener?
  var delegate: BroadcastResponseHandler?
  var connections = [NWConnection]()
  var backgroundQueueUdpListener = DispatchQueue(label: "udp-lis.bg.queue", attributes: [])
  var backgroundQueueUdpConnection = DispatchQueue(label: "udp-con.bg.queue", attributes: [])
  init() {
    let params = NWParameters.udp
    params.allowLocalEndpointReuse = true
//    params.allowFastOpen = true
    outConnection = NWConnection(host: outHost, port: udpBroadcastPort, using: params)
    listener = try? NWListener(using: params, on: udpBroadcastPort)
  }
  // MARK: - Broadcast
  func startBroadcast() {
    outConnection?.stateUpdateHandler = { newState in
      switch newState {
        case .ready:
          print("OutConnectionState: Ready\n")
          self.sendBroadcastMessage()
        case .setup:
          print("OutConnectionState: Setup\n")
        case .cancelled:
          print("OutConnectionState: Cancelled\n")
        case .preparing:
          print("OutConnectionState: Preparing\n")
        default:
          print("ERROR! OutConnectionState not defined!\n")
      }
    }
    outConnection?.start(queue: backgroundQueueUdpConnection)
  }
  private func sendBroadcastMessage() {
    outConnection?.send(content: cbwBroadcastMessage(), completion: NWConnection.SendCompletion.contentProcessed(({ NWError in
      if NWError == nil {
        print("Broadcast message was sent\n")
      } else {
        print("ERROR! Error when broadcast message (Type: Data) sending. NWError: \n \(NWError!)")
      }
    })))
  }
  private let cbwBroadcastMessage = { () -> Data in
    var data = Data(capacity: 13)
    if let header = "ControlByWeb".data(using: String.Encoding.ascii) {
      data.append(header)
      data.append(0)
    }
    return data
  }
  // MARK: - Listener
  func startListener() {
    listener?.service = NWListener.Service(type: "_cbwMobilePlus._udp")
    listener?.stateUpdateHandler = { newState in
      switch newState {
        case .ready:
          print("ListenerState: Ready\n")
          return
        case .waiting(let error):
          print(error)
        case .setup:
          print("ListenerState: Setup\n")
        case .cancelled:
          print("ListenerState: Cancelled\n")
        case .failed(let error):
          print(error)
        default:
          print("ERROR! ListenerState not defined!\n")
      }
    }
    listener?.newConnectionHandler = { newConnection in
      newConnection.stateUpdateHandler = { newState in
        switch newState {
          case .ready:
            print("ListenerConnectionState: Ready\n")
            self.receiveBroadcastResponse(on: newConnection)
            return
          case .setup:
            print("ListenerConnectionState: Setup\n")
          case .cancelled:
            print("ListenerConnectionState: Cancelled\n")
          case .preparing:
            print("ListenerConnectionState: Preparing\n")
          default:
            print("ERROR! ListenerConnectionState not defined!\n")
        }
      }
      newConnection.start(queue: self.backgroundQueueUdpListener)
    }
    listener?.start(queue: backgroundQueueUdpListener)
  }
  private func receiveBroadcastResponse(on connection: NWConnection) {
    connection.receiveMessage { data, _, _, _ in
      guard let data = data else {
        print("Error: Received nil Data\n")
        return
      }
      guard self.delegate != nil else {
        print("Error: UDPClient response handler is nil\n")
        return
      }
      self.delegate?.handleResponse(self, data: data)
    }
  }
}



It's hard to know what is exactly happening without taking a deeper look at your setup.

The first thing to check here is that you are getting the local network privacy prompt on the send side. This is very important. Try the send on the physical device because if you are not getting the prompt then you know things are going sideways already.

When you receive the error you stated, check the .waiting state on outConnection.
Code Block swift
case .waiting(let error):
/* Attempt to account for a disallowed local network privacy prompt */
if let currentPathDescription = self.outConnection.currentPath?.debugDescription {
print("Check local network privacy permissions: \(currentPathDescription)")
} else {
print("Connection waiting: \(error.localizedDescription)")
}

This should tell you whether the your app is being disallowed due to a cached local network privacy permission.

I am not exactly sure what the goal is for the listener. It looks like you want to send UDP broadcast packets via 255.255.255.255, but then you had a NWListener advertising a service for "_cbwMobilePlus._udp". Are you wanting to listen for broadcast packets with port 65431 or have a client connect to your Bonjour service and then just directly communicate with this endpoint?

Last, I would recommend decoupling your NWConnection logic from your NWListener logic. Create a new class that manages the newConnection and pass that in to handle everything around your incoming connection and NWConnection.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Issue with Network Framework and UDP Broadcast
 
 
Q