sockets - 绑定 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
非常感谢任何和所有建议,见解,指针。
解决方案
好的,事实证明,为了使其按预期工作,需要以下附加代码在调用 bind()之前在套接字上设置特定的 IPv6 相关选项:
int one = 1;
...
setsockopt( ctxt->lsocks[sno], IPPROTO_IPV6, IPV6_V6ONLY, (void*) &one, sizeof(one));
这令人惊讶地难以找到,尽管一旦您知道要寻找什么,它当然很容易找到。
推荐阅读
- python - Python 没有检测到语法错误
- authorization - ALFA 中的常量(常量变量)
- javascript - 导入 {functionName} 在运行时构建的函数列表中未定义
- python-sphinx - Sphinx 中是否有任何 MathML 支持(通过 mathjax 扩展)?
- regex - perl 中的正则表达式无法按预期工作
- cassandra - 每分钟调用 DseCluster.init()/close() 有什么后果吗?
- javascript - 如何在 Webpack 中使用 p5js
- javascript - 什么 webpack4 加载器用于加载 *.svg 文件、*.gif、*.eot?
- statistica - Statistica VB - 包括一个外部宏
- javascript - 是否可以有多个 .then 函数?