Porting applications to IPv6 HowTo

home Author: Eva M. Castro
eva arroba gsyc.es

  1. Introduction
  2. Porting Source Code Considerations
  3. Socket Address Structures
  4. Socket Functions
  5. Address Conversion Functions
  6. Resolving Names
  7. Multicasting
  8. Appendix: Real examples of porting process
    8.1. Original Daytime version
    8.2. The unicast daytime ported to IPv6
    8.3. The multicast daytime
  9. Glossary and Abbreviations
  10. References

Thanks to Jeroen Massar for his feedback.

Some changes are needed to adapt the socket API for IPv6 support: a new socket address structure to carry IPv6 addresses, new address conversion functions and several new socket options that are developed in RFC-3493.

These extensions are designed to provide access to the basic IPv6 features required by TCP and UDP applications, including multicasting, while introducing a minimum of changes into the system and providing complete compatibility for existing IPv4 applications. Access to more advanced features (raw sockets, header configuration, etc.) is addressed in RFC-3542.

This document includes some examples of code porting, used to illustrate the required changes in the client and server components. Porting guidelines are valid for any programming language, however for simplicity, application porting examples are provided only in C language. All these examples have been tested in a SuSE Linux 7.3 distribution, kernel version 2.4.10.

Download all example source code, examples.tgz .

1. Introduction

Today the Internet is predominantly based on IPv4. As a result, most of the end systems and network devices are able to exchange packets all together in a global network. However, there is no a single global IPv6 network on the same scale and it will take some time to get it. Therefore, new applications should be designed to work in all environments: IPv4-only nodes, IPv6-only nodes and dual-stack nodes, see Figure 0.


Figure 0. Type of nodes.

Dual-stack nodes are one of the ways to maintain IPv4 compatibility. They provide IPv4 and IPv6 stack implementations, allowing the sending and receiving of IPv4 and IPv6 packets. Most dual-stack implementations work following two operation modes:
  1. IPv4 packets going to IPv4 applications on a dual-stack node use IPv4 stack. IPv6 packets going to IPv6 applications that are running on a dual- stack node use IPv6 stack. In summary: IPv4 applications run as if they were on an IPv4-only node while IPv6 applications run as if they were on an IPv6-only node.
  2. IPv4 packets going to IPv6 applications on a dual-stack node, reach their destination because their addresses are mapped to IPv6 ones (using IPv4-mapped IPv6 addresses) inside dual-stack node, IPv4-mapped IPv6 addresses are IPv6 addresses built from IPv4 ones using the following format: from x.y.z.w IPv4 address the ::FFFF.x.y.z.w IPv4-mapped IPv6 address is built.
In the following, the connections between IPv6 server and IPv6 client applications when they are running on a dual stack node will be analyzed.

If a server application is running bound to the IPv6 wildcard address and a known port on a dual stack node, it will be able to accept connections from IPv4 and IPv6 clients, see Figure 1. When an IPv4 client is connecting to this IPv6 server, the dual stack kernel converts the client IPv4 address to the IPv4-mapped IPv6 address since the IPv6 server can only deal with IPv6 connections. The communication between the IPv4 client and the IPv6 server will take place using IPv4 datagrams, but the server will not know that it is communicating with an IPv4 client, unless the server explicitly checks it.

Figure 1. IPv6 Server on Dual-Stack node


When running an IPv6 client application on a dual stack node, the client can connect to an IPv6 or IPv4 server, see Figure 2. Since the client node runs dual stack, client will try to open an IPv6 connection to the server. If the server is running over IPv4, the resolved server address returned by the resolver system is the IPv4-mapped IPv6 one. Then, the communication between IPv4 server and IPv6 client, which is running on the dual stack, is carried out using IPv4 but the IPv6 client will not know it, unless the client explicitly checks it.

Figure 2. IPv6 Client on Dual-Stack node

2. Porting source code considerations

When porting to IPv6, most of changes will be made in the application transport module, which is in charged of establishing communications to remote nodes. First of all it is recommended to separate the transport module from the rest of application functional modules. This separation makes the application independent on the network system used. Then, if the network protocol is changed, only the transport module should be modified. The transport module should provide the communication channel abstraction with basic channel operations and generic data structures to represent the addresses. These abstractions could be instantiated as different implementations depending on the network protocol required at any moment. The application should deal with this generic communication channel interface without knowing the network protocol used. Using this design, if a new network protocol is added, application developers only need to implement a new instance of the channel abstraction which manages the features of this new protocol.

Once the transport module has been designed, there are some implementation details related to the type of the nodes which will run the application: IPv4-only nodes, IPv6-only nodes or both, dual stack.

Within the transport module the use of the new API with extensions for IPv6 is mandatory, but it is strongly recommended to make the program protocol independent (for instance, using the BSD socket API consider getaddrinfo and getnameinfo instead of gethostbyname and gethostbyaddr). The new IPv6 functions are only valid if this protocol is supported by all installed systems. IPv6 is now reaching maturity and most popular systems provide it as default in their standard distributions. However, IPv6 support does not force to use it.

Besides the application transport module, there are other IP dependencies within the source code:

Dual stack mechanisms do not, by themselves, solve the IPv4 and IPv6 interworking problems. Protocol translation is required many times, for instance when an IPv6 application want to communicate to an IPv4 application exchanging IPv6 packets. Translation refers to the direct translation of protocols, including headers and sometimes protocol payload. Protocol translation often results in features loss. For instance, translation of IPv6 header into an IPv4 header will lead to the loss of the IPv6 flow label. In other cases, tunneling can be used, which is used to bridge compatible networks across incompatible ones.

3. Socket Address Structures

Functions provided by socket API use the socket address structures to determine the communication service access point. Since different protocols can handle socket functions, a generic socket address structure is used as argument of these functions for any of the supported communication protocol families, sockaddr.

struct sockaddr { 
     sa_family_t sa_family;       /* Address family */
     char sa_data[14];            /* protocol-specific address */
};


Although socket functions handle the generic socket address structures, developers must fill the adequate socket address structure according to the communication protocol they are using to establish the socket. In particular, the IPv4 sockets use the following structure, sockaddr_in:

typedef uint32_t in_addr_t;
struct in_addr  {
    in_addr_t s_addr;                    /* IPv4 address */
};

struct sockaddr_in {
     sa_family_t sin_family;             /* AF_INET */
     in_port_t sin_port;                 /* Port number.  */
     struct in_addr sin_addr;            /* Internet address.  */

     /* Pad to size of `struct sockaddr'.  */
     unsigned char sin_zero[sizeof (struct sockaddr) -
                            sizeof (sa_family_t) -
                            sizeof (in_port_t) -
                            sizeof (struct in_addr)];
};


And the IPv6 sockets use the following structure, sockaddr_in6, with a new address family AF_INET6:

struct in6_addr {
    union {
        uint8_t u6_addr8[16];
        uint16_t u6_addr16[8];
        uint32_t u6_addr32[4];
    } in6_u;

    #define s6_addr                 in6_u.u6_addr8
    #define s6_addr16               in6_u.u6_addr16
    #define s6_addr32               in6_u.u6_addr32
};

struct sockaddr_in6 {
    sa_family_t sin6_family;    /* AF_INET6 */
    in_port_t sin6_port;        /* Transport layer port # */
    uint32_t sin6_flowinfo;     /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id;     /* IPv6 scope-id */
};


The sockaddr_in or sockaddr_in6 structures are utilized when using respectively IPv4 or IPv6. Existing applications are written assuming IPv4, using the sockaddr_in structure. They can be easily ported changing this structure by sockaddr_in6. However, when writing portable code, it is preferable to eliminate protocol version dependencies from source code. There is a new data structure, sockaddr_storage, large enough to store all supported protocol-specific address structures and adequately aligned to be cast to the a specific address structure.

/* Structure large enough to hold any socket address (with the 
historical exception of AF_UNIX). 128 bytes reserved.  */

#if ULONG_MAX > 0xffffffff
# define __ss_aligntype __uint64_t
#else
# define __ss_aligntype __uint32_t
#endif
#define _SS_SIZE        128
#define _SS_PADSIZE     (_SS_SIZE - (2 * sizeof (__ss_aligntype)))

struct sockaddr_storage
{
    sa_family_t ss_family;      /* Address family */
    __ss_aligntype __ss_align;  /* Force desired alignment.  */
    char __ss_padding[_SS_PADSIZE];
};


Hence, portable applications should use the sockaddr_storage structure to store their addresses, IPv4 or IPv6 ones. This new structure hides the specific socket address structure that the application is using.

4. Socket functions

The socket API has not been changed since it handles the generic address structure, independent from the protocol it is using. However, applications should change the arguments used to call the socket functions. First, applications should allocate enough memory to store the appropriate socket address structure. And, before calling the socket functions, the specific socket address structure should be cast to the generic one, which is accepted by the socket functions as an argument.

int     socket  (int domain, int type, int protocol);

int     listen  (int s, int backlog);

ssize_t write   (int fd, const void *buf, size_t count);

int     send    (int s, const void *msg, size_t len, int flags);

int     sendmsg (int s, const struct msghdr *msg, int flags);

ssize_t read    (int fd, void *buf, size_t count);

int     recv    (int s, void *buf, size_t len, int flags);

int     recvmsg (int s, struct msghdr *msg, int flags);

int     close   (int fd);

int     shutdown(int s, int how);


THE SOCKET ADDRESS STRUCTURE IS PASSED FROM APPLICATION TO KERNEL

int bind   (int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

int connect(int sockfd, const struct sockaddr *serv_addr, 
            socklen_t addrlen);

int sendto (int s, const void *msg, size_t len, int flags, 
            const struct sockaddr *to, socklen_t tolen);


THE SOCKET ADDRESS STRUCTURE IS PASSED FROM KERNEL TO APPLICATION

int  accept     (int s, struct sockaddr *addr, socklen_t *addrlen);

int  recvfrom   (int s,  void  *buf, size_t len, int flags, 
                 struct sockaddr *from, socklen_t *fromlen);

int  getpeername(int s, struct sockaddr *name, socklen_t *namelen);

int  getsockname(int s, struct sockaddr *name, socklen_t *namelen);


Required modifications when porting to IPv6

When porting to IPv6, some modifications related to the socket API are required in the network applications. Three modification types have been identified: These are the three kind of operations to be changed in the source code. There are some examples below of these types of operation.

5. Address conversion functions

The address conversion functions convert between binary and text address representation. Binary representation is the network byte ordered binary value which is stored in the socket address structure and the text representation, named presentation, is an ASCII string.
The IPv4 address conversion functions are the following ones:


/* 
   From text to IPv4 binary representation
*/
int       inet_aton (const char *cp, struct in_addr *inp);
in_addr_t inet_addr( const char *cp);

/*
   From IPv4 binary to text representation
*/
char 	   *inet_ntoa(struct in_addr in);


The new address conversion functions which work with both IPv4 and IPv6 addresses are the following ones:

/* 
   From presentation to IPv4/IPv6 binary representation
*/
int inet_pton(int family, const char *src, void *dst);

/*
   From IPv4/IPv6 binary to presentation
*/
const char *inet_ntop(int family, const void *src,
                      char *dst, size_t cnt);


IPv4 source code example:

struct sockaddr_in addr;
char *straddr;

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;        // family
addr.sin_port = htons(MYPORT);    // port, networt byte order

/*
   from text to binary representation
*/
inet_aton("138.4.2.10", &(addr.sin_addr));

/*
   from binary to text representation
*/
straddr = inet_ntoa(addr.sin_addr);


IPv6 source code example:

struct sockaddr_in6 addr;
char straddr[INET6_ADDRSTRLEN];

memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;        // family
addr.sin6_port = htons(MYPORT);    // port, networt byte order

/*
   from presentation to binary representation
*/
inet_pton(AF_INET6, "2001:720:1500:1::a100", 
          &(addr.sin6_addr));

/*
   from binary representation to presentation
*/
inet_ntop(AF_INET6, &addr.sin6_addr, straddr,
          sizeof(straddr));


However, the use of these functions should be avoided, using the new resolution ones instead: getnameinfo and getaddrinfo. The inet_* functions work with IPv4 and IPv6, however they are protocol dependent, it means you need to specify which kind of resolution should be made, v4 or v6. Besides these inet_* functions do not work with scoped addresses, so getaddrinfo and getnameinfo are highly recommended.

6. Resolving names

Applications should use names instead of addresses for hosts. Names are easier to remember and remain the same, but numeric addresses could change more frequently.

From applications point of view the name resolution is a system-independent process. Applications call functions in a system library known as the resolver, typically gethostbyname and gethostbyaddr, which are linked into the application when the application is built, see Figure 3. The resolver code is the burden of making the resolution dependent of the system configuration.

Figure 3. Address Conversion Functions.

There are two new functions to make name and address conversions protocol independent, getaddrinfo and getnameinfo. Besides, the use of these new ones instead of gethostbyname and gethostbyaddr is recommended because the latter are not normally reentrant and could provoke problems in threaded applications.

The getaddrinfo function returns a linked list of addrinfo structures which contains information requested for a specific set of hostname, service and additional information stored in an addrinfo structure.



struct addrinfo {
    int     ai_flags;           /* AI_PASSIVE, AI_CANONNAME */
    int     ai_family;          /* AF_UNSPEC, AF_INET, AF_INET6 */
    int     ai_socktype;        /* SOCK_STREAM, SOCK_DGRAM ... */
    int     ai_protocol;        /* IPPROTO_IP, IPPROTO_IPV6 */
    size_t  ai_addrlen;         /* length of ai_addr */
    struct sockaddr ai_addr;    /* socket address structure */
    char   ai_canonname;        /* cannonical name */
    struct addrinfo ai_next;    /* next addrinfo structure */
};


/* function to get socket address structures */
 
int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

    /* Note: Check the error using !=0 instead of <0    */
    /*       On FreeBSD, a failing resolve because the  */
    /*       local nameserver is not reachable or times */
    /*       out, it will result >0 which must be also  */
    /*       considered as an error.                    */



When writing a typical client application, node and service are normally specified. When writing a server application, they both can be specified too, but in many cases only the service is specified allowing clients to connect to any server interfaces.

Applications should examine the linked list returned by getaddrinfo to use the adequate structure, see Figure 4. In some cases, not all the addresses returned by this function can be used to create a socket.

Figure 4. Linked list of addresses returned by getaddrinfo function.

The getaddrinfo function allocates a set of resources for the returned linked list. The freeaddrinfo function frees these resources.



/* function to free the resources allocated by getaddrinfo */

void freeaddrinfo(struct addrinfo *res);


Next, an example of getaddrinfo and freeaddrinfo usage.

error = getaddrinfo(hostname, service, &hints, &res);

/*
    Try open socket with each address getaddrinfo returned,
    until getting a valid socket.
*/

if (error !=0 ) {
    /* Note: Check the error using !=0 instead of <0    */
    /*       On FreeBSD, a failing resolve because the  */
    /*       local nameserver is not reachable or times */
    /*       out, it will result >0 which must be also  */
    /*       considered as an error.                    */

    /* handle getaddrinfo error and return  */
}

resave = res;

for (; res; res = res->ai_next ) {
    sockfd = socket(res->ai_family,
                    res->ai_socktype,
                    res->ai_protocol);

    if ((sockfd < 0)) {
         switch errno {
	      case EAFNOSUPPORT:
	      case EPROTONOSUPPORT:
                  /* last address family is not supported,
		     continue with the next address returned 
		     by getaddrinfo */
                  continue;

	      default:
	          /* handle other socket errors */
		  ;
         }
    } else {
       /* socket succesfully created */
       
       /* check now if bind()/connect(). If they return errors, 
          continue the while loop with the next address returned
	  by getaddrinfo. If they don't return errors, break */

       /* ... */
    }
}

freeaddrinfo(ressave);

The getnameinfo function provides from a socket address structure the address and service as character strings.
char clienthost   [NI_MAXHOST];
char clientservice[NI_MAXSERV];

/* ... */

/* listenfd is a server socket descriptor waiting connections 
   from clients 
*/

connfd = accept(listenfd,
                (struct sockaddr *)&clientaddr,
                &addrlen);

if (connfd < 0) {
    /* handle connect error and return */
}

error = getnameinfo((struct sockaddr *)&clientaddr, addrlen,
                    clienthost, sizeof(clienthost),
                    clientservice, sizeof(clientservice),
                    NI_NUMERICHOST);

if (error != 0) {
    /* handle getnameinfo error and return */
}

printf("Received request from host=[%s] port=[%s]\n",
       clienthost, clientservice);


Typically applications do not require to know the version of the IP they are using. Hence, applications only should try to establish the communication using each address returned by resolver until it works. However, applications could have a different behavior when using IPv4, IPv6, IPv4-compatible-IPv6 or IPv4-mapped, etc. addresses.

There are defined some macros to help applications to test the type of address they are using, see Table 2.

Table 2. Macros for testing type of addresses

int IN6_IS_ADDR_UNSPECIFIED (const struct in6_addr *);
int IN6_IS_ADDR_LOOPBACK (const struct in6_addr *);
int IN6_IS_ADDR_MULTICAST (const struct in6_addr *);
int IN6_IS_ADDR_LINKLOCAL (const struct in6_addr *);
int IN6_IS_ADDR_SITELOCAL (const struct in6_addr *);
int IN6_IS_ADDR_V4MAPPED (const struct in6_addr *);
int IN6_IS_ADDR_V4COMPAT (const struct in6_addr *);
int IN6_IS_ADDR_MC_NODELOCAL (const struct in6_addr *);
int IN6_IS_ADDR_MC_LINKLOCAL (const struct in6_addr *);
int IN6_IS_ADDR_MC_SITELOCAL (const struct in6_addr *);
int IN6_IS_ADDR_MC_ORGLOCAL (const struct in6_addr *);
int IN6_IS_ADDR_MC_GLOBAL (const struct in6_addr *);

7. Multicasting

When using UDP multicast facilities some changes must be carried out to support IPv6. First application must change the multicast IPv4 addresses to the IPv6 ones, and second, the socket configuration options.

IPv6 multicast addresses begin with the following two octets: FF0X.

The multicast socket options are used to configure some of parameters for sending multicast packets, see Table 3.

Table 3. Multicast socket options

IPV6 OPTION
IPV6_MULTICAST_IF Interface to use for outgoing multicast packets.
IPV6_MULTICAST_HOPS Hop limit for multicast packets.
IPV6_MULTICAST_LOOP Multicast packets are looped back to the local application.
IPV6_ADD_MEMBERSHIP Join a multicast group.
IPV6_DROP_MEMBERSHIP Leave a multicast group.

Applications using multicast communication open a socket and need to configure it to receive muticast packets. In the following section some multicast examples over IPv4 and IPv6 networks will be seen.

8. Appendix: Real examples of porting process

This appendix is devoted to show some simple applications which can be used to illustrate porting procedures. This appendix is used to review characteristics and methods described in previous sections.
Migration guidelines are valid for any programming language, however for simplicity, application porting examples are provided only in C language. All these examples have been tested in a SuSE Linux 7.3 distribution, kernel version 2.4.10.

8.1. Original daytime version

Daytime is a simple utility, which returns the time and date of a node in a human-readable format. It is defined in RFCs-867 and it works in the port number 13.

It is a good example to show porting guidelines with a very simple application. Like most of distributed applications, daytime is composed of a client and a server programs. It can operate over TCP and UDP.

8.1.1. IPv4 Daytime server (TCP/UDP)

See source code here.

8.1.2. IPv4 Daytime client (TCP/UDP)

See source code here.

8.2. The unicast daytime ported to IPv6
In the following sections it is shown how daytime service is ported from IPv4 to IPv6. Following previous described porting guidelines and after previous program analysis, the code should be reviewed to support IPv4 and IPv6 without compilation.

IPv6 sockets library is similar to IPv4 version, therefore server or client code does not change for the operation phase. The connection phase maintain the same connection model, however the code to set up connections should be adapted.

8.2.1. IPv6 Daytime server (TCP/UDP)

See source code here.

8.2.2. IPv6 Daytime client (TCP/UDP).

See source code here.

8.3. The multicast daytime
In this section daytime utility is used to analyze porting problems related to multicast applications. Also, daytime service is used to show this porting process. Daytime is a very simple service which is easy to be adapted to multicast.

See source code here.

9. Glossary and Abbreviations

ADU Application Data Unit.
API Application Program Interface.
ASCII American Standard Code for Information Interchange.
BSD Berkeley System Distribution.
FQDN Fully Qualified Domain Name.
IP Internet Protocol.
IPv4 Internet Protocol version 4.
IPv6 Internet Protocol version 6.
MTU Maximum Transfer Unit.
PMTU Path Maximum Transfer Unit.
PMTU-D Path Maximum Transfer Unit Discovery.
TCP Transport Control Protocol.
TU Transfer Unit.
UDP User Datagram Protocol.

10. References


home Author: Eva M. Castro
eva arroba gsyc.es