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