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.
getaddrinfo
allocates memory, and that Swift should not overwrite that memory with own stuff?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
?
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:
If you are only interested in a single address family then
you can replace sockaddr_storage
by sockaddr_in
or
sockaddr_in6
.
I have made the node
and service
parameters non-optional.
The reason is that Swift currently has a Bug when passing more
than one optional String to a C function, see
Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?.
sequence()
is used here to traverse the linked list instead of
a while
loop.