Low-Level Network Address Resolving in Swift

Back in the blog post about the POSIX socket API wrapper I hinted that there would be another initializer for our struct Socket. While answering the question about what that is, we’ll have to take a detour to low-level networking APIs, followed by exploring special memory management requirements outside the realm of ARC or even constructs like Unmanaged<T>, before finally being rewarded with a high level, Swifty, SequenceType-conforming thing whose implementation has similarities to that of Swift’s own types with copy-on-write behavior.

That’s quite a journey, so let’s start with a quick recap of our struct Socket. It only has a single field:

struct Socket {
    let fileDescriptor: Int32

    init(fileDescriptor: Int32) {
        self.fileDescriptor = fileDescriptor
    }
}

So what would we need another initializer for? To answer that question, we need to take that quick detour about how a client initiates a TCP connection to a server.

Initiating a TCP Connection

The short recipe is this:

  1. Decide which peer to connect to, i.e. a host and port.
  2. Resolve the host and port to an address by calling getaddrinfo(3).
  3. Create a suitable socket by calling socket(2).
  4. Connect the socket to the peer with the resolved address by calling connect(2).

This is not too complicated, but all the functions we have to call are not exactly Swifty in terms of their names, the parameters they take and the results they return. Especially what comes out of getaddrinfo has some special memory management requirements that must be taken care of. This is starting to sound like we need a wrapper 😉

But what does this have to do with that other initializer for Socket? It’s point number 3 from the list above: Calling socket(2) returns a file descriptor, which is what we need for our struct Socket. Therefore, this new initializer will take such a “resolved address” and call socket(2) with the appropriate parameters to get a file descriptor with which it can then call the original initializer, Socket.init(fileDescriptor:).

Once More, with Code

Let’s take a closer look at the calls we’ll have to make, this time in reverse order, increasing complexity, and as methods on our Socket.

Socket.connect takes a pointer to a struct sockaddr, an abstract, protocol-specific “socket address” that holds all the information required to initiate a connection from the socket at our end to a socket at the peer that the address describes:

func connect(address: UnsafePointer<sockaddr>, length: socklen_t) throws {
    guard Darwin.connect(fileDescriptor, address, length) != -1 else {
        throw Error.ConnectFailed(code: errno)
    }
}

Before connecting the socket, we must first create it using Socket.init(addrInfo: addrinfo), which takes an abstract, protocol-agnostic “address info” that holds various information on a resolved address. Since our socket wrapper library currently only supports TCP, this will be a protocol family of “IPv4” or “IPv6”, a socket type of “SOCK_STREAM”, and a family-specific protocol that is “TCP”. Don’t worry about these things because luckily, we get all that information from the passed-in addrinfo parameter and we’ll use it to create a suitable socket. This is the promised new initializer:

init(addrInfo: addrinfo) throws {
    let fileDescriptor = Darwin.socket(addrInfo.ai_family, addrInfo.ai_socktype, addrInfo.ai_protocol)
    guard fileDescriptor != -1 else {
        throw Error.CreateFailed(code: errno)
    }
    self.init(fileDescriptor: fileDescriptor)
}

Now comes the missing piece: How do we get from what we have at the beginning (a host and port) to the thing we need to create a socket (an addrinfo) and the thing we need to connect it (a sockaddr)?

It’s the getaddrinfo(3) function. And taming that beast into a Swifty pet is what the remainder of this blog post is about.

getaddrinfo

Let’s take a look at what the POSIX API looks like, with added parameter names and line-wrapped to make it a bit more readable:

func getaddrinfo(
    hostname: UnsafePointer<Int8>,
    serviceName: UnsafePointer<Int8>,
    hints: UnsafePointer<addrinfo>,
    result: UnsafeMutablePointer<UnsafeMutablePointer<addrinfo>>)
    -> Int32

That’s a lot of pointers. Let’s go through them:

  • hostname and serviceName in their original C variant are const char *, i.e. C strings. Luckily, Swift’s String bridges to them automatically. What’s nice is that because these things are represented as strings, we can pass values like “78.46.114.187” and “443” just as well as more user-friendly representations like “obdev.at” and “https”.
  • hints can be nil or a pointer to a struct addrinfo that we can fill in with all the information we already have to narrow down the result that comes out of the function. For example, if we only want IPv6 addresses (and ignore all IPv4 addresses) for a given hostname, we could set the ai_family to PF_INET6.
  • result is an out-parameter, i.e. we pass in an UnsafeMutablePointer and when the function returns, it will contain an UnsafeMutablePointer<addrinfo>.

A very simple call without error handling looks like this:

var result: UnsafeMutablePointer<addrinfo> = nil
getaddrinfo("obdev.at", "https", nil, &result)

// `result.memory` is now an `addrinfo` we can use.
let socket = try Socket(addrInfo: result.memory)

We finally managed to create a usable Socket 🎉

Special Treatment

But wait: Why is result an UnsafeMutablePointer<UnsafeMutablePointer<addrinfo>> and not just simply an UnsafeMutablePointer<addrinfo> like out-parameters usually are? Why the double indirection?

Here’s addrinfo in all its glory and funny abbreviations from the previous century:

struct addrinfo {
    var ai_flags: Int32
    var ai_family: Int32
    var ai_socktype: Int32
    var ai_protocol: Int32
    var ai_addrlen: socklen_t
    var ai_canonname: UnsafeMutablePointer<Int8>
    var ai_addr: UnsafeMutablePointer<sockaddr>
    var ai_next: UnsafeMutablePointer<addrinfo>
}

The last line answers the above questions: addrinfo holds a pointer to a “next” addrinfo in its ai_next property. This means after the getaddrinfo call, result will be a pointer to the first addrinfo, and each addrinfo has a pointer to the next, or nil if it is the last in the list. It’s a singly linked list!

If you know a thing or two about networking, this should make sense even without looking at the types. After all, resolving a hostname to an address doesn’t necessarily lead to only one single result. It could have multiple IP addresses, or an IPv4 as well as an IPv6 address. Therefore, it’s only logical that getaddrinfo returns a list of results.

(The common approach is then to loop over all results and try to connect to each of them until a connection can be established.)

If we call a C function that returns something via a pointer out-parameter that we didn’t allocate ourselves, it usually means we are responsible for freeing the memory that pointer points to. In Swift or Objective-C APIs, ARC takes care of similar situations. In unannotated Core Foundation functions, Unmanaged<T> is used as a way to communicate the memory management needs.

But in plain C, we would expect to pass the pointer to free(3). But how does free(3) know that it should not just free a chunk of memory, but a linked list where every single element of the linked list must be freed? It does not and cannot know, and that’s why a special function exists just for freeing the result of getaddrinfo and this function is freeaddrinfo(3).

What this means for us is that every call to getaddrinfo must be balanced by a call to freeaddrinfo. In between those two – and only then – is the linked list of addrinfos valid and usable.

Wrapping getaddrinfo

Let’s give the wrapper we’re using a more readable name. I’d say we call it AddressInfo:

class AddressInfo {
    private let _addrInfoPointer: UnsafeMutablePointer<addrinfo>

    init(addrInfoPointer: UnsafeMutablePointer<addrinfo>) {
        _addrInfoPointer = addrInfoPointer
    }

    deinit {
        freeaddrinfo(_addrInfoPointer)
    }
}

This time, we’re using a class instead of a struct to take advantage of the fact that we can react to the situation when all references to an instance of AddressInfo go away. That’s when deinit is called and we can assume that no one is using the linked list anymore, so we can free it.

Unless that assumption is wrong. After all, the initializer above takes an existing pointer to the linked list and we can’t know if the calling code has accessed and stored pointers to list elements before the initializer is called. So let’s make sure no pointers to the list elements or inside the list elements can ever be accessed outside of this class. We can do that by adding a new initializer that calls getaddrinfo itself:

class AddressInfo {
    private let _addrInfoPointer: UnsafeMutablePointer<addrinfo>

    init(host: String?, port: String?, hints: UnsafePointer<addrinfo> = nil) throws {
        var addrInfoPointer: UnsafeMutablePointer<addrinfo> = nil
        let result: Int32

        // `String` bridges to `UnsafePointer<Int8>` automatically, but
        // `String?` does not. This switch takes care of the various
        // combinations of `host` and `port` that can occur.
        switch (host, port) {
        case let (host?, port?):
            result = getaddrinfo(host, port, hints, &addrInfoPointer)

        case let (nil, port?):
            result = getaddrinfo(nil, port, hints, &addrInfoPointer)

        case let (host?, nil):
            result = getaddrinfo(host, nil, hints, &addrInfoPointer)

        default:
            preconditionFailure("Either host or port must be given")
        }

        guard result != -1 else {
            throw Socket.Error.GetAddrInfoFailed(code: result)
        }
        guard addrInfoPointer != nil else {
            throw Socket.Error.NoAddressAvailable
        }

        _addrInfoPointer = addrInfoPointer
    }

    deinit {
        freeaddrinfo(_addrInfoPointer)
    }
}

OK, this new version has an initializer that takes all the parameters getaddrinfo needs, except the result pointer. Then it does a little switch-dance to handle the fact that String bridges to UnsafePointer<Int8> automatically, but String? does not, then stores the resulting pointer.

We can now resolve a hostname and serivce name to addresses. So far, so good, but calling code can’t access any resolved addresses just yet because the linked list is private:

let addressInfo = try AddressInfo(host: "obdev.at", port: "443")
// Now what?

We need a way to access the list of resolved addresses, ideally iterate over it. “Iterate over it”? Sounds like we want to put AddressInfo into a for loop, so we need AddressInfo to conform to SequenceType:

extension AddressInfo: SequenceType {
    func generate() -> AnyGenerator<addrinfo> {
        var cursor = _addrInfoPointer
        return AnyGenerator {
            guard cursor != nil else {
                return nil
            }
            let addrInfo = cursor.memory
            cursor = addrInfo.ai_next

            // Prevent access to the next element of the linked list:
            addrInfo.ai_next = nil

            return addrInfo
        }
    }
}

Implementing protocol SequenceType isn’t really hard once you know how the different parts that come from the Swift standard library fit together. SequenceType requires that we implement a generate method that returns a GeneratorType. The protocol GeneratorType on the other hand is something that has a next method that returns “the next element”, in our case the next addrinfo from the linked list.

We could now implement a struct AddressInfoGenerator (or something named similarly) that remembers which element of our linked list it has to return the next time next is called. Or we can use the generic struct AnyGenerator that the standard library offers. That has an initializer that takes a closure that acts as the implementation of next. We can therefore implement all this with just the code above.

This implementation should look very similar for any kind of linked list. The only thing special we’re doing is setting the ai_next to nil to prevent the calling code from doing something stupid and storing a pointer it should not. Note that the above code does not modify the internal linked list. Instead, it copies an addrinfo from the linked list to a local variable, sets the ai_next of only that local copy to nil, then returns a copy of that. Therefore, the internal linked list remains intact, while the for loop gets a copy of an element without a link to the next one.

The call site can now loop over the addresses like this:

let addressInfo = try AddressInfo(host: "obdev.at", port: "443")
for address in addressInfo {
    // Do something with `address`, which is an `addrinfo`.
    ...
}

Where it breaks down

The above code works great… as long as we run that in a Swift Playground or at the top level of a main.swift file. When we start using this in real code we see strange values passed into the for loop body, unexpected numbers of loop iterations, or random crashes in the generate method where cursor.memory is accessed.

This erratic behavior seems to occur more often if the AddressInfo instance isn’t stored in a local variable, but constructed inline in the for loop:

func testAddressInfo() {
    for addrInfo in try! AddressInfo(host: "obdev.at", port: "443") {
        print("Doing something with addrInfo")
    }
    print("Done iterating over list")
}

When we also add a print in AddressInfo.deinit and run this, we see log messages like these:

AddressInfo.deinit called
Doing something with addrInfo
Doing something with addrInfo
Done iterating over list

That doesn’t look good. The linked list is freed and all of its internal pointers invalidated even before the for loop’s body executes even once! Obviously, AddressInfo.deinit is called too early. What’s going on here?

(Note: The lifetime of top level objects is a bit different in Swift Playgrounds and top level code in a main.swift. That’s why the erratic behavior may not be observable there.)

It sure seems like ARC is eager to release the AddressInfo instance too early. We can assume that this is the order things happen in:

  1. AddressInfo is initialized, creating the linked list.
  2. The for loop calls AddressInfo.generate and we return an AnyGenerator, whereas the local cursor variable is set to the start of the linked list.
  3. AddressInfo.deinit is called, freeing the linked list and invalidating all the pointers to it and all the pointers inside all elements. Our cursor is no longer valid.
  4. AnyGenerator.next is called (i.e. the closure in generate) to get the next element of the linked list by accessing the invalid cursor.
  5. If the element is nil, the for loop is finished. Otherwise, the for loop body is executed with the element, then back to 4.

Now, accessing memory that was just freed is a funny thing. The values that were there could still be there, or they could be overwritten with something else, or the memory at that address could not be available to our process anymore. If that’s the case, we’re lucky and the operating system raises an exception right away (EXC_BAD_ACCESS). If we’re unlucky, we get garbage values and our program will behave strangely (but hopefully catch following errors!). We don’t want either thing to happen.

The Easy Solution

What we need to do is make ARC realize that the AddressInfo instance must be kept alive as long as the for loop is doing its thing. There’s another object that the for loop keeps around already: The AnyGenerator we return from generate. That in turn has a reference to the closure we pass to its initializer. So if we can get that closure to keep a reference to the AddressInfo instance, we have a solution:

extension AddressInfo: SequenceType {
    func generate() -> AnyGenerator<addrinfo> {
        var cursor = _addrInfoPointer
        return AnyGenerator {
            // The `withExtendedLifetime()` call isn't actually necessary, 
            // but it makes clearer why `self` is referenced here.
            withExtendedLifetime(self) {}

            guard cursor != nil else {
                return nil
            }
            let addrInfo = cursor.memory
            cursor = addrInfo.ai_next

            // Prevent access to the next element of the linked list:
            addrInfo.ai_next = nil

            return addrInfo
        }
    }
}

It would be sufficient to just write self in any line inside the closure to make this work. But I don’t really like that because it looks like something that was left over or something that doesn’t belong there. Also, it’s a statement with no effect and I’m not sure there won’t be a warning in a future version of Swift for that (I’d like that very much).

The withExtendedLifetime function doesn’t do anything here, because it is intended to just keep the object alive until the passed-in closure has finished and that closure is empty here. But in my opinion, it’s better than just putting self on an otherwise empty line because it makes clear why self is there and everyone who understands what withExtendedLifetime does should at least understand that there is some non trivial memory management going on here.

Wrapping it some more

While the above solution works fine, I don’t feel like this is the best we can do. That’s why the final implementation in our Socket Wrapper repository has a couple of improvements over this hacky memory management solution. Here’s a short rundown of the changes, followed by the final version of the code:

First, AddressInfo is named AddressInfoSequence to underline its sequence-y nature. It’s not a class, but a struct that has a private property named _storage to an instance of an internal class called Storage. This Storage is responsible for all the memory management and nothing else, while the AddressInfoSequence is responsible for exactly what the name implies: The addrinfo and the SequenceType stuff. That’s a nice separation of responsibilities.

Because AddressInfoSequence.Storage is the thing that must be kept alive for the lifetime of the for loop, the generate implementation with the hacky memory management above could simply reference that _storage instead of self (the whole AddressInfoSequence). But again, that would be a bit of a hack around Swift’s memory management. I feel more comfortable using a solution that does not rely on hacks, but instead uses a separate type to handle the memory management correctly. That is, we’re back to this idea of a struct AddressInfoGenerator mentioned before.

Here’s an abbreviated implementation of the whole thing:

struct AddressInfoSequence {
      private class Storage {
        private let _addrInfoPointer: UnsafeMutablePointer<addrinfo>

        init(addrInfoPointer: UnsafeMutablePointer<addrinfo>) {
            _addrInfoPointer = addrInfoPointer
        }

        deinit {
            freeaddrinfo(_addrInfoPointer)
        }   
    }

    private let _storage: Storage

    private init(host: String?, port: String?, hints: UnsafePointer<addrinfo>) throws {
        var addrInfoPointer: UnsafeMutablePointer<addrinfo> = nil

        // Call `getaddrinfo` and fill in `addrInfoPointer`
        ...

        _storage = Storage(addrInfoPointer: addrInfoPointer)
    }
}

extension AddressInfoSequence: SequenceType {

    func generate() -> AddressInfoGenerator {
        return AddressInfoGenerator(storage: _addrInfoStorage)
    }

}

struct AddressInfoGenerator: GeneratorType {

    private let _storage: AddressInfoSequence.Storage
    private var _cursor: UnsafeMutablePointer<addrinfo>

    private init(storage: AddressInfoSequence.Storage) {
        _storage = storage
        _cursor = storage._addrInfoPointer
    }

    mutating func next() -> addrinfo? {
        guard _cursor != nil else {
            return nil
        }
        var addrInfo = _cursor.memory
        _cursor = addrInfo.ai_next

        // Prevent access to the next element of the linked list:
        addrInfo.ai_next = nil

        return addrInfo
    }

}

A struct with a class

In the introduction, I wrote something about this implementation having similarities to that of Swift’s own types with copy-on-write behavior. What I meant by that is a pattern that can be seen on collection types in the Swift standard library, e.g. Array, Dictionary, Set, String, and others.

All these types are structs, i.e. they have value semantics. For example, passing an Array to a function actually copies it and changes to the Array inside the function cannot be observed in the original Array outside of it. This alone sounds like it would be very inefficient, especially considering that it would be completely unnecessary if the function wouldn’t modify the passed-in Array. But of course these fundamentals are not implemented inefficiently in Swift.

It’s not as simple as this, but it’s enough to understand the principle: An Array is a struct with a single property. That property is an instance of a class called _ArrayBuffer. (Sounds familiar? Just like AddressInfoSequence and its internal Storage.) Because the Array only has a single property that is a reference type, copying an Array boils down to a single retain call on the _ArrayBuffer.

The missing thing that enables the copy-on-write behavior is a function called isUniquelyReferenced(). I won’t go into details about it here, but this comment in its source code explains it fairly well.

Understanding this principle allows you to implement your own efficient copy-on-write data types.

Conclusion

All this gives us everything we need to finally connect a socket to a peer. As mentioned before, an addrinfo contains all the information required to create a socket, as well as to connect it to a peer, i.e. the sockaddr we need to pass to Socket.connect. Also mentioned before: It’s common to loop over all resolved addresses and use the first that works.

So here it is, the promised high level, Swifty, SequenceType-conforming thing whose implementation has similarities to that of Swift’s own types with copy-on-write behavior:

func connectTo(host host: String, port: String) throws -> Socket? {
    for addrInfo in try AddressInfoSequence(host: host, port: port) {
        let socket = try Socket(addrInfo: addrInfo)
        try socket.connect(address: addrInfo.ai_addr, length: addrInfo.ai_addrlen)
        return socket
    }
    return nil
}

let socket = try connectTo(host: "obdev.at", port: "https")