Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
}
like image 934
Mala88 Avatar asked Jan 17 '16 11:01

Mala88


People also ask

Why do we use IPv4 instead of IPv6?

IPv4 provides an addressing capability of approximately 4.3 billion addresses. The Internet Protocol version 6 (IPv6) is more advanced and has better features compared to IPv4. It has the capability to provide an infinite number of addresses.

How many IP addresses can IPv6 support?

Before jumping into details, there are a few key features IPv6 incorporates: IPv6 uses 128-bit (2128) addresses, allowing 3.4 x 1038 unique IP addresses. This is equal to 340 trillion trillion trillion IP addresses.

When IPv6 will run out?

Will IPv6 addresses run out eventually? In practical terms, no. There are 2^128 or 340 trillion, trillion, trillion IPv6 addresses, which is more than 100 times the number of atoms on the surface of the Earth. This will be more than sufficient to support trillions of Internet devices for the forseeable future.

Why is IPv6 needed?

The primary function of IPv6 is to allow for more unique TCP/IP address identifiers to be created, now that we've run out of the 4.3 billion created with IPv4. This is one of the main reasons why IPv6 is such an important innovation for the Internet of Things (IoT).


2 Answers

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;
}

...
like image 134
Remy Lebeau Avatar answered Sep 28 '22 12:09

Remy Lebeau


the client.c code is flawed, it's storing the complete addrinfo in sockaddr_in structure. res is of type addrinfo (http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)

struct addrinfo {
   int              ai_flags;
   int              ai_family;
   int              ai_socktype;
   int              ai_protocol;
   socklen_t        ai_addrlen;
   struct sockaddr *ai_addr;
   char            *ai_canonname;
   struct addrinfo *ai_next;
}; 

so instead of
struct sockaddr_in *addr_in = (struct sockaddr_in *)res; it should be struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;

like

case AF_INET: {
    struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
    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->ai_addr;
    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;
  }

after this modification the code works as expected.

like image 23
lightyagami1 Avatar answered Sep 28 '22 13:09

lightyagami1