Wednesday, April 30, 2014

Transparent IPv6 in socket APIs

In the screenshot attached to this post, I'm running my 'heartleech' program against Google's servers. Notice how it's connecting to the servers with IPv6. However, I didn't really do anything special to explicitly support IPv6. The latest Sockets API can transparently support both IPv4 and IPv6, on Win/Mac/Unix, without doing extra work.

The solution starts with the "getaddrinfo()" function, which replaces the older "gethostbyname()". This queries the DNS server, and if the server supports it, returns all the records in response, whether they be A (IPv4) or AAAA (IPv6) records.

By default, Heartleech just choose the first response record, which in this case is IPv6. It next calls the "socket()" function to create a socket. However, instead of choosing the address family explicitly (AF_INET or AF_INET6), it picks it from the returned address above. Likewise, when it calls "connect()", it again uses this same structure.

Thus, the code looks like:

    getaddrinfo(hostname, port, 0, &addr);
    fd = socket(addr->ai_family, SOCK_STREAM, 0);
    connect(fd, addr->ai_addr, addr->ai_addrlen);

You see how this works in the above screenshot. Heartleech calls "getaddrinfo()" and the debug messages list all the IP addresses in response. It picks whichever is the first response  and initiates the TCP connection. Sometimes this is IPv4 and sometimes it's IPv6 (as in the above example).

This would work splendidly expect for when it comes time to print the address in debug messages. The old IPv4-only functions were "inet_addr()" to parse the address, and "inet_ntoa()" to print it. The new functions are "inet_pton()" and "inet_ntop()" respectively. They don't act on the "sockaddr" address structure as the above functions do. Instead, they act on the raw addresses themselves. In other words, I have to call the functions differently depending upon whether they are IPv6 or IPv4, which is a bit annoying.

A further complication is WinXP. I know you want to ignore it, but it's something we'll have to live with for the next several years. It supports the 'getaddrinfo()' function, but not "inet_ntop()" functions. Therefore, I have to wrap this in the code.

Finally, sometimes you to manually select either IPv4 or IPv6, regardless of what getaddrinfo() returns first.

And that's it. If you search the 'heartleech.c' file for AF_INET6, the above list is everything I had to do in order to get IPv6 working. That includes a large list of non-trivial functionality. The program can go through a Socks5 proxy, negotiate the use of SSL using the STARTTLS protocol, complete the SSL handshake, and then send application-layer commands. All of this works with a minimal fuss over IPv6.


Anonymous said...

waw,, this amzing, thanks for your information,, i like it

John Moehrke said...

Even eaiser in java. Actually hard to force ipv4 only. Note that many old firewalls bound to ipv4 only and thus when ipv6 shows up it is unblocked.

John Moehrke