swiftmemoryposixlow-level-api

Swift getaddrinfo


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.

What's the correct way to interface with getaddrinfo?


Solution

  • 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: