Receiving UDP broadcasts stopped working

I maintain an app that works with specific IoT devices. One of the functionalities I need is sending and receiving UDP broadcasts from the devices in the local network. In fact, such a procedure worked perfectly for almost 2 years, but with iOS 12 things got complicated.

  • In the debug mode broadcasts work as they did before - they are can be both send and received.
  • There is a problem with receiving broadcasts in the release compilations of the app. It doesn't show up even if I uncheck "the debug executable" option and install the app on the testing phone via a cable, but the only way to replicate such a behavior that I found is uploading the app to the App Store Connect and installing it using TestFlight.


Because of that, it's really hard to debug. I define a listening address like that (the port I use is 15001):

listening_address = sockaddr_in(
            sin_len:    __uint8_t(MemoryLayout<sockaddr_in>.size),
            sin_family: sa_family_t(AF_INET),
            sin_port:   inboundPort.bigEndian,
            sin_addr:   INADDR_ANY,
            sin_zero:   ( 0, 0, 0, 0, 0, 0, 0, 0 )
        )

Then I create a socket with SO_BROADCAST enabled:

let newSocket: Int32 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

var broadcastEnable = Int32(1);
let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout.size));

And I try to bind it:

let bind_ret = bind(newSocket, sockaddr_cast(&listening_address), socklen_t(__uint8_t(MemoryLayout<sockaddr_in>.size)))


In the debug mode, binding succeeds and receiving broadcasts works well. So it did in the release compilations not long ago (at least prior to iOS 12), but now the bind function returns an error: Operation not permitted (EPERM). Enabling SO_REUSEPORT silences the error, but the packets are still not received. On the contrary, sending UDP packets to the broadcast address works as before both in debug and release apps and the response also reaches the phone (what I've checked with rvictl).

---

Today I tried to rewrite the same procedure using the Network framework. An NWListener on the port 15001 somehow reacts to the incoming messages, but instead of invoking newConnectionHandler, it displays nw_listener_inbox_accept_udp bind failed [49: Can't assign requested address] on the debug console for each received broadcast message.

Accepted Answer

I’m concern by your

sockaddr_cast
function. I’m not sure there’s any way to implement such a function without breaking Swift’s typed pointer rules, and doing that is a common cause of problems in the Release build configuration, where the optimiser starts relying on those rules. What does
sockaddr_cast
look like?

Here’s the standard way to do this:

let newSocket: Int32 = …
let addr = sockaddr_in(…)
let addrLen = MemoryLayout.size(ofValue: addr)
try withUnsafePointer(to: addr) { sin in
    try sin.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
        let success = bind(newSocket, sa, socklen_t(addrLen)) == 0
        guard success else {
            throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
        }
    }
}

This is quite ugly, so if I’m doing a lot of it I generally use some sort of helper, like those shown in the UnsafeRawPointer Migration guide.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for your help! An old sockaddr_cast function, not compatible with modern Swift, was exactly the problem.

Receiving UDP broadcasts stopped working
 
 
Q