It is also possible for the listening IPv6 socket to accept IPv4 connections with a dual-stack socket. Not all operating systems support dual-stack sockets. With Linux in particular, support varies between distros. If your operating system does support dual-stack sockets, then I highly recommend implementing your server programs using this feature. It allows your programs to communicate with both IPv4 and IPv6 peers while requiring no extra work on your part.
We can modify time_server_ipv6.c to use dual-stack sockets with only a minor addition. After the call to socket() and before the call to bind(), we must clear the IPV6_V6ONLY flag on the socket. This is done with the setsockopt() function:
/*time_server_dual.c excerpt*/
int option = 0;
if (setsockopt(socket_listen, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&option, sizeof(option))) {
fprintf(stderr, "setsockopt() failed. (%d)\n", GETSOCKETERRNO());
return 1;
}
We first declare option as an integer and set it to 0. IPV6_V6ONLY is enabled by default, so we clear it by setting it to 0. setsockopt() is called on the listening socket. We pass in IPPROTO_IPV6 to tell it what part of the socket we're operating on, and we pass in IPV6_V6ONLY to tell it which flag we are setting. We then pass in a pointer to our option and its length. setsockopt() returns 0 on success.
Windows Vista and later supports dual-stack sockets. However, many Windows headers are missing the definitions for IPV6_V6ONLY. For this reason, it might make sense to include the following code snippet at the top of the file:
/*time_server_dual.c excerpt*/
#if !defined(IPV6_V6ONLY)
#define IPV6_V6ONLY 27
#endif
Keep in mind that the socket needs to be initially created as an IPv6 socket. This is accomplished with the hints.ai_family = AF_INET6 line in our code.
When an IPv4 peer connects to our dual-stack server, the connection is remapped to an IPv6 connection. This happens automatically and is taken care of by the operating system. When your program sees the client IP address, it will still be presented as a special IPv6 address. These are represented by IPv6 addresses where the first 96 bits consist of the prefix—0:0:0:0:0:ffff. The last 32 bits of the address are used to store the IPv4 address. For example, if a client connects with the IPv4 address 192.168.2.107, then your dual-stack server sees it as the IPv6 address ::ffff.192.168.2.107.
Here is what it looks like to compile, run, and connect to time_server_dual:
Here is a web browser connected to our time_server_dual program using the loopback IPv4 address:
Notice that the browser is navigating to the IPv4 address 127.0.0.1, but we can see on the console that the server sees the connection as coming from the IPv6 address ::ffff:127.0.0.1.
See time_server_dual.c for the complete dual-stack socket server.