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 (19)

  1. 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

  2. wndpteam says:

    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

  3. With the adoption of IPv6 ever increasing (ie. it is the preferred protocol on Windows Vista) it is important

  4. Alan McFarlane says:

    Just to let you know; it really works! :-,)  I added code of that sort to a server here, and on Vista on such a connection, connections from both protocols are accepted. :-)

    e.g.

    Waiting for connection…

    23:18, new connection from: ::ffff:192.168.0.3:49169

    23:18, server instance exited due to: ConnectionGracefulClose

    Waiting for connection…

    23:18, new connection from: fe80::1519:27d:3c72:2b97%11:49170

    23:18, server instance exited due to: ConnectionGracefulClose

    Nice.

    The client-side actually proved more difficult.   If one is using a  TcpClient with an IPEndPoint, then the natural code is:

       TcpClient cli = new TcpClient();

       cli.Connect(ep);

    Which is apparently fine for IPv4 endpoints, but for IPv6 endpoints it fails with:

       System.Net.Sockets.SocketException

       "An address incompatible with the requested protocol was used"

       ErrorCode 10047

       SocketErrorCode AddressFamilyNotSupported

    Connect(IPAddress,Int32) fails the same way.

    And finally if one also has the address in string form, and thus tries instead:

       TcpClient cli = new TcpClient();

       cli.Connect(addrAsString, PortNumber);

    That fails with:

       System.Net.Sockets.SocketException

       "A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied"

       ErrorCode: 10057

       SocketErrorCode: NotConnected

    Huh, wtf?  Where does that error code come from?  That completely confused me, and only many light-years :-,) later did I discover that the required form is:

      TcpClient cli = new TcpClient(ep.AddressFamily);

      cli.Connect(ep);

    Who knew?!

    So some suggestions:

    1. Deprecate TcpClient.#ctor()!  Ok, maybe not… :-)

    2. Or a FxCop rule.  Surely that pattern is best practice; and the warning can be suppressed.

    2. In the TcpClient.Connect methods, check if the family of ‘this’ and the endpoint are not equal, and if so throw something immediately enlightening:  ArgumentException, or perhaps InvalidOperationException, saying "AddressFamily of object and endpoint must be equal, use #ctor(AF)."

    3. I see the line in the #ctor() help page: "The constructor works only for IPv4", a corresponding line in all(?) the Connect methods would likely be helpful: "To suit both IPv4 and IPv6 usage, use #ctor(ep.AddressFamily) and not #ctor()".

    I’ll go and create a suggestion with that content at the ‘connect’ feedback site, unless someone wants to do so internally, or I’ve misunderstood things dramatically. :-)

    Alan

  5. wndpteam says:

    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

  6. Alan McFarlane says:

    Thanks Mike.  I’m glad to say that, having gone back and checked the code, we had used 3), sorry for not noting that in my previous comment.

    Understand about creating the socket as it may be accessed through the Client property.  Good to know extensions are being considered.  At the least I hope an explicit exception can be thrown if the address family at construction and connect (etc) are different.

    Alan

  7. Jeff Melnick says:

    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.

  8. Oscar says:

    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

  9. Oscar says:

    Thanks a lot Dave!

    /Oscar

  10. Bhuvanesh says:

    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?

  11. ewart says:

    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

  12. Alex says:

    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

  13. Chitrak says:

    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.

  14. keerthi says:

    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.

  15. Yu Wang says:

    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

  16. 大 兵 says:

    原文:HowtousetheSocketAsyncEventArgsclass.byMarcosHidalgoNunes

  17. Rhutesh K says:

    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

  18. Naruto says:

    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…

     

     

  19. AriPernick says:

    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