Разрешить и привязать к IPv6-адресу

Я написал программу, которая разрешает адрес или имя хоста, а затем пытается выполнить привязку к этому адресу в режиме listen(2). По какой-то странной причине я не могу использовать IPv6-адреса, программа просто завершается с ошибкой «Недопустимый аргумент» после вызова bind(2). С IPv4 все работает так, как я и ожидал, имена разрешаются, а адреса просто преобразуются в двоичный формат.

Источник

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>

static int
resolve_addr (const char *hostname, const char *service, int family, struct addrinfo **result)
{
    struct addrinfo addr_hint;

    memset (&addr_hint, 0, sizeof (struct addrinfo));

    addr_hint.ai_socktype = SOCK_STREAM;
    addr_hint.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_ADDRCONFIG;
    addr_hint.ai_family = family;

    return getaddrinfo (hostname, service, &addr_hint, result);
}

int
main (int argc, char *argv[])
{
    struct addrinfo *addr;
    int rval, sock, opt_val;

    rval = resolve_addr (argv[1], argv[2], AF_UNSPEC, &addr);

    if ( rval != 0 ){
        fprintf (stderr, "%s\n", gai_strerror (rval));
        return 1;
    }

    sock = socket (addr->ai_family, addr->ai_socktype | SOCK_NONBLOCK, addr->ai_protocol);

    if ( sock == -1 ){
        fprintf (stderr, "error socket: %s\n", strerror (errno));
        return 1;
    }

    if ( setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof (opt_val)) == -1 ){
        fprintf (stderr, "error setsockopt: %s\n", strerror (errno));
        return 1;
    }

    if ( bind (sock, (struct sockaddr*) addr->ai_addr, sizeof (struct sockaddr)) == -1 ){
        fprintf (stderr, "error bind: %s\n", strerror (errno));
        return 1;
    }

    if ( listen (sock, 32) == -1 ){
        fprintf (stderr, "error listen: %s\n", strerror (errno));
        return 1;
    }

    sleep (120);

    freeaddrinfo (addr);
    close (sock);

    return 0;
}

Использование

  1. ./test_bind localhost 8888 # works
  2. ./test_bind 0.0.0.0 8888 # works
  3. ./test_bind 127.0.0.1 8888 # works
  4. ./test_bind :: 8888 # does not work: Invalid argument
  5. ./test_bind ::1 8888 # does not work: Invalid argument

Примечание

Я использовал netcat, чтобы узнать, есть ли что-то не так с моей системой, но работает следующая команда:

ncat -l :: 8888

как видно в netstat:

tcp6 0 0 :::8888 :::* LISTEN


person antagon    schedule 24.11.2015    source источник
comment
Вы можете открыть исходный код netstat. :)   -  person someuser    schedule 24.11.2015
comment
Различные структуры sockaddr_* имеют разные размеры; используйте addr->ai_addrlen вместо sizeof(sockaddr).   -  person dave_thompson_085    schedule 24.11.2015
comment
@dave_thompson Спасибо, я пришел к такому же выводу. Смотрите мой ответ.   -  person antagon    schedule 24.11.2015


Ответы (1)


Я обновил свой код, и IPv4, и IPv6 успешно разрешены и привязаны. Проблема заключалась в третьем параметре функции bind(2), которая вычисляла размер адреса из размера структуры вместо использования значения из результата getaddrinfo.

Я заменил строку:

if ( bind (sock, (struct sockaddr*) addr->ai_addr, sizeof (struct sockaddr)) == -1 ){

с участием:

if ( bind (sock, (struct sockaddr*) addr->ai_addr, addr->ai_addrlen) == -1 ){

Полный исходный код

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <unistd.h>

static int
resolve_addr (const char *hostname, const char *service, int family, struct addrinfo **result)
{
    struct addrinfo addr_hint;

    memset (&addr_hint, 0, sizeof (struct addrinfo));

    addr_hint.ai_socktype = SOCK_STREAM;
    addr_hint.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_ADDRCONFIG;
    addr_hint.ai_family = family;

    return getaddrinfo (hostname, service, &addr_hint, result);
}


int
main (int argc, char *argv[])
{
    struct addrinfo *addr;
    int rval, sock, opt_val;

    rval = resolve_addr (argv[1], argv[2], AF_UNSPEC, &addr);

    if ( rval != 0 ){
        fprintf (stderr, "%s\n", gai_strerror (rval));
        return 1;
    }

    sock = socket (addr->ai_family, addr->ai_socktype | SOCK_NONBLOCK, addr->ai_protocol);

    if ( sock == -1 ){
        fprintf (stderr, "error socket: %s\n", strerror (errno));
        return 1;
    }

    opt_val = 1;

    if ( setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof (opt_val)) == -1 ){
        fprintf (stderr, "error setsockopt: %s\n", strerror (errno));
        return 1;
    }

    if ( bind (sock, (struct sockaddr*) addr->ai_addr, addr->ai_addrlen) == -1 ){
        fprintf (stderr, "error bind: %s\n", strerror (errno));
        return 1;
    }

    if ( listen (sock, 32) == -1 ){
        fprintf (stderr, "error listen: %s\n", strerror (errno));
        return 1;
    }

    sleep (120);

    freeaddrinfo (addr);
    close (sock);

    return 0;
}
person antagon    schedule 24.11.2015