diff options
Diffstat (limited to 'src/VBox/GuestHost/OpenGL/util/tcpip.c')
-rw-r--r-- | src/VBox/GuestHost/OpenGL/util/tcpip.c | 1472 |
1 files changed, 1472 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/OpenGL/util/tcpip.c b/src/VBox/GuestHost/OpenGL/util/tcpip.c new file mode 100644 index 00000000..ce3e8f84 --- /dev/null +++ b/src/VBox/GuestHost/OpenGL/util/tcpip.c @@ -0,0 +1,1472 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#ifdef WINDOWS +#define WIN32_LEAN_AND_MEAN +# ifdef VBOX +# include <iprt/win/winsock2.h> +# else /* !VBOX */ +#pragma warning( push, 3 ) +#include <winsock2.h> +#pragma warning( pop ) +#pragma warning( disable : 4514 ) +#pragma warning( disable : 4127 ) +typedef int ssize_t; +# endif /* !VBOX */ +#else +#include <sys/types.h> +#include <sys/wait.h> +#ifdef OSF1 +typedef int socklen_t; +#endif +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <unistd.h> +#endif + +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#ifdef AIX +#include <strings.h> +#endif + +#ifdef LINUX +#include <sys/ioctl.h> +#include <unistd.h> +#endif + +#include "cr_error.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "cr_bufpool.h" +#include "cr_net.h" +#include "cr_endian.h" +#include "cr_threads.h" +#include "cr_environment.h" +#include "net_internals.h" + +#ifdef ADDRINFO +#define PF PF_UNSPEC +#endif + +#ifdef WINDOWS +# undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +# undef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#endif + +#ifdef WINDOWS + +#undef ECONNRESET +#define ECONNRESET WSAECONNRESET +#undef EINTR +#define EINTR WSAEINTR + +int crTCPIPErrno( void ) +{ + return WSAGetLastError( ); +} + +char *crTCPIPErrorString( int err ) +{ + static char buf[512], *temp; + + sprintf( buf, "err=%d", err ); + +#define X(x) crStrcpy(buf,x); break + + switch ( err ) + { + case WSAECONNREFUSED: X( "connection refused" ); + case WSAECONNRESET: X( "connection reset" ); + default: + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, err, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + (LPTSTR) &temp, 0, NULL ); + if ( temp ) + { + crStrncpy( buf, temp, sizeof(buf)-1 ); + buf[sizeof(buf)-1] = 0; + } + } + +#undef X + + temp = buf + crStrlen(buf) - 1; + while ( temp > buf && isspace( *temp ) ) + { + *temp = '\0'; + temp--; + } + + return buf; +} + +#else /* WINDOWS */ + +int crTCPIPErrno( void ) +{ + int err = errno; + errno = 0; + return err; +} + +char *crTCPIPErrorString( int err ) +{ + static char buf[512], *temp; + + temp = strerror( err ); + if ( temp ) + { + crStrncpy( buf, temp, sizeof(buf)-1 ); + buf[sizeof(buf)-1] = 0; + } + else + { + sprintf( buf, "err=%d", err ); + } + + return buf; +} + +#endif /* WINDOWS */ + + +/* + * Socket callbacks. When a socket is created or destroyed we will + * call these callback functions. + * XXX Currently only implemented for TCP/IP. + * XXX Maybe have lists of callbacks? + */ +static CRSocketCallbackProc SocketCreateCallback = NULL; +static CRSocketCallbackProc SocketDestroyCallback = NULL; + +void +crRegisterSocketCallback(int mode, CRSocketCallbackProc proc) +{ + if (mode == CR_SOCKET_CREATE) { + SocketCreateCallback = proc; + } + else if (mode == CR_SOCKET_DESTROY) { + SocketDestroyCallback = proc; + } + else { + crError("Invalid crRegisterSocketCallbac mode=%d", mode); + } +} + + + +void crCloseSocket( CRSocket sock ) +{ + int fail; + + if (sock <= 0) + return; + + if (SocketDestroyCallback) { + SocketDestroyCallback(CR_SOCKET_DESTROY, sock); + } + +#ifdef WINDOWS + fail = ( closesocket( sock ) != 0 ); +#else + shutdown( sock, 2 /* RDWR */ ); + fail = ( close( sock ) != 0 ); +#endif + if ( fail ) + { + int err = crTCPIPErrno( ); + crWarning( "crCloseSocket( sock=%d ): %s", + sock, crTCPIPErrorString( err ) ); + } +} + +cr_tcpip_data cr_tcpip; + +/** + * Read len bytes from socket, and store in buffer. + * \return 1 if success, -1 if error, 0 if sender exited. + */ +int +__tcpip_read_exact( CRSocket sock, void *buf, unsigned int len ) +{ + char *dst = (char *) buf; + /* + * Shouldn't write to a non-existent socket, ie when + * crTCPIPDoDisconnect has removed it from the pool + */ + if ( sock <= 0 ) + return 1; + + while ( len > 0 ) + { + const int num_read = recv( sock, dst, (int) len, 0 ); + +#ifdef WINDOWS_XXXX + /* MWE: why is this necessary for windows??? Does it return a + "good" value for num_bytes despite having a reset + connection? */ + if ( crTCPIPErrno( ) == ECONNRESET ) + return -1; +#endif + + if ( num_read < 0 ) + { + int error = crTCPIPErrno(); + switch( error ) + { + case EINTR: + crWarning( "__tcpip_read_exact(TCPIP): " + "caught an EINTR, looping for more data" ); + continue; + case EFAULT: + crWarning( "EFAULT" ); + break; + case EINVAL: + crWarning( "EINVAL" ); + break; + default: + break; + } + crWarning( "Bad bad bad socket error: %s", crTCPIPErrorString( error ) ); + return -1; + } + + if ( num_read == 0 ) + { + /* client exited gracefully */ + return 0; + } + + dst += num_read; + len -= num_read; + } + + return 1; +} + +void +crTCPIPReadExact( CRConnection *conn, void *buf, unsigned int len ) +{ + if ( __tcpip_read_exact( conn->tcp_socket, buf, len ) <= 0 ) + { + __tcpip_dead_connection( conn ); + } +} + +/** + * Write the given buffer of len bytes on the socket. + * \return 1 if OK, negative value if error. + */ +int +__tcpip_write_exact( CRSocket sock, const void *buf, unsigned int len ) +{ + const char *src = (const char *) buf; + + /* + * Shouldn't write to a non-existent socket, ie when + * crTCPIPDoDisconnect has removed it from the pool + */ + if ( sock <= 0 ) + return 1; + + while ( len > 0 ) + { + const int num_written = send( sock, src, len, 0 ); + if ( num_written <= 0 ) + { + int err; + if ( (err = crTCPIPErrno( )) == EINTR ) + { + crWarning("__tcpip_write_exact(TCPIP): caught an EINTR, continuing"); + continue; + } + + return -err; + } + + len -= num_written; + src += num_written; + } + + return 1; +} + +void +crTCPIPWriteExact( CRConnection *conn, const void *buf, unsigned int len ) +{ + if ( __tcpip_write_exact( conn->tcp_socket, buf, len) <= 0 ) + { + __tcpip_dead_connection( conn ); + } +} + + +/** + * Make sockets do what we want: + * + * 1) Change the size of the send/receive buffers to 64K + * 2) Turn off Nagle's algorithm + */ +static void +spankSocket( CRSocket sock ) +{ + /* why do we do 1) ? things work much better for me to push the + * the buffer size way up -- karl + */ +#ifdef LINUX + int sndbuf = 1*1024*1024; +#else + int sndbuf = 64*1024; +#endif + + int rcvbuf = sndbuf; + int so_reuseaddr = 1; + int tcp_nodelay = 1; + + if ( setsockopt( sock, SOL_SOCKET, SO_SNDBUF, + (char *) &sndbuf, sizeof(sndbuf) ) ) + { + int err = crTCPIPErrno( ); + crWarning( "setsockopt( SO_SNDBUF=%d ) : %s", + sndbuf, crTCPIPErrorString( err ) ); + } + + if ( setsockopt( sock, SOL_SOCKET, SO_RCVBUF, + (char *) &rcvbuf, sizeof(rcvbuf) ) ) + { + int err = crTCPIPErrno( ); + crWarning( "setsockopt( SO_RCVBUF=%d ) : %s", + rcvbuf, crTCPIPErrorString( err ) ); + } + + + if ( setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, + (char *) &so_reuseaddr, sizeof(so_reuseaddr) ) ) + { + int err = crTCPIPErrno( ); + crWarning( "setsockopt( SO_REUSEADDR=%d ) : %s", + so_reuseaddr, crTCPIPErrorString( err ) ); + } + + if ( setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, + (char *) &tcp_nodelay, sizeof(tcp_nodelay) ) ) + { + int err = crTCPIPErrno( ); + crWarning( "setsockopt( TCP_NODELAY=%d )" + " : %s", tcp_nodelay, crTCPIPErrorString( err ) ); + } +} + + +#if defined( WINDOWS ) || defined( IRIX ) || defined( IRIX64 ) +typedef int socklen_t; +#endif + + +/** + * Create a listening socket using the given port. + * Caller can then pass the socket to accept(). + * If the port is one that's been seen before, we'll reuse/return the + * previously create socket. + */ +static int +CreateListeningSocket(int port) +{ + /* XXX should use an unbounded list here instead of parallel arrays... */ +#define MAX_PORTS 100 + static int ports[MAX_PORTS]; + static int sockets[MAX_PORTS]; + static int count = 0; + int i, sock = -1; + + /* search to see if we've seen this port before */ + for (i = 0; i < count; i++) { + if (ports[i] == port) { + return sockets[i]; + } + } + + /* new port so create new socket */ + { + int err; +#ifndef ADDRINFO + struct sockaddr_in servaddr; +#endif + + /* with the new OOB stuff, we can have multiple ports being + * accepted on, so we need to redo the server socket every time. + */ +#ifndef ADDRINFO + sock = socket( AF_INET, SOCK_STREAM, 0 ); + if ( sock == -1 ) + { + err = crTCPIPErrno( ); + crError( "Couldn't create socket: %s", crTCPIPErrorString( err ) ); + } + spankSocket( sock ); + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = INADDR_ANY; + servaddr.sin_port = htons( (short) port ); + + if ( bind( sock, (struct sockaddr *) &servaddr, sizeof(servaddr) ) ) + { + err = crTCPIPErrno( ); + crError( "Couldn't bind to socket (port=%d): %s", + port, crTCPIPErrorString( err ) ); + } + + if ( listen( sock, 100 /* max pending connections */ ) ) + { + err = crTCPIPErrno( ); + crError( "Couldn't listen on socket: %s", crTCPIPErrorString( err ) ); + } +#else + char port_s[NI_MAXSERV]; + struct addrinfo *res,*cur; + struct addrinfo hints; + + sprintf(port_s, "%u", (short unsigned) port); + + crMemset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = PF; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo( NULL, port_s, &hints, &res ); + if ( err ) + crError( "Couldn't find local TCP port %s: %s", + port_s, gai_strerror(err) ); + + for (cur=res;cur;cur=cur->ai_next) + { + sock = socket( cur->ai_family, cur->ai_socktype, cur->ai_protocol ); + if ( sock == -1 ) + { + err = crTCPIPErrno( ); + if (err != EAFNOSUPPORT) + crWarning("Couldn't create socket of family %i: %s, trying another", + cur->ai_family, crTCPIPErrorString( err ) ); + continue; + } + spankSocket( sock ); + + if ( bind( sock, cur->ai_addr, cur->ai_addrlen ) ) + { + err = crTCPIPErrno( ); + crWarning( "Couldn't bind to socket (port=%d): %s", + port, crTCPIPErrorString( err ) ); + crCloseSocket( sock ); + continue; + } + + if ( listen( sock, 100 /* max pending connections */ ) ) + { + err = crTCPIPErrno( ); + crWarning("Couldn't listen on socket: %s", crTCPIPErrorString(err)); + crCloseSocket( sock ); + continue; + } + break; + } + freeaddrinfo(res); + if (!cur) + crError( "Couldn't find/bind local TCP port %s", port_s); +#endif + } + + /* save the new port/socket */ + if (count == MAX_PORTS) { + crError("Fatal error in tcpip layer: too many listening ports/sockets"); + } + ports[count] = port; + sockets[count] = sock; + count++; + + return sock; +} + + + + +void +crTCPIPAccept( CRConnection *conn, const char *hostname, unsigned short port ) +{ + int err; + socklen_t addr_length; +#ifndef ADDRINFO + struct hostent *host; + struct in_addr sin_addr; + struct sockaddr addr; +#else + struct sockaddr_storage addr; + char host[NI_MAXHOST]; +#endif + (void)hostname; + + cr_tcpip.server_sock = CreateListeningSocket(port); + + /* If brokered, we'll contact the mothership to broker the network + * connection. We'll send the mothership our hostname, the port and + * our endianness and will get in return a connection ID number. + */ + if (conn->broker) { + crError("There shouldn't be any brokered connections in VirtualBox"); + } + + addr_length = sizeof( addr ); + conn->tcp_socket = accept( cr_tcpip.server_sock, (struct sockaddr *) &addr, &addr_length ); + if (conn->tcp_socket == -1) + { + err = crTCPIPErrno( ); + crError( "Couldn't accept client: %s", crTCPIPErrorString( err ) ); + } + + if (SocketCreateCallback) { + SocketCreateCallback(CR_SOCKET_CREATE, conn->tcp_socket); + } + +#ifndef ADDRINFO + sin_addr = ((struct sockaddr_in *) &addr)->sin_addr; + host = gethostbyaddr( (char *) &sin_addr, sizeof( sin_addr), AF_INET ); + if (host == NULL ) + { + char *temp = inet_ntoa( sin_addr ); + conn->hostname = crStrdup( temp ); + } +#else + err = getnameinfo ( (struct sockaddr *) &addr, addr_length, + host, sizeof( host), + NULL, 0, NI_NAMEREQD); + if ( err ) + { + err = getnameinfo ( (struct sockaddr *) &addr, addr_length, + host, sizeof( host), + NULL, 0, NI_NUMERICHOST); + if ( err ) /* shouldn't ever happen */ + conn->hostname = crStrdup("unknown ?!"); + else + conn->hostname = crStrdup( host ); + } +#endif + else + { + char *temp; +#ifndef ADDRINFO + conn->hostname = crStrdup( host->h_name ); +#else + conn->hostname = crStrdup( host ); +#endif + + temp = conn->hostname; + while (*temp && *temp != '.' ) + temp++; + *temp = '\0'; + } + +#ifdef RECV_BAIL_OUT + err = sizeof(unsigned int); + if ( getsockopt( conn->tcp_socket, SOL_SOCKET, SO_RCVBUF, + (char *) &conn->krecv_buf_size, &err ) ) + { + conn->krecv_buf_size = 0; + } +#endif + + crDebug( "Accepted connection from \"%s\".", conn->hostname ); +} + + +void * +crTCPIPAlloc( CRConnection *conn ) +{ + CRTCPIPBuffer *buf; + +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&cr_tcpip.mutex); +#endif + + buf = (CRTCPIPBuffer *) crBufferPoolPop( cr_tcpip.bufpool, conn->buffer_size ); + + if ( buf == NULL ) + { + crDebug("Buffer pool %p was empty; allocated new %d byte buffer.", + cr_tcpip.bufpool, + (unsigned int)sizeof(CRTCPIPBuffer) + conn->buffer_size); + buf = (CRTCPIPBuffer *) + crAlloc( sizeof(CRTCPIPBuffer) + conn->buffer_size ); + buf->magic = CR_TCPIP_BUFFER_MAGIC; + buf->kind = CRTCPIPMemory; + buf->pad = 0; + buf->allocated = conn->buffer_size; + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.mutex); +#endif + + return (void *)( buf + 1 ); +} + + +static void +crTCPIPSingleRecv( CRConnection *conn, void *buf, unsigned int len ) +{ + crTCPIPReadExact( conn, buf, len ); +} + + +static void +crTCPIPSend( CRConnection *conn, void **bufp, + const void *start, unsigned int len ) +{ + if ( !conn || conn->type == CR_NO_CONNECTION ) + return; + + if (!bufp) { + /* We're sending a user-allocated buffer. + * Simply write the length & the payload and return. + */ + const int sendable_len = conn->swap ? SWAP32(len) : len; + crTCPIPWriteExact( conn, &sendable_len, sizeof(len) ); + if (conn->type == CR_NO_CONNECTION) + return; + crTCPIPWriteExact( conn, start, len ); + } + else { + /* The region [start .. start + len + 1] lies within a buffer that + * was allocated with crTCPIPAlloc() and can be put into the free + * buffer pool when we're done sending it. + */ + CRTCPIPBuffer *tcpip_buffer; + unsigned int *lenp; + + tcpip_buffer = (CRTCPIPBuffer *)(*bufp) - 1; + + CRASSERT( tcpip_buffer->magic == CR_TCPIP_BUFFER_MAGIC ); + + /* All of the buffers passed to the send function were allocated + * with crTCPIPAlloc(), which includes a header with a 4 byte + * pad field, to insure that we always have a place to write + * the length field, even when start == *bufp. + */ + lenp = (unsigned int *) start - 1; + *lenp = conn->swap ? SWAP32(len) : len; + + crTCPIPWriteExact(conn, lenp, len + sizeof(unsigned int)); + + /* Reclaim this pointer for reuse */ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&cr_tcpip.mutex); +#endif + crBufferPoolPush(cr_tcpip.bufpool, tcpip_buffer, tcpip_buffer->allocated); +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.mutex); +#endif + /* Since the buffer's now in the 'free' buffer pool, the caller can't + * use it any more. Setting bufp to NULL will make sure the caller + * doesn't try to re-use the buffer. + */ + *bufp = NULL; + } +} + + +void +__tcpip_dead_connection( CRConnection *conn ) +{ + crDebug( "Dead connection (sock=%d, host=%s), removing from pool", + conn->tcp_socket, conn->hostname ); + /* remove from connection pool */ + crTCPIPDoDisconnect( conn ); +} + + +int +__crSelect( int n, fd_set *readfds, int sec, int usec ) +{ + for ( ; ; ) + { + int err, num_ready; + + if (sec || usec) + { + /* We re-init everytime for Linux, as it corrupts + * the timeout structure, but other OS's + * don't have a problem with it. + */ + struct timeval timeout; + timeout.tv_sec = sec; + timeout.tv_usec = usec; + num_ready = select( n, readfds, NULL, NULL, &timeout ); + } + else + num_ready = select( n, readfds, NULL, NULL, NULL ); + + if ( num_ready >= 0 ) + { + return num_ready; + } + + err = crTCPIPErrno( ); + if ( err == EINTR ) + { + crWarning( "select interrupted by an unblocked signal, trying again" ); + } + else + { + crError( "select failed: %s", crTCPIPErrorString( err ) ); + } + } +} + + +void +crTCPIPFree( CRConnection *conn, void *buf ) +{ + CRTCPIPBuffer *tcpip_buffer = (CRTCPIPBuffer *) buf - 1; + + CRASSERT( tcpip_buffer->magic == CR_TCPIP_BUFFER_MAGIC ); + conn->recv_credits += tcpip_buffer->len; + + switch ( tcpip_buffer->kind ) + { + case CRTCPIPMemory: +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&cr_tcpip.mutex); +#endif + if (cr_tcpip.bufpool) { + /* pool may have been deallocated just a bit earlier in response + * to a SIGPIPE (Broken Pipe) signal. + */ + crBufferPoolPush( cr_tcpip.bufpool, tcpip_buffer, tcpip_buffer->allocated ); + } +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.mutex); +#endif + break; + + case CRTCPIPMemoryBig: + crFree( tcpip_buffer ); + break; + + default: + crError( "Weird buffer kind trying to free in crTCPIPFree: %d", tcpip_buffer->kind ); + } +} + + +/** + * Check if message type is GATHER. If so, process it specially. + * \return number of bytes which were consumed + */ +static int +crTCPIPUserbufRecv(CRConnection *conn, CRMessage *msg) +{ + if (msg->header.type == CR_MESSAGE_GATHER) { + /* grab the offset and the length */ + const int len = 2 * sizeof(unsigned int); /* was unsigned long!!!! */ + unsigned int buf[2]; + + if (__tcpip_read_exact(conn->tcp_socket, buf, len) <= 0) + { + __tcpip_dead_connection( conn ); + } + msg->gather.offset = buf[0]; + msg->gather.len = buf[1]; + + /* read the rest into the userbuf */ + if (buf[0] + buf[1] > (unsigned int) conn->userbuf_len) + { + crDebug("userbuf for Gather Message is too small!"); + return len; + } + + if (__tcpip_read_exact(conn->tcp_socket, + conn->userbuf + buf[0], buf[1]) <= 0) + { + __tcpip_dead_connection( conn ); + } + return len + buf[1]; + } + else { + return 0; + } +} + + +/** + * Receive the next message on the given connection. + * If we're being called by crTCPIPRecv(), we already know there's + * something to receive. + */ +static void +crTCPIPReceiveMessage(CRConnection *conn) +{ + CRMessage *msg; + CRMessageType cached_type; + CRTCPIPBuffer *tcpip_buffer; + unsigned int len, total, leftover; + const int sock = conn->tcp_socket; + + /* Our gigE board is acting odd. If we recv() an amount + * less than what is already in the RECVBUF, performance + * goes into the toilet (somewhere around a factor of 3). + * This is an ugly hack, but seems to get around whatever + * funk is being produced + * + * Remember to set your kernel recv buffers to be bigger + * than the framebuffer 'chunk' you are sending (see + * sysctl -a | grep rmem) , or this will really have no + * effect. --karl + */ +#ifdef RECV_BAIL_OUT + { + int inbuf; + (void) recv(sock, &len, sizeof(len), MSG_PEEK); + ioctl(conn->tcp_socket, FIONREAD, &inbuf); + + if ((conn->krecv_buf_size > len) && (inbuf < len)) + return; + } +#endif + + /* this reads the length of the message */ + if ( __tcpip_read_exact( sock, &len, sizeof(len)) <= 0 ) + { + __tcpip_dead_connection( conn ); + return; + } + + if (conn->swap) + len = SWAP32(len); + + CRASSERT( len > 0 ); + + if ( len <= conn->buffer_size ) + { + /* put in pre-allocated buffer */ + tcpip_buffer = (CRTCPIPBuffer *) crTCPIPAlloc( conn ) - 1; + } + else + { + /* allocate new buffer */ + tcpip_buffer = (CRTCPIPBuffer *) crAlloc( sizeof(*tcpip_buffer) + len ); + tcpip_buffer->magic = CR_TCPIP_BUFFER_MAGIC; + tcpip_buffer->kind = CRTCPIPMemoryBig; + tcpip_buffer->pad = 0; + } + + tcpip_buffer->len = len; + + /* if we have set a userbuf, and there is room in it, we probably + * want to stick the message into that, instead of our allocated + * buffer. + */ + leftover = 0; + total = len; + if ((conn->userbuf != NULL) + && (conn->userbuf_len >= (int) sizeof(CRMessageHeader))) + { + leftover = len - sizeof(CRMessageHeader); + total = sizeof(CRMessageHeader); + } + + if ( __tcpip_read_exact( sock, tcpip_buffer + 1, total) <= 0 ) + { + crWarning( "Bad juju: %d %d on socket 0x%x", tcpip_buffer->allocated, + total, sock ); + crFree( tcpip_buffer ); + __tcpip_dead_connection( conn ); + return; + } + + conn->recv_credits -= total; + conn->total_bytes_recv += total; + + msg = (CRMessage *) (tcpip_buffer + 1); + cached_type = msg->header.type; + if (conn->swap) + { + msg->header.type = (CRMessageType) SWAP32( msg->header.type ); + msg->header.conn_id = (CRMessageType) SWAP32( msg->header.conn_id ); + } + + /* if there is still data pending, it should go into the user buffer */ + if (leftover) + { + const unsigned int handled = crTCPIPUserbufRecv(conn, msg); + + /* if there is anything left, plop it into the recv_buffer */ + if (leftover - handled) + { + if ( __tcpip_read_exact( sock, tcpip_buffer + 1 + total, leftover-handled) <= 0 ) + { + crWarning( "Bad juju: %d %d", tcpip_buffer->allocated, leftover-handled); + crFree( tcpip_buffer ); + __tcpip_dead_connection( conn ); + return; + } + } + + conn->recv_credits -= handled; + conn->total_bytes_recv += handled; + } + + crNetDispatchMessage( cr_tcpip.recv_list, conn, msg, len ); +#if 0 + crLogRead( len ); +#endif + + /* CR_MESSAGE_OPCODES is freed in crserverlib/server_stream.c with crNetFree. + * OOB messages are the programmer's problem. -- Humper 12/17/01 + */ + if (cached_type != CR_MESSAGE_OPCODES + && cached_type != CR_MESSAGE_OOB + && cached_type != CR_MESSAGE_GATHER) + { + crTCPIPFree( conn, tcpip_buffer + 1 ); + } +} + + +/** + * Loop over all TCP/IP connections, reading incoming data on those + * that are ready. + */ +int +crTCPIPRecv( void ) +{ + /* ensure we don't get caught with a new thread connecting */ + const int num_conns = cr_tcpip.num_conns; + int num_ready, max_fd, i; + fd_set read_fds; + int msock = -1; /* assumed mothership socket */ +#if CRAPPFAKER_SHOULD_DIE + int none_left = 1; +#endif + +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&cr_tcpip.recvmutex); +#endif + + /* + * Loop over all connections and determine which are TCP/IP connections + * that are ready to be read. + */ + max_fd = 0; + FD_ZERO( &read_fds ); + for ( i = 0; i < num_conns; i++ ) + { + CRConnection *conn = cr_tcpip.conns[i]; + if ( !conn || conn->type == CR_NO_CONNECTION ) + continue; + +#if CRAPPFAKER_SHOULD_DIE + none_left = 0; +#endif + + if ( conn->recv_credits > 0 || conn->type != CR_TCPIP ) + { + /* + * NOTE: may want to always put the FD in the descriptor + * set so we'll notice broken connections. Down in the + * loop that iterates over the ready sockets only peek + * (MSG_PEEK flag to recv()?) if the connection isn't + * enabled. + */ +#if 0 /* not used - see below */ +#ifndef ADDRINFO + struct sockaddr s; +#else + struct sockaddr_storage s; +#endif + socklen_t slen; +#endif + fd_set only_fd; /* testing single fd */ + CRSocket sock = conn->tcp_socket; + + if ( (int) sock + 1 > max_fd ) + max_fd = (int) sock + 1; + FD_SET( sock, &read_fds ); + + /* KLUDGE CITY...... + * + * With threads there's a race condition between + * TCPIPRecv and TCPIPSingleRecv when new + * clients are connecting, thus new mothership + * connections are also being established. + * This code below is to check that we're not + * in a state of accepting the socket without + * connecting to it otherwise we fail with + * ENOTCONN later. But, this is really a side + * effect of this routine catching a motherships + * socket connection and reading data that wasn't + * really meant for us. It was really meant for + * TCPIPSingleRecv. So, if we detect an + * in-progress connection we set the msock id + * so that we can assume the motherships socket + * and skip over them. + */ + + FD_ZERO(&only_fd); + FD_SET( sock, &only_fd ); + +#if 0 /* Disabled on Dec 13 2005 by BrianP - seems to cause trouble */ + slen = sizeof( s ); + /* Check that the socket is REALLY connected */ + /* Doesn't this call introduce some inefficiency??? (BP) */ + if (getpeername(sock, (struct sockaddr *) &s, &slen) < 0) { + /* Another kludge..... + * If we disconnect a socket without writing + * anything to it, we end up here. Detect + * the disconnected socket by checking if + * we've ever sent something and then + * disconnect it. + * + * I think the networking layer needs + * a bit of a re-write.... Alan. + */ + if (conn->total_bytes_sent > 0) { + crTCPIPDoDisconnect( conn ); + } + FD_CLR(sock, &read_fds); + msock = sock; + } +#endif + /* + * Nope, that last socket we've just caught in + * the connecting phase. We've probably found + * a mothership connection here, and we shouldn't + * process it + */ + if ((int)sock == msock+1) + FD_CLR(sock, &read_fds); + } + } + +#if CRAPPFAKER_SHOULD_DIE + if (none_left) { + /* + * Caught no more connections. + * Review this if we want to try + * restarting crserver's dynamically. + */ +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.recvmutex); +#endif + crError("No more connections to process, terminating...\n"); + exit(0); /* shouldn't get here */ + } +#endif + + if (!max_fd) { +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.recvmutex); +#endif + return 0; + } + + if ( num_conns ) { + num_ready = __crSelect( max_fd, &read_fds, 0, 500 ); + } + else { + crWarning( "Waiting for first connection..." ); + num_ready = __crSelect( max_fd, &read_fds, 0, 0 ); + } + + if ( num_ready == 0 ) { +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.recvmutex); +#endif + return 0; + } + + /* + * Loop over connections, receive data on the TCP/IP connections that + * we determined are ready above. + */ + for ( i = 0; i < num_conns; i++ ) + { + CRConnection *conn = cr_tcpip.conns[i]; + CRSocket sock; + + if ( !conn || conn->type == CR_NO_CONNECTION ) + continue; + + /* Added by Samuel Thibault during TCP/IP / UDP code factorization */ + if ( conn->type != CR_TCPIP ) + continue; + + sock = conn->tcp_socket; + if ( !FD_ISSET( sock, &read_fds ) ) + continue; + + if (conn->threaded) + continue; + + crTCPIPReceiveMessage(conn); + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&cr_tcpip.recvmutex); +#endif + + return 1; +} + + +static void +crTCPIPHandleNewMessage( CRConnection *conn, CRMessage *msg, unsigned int len ) +{ + CRTCPIPBuffer *buf = ((CRTCPIPBuffer *) msg) - 1; + + /* build a header so we can delete the message later */ + buf->magic = CR_TCPIP_BUFFER_MAGIC; + buf->kind = CRTCPIPMemory; + buf->len = len; + buf->pad = 0; + + crNetDispatchMessage( cr_tcpip.recv_list, conn, msg, len ); +} + + +static void +crTCPIPInstantReclaim( CRConnection *conn, CRMessage *mess ) +{ + crTCPIPFree( conn, mess ); +} + + +void +crTCPIPInit( CRNetReceiveFuncList *rfl, CRNetCloseFuncList *cfl, + unsigned int mtu ) +{ + (void) mtu; + + cr_tcpip.recv_list = rfl; + cr_tcpip.close_list = cfl; + if ( cr_tcpip.initialized ) + { + return; + } + + cr_tcpip.initialized = 1; + + cr_tcpip.num_conns = 0; + cr_tcpip.conns = NULL; + + cr_tcpip.server_sock = -1; + +#ifdef CHROMIUM_THREADSAFE + crInitMutex(&cr_tcpip.mutex); + crInitMutex(&cr_tcpip.recvmutex); +#endif + cr_tcpip.bufpool = crBufferPoolInit(16); +} + + +/** + * The function that actually connects. This should only be called by clients + * Servers have another way to set up the socket. + */ +int +crTCPIPDoConnect( CRConnection *conn ) +{ + int err; +#ifndef ADDRINFO + struct sockaddr_in servaddr; + struct hostent *hp; + int i; + + conn->tcp_socket = socket( AF_INET, SOCK_STREAM, 0 ); + if ( conn->tcp_socket < 0 ) + { + int err = crTCPIPErrno( ); + crWarning( "socket error: %s", crTCPIPErrorString( err ) ); + cr_tcpip.conns[conn->index] = NULL; /* remove from table */ + return 0; + } + + if (SocketCreateCallback) { + SocketCreateCallback(CR_SOCKET_CREATE, conn->tcp_socket); + } + + /* Set up the socket the way *we* want. */ + spankSocket( conn->tcp_socket ); + + /* Standard Berkeley sockets mumbo jumbo */ + hp = gethostbyname( conn->hostname ); + if ( !hp ) + { + crWarning( "Unknown host: \"%s\"", conn->hostname ); + cr_tcpip.conns[conn->index] = NULL; /* remove from table */ + return 0; + } + + crMemset( &servaddr, 0, sizeof(servaddr) ); + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons( (short) conn->port ); + + crMemcpy((char *) &servaddr.sin_addr, hp->h_addr, sizeof(servaddr.sin_addr)); +#else + char port_s[NI_MAXSERV]; + struct addrinfo *res,*cur; + struct addrinfo hints; + + sprintf(port_s, "%u", (short unsigned) conn->port); + + crMemset(&hints, 0, sizeof(hints)); + hints.ai_family = PF; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo( conn->hostname, port_s, &hints, &res); + if ( err ) + { + crWarning( "Unknown host: \"%s\": %s", conn->hostname, gai_strerror(err) ); + cr_tcpip.conns[conn->index] = NULL; /* remove from table */ + return 0; + } +#endif + + /* If brokered, we'll contact the mothership to broker the network + * connection. We'll send the mothership our hostname, the port and + * our endianness and will get in return a connection ID number. + */ + if (conn->broker) + { + crError("There shouldn't be any brokered connections in VirtualBox"); + } + +#ifndef ADDRINFO + for (i=1;i;) +#else + for (cur=res;cur;) +#endif + { +#ifndef ADDRINFO + +#ifdef RECV_BAIL_OUT + err = sizeof(unsigned int); + if ( getsockopt( conn->tcp_socket, SOL_SOCKET, SO_RCVBUF, + (char *) &conn->krecv_buf_size, &err ) ) + { + conn->krecv_buf_size = 0; + } +#endif + if ( !connect( conn->tcp_socket, (struct sockaddr *) &servaddr, + sizeof(servaddr) ) ) + return 1; +#else + + conn->tcp_socket = socket( cur->ai_family, cur->ai_socktype, cur->ai_protocol ); + if ( conn->tcp_socket < 0 ) + { + int err2 = crTCPIPErrno( ); + if (err2 != EAFNOSUPPORT) + crWarning( "socket error: %s, trying another way", crTCPIPErrorString( err2 ) ); + cur=cur->ai_next; + continue; + } + + if (SocketCreateCallback) { + SocketCreateCallback(CR_SOCKET_CREATE, conn->tcp_socket); + } + + err = 1; + setsockopt(conn->tcp_socket, SOL_SOCKET, SO_REUSEADDR, &err, sizeof(int)); + + /* Set up the socket the way *we* want. */ + spankSocket( conn->tcp_socket ); + +#if RECV_BAIL_OUT + err = sizeof(unsigned int); + if ( getsockopt( conn->tcp_socket, SOL_SOCKET, SO_RCVBUF, + (char *) &conn->krecv_buf_size, &err ) ) + { + conn->krecv_buf_size = 0; + } +#endif + + if ( !connect( conn->tcp_socket, cur->ai_addr, cur->ai_addrlen ) ) { + freeaddrinfo(res); + return 1; + } +#endif + + err = crTCPIPErrno( ); + if ( err == EADDRINUSE || err == ECONNREFUSED ) + crWarning( "Connection refused to %s:%d, %s", + conn->hostname, conn->port, crTCPIPErrorString( err ) ); + + else if ( err == EINTR ) + { + crWarning( "connection to %s:%d " + "interrupted, trying again", conn->hostname, conn->port ); + continue; + } + else + crWarning( "Couldn't connect to %s:%d, %s", + conn->hostname, conn->port, crTCPIPErrorString( err ) ); + crCloseSocket( conn->tcp_socket ); +#ifndef ADDRINFO + i=0; +#else + cur=cur->ai_next; +#endif + } +#ifdef ADDRINFO + freeaddrinfo(res); + crWarning( "Couldn't find any suitable way to connect to %s", conn->hostname ); +#endif + cr_tcpip.conns[conn->index] = NULL; /* remove from table */ + return 0; +} + + +/** + * Disconnect this connection, but don't free(conn). + */ +void +crTCPIPDoDisconnect( CRConnection *conn ) +{ + int num_conns = cr_tcpip.num_conns; + int none_left = 1; + int i; + + /* If this connection has already been disconnected (e.g. + * if the connection has been lost and disabled through + * a call to __tcpip_dead_connection(), which will then + * call this routine), don't disconnect it again; if we + * do, and if a new valid connection appears in the same + * slot (conn->index), we'll effectively disable the + * valid connection by mistake, leaving us unable to + * receive inbound data on that connection. + */ + if (conn->type == CR_NO_CONNECTION) return; + + crCloseSocket( conn->tcp_socket ); + if (conn->hostname) { + crFree(conn->hostname); + conn->hostname = NULL; + } + conn->tcp_socket = 0; + conn->type = CR_NO_CONNECTION; + cr_tcpip.conns[conn->index] = NULL; + + /* see if any connections remain */ + for (i = 0; i < num_conns; i++) + { + if ( cr_tcpip.conns[i] && cr_tcpip.conns[i]->type != CR_NO_CONNECTION ) + none_left = 0; /* found a live connection */ + } + +#if 0 /* disabled on 13 Dec 2005 by BrianP - this prevents future client + * connections after the last one goes away. + */ + if (none_left && cr_tcpip.server_sock != -1) + { + crDebug("Closing master socket (probably quitting)."); + crCloseSocket( cr_tcpip.server_sock ); + cr_tcpip.server_sock = -1; +#ifdef CHROMIUM_THREADSAFE + crFreeMutex(&cr_tcpip.mutex); + crFreeMutex(&cr_tcpip.recvmutex); +#endif + crBufferPoolFree( cr_tcpip.bufpool ); + cr_tcpip.bufpool = NULL; + last_port = 0; + cr_tcpip.initialized = 0; + } +#endif +} + + +/** + * Initialize a CRConnection for tcp/ip. This is called via the + * InitConnection() function (and from the UDP module). + */ +void +crTCPIPConnection( CRConnection *conn ) +{ + int i, found = 0; + int n_bytes; + + CRASSERT( cr_tcpip.initialized ); + + conn->type = CR_TCPIP; + conn->Alloc = crTCPIPAlloc; + conn->Send = crTCPIPSend; + conn->SendExact = crTCPIPWriteExact; + conn->Recv = crTCPIPSingleRecv; + conn->RecvMsg = crTCPIPReceiveMessage; + conn->Free = crTCPIPFree; + conn->Accept = crTCPIPAccept; + conn->Connect = crTCPIPDoConnect; + conn->Disconnect = crTCPIPDoDisconnect; + conn->InstantReclaim = crTCPIPInstantReclaim; + conn->HandleNewMessage = crTCPIPHandleNewMessage; + conn->index = cr_tcpip.num_conns; + conn->sizeof_buffer_header = sizeof( CRTCPIPBuffer ); + conn->actual_network = 1; + + conn->krecv_buf_size = 0; + + /* Find a free slot */ + for (i = 0; i < cr_tcpip.num_conns; i++) { + if (cr_tcpip.conns[i] == NULL) { + conn->index = i; + cr_tcpip.conns[i] = conn; + found = 1; + break; + } + } + + /* Realloc connection stack if we couldn't find a free slot */ + if (found == 0) { + n_bytes = ( cr_tcpip.num_conns + 1 ) * sizeof(*cr_tcpip.conns); + crRealloc( (void **) &cr_tcpip.conns, n_bytes ); + cr_tcpip.conns[cr_tcpip.num_conns++] = conn; + } +} + + +int crGetHostname( char *buf, unsigned int len ) +{ + const char *override; + int ret; + + override = crGetenv("CR_HOSTNAME"); + if (override) + { + crStrncpy(buf, override, len); + ret = 0; + } + else + ret = gethostname( buf, len ); + return ret; +} + + +CRConnection** crTCPIPDump( int *num ) +{ + *num = cr_tcpip.num_conns; + + return cr_tcpip.conns; +} |