Find available TCP port

Hello,

I'd like to find an available TCP port. I came up with this code:

#!/usr/bin/env swift
import Foundation
import Network

func findAvailablePort() -> UInt16 {
    let semaphore = DispatchSemaphore(value: 0)
    let listener = try! NWListener(using: .tcp, on: .any)

    listener.stateUpdateHandler = { state in
        switch state {
        case .ready:
            semaphore.signal()
        case .failed(let e):
            fatalError(e.localizedDescription)
        default:
            break
        }
    }

    listener.newConnectionHandler = { conn in }
    listener.start(queue: .global())
    let timeout = DispatchTime.now() + DispatchTimeInterval.seconds(5)

    if semaphore.wait(timeout: timeout) == .timedOut {
        fatalError("timeout")
    }

    return listener.port!.rawValue
}

print(findAvailablePort())

Any feedback, especially around error handling, would be very appreciated. (The two error conditions I've identified, network error and semaphore timeout, seem unlikely to hit so I reached for fatalError.)

To give some more context, I'm writing an application with processes A and B. A starts B via Process(). B will listen on a TCP socket but A needs to know which one. (I'd rather not use stdio to let it know.) So A will find a free tcp port, start B, and pass port in argv. There's obviously a possible race condition between finding the free port and listening on it but it seems unlikely to hit given the OS doesn't seem to "recycle" free ports right away.

Usual mechanism for "borrowing" an IP port is requesting an ephemeral port, but (without knowing more about the app and plans) you might be better served using XPC than TCP.

I came up with this code

Yeah, don’t do that. Even if you decide to continue with this approach — more on that below — using Network framework for this is unnecessarily convoluted. You’d be much better off doing this with BSD Sockets.

it seems unlikely to hit given the OS doesn't seem to "recycle" free ports right away.

That’s not something you want to be relying on.

I'd rather not use stdio to let it know.

That seems like a better option. Or use some other IPC mechanism to get the port number back to A, like writing it to a file.

Share and Enjoy

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

I'd rather not use stdio to let it know.

That seems like a better option. Or use some other IPC mechanism to get the port number back to A, like writing it to a file.

Yeah, unfortunately I cannot use stdio in this particular case. I ended up talking over a FIFO, created with mkfifo(2). Ideally I'd use Pipe() but the file descriptors doesn't seem to be inherited by the child process that is spawned with Process(). I'd assume it would be possible if I used posix_spawn and friends but I'd prefer to stay with the higher-level APIs, if possible. Am I missing something?

Ideally I'd use Pipe but the file descriptors doesn't seem to be inherited by the child process that is spawned with Process.

That’s correct. Under the covers Process calls posix_spawn with the POSIX_SPAWN_CLOEXEC_DEFAULT flag, meaning that file descriptors are not inherited by default. It then explicitly sets up inheritance for std{in,out,err}.

I'd assume it would be possible if I used posix_spawn and friends

Correct.

but I'd prefer to stay with the higher-level APIs

Wouldn’t we all (-:

Share and Enjoy

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

Find available TCP port
 
 
Q