csocketsipv6berkeley-sockets

Problems with IPv6 connect in C


I'm trying to code an agnostic echo server, that could accept both IPv4 and IPv6 connection. I'm working with addrinfo structure, set with getaddrinfo.
Ipv4 connect has no problem while I can't get a working ipV6 connection. I think my problem could be due to a wrong getaddrinfo parameter, but I'm not able to see where I'm going wrong.
Here's my code

client.c

#include <stdio.h>      
#include <sys/types.h>
#include <sys/socket.h>   
#include <netdb.h>
#include <stdlib.h>
#include <string.h> 
#include <errno.h>

int main(int argc, char *argv[])
{
      int simpleSocket = 0, simplePort = 0,returnStatus = 0, n; 
      char buffer[1024] = "";
      struct hostent *hostinfo;
      struct addrinfo simpleServer, *res;

      if (3 != argc) {
          fprintf(stderr, "Usage: %s <server> <port>\n", argv[0]);
          exit(1);
      }

      simplePort = atoi(argv[2]);    

      memset(&simpleServer, 0, sizeof simpleServer);
      simpleServer.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
      simpleServer.ai_socktype = SOCK_STREAM;
      simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me

      returnStatus = getaddrinfo(argv[1], argv[2], &simpleServer, &res);

      simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

      char *s = NULL;
      switch(res->ai_addr->sa_family) {
      case AF_INET: {
            struct sockaddr_in *addr_in = (struct sockaddr_in *)res;
            s = malloc(INET_ADDRSTRLEN);
            inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
            returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
            break;
      }
      case AF_INET6: {
            struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res;
            s = malloc(INET6_ADDRSTRLEN);
            inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
            returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
            break;
      }
      default:
            break;
      }
      fprintf(stdout, "IP address: %s\n", s);

      returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
      fprintf(stdout, "Type a message \n");

      memset(buffer, '\0', strlen(buffer));
      fgets(buffer, sizeof(buffer), stdin);
      returnStatus = write(simpleSocket, buffer, sizeof(buffer));

      memset(&buffer, '\0', sizeof(buffer));
      fprintf(stdout, "Waiting server..\n");     
      returnStatus = read(simpleSocket, buffer, sizeof(buffer));

      fprintf(stdout, "Message: %s\n", buffer);

      close(simpleSocket);
      return 0;
}

server.c

#include <stdio.h>      
#include <sys/types.h>
#include <sys/socket.h>   
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{
      int simpleSocket = 0, simplePort = 0, returnStatus = 0, check = 1, n; 
      char buffer[1024];

      struct addrinfo simpleServer, *res;

      if (2 != argc) {
          fprintf(stderr, "Usage: %s <port>\n", argv[0]);
          exit(1);
      }

      simplePort = atoi(argv[1]);

      memset(&simpleServer, 0, sizeof simpleServer);
      simpleServer.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
      simpleServer.ai_socktype = SOCK_STREAM;
      simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me

      getaddrinfo(NULL, argv[1], &simpleServer, &res);

      simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
      returnStatus =bind(simpleSocket, res->ai_addr, res->ai_addrlen);
      returnStatus = listen(simpleSocket, 5);

      struct addrinfo clientName = { 0 };
      int clientNameLength = sizeof(clientName);
      int simpleChildSocket = 0;

      while (1) {
         while (1) { 
            simpleChildSocket = accept(simpleSocket,(struct sockaddr *)&clientName, &clientNameLength);

            fprintf(stdout,"Waiting..\n");

            memset(&buffer, '\0', sizeof(buffer));        
            returnStatus = read(simpleChildSocket, buffer, sizeof(buffer));

            fprintf(stdout, "Message: %s\n", buffer);

            write(simpleChildSocket, buffer, sizeof(buffer));     
         }
      }
      close(simpleChildSocket);
      close(simpleSocket);
      return 0;
}

Solution

  • As @JoachimPileborg mentioned in comments, the problem is that your server code is not opening listening sockets for every address that getaddrinfo() gives you. You are specifying AF_UNSPEC in your simpleServer struct, so getaddrinfo() will give you a list of both IPv4 and IPv6 addresses. You are creating a listening socket for only the first entry in that list, which happens to be an IPv4 address. That is why your IPv4 client succeeds and your IPv6 client fails. You need to loop through the list creating a separate listening socket for each entry.

    You are also making other mistakes, like not resetting your clientNameLength variable each time you call accept(), using addrinfo as your clientName buffer when you should be using sockaddr_storage instead, and not paying attention to the return value of read().

    Try something more like this:

    include <stdio.h>      
    #include <sys/types.h>
    #include <sys/socket.h>   
    #include <netdb.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <poll.h>
    
    #define MAX_SERVERS 10
    #define MAX_CLIENTS 50
    #define MAX_SOCKETS (MAX_SERVERS + MAX_CLIENTS)
    
    int main(int argc, char *argv[])
    {    
        int simpleSocket, simplePort, returnStatus, n, m; 
        char buffer[1024];
        pollfd simpleSockets[MAX_SOCKETS];
        int numSockets = 0, numServers = 0;
    
        struct addrinfo simpleServer, *res, *addr;
    
        if (2 != argc)
        {
            fprintf(stderr, "Usage: %s <port>\n", argv[0]);
            exit(1);
        }
    
        simplePort = atoi(argv[1]);
    
        memset(&simpleServer, 0, sizeof simpleServer);
        simpleServer.ai_family = AF_UNSPEC;  // use IPv4 and/or IPv6, whatever is available
        simpleServer.ai_socktype = SOCK_STREAM;
        simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me
    
        if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
        {
            fprintf(stderr, "getaddrinfo failed\n");
            exit(1);
        }
    
        addr = res;
        while (NULL != addr)
        {
            simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
            if (-1 == simpleSocket)
            {
                fprintf(stderr, "socket failed\n");
            }
            else
            {
                returnStatus = bind(simpleSocket, addr->ai_addr, addr->ai_addrlen);
                if (0 == returnStatus)
                    returnStatus = listen(simpleSocket, 5);
    
                if (0 == returnStatus)
                {
                    simpleSockets[numSockets].fd = simpleSocket;
                    simpleSockets[numSockets].events = POLLIN;
                    simpleSockets[numSockets].revents = 0;
                    ++numSockets;
                    ++numServers;
                    if (MAX_SERVERS == numServers)
                        break;
                }
                else
                {
                    fprintf(stderr, "bind/listen failed\n");
                    close(simpleSocket);
                }
            }
            addr = addr->next;
        }
    
        freeaddrinfo(res);
    
        if (0 == numServers)
        {
            fprintf(stderr, "no servers are listening\n");
            exit(1);
        }
    
        struct sockaddr_storage clientName;
        int clientNameLength;
    
        while (1)
        { 
            returnStatus = poll(simpleSockets, numSockets, -1);
            if (-1 == returnStatus)
            {
                fprintf(stderr, "poll failed\n");
                exit(1);
            }
    
            if (0 == returnStatus)
                continue;
    
            for (n = 0; n < numSockets; ++n)
            {
                if (simpleSockets[n].revents & POLLIN)
                {
                    if (n < numServers)
                    {
                        clientNameLength = sizeof(clientName);
    
                        simpleSocket = accept(simpleSockets[n].fd, (struct sockaddr *)&clientName, &clientNameLength);
                        if (-1 == simpleSocket)
                        {
                            fprintf(stderr, "accept failed\n");
                            continue;
                        }
    
                        for (m = numServers; m < numSockets; ++m)
                        {
                            if (-1 == simpleSockets[m].fd)
                            {
                                simpleSockets[m].fd = simpleSocket;
                                simpleSockets[m].events = POLLIN;
                                simpleSockets[m].revents = 0;
                                simpleSocket = -1;
                                break;
                            }
                        }
    
                        if ((-1 != simpleSocket) && (MAX_SOCKETS > numSockets))
                        {
                            simpleSockets[numSockets].fd = simpleSocket;
                            simpleSockets[numSockets].events = POLLIN;
                            simpleSockets[numSockets].revents = 0;
                            ++numSockets;
                            simpleSocket = -1;
                        }
    
                        if (-1 != simpleSocket)
                        {
                            fprintf(stderr, "Too many clients connected\n");
                            close(simpleSocket);
                        }
                        else
                            fprintf(stdout, "Client connected\n");
                    }
                    else
                    {
                        returnStatus = read(simpleSockets[n].fd, buffer, sizeof(buffer));
                        if (0 >= returnStatus)
                        {
                            if (0 > returnStatus)
                                fprintf(stdout, "Client error, disconnecting\n");
                            else
                                fprintf(stdout, "Client disconnected\n");
    
                            close(simpleSockets[n].fd);
                            simpleSockets[n].fd = -1;
                            simpleSockets[n].events = 0;
                            simpleSockets[n].revents = 0;
    
                            continue;
                        }
    
                        fprintf(stdout, "Message: %.*s\n", returnStatus, buffer);
                        write(simpleSockets[n].fd, buffer, returnStatus);     
                    }
                }
    
                if (simpleSockets[n].revents & (POLLERR|POLLHUP|POLLNVAL))
                {
                    if (simpleSockets[n].revents & POLLHUP)
                        fprintf(stdout, "Client disconnected\n");
                    else if (n >= numServers)
                        fprintf(stdout, "Client error, disconnecting\n");
                    else
                        fprintf(stdout, "Server error, closing\n");
    
                    close(simpleSockets[n].fd);
                    simpleSockets[n].fd = -1;
                    simpleSockets[n].events = 0;
                    simpleSockets[n].revents = 0;
                }
            }
        }
    
        for (n = 0; n < numSockets; ++n)
        {
            if (-1 != simpleSockets[n].fd)
                close(simpleSockets[n].fd);
        }
    
        return 0;
    }
    

    With that said, if you are running on a platform that supports dual-stack sockets, your server does not have to create any IPv4 listening sockets at all. It can create IPv6 sockets only, and then disable their IPV6_V6ONLY option. This will allow them to accept both IPv4 and IPv6 clients. The client address returned by accept() will tell you whether an IPv4 or IPv6 client has connected.

    memset(&simpleServer, 0, sizeof simpleServer);
    simpleServer.ai_family = AF_INET6;
    simpleServer.ai_socktype = SOCK_STREAM;
    simpleServer.ai_flags = AI_PASSIVE;
    
    if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
    {
        fprintf(stderr, "getaddrinfo failed\n");
        exit(1);
    }
    
    addr = res;
    while (NULL != addr)
    {
        simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (-1 == simpleSocket)
        {
            fprintf(stderr, "socket failed\n");
        }
        else
        {
            n = 0;
            returnStatus = setsockopt(simpleSocket, IPPROTO_IPV6, IPV6_V6ONLY, &n, sizeof(n));
            ...
        }
        addr = addr->next;
    }
    
    ...