Writing IP Version-Independent Programs

Writing IP Version–Independent Programs

In this section, we'll cover how to develop applications that work seamlessly over IPv4 and IPv6. This method requires using the new name resolution APIs getaddrinfo and getnameinfo and requires a bit of rearranging Winsock calls from what you are probably used to.

Before we get into the specifics, let's cover some of the basic practices that you should follow. First, applications should not allocate the socket address structures specific to each protocol (such as SOCKADDR_IN and SOCKADDR_IN6 for IPv4 and IPv6, respectively) because they can be different sizes. Instead, a new socket address structure SOCKADDR_STORAGE has been introduced that is as large as the largest possible protocol specific address structure and includes padding for 64-bit alignment issues. The following code uses a SOCKADDR_STORAGE structure to store the destination IPv6 address.

SOCKADDR_STORAGE saDestination; SOCKET s; int addrlen, rc; s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { // socket failed } addrlen = sizeof(saDestination); rc = WSAStringToAddress( "3ffe:2900:d005:f28d:250:8bff:fea0:92ed", AF_INET6, NULL, (SOCKADDR *)&saDestination, &addrlen ); if (rc == SOCKET_ERROR) { // conversion failed } rc = connect(s, (SOCKADDR *)&saDestination, sizeof(saDestination)); if (rc == SOCKET_ERROR) { // connect failed }

Second, functions that take an address as a parameter should pass the entire socket address structure and not the protocol specific types like struct in_addr or struct in6_addr. This is important for IPv6, which might require the scope ID information to successfully connect. The SOCKADDR_STORAGE structure containing the address should be passed instead.

Third, avoid hardcode addresses regardless of whether they are IPv4 or IPv6. The Winsock header files define constants for all the address that are hard coded such as the loopback address and the wildcard address used for binding.

Now that some of the basic issues are out of the way, let's move to discussing how an application should be structured to be IP independent. We will divide our discussion into two sections: the client and the server.

Client

For both TCP and UDP clients, the application typically possesses the server (or recipient's) IP address or hostname. Whether it resolves to an IPv4 address or IPv6 address doesn't matter. The client should follow these three steps:

  1. Resolve the address using the getaddrinfo function. The hints should contain AF_UNSPEC as well as the socket type and protocol depending on whether the client uses TCP or UDP to communicate.

  2. Create the socket using the ai_family, ai_socktype, and ai_protocol fields from the addrinfo structure returned in step 1.

  3. Call connect or sendto with the ai_addr member of the addrinfo structure.

The following code sample illustrates these principles.

SOCKET  s; struct addrinfo hints,        *res=NULL char *szRemoteAddress=NULL,        *szRemotePort=NULL; int rc; // Parse the command line to obtain the remote server's // hostname or address along with the port number, which are contained // in szRemoteAddress and szRemotePort. memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // first resolve assuming string is a string literal address rc = getaddrinfo( szRemoteAddress, szRemotePort,       &hints,       &res          ); if (rc == WSANO_DATA) { // Unable to resolve name - bail out    } s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s == INVALID_SOCKET) { // socket failed } rc = connect(s, res->ai_addr, res->ai_addrlen); if (rc == SOCKET_ERROR) { // connect failed } freeaddrinfo(res);

First, you will notice that there are no explicit references to AF_INET or AF_INET6. Also, there's no need to manipulate the underlying SOCKADDR_IN or SOCKADDR_IN6 addresses. The getaddrinfo call fully initializes the returned socket address structure with all the required information—address family, binary address, etc.—that is necessary for connecting or sending datagrams.

If the client application needs to explicitly bind the socket to a local port after socket creation but before connect or sendto, then another getaddrinfo call can be made. This call would specify the address family, socket type, and protocol returned from the first call along with the AI_PASSIVE flag and desired local port, which will return another socket address structure initialized to the necessary bind address (such as 0.0.0.0 for IPv4 and :: for IPv6).

Server

The server side is a bit more involved than the client side. This is because the Windows IPv6 stack is a dual stack. That is, there is a separate stack for IPv4 and IPv6, so if a server wishes to accept both IPv4 and IPv6 connections, it must create a socket for each one. The two steps for creating an IP independent server are the following:

  1. Call getaddrinfo with hints containing AI_PASSIVE, AF_UNSPEC, and the desired socket type and protocol along with the desired local port to listen or receive data on. This will return two addrinfo structures: one containing the listening address for IPv4 and the other containing the listening address for IPv6.

  2. For every addrinfo structure returned, create a socket with the ai_family, ai_socktype, and ai_protocol fields followed by calling bind with the ai_addr and ai_addrlen members.

The following code illustrates this principle.

SOCKET slisten[16]; char *szPort="5150"; struct addrinfo   hints, * res=NULL, * ptr=NULL; int   count=0,   rc; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; rc = getaddrinfo(NULL, szPort, &hints, &res); if (rc != 0) { // failed for some reason } ptr = res; while (ptr) { slisten[count] = socket(ptr->ai_family,  ptr->ai_socktype, ptr->ai_protocol); if (slisten[count] == INVALID_SOCKET) { // socket failed } rc = bind(slisten[count], ptr->ai_addr, ptr->ai_addrlen); if (rc == SOCKET_ERROR) { // bind failed } rc = listen(slisten[count], 7); if (rc == SOCKET_ERROR) { // listen failed } count++; ptr = ptr->ai_next; }

Once the sockets are created and bound, the application simply needs to wait for incoming connections on each. Chapter 5 covers the various I/O models available in Winsock and provides fully functioning client and server samples that are written using the principles covered in this section.



Network Programming for Microsoft Windows
Network Programming for Microsoft Windows (Microsoft Professional Series)
ISBN: 0735605602
EAN: 2147483647
Year: 2001
Pages: 172
Authors: Anthony Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net