UDP Broadcast on iOS18

I am writing an app using Microsoft's MAUI platform. I am posting this here because that team wants me to make an xcode project to help determine an issue I am having.

My MAUI app sends a broadcast packet on a UDP socket using address 255.255.255.255. This worked fine in iOS version 17.x. After upgrading my phone to iOS 18.x it stopped working.

The error I get is "no route to host".

The exact same code works fine on MacOS. It does not work on iPadOs 18.

My question here is 3 fold:

  1. Did something specific change between iOS 17 and 18 that would cause a 'no route to host' error when sending a UDP broadcast packet?

  2. Can someone provide sample code to show me how to do this type of broadcast using Swift in Xcode for iOS?

  3. I read an article that said my app would need the com.apple.developer.networking.multicast entitlement in order to use boradcast functionality. This was introduced in iOS 14. Why did my app work fine in iOS 17 then? Is this what changed? Did this requirement use to be optional and is now required? I did get this entitlement from Apple and applied it to my provisioning profile and my app gave the same "no route to host" error. Why?

I found some Swift code, but when I try to use it I get a different error. In the following code the status goes from preparing to waiting. I have show both the code and the logs in Xcode below.

Thoughts?

//
//  ContentView.swift
//  UDP Broadcast Test
//
//  Created by Tony Pitman on 12/19/24.
//

import SwiftUI
import Network

struct ContentView: View {
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Button("UDP Test") {
                
                let connection = NWConnection(host: "255.255.255.255", port: 11000, using: .udp)
                connection.stateUpdateHandler = { (newState) in
                    print("This is stateUpdateHandler:")
                    switch (newState) {
                    case .ready:
                        print("State: Ready\n")
                        self.sendUDP(connection: connection, content: "Polaris Discovery")
                        self.receiveUDP(connection: connection)
                    case .setup:
                        print("State: Setup\n")
                    case .cancelled:
                        print("State: Cancelled\n")
                    case .preparing:
                        print("State: Preparing\n")
                    case .waiting(let error):
                        print("State: Waiting: \(error)\n")
                    default:
                        print("ERROR! State not defined!\n")
                    }
                }
                
                connection.start(queue: .global())
            }
        }
        .padding()
    }
    
    func sendUDP(connection: NWConnection, content: Data) {
        connection.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
            if (NWError == nil) {
                print("Data was sent to UDP")
            } else {
                print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
            }
        })))
    }
    
    func sendUDP(connection: NWConnection, content: String) {
        let contentToSendUDP = content.data(using: String.Encoding.utf8)
        connection.send(content: contentToSendUDP, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
            if (NWError == nil) {
                print("Data was sent to UDP")
            } else {
                print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
            }
        })))
    }
    
    func receiveUDP(connection: NWConnection) {
        connection.receiveMessage { (data, context, isComplete, error) in
            if (isComplete) {
                print("Receive is complete")
                if (data != nil) {
                    let backToString = String(decoding: data!, as: UTF8.self)
                    print("Received message: \(backToString)")
                } else {
                    print("Data == nil")
                }
            }
        }
    }
}

#Preview {
    ContentView()
}

Log output including the waiting error:

NSBundle file:///System/Library/PrivateFrameworks/MetalTools.framework/ principal class is nil because all fallbacks have failed
This is stateUpdateHandler:
This is stateUpdateHandler:
State: Preparing

This is stateUpdateHandler:
State: Waiting: POSIXErrorCode(rawValue: 50): Network is down

State: Waiting: POSIXErrorCode(rawValue: 50): Network is down
UDP Broadcast on iOS18
 
 
Q