The Problem:
If you’ve worked with network programing in recent years you’ve probably had to grapple with the IPv4 vs. IPv6 issue at some point. In short, IPv6 is a great new technology for the future of networking, but most applications will still have to support IPv4 for several years during the transition.
Working at the socket layer developers most commonly address this by creating both an IPv4 and IPv6 socket and then juggling them back and forth as needed. Some existing System.Net APIs do this for you, such as:
- ServicePoint [WebClient, HttpWebRequest, FtpWebRequest, SmtpClient, etc.]
- TcpClient(hostname, port)
- TcpClient.Connect(hostname, port)
- UdpClient.Connect(hostname, port)
- Socket.ConnectAsync(args) with DnsEndPoint.AddressFamily = Unspecified
These APIs were a helpful mitigation but they only covered a subset of the scenarios.
The Early Solution:
The native Windows Winsock APIs introduced a new feature in Vista called Dual Mode sockets (http://msdn.microsoft.com/en-us/library/windows/desktop/bb513665(v=vs.85).aspx). This allows you to create one socket and use it for either IPv4 or IPv6 communication. To do this you create an IPv6 socket, set the IPV6_V6ONLY socket option to false (0), and then append the IPv6 prefix “0:0:0:0:0:FFFF:” to your IPv4 addresses such as “0:0:0:0:0:FFFF:127.0.0.1”. It takes some getting used to reading your IPv4 addresses like that, but it is easier than trying to manage two different sockets.
Since System.Net.Sockets.Socket is a thin wrapper over the native Winsock APIs, the Dual Mode functionality was immediately available at the .NET layer. Unfortunately it was difficult to configure and use, and a few scenarios didn’t work (UDP ReceiveMessageFrom, etc.). Take the following example of a Dual Mode client and server written using .NET 3.5:
// Server
TcpListener listener = new TcpListener(IPAddress.IPv6Any, port);
listener.Server.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
listener.Start();
// Client
Socket socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
IPAddress address = IPAddress.Parse(“::FFFF:” + IPAddress.Loopback.ToString());
socket.Connect(address, port);
This approach gets increasingly difficult for APIs such as Socket.Connect(IPAddress[] list, int port), and is impossible for APIs like Socket.Connect(string hostname, int port).
In .NET 4.0 the SocketOptionName.IPv6Only (27) enum value was added, but otherwise this scenario was unchanged.
The .NET 4.5 Polished Solution:
With .NET 4.5 the entire Socket API has been updated so that Dual Mode is easy to configure, use, and it just works! The Socket class now has a boolean DualMode property to enable the socket option, a new constructor that does this by default, IPv4 addresses will be internally converted to IPv6 addresses for you, and every API has been validated to make sure it works when Dual Mode is enabled. There is also a new static TcpListener.Create method for creating a simple Dual Mode server. See what the above code sample looks like now:
// Server
TcpListener listener = TcpListener.Create(port);
listener.Start();
// Client
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(IPAddress.Loopback, port);
Something similar can be done for TcpClient:
TcpClient client = new TcpClient(AddressFamily.InterNetworkV6);
client.Client.DualMode = true;
client.Connect(IPAddress.Loopback, port);
Note that the APIs listed at the top of this article that internally emulate Dual Mode have not been changed in order to maintain compatibility with existing applications. Otherwise all Connect, Bind, SendTo, ReceiveFrom, etc. Socket APIs now work with Dual Mode. There are also some new helper methods on the IPAddress class if you find yourself needing to convert between IPv4 and IPv6 address representations (e.g. 127.0.0.1 to or from ::FFFF:127.0.0.1), see IPAddress.MapToIPv4() and MapToIPv6().
Enjoy!
~Chris Ross
0 comments