⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tcp.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 5 页
字号:
		if( isSocketError( status ) )
			{
			/* There was a problem closing the socket, mark it as not-
			   present for matching purposes but keep its entry active so
			   that we'll periodically try and close it when we search the
			   socket pool for these slots, and again when we close down */
			socketInfo[ i ].iChecksum = 0;
			memset( socketInfo[ i ].iData, 0,
					sizeof( socketInfo[ i ].iData ) );
			socketInfo[ i ].iDataLen = 0;

			assert( DEBUG_WARN );
			}
		else
			socketInfo[ i ] = SOCKET_INFO_TEMPLATE;
		}

	krnlExitMutex( MUTEX_SOCKETPOOL );
	}

/* Force all objects waiting on sockets to exit by closing their sockets.
   This is the only portable and reliable way to cause them to terminate 
   since an object waiting on a socket is marked as busy by the cryptlib 
   kernel, and in fact will be blocked inside the OS out of reach of even 
   the cryptlib kernel.  Alternatively, the user can provide their own 
   socket externally and close it from the outside, which will unblock the 
   thread waiting on it.

   A somewhat less drastic alternative to closing the socket is to use
   shutdown(), but the behaviour of this is somewhat implementation-specific.
   For example under Slowaris 5.x trying to shutdown a listening socket (to
   unlock a thread blocking in accept()) returns ENOTCONN, so the shutdown
   requires setting up a dummy connection to the socket to be shut down
   before it can actually be shut down.  Trying to shut down a thread blocked
   in connect() is more or less impossible under Slowaris 5.x.  Other systems
   are more flexible, but there's not enough consistency to rely on this */

void netSignalShutdown( void )
	{
	int i, status;

	/* Exactly what to do if we can't acquire the mutex is a bit complicated
	   because at this point our primary goal is to force all objects to exit 
	   rather than worrying about socket-pool consistency.  On the other
	   hand if another object is currently in the middle of cleaning up and
	   is holding the socket pool mutex we don't want to stomp on it while 
	   it's doing its cleanup.  Since failing to acquire the mutex is a 
	   special-case exception condition, it's not even possible to plan for
	   this since it's uncertain under which conditions (if ever) this 
	   situation would occur.  For now we play it by the book and don't do
	   anything if we can't acquire the mutex, which is at least 
	   consistent */
	status = krnlEnterMutex( MUTEX_SOCKETPOOL );
	if( cryptStatusError( status ) )
		retIntError_Void();

	/* For each open socket, close it and set its reference count to zero */
	for( i = 0; i < SOCKETPOOL_SIZE; i++ )
		{
		if( !isBadSocket( socketInfo[ i ].netSocket ) )
			{
			closesocket( socketInfo[ i ].netSocket );
			socketInfo[ i ] = SOCKET_INFO_TEMPLATE;
			}
		}

	krnlExitMutex( MUTEX_SOCKETPOOL );
	}

/****************************************************************************
*																			*
*							Network Socket Interface						*
*																			*
****************************************************************************/

/* Wait for I/O to become possible on a socket.  The particular use of 
   select that we employ here is reasonably optimal under load because we're 
   only asking select() to monitor a single descriptor.  There are a variety 
   of inefficiencies related to select that fall into either the category of 
   user <-> kernel copying or of descriptor list scanning.  For the first 
   category, when calling select() the system has to copy an entire list of 
   descriptors into kernel space and then back out again.  Large selects can 
   potentially contain hundreds or thousands of descriptors, which can in 
   turn involve allocating memory in the kernel and freeing it on return.  
   We're only using one so the amount of data to copy is minimal.

   The second category involves scanning the descriptor list, an O(n) 
   activity.  First the kernel has to scan the list to see whether there's 
   pending activity on a descriptor.  If there aren't any descriptors with 
   activity pending it has to update the descriptor's selinfo entry in the 
   event that the calling process calls tsleep() (used to handle event-based 
   process blocking in the kernel) while waiting for activity on the 
   descriptor.  After the select() returns or the process is woken up from a 
   tsleep() the user process in turn has to scan the list to see which 
   descriptors the kernel indicated as needing attention.  As a result, the 
   list has to be scanned three times.

   These problems arise because select() (and it's cousin poll()) are 
   stateless by design so everything has to be recalculated on each call.  
   After various false starts the kqueue interface is now seen as the best 
   solution to this problem.  However cryptlib's use of only a single 
   descriptor per select() avoids the need to use system-specific and rather 
   non-portable interfaces like kqueue (and earlier alternatives like Sun's 
   /dev/poll, FreeBSD's get_next_event(), and SGI's /dev/imon) */

typedef enum { 
	IOWAIT_NONE,			/* No I/O wait type */
	IOWAIT_READ,			/* Wait for read availability */
	IOWAIT_WRITE,			/* Wait for write availability */
	IOWAIT_CONNECT,			/* Wait for connect to complete */
	IOWAIT_ACCEPT,			/* Wait for accept to complete */
	IOWAIT_LAST				/* Last possible wait type */
	} IOWAIT_TYPE;

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int ioWait( INOUT NET_STREAM_INFO *netStream, 
				   IN_INT_Z const int timeout,
				   const BOOLEAN previousDataRead, 
				   IN_ENUM( IOWAIT ) const IOWAIT_TYPE type )
	{
	static const struct {
		const int status;
		const char *errorString;
		} errorInfo[] = {
		{ CRYPT_ERROR_OPEN, "unknown" },
		{ CRYPT_ERROR_READ, "read" },		/* IOWAIT_READ */
		{ CRYPT_ERROR_WRITE, "write" },		/* IOWAIT_WRITE */
		{ CRYPT_ERROR_OPEN, "connect" },	/* IOWAIT_CONNECT */
		{ CRYPT_ERROR_OPEN, "accept" },		/* IOWAIT_ACCEPT */
		{ CRYPT_ERROR_OPEN, "unknown" }, { CRYPT_ERROR_OPEN, "unknown" }
		};
	MONOTIMER_INFO timerInfo;
	struct timeval tv;
	fd_set readfds, writefds, exceptfds;
	fd_set *readFDPtr = ( type == IOWAIT_READ || \
						  type == IOWAIT_CONNECT || \
						  type == IOWAIT_ACCEPT ) ? &readfds : NULL;
	fd_set *writeFDPtr = ( type == IOWAIT_WRITE || \
						   type == IOWAIT_CONNECT ) ? &writefds : NULL;
	int selectIterations = 0, status;

	assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );

	REQUIRES( timeout >= 0 && timeout < MAX_INTLENGTH );
	REQUIRES( type > IOWAIT_NONE && type < IOWAIT_LAST );

	/* Set up the information needed to handle timeouts and wait on the
	   socket.  If there's no timeout, we wait at least 5ms on the theory
	   that it isn't noticeable to the caller but ensures that we at least
	   get a chance to get anything that may be pending.

	   The exact wait time depends on the system, but usually it's quantised
	   to the system timer quantum.  This means that on Unix systems with a
	   1ms timer resolution the wait time is quantised on a 1ms boundary.
	   Under Windows NT/2000/XP/Vista it's quantised on a 10ms boundary 
	   (some early NT systems had a granularity ranging from 7.5 - 15ms but 
	   all newer systems use 10ms) and for Win95/98/ME it's quantised on a 
	   55ms boundary.  In other words when performing a select() on a Win95 
	   box it'll either return immediately or wait some multiple of 55ms 
	   even with the time set to 1ms.

	   In theory we shouldn't have to reset either the fds or the timeval
	   each time through the loop since we're only waiting on one descriptor
	   so it's always set and the timeval is a const, however some versions
	   of Linux can update it if the select fails due to an EINTR (which is
	   the exact reason why we'd be going through the loop a second time in
	   the first place) and/or if a file descriptor changes status (e.g. due 
	   to data becoming available) so we have to reset it each time to be on 
	   the safe side.  It would actually be nice if the tv value were 
	   updated reliably to reflect how long the select() had to wait since 
	   it'd provide a nice source of entropy for the randomness pool (we 
	   could simulate this by readig a high-res timer before and after the
	   select() but that would adds a pile of highly system-dependent code
	   and defeat the intent of making use of using the "free" entropy 
	   that's provided as a side-effect of the select().

	   The wait on connect is a slightly special case, the socket will
	   become writeable if the connect succeeds normally, but both readable
	   and writeable if there's an error on the socket or if there's data
	   already waiting on the connection (i.e. it arrives as part of the
	   connect).  It's up to the caller to check for these conditions */
	status = setMonoTimer( &timerInfo, timeout );
	if( cryptStatusError( status ) )
		return( status );
	do
		{
		if( readFDPtr != NULL )
			{
			FD_ZERO( readFDPtr );
			FD_SET( netStream->netSocket, readFDPtr );
			}
		if( writeFDPtr != NULL )
			{
			FD_ZERO( writeFDPtr );
			FD_SET( netStream->netSocket, writeFDPtr );
			}
		FD_ZERO( &exceptfds );
		FD_SET( netStream->netSocket, &exceptfds );
		tv.tv_sec = timeout;
		tv.tv_usec = ( timeout <= 0 ) ? 5000 : 0;

		/* See if we can perform the I/O */
		status = select( netStream->netSocket + 1, readFDPtr, writeFDPtr,
						 &exceptfds, &tv );

		/* If there's a problem and it's not something transient like an
		   interrupted system call, exit.  For a transient problem, we just
		   retry the select until the overall timeout expires */
		if( isSocketError( status ) && !isRestartableError() )
			return( getSocketError( netStream, errorInfo[ type ].status ) );
		}
	while( isSocketError( status ) && \
		   !checkMonoTimerExpired( &timerInfo ) && \
		   selectIterations++ < FAILSAFE_ITERATIONS_MED );
	if( selectIterations > FAILSAFE_ITERATIONS_MED )
		{
		char errorMessage[ 128 + 8 ];
		int errorMessageLength;

		/* We've gone through the select loop a suspiciously large number
		   of times, there's something wrong.  In theory we could report 
		   this as a more serious error than a simple timeout since it means
		   that there's either a bug in our code or a bug in the select()
		   implementation, but without knowing in advance what caused this
		   can't-occur condition it's difficult to anticipate the correct
		   action to take, so all that we do is warn in the debug build */
		assert( DEBUG_WARN );
		errorMessageLength = sprintf_s( errorMessage, 128,
										"select() on %s went through %d "
										"iterations without returning a "
										"result",
										errorInfo[ type ].errorString, 
										timeout );
		return( setSocketError( netStream, errorMessage, errorMessageLength,
								CRYPT_ERROR_TIMEOUT, FALSE ) );
		}

	/* If the wait timed out, either explicitly in the select (status == 0)
	   or implicitly in the wait loop (isSocketError()), report it as a
	   select() timeout error */
	if( status == 0 || isSocketError( status ) )
		{
		char errorMessage[ 128 + 8 ];
		int errorMessageLength;

		/* If we've already received data from a previous I/O, tell the 
		   caller to use that as the transferred byte count even though we 
		   timed out this time round */
		if( previousDataRead )
			return( OK_SPECIAL );

		/* If it's a nonblocking wait (usually used as a poll to determine
		   whether I/O is possible) then a timeout isn't an error (this can
		   be distinguished from the previous OK_SPECIAL return by whether
		   previousDataRead is set or not) */
		if( timeout <= 0 )
			return( OK_SPECIAL );

		/* The select() timed out, exit */
		errorMessageLength = sprintf_s( errorMessage, 128,
										"Timeout on %s (select()) after %d "
										"seconds",
										errorInfo[ type ].errorString, 
										timeout );
		return( setSocketError( netStream, errorMessage, errorMessageLength,
								CRYPT_ERROR_TIMEOUT, FALSE ) );
		}

#if 0	/* 12/6/04 Shouldn't be necessary any more since to get to this
		   point the socket has to be either readable or writeable or
		   subject to an exception condition, which is handled below */
	/* If we encountered an error condition on a connect (the socket is
	   neither readable nor writeable), exit */
	if( ( type == IOWAIT_CONNECT ) && \
		!( FD_ISSET( netStream->netSocket, &readfds ) || \
		   FD_ISSET( netStream->netSocket, &writefds ) ) )
		{
		REQUIRES_S( FD_ISSET( netStream->netSocket, &exceptfds ) );

		status = getSocketError( stream, CRYPT_ERROR_OPEN );
		if( netStream->errorCode == 0 )
			{
			/* Some implementations don't treat a soft timeout as an error,
			   and at least one (Tandem) returns EINPROGRESS rather than
			   ETIMEDOUT, so we insert a timeout error code ourselves */
			netStream->errorCode = TIMEOUT_ERROR;
			mapError( stream, FALSE, CRYPT_ERROR );
			}
		return( status );
		}
#endif /* 0 */

	/* If there's an exception condition on a socket, exit.  This is
	   implementation-specific, traditionally under Unix this only indicates
	   the arrival of out-of-band data rather than any real error condition,
	   but in some cases it can be used to signal errors.  In these cases we
	   have to explicitly check for an exception condition because some
	   types of errors will result in select() timing out waiting for
	   readability rather than indicating an error and returning.  In 
	   addition for OOB data we could just ignore the notification (which 
	   happens automatically with the default setting of SO_OOBINLINE = 
	   false and a socket owner to receive SIGURG's not set, the OOB data 
	   byte just languishes in a side-buffer), however we shouldn't be 
	   receiving OOB data so we treat that as an error too */
	if( FD_ISSET( netStream->netSocket, &exceptfds ) )
		{
		status = getSocketError( netStream, errorInfo[ type ].status );
		if( netStream->errorInfo.errorCode == 0 )
			{
			/* If there's a (supposed) exception condition present but no
			   error information available then this may be a mis-handled
			   select() timeout.  This can happen with Winsock under
			   certain circumstances and seems to be related to another
			   socket-using application performing network I/O at the same 
			   time as we do the select() wait.  Non-Winsock cases can occur 
			   because some implementations don't treat a soft timeout as an 
			   error, and at least one (Tandem) returns EINPROGRESS rather 
			   than ETIMEDOUT, so we insert a timeout error code ourselves.
			   Since we're merely updating the extended internal error 
			   information (we already know what the actual error status
			   is) we don't need to do anything with the mapError() return 
			   value */
			netStream->errorInfo.errorCode = TIMEOUT_ERROR;
			( void ) mapError( netStream, FALSE, CRYPT_ERROR_TIMEOUT );
			}
		return( status );
		}

	/* The socket is read for reading or writing */
	ENSURES( status > 0 );
	ENSURES( ( type == IOWAIT_READ && \
			   FD_ISSET( netStream->netSocket, &readfds ) ) || \
			 ( type == IOWAIT_WRITE && \
			   FD_ISSET( netStream->netSocket, &writefds ) ) || \
			 ( type == IOWAIT_CONNECT && \
			   ( FD_ISSET( netStream->netSocket, &readfds ) || \
				 FD_ISSET( netStream->netSocket, &writefds ) ) ) || \

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -