📄 tcp.c
字号:
}
if( writeFDPtr != NULL )
{
FD_ZERO( writeFDPtr );
FD_SET( stream->netSocket, writeFDPtr );
}
FD_ZERO( &exceptfds );
FD_SET( stream->netSocket, &exceptfds );
tv.tv_sec = timeout;
tv.tv_usec = ( timeout <= 0 ) ? 5000 : 0;
/* See if we can perform the I/O */
status = select( stream->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( stream, errorInfo[ type ].status ) );
}
while( isSocketError( status ) && ( getTime() - startTime ) < timeout );
/* 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 ];
/* If we've already received data from a previous I/O, it counts as
the transferred byte count even though we timed out this time
round */
if( currentByteCount > 0 )
return( currentByteCount );
/* 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 */
if( timeout <= 0 )
return( OK_SPECIAL );
/* The select() timed out, exit */
sPrintf( errorMessage, "Timeout on %s (select()) after %d seconds",
errorInfo[ type ].errorString, timeout );
return( setSocketError( stream, errorMessage, 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( stream->netSocket, &readfds ) || \
FD_ISSET( stream->netSocket, &writefds ) ) )
{
assert( FD_ISSET( stream->netSocket, &exceptfds ) );
status = getSocketError( stream, CRYPT_ERROR_OPEN );
if( stream->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 */
stream->errorCode = TIMEOUT_ERROR;
mapError( stream, socketErrorInfo, CRYPT_UNUSED );
}
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. 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 it as an error */
if( FD_ISSET( stream->netSocket, &exceptfds ) )
{
status = getSocketError( stream, errorInfo[ type ].status );
if( stream->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 under Winsock under
certain circumstances, and seems to be related to another
app performing network I/O at the same time as we do the
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 */
stream->errorCode = TIMEOUT_ERROR;
mapError( stream, socketErrorInfo, CRYPT_UNUSED );
}
return( status );
}
/* The socket is read for reading or writing */
assert( status > 0 );
assert( ( type == IOWAIT_READ && \
FD_ISSET( stream->netSocket, &readfds ) ) || \
( type == IOWAIT_WRITE && \
FD_ISSET( stream->netSocket, &writefds ) ) || \
( type == IOWAIT_CONNECT && \
( FD_ISSET( stream->netSocket, &readfds ) || \
FD_ISSET( stream->netSocket, &writefds ) ) ) || \
( type == IOWAIT_ACCEPT ) );
return( CRYPT_OK );
}
/* Open a connection to a remote server/wait for a connection from a remote
client. The connection-open function performs that most amazing of all
things, the nonblocking connect. This is currently done in order to
allow a shorter timeout than the default fortnight or so but it also
allows for two-phase connects in which we start the connect operation,
perform further processing (e.g. signing and encrypting data prior to
sending it over the connected socket) and then complete the connect
before the first read or write. Currently we just use a wrapper that
performs the two back-to-back as a single operation, so it only functions
as a timeout-management mechanism */
static int preOpenSocket( STREAM *stream, const char *server,
const int serverPort )
{
SOCKET netSocket;
struct addrinfo *addrInfoPtr, *addrInfoCursor;
BOOLEAN nonBlockWarning = FALSE;
int port = serverPort, socketStatus, status;
/* Clear return value */
stream->netSocket = CRYPT_ERROR;
/* Set up addressing information */
status = getAddressInfo( stream, &addrInfoPtr, server, port, FALSE );
if( cryptStatusError( status ) )
return( status );
/* Create a socket, make it nonblocking, and start the connect to the
remote server, falling back through alternative addresses if the
connect fails. Since this is a nonblocking connect it could still
fail during the second phase where we can no longer try to recover
by falling back to an alternative address, but it's better than just
giving up after the first address we try */
for( addrInfoCursor = addrInfoPtr; addrInfoCursor != NULL;
addrInfoCursor = addrInfoCursor->ai_next )
{
status = newSocket( &netSocket, addrInfoCursor, FALSE );
if( cryptStatusError( status ) )
{
/* We need to get the socket error code now because further
calls to functions such as freeaddrinfo() will overwrite
the global error value before we can read it later on */
socketStatus = getErrorCode();
continue;
}
setSocketNonblocking( netSocket );
status = connect( netSocket, addrInfoCursor->ai_addr,
addrInfoCursor->ai_addrlen );
nonBlockWarning = isNonblockWarning();
if( status >= 0 || nonBlockWarning )
/* We've got a successfully-started connect, exit */
break;
socketStatus = getErrorCode(); /* Remember socket error code */
deleteSocket( netSocket );
}
freeAddressInfo( addrInfoPtr );
if( status < 0 && !nonBlockWarning )
{
/* There was an error condition other than a notification that the
operation hasn't completed yet */
status = mapError( stream, socketErrorInfo, CRYPT_ERROR_OPEN );
deleteSocket( netSocket );
return( status );
}
if( status == 0 )
{
/* If we're connecting to a local host, the connect can complete
immediately rather than returning an in-progress status, in
which case we don't need to do anything else */
stream->netSocket = netSocket;
return( CRYPT_OK );
}
/* The connect is in progress, mark the stream as not-quite-ready */
/* stream->xxx = yyy; */
stream->netSocket = netSocket;
return( CRYPT_OK );
}
static int completeOpen( STREAM *stream )
{
static const int trueValue = 1;
SIZE_TYPE intLength = sizeof( int );
int value, status;
/* Wait around until the connect completes. Some select()s limit the
size of the second count, so we set it to a maximum of 1 year's worth.
BeOS doesn't allow setting a timeout (that is, it doesn't allow
asynchronous connects), but it hardcodes in a timeout of about a
minute so we get a vaguely similar effect */
status = ioWait( stream, min( stream->timeout, 30000000L ), 0,
IOWAIT_CONNECT );
if( cryptStatusError( status ) )
{
stream->transportDisconnectFunction( stream, TRUE );
return( status );
}
/* The socket is readable or writeable, however this may be because of
an error (it's readable and writeable) or because everything's OK
(it's writeable) or because everything's OK and there's data waiting
(it's readable and writeable), so we have to see what the error
condition is for the socket to determine what's really happening.
This is a somewhat tricky area, other possibilities are calling recv()
with a length of zero bytes (returns an error if the connect failed),
calling connect() again (fails with EISCONN if the connect succeeded),
and calling getmsg( netSocket, NULL, NULL, &( flags = 0 ) ) (fails
with errno == EAGAIN or EWOULDBLOCK if the only error is that there's
nothing available yet) */
status = getsockopt( stream->netSocket, SOL_SOCKET, SO_ERROR,
( void * ) &value, &intLength );
if( status == 0 )
{
/* Berkeley-derived implementation, error is in value variable */
if( value != 0 )
{
status = mapError( stream, socketErrorInfo, CRYPT_ERROR_OPEN );
stream->transportDisconnectFunction( stream, TRUE );
return( status );
}
}
else
/* Slowaris, error is in errno */
if( isSocketError( status ) )
{
status = getSocketError( stream, CRYPT_ERROR_OPEN );
stream->transportDisconnectFunction( stream, TRUE );
return( status );
}
/* Turn off Nagle (since we do our own optimised TCP handling) and make
the socket blocking again. This is necessary because with a
nonblocking socket Winsock will occasionally return 0 bytes from
recv() (a sign that the receiver has closed the connection, see the
comment in readSocketFunction()) even though the connection is still
fully open, and in any case there's no real need for a nonblocking
socket since we have select() handling timeouts/blocking for us */
setsockopt( stream->netSocket, IPPROTO_TCP, TCP_NODELAY,
( void * ) &trueValue, sizeof( int ) );
setSocketBlocking( stream->netSocket );
/* We've completed the connection, mark the stream as ready for use */
/* stream->xxx = zzz; */
return( CRYPT_OK );
}
static int openServerSocket( STREAM *stream, const char *server, const int port )
{
SOCKET listenSocket, netSocket;
SOCKADDR_STORAGE clientAddr;
struct addrinfo *addrInfoPtr, *addrInfoCursor;
static const int trueValue = 1;
SIZE_TYPE clientAddrLen = sizeof( SOCKADDR_STORAGE );
int socketStatus, status;
/* Clear return value */
stream->netSocket = CRYPT_ERROR;
/* Set up addressing information. If we're not binding to a specified
interface, we allow connections on any interface. Note that, in
combination with SO_REUSEADDR and older, unpatched kernels, this
allows port hijacking by another process running on the same machine
that binds to the port with a more specific binding than "any" */
status = getAddressInfo( stream, &addrInfoPtr, server, port, TRUE );
if( cryptStatusError( status ) )
return( status );
/* Create a new server socket, falling back through alternative
interfaces if the initial socket creation fails. This may seem less
necessary than for the client-side connect, but is in fact required
because getaddrinfo() usually preferentially provides an IPv6
interface even if there's no IPv6 configured for the system (see the
long comment in getAddressInfo() for more on this), so we have to
step through until we get to an IPv4 interface, or at least one that
we can listen on */
for( addrInfoCursor = addrInfoPtr; addrInfoCursor != NULL;
addrInfoCursor = addrInfoCursor->ai_next )
{
status = newSocket( &listenSocket, addrInfoCursor, TRUE );
if( status == CRYPT_OK )
/* It's a second thread listening on an existing socket,
we're done */
break;
if( status != OK_SPECIAL )
{
/* There was a problem creating the socket, try again with
another interface. We need to get the socket error code now
because further calls to functions such as freeaddrinfo()
will overwrite the global error value before we can read it
later on */
socketStatus = getErrorCode();
continue;
}
status = CRYPT_OK;
/* This is a new socket, set SO_REUSEADDR to avoid TIME_WAIT
problems, and prepare to accept connections (nemo surdior est
quam is qui non audiet). Note that BeOS can only bind to one
interface at a time, so if we're binding to INADDR_ANY under
BeOS we actually bind to the first interface that we find */
if( setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR,
( char * ) &trueValue, sizeof( int ) ) || \
bind( listenSocket, addrInfoCursor->ai_addr,
addrInfoCursor->ai_addrlen ) || \
listen( listenSocket, 5 ) )
{
socketStatus = getErrorCode(); /* Remember socket error code */
deleteSocket( listenSocket );
newSocketDone();
continue;
}
/* We've finished initialising the socket, tell the socket pool
manager that it's safe to let others access the pool */
newSocketDone();
break;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -