📄 tcp.c
字号:
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 + -