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:
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 belowsock.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
Socket sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
// 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet belowsock.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 socketsetsockopt(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();
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 socketsetsockopt(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 socketsetsockopt(csock,IPPROTO_IPV6,IPV6_V6ONLY,(char *)&off, sizeof(off)); // format the address as a v4 mapped address if neededConvertToV4MappedAddressIfNeeded((SOCKADDR *) &addrLoopback4);// connectconnect(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 ); }}
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 socketsetsockopt(csock,IPPROTO_IPV6,IPV6_V6ONLY,(char *)&off, sizeof(off));
// format the address as a v4 mapped address if neededConvertToV4MappedAddressIfNeeded((SOCKADDR *) &addrLoopback4);// connectconnect(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