Over the years I've developed a small mass of C++ server/client applications for Windows using WinSock (Routers, Web/Mail/FTP Servers, etc... etc...).
I’m starting to think more and more of creating an IPv6 version of these applications (While maintaining the original IPv4 version as well, of course).
Questions:
- What pitfalls might I run into?
- Is the porting/conversion difficult?
- Is the conversion worth it?
For a reference (or for fun), you can sneek a peak of the IPv4 code at the core of my applications.
getaddrinfo and getnameinfo are your friends.. As much as possible I suggest they be your best friends in your quest to provide IPv4 and IPv6 support in an existing application.
If done right by adding IPv6 support you also end up abstracting the system to the point where an unknown future IP protocol can run without code modification.
Normally when connecting you would fill out a socket structure, port, address family, IP address, converting address/ports to network byte order, etc.
With getaddrinfo
you send an IP address or hostname and port or port name, and it returns a linked list with the structures and everything ready to be passed directly into socket()
and connect()
.
getaddrinfo
is critical for working with both IP protocols as it knows if the host has IPv6 or IPv4 connectivity and it knows if the peer does as well by looking at DNS AAAA
vs A
records and dynamically figures out which protocol(s) are available to service the specific connection request.
I highly advise against use of inet_pton()
, inet_addr()
or smiliar devices that are IP version specific. On the Windows platform specifically inet_pton()
is not compatible with earlier versions of MS Windows (XP, 2003 et al.) unless you roll your own. Also advise against separate versions for IPv4 and IPv6... This is unworkable as a technical solution because in the near future both protocols will need to be used concurrently and people may not know ahead of time which to use. The socket interfaces are abstract and it's easy to detect dualstack or IPv6 support by attempting to create an IPv6 socket or attempt to set the IPv6 dualstack socket option for listeners. There is no reason the resulting application won't run on a system that does not support or know about IPv6.
For outgoing connections use PF_UNSPEC
in getaddrinfo
so that the address family is chosen for you when making outgoing connections. This, IMHO, is better than the dualstack approach because it allows platforms that do not support dualstack to work.
For incoming connections you can either bind IPv4/IPv6 sockets separately if it's reasonable given the design or use dualstack if you can't do separate listeners. When using dualstack getnameinfo
returns an IPv6 address for IPv4 addresses which IMHO ends up being quite useless. A small utility routine can convert the string to a normal IPv4 address.
From my experience when done right you've removed dependencies on specific IP versions and ended up with less socket management code than you started.
I added IPv6 support to my previously-IPv4-only networking library about a year ago, and I didn't find it terribly difficult or traumatizing to do.
The only big difference is how you store IP addresses :
In IPv4 you store them as sockaddr_in
's (or if you're naughty, like me, as uint32_t's).
For IPv6 you need to store them as sockaddr_in6
's (or some equivalent 128-bit structure).
A good pre-conversion step would be to go through your code and find all of the places where IPv4 addresses are currently stored, and abstract them out into a generic IP Address class that can later be reimplemented internally to be either an IPv4 address or an IPv6 address.
Then re-test to make sure nothing is broken in IPv4 mode... once that's checked out, you should be able to make the switch to IPv6 with just a few more changes (mainly changing PF_INET
to PF_INET6
, inet_aton()
to inet_pton()
, etc...).
My library still ships as IPv4-only by default, but with the option of defining a preprocessor macro (-DMUSCLE_USE_IPV6
) to recompile it in IPv6-aware mode.
That way it can still be compiled on systems that don't support IPv6. One very useful feature I found along the way is IPv4-mapped IPv6 addresses:
By specifying one of these (essentially an IPv4 address with 0xFFFF
prepended to it), you get a socket that can talk both IPv4 and IPv6, and thus a server that can talk to both IPv4 and IPv6 clients simultaneously, without having to write separate IPv4 and IPv6 code paths for everything.
As for whether it's worth the effort, that really depends on what you intend to do with the code. I'd say it's a good educational experience if nothing else, and it does allow your software to be used in IPv6 environments, which will become more common over time.
Ulrich Drepper, the maintainer of glibc, has a good article on the topic,
http://people.redhat.com/drepper/userapi-ipv6.html
But don't forget Richard Steven's book, Unix Network Programming, Volume 1: The Sockets Networking API for good practice.
Look at the change logs of some of the open source projects that have implemented IPv6. Most of it is Unix code but Winsock is very similar to BSD sockets.
Exim, Courier, Squid, Apache, BIND DNS are some places to start looking.