Пример программы IPv6 завершается неудачно при подключении () - PullRequest
0 голосов
/ 27 октября 2019

Пример программы 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)

Я нашел похожий пост, где они рекомендуют добавить имя интерфейса к имени сервера,как www.example.com% enp2s0, но он был отклонен как недействительный getaddrinfo().

Ответы [ 2 ]

3 голосов
/ 27 октября 2019

Основная проблема заключается в том, что вы неправильно заполняете serv_addr.

При вызове getadr() ваш вызов memcpy() для результата не копирует достаточное количество байтов для полного sockaddr_in6 (sizeof(sockaddr) is less than sizeof (sockaddr_in6) ). Also, you are not asking getaddrinfo () to output a port number, so the sin6_port` результата не будет 80, как вы ожидаете.

Именно поэтому connect() завершается неудачно, когда вы используете getaddrinfo() вместоиз inet_pton().

Есть и другие проблемы с вашим кодом.

При использовании memset() вам нужно использовать целое число 0 вместо символа '0'. Это не одно и то же значение. Неиспользуемые sockaddr_in6 поля должны быть обнулены должным образом.

Ваша функция getadr() приводит к утечке памяти, и в целом она неэффективна. Используйте параметр hints getaddrinfo()чтобы ограничить результаты, которые он выводит, чтобы вам не пришлось охотиться за ними. И вам нужно освободить вывод с помощью freeaddrinfo(), когда вы закончите с его использованием.

isdigit() не является правильным способомчтобы отличить числовой IP от имени хоста. И кроме того, вы на самом деле не нужноd сделать это различие вручную, так как 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-адресов, не все из которых могут быть доступны с вашего компьютера. Вам следует перебирать весь список connect() на каждом 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;
}
0 голосов
/ 27 октября 2019

Рефакторированный код:

/*
 * 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)

Мне придется немного подумать над тем, что вы сказали, что не все действительные адреса действительныЯ не знал об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...