绑定INADDR_ANY: 和INADDR6_ANY_INIT: 在Linux上侦听失败,但在macOS上成功侦听

问题描述 投票:0回答:1

我正在编写服务器应用程序,我需要它来监听运行它的主机的所有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

非常感谢任何建议,见解和建议。

我正在编写服务器应用程序,我需要它来监听运行它的主机的所有IPv4和IPv6地址上的连接。显而易见的事情是同时监听INADDR_ANY和...

sockets networking bind listen
1个回答
0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.