Etan Etan - 2 months ago 47
Swift Question

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.


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

Answer

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: