首页 > 解决方案 > 绑定 INADDR_ANY:和 INADDR6_ANY_INIT:在 Linux 上监听失败,但在 macOS 上成功

问题描述

我正在编写一个服务器应用程序,我需要它来侦听运行它的主机的所有 IPv4 和 IPv6 地址上的连接。显而易见的事情是同时监听 INADDR_ANY 和 INADDR6_ANY_INIT。所以我相应地编写了我的代码,但我看到了奇怪的行为。

在 macOS(10.15.4 FWIW)上,如果我先绑定到 INADDR_ANY:然后(当然是在不同的套接字上)绑定到 INADDR6_ANY_INIT,一切都会正常工作。如果我颠倒绑定的顺序,那么第二个绑定将失败,并显示“地址已在使用中”。对于任何显式地址(即不是通配符地址),该代码可以很好地绑定到具有相同端口(当然是不同的套接字)的 IPv4 和 IPv6 地址。

在 Linux 上(我尝试了几种风格),无论顺序如何,第二次绑定总是失败,“地址已在使用”,因此我的“服务器”不可能按照我需要的方式工作。当然,必须可以这样做,因为这是一件很常见的事情,而且许多现有的事情就是这样做的(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

非常感谢任何和所有建议,见解,指针。

标签: socketsnetworkingbindlisten

解决方案


好的,事实证明,为了使其按预期工作,需要以下附加代码在调用 bind()之前在套接字上设置特定的 IPv6 相关选项:

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

这令人惊讶地难以找到,尽管一旦您知道要寻找什么,它当然很容易找到。


推荐阅读