IPv6 示例程序在 connect() 上失败

IPv6 example program fails on connect()

IPv6 示例程序在 connect() 上失败

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <ctype.h>

void error(const char * es)
{
  fprintf(stderr, "Error: %s\n", es);
  exit(1);
}

struct sockaddr * getadr(char * name)
{
  struct addrinfo * p;
  int r;
  struct sockaddr_in6 * sap;
  unsigned long long addrl, addrh;

  printf("getadr: begin\n");
  r = getaddrinfo(name, NULL, NULL, & p);
  if (r) error(gai_strerror(r));
  sap = NULL;
  while (p && !sap) {

    /* traverse the available addresses */
    if (p - > ai_family == AF_INET6 && p - > ai_socktype == SOCK_STREAM) {

      /* get the IPv6 address */
      sap = (struct sockaddr_in6 * ) p - > ai_addr;

    }
    p = p - > ai_next;

  }
  if (!sap) error("No address found");
  addrh = (unsigned long long) ntohl(sap - > sin6_addr.__in6_u.__u6_addr32[0]) << 32 |
    (unsigned long long) ntohl(sap - > sin6_addr.__in6_u.__u6_addr32[1]);
  addrl = (unsigned long long) ntohl(sap - > sin6_addr.__in6_u.__u6_addr32[2]) << 32 |
    (unsigned long long) ntohl(sap - > sin6_addr.__in6_u.__u6_addr32[3]);
  printf("Address: %llx:%llx:%llx:%llx:%llx:%llx:%llx:%llx\n",
    addrh >> 48 & 0xffff, addrh >> 32 & 0xffff, addrh >> 16 & 0xffff, addrh & 0xffff,
    addrl >> 48 & 0xffff, addrl >> 32 & 0xffff, addrl >> 16 & 0xffff, addrl & 0xffff);
  printf("getadr: end\n");

  return ((struct sockaddr * ) sap);

}

int main(int argc, char * argv[]) {
  int sockfd = 0, n = 0;
  char buff[1024];
  struct sockaddr_in6 serv_addr;
  int r;
  struct sockaddr * sap;

  if (argc != 3) {

    printf("Usage: socket <server> <page>\n");
    exit(1);

  }

  if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
    error("Could not create socket");

  memset( & serv_addr, '0', sizeof(serv_addr));

  serv_addr.sin6_family = AF_INET6;
  serv_addr.sin6_port = htons(80);

  printf("before address resolve\n");
  if (isdigit(argv[1][0])) {

    r = inet_pton(AF_INET6, argv[1], & serv_addr.sin6_addr);
    if (r <= 0) error("inet_pton error occured");

  } else {

    sap = getadr(argv[1]);
    memcpy( & serv_addr, sap, sizeof(struct sockaddr));

  }
  printf("after address resolve\n");

  r = connect(sockfd, (struct sockaddr * ) & serv_addr, sizeof(serv_addr));
  if (r < 0) error("Connect Failed");
  printf("after connect\n");

  /* send request */
  sprintf(buff, "GET %s HTTP/1.1\r\n", argv[2]);
  write(sockfd, buff, strlen(buff));

  sprintf(buff, "Host: %s\r\n\r\n", "www.example.com" /*argv[1]*/ );
  write(sockfd, buff, strlen(buff));
  do {

    r = read(sockfd, buff, sizeof(buff));
    if (r > 0) {

      buff[r] = 0;
      printf("%s", buff);

    }

  } while (r);

  return 0;

}

如果是数字,我安排服务器参数由 inet_pton() 评估,否则,它通过 getaddrinfo()inet_pton() 设置地址并且它有效。 getaddrinfo() 不,显然,它在连接中死亡(挂断)。示例程序是一个简单的网页获取和打印(不是 https)。我使用 www.example.com 服务器进行测试。

请注意在下面的示例中 运行 我使用相同的地址 getaddrinfo() 在一个数字示例中给我,那么它工作正常。

我做错了什么?

编译就是gcc socket.c -o socket.

$ socket6 www.example.com /
before address resolve
getadr: begin
Address: 2606:2800:220:1:248:1893:25c8:1946
getadr: end
after address resolve
^C
(hangs up in connect, CTL-C out of it)
$ socket6 2606:2800:220:1:248:1893:25c8:1946 /
before address resolve
after address resolve
after connect
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
...
(prints the rest of the page)

我确实找到了一个类似的 post,他们建议将接口名称添加到服务器名称,例如 www.example.com%enp2s0,但被 getaddrinfo() 拒绝为无效。

主要问题是您没有正确填充 serv_addr

当调用 getadr() 时,您对 memcpy() 的调用结果没有复制足够的字节来完成 sockaddr_in6 (sizeof(sockaddr) is less thansizeof(sockaddr_in6 )). Also, you are not askinggetaddrinfo()to output a port number, so thesin6_port` 结果不会像您期望的那样是 80。

这就是为什么当您使用 getaddrinfo() 而不是 inet_pton()connect() 失败的原因。

您的代码还有其他问题。

使用memset()时,需要使用整数0而不是字符'0'。它们不是相同的值。未使用的 sockaddr_in6 字段需要正确清零。

您的 getadr() 函数会泄漏内存,并且通常效率低下。使用 getaddrinfo()hints 参数来限制它输出的结果,因此您不必去寻找它们。使用完后,您需要使用 freeaddrinfo() 释放输出。

isdigit() 不是区分数字 IP 和主机名的正确方法。此外,您实际上并不需要手动执行此区分,因为 getaddrinfo() 可以解析数字 IP 字符串。但是,如果您手动区分,则无条件调用 inet_pton(),然后在 inet_pton() 失败时调用 getaddrinfo()

话虽如此,请尝试更像这样的东西:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <ctype.h>

void error(const char* es)
{
    fprintf(stderr, "Error: %s\n", es);
    exit(1);
}

void getadr(const char* name, struct in6_addr *addr)
{
    struct addrinfo hints, *p;
    int r;
    struct sockaddr_in6 *sap;
    char addrstr[INET6_ADDRSTRLEN];

    printf("getadr: begin\n");

    /*
    r = inet_pton(AF_INET6, name, addr);
    if (r == 1)
    {
        printf("getadr: end\n")
        return;
    }
    */

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    r = getaddrinfo(name, NULL, &hints, &p);
    if (r != 0)
        error(gai_strerror(r));

    sap = (struct sockaddr_in6*)p->ai_addr;

    memcpy(addr, &(sap->sin6_addr), sizeof(*addr));
    freeaddrinfo(p);

    printf("Address: %s\n", inet_ntop(AF_INET6, addr, addrstr, sizeof(addrstr)));

    printf("getadr: end\n");
}

int main(int argc, char *argv[])
{
    int sockfd, r;
    char buff[1024];
    struct sockaddr_in6 serv_addr;

    if (argc != 3)
    {
        printf("Usage: socket <server> <page>\n");
        exit(1);
    }

    sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0)
        error("Could not create socket");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin6_family = AF_INET6;
    serv_addr.sin6_port = htons(80);

    printf("before address resolve\n");

    getadr(argv[1], &(serv_addr.sin6_addr));

    printf("after address resolve\n");

    r = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (r < 0)
        error("Connect Failed");

    printf("after connect\n");

    /* send request */

    sprintf(buff, "GET %s HTTP/1.1\r\n", argv[2]);
    write(sockfd, buff, strlen(buff));
    sprintf(buff, "Host: %s\r\n", argv[1]);
    write(sockfd, buff, strlen(buff));
    sprintf(buff, "%s", "Connection: close\r\n\r\n");
    write(sockfd, buff, strlen(buff));

    do
    {
        r = read(sockfd, buff, sizeof(buff));
        if (r <= 0) break;
        printf("%.*s", r, buff);
    }
    while (true);

    close(sockfd);
    return 0;
}

也就是说,getaddrinfo() 输出一个 IP 地址链表。一个主机名可以解析为多个 IP,但您的计算机可能无法访问所有 IP。您应该遍历整个列表 connect()'ing 到每个 IP,直到其中一个成功或列表耗尽。例如:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <ctype.h>

void error(const char* es)
{
    fprintf(stderr, "Error: %s\n", es);
    exit(1);
}

struct addrinfo* getadrs(const char* name, const char* port)
{
    struct addrinfo hints, *p;
    int r;

    printf("getadr: begin\n");

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET6 /*AF_UNSPEC*/;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    r = getaddrinfo(name, port, &hints, &p);
    if (r != 0)
        error(gai_strerror(r));

    printf("getadr: end\n");
    return p;
}

void* adrptr(struct sockaddr* addr)
{
    switch (addr->sa_family)
    {
        case AF_INET:
            return &(((struct sockaddr_in*)addr)->sin_addr);

        case AF_INET6:
            return &(((struct sockaddr_in6*)addr)->sin6_addr);
    }

    return NULL;
}

int main(int argc, char *argv[])
{
    int sockfd = -1, r;
    char buff[1024], addrstr[INET6_ADDRSTRLEN];
    struct addrinfo *serv_addrs, *addr;

    if (argc != 3)
    {
        printf("Usage: socket <server> <page>\n");
        exit(1);
    }

    printf("before address resolve\n");

    serv_addrs = getadrs(argv[1], "80");

    printf("after address resolve\n");

    for(addr = serv_addrs; addr != NULL; addr = addr->ai_next)
    {
        sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (sockfd < 0)
            error("Could not create socket");

        printf("Address: %s\n", inet_ntop(addr->ai_family, adrptr(addr->ai_addr), addrstr, sizeof(addrstr)));

        r = connect(sockfd, addr->ai_addr, addr->ai_addrlen);
        if (r == 0) break;

        close(sockfd);
        sockfd = -1;
    }

    if (sockfd < 0)
        error("Connect Failed");

    printf("after connect\n");

    /* send request */

    sprintf(buff, "GET %s HTTP/1.1\r\n", argv[2]);
    write(sockfd, buff, strlen(buff));
    sprintf(buff, "Host: %s\r\n", argv[1]);
    write(sockfd, buff, strlen(buff));
    sprintf(buff, "%s", "Connection: close\r\n\r\n");
    write(sockfd, buff, strlen(buff));

    do
    {
        r = read(sockfd, buff, sizeof(buff));
        if (r <= 0) break;
        printf("%.*s", r, buff);
    }
    while (true);

    close(sockfd);
    return 0;
}

重构代码:

/*
 * Socket.c program taken from the sockets example for linux
 * and refactored for IPv6.
 */

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <ctype.h>

void error(const char* es)

{

    fprintf(stderr, "Error: %s\n", es);
    exit(1);

}

int main(int argc, char *argv[])
{
    int sockfd = 0, n = 0;
    char buff[1024];
    struct sockaddr_in6 serv_addr;
    int r;
    struct sockaddr* sap;
    struct addrinfo hints, *p;

    if(argc != 3)
    {

        printf("Usage: socket <server> <page>\n");
        exit(1);

    }

    if((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
        error("Could not create socket");

    /* resolve address */
    memset(&hints, 0, sizeof(hints));

    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    r = getaddrinfo(argv[1], "80", &hints, &p);
    if (r != 0) error(gai_strerror(r));

    memcpy(&serv_addr, (struct sockaddr_in6*)p->ai_addr, sizeof(struct sockaddr_in6));
    freeaddrinfo(p);

    /* connect to server */
    r = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (r < 0) error("Connect Failed");

    /* send request */
    sprintf(buff, "GET %s HTTP/1.1\r\n", argv[2]);
    write(sockfd, buff, strlen(buff));

    sprintf(buff, "Host: %s\r\n\r\n", "www.example.com" /*argv[1]*/);
    write(sockfd, buff, strlen(buff));
    do {

        r = read(sockfd, buff, sizeof(buff));
        if (r > 0) {

            buff[r] = 0;
            printf("%s", buff);

        }

    } while (r);

    return 0;

}

运行现在是:

$ addr www.example.com
Addresses for host: www.example.com
Address: type: AF_INET6 Socket type: SOCK_STREAM Address: 2606:2800:220:1:248:1893:25c8:1946
Address: type: AF_INET6 Socket type: SOCK_DGRAM Address: 2606:2800:220:1:248:1893:25c8:1946
Address: type: AF_INET6 Socket type: SOCK_RAW Address: 2606:2800:220:1:248:1893:25c8:1946
Address: type: AF_INET Socket type: SOCK_STREAM Address: 93.184.216.34
Address: type: AF_INET Socket type: SOCK_DGRAM Address: 93.184.216.34
Address: type: AF_INET Socket type: SOCK_RAW Address: 93.184.216.34

$ socket6 www.example.com /
HTTP/1.1 200 OK
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 27 Oct 2019 17:50:10 GMT
...
(full page)

$ socket6 2606:2800:220:1:248:1893:25c8:1946 /
HTTP/1.1 200 OK
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 27 Oct 2019 17:50:21 GMT
...
(full page)

我必须仔细考虑一下您所说的并非所有远程地址都有效,我当时并不知道这一点。