GNOME over IPv6



Hi,
  Me and a few of my collegues are working on porting GNOME
applications/libraries to work with the IPv6 protocol. Currently we are
working on porting esound and gnome-vfs. The patch for esound is ready and
attached to this mail. I have attached a small write up on how we are
proceeding with this task. The write up goes into the following details
a) A brief introduction to the concepts of IPv6.
b) Porting aspects to be considered.
c) Code changes to be done.
d) Code examples to illustrate IP6 code mixed with IPv4 code.

  Please do have a look at the write up and the attached patch.
  We plan to upload the patches in bugzilla as and when we complete the
porting for a component.

regards,
shivram
Porting of networked applications/libraries in GNOME to be IPv6 aware
---------------------------------------------------------------------

Introduction
------------
  IPv6 is short term for "Internet Protocol version 6". IPv6 is the new version of the IP protocol, designed to replace the current version of IP (IPv4)

  The protocol is designed to overcome the depletion of IPv4 addresses and also to cater to the needs of the new emerging networks.
  Below we would explain a few IPv6 concepts required in context of this document. The references section would provide links to documents for in-depth explanation of the concepts

Transition from IPv4 to IPv6
----------------------------
 The transition from IPv4 to IPv6 would be gradual and flexible. The objective is to allow IPv4 hosts to interoperate with IPv6 hosts. Also the transition should be smooth for end-users, system and network administrators.
 The most common technique for transition is know as a dual stack. IPv4 nodes are upgraded to support both IPv4 and IPv6.

IPv6 address structure
----------------------
  The IPv6 addresses are four times as long as IPv4 addresses. 
  The textual format for IPv4 addresses is of the form ddd.ddd.ddd.ddd
  for e.g.: 10.114.9.98
  The textual format for IPv6 addresses is of the form xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
  for e.g.: fe80:0000:0000:0000:250:4ff:fe0b:6f3

Terminology
-----------
 IPv4 compatible address
   The IPv6 transition mechanisms include a technique for hosts and routers to dynamically tunnel IPv6 packets over IPv4 routing infrastructure. Using this technique, IPv6 nodes are assigned a special IPv6 unicast address that carry an IPv4 address in the low order 32-bits.

 IPv4 mapped address
  IPv4 mapped address is an IPv6 address that is used to represent an IPv4 address. This address is used to represent the addresses of IPv4 only nodes.

IPv6 application types
----------------------
 a. IPv6 unaware
   Applications that cannot communicate with nodes with only IPv6 addresses fall under this category.
 b. IPv6 aware
   Applications that can communicate with IPv6 only nodes fall under this category. We are aiming for this now.
 c. IPv6 enabled.
   Applications that in addition to being IPv6 aware, utilize some specific IPv6 feature. 
 d. IPv6 required
   Applications that cannot operate over IPv4 nodes.


Porting Changes
--------------
 Now we shall try to enumerate the changes required for porting GNOME applications and libraries for the IPv6 protocol 

Build changes
-------------
  The changes should ensure proper configure and build on all systems. Below we illustrate the changes needed for the configure.in file. 

Changes to configure.in
-----------------------
 The configure script would contain an additional configurable parameter "--enable-ipv6". By default IPv6 is enabled if the build system is IPv6 enabled. This can be turned off explicitly by passing "--enable-ipv6=no" to the configure script.
 
 C_MSG_CHECKING([whether to enable ipv6])
AC_ARG_ENABLE(ipv6, [ --enable-ipv6 enable IPv6 extensions],
[ case "$enableval" in
        no)
                AC_MSG_RESULT(no)
                ipv6=no
                ;;
        *)
                AC_MSG_RESULT(yes)
                ipv6=yes
                ;;
        esac ],
        AC_TRY_RUN([ /* AF_INET6 available check */
#include <sys/types.h>
#include <sys/socket.h>
main()
{
 if (socket(AF_INET6, SOCK_STREAM, 0) < 0)
   exit(1);
 else
   exit(0);
}
],
  AC_MSG_RESULT(yes)
  AC_DEFINE(ENABLE_IPV6)
  ipv6=yes,
  AC_MSG_RESULT(no)
  ipv6=no,
  AC_MSG_RESULT(no)
  ipv6=no
))

if ipv6=yes; then
  .. specific functions like getaddrinfo () tests
fi

 In the above changes to configure.in, --enable-ipv6 is turned on by default, and if --enable-ipv6 is not "no" then, we try to run a program which tries to create a socket with address family AF_INET6. If the program exits with a return value of 0, then the build system IPv6 is supported. We then define the variable ENABLE_IPV6.

Developing applications/libraries that work with/without IPv6 support
---------------------------------------------------------------------
 The application/libraries that are made IPv6 aware should still work on systems that implement only IPv4.
 The following runtime check would be useful  

int ipv6_supported()
{
 int s;

#if defined (ENABLE_IPV6)
  s = socket (AF_INET6, SOCK_STREAM, 0);
  if (s != -1) {
    (void) close (s);
    return 1;
  }
#endif
  return 0;
}

Preparation for code changes
---------------------------

Removing any assumptions of address length
-------------------------------------------

   The address length in the IPv4 protocol is 32 bits. In the IPv6 protocol this is now 128 bits, to hold the larger addresses. A new in6_addr holds a single IPv6 address and is defined as a result of including <netinet/in.h>

        struct in6_addr {
            uint8_t  s6_addr[16];      /* IPv6 address */
        };

Equivalent of the INADDR_LOOPBACK and the INADDR_ANY constants
--------------------------------------------------------------
  Applications may need to send UDP packets to, or originate TCP connections to the localhost. In IPv4 they can do this by using the constant IPv4 address INADDR_LOOPBACK in their connect(), sendto() or sendmsg() call.
   The counterparts for INADDR_LOOPBACK and INADDR_ANY come in two flavors since it is not possible to define a constant that is 128 bits in size.
   As with the unspecified address the IPv6 loopback address is provided in two forms - a global variable and a symbolic constant. 
   The global variable is an in6_addr structure named "in6addr_loopback". The extern declaration for this variable is defined in <netinet/in.h>
   extern constant struct in6_addr in6addr_loopback;

   Applications use the in6addr_loopback as they would use the INADDR_LOOPBACK in IPv4. For example to open a connection to the local telnet server :


      struct sockaddr_in6 sin6;
       . . .
      sin6.sin6_family = AF_INET6;
      sin6.sin6_flowinfo = 0;
      sin6.sin6_port = htons(23);
      sin6.sin6_addr = in6addr_loopback;  /* structure assignment */
       . . .
      if (connect(s, (struct sockaddr *) &sin6, sizeof(sin6)) == -1)

   The symbolic constant is named IN6ADDR_LOOPBACK_INIT and is defined in <netinet/in.h>. It can be used at declaration time only. For example

      struct in6_addr loopbackaddr = IN6ADDR_LOOPBACK_INIT; 

   Similarly while the bind() function allows applications to select the source IP address, an application would often want the system to select the source address for them. With IPv4 an application would use the constant INADDR_ANY. As with the in6addr_loopback, in IPv6 the unspecfied address is available in the following forms
   a. a global variable in6addr_any
   b. a macro IN6ADDR_ANY_INIT.

   NOTE: IPv4 INADDR_* constants are specified in the host byte order where as the IPv6 equivalents are defined in the network byte order.

Removing any assumptions of AF_INET from the code
-------------------------------------------------

   A new address family AF_INET6 is defined in <sys/socket.h> for the IPv6 protocol. A new protocol family PF_INET6 is defined in <sys/socket.h>. The use of AF_INET/PF_INET would only be used if the application would  communicate using IPv4. In the case of IPv6 we would be using AF_INET6/PF_INET6 

Changes to the socket structure
-------------------------------
   In the socket interface a different protocol-specific data structure is defined, to carry the addresses for each protocol suite. The core socket function, which deal with setting up TCP connections and disconnecting them and sending and receiving UDP packets are designed to be protocol independent. Where protocol addresses are passed to these functions they are carried via opaque pointers (struct sockaddr). Each protocol specific structure has a "family" field, that overlays the "sa_family" field of the "struct sockaddr" data structure. This field identifies the type of data structure.
   The struct sockaddr_in is the protocol specific address data structure for IPv4. For IPv6 the structure is struct sockaddr_in6. :w

The socket functions
--------------------
  Applications call the socket() function to create a socket descriptor that represents a communication end point. The parameters passed to the socket structure indicate which protocol to use. 

    For example to create an TCP/IPv4 socket applications would call
      socket (AF_INET, SOCK_STREAM, 0);
    And in the case of UDP/IPv4 
      socket (AF_INET, SOCK_DGRAM, 0);
    In the case of IPv6, to create a TCP/IPv6 socket applications would call
      socket (AF_INET6, SOCK_STREAM, 0);
    And in the case of UDP/IPv6 
      socket (AF_INET6, SOCK_DGRAM, 0); 
   Once the application has created a socket with address family AF_INET6, it would pass a sockaddr_in6 structure to the system. The functions used to pass addresses to the system are bind(), connect(), sendmsg(), sendto(). The system will use the sockaddr_in6 structures to return addresses to applications using AF_INET6 sockets. These functions that return an address from the system are accept(), recvfrom(), recvmsg(), getpeername(), getsockname().
   No changes are required to the above functions for IPv6 support since they use the opaque address pointer (struct sockaddr *) and carry an address length as a function parameter.


Compatibility with IPv4 only nodes
----------------------------------
  Applications which create AF_INET6 sockets should be able to communicate with IPv4 nodes. In this case the IPv4 node's address is mapped to an IPv6 address (IPv4 mapped IPv6 address). This address allows the IPv4 address of an IPv4 node to be represented as an IPv6 address. The IPv4 mapped address is as follows
 ::FFFF:<IPv4 address>
  These addresses would be generated by a call to getaddrinfo () when resolving the hostname. IPv6 aware applications may use AF_INET6 sockets to open TCP connections or send UDP packets to IPv4 nodes by simply encoding the destination's IPv4 address as IPv4-mapped IPv6 address and passing that address with a sockaddr_in6 address to the connect() or sendto() call. When applications use AF_INET6 sockets () to accept TCP connections or receive UDP packets from IPv4 nodes, the system returns the peer's IPv4 address as an IPv4-mapped IPv6 address in the sockaddr_in6 address passed to the accept(), recvfrom () etc calls.

Address Conversion Functions
----------------------------
  The two functions inet_addr() and inet_ntoa() convert an IPv4 address to its binary and text form. IPv6 has similar functions. The following convert both IPv4 and IPv6 addresses
  
  int inet_pton(int af, const char *src, void *dst);

  const char *inet_ntop(int af, const void *src,
                        char *dst, size_t size); 

The inet_pton() function converts an address in its standard text presentation form into its numeric binary form.  The inet_ntop() function converts a numeric address into a text string.  The af argument specifies the family of the address.  This can be AF_INET or AF_INET6. In order to allow applications to easily declare buffers of the proper size to store IPv4 and IPv6 addresses in string form, the following two constants are defined in <netinet/in.h>:

      #define INET_ADDRSTRLEN    16
      #define INET6_ADDRSTRLEN   46

Protocol-Independent Nodename translation
  Nodename to address translation is done in a protocol independent manner by the use of the getaddrinfo() function. 

  int getaddrinfo(const char *nodename, const char *servname,
                  const struct addrinfo *hints, struct addrinfo **res);

  The getaddrinfo() function translates the name of a service location
   (for example, a host name) and/or a service name and returns a set of
   socket addresses and associated information to be used in creating a
   socket with which to address the specified service.
   We would be using getaddrinfo() for all nodename to address translation. The use of this is explained in a sample code later. 
   The getaddrinfo() could return a list of addresses (IPv4 or IPv6 or both) after nodename is translated. The aim is try all addresses when communicating with a host till one succeeds.

  Till now we have mentioned the key aspects to be considered while porting IPv4 applications to be IPv6 aware.

  We shall try to illustrate two functions which are IPv6 aware and which fallback to IPv4 code if IPv6 is not enabled during compile time or if IPv6 support is not available on the host during run time.


 The first function is ipv6_test_bind_accept()

/* Server code. Bind to port and accept connections */

int ipv6_test_bind_accept(int port)
{
 int lsock, newsock; /* The listen socket */

#if defined (ENABLE_IPV6)
  struct sockaddr_in6 sin6;
  struct sockaddr_in6 fsin6;
#endif
  struct sockaddr_in sin;
  struct sockaddr_in fsin;
  struct sockaddr *saddr, *fsaddr;
  int socklen, fsocklen;
  int sock_opt;

#ifdef ENABLE_IPV6
  if (ipv6_supported()) { /* Runtime check for IPv6 */
    memset(&sin6, 0, sizeof (sin6));

    sin6.sin6_family = AF_INET6; /* Address family is AF_INET6 */
    sin6.sin6_port = htons(port);
    sin6.sin6_addr = in6addr_any; /* Specify any address */
                                  /* Addresses of IPv4 nodes would be specified
                                     as IPv4-mapped addresses */

    /* Try to create a socket for listening */
    lsock = socket (AF_INET6, SOCK_STREAM, 0);
    if (lsock == -1) {
       fprintf (stderr, "Error while creating a socket\n");
       return -1;
    }
    saddr = (struct sockaddr *)&sin6; /* This should work since we are assigning
 the pointer */
    fsaddr = (struct sockaddr *)&fsin6; /* This should work since we are assigni
ng the pointer */
    socklen = sizeof(sin6);
  }
  else
#endif
  {
    /* Here would be the original IPv4 code as usual */
    memset (&sin, 0, sizeof (sin));
    sin.sin_family = AF_INET; /* IPv4 address family */
    sin.sin_port = htons(port);
    sin.sin_addr.s_addr = INADDR_ANY; 

     /* Create the socket */
    lsock = socket (AF_INET, SOCK_STREAM, 0); /* Create an AF_INET socket */
    if (lsock == -1) {
      fprintf (stderr, "Error while creating a socket\n");
      return -1;
    }
    saddr = (struct sockaddr *)&sin;
    fsaddr = (struct sockaddr *)&fsin;
    socklen = sizeof(sin);
  }

  /* Now that we created a socket. Now we have the protocol independent 
   * functions
   * Firstly tell the system to allow local addresses to be reused */

  sock_opt = 1;
  if (setsockopt (lsock, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) == -1) {
    fprintf (stderr, "Error while setting socket option SO_REUSEADDR\n");
    close (lsock);
    return -1;
  }

  /* saddr=(sockaddr_in) if lsock is of type AF_INET else its (sockaddr_in6) */
  if (bind(lsock, (struct sockaddr*)saddr, socklen) == -1) {
    fprintf(stderr, "Unable to bind\n");
    close (lsock);
    return -1;
  }

  if (listen (lsock, 1) == -1) {
    fprintf(stderr, "Error in listen()\n");
    close (lsock);
    return -1;
  }

  /* Wait for a connection request */
  for (;;) {
    fsocklen = socklen;
    newsock = accept(lsock, (struct sockaddr*)fsaddr, &fsocklen);

    if (newsock == -1) {
      /* Possibly the connection got aborted */
      fprintf (stderr, "Unable to accept the new connection\n");
      continue;
    }

    fprintf (stdout, "Received a new connection\n");
    /* Now process the connection as per the protocol */
#ifdef ENABLE_IPV6
    if (ipv6_supported()) {
      /* This casting would work since we have take care of the appropriate
       * data structures
       */
      struct sockaddr_in6 *sin6_ptr = (struct sockaddr_in6 *)fsaddr;
      char addrbuf[INET6_ADDRSTRLEN];
      if (IN6_IS_ADDR_V4MAPPED(&(sin6_ptr->sin6_addr))) {
        fprintf(stdout, "Connection from a IPv4 client\n");
      }

      (void) printf("Connection from %s/%d\n",
                     inet_ntop(AF_INET6, (void *)&(sin6_ptr->sin6_addr),
                     addrbuf, sizeof (addrbuf)),
                     ntohs(sin6_ptr->sin6_port));
     } 
     else
#endif
     {
        struct sockaddr_in *sin_ptr = (struct sockaddr_in *)fsaddr;
        printf ("Connection from %s/%d\n", inet_ntoa(sin_ptr->sin_addr), ntohs(s
in_ptr->sin_port));
     }
   }
}

The next function is ipv6_test_connect() which tries to connect to a host

/* Connect to a host:port. Returns the Socket file descriptor */
int
ipv6_test_connect (char *host, int port)
{
 int sock, s;
 struct sockaddr_in sin;
 int ret_val;

#if defined (ENABLE_IPV6)
  struct sockaddr_in6 sin6;
  struct addrinfo hints, *result = NULL, *res;
  int af = AF_INET;
#endif
  unsigned long inaddr;
  struct hostent *hp;

  memset (&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;

  /* Notice that it will fallback to IPv4 if ipv6_supported is false */
#if defined (ENABLE_IPV6)
  if (ipv6_supported ()) /* IPv6 enabled and supported on the system */
  {
    memset (&sin6, 0, sizeof(sin6));
    sin6.sin6_family = AF_INET6;

    memset (&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;

    if (getaddrinfo (host, NULL, &hints, &result) != 0) {
      fprintf (stderr, "Unable to resolve host %s\n", host);
      return -1; /* Unable to resolve the host */
    }

    /* Try all the addresses for a connect () */
    for (res = result; res; res = res->ai_next) {
      af = res->ai_family;

      if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
        continue;

      if (af == AF_INET) {
        fprintf(stdout,"Connecting using IPv4\n");
         memcpy (&sin.sin_addr, &((struct sockaddr_in *)res->ai_addr)->sin_addr,sizeof (struct in_addr));
         sin.sin_port = htons(port);
         sock = socket (AF_INET, SOCK_STREAM, 0);
         if (sock < 0)
           continue;
         if (connect (sock, (struct sockaddr *)&sin, sizeof (sin)) != -1)
{
           break;
         }
         close (sock);
      }
      if (af == AF_INET6) {
        fprintf(stdout,"Connecting using IPv6\n");
        memcpy (&sin6.sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof (struct in6_addr));
        sin6.sin6_port = htons(port);
        sock = socket (AF_INET6, SOCK_STREAM, 0);
        if (sock < 0)
          continue;
        if (connect (sock, (struct sockaddr *)&sin6, sizeof (sin6)) != -1)
          break;
        close (sock);
      }
    }
    freeaddrinfo (result);
    if (!res) /* Couldnt connect to any of the addresses */
      return -1;
    return sock;
  }
  else
#endif
  { /* Begin IPv4 only original code */
    inaddr = inet_addr(host);
    if (inaddr != (unsigned long) -1)
        memcpy(&sin.sin_addr, &inaddr, sizeof(inaddr));
    else {
        hp = gethostbyname(host);
        if (hp == NULL)
            return -1;
        memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    }
    sin.sin_port = htons(port);
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
        return -1;
    if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
        close(sock);
        return -1;
    }
    return sock;
  }
}

References
----------
RFC 2553 - Basic Socket Interface Extensions for IPv6
IPv6 Overview paper available at http://playground.sun.com/pub/ipng/html/INET-IPng-Paper.html
"Porting Networking Applications to the IPv6 APIs" available from http://www.sun.com/solaris/ipv6/

Attachment: esound-ipv6port.diff
Description: Binary data



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]