NWMulticastGroup returns POSIXErrorCode error

let localhost: NWEndpoint.Host = NWEndpoint.Host("127.0.0.1")
let port: NWEndpoint.Port = NWEndpoint.Port(integerLiteral: 555)

do {
    let multicast = try NWMulticastGroup(for: [.hostPort(host: localhost, port: port)])
    print(multicast)
} catch {
    print(error)
    return
}

Returns the error POSIXErrorCode(rawValue: 22): Invalid argument.

Running on macOS. (Follow up question, does only receiving multicast also require entitlements for iOS?)

How can I make NWMulticastGroup work?

Answered by DTS Engineer in 789858022

As has been mentioned in TN3151 Apple does not support UDP broadcast.

Well, Apple supports it, but just not with Network framework [1]. In most broadcast scenarios you have to use BSD Sockets.


Thanks for all the info about your goals. BSD Sockets is definitely (albeit sadly) the right path forward here.

I was able to create code that made it work

Cool.

A word of caution about that code. Currently it’s ‘burning’ a Dispatch worker thread by having it block indefinitely in that recv(…) system call. It would be better to do this using non-blocking I/O with a Dispatch read source. This has a number of benefits:

  • It doesn’t tie up a Dispatch worker thread.

  • It allows for easy cancellation.

  • With a Dispatch read source you can target a specific queue, which makes concurrency easy.

For an example of how to set this up, see this post.

Share and Enjoy

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

[1] There is one very specific case that works.

I also don't understand why an entitlement is needed, why wouldn't a privacy notification suffice?

does only receiving multicast also require entitlements for iOS?

Local network privacy (LNP) is an iOS thing. For more on that, see Local Network Privacy FAQ.

For that reason it’s often a good idea to develop code like this on the Mac (or in the iOS simulator on the Mac), so you can separate LNP issues from other coding issues.

Coming back to your code, I can help explain how to use NWMulticastGroup but it’s best if I start by understanding what your multicast protocol looks like. Specifically:

  • Are all the peers Apple devices? Or do you have a hardware accessory involved?

  • What is role of each peer?

  • Is all traffic multicast? Or do you switch to unicast at some point?

  • Is this IPv4? Or IPv6? Or both?

  • What multicast address do you plan to use? And port?

  • And do you use the same port for both the source and destination?


Finally, a significant number of folks I talk to about multicast are implementing service discovery. If that’s the case I recommend that you save yourself a lot of headaches and just implement Bonjour. Bonjour is an Apple term for three industry-standard protocols:

By using Bonjour you simplify your code, improve the on-the-wire behaviour of your app, and avoid the need for the multicast entitlement.

Apple systems have Bonjour support built in, as do most other OSes you’re likely to be using (Android, Windows, Linux, and so on).

Share and Enjoy

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

Thanks Quinn for replying. Really appreciate you taking the time to write your response.

I have discovered a lot in the meantime.

  1. I now understand that are different broadcasting modes for UDP: Unicast, Broadcast en Multicast.
  2. TN3151: Choosing the right networking API - Explained:

Network framework supports UDP multicast using the NWConnectionGroup class, but that support has limits and, specifically, it does not support UDP broadcast. If you need something that’s not supported by Network framework, use BSD Sockets.

The game on console was using broadcast mode to broadcast data using UDP. I wrongfully assumed it was the same as multicast.

As has been mentioned in TN3151 Apple does not support UDP broadcast. Hopefully this will change in the future.


Having said that, coming back to your questions:

Are all the peers Apple devices? Or do you have a hardware accessory involved?

A game broadcasts data via UDP. So other than my app that is reading and visualizing that data to the user no Apple devices are involved.

What is role of each peer?

The role of each peer is just to receive the data being broadcasted by the game and processing it in different ways.

Is all traffic multicast? Or do you switch to unicast at some point?

Traffic is broadcast. I made a mistake. The game is able to switch between unicast and broadcast. Unicast works with the Network Framework, broadcast doesn't. Which is a shame, would be incredibly useful if it were.

Is this IPv4? Or IPv6? Or both?

This is IPv4.

What multicast address do you plan to use? And port?

The source (game) will broadcast the data to ALL machines on a local area network on a specific port.

And do you use the same port for both the source and destination?

Correct.


I was able to create code that made it work without the Network Framework.

class UDPBroadcastReceiver {
    private var socketDescriptor: Int32 = -1
    private let port: UInt16
    private let queue = DispatchQueue(label: "UDPBroadcastReceiverQueue")

    init(port: UInt16) {
        self.port = port
        setupSocket()
    }

    private func setupSocket() {
        socketDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
        guard socketDescriptor > 0 else {
            print("Failed to create socket")
            return
        }

        var broadcastEnable: Int32 = 1
        let broadcastEnableSize = socklen_t(MemoryLayout.size(ofValue: broadcastEnable))
        setsockopt(socketDescriptor, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, broadcastEnableSize)

        var address = sockaddr_in(
            sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
            sin_family: sa_family_t(AF_INET),
            sin_port: in_port_t(port.bigEndian),
            sin_addr: in_addr(s_addr: INADDR_ANY),
            sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)
        )

        let bindResult = withUnsafePointer(to: &address) {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                bind(socketDescriptor, $0, socklen_t(MemoryLayout<sockaddr_in>.size))
            }
        }

        guard bindResult == 0 else {
            print("Failed to bind socket")
            close(socketDescriptor)
            return
        }

        print("Socket setup and bound to port \(port)")

        receive()
    }

    private func receive() {
        queue.async {
            var buffer = [UInt8](repeating: 0, count: 1024)

            while true {
                let bytesRead = recv(self.socketDescriptor, &buffer, buffer.count, 0)

                if bytesRead > 0 {
                    let data = Data(buffer[0..<bytesRead])
                    // To Do: Parse data
                } else if bytesRead == 0 {
                    print("Socket closed by peer")
                    break
                } else {
                    print("Error receiving data: \(String(cString: strerror(errno)))")
                    break
                }
            }
        }
    }
    
    deinit {
        if socketDescriptor > 0 {
            close(socketDescriptor)
        }
    }

}

As has been mentioned in TN3151 Apple does not support UDP broadcast.

Well, Apple supports it, but just not with Network framework [1]. In most broadcast scenarios you have to use BSD Sockets.


Thanks for all the info about your goals. BSD Sockets is definitely (albeit sadly) the right path forward here.

I was able to create code that made it work

Cool.

A word of caution about that code. Currently it’s ‘burning’ a Dispatch worker thread by having it block indefinitely in that recv(…) system call. It would be better to do this using non-blocking I/O with a Dispatch read source. This has a number of benefits:

  • It doesn’t tie up a Dispatch worker thread.

  • It allows for easy cancellation.

  • With a Dispatch read source you can target a specific queue, which makes concurrency easy.

For an example of how to set this up, see this post.

Share and Enjoy

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

[1] There is one very specific case that works.

Thank you so much for actually reading my code and giving recommendations. I am definitely giving the non-blocking I/O solution a shot.

Have a great WWDC this year!

Accepted Answer

This code worked for me using dispatch source as pointed out by DTS Engineer. Hopefully it will help out other developers.

final class UDPConnection {
    private var socketDescriptor: Int32 = -1
    private let port: UInt16
    private var dispatchSource: DispatchSourceRead?
    
    init(port: UInt16) {
        self.port = port
    }
    
    func start() {
        socketDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
        guard socketDescriptor > 0 else {
            print("Failed to create socket")
            return
        }

        var broadcastEnable = Int32(1)
        setsockopt(socketDescriptor, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout<Int32>.size))

        var address = sockaddr_in()
        address.sin_family = sa_family_t(AF_INET)
        address.sin_port = in_port_t(port).bigEndian
        address.sin_addr = in_addr(s_addr: INADDR_ANY)

        let bindResult = withUnsafePointer(to: &address) { pointer in
            pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in
                bind(socketDescriptor, sockaddrPointer, socklen_t(MemoryLayout<sockaddr_in>.size))
            }
        }

        guard bindResult == 0 else {
            print("Failed to bind socket")
            close(socketDescriptor)
            return
        }

        print("Started receiving on port \(port)")

        // Set up the DispatchSource to monitor the socket for read events
        dispatchSource = DispatchSource.makeReadSource(fileDescriptor: socketDescriptor, queue: DispatchQueue.global())

        dispatchSource?.setEventHandler { [weak self] in
            self?.handleSocketEvent()
        }

        dispatchSource?.setCancelHandler {
            close(self.socketDescriptor)
        }

        dispatchSource?.resume()
    }
    
    private func handleSocketEvent() {
        var buffer = [UInt8](repeating: 0, count: 1500)
        var address = sockaddr_in()
        var addressLength = socklen_t(MemoryLayout<sockaddr_in>.size)

        let bytesRead = withUnsafeMutablePointer(to: &address) { pointer in
            pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in
                recvfrom(self.socketDescriptor, &buffer, buffer.count, 0, sockaddrPointer, &addressLength)
            }
        }

        if bytesRead > 0 {
            let message = String(bytes: buffer[..<bytesRead], encoding: .utf8) ?? "Invalid UTF-8 message"
            print("Received message: \(message)")
        } else if bytesRead == -1 {
            print("Error receiving message: \(String(cString: strerror(errno)))")
        }
    }
}
NWMulticastGroup returns POSIXErrorCode error
 
 
Q