Migrating to IPv6

When writing a multiplayer game the question that arises more and more often is – "Should I bother about an IPv6?"

Short answer – YES! The IPv6 is no longer a tale from the future. The number of people using the newest version of the Internet Protocol increases really fast. Quick look at Google Statiscits and you will see, that numbers of requests to Google via IPv6 grows expotentially and when writing this post it reaches 18%

One of the reason why the IPv6 suddenly became a thing (except the main one – that we’re running out of addresses) is that Apple started rejecting apps that were not working with IPv6-only networks (Source). Shortly after that – Amazon announced support for IPv6 on their EC2 servers (Source). Currently – the biggest market that uses IPv6 is the mobile one, where number of devices might be even greater than the number of stationary computers.

So, how the networking code should be written to support the IPv6?

IPv4

Let’s start with a code that is usually used when implementing UDP with IPv4 support.
// Create Socket
int Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
// Create Address
sockaddr_in Address;
Address.sin_family = AF_INET;
Address.sin_addr.s_addr = htonl(INADDR_ANY);
Address.sin_port = 0;
 
// Bind
bind(Socket, (const sockaddr*)&Address, sizeof(sockaddr_in));
 
// Send Data to DstAddress
sendto(Socket, (const char*)Data, DataSize, 0, (const sockaddr*)&DstAddress, sizeof(sockaddr_in));
 
// Receive Data
int Size = sizeof(sockaddr_in);
recvfrom(Socket, (char*)Data, BufferSize, 0, (sockaddr*)&SrcAddress, &Size);
Now let’s jump to...

IPv6

The only things that must be changed are:
  • Socket – AF_INET to AF_INET6
  • Address – sockaddr_in to sockaddr_in6
  • Address family – AF_INET to AF_INET6
  • Any address implementation – INADDR_ANY to in6addr_any
// Create Socket
int Socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
 
// Create Address
sockaddr_in6 Address;
Address.sin6_family = AF_INET6;
Address.sin6_addr = in6addr_any;
Address.sin6_port = 0;
 
// Bind
bind(Socket, (const sockaddr*)&Address, sizeof(sockaddr_in6));
 
// Send Data to DstAddress
sendto(Socket, (const char*)Data, DataSize, 0, (const sockaddr*)&DstAddress, sizeof(sockaddr_in6));
 
// Receive Data
int Size = sizeof(sockaddr_in6);
recvfrom(Socket, (char*)Data, BufferSize, 0, (sockaddr*)&SrcAddress, &Size);
It doesn’t look so difficult, right? It is very important to remember, that when implementing IPv6 – AF_INET6 socket can also handle IPv4 transfers. And this is really neat, because you don’t have to, and you mustn’t do separate implementations for two versions of the internet protocol. Just stick with AF_INET6!

Address

We’ve got a socket, but how to properly construct an address to which we want to send data? The easiest way is to use DNS which should gives us the properly formatted address. But still, there are two types of addresses possible (IPv4 and IPv6) but one structure to handle them (sockaddr_in6). To learn how to store it properly – we must learn how getting addresses looks like in every situation.

IPv4 -> IPv4


The DNS recognizes that sender and recipient are in IPv4 networks, so it simply returns the IPv4 address. To fit it into the sockaddr_in6 structure we simply put 16 bits right before the 32 bits of the IPv4 address. This kind of transition is described in RFC2765. The structure of the address looks like that:


IPv6 -> IPv6


The situation where sender and recipient are in IPv6 network is really simple. We just send data to the address we’ve received.

IPv6 -> IPv4


When a sender is in IPv6 network and want to send data to the recipient in IPv4 network the data will have to go through NAT64 which is a special type of NAT that separates IPv6 networks from the IPv4 networks. NAT64 works very similar to the normal NAT, but when it opens a gateway it maps the IPv6 address to the IPv4 one, so the recipient can respond to this address even without the access to the IPv6 infrastructure! The synthesized address that came from DNS is written in a special format (defined in RFC6052 “Well-Known Prefix”) that NAT64 can translate into the IPv4 address of the recipient. In fact – there is no extra work for us to do in the code, because we simply operate on received from DNS IPv6 address.

IPv4 -> IPv6


As you can see – if the recipient doesn’t have any public IPv4 address there is no way for the sender from IPv4 network to send data to it. The code will handle it properly but the network will not be able to pass the data through itself.

Getting proper address

With the knowledge about how addresses are obtained in different situations we can finally write a code that will give us the address of the recipient.
// Prepare address
sockaddr_in6 Address;
Address.sin6_family = AF_INET6;
 
// Looking for IP of HostName
std::string HostName = "test.game.amazon.com";
 
// Prepare AddrInfo
addrinfo* AddrInfo = nullptr;
 
// Prepare hints info
addrinfo HintAddrInfo;
 
// Get AddrInfo
getaddrinfo(HostName, NULL, &HintAddrInfo, &AddrInfo);
 
// Check all received addresses
for (; AddrInfo != nullptr; AddrInfo = AddrInfo->ai_next)
{
    if (AddrInfo->ai_family == AF_INET6)
    {
        // Received IPv6 formatted Address
        sockaddr_in6* IPv6SockAddr = reinterpret_cast(AddrInfo->ai_addr);
        if (IPv6SockAddr != nullptr)
        {
            memcpy(&Address.sin6_addr, &IPv6SockAddr->sin6_addr, sizeof(struct in6_addr));
            break;
        }
    }
    else
    {
        // Received IPv4 formatted Address - map it to IPv6
        sockaddr_in* IPv4SockAddr = reinterpret_cast(AddrInfo->ai_addr);
        if (IPv4SockAddr != nullptr)
        {                       
            Address.sin6_addr.s6_addr[10] = 0xff;
            Address.sin6_addr.s6_addr[11] = 0xff;
            memcpy(&Address.sin6_addr.s6_addr[12], &IPv4SockAddr->sin_addr, sizeof(struct in_addr));
            break;
        }
    }
}
In the situation when the sender and recipient are in IPv4 network – the IPv4 address will be proposed. This address must be converted to fit the sin6_addr structure. As mentioned before – in the IPv4->IPv4 chapter – we must simply put 16 bits right before the 32 bits of the IPv4 address.

In any other situation we simply copy the IPv6 address to the previously prepared structure.

Conclusion

In fact – this is it! Moving to the IPv6 is rather easy and will let the users of IPv6-only networks to play multiplayer games. But remember to not to cut off IPv4 completely! Make sure that your server supports Dual-Stack and can be accessed via both IPv4 and IPv6 addresses. On Amazon EC2 server you can do this by setting up properly Security Group Inbound and Outbound:


In few years, lets hope, because of the IPv4 becoming obsolete, the implementation will be even easier.