/* $Id$ */
/** @file
* TestExecServ - Basic Remote Execution Service, TCP/IP Transport Layer.
*/
/*
* 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;
* you can redistribute it and/or modify it under the terms of the GNU
* 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/asm.h>
#include <iprt/assert.h>
#include <iprt/critsect.h>
#include <iprt/err.h>
#include <iprt/log.h>
#include <iprt/mem.h>
#include <iprt/message.h>
#include <iprt/poll.h>
#include <iprt/string.h>
#include <iprt/tcp.h>
#include <iprt/thread.h>
#include <iprt/time.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
* @{ */
static enum { TXSTCPMODE_BOTH, TXSTCPMODE_CLIENT, TXSTCPMODE_SERVER }
g_enmTcpMode = TXSTCPMODE_BOTH;
/** The addresses to bind to. Empty string means any. */
static char g_szTcpBindAddr[256] = TXS_TCP_DEF_BIND_ADDRESS;
/** The TCP port to listen to. */
static uint32_t g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT;
/** The addresses to connect to if fRevesedSetupMode is @c true. */
static char g_szTcpConnectAddr[256] = TXS_TCP_DEF_CONNECT_ADDRESS;
/** The TCP port to listen to. */
static uint32_t g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT;
/** @} */
/** Critical section for serializing access to the next few variables. */
static RTCRITSECT g_TcpCritSect;
/** Pointer to the TCP server instance. */
static PRTTCPSERVER g_pTcpServer = NULL;
/** Thread calling RTTcpServerListen2. */
static RTTHREAD g_hThreadTcpServer = NIL_RTTHREAD;
/** Thread calling RTTcpClientConnect. */
static RTTHREAD g_hThreadTcpConnect = NIL_RTTHREAD;
/** The main thread handle (for signalling). */
static RTTHREAD g_hThreadMain = NIL_RTTHREAD;
/** Stop connecting attempts when set. */
static bool g_fTcpStopConnecting = false;
/** Connect cancel cookie. */
static PRTTCPCLIENTCONNECTCANCEL volatile g_pTcpConnectCancelCookie = NULL;
/** Socket of the current client. */
static RTSOCKET g_hTcpClient = NIL_RTSOCKET;
/** 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. */
static uint8_t *g_pbTcpStashed = NULL;
/**
* Disconnects the current client.
*/
static void txsTcpDisconnectClient(void)
{
int rc;
if (g_fTcpClientFromServer)
rc = RTTcpServerDisconnectClient2(g_hTcpClient);
else
rc = RTTcpClientClose(g_hTcpClient);
AssertRCSuccess(rc);
g_hTcpClient = NIL_RTSOCKET;
}
/**
* 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.
*/
static RTSOCKET txsTcpSetClient(RTSOCKET hTcpClient, bool fFromServer)
{
RTCritSectEnter(&g_TcpCritSect);
if ( g_hTcpClient == NIL_RTSOCKET
&& !g_fTcpStopConnecting
&& g_hThreadMain != NIL_RTTHREAD
)
{
g_fTcpClientFromServer = true;
g_hTcpClient = hTcpClient;
int rc = RTThreadUserSignal(g_hThreadMain); AssertRC(rc);
hTcpClient = NIL_RTSOCKET;
}
RTCritSectLeave(&g_TcpCritSect);
return hTcpClient;
}
/**
* Server mode connection thread.
*
* @returns iprt status code.
* @param hSelf Thread handle. Ignored.
* @param pvUser Ignored.
*/
static DECLCALLBACK(int) txsTcpServerConnectThread(RTTHREAD hSelf, void *pvUser)
{
RTSOCKET hTcpClient;
int rc = RTTcpServerListen2(g_pTcpServer, &hTcpClient);
Log(("txsTcpConnectServerThread: RTTcpServerListen2 -> %Rrc\n", rc));
if (RT_SUCCESS(rc))
{
hTcpClient = txsTcpSetClient(hTcpClient, true /*fFromServer*/);
RTTcpServerDisconnectClient2(hTcpClient);
}
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_NET_CONNECTION_REFUSED
&& 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.
*/
static DECLCALLBACK(int) txsTcpClientConnectThread(RTTHREAD hSelf, void *pvUser)
{
for (;;)
{
/* Stop? */
RTCritSectEnter(&g_TcpCritSect);
bool fStop = g_fTcpStopConnecting;
RTCritSectLeave(&g_TcpCritSect);
if (fStop)
return VINF_SUCCESS;
/* Try connect. */ /** @todo make cancelable! */
RTSOCKET hTcpClient;
Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort));
int rc = RTTcpClientConnectEx(g_szTcpConnectAddr, g_uTcpConnectPort, &hTcpClient,
RT_SOCKETCONNECT_DEFAULT_WAIT, &g_pTcpConnectCancelCookie);
Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc));
if (RT_SUCCESS(rc))
{
hTcpClient = txsTcpSetClient(hTcpClient, true /*fFromServer*/);
RTTcpClientCloseEx(hTcpClient, true /* fGracefulShutdown*/);
break;
}
if (txsTcpIsFatalClientConnectStatus(rc))
return rc;
/* Delay a wee bit before retrying. */
RTThreadUserWait(hSelf, 1536);
}
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.
*/
static int txsTcpConnectWaitOnThreads(RTMSINTERVAL cMillies)
{
int rcRet = VINF_SUCCESS;
if (g_hThreadTcpConnect != NIL_RTTHREAD)
{
int rcThread;
int rc2 = RTThreadWait(g_hThreadTcpConnect, cMillies, &rcThread);
if (RT_SUCCESS(rc2))
{
g_hThreadTcpConnect = NIL_RTTHREAD;
rcRet = rcThread;
}
}
if (g_hThreadTcpServer != NIL_RTTHREAD)
{
int rcThread;
int rc2 = RTThreadWait(g_hThreadTcpServer, cMillies, &rcThread);
if (RT_SUCCESS(rc2))
{
g_hThreadTcpServer = NIL_RTTHREAD;
if (RT_SUCCESS(rc2))
rcRet = rcThread;
}
}
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;
rc = RTTcpServerListen2(g_pTcpServer, &g_hTcpClient);
Log(("txsTcpRecvPkt: RTTcpServerListen2 -> %Rrc\n", rc));
}
else if (g_enmTcpMode == TXSTCPMODE_CLIENT)
{
g_fTcpClientFromServer = false;
for (;;)
{
Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort));
rc = RTTcpClientConnect(g_szTcpConnectAddr, g_uTcpConnectPort, &g_hTcpClient);
Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc));
if (RT_SUCCESS(rc) || txsTcpIsFatalClientConnectStatus(rc))
break;
/* Delay a wee bit before retrying. */
RTThreadSleep(1536);
}
}
else
{
Assert(g_enmTcpMode == TXSTCPMODE_BOTH);
RTTHREAD hSelf = RTThreadSelf();
/*
* Create client threads.
*/
RTCritSectEnter(&g_TcpCritSect);
RTThreadUserReset(hSelf);
g_hThreadMain = hSelf;
g_fTcpStopConnecting = false;
RTCritSectLeave(&g_TcpCritSect);
txsTcpConnectWaitOnThreads(32);
rc = VINF_SUCCESS;
if (g_hThreadTcpConnect == NIL_RTTHREAD)
{
g_pTcpConnectCancelCookie = NULL;
rc = RTThreadCreate(&g_hThreadTcpConnect, txsTcpClientConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT,
RTTHREADFLAGS_WAITABLE, "tcpconn");
}
if (g_hThreadTcpServer == NIL_RTTHREAD && RT_SUCCESS(rc))
rc = RTThreadCreate(&g_hThreadTcpServer, txsTcpServerConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT,
RTTHREADFLAGS_WAITABLE, "tcpserv");
RTCritSectEnter(&g_TcpCritSect);
/*
* Wait for connection to be established.
*/
while ( RT_SUCCESS(rc)
&& g_hTcpClient == NIL_RTSOCKET)
{
RTCritSectLeave(&g_TcpCritSect);
RTThreadUserWait(hSelf, 1536);
rc = txsTcpConnectWaitOnThreads(0);
RTCritSectEnter(&g_TcpCritSect);
}
/*
* Cancel the threads.
*/
g_hThreadMain = NIL_RTTHREAD;
g_fTcpStopConnecting = true;
RTCritSectLeave(&g_TcpCritSect);
RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie);
}
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)
{
Log(("txsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer));
if (g_pTcpServer)
{
int rc = RTTcpServerDestroy(g_pTcpServer);
if (RT_FAILURE(rc))
RTMsgInfo("RTTcpServerDestroy failed in txsTcpNotifyReboot: %Rrc", rc);
g_pTcpServer = NULL;
}
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnNotifyBye}
*/
static DECLCALLBACK(void) txsTcpNotifyBye(void)
{
Log(("txsTcpNotifyBye: txsTcpDisconnectClient %RTsock\n", g_hTcpClient));
txsTcpDisconnectClient();
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy}
*/
static DECLCALLBACK(void) txsTcpNotifyHowdy(void)
{
/* nothing to do here */
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnBabble}
*/
static DECLCALLBACK(void) txsTcpBabble(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)
{
/*
* Quietly ignore already disconnected client.
*/
RTSOCKET hTcpClient = g_hTcpClient;
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;
size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT);
do rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend);
while (rc == VERR_INTERRUPTED);
/*
* Disconnect the client.
*/
Log(("txsTcpBabble: txsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", g_hTcpClient, rc));
txsTcpDisconnectClient();
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnSendPkt}
*/
static DECLCALLBACK(int) txsTcpSendPkt(PCTXSPKTHDR pPktHdr)
{
Assert(pPktHdr->cb >= sizeof(TXSPKTHDR));
/*
* Fail if no client connection.
*/
RTSOCKET hTcpClient = g_hTcpClient;
if (hTcpClient == NIL_RTSOCKET)
return VERR_NET_NOT_CONNECTED;
/*
* Write it.
*/
size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT);
int rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend);
if ( RT_FAILURE(rc)
&& rc != VERR_INTERRUPTED)
{
/* assume fatal connection error. */
Log(("RTTcpWrite -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient));
txsTcpDisconnectClient();
}
return rc;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnRecvPkt}
*/
static DECLCALLBACK(int) txsTcpRecvPkt(PPTXSPKTHDR ppPktHdr)
{
int rc = VINF_SUCCESS;
*ppPktHdr = NULL;
/*
* Do we have to wait for a client to connect?
*/
RTSOCKET hTcpClient = g_hTcpClient;
if (hTcpClient == NIL_RTSOCKET)
{
rc = txsTcpConnect();
if (RT_FAILURE(rc))
return rc;
hTcpClient = g_hTcpClient; Assert(hTcpClient != NIL_RTSOCKET);
}
/*
* Read state.
*/
size_t offData = 0;
size_t cbData = 0;
size_t cbDataAlloced;
uint8_t *pbData = NULL;
/*
* Any stashed data?
*/
if (g_cbTcpStashedAlloced)
{
offData = g_cbTcpStashed;
cbDataAlloced = g_cbTcpStashedAlloced;
pbData = g_pbTcpStashed;
g_cbTcpStashed = 0;
g_cbTcpStashedAlloced = 0;
g_pbTcpStashed = NULL;
}
else
{
cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT);
pbData = (uint8_t *)RTMemAlloc(cbDataAlloced);
if (!pbData)
return VERR_NO_MEMORY;
}
/*
* Read and valid the length.
*/
while (offData < sizeof(uint32_t))
{
size_t cbRead;
rc = RTTcpRead(hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead);
if (RT_FAILURE(rc))
break;
if (cbRead == 0)
{
Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc));
rc = VERR_NET_NOT_CONNECTED;
break;
}
offData += cbRead;
}
if (RT_SUCCESS(rc))
{
ASMCompilerBarrier(); /* paranoia^3 */
cbData = *(uint32_t volatile *)pbData;
if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE)
{
/*
* Align the length and reallocate the return packet it necessary.
*/
cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT);
if (cbData > cbDataAlloced)
{
void *pvNew = RTMemRealloc(pbData, cbData);
if (pvNew)
{
pbData = (uint8_t *)pvNew;
cbDataAlloced = cbData;
}
else
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
{
/*
* Read the remainder of the data.
*/
while (offData < cbData)
{
size_t cbRead;
rc = RTTcpRead(hTcpClient, pbData + offData, cbData - offData, &cbRead);
if (RT_FAILURE(rc))
break;
if (cbRead == 0)
{
Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc));
rc = VERR_NET_NOT_CONNECTED;
break;
}
offData += cbRead;
}
}
}
else
rc = VERR_NET_PROTOCOL_ERROR;
}
if (RT_SUCCESS(rc))
*ppPktHdr = (PTXSPKTHDR)pbData;
else
{
/*
* Deal with errors.
*/
if (rc == VERR_INTERRUPTED)
{
/* stash it away for the next call. */
g_cbTcpStashed = cbData;
g_cbTcpStashedAlloced = cbDataAlloced;
g_pbTcpStashed = pbData;
}
else
{
RTMemFree(pbData);
/* assume fatal connection error. */
Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient));
txsTcpDisconnectClient();
}
}
return rc;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnPollSetAdd}
*/
static DECLCALLBACK(int) txsTcpPollSetAdd(RTPOLLSET hPollSet, uint32_t idStart)
{
return RTPollSetAddSocket(hPollSet, g_hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart);
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnPollIn}
*/
static DECLCALLBACK(bool) txsTcpPollIn(void)
{
RTSOCKET hTcpClient = g_hTcpClient;
if (hTcpClient == NIL_RTSOCKET)
return false;
int rc = RTTcpSelectOne(hTcpClient, 0/*cMillies*/);
return RT_SUCCESS(rc);
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnTerm}
*/
static DECLCALLBACK(void) txsTcpTerm(void)
{
/* Signal thread */
if (RTCritSectIsInitialized(&g_TcpCritSect))
{
RTCritSectEnter(&g_TcpCritSect);
g_fTcpStopConnecting = true;
RTCritSectLeave(&g_TcpCritSect);
}
if (g_hThreadTcpConnect != NIL_RTTHREAD)
{
RTThreadUserSignal(g_hThreadTcpConnect);
RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie);
}
/* Shut down the server (will wake up thread). */
if (g_pTcpServer)
{
Log(("txsTcpTerm: Destroying server...\n"));
int rc = RTTcpServerDestroy(g_pTcpServer);
if (RT_FAILURE(rc))
RTMsgInfo("RTTcpServerDestroy failed in txsTcpTerm: %Rrc", rc);
g_pTcpServer = NULL;
}
/* Shut down client */
if (g_hTcpClient != NIL_RTSOCKET)
{
if (g_fTcpClientFromServer)
{
Log(("txsTcpTerm: Disconnecting client...\n"));
int rc = RTTcpServerDisconnectClient2(g_hTcpClient);
if (RT_FAILURE(rc))
RTMsgInfo("RTTcpServerDisconnectClient2(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc);
}
else
{
int rc = RTTcpClientClose(g_hTcpClient);
if (RT_FAILURE(rc))
RTMsgInfo("RTTcpClientClose(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc);
}
g_hTcpClient = NIL_RTSOCKET;
}
/* Clean up stashing. */
RTMemFree(g_pbTcpStashed);
g_pbTcpStashed = NULL;
g_cbTcpStashed = 0;
g_cbTcpStashedAlloced = 0;
/* Wait for the thread (they should've had some time to quit by now). */
txsTcpConnectWaitOnThreads(15000);
/* Finally, clean up the critical section. */
if (RTCritSectIsInitialized(&g_TcpCritSect))
RTCritSectDelete(&g_TcpCritSect);
Log(("txsTcpTerm: done\n"));
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnInit}
*/
static DECLCALLBACK(int) txsTcpInit(void)
{
int rc = RTCritSectInit(&g_TcpCritSect);
if (RT_SUCCESS(rc) && g_enmTcpMode != TXSTCPMODE_CLIENT)
{
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",
g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc);
uint64_t StartMs = RTTimeMilliTS();
do
{
RTThreadSleep(1000);
rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer);
} while ( rc == VERR_NET_DOWN
&& RTTimeMilliTS() - StartMs < 20000);
if (RT_SUCCESS(rc))
RTMsgInfo("RTTcpServerCreateEx succceeded.\n");
}
if (RT_FAILURE(rc))
{
g_pTcpServer = NULL;
RTCritSectDelete(&g_TcpCritSect);
RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n",
g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc);
}
}
}
return rc;
}
/** Options */
enum TXSTCPOPT
{
TXSTCPOPT_MODE = 1000,
TXSTCPOPT_BIND_ADDRESS,
TXSTCPOPT_BIND_PORT,
TXSTCPOPT_CONNECT_ADDRESS,
TXSTCPOPT_CONNECT_PORT,
/* legacy: */
TXSTCPOPT_LEGACY_PORT,
TXSTCPOPT_LEGACY_CONNECT
};
/**
* @interface_method_impl{TXSTRANSPORT,pfnOption}
*/
static DECLCALLBACK(int) txsTcpOption(int ch, PCRTGETOPTUNION pVal)
{
int rc;
switch (ch)
{
case TXSTCPOPT_MODE:
if (!strcmp(pVal->psz, "both"))
g_enmTcpMode = TXSTCPMODE_BOTH;
else if (!strcmp(pVal->psz, "client"))
g_enmTcpMode = TXSTCPMODE_CLIENT;
else if (!strcmp(pVal->psz, "server"))
g_enmTcpMode = TXSTCPMODE_SERVER;
else
return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid TCP mode: '%s'\n", pVal->psz);
break;
case TXSTCPOPT_BIND_ADDRESS:
rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz);
if (RT_FAILURE(rc))
return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc);
return VINF_SUCCESS;
case TXSTCPOPT_BIND_PORT:
g_uTcpBindPort = pVal->u16 == 0 ? TXS_TCP_DEF_BIND_PORT : pVal->u16;
break;
case TXSTCPOPT_LEGACY_CONNECT:
g_enmTcpMode = TXSTCPMODE_CLIENT;
/* fall thru */
case TXSTCPOPT_CONNECT_ADDRESS:
rc = RTStrCopy(g_szTcpConnectAddr, sizeof(g_szTcpConnectAddr), pVal->psz);
if (RT_FAILURE(rc))
return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP connect address is too long (%Rrc)", rc);
if (!g_szTcpConnectAddr[0])
strcpy(g_szTcpConnectAddr, TXS_TCP_DEF_CONNECT_ADDRESS);
return VINF_SUCCESS;
case TXSTCPOPT_CONNECT_PORT:
g_uTcpConnectPort = pVal->u16 == 0 ? TXS_TCP_DEF_CONNECT_PORT : pVal->u16;
break;
case TXSTCPOPT_LEGACY_PORT:
if (pVal->u16 == 0)
{
g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT;
g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT;
}
else
{
g_uTcpBindPort = pVal->u16;
g_uTcpConnectPort = pVal->u16;
}
return VINF_SUCCESS;
}
return VERR_TRY_AGAIN;
}
/**
* @interface_method_impl{TXSTRANSPORT,pfnUsage}
*/
DECLCALLBACK(void) txsTcpUsage(PRTSTREAM pStream)
{
RTStrmPrintf(pStream,
" --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"
" Default: " TXS_TCP_DEF_CONNECT_ADDRESS "\n"
" --tcp-connect-port <port>\n"
" The port on the server to connect to in client mode.\n"
" Default: %u\n"
, TXS_TCP_DEF_BIND_PORT, TXS_TCP_DEF_CONNECT_PORT);
}
/** Command line options for the TCP/IP transport layer. */
static const RTGETOPTDEF g_TcpOpts[] =
{
{ "--tcp-mode", TXSTCPOPT_MODE, RTGETOPT_REQ_STRING },
{ "--tcp-bind-address", TXSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING },
{ "--tcp-bind-port", TXSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 },
{ "--tcp-connect-address", TXSTCPOPT_CONNECT_ADDRESS, RTGETOPT_REQ_STRING },
{ "--tcp-connect-port", TXSTCPOPT_CONNECT_PORT, RTGETOPT_REQ_UINT16 },
/* legacy */
{ "--tcp-port", TXSTCPOPT_LEGACY_PORT, RTGETOPT_REQ_UINT16 },
{ "--tcp-connect", TXSTCPOPT_LEGACY_CONNECT, RTGETOPT_REQ_STRING },
};
/** TCP/IP transport layer. */
const TXSTRANSPORT g_TcpTransport =
{
/* .szName = */ "tcp",
/* .pszDesc = */ "TCP/IP",
/* .cOpts = */ &g_TcpOpts[0],
/* .paOpts = */ RT_ELEMENTS(g_TcpOpts),
/* .pfnUsage = */ txsTcpUsage,
/* .pfnOption = */ txsTcpOption,
/* .pfnInit = */ txsTcpInit,
/* .pfnTerm = */ txsTcpTerm,
/* .pfnPollIn = */ txsTcpPollIn,
/* .pfnPollSetAdd = */ txsTcpPollSetAdd,
/* .pfnRecvPkt = */ txsTcpRecvPkt,
/* .pfnSendPkt = */ txsTcpSendPkt,
/* .pfnBabble = */ txsTcpBabble,
/* .pfnNotifyHowdy = */ txsTcpNotifyHowdy,
/* .pfnNotifyBye = */ txsTcpNotifyBye,
/* .pfnNotifyReboot = */ txsTcpNotifyReboot,
/* .u32EndMarker = */ UINT32_C(0x12345678)
};