TestExecServiceTcp.cpp revision ca7ea74e4145b138179345f2c83316a97452467c
/* $Id$ */
/** @file
*/
/*
* Copyright (C) 2010-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP RTLOGGROUP_DEFAULT
#include <iprt/critsect.h>
#include "TestExecServiceInternal.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The default server port. */
#define TXS_TCP_DEF_BIND_PORT 5042
/** The default client port. */
#define TXS_TCP_DEF_CONNECT_PORT 5048
/** The default server bind address. */
#define TXS_TCP_DEF_BIND_ADDRESS ""
/** The default client connect address (i.e. of the host server). */
#define TXS_TCP_DEF_CONNECT_ADDRESS "10.0.2.2"
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** @name TCP Parameters
* @{ */
/** The addresses to bind to. Empty string means any. */
/** The TCP port to listen to. */
/** The addresses to connect to if fRevesedSetupMode is @c true. */
/** The TCP port to listen to. */
/** @} */
/** Critical section for serializing access to the next few variables. */
static RTCRITSECT g_TcpCritSect;
/** Pointer to the TCP server instance. */
/** Thread calling RTTcpServerListen2. */
/** Thread calling RTTcpClientConnect. */
/** The main thread handle (for signalling). */
/** Stop connecting attempts when set. */
static bool g_fTcpStopConnecting = false;
/** Connect cancel cookie. */
/** Socket of the current client. */
/** Indicates whether g_hTcpClient comes from the server or from a client
* connect (relevant when closing it). */
static bool g_fTcpClientFromServer = false;
/** The size of the stashed data. */
static size_t g_cbTcpStashed = 0;
/** The size of the stashed data allocation. */
static size_t g_cbTcpStashedAlloced = 0;
/** The stashed data. */
/**
* Disconnects the current client.
*/
static void txsTcpDisconnectClient(void)
{
int rc;
else
}
/**
* Sets the current client socket in a safe manner.
*
* @returns NIL_RTSOCKET if consumed, other wise hTcpClient.
* @param hTcpClient The client socket.
* @param fFromServer Set if server type connection.
*/
{
if ( g_hTcpClient == NIL_RTSOCKET
&& g_hThreadMain != NIL_RTTHREAD
)
{
g_fTcpClientFromServer = true;
}
return hTcpClient;
}
/**
* Server mode connection thread.
*
* @returns iprt status code.
* @param hSelf Thread handle. Ignored.
* @param pvUser Ignored.
*/
{
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* Checks if it's a fatal RTTcpClientConnect return code.
*
* @returns true / false.
* @param rc The iprt status.
*/
static bool txsTcpIsFatalClientConnectStatus(int rc)
{
return rc != VERR_NET_UNREACHABLE
&& rc != VERR_NET_HOST_DOWN
&& rc != VERR_NET_HOST_UNREACHABLE
&& rc != VERR_TIMEOUT
&& rc != VERR_NET_CONNECTION_TIMED_OUT;
}
/**
* Client mode connection thread.
*
* @returns iprt status code.
* @param hSelf Thread handle. Use to sleep on. The main thread will
* signal it to speed up thread shutdown.
* @param pvUser Ignored.
*/
{
for (;;)
{
/* Stop? */
bool fStop = g_fTcpStopConnecting;
if (fStop)
return VINF_SUCCESS;
/* Try connect. */ /** @todo make cancelable! */
if (RT_SUCCESS(rc))
{
break;
}
return rc;
/* Delay a wee bit before retrying. */
}
return VINF_SUCCESS;
}
/**
* Wait on the threads to complete.
*
* @returns Thread status (if collected), otherwise VINF_SUCCESS.
* @param cMillies The period to wait on each thread.
*/
{
int rcRet = VINF_SUCCESS;
if (g_hThreadTcpConnect != NIL_RTTHREAD)
{
int rcThread;
if (RT_SUCCESS(rc2))
{
}
}
if (g_hThreadTcpServer != NIL_RTTHREAD)
{
int rcThread;
if (RT_SUCCESS(rc2))
{
if (RT_SUCCESS(rc2))
}
}
return rcRet;
}
/**
* Connects to the peer.
*
* @returns VBox status code. Updates g_hTcpClient and g_fTcpClientFromServer on
* success
*/
static int txsTcpConnect(void)
{
int rc;
if (g_enmTcpMode == TXSTCPMODE_SERVER)
{
g_fTcpClientFromServer = true;
}
else if (g_enmTcpMode == TXSTCPMODE_CLIENT)
{
g_fTcpClientFromServer = false;
for (;;)
{
break;
/* Delay a wee bit before retrying. */
RTThreadSleep(1536);
}
}
else
{
/*
* Create client threads.
*/
g_fTcpStopConnecting = false;
rc = VINF_SUCCESS;
if (g_hThreadTcpConnect == NIL_RTTHREAD)
{
RTTHREADFLAGS_WAITABLE, "tcpconn");
}
RTTHREADFLAGS_WAITABLE, "tcpserv");
/*
* Wait for connection to be established.
*/
while ( RT_SUCCESS(rc)
&& g_hTcpClient == NIL_RTSOCKET)
{
rc = txsTcpConnectWaitOnThreads(0);
}
/*
* Cancel the threads.
*/
g_fTcpStopConnecting = true;
}
AssertMsg(RT_SUCCESS(rc) ? g_hTcpClient != NIL_RTSOCKET : g_hTcpClient == NIL_RTSOCKET, ("%Rrc %p\n", rc, g_hTcpClient));
g_cbTcpStashed = 0;
return rc;
}
/**
* @interface_method_impl{TXSTRANSPORT,txsTcpNotifyReboot}
*/
static DECLCALLBACK(void) txsTcpNotifyReboot(void)
{
if (g_pTcpServer)
{
if (RT_FAILURE(rc))
g_pTcpServer = NULL;
}
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnNotifyBye}
*/
static DECLCALLBACK(void) txsTcpNotifyBye(void)
{
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy}
*/
static DECLCALLBACK(void) txsTcpNotifyHowdy(void)
{
/* nothing to do here */
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnBabble}
*/
{
/*
* Quietly ignore already disconnected client.
*/
if (hTcpClient == NIL_RTSOCKET)
return;
/*
* Try send the babble reply.
*/
NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */
int rc;
while (rc == VERR_INTERRUPTED);
/*
* Disconnect the client.
*/
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnSendPkt}
*/
{
/*
* Fail if no client connection.
*/
if (hTcpClient == NIL_RTSOCKET)
return VERR_NET_NOT_CONNECTED;
/*
* Write it.
*/
if ( RT_FAILURE(rc)
&& rc != VERR_INTERRUPTED)
{
/* assume fatal connection error. */
}
return rc;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnRecvPkt}
*/
{
int rc = VINF_SUCCESS;
/*
* Do we have to wait for a client to connect?
*/
if (hTcpClient == NIL_RTSOCKET)
{
rc = txsTcpConnect();
if (RT_FAILURE(rc))
return rc;
}
/*
* Read state.
*/
/*
* Any stashed data?
*/
{
g_cbTcpStashed = 0;
}
else
{
if (!pbData)
return VERR_NO_MEMORY;
}
/*
* Read and valid the length.
*/
{
if (RT_FAILURE(rc))
break;
if (cbRead == 0)
{
break;
}
}
if (RT_SUCCESS(rc))
{
ASMCompilerBarrier(); /* paranoia^3 */
{
/*
* Align the length and reallocate the return packet it necessary.
*/
if (cbData > cbDataAlloced)
{
if (pvNew)
{
}
else
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
{
/*
* Read the remainder of the data.
*/
{
if (RT_FAILURE(rc))
break;
if (cbRead == 0)
{
break;
}
}
}
}
else
}
if (RT_SUCCESS(rc))
else
{
/*
* Deal with errors.
*/
if (rc == VERR_INTERRUPTED)
{
/* stash it away for the next call. */
}
else
{
/* assume fatal connection error. */
}
}
return rc;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnPollSetAdd}
*/
{
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnPollIn}
*/
static DECLCALLBACK(bool) txsTcpPollIn(void)
{
if (hTcpClient == NIL_RTSOCKET)
return false;
return RT_SUCCESS(rc);
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnTerm}
*/
static DECLCALLBACK(void) txsTcpTerm(void)
{
/* Signal thread */
{
g_fTcpStopConnecting = true;
}
if (g_hThreadTcpConnect != NIL_RTTHREAD)
{
}
/* Shut down the server (will wake up thread). */
if (g_pTcpServer)
{
Log(("txsTcpTerm: Destroying server...\n"));
if (RT_FAILURE(rc))
g_pTcpServer = NULL;
}
/* Shut down client */
if (g_hTcpClient != NIL_RTSOCKET)
{
{
Log(("txsTcpTerm: Disconnecting client...\n"));
if (RT_FAILURE(rc))
}
else
{
if (RT_FAILURE(rc))
}
}
/* Clean up stashing. */
g_cbTcpStashed = 0;
/* Wait for the thread (they should've had some time to quit by now). */
txsTcpConnectWaitOnThreads(15000);
/* Finally, clean up the critical section. */
Log(("txsTcpTerm: done\n"));
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnInit}
*/
static DECLCALLBACK(int) txsTcpInit(void)
{
{
rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer);
if (RT_FAILURE(rc))
{
if (rc == VERR_NET_DOWN)
{
RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n",
do
{
RTThreadSleep(1000);
rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer);
} while ( rc == VERR_NET_DOWN
if (RT_SUCCESS(rc))
RTMsgInfo("RTTcpServerCreateEx succceeded.\n");
}
if (RT_FAILURE(rc))
{
g_pTcpServer = NULL;
RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n",
}
}
}
return rc;
}
/** Options */
enum TXSTCPOPT
{
TXSTCPOPT_MODE = 1000,
/* legacy: */
};
/**
* @interface_method_impl{TXSTRANSPORT,pfnOption}
*/
{
int rc;
switch (ch)
{
case TXSTCPOPT_MODE:
else
break;
case TXSTCPOPT_BIND_ADDRESS:
if (RT_FAILURE(rc))
return VINF_SUCCESS;
case TXSTCPOPT_BIND_PORT:
break;
case TXSTCPOPT_LEGACY_CONNECT:
/* fall thru */
if (RT_FAILURE(rc))
if (!g_szTcpConnectAddr[0])
return VINF_SUCCESS;
case TXSTCPOPT_CONNECT_PORT:
break;
case TXSTCPOPT_LEGACY_PORT:
{
}
else
{
}
return VINF_SUCCESS;
}
return VERR_TRY_AGAIN;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnUsage}
*/
{
" --tcp-mode <both|client|server>\n"
" Selects the mode of operation.\n"
" Default: both\n"
" --tcp-bind-address <address>\n"
" The address(es) to listen to TCP connection on. Empty string\n"
" means any address, this is the default.\n"
" --tcp-bind-port <port>\n"
" The port to listen to TCP connections on.\n"
" Default: %u\n"
" --tcp-connect-address <address>\n"
" The address of the server to try connect to in client mode.\n"
" --tcp-connect-port <port>\n"
" The port on the server to connect to in client mode.\n"
" Default: %u\n"
}
static const RTGETOPTDEF g_TcpOpts[] =
{
/* legacy */
};
const TXSTRANSPORT g_TcpTransport =
{
/* .szName = */ "tcp",
/* .cOpts = */ &g_TcpOpts[0],
/* .pfnUsage = */ txsTcpUsage,
/* .pfnOption = */ txsTcpOption,
/* .pfnInit = */ txsTcpInit,
/* .pfnTerm = */ txsTcpTerm,
/* .pfnPollIn = */ txsTcpPollIn,
/* .pfnPollSetAdd = */ txsTcpPollSetAdd,
/* .pfnRecvPkt = */ txsTcpRecvPkt,
/* .pfnSendPkt = */ txsTcpSendPkt,
/* .pfnBabble = */ txsTcpBabble,
/* .pfnNotifyHowdy = */ txsTcpNotifyHowdy,
/* .pfnNotifyBye = */ txsTcpNotifyBye,
/* .pfnNotifyReboot = */ txsTcpNotifyReboot,
};