Creating IP Agnostic Applications - Part 2 (Dual Mode Sockets)

In a previous post I wrote about how on Windows Vista and Windows Server "Longhorn," IPv6 is installed and enabled by default and that when both IPv4 and IPv6 are enabled, the TCP/IP stack prefers to use IPv6 over IPv4.  With the growth of IPv6, applications must now work seamlessly over both protocols (IPv4 & IPv6).  The remainder of this post discusses one way to make handling both protocols easier by using a single socket that works with IPv4 & IPv6.  No longer do you need to create two sockets to make your application IPv4 & IPv6 aware :).

Windows Vista and Windows Server Longhorn includes a new TCP/IP stack which includes common TCP & UDP layers on top of the IPv4 and IPv6 layers.  This new architecture has made it possible for a single IPv6 socket to work with both IPv6 and IPv4.  For example, using a single IPv6 socket one can now accept BOTH IPv4 and IPv6 traffic.  Such a socket is typically dubbed a "dual mode" socket.  Dual mode sockets are usable by managed code sockets (ie. System.Net.Sockets.Socket), native Winsock sockets as well as Winsock Kernel (WSK) sockets.    

To create a "dual mode" socket, the following steps are needed:

  1. Create an IPv6 socket
  2. Set the IPV6_V6ONLY socket option to false

Once you have created the socket and set the appropriate option, the socket can be used to accept incoming IPv4/v6 connections or connect to an IPv4/v6 destination.  The snippets below outline accepting incoming connections over both protocols in native and managed code:

Note: The code is meant as demonstration code; error code checking and exception handling have been left off the code snippets to keep them as concise as possible.

.NET (C#)

 Socket sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
// 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below
sock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);
sock.Bind(new IPEndPoint(IPAddress.IPv6Any, 8000));
sock.Listen(5);
Socket client = sock.Accept();
// send / receive data on 'client' socket 

Winsock

 WSADATA wsaData;
SOCKET lsock = INVALID_SOCKET;
SOCKET csock = INVALID_SOCKET;
SOCKADDR_STORAGE serverAddr = {0};
SOCKADDR_STORAGE clientAddr = {0};
int off = 0;
int port = 8000;
int clientAddrLen = sizeof(clientAddr);
WSAStartup(MAKEWORD(2,2), &wsaData); 
serverAddr.ss_family = AF_INET6;
INETADDR_SETANY((SOCKADDR *)&serverAddr);
SS_PORT(&serverAddr) = htons(port);
lsock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)

//make the socket a dual mode socket
setsockopt(lsock,IPPROTO_IPV6,IPV6_V6ONLY,(char *)&off, sizeof(off));
bind(lsock, (SOCKADDR *)&serverAddr, (int)INET_SOCKADDR_LENGTH(serverAddr.ss_family));
listen(lsock, 5);
csock = accept(lsock, (SOCKADDR *)&clientAddr, &clientAddrLen); 
... 
... 
WSACleanup(); 

When using a dual mode socket, it is important to remember the socket is at root an IPv6 socket.   Since this is the case, when specifying an IPv4 address to such a socket (ie. connect to IPv4 endpoint using a dual mode socket) one must format the IPv4 address as an IPv4 mapped address prior to passing the address to a socket function.  Fortunately, one can use IN6ADDR_SETV4MAPPED(...), defined in mstcpip.h to simplify this task.

The following shows an example of what this looks like in Winsock:

 WSADATA wsaData;
SOCKET csock = INVALID_SOCKET;
SOCKADDR_STORAGE addrLoopback4 = {0}; 
int port = 8000;
int off = 0; 
addrLoopback4.ss_family = AF_INET;
INETADDR_SETLOOPBACK((SOCKADDR*)&addrLoopback4);
SS_PORT(&addrLoopback4) = htons(port); 
WSAStartup(MAKEWORD(2,2), &wsaData);
if((csock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){
     //error creating the listening socket
     WSACleanup();
     return 1;
} 
//make the socket a dual mode socket
setsockopt(csock,IPPROTO_IPV6,IPV6_V6ONLY,(char *)&off, sizeof(off)); 
// format the address as a v4 mapped address if needed
ConvertToV4MappedAddressIfNeeded((SOCKADDR *) &addrLoopback4);
// connect
connect(csock, (SOCKADDR *)&addrLoopback4, sizeof(addrLoopback4)); 
printf("Connect complete"); 
closesocket(csock); 
WSACleanup();
return 0; 
  


void ConvertToV4MappedAddressIfNeeded(PSOCKADDR pAddr)
{
    // if v4 address, convert to v4 mapped v6 address
    if (AF_INET == pAddr->sa_family)
    {
        IN_ADDR In4addr;    
        SCOPE_ID scope = INETADDR_SCOPE_ID(pAddr);
        USHORT port = INETADDR_PORT(pAddr);
        In4addr = *(IN_ADDR*)INETADDR_ADDRESS(pAddr);
        ZeroMemory(pAddr, sizeof(SOCKADDR_STORAGE));
        IN6ADDR_SETV4MAPPED(
             (PSOCKADDR_IN6)pAddr,
             &In4addr,
             scope,
             port
             ); 
    }
} 

 

Cheers,

-- Mike Flasko