UDP Listener on iOS 14

Hi all, I have a question regarding how to set up a UDP listener on iOS 14. I have a UDP listener which has worked in the past, but after updating to iOS 14 it works sporadically/not at all.

This lives in an NSObject, and listens for a UDP broadcast across the local network on port 15000 (no specific IP address). It uses the CocoaAsyncSocket library. When I call setUpSocket() local network permissions are not triggered, but the app is able to sporadically pick up UDP packets.

Code Block Swift
var socket: GCDAsyncUdpSocket?
var broadcastPort: UInt16 = 15000
var broadcastAddress: String = ""
var connectAddress = ""
var connectPort = 0
func setUpSocket() {
    findUDP()
    let socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
     
    socket.setIPv4Enabled(true)
    socket.setIPv6Enabled(false)
     
    do {
      try socket.bind(toPort: broadcastPort) /*15000*/
      try socket.enableBroadcast(false)
      try socket.beginReceiving()
       
    } catch let error as NSError {
       
      print("Issue with setting up listener \(error)")
       
    }
     
  }
/*Called when UDP packets are received.*/
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress: Data, withFilterContext filterContext: Any?) {
     
    do {
      let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
       
      if (connected == false) {
        if (jsonDictionary["Addresses"] != nil) {
          if (jsonDictionary["Addresses"] is NSArray) {
            let addresses = jsonDictionary["Addresses"] as! NSArray
             
            for i in addresses {
              let ipAddress:String = i as! String
              if (ipAddress.range(of: "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", options: .regularExpression) != nil) {
                connectAddress = ipAddress
              }
            }
            connectPort = jsonDictionary["Port"] as! Int
          }
           
/*Sets up a TCP connection on the IP and Port provided in the UDP broadcast.*/
setupNetworkCommunication(ip: connectAddress, port: connectPort)
          
          closeSocket()
        }
      }
       
    } catch let error {
      return print(error)
    }
  }


How can I update this to comply with iOS 14? If I need to update to use Bonjour services, how can I listen on a port without specifying an address (and without having to look for a specific Bonjour service broadcast, because the broadcast I'm looking for doesn't use Bonjour).

Is it acceptable to quickly open and close a Bonjour NWBrowser in order to trigger the network permissions, and then use my code as-is? This seems to work but seems hacky at best.

Thanks in advance.

Answers

First things first, make sure you populate NSLocalNetworkUsageDescription in your Info.plist.

Next, make sure to apply for and, once it’s granted, enable the com.apple.developer.networking.multicast. iOS 14 will eventually require this for anyone doing multicasts and broadcasts (currently this requirement is not enforced for BSD Sockets clients).

Finally, be aware that the local network privacy alert is only triggered when you send traffic, so if your UDP socket only receives and never sends then you won’t see the alert. The current workaround is to send a dummy packet to trigger the alert.

Note This last restriction is definitely a bug (r. 67975514) and we hope to fix it sooner rather than later.

Share and Enjoy

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

Hopefully these steps will solve my UDP receiving issues. I will update your answer as a solution once I am able to check.
  • I have already populated NSLocalNetworkUsageDescription in my Info.plist.

  • I just applied for the com.apple.developer.networking.multicast permissions, and will enable them once granted.

I use the info I get from my UDP listener to set up a TCP connection... so far, this has not triggered the local network privacy alert. I do not use NWConnection or any other Apple library for this, rather Swiftsocket. Would the multicast entitlement result in my TCP connection triggering the network permissions?

I've also been able to use NWListener to set up an NWConnection on the UDP port, which does trigger the local network privacy alert. If using this, do I still need an entitlement? Do I need to alter my TCP connection at all (it doesn't trigger a privacy alert when I run it alone, and am not sure if this is an issue)?

Thanks for your help. I am straying a bit off topic here so please let me know if an email might be better for this.

I use the info I get from my UDP listener to set up a TCP
connection... so far, this has not triggered the local network privacy
alert.

Right, because if your UDP listener never receives any messages then you’ll never start the outgoing TCP connection.

Would the multicast entitlement result in my TCP connection triggering
the network permissions?

Right now the local network privacy alert is triggered by outgoing network traffic to a local network (and some other things). It doesn’t matter which API you use to generate that traffic.

I've also been able to use NWListener to set up an NWConnection on
the UDP port, which does trigger the local network privacy alert. If
using this, do I still need an entitlement?

I was under the impression that you’re using UDP broadcasts? If so, NWListener and NWConnection are not appropriate. This is not correct. See my update here.

Share and Enjoy

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

Right, because if your UDP listener never receives any messages then you’ll never start the outgoing TCP connection.

I was able to receive UDP packets and then set up a TCP connection without triggering the network permissions, so long as I wasn't using Bonjour. This was on iOS 14 with no entitlements listed in the info.plist, and an empty Bonjour array.

I was under the impression that you’re using UDP broadcasts? If so, NWListener and NWConnection are not appropriate.

I am. My current implementation (updated from the OP) is to receive the UDP broadcasts via an NWListener (which uses Bonjour) and then set up an NWConnection on the port to read the messages. This is functional on iOS 14. Emailing with Network Entitlement Requests suggested I should accomplish the UDP reception via Bonjour, so I implemented the below. Should I be using something different?

Code Block Swift
var udpListener: NWListener?
  var udpConnection: NWConnection?
  var backgroundQueueUdpListener  = DispatchQueue.main
   
  func findUDP() {
    let params = NWParameters.udp
    udpListener = try? NWListener(using: params, on: 15000)
     
    udpListener?.service = NWListener.Service.init(type: "_appname._udp")
     
    self.udpListener?.stateUpdateHandler = { update in
      print("update")
      print(update)
      switch update {
      case .failed:
        print("failed")
      default:
        print("default update")
      }
    }
    self.udpListener?.newConnectionHandler = { connection in
      print("connection")
      print(connection)
      self.createConnection(connection: connection)
      self.udpListener?.cancel()
    }
    udpListener?.start(queue: self.backgroundQueueUdpListener)
  }
   
  func createConnection(connection: NWConnection) {
    self.udpConnection = connection
      self.udpConnection?.stateUpdateHandler = { (newState) in
        switch (newState) {
        case .ready:
          print("ready")
          self.send()
          self.receive()
        case .setup:
          print("setup")
        case .cancelled:
          print("cancelled")
        case .preparing:
          print("Preparing")
        default:
          print("waiting or failed")
        }
      }
      self.udpConnection?.start(queue: .global())
  }
   
  func endConnection() {
    self.udpConnection?.cancel()
  }





Earlier I wrote:

I was under the impression that you’re using UDP broadcasts? If so,
NWListener and NWConnection are not appropriate.

It turns out that this is wrong. I pinged the Network framework team about this and they set me straight. Listening for UDP broadcasts via an NWListener and then using the NWConnection objects it vends (via the new connection handler) to communicate over unicast with the broadcast’s sender is an expected use case.

Hey, you learn something every day! Sorry about the bum steer.

Share and Enjoy

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

Finally, be aware that the local network privacy alert is only triggered when you send traffic, so if your UDP socket only receives and never sends then you won’t see the alert. The current workaround is to send a dummy packet to trigger the alert.

Can you provide a code snippet that does this? I've tried a bunch of things and can't seem to get the network privacy alert to show up.
Any update on this? Our app is failing too ( on the call to bind() in our own implementation or using GCDAsyncUdpSocket ). I've applied for, and implemented the MultiCast entitlement. We're also on port 55555.

I updated to iOS 14.0.1 this am and still no joy.

@eskimo, can we get an update?
@Eskimo, we are also quite interested by a code snippet to trigger the authorization dialog, as we have the same issue than DaveWhipps

Our app usually only receive UDP broadcast (no transmit except in a few cases).

We have got the com.apple.developer.networking.multicast entitlement, have added it and are signing our app with a provisioning profile including this entitlement. Since then, situation has improved as the UDP packet reception that was not working at all before entitlement granted, it is now sometime working, but unfortunately also sometimes not working (situation seems worse on iOS 14.0.1 than on iOS 14).

Most importantly, we never got the authorization dialog displayed and our app does not appear as authorized in Privacy/Local Network. We suspect this may be the cause for this spurious reception issue. As we read in the forums the authorization dialog was only shown when sending data, we configured our app to send data to the local network to try to trigger the dialog, using all below methods:
  1. TcpSocket class (using CFStreamCreatePairWithSocketToHost) to connect to 192.168.1.1 on port 80 and send a few bytes (there is a device at this address)

  2. using GCDAsyncSocket to connect and send a test TCP packet to same address/port

  3. using GCDAsyncUdpSocket to create a UDP socket, enabling it for broadcast, then joinMulticastGroup 224.0.1.0 and broadcasting a test UDP packet on port 80.

  4. using GCDAsyncUdpSocket to create a UDP socket, enabling it for broadcast, then broadcasting a test UDP packet on port 80 to 255.255.255.255.

  5. and finally reusing the example from Apple article sending multicast packets with NWConnectionGroup to 224.0.1.0

None of the above actions are triggering the authorization dialog, and our app is still not listed as authorized in Privacy/Local Network, with intermittent reception of UDP packets.

So, if you could share a code snippet triggering this authorization dialog or otherwise advise us on what could be the issue, this would be great.

Many thanks in advance

Daniel

Hi, all

same problem for me and I wasn't able to solve populating info.plist.
Just to describe better what I need to solve:
I connect my iPhone to a device WiFi module and start receiving message from it.
When I receive the first "message" I was able to get ip and port through an UDP connection. Once I got ip and port, I can send a command to ask the device for networks list he sees around him. Then from the app I can select the network and send the password to device.
I'm using a library BlueSocket but I also tried with NWListener and NWConnection.
It just works sometimes in debug mode but not in production.
I also tried to apply to com.apple.developer.networking.multicast but they reply me that I don't need that.
Hi @eskimo,
I just need help to solve the problem. My users are not able to use the app anymore after updating to iOS 14.
This is Apple reply for the entitlement:

Thank you for your interest in Multicast Networking. It sounds like you’re using WebSockets to make a unicast UDP connection to your devices. If that’s the case, you shouldn’t need the entitlement, just add the usage description so your users know why you use their local network and you should be all set. See How to use multicast networking in your app for more details, and if you have questions or issues, please use the “Network” tag on the Apple Developer Forums.

Do you have some update?

I just need help to solve the problem.

I’m going to recommend that you open a DTS tech support incident so that either Matt or I can help you out one-on-one.

Share and Enjoy

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