0

I have the following code to get information about the tcp ports:

var length = 0
if (sysctlbyname("net.inet.tcp.pcblist", nil, &length, nil, 0) < 0)
{
    perror("sysctlbyname")
}
else
{
    var buffer: [UInt8] = [UInt8](repeating: 0, count: Int(length))
    sysctlbyname("net.inet.tcp.pcblist", &buffer, &length, nil, 0)
}

I now want to convert the buffer into something more "useful". I read that the return value is a struct called "xinpgen". How can I convert the buffer into that struct?

I tried the following code to directly write the result into the struct variable:

var length = 0
if (sysctlbyname("net.inet.tcp.pcblist", nil, &length, nil, 0) < 0)
{
    perror("sysctlbyname")
}
else
{
    var input = xinpgen()
    sysctlbyname("net.inet.tcp.pcblist", &input, &length, nil, 0)
}

The call itself doesn't fail and seems to be successful. The variable contains some data that isn't zero. But shortly after the call is finished and the program continues, the app crashes:

error: memory read failed for 0x0

How can I use the buffer to fill the struct variable? And why does the second call fail?

inexcitus
  • 2,471
  • 2
  • 26
  • 41

1 Answers1

1

The data returned by sysctlbyname("net.inet.tcp.pcblist", ...) is not a single xinpgen structure, but a "packed list" of structures.

Your code writes more bytes to the memory address of the input variable than its size, the behaviour is undefined and a crash very likely.

I don't know if the structure of the returned data is documented, but the source code of inet.c shows how it can be parsed. Apparently the buffer starts with a struct xinpgen, followed by a variable number of struct xtcpcb, and each element has a length field which contains the offset to the next structure.

This is my attempt to translate the C code from the above source file to Swift:

var length = 0
if (sysctlbyname("net.inet.tcp.pcblist", nil, &length, nil, 0) < 0) {
    fatalError("sysctlbyname")
}

var buffer = [UInt8](repeating: 0, count: Int(length))
sysctlbyname("net.inet.tcp.pcblist", &buffer, &length, nil, 0)

buffer.withUnsafeBytes { bufPtr in

    // Pointer to first xinpgen structure:
    var p = bufPtr.baseAddress!
    var xig = p.bindMemory(to: xinpgen.self, capacity: 1)

    // Skip first xinpgen structure:
    p += Int(xig.pointee.xig_len)
    xig = p.bindMemory(to: xinpgen.self, capacity: 1)

    while Int(xig.pointee.xig_len) > MemoryLayout<xinpgen>.size {
        // Cast xig to xtcpcb pointer and derefernce:
        let tcpcb = xig.withMemoryRebound(to: xtcpcb.self, capacity: 1) {
            $0.pointee
        }
        print(tcpcb)

        // Advance pointer to next structure
        p += Int(xig.pointee.xig_len)
        xig = p.bindMemory(to: xinpgen.self, capacity: 1)
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thank you, I got it to work. netstat prints the url (stackoverflow.com or localhost) instead of the IP address. Is there any way to get this information in Swift? I have the IP address and want the url. – inexcitus Jul 02 '17 at 16:13
  • 1
    @SaschaSimon: `getnameinfo()` can be used to get the host name for an IP address. – Martin R Jul 02 '17 at 17:50
  • 1
    @SaschaSimon: This might help https://stackoverflow.com/questions/41494466/getting-an-ip-address-and-port-number-from-a-sockaddr-in-struct-in-swift (but without the NI_NUMERIC... options) – Martin R Jul 02 '17 at 18:06
  • Had to figure out how to convert my in_addr to a new sockaddr object (using inet_aton()) but now it works like a charm. Thank you! – inexcitus Jul 02 '17 at 18:53
  • 1
    @SaschaSimon: If you have a in_addr then you can just fill sa_addr, sa_family and sa_len of a sockaddr_in. – Martin R Jul 02 '17 at 19:12
  • Thank you (again), it works great. I have one final question: How can I use the getnameinfo() for IPv6 addresses? Is this even possible? the sockaddr has 14 bytes for the representation of the address, I don't think that this is enough for an IPv6 address. – inexcitus Jul 03 '17 at 15:47
  • And another question: is it possible to find out which process opened the connection (like lsof). – inexcitus Jul 03 '17 at 16:28
  • 1
    @SaschaSimon: getnameinfo is *meant* to work with IPv4+6 addresses. It gets the *pointer* to a sockaddr as argument, that pointer can point to a sockaddr_in6 (or sockaddr_storage which has room for all kind of addresses). – I cannot answer your other question, but note that "lsof" is open source, you can inspect what it does. – Martin R Jul 03 '17 at 18:20