Low-Level Network Address Resolving in Swift
This blog post is the third in a five part series about POSIX Sockets in Swift and our SocketWrapper Library:
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:
- Decide which peer to connect to, i.e. a host and port.
- Resolve the host and port to an address by calling
getaddrinfo(3)
. - Create a suitable socket by calling
socket(2)
. - 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
andserviceName
in their original C variant areconst char *
, i.e. C strings. Luckily, Swift’sString
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 benil
or a pointer to astruct 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 theai_family
toPF_INET6
.result
is an out-parameter, i.e. we pass in anUnsafeMutablePointer
and when the function returns, it will contain anUnsafeMutablePointer<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 addrinfo
s 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:
AddressInfo
is initialized, creating the linked list.- The
for
loop callsAddressInfo.generate
and we return anAnyGenerator
, whereas the localcursor
variable is set to the start of the linked list. AddressInfo.deinit
is called, freeing the linked list and invalidating all the pointers to it and all the pointers inside all elements. Ourcursor
is no longer valid.AnyGenerator.next
is called (i.e. the closure ingenerate
) to get the next element of the linked list by accessing the invalidcursor
.- If the element is
nil
, thefor
loop is finished. Otherwise, thefor
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 struct
s, 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")