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:
- Create an IPv6 socket
- 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