Привязка INADDR_ANY: <port>и INADDR6_ANY_INIT: <port>для прослушивания завершается с ошибкой на Linux, но завершается успешно на macOS - PullRequest
0 голосов
/ 03 апреля 2020

Я пишу серверное приложение, и мне нужно, чтобы оно прослушивало соединения по всем адресам IPv4 и IPv6 для хоста, на котором оно работает. Очевидная вещь, которую нужно сделать, это прослушать INADDR_ANY и INADDR6_ANY_INIT. Поэтому я написал свой код соответствующим образом, но я вижу странное поведение.

В macOS (10.15.4 FWIW) все работает отлично, если я сначала привязываюсь к INADDR_ANY: а затем впоследствии (конечно, на другом сокете) к INADDR6_ANY_INIT. Если я переверну порядок привязок, то вторая привязка завершится неудачно с «Адрес уже используется». Код прекрасно связывается как с адресами IPv4, так и с адресами IPv6 с одним и тем же портом (конечно, с разными сокетами) для любого явного адреса (т. Е. Не подстановочного адреса).

Вкл. Linux (я пробовал несколько flavors) второе связывание всегда завершается неудачей с «Адресом уже используется» независимо от порядка, поэтому мой «сервер» не может работать так, как мне нужно. Конечно, это должно быть возможно, так как это обычная вещь, и многие существующие вещи делают именно это (sshd - только один пример).

Я перевел проблему в функциональную примерную программу, но длина его составляет 434 строки, поэтому, вероятно, слишком длинна для публикации здесь, но любой желающий может скачать ее отсюда:

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

static void
usage(
    void
     )
{
    printf(
    "\nUsage:\n\n"
    "    nwbug <port> [ <hostname> | <ipaddress> ]\n\n"
          );
    exit( 100 );
} // usage

/*
 * Print an IPv4 address.
 */

void
printIPv4address(
    FILE *f,
    struct sockaddr *addr4,
    int full
                )
{
    unsigned char * addr;
    int adbyte, port,  i;

    if (  (f != NULL) && (addr4 != NULL)  )
    {
        addr = (unsigned char *)&(addr4->sa_data[2]);
        for (i=0; i<3; i++)
        {
            adbyte = (int)*addr++;
            fprintf( f, "%d.", adbyte );
        }
        adbyte = (int)*addr++;
        fprintf( f, "%d", adbyte );
        if (  full )
        {
            port = (int)ntohs( *((uint16_t *)&(addr4->sa_data[0])) );
            if (  port  )
                fprintf( f, ":%d", port );
        }
    }
} // printIPv4address

/*
 * Print an IPv6 address.
 */

void
printIPv6address(
    FILE *f,
    struct sockaddr *addr6,
    int full
                )
{
    int advalue, port = 0, i;
    int zrl = 0, zrlm = 0, zrls = -1, zrle = -1;
    unsigned char * addr;
    unsigned char * p;
    char colon[2];

    if (  (f != NULL) && (addr6 != NULL)  )
    {
        addr = (unsigned char *)&(addr6->sa_data[6]);
        p = addr + 15;
        if (  full )
            port = (int)ntohs( *((uint16_t *)&(addr6->sa_data[0])) );
        if (  port  )
            fprintf( f, "[" );
        for (i=7; i>=0; i--)
        {
            advalue = (int)*p--;
            advalue += (256 * (int)*p--);
            if (  advalue == 0  )
                zrl++;
            else
            {
                if (  zrl  )
                {
                    if (  (zrl > 1) && (zrl >= zrlm)  )
                    {
                        zrls = i + 1;
                        zrlm = zrl;
                    }
                    zrl = 0;
                }
            }
        }
        if (  zrl  )
        {
            if (  (zrl > 1) && (zrl >= zrlm)  )
            {
                zrls = i + 1;
                zrlm = zrl;
            }
            zrl = 0;
        }
        if (  zrlm  )
        {
            zrle = zrls + zrlm - 1;
            strcpy(colon,":");
        }
        for (i=0; i<7; i++)
        {
            advalue = (256 * (int)*addr++);
            advalue += (int)*addr++;
            if ( ! zrlm )
                fprintf( f, "%x:", advalue );
            else
            if ( advalue || (i < zrls) || (i > zrle) )
                fprintf( f, "%x:", advalue );
            else
            {
                fprintf( f, "%s", colon );
                if ( i )
                    colon[0] = '\0';
            }
        }
        advalue = (256 * (int)*addr++);
        advalue += (int)*addr++;
        if ( ! zrlm )
            fprintf( f, "%x", advalue );
        else
        if ( advalue || (i < zrls) || (i > zrle) )
            fprintf( f, "%x", advalue );
        else
            fprintf( f, "%s", colon );
        if (  port  )
            fprintf( f, "]:%d", port );
    }
} // printIPv6address

/*
 * Print an IPv4 or an IPv6 address.
 */

void
printIPaddress(
    FILE *f,
    struct sockaddr *addr,
    socklen_t laddr,
    int full
              )
{
    if (  (f != NULL) && (addr != NULL)  )
        switch ( laddr  )
        {
            case sizeof( struct sockaddr_in ):
                printIPv4address( f, addr, full );
                break;
            case sizeof( struct sockaddr_in6 ):
                printIPv6address( f, addr, full );
                break;
            default:
                fprintf( f, "<invalid>" );
                break;
        }
} // printIPAddress

/*
 * Convert a hostname and/or a service name to a list of
 * address structures that can be used either for listen()
 * or connect().
 */

int
hostToAddr(
           char * hostname,
           char * servname,
           int    listen,
           struct addrinfo ** addr
          )
{
    struct addrinfo * haddr = NULL, * caddr = NULL, gaihints;
    int ret = -1;

    if (  ( addr == NULL)  || 
          ( ( hostname == NULL ) && ( servname == NULL) ) ||
          ( ( hostname == NULL ) && ! listen )  )
    {
        fprintf( stderr, "error: invalid parameters passed to hostToAddr()\n" );
        return ret;
    }

    *addr = NULL;

    memset( (void *)&gaihints, 0, sizeof(struct addrinfo) );
    gaihints.ai_family = PF_UNSPEC;
    gaihints.ai_protocol = IPPROTO_TCP;
    gaihints.ai_flags = AI_ADDRCONFIG;
    if (  listen  )
        gaihints.ai_flags |= AI_PASSIVE;

    ret = getaddrinfo( hostname, servname, &gaihints, addr );
    if (  ret  )
    {
        fprintf( stderr, "error: getaddrinfo() returned %d\n", ret );
        return ret;
    }

    return ret;
} // hostToAddr

int
main(
    int    argc,
    char * argv[]
    )
{
    struct addrinfo * addr = NULL;
    struct addrinfo * taddr = NULL;
    char            * host = NULL;
    char            * port = NULL;
    int               naddr = 0;
    int               sind = 0;
    int               ret = 0;
    int             * sock = NULL;

    if (  (argc < 2) || (argc > 3)  )
        usage();
    port = argv[1];
    if (  argc > 2  )
        host = argv[2];

    if (  hostToAddr( host, port, 1, &addr )  )
        return( 1 );

    taddr = addr;
    while (  taddr != NULL  )
    {
        if (  taddr->ai_family == PF_INET  )
        {
            printf( "info: address %d is '", naddr );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );
        }
        else
        if (  taddr->ai_family == PF_INET6  )
        {
            printf( "info: address %d is '", naddr );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );
        }
        else
            printf( "warning: unexpected protocol family\n" );
        naddr += 1;
        taddr = taddr->ai_next;
    }

    sock = (int *)calloc( naddr, sizeof( int ) );
    if (  sock == NULL  )
    {
        fprintf( stderr, "error: unable to allocate memory for socket array\n" );
        return 2;
    }

#if 1
    sind = 0;
    for ( taddr = addr; taddr != NULL; taddr = taddr->ai_next )
    {
        if (  taddr->ai_family == PF_INET6  )
        {
            printf( "info: binding '" );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );

            errno = 0;
            sock[sind] = socket( taddr->ai_family, taddr->ai_socktype, taddr->ai_protocol );
            if (  sock[sind] < 0  )
            {
                fprintf( stderr, "error: socket() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 3;
                continue;
            }
            errno = 0;
            if (  bind( sock[sind], taddr->ai_addr, taddr->ai_addrlen )  )
            {
                fprintf( stderr, "error: bind() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 4;
                continue;
            }
            errno = 0;
            if (  listen( sock[sind], 5 )  )
            {
                fprintf( stderr, "error: listen() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 5;
                continue;
            }
            sind++;
        }
    }
    for ( taddr = addr; taddr != NULL; taddr = taddr->ai_next )
    {
        if (  taddr->ai_family == PF_INET  )
        {
            printf( "info: binding '" );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );

            errno = 0;
            sock[sind] = socket( taddr->ai_family, taddr->ai_socktype, taddr->ai_protocol );
            if (  sock[sind] < 0  )
            {
                fprintf( stderr, "error: socket() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 3;
                continue;
            }
            errno = 0;
            if (  bind( sock[sind], taddr->ai_addr, taddr->ai_addrlen )  )
            {
                fprintf( stderr, "error: bind() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 4;
                continue;
            }
            errno = 0;
            if (  listen( sock[sind], 5 )  )
            {
                fprintf( stderr, "error: listen() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 5;
                continue;
            }
            sind++;
        }
    }
#else
    sind = 0;
    for ( taddr = addr; taddr != NULL; taddr = taddr->ai_next )
    {
        if (  taddr->ai_family == PF_INET  )
        {
            printf( "info: binding '" );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );

            errno = 0;
            sock[sind] = socket( taddr->ai_family, taddr->ai_socktype, taddr->ai_protocol );
            if (  sock[sind] < 0  )
            {
                fprintf( stderr, "error: socket() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 3;
                continue;
            }
            errno = 0;
            if (  bind( sock[sind], taddr->ai_addr, taddr->ai_addrlen )  )
            {
                fprintf( stderr, "error: bind() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 4;
                continue;
            }
            errno = 0;
            if (  listen( sock[sind], 5 )  )
            {
                fprintf( stderr, "error: listen() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 5;
                continue;
            }
            sind++;
        }
    }
    for ( taddr = addr; taddr != NULL; taddr = taddr->ai_next )
    {
        if (  taddr->ai_family == PF_INET6  )
        {
            printf( "info: binding '" );
            printIPaddress( stdout, taddr->ai_addr, taddr->ai_addrlen, 1 );
            printf( "'\n" );

            errno = 0;
            sock[sind] = socket( taddr->ai_family, taddr->ai_socktype, taddr->ai_protocol );
            if (  sock[sind] < 0  )
            {
                fprintf( stderr, "error: socket() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 3;
                continue;
            }
            errno = 0;
            if (  bind( sock[sind], taddr->ai_addr, taddr->ai_addrlen )  )
            {
                fprintf( stderr, "error: bind() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 4;
                continue;
            }
            if (  listen( sock[sind], 5 )  )
            {
                fprintf( stderr, "error: listen() failed for '" );
                printIPaddress( stderr, taddr->ai_addr, taddr->ai_addrlen, 1 );
                fprintf( stderr, "' - %d (%s)\n", errno, strerror( errno ) );
                ret = 5;
                continue;
            }
            sind++;
        }
    }
#endif

    if (  ret == 0  )
        printf( "info: success\n" );

    sleep( 60 );

    return ret;
} // main

Любые предложения, идеи, указатели очень ценятся.

1 Ответ

0 голосов
/ 04 апреля 2020

Хорошо, получается, что для того, чтобы это работало должным образом, необходим следующий дополнительный код для установки специфичной c связанной с IPv6 опции для сокета перед вызовом bind ():

int one = 1;
...
setsockopt( ctxt->lsocks[sno], IPPROTO_IPV6, IPV6_V6ONLY, (void*) &one,     sizeof(one));

Это было на удивление трудно найти, хотя, когда вы знаете, что искать, его, конечно, легко найти.

...