How to initialize NWUDPSession to listen on a given port?

I’m on an up to date iPad using Swift 5.7 in the Playgrounds app, creating an app, not a playground, so basically doing Xcode lite.

I’m baffled by the fact NWUDPSession takes no arguments. By the meagre bits and pieces I find online it looks like it used to in older versions of Swift but not anymore. So can anybody please give me the syntax on how to initialize an NWUDPSession so it listens to a specific port. I’ve been trying for about 7 hours now and getting nowhere.

Thanks!

PS: NWListener and NWConnection are a no-go for my needs.

I’d like to clarify your goals here. NWUDPSession is not meant to be a general-purpose UDP API. Rather, it’s only relevant if you’re create an Network Extension provider. You mentioned Swift Playgrounds, which only lets you create apps, so I suspect that this isn’t what you’re doing.

So, are you building an NE provider? Or just an app?

Share and Enjoy

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

I’m building an app with this goal: I have a continuous stream of UDP datagrams coming in but NWListener stops listening as soon as it generates a NWConnection and doesn’t start listening again until the connection was cancelled and the listener restarted, which is all extremely time consuming and causes about 1/3 of the datagrams to be dropped. So I either find a solution through NWUDPSession or else there’s a trick to keep NWListener actively listening at all times in which case I’m all ears.

Here’s the code that drops a huge amount of datagrams. I have also tried with queue .main with the same results. Using NWConnection.receive or .receiveMessage makes no difference.

import Network

import SwiftUI



class udpCatcher: ObservableObject {

    

    init() {

        print("init started.")

        

        var lastTime = Date() // used to know how much time it took between receiving datagrams, mostly for my use in combination with optional filtering for specific data that can be enabled by un-commenting some code below

        var counter = 0 // used to add indentation to better follow the scrolling of the data

        let printState = false // if false then only the data will print, if true then also the state of the listener and the connection

        let listener = try! NWListener(using: .udp, on: 5000)

        listener.newConnectionLimit = NWListener.InfiniteConnectionLimit

        

        listener.stateUpdateHandler = { state in

            if printState {print("listener state: \(listener.state)")}

        }

        

        listener.newConnectionHandler = { connection in

            

            if printState {print("newConnectionHandler triggered: connection state: \(connection.state), listener state: \(listener.state)")}

            

            connection.stateUpdateHandler = { newState in

                switch newState {

                    

                case .ready:

                    

                    if printState {print("Connection state: \(connection.state), listener state: \(listener.state)")}

                    

                    connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, context, isComplete, error) in

                        if let data = data, !data.isEmpty {

                            var message = String(decoding: data, as: UTF8.self)

                            

//                            if message.hasPrefix("$XXXX") { // for optional filtering of messages

                                counter += 1

                                if counter % 5 == 0 { // indentation every 5th line to better follow the scrolling

                                    print("\(counter) Received data:     \(message)   \((Date().timeIntervalSince(lastTime)*100).rounded()/100)\"")

                                } else {

                                    print("\(counter)     Received data: \(message)   \((Date().timeIntervalSince(lastTime)*100).rounded()/100)\"")

                                }

                                lastTime = Date()

//                            }

                            // Process the data here 

                        }

                        

                        if let error = error {

                            if printState {print("Connection error: \(error)")}

                        }

                        

                        if isComplete {

                            connection.cancel()

                            if printState {print("Connection state: \(connection.state), listener state: \(listener.state)")}

                        }

                    }

                    

                case let .failed(error):

                    if printState {print("Connection failed with error \(error.localizedDescription), listener state: \(listener.state)")}

                    

                default:

                    if printState {print("Connection state: \(connection.state), listener state: \(listener.state)")}

                    

                }

            }

            

            connection.start(queue: .global(qos: .userInteractive))

        }

        

        listener.start(queue: .global(qos: .userInteractive))

    }

}

App developers can’t use NWUDPSession. The only supported way to construct one is to call methods on an NEProvider subclass, and those can only be instantiated as the principle class of an Network Extension provider.

If Network framework does not work for you, the next step is BSD Sockets.


Regarding Network framework, you wrote:

I have a continuous stream of UDP datagrams coming in

What does the address tuple look like for these? That, the tuple made up of local IP, local port, remote IP, remote port. Are they all the same? If not, which components vary?

Share and Enjoy

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

All the adresses and ports are identical between datagrams.

Regarding BSD, could you please help me on my way? This isn’t built into Playgrounds and not part of its documentation either. Can you give me links to files, documentation, and code snippets and can you tell me how to import it into Playgrounds on iPad? That would save me hours, if not days, and would be mightily appreciated! Thanks.

Calling BSD Sockets from Swift is… well… frankly… un-fun. I strongly recommend that you try to get this working with Network framework.

Apropos that, you wrote:

All the adresses and ports are identical between datagrams.

Cool. Are all the addresses unicast? Or are broadcasts or multicasts in play here?

Share and Enjoy

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

It’s broadcast.

I’d love to stick with Network but as far as I can tell as a beginning Swift developer, it’s broken. newConnectionHandler won’t be triggered until the current connection is closed therefore dropping all the datagrams in the interval. Unless you have a fix for that through threads or whatever, there’s going to be no way around it. I’ve already tried different threads in the queues of the listener and connection to no avail. I haven’t found any working sample code on the internet. I’m stumped.

What I did find is complaints about datagrams being dropped going back 3 years, with no solution offered.

But realistically, I expect I’m going to have to go the BSD route, or find yet another alternative. I did discover Darwin in the mean time and managed to start a UDP connection and receive the first few bytes. But it’s a hard slog without guidance. Trying infinite combinations of possible syntax in the hope of making it work is not fun. I really do need sample code if you can spare some. Is your BSD solution the same as going through Darwin or is it another alley?

I think I’m well enough on my way now to succeed with Darwin Sockets. I’m still curious to know whether that’s what you meant with BSD.Sockets or whether they are a third alternative. Anyway, if you don’t hear from me anymore it means I made it. Thank you Quinn for chiming in on this.

It’s broadcast.

Blat!

We do not have a good story for UDP broadcasts in Network framework (r. 103252571), which means you have to use BSD Sockets )-:

Calling the BSD Sockets API from Swift is, as I mentioned before, un-fun. Over the years I’ve come up with many different approaches for this — you can see one of my early attempts here — but these days I tend to lean in to getaddrinfo and getnameinfo. For example, using the QSocketHelpers functions below, it’s easy to wrap routines that need a sockaddr pointer, like bind:

import System

extension FileDescriptor {

    func bind(address: String, port: UInt16) throws {
        try QSocketHelpers.withSockAddr(address: address, port: port) { sa, saLen in
            let success = Darwin.bind(self.rawValue, sa, saLen) >= 0
            guard success else {
                let err = errno
                throw Errno(rawValue: err)
            }
        }
    }
}

and routines that return an address via a sockaddr pointer, like getsockname:

extension FileDescriptor {
    
    func getsockname() throws -> (address: String, port: UInt16) {
        let (_, address, port) = try QSocketHelpers.fromSockAddr { sa, saLen in
            let success = Darwin.getsockname(self.rawValue, sa, &saLen) >= 0
            guard success else {
                let err = errno
                throw Errno(rawValue: err)
            }
        }
        return (address, port)
    }
}

This means representing addresses as strings, which is not ideal. I wouldn’t do this if I were designing a networking library from scratch but I’ve found that this approach represents a good balance between convenience and flexibility when messing around with BSD Sockets.

Share and Enjoy

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


enum QSocketHelpers {

    static func withSockAddr<ReturnType>(
        address: String,
        port: UInt16,
        _ body: (_ sa: UnsafePointer<sockaddr>, _ saLen: socklen_t) throws -> ReturnType
    ) throws -> ReturnType {
        var addrList: UnsafeMutablePointer<addrinfo>? = nil
        var hints = addrinfo()
        hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV
        let err = getaddrinfo(address, "\(port)", &hints, &addrList)
        guard err == 0 else { throw QSocketHelpers.NetDBError(code: err) }
        guard let addr = addrList else { throw QSocketHelpers.NetDBError(code: EAI_NODATA) }
        defer { freeaddrinfo(addrList) }
        return try body(addr.pointee.ai_addr, addr.pointee.ai_addrlen)
    }

    static func fromSockAddr<ReturnType>(_ body: (_ sa: UnsafeMutablePointer<sockaddr>, _ saLen: inout socklen_t) throws -> ReturnType) rethrows -> (result: ReturnType, address: String, port: UInt16) {
        var ss = sockaddr_storage()
        var saLen = socklen_t(MemoryLayout<sockaddr_storage>.size)
        return try withUnsafeMutablePointer(to: &ss) {
            try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
                let result = try body(sa, &saLen)
                var host = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                var serv = [CChar](repeating: 0, count: Int(NI_MAXSERV))
                let err = getnameinfo(sa, saLen, &host, socklen_t(host.count), &serv, socklen_t(serv.count), NI_NUMERICHOST | NI_NUMERICSERV)
                guard err == 0 else { throw QSocketHelpers.NetDBError(code: err) }
                guard let port = UInt16(String(cString: serv)) else { throw QSocketHelpers.NetDBError(code: EAI_SERVICE) }
                return (result, String(cString: host), port)
            }
        }
    }

    struct NetDBError: Error {
        var code: CInt
    }
}
How to initialize NWUDPSession to listen on a given port?
 
 
Q