📄 tcp.c
字号:
( type == IOWAIT_ACCEPT ) );
return( CRYPT_OK );
}
/* Open a connection to a remote server or 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 for now it only
functions as a timeout-management mechanism - the high-level API for
this would be a bit difficult to handle since there's no readily-
available facility for handling an interruptible sNetConnect(), the best
option would be to handle it via a complete-connect IOCTL. However since
we've got at least a little time to play with in most cases we could at
least perform a quick entropy poll in the idle interval, if nothing
else */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int preOpenSocket( INOUT NET_STREAM_INFO *netStream,
IN_BUFFER( hostNameLen ) const char *host,
IN_LENGTH_DNS const int hostNameLen,
IN_PORT const int port )
{
SOCKET netSocket = DUMMY_INIT;
struct addrinfo *addrInfoPtr, *addrInfoCursor;
BOOLEAN nonBlockWarning = FALSE;
int socketStatus, addressCount, status;
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
assert( isReadPtr( host, hostNameLen ) );
REQUIRES( hostNameLen > 0 && hostNameLen <= MAX_DNS_SIZE );
REQUIRES( port >= 22 && port < 65536L );
/* Clear return value */
netStream->netSocket = CRYPT_ERROR;
/* Set up addressing information */
status = getAddressInfo( netStream, &addrInfoPtr, host, hostNameLen, 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 that we try */
for( addrInfoCursor = addrInfoPtr, addressCount = 0;
addrInfoCursor != NULL && addressCount < IP_ADDR_COUNT;
addrInfoCursor = addrInfoCursor->ai_next, addressCount++ )
{
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 );
}
if( addressCount >= IP_ADDR_COUNT )
{
/* We went through a suspiciously large number of remote server
addresses without being able to even initiate a connect attempt
to any of them, there's something wrong */
assert( DEBUG_WARN );
return( mapError( netStream, FALSE, CRYPT_ERROR_OPEN ) );
}
freeAddressInfo( addrInfoPtr );
if( status < 0 && !nonBlockWarning )
{
/* There was an error condition other than a notification that the
operation hasn't completed yet */
return( mapError( netStream, FALSE, CRYPT_ERROR_OPEN ) );
}
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 */
netStream->netSocket = netSocket;
return( CRYPT_OK );
}
/* The connect is in progress, mark the stream as not-quite-ready for
use */
/* netStream->xxx = yyy; */
netStream->netSocket = netSocket;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int completeOpen( INOUT NET_STREAM_INFO *netStream )
{
static const int trueValue = 1;
SIZE_TYPE intLength = sizeof( int );
int value, status;
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
/* Wait around until the connect completes. Some select()s limit the
size of the second count so we set it to a maximum of about a week,
although why anyone would wait around that long (and whether any
network stack would even maintain a SYN_SENT for that amount of time)
is unclear.
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( netStream, min( netStream->timeout, 500000L ), FALSE,
IOWAIT_CONNECT );
if( cryptStatusError( status ) )
{
netStream->transportDisconnectFunction( netStream, 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.
How to best determine all of these conditions is a bit tricky. Other
possibilities include 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), but these are somewhat implementation-
specific and not consistent across different platforms */
status = getsockopt( netStream->netSocket, SOL_SOCKET, SO_ERROR,
( void * ) &value, &intLength );
if( status == 0 )
{
/* Berkeley-derived implementation, error is in value variable */
if( value != 0 )
{
status = mapError( netStream, FALSE, CRYPT_ERROR_OPEN );
netStream->transportDisconnectFunction( netStream, TRUE );
return( status );
}
}
else
{
/* Slowaris, error is in errno */
if( isSocketError( status ) )
{
status = getSocketError( netStream, CRYPT_ERROR_OPEN );
netStream->transportDisconnectFunction( netStream, 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 other side 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( netStream->netSocket, IPPROTO_TCP, TCP_NODELAY,
( void * ) &trueValue, sizeof( int ) );
setSocketBlocking( netStream->netSocket );
/* We've completed the connection, mark the stream as ready for use */
/* netStream->xxx = zzz; */
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int openServerSocket( INOUT NET_STREAM_INFO *netStream,
IN_BUFFER_OPT( hostNameLen ) const char *host,
IN_LENGTH_DNS const int hostNameLen,
IN_PORT const int port )
{
SOCKET listenSocket = DUMMY_INIT, netSocket;
SOCKADDR_STORAGE clientAddr;
struct addrinfo *addrInfoPtr, *addrInfoCursor;
static const int trueValue = 1;
SIZE_TYPE clientAddrLen = sizeof( SOCKADDR_STORAGE );
char hostNameBuffer[ MAX_DNS_SIZE + 1 + 8 ];
int socketStatus, addressCount, status = CRYPT_ERROR_OPEN;
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
assert( ( host == NULL && hostNameLen == 0 ) || \
isReadPtr( host, hostNameLen ) );
REQUIRES( ( host == NULL && hostNameLen == 0 ) || \
( host != NULL && \
hostNameLen > 0 && hostNameLen <= MAX_DNS_SIZE ) );
REQUIRES( port >= 22 && port < 65536L );
/* Clear return value */
netStream->netSocket = CRYPT_ERROR;
/* Convert the host name into the null-terminated string required by the
sockets API if necessary */
if( host != NULL )
{
REQUIRES( hostNameLen > 0 && hostNameLen < MAX_DNS_SIZE );
memcpy( hostNameBuffer, host, hostNameLen );
hostNameBuffer[ hostNameLen ] = '\0';
host = hostNameBuffer;
}
/* 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 old 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( netStream, &addrInfoPtr, host, hostNameLen,
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 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.
Qui habet aures audiendi audiat (the speaker appears to be speaking
metaphorically with 'ears' referring to 'network sockets', latin
having no native term for the latter) */
for( addrInfoCursor = addrInfoPtr, addressCount = 0;
addrInfoCursor != NULL && addressCount < IP_ADDR_COUNT;
addrInfoCursor = addrInfoCursor->ai_next, addressCount++ )
{
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;
/* At this point we still have the socket pool locked while we
complete initialisation so we need to call newSocketDone()
before we break out of the loop at any point */
/* 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;
}
freeAddressInfo( addrInfoPtr );
if( addressCount >= IP_ADDR_COUNT )
{
/* We went through a suspiciously large number of server addresses
without being able to even initiate a listen attempt on any of
them, there's something wrong */
assert( DEBUG_WARN );
return( mapError( netStream, FALSE, CRYPT_ERROR_OPEN ) );
}
if( cryptStatusError( status ) )
{
/* There was an error setting up the socket, don't try anything
further */
return( mapError( netStream, FALSE, CRYPT_ERROR_OPEN ) );
}
/* Wait for a connection. At the moment this always waits forever
(actually some select()s limit the size of the second count so we
set it to a maximum of 1 year's worth), but in the future we could
have a separate timeout value for accepting incoming connections to
mirror the connection-wait timeout for outgoing connections.
Because of the way that accept works, the socket that we eventually
and up with isn't the one that we listen on, but we have to
temporarily make it the one associated with the stream in order for
ioWait() to work */
netStream->netSocket = listenSocket;
status = ioWait( netStream, min( netStream->timeout, 30000000L ), FALSE,
IOWAIT_ACCEPT );
netStream->netSocket = CRYPT_ERROR;
if( cryptStatusError( status ) )
return( status );
/* We have an incoming connection ready to go, accept it. There's a
potential complication here in that if a client connects and then
immediately sends a RST after the TCP handshake has completed,
ioWait() will return with an indication that there's an incoming
connection ready to go but the following accept(), if it's called
after the RST has arrived, will block waiting for the next incoming
connection. This is rather unlikely in practice, but could occur
as part of a DoS by setting the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -