IPv4-only & IPv6-only applications HowTo

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

When porting applications to IPv6, IMHO, they should work as dual applications, using both protocol versions. However, sometimes we want to maintain two different versions of the same application running at the same node. One application version working with IPv4 and the other one with IPv6.

In many systems, the default behavior of IPv6 applications in the dual stack nodes allows the IPv4 communication with remote nodes, using IPv4-mapped IPv6 addresses. When an IPv6 application is using an IPv4-mapped IPv6 address, the kernel will exchange to IPv4 to establish the communication.

If we want the IPv6 application only manages IPv6 connections we can use the IPV6_V6ONLY socket option. First, check if this option is supported in your kernel version (USAGI does it). To study the IPv6-only behaviour we consider the client/server communication model.

An IPv6 server application can be configured to only accept IPv6 connections. This way allows to run at the same node the two versions of the same application service, dealing each one with different IP version. The IPv6 application version must configure the server socket to accept only IPv6 communications, using the IPV6_V6ONLY option.

Note, if this option is not used and one of the server applications is started, the second one will not be able to use the same service port, since the IPv6 application is also receiving IPv4 packets. The bind operation will return the following error message: "Address already in use".

An IPv6 client application can be configured to only connect to IPv6 nodes using the IPV6_ONLY option.

Setting IPV6_V6ONLY option:



 int on=1;
 if (family==AF_INET6) {
     if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, 
                    (char *)&on, sizeof(on)) == -1) 
     {
         /* handle setsockopt error*/
     }
 }


For instance, we can start the following TCP server daytime using IPv4 or IPv6-only. To start the IPv4 application, run "./server -inet" and the IPv6-only application, run "./server -inet6".

Start both instances and check it using telnet application. If you use ::1 and 127.0.0.1, each application will answer to its daytime request: IPv4 server for 127.0.0.1 and IPv6 server for ::1.



#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


const char *DAYTIME_PORT="13111";
const int LISTEN_QUEUE=128;


void
usage(char *argv[])
{
	printf("Usage:: \n   %s (-inet | -inet6)\n", argv[0]);
	exit(-1);
}

int
main(int argc, char *argv[])
{
    struct addrinfo hints, *res, *aip;
    int error, sockfd, connfd, family, socktype;
    socklen_t addrlen;
    char timeStr[256];
    struct sockaddr_storage clientaddr;
    time_t now;
    char clienthost[NI_MAXHOST];
    char clientservice[NI_MAXSERV];

    if (argc != 2) usage(argv);

    if (!strcmp(argv[1], "-inet")) 
	    family=AF_INET;
    else if (!strcmp(argv[1], "-inet6")) 
	    family=AF_INET6;
    else usage(argv);
    
    socktype=SOCK_STREAM;

    memset(&hints, 0, sizeof(struct addrinfo));

    /*
       AI_PASSIVE flag: we use the resulting address to bind
       to a socket for accepting incoming connections. 
       So, when the hostname==NULL, getaddrinfo function will 
       return one entry per allowed protocol family containing 
       the unspecified address for that family.
    */

    hints.ai_flags    = AI_PASSIVE;
    hints.ai_family   = family;
    hints.ai_socktype = socktype; 
    
    error = getaddrinfo(NULL, DAYTIME_PORT, &hints, &res);
    
    if (error != 0) {
        fprintf(stderr, 
                "getaddrinfo error:: [%s]\n",  
                gai_strerror(error));
        exit(-1);
    }

    /* 
       Try open socket with each address getaddrinfo returned, 
       until we get a valid listening socket. 
    */
    sockfd=-1;
    for (aip=res; aip; aip=aip->ai_next) {
        sockfd = socket(aip->ai_family, 
                        aip->ai_socktype, 
                        aip->ai_protocol); 

        if (sockfd >= 0) {
            int on =1;
	    if (aip->ai_family==AF_INET6) { 
	        if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, 
		               (char *)&on, sizeof(on)) == -1) 
	        {
	            perror("setsockopt IPV6_V6ONLY");
	 	    exit(-1);
	        } 
            }
            
            if (bind(sockfd, aip->ai_addr, aip->ai_addrlen) == 0) 
                break;

            close(sockfd);
            sockfd=-1;
        }
    }
    
    freeaddrinfo(res);

    if (sockfd < 0) {
        fprintf(stderr,
                "socket error:: could not open socket\n");
        exit(-1);
    }

    if (listen(sockfd, LISTEN_QUEUE) < 0) {
         fprintf(stderr,
                 "listen_socket error:: could not create listening "
                 "socket\n");
         exit(-1);
    }

    for ( ; ;) {
        addrlen = sizeof(clientaddr);

        /* accept daytime client connections */
        connfd = accept(sockfd, 
                        (struct sockaddr *)&clientaddr, 
                        &addrlen);

        if (connfd < 0) {
	    perror("connect error:: ");
            continue;
        }

        memset(clienthost, 0, sizeof(clienthost));
        memset(clientservice, 0, sizeof(clientservice));

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

        if (error != 0) {
	    fprintf(stderr, "getnameinfo error:: can not print
	            the received request information \n");
	} else {
            printf("Received request from host=[%s] port=[%s]\n",
                   clienthost, clientservice);
        }

        /* process daytime request from a client */
        memset(timeStr, 0, sizeof(timeStr));
        time(&now);
        sprintf(timeStr, "%s", ctime(&now));
        write(connfd, timeStr, strlen(timeStr));
        close(connfd);
    }

    return 0;
}



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