3

POSIX getaddrinfo allocates memory that must later be freed using freeaddrinfo. See http://manpages.ubuntu.com/manpages/xenial/en/man3/getaddrinfo.3.html

To simplify the API, I've created this function:

import Foundation

enum SystemError: Swift.Error {
    case getaddrinfo(Int32, Int32?)
}

public func getaddrinfo(node: String?, service: String?, hints: addrinfo?) throws -> [addrinfo] {
    var err: Int32
    var res: UnsafeMutablePointer<addrinfo>?
    if var hints = hints {
        err = getaddrinfo(node, service, &hints, &res)
    } else {
        err = getaddrinfo(node, service, nil, &res)
    }
    if err == EAI_SYSTEM {
        throw SystemError.getaddrinfo(err, errno)
    }
    if err != 0 {
        throw SystemError.getaddrinfo(err, nil)
    }
    defer {
        freeaddrinfo(res)
    }
    var result = [addrinfo]()
    var ai = res?.pointee
    while ai != nil {
        result.append(ai!)
        ai = ai!.ai_next?.pointee
    }
    return result
}

I don't feel that the function is correct, though.

  • How can the Swift memory model know that getaddrinfo allocates memory, and that Swift should not overwrite that memory with own stuff?
  • How can Swift know that freeaddrinfo deletes the whole list, and that it should copy out ai information that has been assigned to the result array?

What's the correct way to interface with getaddrinfo?

Etan
  • 17,014
  • 17
  • 89
  • 148

1 Answers1

4

Memory allocated by getaddrinfo (e.g. by malloc) will not be given to any other dynamic memory allocation function in the same running process until released by freeaddrinfo (e.g. by free). Therefore the Swift runtime will not trample on that memory (if we assume that it has no programming errors such as wrong pointer calculations).

Also struct addrinfo is a value type, so

result.append(ai!)

will append a copy of the pointed-to structure to the array.

But there is still a problem. Some members of struct addrinfo are pointers

public var ai_canonname: UnsafeMutablePointer<Int8>! /* canonical name for hostname */
public var ai_addr: UnsafeMutablePointer<sockaddr>! /* binary address */

which may point into the memory allocated by getaddrinfo and therefore invalid after freeaddrinfo, and dereferencing them after your function returns causes undefined behaviour.

Therefore you must either postpone the freeaddrinfo until the address list is not needed anymore, or copy the information. This is a bit cumbersome because ai_addr may point to a IPv4 or IPv6 socket address structure which have different length.

The following code demonstrates how the address list can be copied to an array of sockaddr_storage structures (which are large enough to hold any IP address). This code has not been thoroughly tested, so use it with care.

public func getaddrinfo(node: String, service: String, hints: addrinfo?) throws -> [sockaddr_storage] {
    var err: Int32
    var res: UnsafeMutablePointer<addrinfo>?
    if var hints = hints {
        err = getaddrinfo(node, service, &hints, &res)
    } else {
        err = getaddrinfo(node, service, nil, &res)
    }
    if err == EAI_SYSTEM {
        throw SystemError.getaddrinfo(err, errno)
    }
    if err != 0 {
        throw SystemError.getaddrinfo(err, nil)
    }
    defer {
        freeaddrinfo(res)
    }
    guard let firstAddr = res else {
        return []
    }

    var result = [sockaddr_storage]()
    for addr in sequence(first: firstAddr, next: { $0.pointee.ai_next }) {
        var sockAddr = sockaddr_storage()
        memcpy(&sockAddr, addr.pointee.ai_addr, Int(addr.pointee.ai_addrlen))
        result.append(sockAddr)
    }
    return result
}

Remarks:

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • This is tangential to your answer, but: where did you get the Swift versions of the `addrinfo` fields? Did you just rewrite them by hand, or is there a tool that displays the Swift interface to C code? (I am dealing with C in Swift this week and would love to know.) – jscs Oct 04 '16 at 18:54
  • @JoshCaswell: I am not sure if I understand your question. Command-click on `addrinfo` displays the header as it is imported to Swift. – You can also add `#import ` to any C file, command-click on "netdb.h" to jump to that file, and then choose "Generated Interface". – Martin R Oct 04 '16 at 18:59
  • "Generated interface" in Xcode is the piece I was missing, thank you! – jscs Oct 04 '16 at 19:05
  • Even though in theory, value types should be copied, can't the compiler optimization mess this up? e.g. copy-on-write etc. – Etan Oct 04 '16 at 19:54
  • 1
    @Etan: Swift uses cow for the *backing store* of e.g. strings and arrays, i.e. only for types that it knows and manages. `struct addrinfo` is imported from C and Swift won't mess with it. – Martin R Oct 04 '16 at 19:57
  • Thanks for that info, makes sense! However, is there any official source to read about Swift's memory model, or is this just insider knowledge? – Etan Oct 04 '16 at 20:19
  • 1
    @Etan: Unfortunately I do not have *the* reference but here are some pointers: https://developer.apple.com/reference/swift/array mentions copy-on-write, https://github.com/apple/swift/blob/master/docs/Arrays.rst and https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html show how arrays are implemented. Joe Groff states at https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20160516/001980.html that Swift respects the layout of imported structures. – Martin R Oct 04 '16 at 20:36
  • Thanks for the resources! I've revised the function and put it up here: http://pastebin.com/y7nDATSH – in the end, I went with the linked list to mimic the original behaviour as close as possible. – Etan Oct 04 '16 at 21:47