Jigzat Jigzat - 4 months ago 33
Swift Question

Swift obtaining ip address from socket returns weird value

I'm trying to obtain the remote ip address using

getpeername()
under iOS/Swift (real hardware no emulation).

This is what I'm doing:

var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
var len: socklen_t = socklen_t(sizeof(Int32))

if getpeername(socket, &addr, &len) != -1
{
var ipAddressString = [CChar](count:Int(INET_ADDRSTRLEN), repeatedValue: 0)
inet_ntop(
AF_INET ,
&addr,
&ipAddressString,
socklen_t(INET_ADDRSTRLEN))
println("socket \(socket) ip \(String.fromCString(ipAddressString))")
}


the values that I'm getting are:

socket 7 ip Optional("16.2.209.237")


and that is certainly not the remote address. Please can someone help me?
What I'm doing wrong?

Answer

The main error is that inet_ntop() takes the address of a struct in_addr (or struct in_addr6 for IPv6), and not the address of a struct sockaddr.

Another error is that the length argument to getpeername() must be the length of the passed socket address structure, you are passing the length of an Int32.

Your current code passes AF_INET to inet_ntop() and is therefore limited to IPv4 addresses. If that is sufficient for you, the following should work:

var addr = UnsafeMutablePointer<sockaddr_in>.alloc(1)
var len = socklen_t(sizeofValue(addr.memory))

if getpeername(sockfd, UnsafeMutablePointer(addr), &len) != -1 {
    var ipAddressString = [CChar](count:Int(INET_ADDRSTRLEN), repeatedValue: 0)
    inet_ntop(
        AF_INET ,
        &addr.memory.sin_addr, // <-- main difference here
        &ipAddressString,
        socklen_t(INET_ADDRSTRLEN))
    println("socket \(sockfd) ip \(String.fromCString(ipAddressString)!)")
}
addr.dealloc(1)

Allocating the socket address structure makes casting between the different pointer types a bit easier. I have also replaced the variable name socket by sockfd to avoid confusion with the socket() function.

The more "modern" function to convert socket addresses to strings is getnameinfo(). The following code demonstrates how to use it. It works for both IPv4 and IPv6 addresses:

var addr = UnsafeMutablePointer<sockaddr_storage>.alloc(1)
var len = socklen_t(sizeofValue(addr.memory))

if getpeername(sockfd, UnsafeMutablePointer(addr), &len) != -1 {

    var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
    if (getnameinfo(UnsafeMutablePointer(addr), socklen_t(addr.memory.ss_len),
        &hostBuffer, socklen_t(hostBuffer.count), nil, 0,
        NI_NUMERICHOST) == 0) {
            let host = String.fromCString(hostBuffer)!
            println("socket \(sockfd) ip \(host)")
    }
}
addr.dealloc(1)

Swift 2 update: First method:

var addr = sockaddr_in()
var len = socklen_t(sizeofValue(addr))

withUnsafeMutablePointer(&addr) {
    if getpeername(sockfd, UnsafeMutablePointer($0), &len) != -1 {
        var ipAddressString = [CChar](count:Int(INET_ADDRSTRLEN), repeatedValue: 0)
        inet_ntop(
            AF_INET ,
            &addr.sin_addr, // <-- main difference here
            &ipAddressString,
            socklen_t(INET_ADDRSTRLEN))
        print("socket \(sockfd) ip \(String.fromCString(ipAddressString)!)")
    }
}

Second method:

var addr = sockaddr_storage()
var len = socklen_t(sizeofValue(addr))

withUnsafeMutablePointer(&addr) {
    if getpeername(sockfd, UnsafeMutablePointer($0), &len) != -1 {
        var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
        if (getnameinfo(UnsafeMutablePointer($0), socklen_t(addr.ss_len),
            &hostBuffer, socklen_t(hostBuffer.count), nil, 0,
            NI_NUMERICHOST) == 0) {
                let host = String.fromCString(hostBuffer)!
                print("socket \(sockfd) ip \(host)")
        }
    }
}