Я пишу серверное приложение, и мне нужно, чтобы оно прослушивало соединения по всем адресам 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
Любые предложения, идеи, указатели очень ценятся.