I hope this is the correct place to report this.... I have been porting some programs to Linux and came across some problems with the networking layer, investigation discovered that the cause of this was recv blocking on a socket that should be non-blocking. More investigation showed that while the listening socket was non-blocking sockets formed from connections on that listening socket were _not_ non- blocking. Changing the connection accepting code to set the socket back to non-blocking fixed the problems. The TCP/IP documentation we have clearly states that the new socket should inherit this property from the listening socket that is forming it and this is what does happen on both the PC and Acorn.
Can you provide a simple test case for consideration? Thanks ...
The following function is used to open a socket, which is then turned into a listening socket. /****************************************************************************** * Function : tcpip_open_tcpsocket * * * * Description : Create tcp (stream) socket using specific port. * * Uses real port nums ie 1024 and 0 for any. * * * * Entry : * * ptrSocketHandle - Where the socket handle will be returned. * * iPortNum - The port number to be used (in network byte order). * * * * Exit : * * NO_ERROR - If sucessful. * * Error code if not. * * * ******************************************************************************/ int tcpip_open_tcpsocket( int *ptrSocketHandle, int iPortNum ) { #if (defined PC) || (defined VX_WORKS) sockad sockadHostAddr; #endif sockad sockadSocketAddr; int iSocketHandle; int iOptionValue; #if TCPIP_SOCKET_LIST SocketListStruct *ptrNewTCPSocket = NULL; #endif /* Check to make sure that passed parameters are okay. */ if( ptrSocketHandle == NULL ) ERR_QUIT( ERR_LVL_WARNING, "tcpip_open_tcpsocket: Invalid socket handle address pointer.", ERR_NULL_POINTER ); /* Create a new socket. AF_INET is currently only valid address format, type TCP, and with no specific protocol. */ if( ( iSocketHandle = socket( AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET ) ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not form socket (%s).", TCPIP_ERROR(), tcpip_get_error_description(TCPIP_ERROR()) ); /* Do not set option so new socket can have same addr/port as previous sockets. */ /* Bind port and allow rx from anybody. */ /* Get an initialised but blank sockad. */ if( tcpip_make_stn( &sockadSocketAddr, "", "" ) == NULL ) ERR_QUIT( ERR_LVL_WARNING, "tcpip_open_tcpsocket: Could not form host address.", ERR_TCPIP_ADDR ); #ifdef PC /* Get the host address. We want to bind the socket to the right address - especially if there is more than one. Thsi function will either return the user's explicit choice, or the first on the list. */ tcpip_host_addr( &sockadHostAddr ); /* Set up the socket address, with the chosen ip address, and the specified port. */ sockadSocketAddr.sin_addr = sockadHostAddr.sin_addr; #elif defined VX_WORKS sockadSocketAddr.sin_addr.s_addr = INADDR_ANY; #endif sockadSocketAddr.sin_port = (unsigned short) iPortNum; /* Bind the port to the desired sockad. */ if( bind( iSocketHandle, (struct sockaddr *) &sockadSocketAddr, sizeof (sockadSocketAddr) ) == SOCKET_ERROR ) { int iError = TCPIP_ERROR(); tcpip_socket_closedown( &iSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ if (iError != ERR_ADDRINUSE) ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not bind socket (%s).", iError, tcpip_get_error_description(iError) ); else return ERR_TCPIP_PORT_UNAVAILABLE; } /* Set socket i/o options concerning NON_BLOCKING. */ #ifdef TCPIP_NON_BLOCKING iOptionValue = 1; #ifdef VX_WORKS if( socketioctl( iSocketHandle, FIONBIO, (int) &iOptionValue ) == SOCKET_ERROR ) #elif defined UNIX if (fcntl( iSocketHandle, F_SETFL, fcntl(iSocketHandle, F_GETFL) | O_NONBLOCK) < 0) #else if( socketioctl( iSocketHandle, FIONBIO, (ULONG *) &iOptionValue ) == SOCKET_ERROR ) #endif { int iError = TCPIP_ERROR(); tcpip_socket_closedown( &iSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not set NON_BLOCKING mode (%s).", iError, tcpip_get_error_description(iError) ); } #endif /* Set socket i/o options concerning asynchronise data transfers. Enable socket events. */ #ifdef TCPIP_ASYNC iOptionValue = 1; if( socketioctl( iSocketHandle, FIOASYNC, &iOptionValue ) == SOCKET_ERROR ) { int iError = TCPIP_ERROR(); tcpip_socket_closedown( &iSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not set asynchronise mode (%s).", iError, tcpip_get_error_description(iError) ); } #endif /* Forces tcp to send small packets without collecting them together. */ #ifdef TCPIP_NODELAY iOptionValue = 1; if( setsockopt( iSocketHandle, IPPROTO_TCP, TCP_NODELAY, (char*) &iOptionValue, sizeof(iOptionValue) ) == SOCKET_ERROR ) { int iError = TCPIP_ERROR(); tcpip_socket_closedown( &iSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not set asynchronise mode (%s).", iError, tcpip_get_error_description(iError) ); } #endif /* Uses KEEP_ALIVE socket option. */ #ifdef TCPIP_KEEP_ALIVE iOptionValue = TRUE; if( setsockopt( iSocketHandle, SOL_SOCKET, SO_KEEPALIVE, (char*) &iOptionValue, sizeof(iOptionValue) ) == SOCKET_ERROR ) { int iError = TCPIP_ERROR(); tcpip_socket_closedown( &iSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_open_tcpsocket: Could not set KEEP_ALIVE socket option (%s).", iError, tcpip_get_error_description(iError) ); } #endif /* Return handle to user. */ *ptrSocketHandle = iSocketHandle; #if TCPIP_SOCKET_LIST /* If all has gone well, then form a new socket list. */ ptrNewTCPSocket = heap_malloc( ptrOpenSocketsHeap ); /* Fill the socket list structure. */ tcpip_socket_type(ptrNewTCPSocket) = TCPIP_TCP; tcpip_socket_handle(ptrNewTCPSocket) = iSocketHandle; time( tcpip_socket_time(ptrNewTCPSocket) ); /* Add new the socket list to the "currently open sockets" linked list. */ list_addtail( &OpenSocketsList, tcpip_socket_list(ptrNewTCPSocket) ); #endif return NO_ERROR; } /* End of tcpip_open_tcpsocket. */ When a connection is recieved on the listening socket it is processed by the following function: /********************************************************************* * Function : tcpip_poll_listen * * * * Description : Poll listening tcp socket for pending connections. * * * * Entry : * * iSocketHandle - Handle supplied by successful tcpip_open.. * * ptrNewSocketHandle - Where handle to any new connection will be * * stored. * * Will contain the handle to the new socket. * * If NO_ERROR but blocking occurred, then * * INVALID_SOCKET will be returned. * * ptrSourceAddr - Where the address of the sending machine will * * be stored. * * If NULL then use udp, otherwise tcp. * * * * Exit : * * NO_ERROR - If sucessful. * * Error code if not. * * * *********************************************************************/ int tcpip_poll_listen( int iSocketHandle, int *ptrNewSocketHandle, sockad *ptrSourceAddr ) { #ifdef TCPIP_NON_BLOCKING int iOptionValue; #endif int iAddrLen = sizeof(sockad); #if TCPIP_SOCKET_LIST SocketListStruct *ptrNewTCPSocket = NULL; #endif *ptrNewSocketHandle = INVALID_SOCKET; /* Set handle to INVALID_SOCKET in case of error. */ /* Check to make sure that passed parameters are okay. */ if( ptrSourceAddr == NULL ) ERR_QUIT( ERR_LVL_WARNING, "tcpip_poll_listen: Invalid source address pointer.", ERR_NULL_POINTER ); /* Accept any pending connections. */ if( ( *ptrNewSocketHandle = accept( iSocketHandle, (struct sockaddr *) ptrSourceAddr, &iAddrLen ) ) == INVALID_SOCKET ) { int iError = TCPIP_ERROR(); /* If a blocking error, then ignore. */ if( iError != ERR_WOULDBLOCK ) ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_poll_listen: Could not form new socket (%s).", TCPIP_ERROR(), tcpip_get_error_description(TCPIP_ERROR()) ); } /* Add the new socket to the socket list. */ #if TCPIP_SOCKET_LIST /* If all has gone well, then form a new socket list. */ ptrNewTCPSocket = heap_malloc( ptrOpenSocketsHeap ); /* Fill the socket list structure. */ tcpip_socket_type(ptrNewTCPSocket) = TCPIP_UDP; tcpip_socket_handle(ptrNewTCPSocket) = iSocketHandle; time( tcpip_socket_time(ptrNewTCPSocket) ); /* Add new the socket list to the "currently open sockets" linked list. */ list_addtail( &OpenSocketsList, tcpip_socket_list(ptrNewTCPSocket) ); #endif /**************This code should not be required*****************/ /* Set socket i/o options concerning NON_BLOCKING. */ #ifdef TCPIP_NON_BLOCKING iOptionValue = 1; #ifdef VX_WORKS if( socketioctl( iSocketHandle, FIONBIO, (int) &iOptionValue ) == SOCKET_ERROR ) #elif defined UNIX if (fcntl( *ptrNewSocketHandle, F_SETFL, fcntl(*ptrNewSocketHandle, F_GETFL) | O_NONBLOCK) < 0) #else if( socketioctl( iSocketHandle, FIONBIO, (ULONG *) &iOptionValue ) == SOCKET_ERROR ) #endif /**************End code that should not be needed*****************/ { int iError = TCPIP_ERROR(); tcpip_socket_closedown( ptrNewSocketHandle, TCPIP_ABORT_ALL ); /* Close socket before quitting. */ ERR_QUIT_1( ERR_LVL_SERIOUS, "tcpip_poll_listen: Could not set NON_BLOCKING mode (%s).", iError, tcpip_get_error_description(iError) ); } #endif return NO_ERROR; } /* End of tcpip_poll_listen. */ This function, as you can see, has to set the non-blocking option for the new socket - however the TCP/IP documentation says that the new socket should inherit all such properties from the listening socket that forms it. As a precaution the non-blocking option is now set for all platforms, however it took quite a while to track down the problem and could well trip other people up as well. Thanks, Tim
Here 's what the kernel folks have to say: From: Alan Cox <alan> Date: Sat, 30 Dec 2000 20:28:28 -0500 (EST) > The TCP/IP documentation we have clearly states that the new > socket should inherit this property from the listening socket > that is forming it and this is what does happen on both the PC > and Acorn. I can't find that in my copy of the POSIX 1003.1g draft. I can't find it in my copy of the XNS docs. I've never seen a standards body cite for it. If he has one then he may well be right but that'll break some of our userspace Also, this gets the usual standard disclaimer: Even if I were to change this behavior to what you expect today, you would be ill advised to assume it in your applications since this would make your application not work with every other existing version of the Linus kernel. This behavior isn't changing till at least 2.5.x for this and other reasons (RedHat's inetd is the main one, it breaks when accept'd sockets behave as this bugzilla reporter expects it to :-(, it basically assumes Linux non-block propagation to accept() socket file-descriptors) Later, David S. Miller davem Changing component to kernel ...