Проблемы с подключением IPv6 в C

Я пытаюсь закодировать независимый эхо-сервер, который может принимать как IPv4, так и IPv6-соединение. Я работаю со структурой addrinfo, установленной с помощью getaddrinfo.
Ipv4 connect не имеет проблем, пока я не могу получить работающее ipV6 соединение. Я думаю, что моя проблема может быть связана с неправильным параметром getaddrinfo, но я не могу понять, в чем проблема.
Вот мой код

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

1 ответ

Решение

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 вариант. 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;
}

...

Код client.c ошибочен, он хранит полное addrinfo в структуре sockaddr_in. res имеет тип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;
}; 

так что вместо
struct sockaddr_in *addr_in = (struct sockaddr_in *)res;должен бытьstruct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;

подобно

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

после этой модификации код работает должным образом.

Другие вопросы по тегам