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

Comments

  • Anonymous
    October 26, 2006
    The first pair of samples use respectively AddressFamily.InterNetwork (2), and AF_INET6 (23).  Is there some magic in the .NET FCL, or should we actually be using AddressFamily.InterNetworkV6 in the .NET code? Alan

  • Anonymous
    October 26, 2006
    Good eye!  Thank is a typo on my part.  I have changed the .NET sample to create a socket of address family InterNetworkV6.  Thanks. -Mike

  • Anonymous
    October 30, 2006
    With the adoption of IPv6 ever increasing (ie. it is the preferred protocol on Windows Vista) it is important

  • Anonymous
    November 17, 2006
    The comment has been removed

  • Anonymous
    November 17, 2006
    Thanks for the feedback!  I'm glad to hear you are having success with dual mode sockets. Going through your comments below: As you found out, the core of all the exceptions you are seeing is rooted in the fact you are using the default constructor // from your snippet: TcpClient cli = new TcpClient(); This constructor creates an IPv4 socket and thus connecting to an IPv6 endpoint will fail.  For compatibility reasons, this has not been updated (ie. we cannot delay socket creation in this case as existing users may have been accessing the Client property prior to calling connect).  We are considering extensions to TCPClient to make writing dual mode code even simpler, stay tuned :) The options you have on Vista are:

  1. use TcpClient(IPEndPoint localEP) constructor.  This creates a socket based on the address family of the endpoint provided and binds the socket to localEP.
  2. as you pointed out use TcpClient(AddressFamily family) constructor.  This simply creates a socket of the specified address family.
  3. Possibly the best bet of them all is to use TcpClient(string hostname, int port). This will resolve the hostname to a collection of IPAddressses, then foreach address (ipv4 or ipv6) it will create the appropriate socket and attempt a conection.  The first successful connection is then used by the TcpClient instance. On the FxCop side, thanks for the suggestion, we are currently working with the FxCop team to make rule additions that flag IPv4 only API usage. -Mike
  • Anonymous
    November 21, 2006
    The comment has been removed

  • Anonymous
    December 17, 2007
    Hello, I was wondering if you could think of a reason why INET_SOCKADDR_LENGTH would be undefined?  I've been getting compiler errors on this and I havent' been able to figure it out.  Any insight would be appreciated. Thank you.

  • Anonymous
    March 03, 2008
    Hi, is there a c# function that we can use to convert ipv4 to ipv6 addresses? /Oscar There's no built-in function to do this for you yet, but it's not hard to do yourself: IPAddress v4address = ...; byte[] v4bytes = v4address.GetAddressBytes(); byte[] v6bytes = new byte[16]; v6bytes[10] = 0xFF; v6bytes[11] = 0xFF; v4bytes.CopyTo(v6bytes, 12); IPAddress v6address = new IPAddress(v6bytes); -dave

  • Anonymous
    March 06, 2008
    Thanks a lot Dave! /Oscar

  • Anonymous
    March 11, 2008
    I am using managed framework to build application. The socket option is available only for OS which are Vista and higher ( http://msdn2.microsoft.com/en-us/library/ms738574(VS.85).aspx ). How does one create IP agnostic application on older OS?

  • Anonymous
    March 13, 2008
    I take it dual mode sockets are not supported on XP?   I assume that would explain why the below fails on XP (even though Socket.OSSupportsIPv6 returns true as I have the IPv6 protocol installed) sock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0); in which case do I have to check for Vista/2003 to use dual sockets, and on XP set some other socket option to use IPv6 only? ..having a bit of difficulty in finding my local IPv6 address and binding to it on XP. cheers ewart

  • Anonymous
    March 17, 2008
    Great posting, thanks Mike. Does the dual mode only work for listening sockets, or also for "normal" outgoing socket connections? This could help me to get rid of lots of socket constructor code which either creates a V4 or V6 socket. // example // IPV6 Support                if (Socket.OSSupportsIPv6 && ipEndPoint.AddressFamily == AddressFamily.InterNetworkV6)                                    proxySocket = new ProxySocket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); // V6                else                                    proxySocket = new ProxySocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  // V4 Alex

  • Anonymous
    July 20, 2008
    I have an app with dual stack, supporting IPv4 mapped and IPV6 addresses. I have an option of choosing my application to use IPv4 only IPv6 only or dual using IPTables. If I select IPv4 only and filter using IPtables will the dual stack operation work or do we have to use two socket way.

  • Anonymous
    July 30, 2008
    hi, i tried running the example given by you, but it says INETADDR_ADDRESS identifier not found. Do i need to include some other libraries/headers.

  • Anonymous
    November 09, 2008
    Hello Mike, I even cannot find definition of macro "IPV6_V6ONLY" in my compilation environment which is windows 2003 and VC8. And I cannot find macro IN6ADDR_SETV4MAPPED. Is it only supported on vista or windows 2008. IPV6_V6ONLY appears in ws2ipdef.h which I believe rolls up into ws2tpcip.h. According to MSDN, IN6ADDR_SETV4MAPPED is in mstcpip.h. In my copy of of it is protected by a #if (NTDDI_VERSION >= NTDDI_VISTA) block. So yes, Vista and above. -- Ari

  • Anonymous
    June 16, 2009
    原文:HowtousetheSocketAsyncEventArgsclass.byMarcosHidalgoNunes

  • Anonymous
    June 21, 2010
    No need to do all these.. While creating socket object, simply use first parameter as IPAddress.AddressFamily instead of directly specifying "InterNetwork OR InterNetworkV6" This will surely solve the problem Use this link: stackoverflow.com/.../socketexception-address-incompatible-with-requested-protocol

  • Anonymous
    December 14, 2011
    Hi, When i compile above code, i get the following error message. code inline below. //Code #include <iostream> #include <WinSock2.h> #include <conio.h> #include <mstcpip.h> #include <WS2tcpip.h> #include <Ws2ipdef.h> using namespace std; #pragma comment(lib, "WS2_32.lib") void ConvertToV4MappedAddressIfNeeded(PSOCKADDR pAddr); int main() { WSADATA wsaData; SOCKET csock = INVALID_SOCKET; SOCKADDR_STORAGE addrLoopback4 = {0}; int port = 8000; int off = 0; int err; ADDRINFO addrInfoHints; ADDRINFO* paddrServerInfo; WSAStartup(MAKEWORD(2,2), &wsaData); addrInfoHints.ai_family = AF_INET6; addrInfoHints.ai_protocol = IPPROTO_IPV6; addrInfoHints.ai_flags = AI_PASSIVE; addrInfoHints.ai_socktype = SOCK_DGRAM; err = getaddrinfo("fe80:xxxxxxxxxxx", "8989", &addrInfoHints, &paddrServerInfo); addrLoopback4.ss_family = AF_INET; SS_PORT(&addrLoopback4) = htons(port); 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 *) &paddrServerInfo); // 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 );    } } //Code error C3861: 'INETADDR_SCOPE_ID': identifier not found error C3861: 'INETADDR_PORT': identifier not found error C3861: 'INETADDR_ADDRESS': identifier not found error C3861: 'IN6ADDR_SETV4MAPPED': identifier not found; Please let me know how do i resolve it...    

  • Anonymous
    December 16, 2011
    Forwarding a reply :) -- Ari


Sorry about the #include files here – as near as I can tell (I’m using a pre-release version of Windows on my machine), a working set of include files is:

 #define WIN32_LEAN_AND_MEAN // must set to lean
#include <windows.h> // must include windows
#include <iostream>
#include <WinSock2.h>
#include <conio.h>
#include <Ws2ipdef.h> // ws2ipdef.h has to come before mstcpip.h
#include <mstcpip.h>
#include <WS2tcpip.h>
 

Hope this helps, Peter Smith