http://www.developer.com/

Back to article

Running IPv6 Code in Multiple Windows Environments


July 19, 2004

Introduction

IPv6, or "Internet protocol Version 6," has been around for a long time now. It took a long time to reach the level of acceptance it has reached today, but its growth in terms of acceptance and popularity has been phenomenal. Many vendors are already supporting IPv6 in their products. Microsoft has been a little late in entering this field. There is no IPv6 support in older versions of Windows, such as Windows NT 4 or Windows 98. There is some support for Windows 2000, but that is just for experiments and is not a production quality implementation. But, Microsoft has started providing full support for IPv6 in its operating systems from Windows XP SP1 and Windows Server 2003 onwards.

This different level of support has created problems for vendors who support their IPv6 enabled products on multiple Windows environments. To make your code IPv6 enabled, you need to use new function calls and data structures introduced in Window Sockets v2 API. But, implementation of these new programming elements is available only in Windows XP SP1 or Windows Server 2003. Then, how can you run the same code on Windows 2000 platform?

This article addresses the above problem. It tells you how to use new IPv6 function calls in your code and run the same code seamlessly on Windows 2000, Windows XP, and Windows Server 2003. Microsoft handles this backward compatibility of newly introduced function calls in a very cryptic way that a normal programmer doesn't understand easily. In this article, I will show you what happens behind the scenes that makes the new function calls work even on platforms where these are not available.

Different Levels of IPv6 Support

As mentioned earlier, Microsoft does not provide support for IPv6 on all Windows platforms. The support in different versions of OS is given here:

  • Windows 95/98/Me/NT: no support
  • Windows 2000: non-production-quality support with IPv6 Technology Preview
  • Windows XP (SP1), Windows Server 2003, and later: Full support

Even in the supported platforms, IPv6 is not installed by default. If required, administrators need to install the IPv6 protocol manually on the system.

On Windows 2000 (with the IPv6 technology preview installed), the IPv6 routines are available in wship6.dll whereas on Windows XP (SP1) and Windows Server 2003 all the IPv6 routines are available in ws2_32.dll along with legacy winsock routines.

Newly Added Programming Elements

There are many implementations of sockets API available, but the two extensively used implementations across the industry are Windows Sockets and BSD sockets. The two implementations have very similar API and are compatible with each other to a great extent. But, we will not delve into that area and will concentrate only on Microsoft's implementation or Windows Sockets.

Also, I'll assume that you are using Visual C++ 7.0 or later to build your code for all the samples here. The header and library files of Visual C++ 6 environment do not contain the new function calls. So, you can not compile the code containing new function calls in a default VC6 environment. Although it is possible to compile the code in VC6 by including the newer header files, I'll not discuss that here.

Totally, there are nine new function calls and five new data structures that have been added to Windows Sockets for IPv6. You can see the list of these in the MSDN library. The purpose of this article is not to show you how to code for IPv6, but to show how the code written for IPv6 can also run on non-supported systems. So, I'll take an example of an often-used function call (among the newly added calls) and explain the solution with that.

This function call is getaddrinfo(). The official documentation describes this call as this: "The getaddrinfo function provides protocol-independent translation from host name to address." This call should be used in place of the gethostbyname() function call when writing IPv6 code. Apart from retrieving host information corresponding to a host name, the new getaddrinfo() call also performs the processing work of many functions. The lines of code necessary to perform the usual work of creating, opening, and then binding a socket can be significantly reduced by using this new call.

Building a Simple Program

To explain the process, I'll take following very simple program that only has a Winsock function, namely getaddrinfo():

Listing 1: Simple Program

#include <winsock2.h>
#include <ws2tcpip.h>
void main()
{
   char* serverName  = "server.domain.com";
   char* serviceName = "service_name";
   ADDRINFO *AI;
   int retVal = getaddrinfo(serverName, serviceName, NULL, &AI);
}

The above program translates the given host name and service name to a protocol-independent network address and puts that information into an ADDRINFO structure. This structure then can be used to create a socket and perform operations such as connect. But, I will not show that here to keep the program simple.

Now, let us compile and link this program. To get it compiled, we included the header file ws2tcpip.h, which has the declaration of the getaddrinfo() call. To get it linked, we need to add the additional library ws2_32.lib to the linker list to resolve the getaddrinfo symbol.

Now, let us perform a dumpbin/imports on resulting executable. It shows the following output given in Listing 2 below. For simplicity sake I'm showing imports for WS2_32.dll only, but actual dumpbin ouput also has quite a few imports from KERNEL32.dll.

Listing 2: Output of dumpbin/imports (shortened)

Microsoft (R) COFF/PE Dumper Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file IPv6Article.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    WS2_32.dll
                42D348 Import Address Table
                42D194 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                      Ordinal    15
                      Ordinal    56
                      Ordinal    51
                      Ordinal    52
                      Ordinal   111
                      Ordinal    11
                      Ordinal     9
                      Ordinal    55
                      Ordinal     8
                      Ordinal    12

You will notice that there are total 10 imports from WS2_32.dll. Ten imports for just one function call—getaddrinfo()! How's that possible? To get the answer, let's first see what these functions are. As these are imported by ordinal numbers, we need to see the dumpbin exports of WS2_32.dll. They are shown in Listing 3. Again, for simplicity, I'm not showing calls with the WSA prefix, except just one call, WSAGetLastError, that is being used in our program.

Listing 3: Output of dumpbin/exports of WS2_32.dll (shortened)

Microsoft (R) COFF/PE Dumper Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file c:\windows\system32\ws2_32.dll

File Type: DLL

  Section contains the following exports for WS2_32.dll

    00000000 characteristics
    3B7DE11B time date stamp Sat Aug 18 08:59:31 2001
        0.00 version
           1 ordinal base
         500 number of functions
         114 number of names

    ordinal hint RVA      name
        111   1A 00001740 WSAGetLastError
          1   51 0000868D accept
          2   52 00003ECE bind
          3   53 00001A6D closesocket
          4   54 00003E5D connect
         94   55 00003A2C freeaddrinfo
         95   56 000033DF getaddrinfo
         51   57 0000D755 gethostbyaddr
         52   58 00002BBF gethostbyname
         57   59 000032CA gethostname
         96   5A 0000C076 getnameinfo
          5   5B 0000F628 getpeername
         53   5C 0000D24E getprotobyname
         54   5D 0000D1A2 getprotobynumber
         55   5E 0000D969 getservbyname
         56   5F 0000D850 getservbyport
          6   60 0000157E getsockname
          7   61 00004122 getsockopt
          8   62 000012A7 htonl
          9   63 00001746 htons
         11   64 000012F8 inet_addr
         12   65 0000401C inet_ntoa
         10   66 0000155A ioctlsocket
         13   67 00005DE2 listen
         14   68 000012A7 ntohl
         15   69 00001746 ntohs
         16   6A 00005690 recv
         17   6B 00001444 recvfrom
         18   6C 00001890 select
         19   6D 00001AF4 send
         20   6E 00001ED3 sendto
         21   6F 00003F8D setsockopt
         22   70 00008629 shutdown
         23   71 00003C22 socket

Now, matching the ordinal numbers from Listing 2 with those in Listing 3, we can find that our program uses the following functions:

Function Ordinal Number
ntohs 15
getservbyport 56
gethostbyaddr 51
bind 52
WSAGetLastError 111
inet_addr 11
htons 9
getservbyname 55
htonl 8
inet_ntoa 12

So, although we used a new function call that is introduced for IPv6, the linker is mapping that call to the legacy Winsock function calls that also were available for IPv4. How does this happen? We'll see that in later section. For now, we will find out the way to prevent it. In the next section, let's see how we target an IPv6 environment explicitly so that new IPv6 calls don't get mapped to legacy IPv4 calls.

Building a Program for the IPv6 Environment

I'll take the same old program shown in Listing 1 and demonstrate how you can target that program for the IPv6 environment explicitly. All that you need to do is to add a preprocessor directive that instructs the compiler to build the program for Windows XP or later system. The directive is _WIN32_WINNT and its value for different operating systems is given in the following table:

Operating System Value of _WIN32_WINNT
Windows 2000 0x0500
Windows XP 0x0501
Windows Server 2003 0x0502

The values for _WIN32_WINNT are the version numbers that are used in Platform SDK header files. Defining this directive explicitly in the program enables the SDK APIs added for that version of OS. For example, defining this directive as 0x0501 will enable Windows XP-specific functions. Because IPv6 calls are supported from Windows XP (SP1) onwards, you can define _WIN32_WINNT>=0x0501 to target a program for IPv6 environment. You can do it in two ways:

  1. Define the macro in the program itself, before including any header file. as shown in Listing 4:
  2. Listing 4: Program to target IPv6 environment

    #define _WIN32_WINNT 0x0501
    #include <winsock2.h>
    #include <ws2tcpip.h>
    void main()
    {
        char* serverName  = "server.domain.com";
        char* serviceName = "service_name";
        ADDRINFO *AI;
        int retVal = getaddrinfo(serverName, serviceName, NULL, &AI);
    }
    
  3. Add the directive _WIN32_WINNT=0x0500 in compiler options. In Visual C++ 7.x, you can do this by selecting Project properties and then adding this macro to Preprocessor field under the C/C++ category.

Now, let us also perform dumpbin/imports on the resulting executable of this program. It shows the output given in Listing 5. Again, for simplicity's sake, I'm showing imports for WS2_32.dll only, but actual dumpbin ouput also has quite a few imports from KERNEL32.dll.

Listing 5: Output of dumpbin/imports for IPv6 program (shortened)

Microsoft (R) COFF/PE Dumper Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file IPv6Article.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    WS2_32.dll
                42B314 Import Address Table
                42B190 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                   58 getaddrinfo

You'll notice that this time the executable has only one import from WS2_32.dll; that is for the function we actually called in the program.

Comparing the Two Programs

The output of dumpbin/imports in Listing 5 reveals one important concept. Microsoft has written header files for the Winsock2 API in such a way that the compiler can choose the appropriate functions to call based on a targeted environment. For the program in Listing 1, we didn't specify any particular environment to target. So, the compiler generated code that can run on all the environments, even on the environments where IPv6 is not supported. But for the program in Listing 4, we explicitly specified a preprocessor directive to target Windows XP. In this case, the compiler generated code with new IPv6 calls, which can run only on those environments that have IPv6 support.

That explains how Microsoft maintains backward compatibility for the code written with newly added IPv6 calls.

Preparing the Environment for IPv6

Now, if you run the executable from the program in Listing 4 on a default installation of Windows XP (SP1), it will run successfully, but the getaddrinfo() call will fail. The reason is that IPv6 protocol is not installed by default. You can install it by running the simple command "ipv6 install" on a command prompt. In Windows Server 2003 also, IPv6 doesn't come in a default installation. You can install it by using the Properties dialog box of Local Area Connection under Network Connections. Once you install the protocol, the program will run successfully and give the expected results.

Behind the Scenes

Now, you know exactly what happens that enables the backward compatibility. But, how does it happen? In this section, we'll explore the actual mechanism that is making this happen.

The getaddrinfo() function is declared in ws2tcpip.h. At the bottom of this header file, you will find the following code:

//
// Unless the build environment is explicitly targeting only
// platforms that include built-in getaddrinfo() support, include
// the backwards-compatibility version of the relevant APIs.
//
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT <= 0x0500)
#include <wspiapi.h>
#endif

In the file wspiapi.h there is following preprocessor instruction:

#define getaddrinfo             WspiapiGetAddrInfo

So, the call to getaddrinfo() goes to WspiapiGetAddrInfo(). The implementation of this function is provided in this header file only. Do not be surprised to see the implementation of the function in a header file. Microsoft had to do it to provide the backward compatibility. At run time, the calls to getaddrinfo() actually call the implementation of WspiapiGetAddrInfo() provided in this header file.

Internally, WspiapiGetAddrInfo(), with the help of another function, WspiapiLoad(), calls the appropriate implementation of getaddrinfo() as per the current environment. This choice is made in the following steps:

  1. It tries to find the getaddrinfo in ws2_32.dll (for Windows XP and Windows Server 2003) using LoadLibrary and GetProcAddress calls. If successful, it does the mapping.
  2. If the preceding step fails, it tries to find the getaddrinfo in wship6.dll (for the IPv6 technology preview on Windows 2000). If successful, it does the mapping.
  3. If the preceding step also fails, it maps the call to the WspiapiLegacyGetAddrInfo routine. This function is implemented in the wspiapi.h header file. This routine internally calls legacy Winsock routines already available in ws2_32.dll (for systems with no IPv6 support) to provide the same functionality.

Exactly the same procedure that is followed for calling getaddrinfo() is also followed for the other two IPv6 calls, getnameinfo() and freeaddrinfo(). The ten function imports that you saw in dumpbin/imports output in Listing 2 was because of the implementations of these functions provided in the wspiapi.h file.

Note that all this procedure is followed only when you do not define the _WIN32_WINNT directive or its value is less than or equal to 0x0500. So, the executable of the program in Listing 1 will go through all this procedure. But, if the _WIN32_WINNT macro is defined and its value is more than 0x0500, the compiler maps the function call statically and the header file wspiapi.h is not included.

Conclusion

The support for IPv6 is of varied levels in different versions of Windows operating systems. But, you still can write code with new IPv6 functions and run that code on earlier versions of Windows operating systems that do not have IPv6 support. Internally, this is done by dynamically finding the implementation of new functions at run time. If the implementation of new functions is not found, the calls to them are mapped to older IPv4 functions. If you do not want this dynamic searching, you can define a preprocessor directive to target the IPv6 environment explicitly. The good thing about this is that everything happens behind the scenes and programmers do not need to worry about backward compatibility.

References

  • IPv6 Information page: http://www.ipv6.org/
  • IPv6 Guide for Windows Sockets Applications: http://www.msdn.microsoft.com/library/en-us/winsock/winsock/ipv6_guide_for_windows_sockets_applications_2.asp
  • Microsoft IPv6 Technology Preview for Windows 2000: http://msdn.microsoft.com/downloads/sdks/platform/tpipv6.asp
  • About the Author

    Sanjay Narang is a software analyst at Hewlett-Packard, India. He is certified as a MCSD.NET and SCJP. He is a Post Graduate from the Indian Institute of Information Technology, Bangalore.

    Sanjay has been involved in designing and developing Micosoft-based solutions for more than five years in various domains: CRM, eGovernance, and Instant Messaging. He can be contacted at sanjai_narang@yahoo.com.

    Sitemap | Contact Us

    Thanks for your registration, follow us on our social networks to keep up-to-date