C 中 IPv6 连接的问题

Problems with IPv6 connect in C

我正在尝试编写一个可以接受 IPv4 和 IPv6 连接的不可知回显服务器。我正在使用 addrinfo 结构,使用 getaddrinfo 进行设置。
Ipv4 连接没有问题,但我无法获得有效的 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, '[=10=]', strlen(buffer));
      fgets(buffer, sizeof(buffer), stdin);
      returnStatus = write(simpleSocket, buffer, sizeof(buffer));

      memset(&buffer, '[=10=]', 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, '[=11=]', 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;
}

正如@JoachimPileborg 在评论中提到的,问题是您的服务器代码没有为 getaddrinfo() 给您的每个地址打开监听套接字。您在 simpleServer 结构中指定 AF_UNSPEC,因此 getaddrinfo() 将为您提供 IPv4 和 IPv6 地址的列表。您正在为该列表中的第一个条目创建一个监听套接字,它恰好是一个 IPv4 地址。这就是您的 IPv4 客户端成功而 IPv6 客户端失败的原因。您需要遍历列表,为每个条目创建一个单独的监听套接字。

您还犯了其他错误,比如每次调用 accept() 时都没有重置 clientNameLength 变量,在您应该使用 addrinfo 作为 clientName 缓冲区时使用 sockaddr_storage 而不是注意 read().

的 return 值

试试像这样的东西:

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

也就是说,如果您 运行 在支持 dual-stack 套接字的平台上,您的服务器不必创建任何 IPv4 侦听套接字根本。它只能创建 IPv6 套接字,然后禁用它们的 IPV6_V6ONLY 选项。这将允许他们接受 IPv4 和 IPv6 客户端。由 accept() 编辑的客户端地址 return 将告诉您是否已连接 IPv4 或 IPv6 客户端。

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 代码有缺陷,它将完整的地址信息存储在 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;
  }

此修改后代码按预期工作。