Listening to port

I have a function which get a port number and listen on that port for X seconds.

For testing I'm calling this function with some port (say 9090) and it's working great.

My question is - can I pass it any port I want (minus the privileged ports) as I did at my testing? Or maybe the port might not be free and I should find an open and available port ?


Asking both for iOS and MacOS apps.

If you hardwire a port number then you run the risk of colliding with other software that hardwires that some port number. There’s a bunch of approaches you can take here, listed from most to least preferred:

  • Pass in 0 and let the kernel allocate you a port (A)

  • Have a fixed port but, if that can’t be used, fall back to using a dynamically allocated one (B)

  • Have a fixed port but let the user configure it, on both the server and the client (C)

  • Have a fixed port and just fail if it collides (D)

Which is best depends on your specific circumstances. For example, if the client is always on the same network as the server, option A works well because you can use Bonjour to find that port.

If you chose options B through D then you really should register your port number with IANA, although that’s no guarantee you won’t collide.

Share and Enjoy

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

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

Thanks, but just now I found something that helped me -

a function to tell if a port is free:



func isPortFree(port: in_port_t) -> (Bool, descr: String) {
      
        let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
        if socketFileDescriptor == -1 {
            return (false, "SocketCreationFailed, \(descriptionOfLastError())")
        }
      
        var addr = sockaddr_in()
        let sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size
        addr.sin_len = __uint8_t(sizeOfSockkAddr)
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
        addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0"))
        addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
        var bind_addr = sockaddr()
        memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
      
        if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
            let details = descriptionOfLastError()
            release(socket: socketFileDescriptor)
            return (false, "\(port), BindFailed, \(details)")
        }
        if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
            let details = descriptionOfLastError()
            release(socket: socketFileDescriptor)
            return (false, "\(port), ListenFailed, \(details)")
        }
        release(socket: socketFileDescriptor)
        return (true, "\(port) is free for use")
    }
Accepted Answer

a function to tell if a port is free

I’m not sure how that helps. Any check like this automatically opens you up to race conditions; someone could bind to the port after your check and before you take action based on that check.

A better approach is to bind to the port. If that works you know you have it; if it doesn’t then you can call

bind
again with 0 to get a dynamic port.

Also, FYI, while I realise that BSD Sockets is a pain to call from Swift, your code is much more complex than it needs to be:

  • Lines 20, 25 and 28 can be replaced by a

    defer
    statement between lines 6 and 7.
    defer {
        release(socket: socketFileDescriptor)  
    }

    This code will run regardless of how you exit the function.

  • Lines 13 and 14 are redundant because line 8 guarantees to initialise everything to 0.

  • Line 12 can be written much more easily as:

    addr.sin_port = port.bigEndian

    Note I generally recommend that you pass around ports as an

    Int
    rather than
    in_port_t
    (aka
    UInt16
    ). Swift generally tries to avoid dealing with specifically-sized integer types unless absolutely necessary.

    Another option is to define your own new type to represent a port (I’ve pasted in an example at the end of this response), which prevents you from accidentally confusing ports and general-purpose integers.

  • Your approach for dealing with the

    sockaddr
    type is very troubling. You’re assuming that
    sockaddr
    is big enough to hold
    sockaddr_in
    . That’s true in this case, but it’s not universally true. For example,
    sockaddr
    is not big enough to hold
    sockaddr_in6
    . Consider this code:
    print("sockaddr: ", MemoryLayout<sockaddr>.size)
    print("sockaddr_in: ", MemoryLayout<sockaddr_in>.size)
    print("sockaddr_in6: ", MemoryLayout<sockaddr_in6>.size)
    print("sockaddr_storage: ", MemoryLayout<sockaddr_storage>.size)

    which prints:

    sockaddr:  16
    sockaddr_in:  16
    sockaddr_in6:  28
    sockaddr_storage:  128

    The last item is key.

    sockaddr_storage
    is defined to be big enough to hold any socket address, and that’s what I recommend that folks use here.

Adopting

sockaddr_storage
presents two problems:
  • How do you fill in a

    sockaddr_storage
    as a
    sockaddr_in
    ?
  • How do you pass a

    sockaddr_storage
    to a function, like
    bind
    , that takes
    sockaddr
    .

The answer to both of these can be found in Socket API Helper section of the UnsafeRawPointer Migration doc.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
struct IPPort {
    var networkEndian: UInt16
    init?(networkEndian: UInt16) {
        self.networkEndian = networkEndian
    }
    init?(hostEndian: Int) {
        guard let p = UInt16(exactly: hostEndian) else { return nil }
        self.networkEndian = p.bigEndian
    }
    static func from(_ sin: sockaddr_in) -> IPPort {
        return IPPort(networkEndian: sin.sin_port)!
    }
    static func from(_ sin6: sockaddr_in6) -> IPPort {
        return IPPort(networkEndian: sin6.sin6_port)!
    }
}

Thanks! I'll improve my code as you suggested, but first I'll try calling bind with (0).

Thanks for the detailed answer!

Listening to port
 
 
Q