我正在编写服务器应用程序,我需要它来监听运行它的主机的所有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和...
好,事实证明,要使其按预期工作,需要以下附加代码在套接字上设置特定的IPv6相关选项之前