summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/network
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/ValidationKit/utils/network
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/utils/network')
-rw-r--r--src/VBox/ValidationKit/utils/network/Makefile.kmk49
-rw-r--r--src/VBox/ValidationKit/utils/network/NetPerf.cpp2008
2 files changed, 2057 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/network/Makefile.kmk b/src/VBox/ValidationKit/utils/network/Makefile.kmk
new file mode 100644
index 00000000..7e5d6db3
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/network/Makefile.kmk
@@ -0,0 +1,49 @@
+# $Id: Makefile.kmk $
+## @file
+# VirtualBox Validation Kit - Network tests.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Network Performance Benchmark.
+#
+PROGRAMS += NetPerf
+NetPerf_TEMPLATE = VBoxValidationKitR3
+NetPerf_SOURCES = NetPerf.cpp
+
+$(evalcall def_vbox_validationkit_process_python_sources)
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/ValidationKit/utils/network/NetPerf.cpp b/src/VBox/ValidationKit/utils/network/NetPerf.cpp
new file mode 100644
index 00000000..ceb6a201
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/network/NetPerf.cpp
@@ -0,0 +1,2008 @@
+/* $Id: NetPerf.cpp $ */
+/** @file
+ * NetPerf - Network Performance Benchmark.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/thread.h>
+#include <iprt/test.h>
+#include <iprt/time.h>
+#include <iprt/timer.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Default TCP port (update help text if you change this) */
+#define NETPERF_DEFAULT_PORT 5002
+
+/** Default TCP packet size (bytes) */
+#define NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT 8192
+/** Default TCP packet size (bytes) */
+#define NETPERF_DEFAULT_PKT_SIZE_LATENCY 1024
+/** Maximum packet size possible (bytes). */
+#define NETPERF_MAX_PKT_SIZE _1M
+/** Minimum packet size possible (bytes). */
+#define NETPERF_MIN_PKT_SIZE sizeof(NETPERFHDR)
+
+/** Default timeout in (seconds) */
+#define NETPERF_DEFAULT_TIMEOUT 10
+/** Maximum timeout possible (seconds). */
+#define NETPERF_MAX_TIMEOUT 3600 /* 1h */
+/** Minimum timeout possible (seconds). */
+#define NETPERF_MIN_TIMEOUT 1
+
+/** The default warmup time (ms). */
+#define NETPERF_DEFAULT_WARMUP 1000 /* 1s */
+/** The maxium warmup time (ms). */
+#define NETPERF_MAX_WARMUP 60000 /* 60s */
+/** The minimum warmup time (ms). */
+#define NETPERF_MIN_WARMUP 1000 /* 1s */
+
+/** The default cool down time (ms). */
+#define NETPERF_DEFAULT_COOL_DOWN 1000 /* 1s */
+/** The maxium cool down time (ms). */
+#define NETPERF_MAX_COOL_DOWN 60000 /* 60s */
+/** The minimum cool down time (ms). */
+#define NETPERF_MIN_COOL_DOWN 1000 /* 1s */
+
+/** Maximum socket buffer size possible (bytes). */
+#define NETPERF_MAX_BUF_SIZE _128M
+/** Minimum socket buffer size possible (bytes). */
+#define NETPERF_MIN_BUF_SIZE 256
+
+/** The length of the length prefix used when submitting parameters and
+ * results. */
+#define NETPERF_LEN_PREFIX 4
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum NETPERFPROTO
+{
+ NETPERFPROTO_INVALID = 0,
+ NETPERFPROTO_TCP
+ //NETPERFPROTO_UDP
+} NETPERFPROTO;
+
+/**
+ * What kind of test we're performing.
+ */
+typedef enum NETPERFMODE
+{
+ NETPERFMODE_INVALID = 0,
+ /** Latency of a symmetric packet exchange. */
+ NETPERFMODE_LATENCY,
+ /** Separate throughput measurements for each direction. */
+ NETPERFMODE_THROUGHPUT,
+ /** Transmit throughput. */
+ NETPERFMODE_THROUGHPUT_XMIT,
+ /** Transmit throughput. */
+ NETPERFMODE_THROUGHPUT_RECV
+} NETPERFMODE;
+
+/**
+ * Statistics.
+ */
+typedef struct NETPERFSTATS
+{
+ uint64_t cTx;
+ uint64_t cRx;
+ uint64_t cEchos;
+ uint64_t cErrors;
+ uint64_t cNsElapsed;
+} NETPERFSTATS;
+
+/**
+ * Settings & a little bit of state.
+ */
+typedef struct NETPERFPARAMS
+{
+ /** @name Static settings
+ * @{ */
+ /** The TCP port number. */
+ uint32_t uPort;
+ /** Client: Use server statistcs. */
+ bool fServerStats;
+ /** Server: Quit after the first client. */
+ bool fSingleClient;
+ /** Send and receive buffer sizes for TCP sockets, zero if to use defaults. */
+ uint32_t cbBufferSize;
+ /** @} */
+
+ /** @name Dynamic settings
+ * @{ */
+ /** Disable send packet coalescing. */
+ bool fNoDelay;
+ /** Detect broken payloads. */
+ bool fCheckData;
+ /** The test mode. */
+ NETPERFMODE enmMode;
+ /** The number of seconds to run each of the test steps. */
+ uint32_t cSecTimeout;
+ /** Number of millisecond to spend warning up before testing. */
+ uint32_t cMsWarmup;
+ /** Number of millisecond to spend cooling down after the testing. */
+ uint32_t cMsCoolDown;
+ /** The packet size. */
+ uint32_t cbPacket;
+ /** @} */
+
+ /** @name State
+ * @{ */
+ RTSOCKET hSocket;
+ /** @} */
+} NETPERFPARAMS;
+
+/**
+ * Packet header used in tests.
+ *
+ * Need to indicate when we've timed out and it's time to reverse the roles or
+ * stop testing.
+ */
+typedef struct NETPERFHDR
+{
+ /** Magic value (little endian). */
+ uint32_t u32Magic;
+ /** State value. */
+ uint32_t u32State;
+ /** Sequence number (little endian). */
+ uint32_t u32Seq;
+ /** Reserved, must be zero. */
+ uint32_t u32Reserved;
+} NETPERFHDR;
+
+/** Magic value for NETPERFHDR::u32Magic. */
+#define NETPERFHDR_MAGIC UINT32_C(0xfeedf00d)
+
+/** @name Packet State (NETPERF::u32Magic)
+ * @{ */
+/** Warm up. */
+#define NETPERFHDR_WARMUP UINT32_C(0x0c0ffe01)
+/** The clock is running. */
+#define NETPERFHDR_TESTING UINT32_C(0x0c0ffe02)
+/** Stop the clock but continue the package flow. */
+#define NETPERFHDR_COOL_DOWN UINT32_C(0x0c0ffe03)
+/** Done, stop the clock if not done already and reply with results. */
+#define NETPERFHDR_DONE UINT32_C(0x0c0ffe04)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Connection start/identifier to make sure other end is NetPerf. */
+static const char g_ConnectStart[] = "yo! waaazzzzzaaaaup dude?";
+/** Start of parameters proposal made by the client. */
+static const char g_szStartParams[] = "deal?";
+/** All okay to start test */
+static const char g_szAck[] = "okay!";
+/** Negative. */
+static const char g_szNegative[] = "nope!";
+AssertCompile(sizeof(g_szAck) == sizeof(g_szNegative));
+/** Start of statistics. */
+static const char g_szStartStats[] = "dude, stats";
+
+/** Command line parameters */
+static const RTGETOPTDEF g_aCmdOptions[] =
+{
+ { "--server", 's', RTGETOPT_REQ_NOTHING },
+ { "--client", 'c', RTGETOPT_REQ_STRING },
+ { "--interval", 'i', RTGETOPT_REQ_UINT32 },
+ { "--port", 'p', RTGETOPT_REQ_UINT32 },
+ { "--len", 'l', RTGETOPT_REQ_UINT32 },
+ { "--nodelay", 'N', RTGETOPT_REQ_NOTHING },
+ { "--mode", 'm', RTGETOPT_REQ_STRING },
+ { "--warmup", 'w', RTGETOPT_REQ_UINT32 },
+ { "--cool-down", 'W', RTGETOPT_REQ_UINT32 },
+ { "--server-stats", 'S', RTGETOPT_REQ_NOTHING },
+ { "--single-client", '1', RTGETOPT_REQ_NOTHING },
+ { "--daemonize", 'd', RTGETOPT_REQ_NOTHING },
+ { "--daemonized", 'D', RTGETOPT_REQ_NOTHING },
+ { "--check-data", 'C', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--buffer-size", 'b', RTGETOPT_REQ_UINT32 },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
+};
+
+/** The test handle. */
+static RTTEST g_hTest;
+/** Verbosity level. */
+static uint32_t g_uVerbosity = 0;
+
+
+
+static void Usage(PRTSTREAM pStrm)
+{
+ char szExec[RTPATH_MAX];
+ RTStrmPrintf(pStrm, "usage: %s <-s|-c <host>> [options]\n",
+ RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
+ RTStrmPrintf(pStrm, "\n");
+ RTStrmPrintf(pStrm, "options: \n");
+
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
+ {
+ const char *pszHelp;
+ switch (g_aCmdOptions[i].iShort)
+ {
+ case 'h':
+ pszHelp = "Displays this help and exit";
+ break;
+ case 's':
+ pszHelp = "Run in server mode, waiting for clients (default)";
+ break;
+ case 'c':
+ pszHelp = "Run in client mode, connecting to <host>";
+ break;
+ case 'i':
+ pszHelp = "Interval in seconds to run the test (default " RT_XSTR(NETPERF_DEFAULT_TIMEOUT) " s)";
+ break;
+ case 'p':
+ pszHelp = "Server port to listen/connect to (default " RT_XSTR(NETPERF_DEFAULT_PORT) ")";
+ break;
+ case 'l':
+ pszHelp = "Packet size in bytes (defaults to " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_LATENCY)
+ " for latency and " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT) " for throughput)";
+ break;
+ case 'm':
+ pszHelp = "Test mode: latency (default), throughput, throughput-xmit or throughput-recv";
+ break;
+ case 'N':
+ pszHelp = "Set TCP no delay, disabling Nagle's algorithm";
+ break;
+ case 'S':
+ pszHelp = "Report server stats, ignored if server";
+ break;
+ case '1':
+ pszHelp = "Stop the server after the first client";
+ break;
+ case 'd':
+ pszHelp = "Daemonize if server, ignored if client";
+ break;
+ case 'D':
+ continue; /* internal */
+ case 'w':
+ pszHelp = "Warmup time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_WARMUP) " ms)";
+ break;
+ case 'W':
+ pszHelp = "Cool down time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_COOL_DOWN) " ms)";
+ break;
+ case 'C':
+ pszHelp = "Check payload data at the receiving end";
+ break;
+ case 'b':
+ pszHelp = "Send and receive buffer sizes for TCP";
+ break;
+ case 'v':
+ pszHelp = "Verbose execution.";
+ break;
+ default:
+ pszHelp = "Option undocumented";
+ break;
+ }
+ char szOpt[256];
+ RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
+ RTStrmPrintf(pStrm, " %-20s%s\n", szOpt, pszHelp);
+ }
+}
+
+/**
+ * Timer callback employed to set the stop indicator.
+ *
+ * This is used both by the client and server side.
+ *
+ * @param hTimer The timer, ignored.
+ * @param pvUser Pointer to the stop variable.
+ * @param iTick The tick, ignored.
+ */
+static DECLCALLBACK(void) netperfStopTimerCallback(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
+{
+ bool volatile *pfStop = (bool volatile *)pvUser;
+ if (g_uVerbosity > 0)
+ RTPrintf("Time's Up!\n");
+ ASMAtomicWriteBool(pfStop, true);
+ NOREF(hTimer); NOREF(iTick);
+}
+
+/**
+ * Sends a statistics packet to our peer.
+ *
+ * @returns IPRT status code.
+ * @param pStats The stats to send.
+ * @param hSocket The TCP socket to send them to.
+ */
+static int netperfSendStats(NETPERFSTATS const *pStats, RTSOCKET hSocket)
+{
+ char szBuf[256 + NETPERF_LEN_PREFIX];
+ size_t cch = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX,
+ "%s:%llu:%llu:%llu:%llu:%llu",
+ g_szStartStats,
+ pStats->cTx,
+ pStats->cRx,
+ pStats->cEchos,
+ pStats->cErrors,
+ pStats->cNsElapsed);
+
+ RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cch);
+ szBuf[NETPERF_LEN_PREFIX] = g_szStartStats[0];
+ Assert(strlen(szBuf) == cch + NETPERF_LEN_PREFIX);
+
+ int rc = RTTcpWrite(hSocket, szBuf, cch + NETPERF_LEN_PREFIX);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "stats: Failed to send stats: %Rrc\n", rc);
+
+ /*
+ * Wait for ACK.
+ */
+ rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "stats: failed to write stats: %Rrc\n", rc);
+ szBuf[sizeof(g_szAck) - 1] = '\0';
+ if (!strcmp(szBuf, g_szNegative))
+ return RTTestIFailedRc(rc, "stats: client failed to parse them\n");
+ if (strcmp(szBuf, g_szAck))
+ return RTTestIFailedRc(rc, "stats: got '%s' in instead of ack/nack\n", szBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Receives a statistics packet from our peer.
+ *
+ * @returns IPRT status code. Error signalled.
+ * @param pStats Where to receive the stats.
+ * @param hSocket The TCP socket to recevie them from.
+ */
+static int netperfRecvStats(NETPERFSTATS *pStats, RTSOCKET hSocket)
+{
+ /*
+ * Read the stats message.
+ */
+ /* the length prefix */
+ char szBuf[256 + NETPERF_LEN_PREFIX];
+ int rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "stats: failed to read stats prefix: %Rrc\n", rc);
+
+ szBuf[NETPERF_LEN_PREFIX] = '\0';
+ uint32_t cch;
+ rc = RTStrToUInt32Full(szBuf, 10, &cch);
+ if (rc != VINF_SUCCESS)
+ return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad stat length prefix: '%s' - %Rrc\n", szBuf, rc);
+ if (cch >= sizeof(szBuf))
+ return RTTestIFailedRc(VERR_BUFFER_OVERFLOW, "stats: too large: %u bytes\n", cch);
+
+ /* the actual message */
+ rc = RTTcpRead(hSocket, szBuf, cch, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "failed to read stats: %Rrc\n", rc);
+ szBuf[cch] = '\0';
+
+ /*
+ * Validate the message header.
+ */
+ if ( strncmp(szBuf, g_szStartStats, sizeof(g_szStartStats) - 1)
+ || szBuf[sizeof(g_szStartStats) - 1] != ':')
+ return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "stats: invalid packet start: '%s'\n", szBuf);
+ char *pszCur = &szBuf[sizeof(g_szStartStats)];
+
+ /*
+ * Parse it.
+ */
+ static const char * const s_apszNames[] =
+ {
+ "cTx", "cRx", "cEchos", "cErrors", "cNsElapsed"
+ };
+ uint64_t *apu64[RT_ELEMENTS(s_apszNames)] =
+ {
+ &pStats->cTx,
+ &pStats->cRx,
+ &pStats->cEchos,
+ &pStats->cErrors,
+ &pStats->cNsElapsed
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(apu64); i++)
+ {
+ if (!pszCur)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: missing %s\n", s_apszNames[i]);
+
+ char *pszNext = strchr(pszCur, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+ rc = RTStrToUInt64Full(pszCur, 10, apu64[i]);
+ if (rc != VINF_SUCCESS)
+ return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad value for %s: '%s' - %Rrc\n",
+ s_apszNames[i], pszCur, rc);
+
+ pszCur = pszNext;
+ }
+
+ if (pszCur)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: Unparsed data: '%s'\n", pszCur);
+
+ /*
+ * Send ACK.
+ */
+ rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "stats: failed to write ack: %Rrc\n", rc);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * TCP Throughput: Print the statistics.
+ *
+ * @param pSendStats Send stats.
+ * @param pRecvStats Receive stats.
+ * @param cbPacket Packet size.
+ */
+static void netperfPrintThroughputStats(NETPERFSTATS const *pSendStats, NETPERFSTATS const *pRecvStats, uint32_t cbPacket)
+{
+ RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES);
+
+ if (pSendStats)
+ {
+ double rdSecElapsed = (double)pSendStats->cNsElapsed / 1000000000.0;
+ RTTestIValue("Sends", pSendStats->cTx, RTTESTUNIT_PACKETS);
+ RTTestIValue("Send Interval", pSendStats->cNsElapsed, RTTESTUNIT_NS);
+ RTTestIValue("Send Throughput", (uint64_t)((double)(cbPacket * pSendStats->cTx) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC);
+ RTTestIValue("Send Rate", (uint64_t)((double)pSendStats->cTx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC);
+ RTTestIValue("Send Latency", (uint64_t)(rdSecElapsed / (double)pSendStats->cTx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET);
+ }
+
+ if (pRecvStats)
+ {
+ double rdSecElapsed = (double)pRecvStats->cNsElapsed / 1000000000.0;
+ RTTestIValue("Receives", pRecvStats->cRx, RTTESTUNIT_PACKETS);
+ RTTestIValue("Receive Interval", pRecvStats->cNsElapsed, RTTESTUNIT_NS);
+ RTTestIValue("Receive Throughput", (uint64_t)((double)(cbPacket * pRecvStats->cRx) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC);
+ RTTestIValue("Receive Rate", (uint64_t)((double)pRecvStats->cRx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC);
+ RTTestIValue("Receive Latency", (uint64_t)(rdSecElapsed / (double)pRecvStats->cRx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET);
+ }
+}
+
+/**
+ * TCP Throughput: Send data to the other party.
+ *
+ * @returns IPRT status code.
+ * @param pParams The TCP parameters block.
+ * @param pBuf The buffer we're using when sending.
+ * @param pSendStats Where to return the statistics.
+ */
+static int netperfTCPThroughputSend(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pSendStats)
+{
+ RT_ZERO(*pSendStats);
+
+ /*
+ * Create the timer
+ */
+ RTTIMERLR hTimer;
+ bool volatile fStop = false;
+ int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t u32Seq = 0;
+
+ RT_BZERO(pBuf, pParams->cbPacket);
+ pBuf->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC);
+ pBuf->u32State = 0;
+ pBuf->u32Seq = 0;
+ pBuf->u32Reserved = 0;
+
+ /*
+ * Warm up.
+ */
+ if (g_uVerbosity > 0)
+ RTPrintf("Warmup...\n");
+ pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP);
+ rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ while (!fStop)
+ {
+ u32Seq++;
+ pBuf->u32Seq = RT_H2LE_U32(u32Seq);
+ rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc);
+
+ /*
+ * The real thing.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (g_uVerbosity > 0)
+ RTPrintf("The real thing...\n");
+ pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING);
+ fStop = false;
+ rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t u64StartTS = RTTimeNanoTS();
+ while (!fStop)
+ {
+ u32Seq++;
+ pBuf->u32Seq = RT_H2LE_U32(u32Seq);
+ rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc);
+ break;
+ }
+ pSendStats->cTx++;
+ }
+ pSendStats->cNsElapsed = RTTimeNanoTS() - u64StartTS;
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc);
+ }
+
+ /*
+ * Cool down.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (g_uVerbosity > 0)
+ RTPrintf("Cool down...\n");
+ pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN);
+ fStop = false;
+ rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ while (!fStop)
+ {
+ u32Seq++;
+ pBuf->u32Seq = RT_H2LE_U32(u32Seq);
+ rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/cool down: %Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc);
+ }
+
+ /*
+ * Send DONE packet.
+ */
+ if (g_uVerbosity > 0)
+ RTPrintf("Done\n");
+ if (RT_SUCCESS(rc))
+ {
+ u32Seq++;
+ pBuf->u32Seq = RT_H2LE_U32(u32Seq);
+ pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE);
+ rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc);
+ }
+
+ RTTimerLRDestroy(hTimer);
+ }
+ else
+ RTTestIFailed("Failed to create timer object: %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * TCP Throughput: Receive data from the other party.
+ *
+ * @returns IPRT status code.
+ * @param pParams The TCP parameters block.
+ * @param pBuf The buffer we're using when sending.
+ * @param pStats Where to return the statistics.
+ */
+static int netperfTCPThroughputRecv(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pStats)
+{
+ RT_ZERO(*pStats);
+
+ int rc;
+ uint32_t u32Seq = 0;
+ uint64_t cRx = 0;
+ uint64_t u64StartTS = 0;
+ uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP);
+
+ for (;;)
+ {
+ rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL);
+ if (RT_FAILURE(rc))
+ {
+ pStats->cErrors++;
+ RTTestIFailed("RTTcpRead failed: %Rrc\n", rc);
+ break;
+ }
+ if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC)
+ || pBuf->u32Reserved != 0))
+ {
+ pStats->cErrors++;
+ RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved));
+ rc = VERR_INVALID_MAGIC;
+ break;
+ }
+
+ u32Seq += 1;
+ if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq)))
+ {
+ pStats->cErrors++;
+ RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq);
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ if (pParams->fCheckData && uState == RT_H2LE_U32_C(NETPERFHDR_TESTING))
+ {
+ unsigned i = sizeof(NETPERFHDR);
+ for (;i < pParams->cbPacket; ++i)
+ if (((unsigned char *)pBuf)[i])
+ break;
+ if (i != pParams->cbPacket)
+ {
+ pStats->cErrors++;
+ RTTestIFailed("Broken payload: at %#x got %#x, expected %#x\n", i, ((unsigned char *)pBuf)[i], 0);
+ rc = VERR_NOT_EQUAL;
+ break;
+ }
+ }
+ if (RT_LIKELY(pBuf->u32State == uState))
+ cRx++;
+ /*
+ * Validate and act on switch state.
+ */
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP)
+ && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING))
+ {
+ cRx = 0;
+ u64StartTS = RTTimeNanoTS();
+ uState = pBuf->u32State;
+ }
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING)
+ && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)
+ || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) )
+ {
+ pStats->cNsElapsed = RTTimeNanoTS() - u64StartTS;
+ pStats->cRx = cRx + 1;
+ uState = pBuf->u32State;
+ if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE))
+ break;
+ }
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)
+ && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE))
+ {
+ uState = pBuf->u32State;
+ break;
+ }
+ else
+ {
+ pStats->cErrors++;
+ RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n",
+ RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State));
+ rc = VERR_INVALID_MAGIC;
+ break;
+ }
+ }
+
+ AssertReturn(uState == RT_H2LE_U32_C(NETPERFHDR_DONE) || RT_FAILURE(rc), VERR_INVALID_STATE);
+ return rc;
+}
+
+
+/**
+ * Prints the statistics for the latency test.
+ *
+ * @param pStats The statistics.
+ * @param cbPacket The packet size in bytes.
+ */
+static void netperfPrintLatencyStats(NETPERFSTATS const *pStats, uint32_t cbPacket)
+{
+ double rdSecElapsed = (double)pStats->cNsElapsed / 1000000000.0;
+ RTTestIValue("Transmitted", pStats->cTx, RTTESTUNIT_PACKETS);
+ RTTestIValue("Successful echos", pStats->cEchos, RTTESTUNIT_PACKETS);
+ RTTestIValue("Errors", pStats->cErrors, RTTESTUNIT_PACKETS);
+ RTTestIValue("Interval", pStats->cNsElapsed, RTTESTUNIT_NS);
+ RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES);
+ RTTestIValue("Average rate", (uint64_t)((double)pStats->cEchos / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC);
+ RTTestIValue("Average throughput", (uint64_t)((double)(cbPacket * pStats->cEchos) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC);
+ RTTestIValue("Average latency", (uint64_t)(rdSecElapsed / (double)pStats->cEchos * 1000000000.0), RTTESTUNIT_NS_PER_ROUND_TRIP);
+ RTTestISubDone();
+}
+
+
+/**
+ * NETPERFMODE -> string.
+ *
+ * @returns readonly string.
+ * @param enmMode The mode.
+ */
+static const char *netperfModeToString(NETPERFMODE enmMode)
+{
+ switch (enmMode)
+ {
+ case NETPERFMODE_LATENCY: return "latency";
+ case NETPERFMODE_THROUGHPUT: return "throughput";
+ case NETPERFMODE_THROUGHPUT_XMIT: return "throughput-xmit";
+ case NETPERFMODE_THROUGHPUT_RECV: return "throughput-recv";
+ default: AssertFailed(); return "internal-error";
+ }
+}
+
+/**
+ * String -> NETPERFMODE.
+ *
+ * @returns The corresponding NETPERFMODE, NETPERFMODE_INVALID on failure.
+ * @param pszMode The mode string.
+ */
+static NETPERFMODE netperfModeFromString(const char *pszMode)
+{
+ if (!strcmp(pszMode, "latency"))
+ return NETPERFMODE_LATENCY;
+ if ( !strcmp(pszMode, "throughput")
+ || !strcmp(pszMode, "thruput") )
+ return NETPERFMODE_THROUGHPUT;
+ if ( !strcmp(pszMode, "throughput-xmit")
+ || !strcmp(pszMode, "thruput-xmit")
+ || !strcmp(pszMode, "xmit") )
+ return NETPERFMODE_THROUGHPUT_XMIT;
+ if ( !strcmp(pszMode, "throughput-recv")
+ || !strcmp(pszMode, "thruput-recv")
+ || !strcmp(pszMode, "recv") )
+ return NETPERFMODE_THROUGHPUT_RECV;
+ return NETPERFMODE_INVALID;
+}
+
+
+
+
+
+/**
+ * TCP Server: Throughput test.
+ *
+ * @returns IPRT status code.
+ * @param pParams The parameters to use for this test.
+ */
+static int netperfTCPServerDoThroughput(NETPERFPARAMS const *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Receive first, then Send. The reverse of the client.
+ */
+ NETPERFSTATS RecvStats;
+ int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats);
+ if (RT_SUCCESS(rc))
+ {
+ rc = netperfSendStats(&RecvStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS SendStats;
+ rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats);
+ if (RT_SUCCESS(rc))
+ {
+ rc = netperfSendStats(&SendStats, pParams->hSocket);
+ netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket);
+ }
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * TCP Server: Throughput xmit test (receive from client).
+ *
+ * @returns IPRT status code.
+ * @param pParams The parameters to use for this test.
+ */
+static int netperfTCPServerDoThroughputXmit(NETPERFPARAMS const *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Receive the transmitted data (reverse of client).
+ */
+ NETPERFSTATS RecvStats;
+ int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats);
+ if (RT_SUCCESS(rc))
+ {
+ rc = netperfSendStats(&RecvStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket);
+ }
+
+ return rc;
+}
+
+/**
+ * TCP Server: Throughput recv test (transmit to client).
+ *
+ * @returns IPRT status code.
+ * @param pParams The parameters to use for this test.
+ */
+static int netperfTCPServerDoThroughputRecv(NETPERFPARAMS const *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Send data to the client (reverse of client).
+ */
+ NETPERFSTATS SendStats;
+ int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats);
+ if (RT_SUCCESS(rc))
+ {
+ rc = netperfSendStats(&SendStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket);
+ }
+
+ return rc;
+}
+
+/**
+ * TCP Server: Latency test.
+ *
+ * @returns IPRT status code.
+ * @param pParams The parameters to use for this test.
+ */
+static int netperfTCPServerDoLatency(NETPERFPARAMS const *pParams)
+{
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Failed to allocated packet buffer of %u bytes.\n", pParams->cbPacket);
+
+ /*
+ * Ping pong with client.
+ */
+ int rc;
+ uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP);
+ uint32_t u32Seq = 0;
+ uint64_t cTx = 0;
+ uint64_t cRx = 0;
+ uint64_t u64StartTS = 0;
+ NETPERFSTATS Stats;
+ RT_ZERO(Stats);
+ for (;;)
+ {
+ rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("Failed to read data from client: %Rrc\n", rc);
+ break;
+ }
+
+ /*
+ * Validate the packet
+ */
+ if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC)
+ || pBuf->u32Reserved != 0))
+ {
+ RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved));
+ rc = VERR_INVALID_MAGIC;
+ break;
+ }
+
+ u32Seq += 1;
+ if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq)))
+ {
+ RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq);
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ /*
+ * Count the packet if the state remains unchanged.
+ */
+ if (RT_LIKELY(pBuf->u32State == uState))
+ cRx++;
+ /*
+ * Validate and act on the state transition.
+ */
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP)
+ && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING))
+ {
+ cRx = cTx = 0;
+ u64StartTS = RTTimeNanoTS();
+ uState = pBuf->u32State;
+ }
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING)
+ && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)
+ || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) )
+ {
+ Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS;
+ Stats.cEchos = cTx;
+ Stats.cTx = cTx;
+ Stats.cRx = cRx;
+ uState = pBuf->u32State;
+ if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE))
+ break;
+ }
+ else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)
+ && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE))
+ {
+ uState = pBuf->u32State;
+ break;
+ }
+ else
+ {
+ RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n",
+ RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State));
+ break;
+ }
+
+ /*
+ * Write same data back to client.
+ */
+ rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("Failed to write data to client: %Rrc\n", rc);
+ break;
+ }
+
+ cTx++;
+ }
+
+ /*
+ * Send stats to client and print them.
+ */
+ if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE))
+ netperfSendStats(&Stats, pParams->hSocket);
+
+ if ( uState == RT_H2LE_U32_C(NETPERFHDR_DONE)
+ || uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN))
+ netperfPrintLatencyStats(&Stats, pParams->cbPacket);
+
+ RTMemFree(pBuf);
+ return rc;
+}
+
+/**
+ * Parses the parameters the client has sent us.
+ *
+ * @returns IPRT status code. Message has been shown on failure.
+ * @param pParams The parameter structure to store the parameters
+ * in.
+ * @param pszParams The parameter string sent by the client.
+ */
+static int netperfTCPServerParseParams(NETPERFPARAMS *pParams, char *pszParams)
+{
+ /*
+ * Set defaults for the dynamic settings.
+ */
+ pParams->fNoDelay = false;
+ pParams->enmMode = NETPERFMODE_LATENCY;
+ pParams->cSecTimeout = NETPERF_DEFAULT_WARMUP;
+ pParams->cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN;
+ pParams->cMsWarmup = NETPERF_DEFAULT_WARMUP;
+ pParams->cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY;
+
+ /*
+ * Parse the client parameters.
+ */
+ /* first arg: transport type. [mandatory] */
+ char *pszCur = strchr(pszParams, ':');
+ if (!pszCur)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: No colon\n");
+ char *pszNext = strchr(++pszCur, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+ if (strcmp(pszCur, "TCP"))
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid transport type: \"%s\"\n", pszCur);
+ pszCur = pszNext;
+
+ /* second arg: mode. [mandatory] */
+ if (!pszCur)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Missing test mode\n");
+ pszNext = strchr(pszCur, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+ pParams->enmMode = netperfModeFromString(pszCur);
+ if (pParams->enmMode == NETPERFMODE_INVALID)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid test mode: \"%s\"\n", pszCur);
+ pszCur = pszNext;
+
+ /*
+ * The remainder are uint32_t or bool.
+ */
+ struct
+ {
+ bool fBool;
+ bool fMandatory;
+ void *pvValue;
+ uint32_t uMin;
+ uint32_t uMax;
+ const char *pszName;
+ } aElements[] =
+ {
+ { false, true, &pParams->cSecTimeout, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT, "timeout" },
+ { false, true, &pParams->cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE, "packet size" },
+ { false, true, &pParams->cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP, "warmup period" },
+ { false, true, &pParams->cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN, "cool down period" },
+ { true, true, &pParams->fNoDelay, false, true, "no delay" },
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(aElements); i++)
+ {
+ if (!pszCur)
+ return aElements[i].fMandatory
+ ? RTTestIFailedRc(VERR_PARSE_ERROR, "client params: missing %s\n", aElements[i].pszName)
+ : VINF_SUCCESS;
+
+ pszNext = strchr(pszCur, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+ uint32_t u32;
+ int rc = RTStrToUInt32Full(pszCur, 10, &u32);
+ if (rc != VINF_SUCCESS)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: bad %s value \"%s\": %Rrc\n",
+ aElements[i].pszName, pszCur, rc);
+
+ if ( u32 < aElements[i].uMin
+ || u32 > aElements[i].uMax)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: %s %u s is out of range (%u..%u)\n",
+ aElements[i].pszName, u32, aElements[i].uMin, aElements[i].uMax);
+ if (aElements[i].fBool)
+ *(bool *)aElements[i].pvValue = u32 ? true : false;
+ else
+ *(uint32_t *)aElements[i].pvValue = u32;
+
+ pszCur = pszNext;
+ }
+
+ /* Fail if too many elements. */
+ if (pszCur)
+ return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: too many elements: \"%s\"\n",
+ pszCur);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TCP server callback that handles one client connection.
+ *
+ * @returns IPRT status code. VERR_TCP_SERVER_STOP is special.
+ * @param hSocket The client socket.
+ * @param pvUser Our parameters.
+ */
+static DECLCALLBACK(int) netperfTCPServerWorker(RTSOCKET hSocket, void *pvUser)
+{
+ NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser;
+ AssertReturn(pParams, VERR_INVALID_POINTER);
+
+ pParams->hSocket = hSocket;
+
+ RTNETADDR Addr;
+ int rc = RTTcpGetPeerAddress(hSocket, &Addr);
+ if (RT_SUCCESS(rc))
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Client connected from %RTnaddr\n", &Addr);
+ else
+ {
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Failed to get client details: %Rrc\n", rc);
+ Addr.enmType = RTNETADDRTYPE_INVALID;
+ }
+
+ /*
+ * Adjust send and receive buffer sizes if necessary.
+ */
+ if (pParams->cbBufferSize)
+ {
+ rc = RTTcpSetBufferSize(hSocket, pParams->cbBufferSize);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to set socket buffer sizes to %#x: %Rrc\n", pParams->cbBufferSize, rc);
+ }
+
+ /*
+ * Greet the other dude.
+ */
+ rc = RTTcpWrite(hSocket, g_ConnectStart, sizeof(g_ConnectStart) - 1);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to send connection start Id: %Rrc\n", rc);
+
+ /*
+ * Read connection parameters.
+ */
+ char szBuf[256];
+ rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc);
+ szBuf[NETPERF_LEN_PREFIX] = '\0';
+ uint32_t cchParams;
+ rc = RTStrToUInt32Full(szBuf, 10, &cchParams);
+ if (rc != VINF_SUCCESS)
+ return RTTestIFailedRc(RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR : rc,
+ "Failed to read connection parameters: %Rrc\n", rc);
+ if (cchParams >= sizeof(szBuf))
+ return RTTestIFailedRc(VERR_TOO_MUCH_DATA, "parameter packet is too big (%u bytes)\n", cchParams);
+ rc = RTTcpRead(hSocket, szBuf, cchParams, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc);
+ szBuf[cchParams] = '\0';
+
+ if (strncmp(szBuf, g_szStartParams, sizeof(g_szStartParams) - 1))
+ return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid connection parameters '%s'\n", szBuf);
+
+ /*
+ * Parse the parameters and signal whether we've got a deal or not.
+ */
+ rc = netperfTCPServerParseParams(pParams, szBuf);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = RTTcpWrite(hSocket, g_szNegative, sizeof(g_szNegative) - 1);
+ if (RT_FAILURE(rc2))
+ RTTestIFailed("Failed to send negative ack: %Rrc\n", rc2);
+ return rc;
+ }
+
+ if (Addr.enmType != RTNETADDRTYPE_INVALID)
+ RTTestISubF("%RTnaddr - %s, %u s, %u bytes", &Addr,
+ netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket);
+ else
+ RTTestISubF("Unknown - %s, %u s, %u bytes",
+ netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket);
+
+ rc = RTTcpSetSendCoalescing(hSocket, !pParams->fNoDelay);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to apply no-delay option (%RTbool): %Rrc\n", pParams->fNoDelay, rc);
+
+ rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to send start test commend to client: %Rrc\n", rc);
+
+ /*
+ * Take action according to our mode.
+ */
+ switch (pParams->enmMode)
+ {
+ case NETPERFMODE_LATENCY:
+ rc = netperfTCPServerDoLatency(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT:
+ rc = netperfTCPServerDoThroughput(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT_XMIT:
+ rc = netperfTCPServerDoThroughputXmit(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT_RECV:
+ rc = netperfTCPServerDoThroughputRecv(pParams);
+ break;
+
+ case NETPERFMODE_INVALID:
+ rc = VERR_INTERNAL_ERROR;
+ break;
+
+ /* no default! */
+ }
+ if (rc == VERR_NO_MEMORY)
+ return VERR_TCP_SERVER_STOP;
+
+ /*
+ * Wait for other clients or quit.
+ */
+ if (pParams->fSingleClient)
+ return VERR_TCP_SERVER_STOP;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TCP server.
+ *
+ * @returns IPRT status code.
+ * @param pParams The TCP parameter block.
+ */
+static int netperfTCPServer(NETPERFPARAMS *pParams)
+{
+ /*
+ * Spawn the TCP server thread & listen.
+ */
+ PRTTCPSERVER pServer;
+ int rc = RTTcpServerCreateEx(NULL, pParams->uPort, &pServer);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Server listening on TCP port %d\n", pParams->uPort);
+ rc = RTTcpServerListen(pServer, netperfTCPServerWorker, pParams);
+ RTTcpServerDestroy(pServer);
+ }
+ else
+ RTPrintf("Failed to create TCP server thread: %Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * The server part.
+ *
+ * @returns Exit code.
+ * @param enmProto The protocol.
+ * @param pParams The parameter block.
+ */
+static RTEXITCODE netperfServer(NETPERFPROTO enmProto, NETPERFPARAMS *pParams)
+{
+
+ switch (enmProto)
+ {
+ case NETPERFPROTO_TCP:
+ {
+ int rc = netperfTCPServer(pParams);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+ }
+
+ default:
+ RTTestIFailed("Protocol not supported.\n");
+ return RTEXITCODE_FAILURE;
+ }
+}
+
+
+
+
+
+/**
+ * TCP client: Do the throughput test.
+ *
+ * @returns IPRT status code
+ * @param pParams The parameters.
+ */
+static int netperfTCPClientDoThroughput(NETPERFPARAMS *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Send first, then Receive.
+ */
+ NETPERFSTATS SendStats;
+ int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS SrvSendStats;
+ rc = netperfRecvStats(&SrvSendStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS RecvStats;
+ rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS SrvRecvStats;
+ rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ if (pParams->fServerStats)
+ netperfPrintThroughputStats(&SrvSendStats, &SrvRecvStats, pParams->cbPacket);
+ else
+ netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket);
+ }
+ }
+ }
+ }
+
+ RTTestISubDone();
+ return rc;
+}
+
+/**
+ * TCP client: Do the throughput xmit test.
+ *
+ * @returns IPRT status code
+ * @param pParams The parameters.
+ */
+static int netperfTCPClientDoThroughputXmit(NETPERFPARAMS *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Do the job.
+ */
+ NETPERFSTATS SendStats;
+ int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS SrvSendStats;
+ rc = netperfRecvStats(&SrvSendStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ if (pParams->fServerStats)
+ netperfPrintThroughputStats(&SrvSendStats, NULL, pParams->cbPacket);
+ else
+ netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket);
+ }
+ }
+
+ RTTestISubDone();
+ return rc;
+}
+
+/**
+ * TCP client: Do the throughput recv test.
+ *
+ * @returns IPRT status code
+ * @param pParams The parameters.
+ */
+static int netperfTCPClientDoThroughputRecv(NETPERFPARAMS *pParams)
+{
+ /*
+ * Allocate the buffer.
+ */
+ NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!pBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ /*
+ * Do the job.
+ */
+ NETPERFSTATS RecvStats;
+ int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats);
+ if (RT_SUCCESS(rc))
+ {
+ NETPERFSTATS SrvRecvStats;
+ rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ if (pParams->fServerStats)
+ netperfPrintThroughputStats(NULL, &SrvRecvStats, pParams->cbPacket);
+ else
+ netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket);
+ }
+ }
+
+ RTTestISubDone();
+ return rc;
+}
+
+/**
+ * TCP client: Do the latency test.
+ *
+ * @returns IPRT status code
+ * @param pParams The parameters.
+ */
+static int netperfTCPClientDoLatency(NETPERFPARAMS *pParams)
+{
+ /*
+ * Generate a selection of packages before we start, after all we're not
+ * benchmarking the random number generator, are we. :-)
+ */
+ void *pvReadBuf = RTMemAllocZ(pParams->cbPacket);
+ if (!pvReadBuf)
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+
+ size_t i;
+ NETPERFHDR *apPackets[256];
+ for (i = 0; i < RT_ELEMENTS(apPackets); i++)
+ {
+ apPackets[i] = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket);
+ if (!apPackets[i])
+ {
+ while (i-- > 0)
+ RTMemFree(apPackets[i]);
+ RTMemFree(pvReadBuf);
+ return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory");
+ }
+ RTRandBytes(apPackets[i], pParams->cbPacket);
+ apPackets[i]->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC);
+ apPackets[i]->u32State = 0;
+ apPackets[i]->u32Seq = 0;
+ apPackets[i]->u32Reserved = 0;
+ }
+
+ /*
+ * Create & start a timer to eventually disconnect.
+ */
+ bool volatile fStop = false;
+ RTTIMERLR hTimer;
+ int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t u32Seq = 0;
+ NETPERFSTATS Stats;
+ RT_ZERO(Stats);
+
+ /*
+ * Warm up.
+ */
+ if (g_uVerbosity > 0)
+ RTPrintf("Warmup...\n");
+ rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ while (!fStop)
+ {
+ NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)];
+ u32Seq++;
+ pPacket->u32Seq = RT_H2LE_U32(u32Seq);
+ pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP);
+ rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc);
+ break;
+ }
+ rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc);
+
+ /*
+ * The real thing.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (g_uVerbosity > 0)
+ RTPrintf("The real thing...\n");
+ fStop = false;
+ rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t u64StartTS = RTTimeNanoTS();
+ while (!fStop)
+ {
+ NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)];
+ u32Seq++;
+ pPacket->u32Seq = RT_H2LE_U32(u32Seq);
+ pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING);
+ rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc);
+ break;
+ }
+ Stats.cTx++;
+
+ rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpRead/testing: %Rrc\n", rc);
+ break;
+ }
+ Stats.cRx++;
+
+ if (!memcmp(pvReadBuf, pPacket, pParams->cbPacket))
+ Stats.cEchos++;
+ else
+ Stats.cErrors++;
+ }
+ Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS;
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc);
+ }
+
+ /*
+ * Cool down.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (g_uVerbosity > 0)
+ RTPrintf("Cool down...\n");
+ fStop = false;
+ rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */);
+ if (RT_SUCCESS(rc))
+ {
+ while (!fStop)
+ {
+ NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)];
+ u32Seq++;
+ pPacket->u32Seq = RT_H2LE_U32(u32Seq);
+ pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN);
+ rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc);
+ break;
+ }
+ rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ else
+ RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc);
+ }
+
+ /*
+ * Send DONE packet.
+ */
+ if (g_uVerbosity > 0)
+ RTPrintf("Done\n");
+ if (RT_SUCCESS(rc))
+ {
+ u32Seq++;
+ NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)];
+ pPacket->u32Seq = RT_H2LE_U32(u32Seq);
+ pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE);
+ rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket);
+ if (RT_FAILURE(rc))
+ RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc);
+ }
+
+
+ /*
+ * Get and print stats.
+ */
+ NETPERFSTATS SrvStats;
+ if (RT_SUCCESS(rc))
+ {
+ rc = netperfRecvStats(&SrvStats, pParams->hSocket);
+ if (RT_SUCCESS(rc) && pParams->fServerStats)
+ netperfPrintLatencyStats(&SrvStats, pParams->cbPacket);
+ else if (!pParams->fServerStats)
+ netperfPrintLatencyStats(&Stats, pParams->cbPacket);
+ }
+
+ /* clean up*/
+ RTTimerLRDestroy(hTimer);
+ }
+ else
+ RTTestIFailed("Failed to create timer object: %Rrc\n", rc);
+ for (i = 0; i < RT_ELEMENTS(apPackets); i++)
+ RTMemFree(apPackets[i]);
+
+ RTMemFree(pvReadBuf);
+
+ return rc;
+}
+
+/**
+ * TCP client test driver.
+ *
+ * @returns IPRT status code
+ * @param pszServer The server name.
+ * @param pParams The parameter structure.
+ */
+static int netperfTCPClient(const char *pszServer, NETPERFPARAMS *pParams)
+{
+ AssertReturn(pParams, VERR_INVALID_POINTER);
+ RTTestISubF("TCP - %u s, %u bytes%s", pParams->cSecTimeout,
+ pParams->cbPacket, pParams->fNoDelay ? ", no delay" : "");
+
+ RTSOCKET hSocket = NIL_RTSOCKET;
+ int rc = RTTcpClientConnect(pszServer, pParams->uPort, &hSocket);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to connect to %s on port %u: %Rrc\n", pszServer, pParams->uPort, rc);
+ pParams->hSocket = hSocket;
+
+ /*
+ * Disable send coalescing (no-delay).
+ */
+ if (pParams->fNoDelay)
+ {
+ rc = RTTcpSetSendCoalescing(hSocket, false /*fEnable*/);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to set no-delay option: %Rrc\n", rc);
+ }
+
+ /*
+ * Adjust send and receive buffer sizes if necessary.
+ */
+ if (pParams->cbBufferSize)
+ {
+ rc = RTTcpSetBufferSize(hSocket, pParams->cbBufferSize);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to set socket buffer sizes to %#x: %Rrc\n", pParams->cbBufferSize, rc);
+ }
+
+ /*
+ * Verify the super secret Start Connect Id to start the connection.
+ */
+ char szBuf[256 + NETPERF_LEN_PREFIX];
+ RT_ZERO(szBuf);
+ rc = RTTcpRead(hSocket, szBuf, sizeof(g_ConnectStart) - 1, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to read connection initializer: %Rrc\n", rc);
+
+ if (strcmp(szBuf, g_ConnectStart))
+ return RTTestIFailedRc(VERR_INVALID_MAGIC, "Invalid connection initializer '%s'\n", szBuf);
+
+ /*
+ * Send all the dynamic parameters to the server.
+ * (If the server is newer than the client, it will select default for any
+ * missing parameters.)
+ */
+ size_t cchParams = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX,
+ "%s:%s:%s:%u:%u:%u:%u:%u",
+ g_szStartParams,
+ "TCP",
+ netperfModeToString(pParams->enmMode),
+ pParams->cSecTimeout,
+ pParams->cbPacket,
+ pParams->cMsWarmup,
+ pParams->cMsCoolDown,
+ pParams->fNoDelay);
+ RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cchParams);
+ szBuf[NETPERF_LEN_PREFIX] = g_szStartParams[0];
+ Assert(strlen(szBuf) == NETPERF_LEN_PREFIX + cchParams);
+ rc = RTTcpWrite(hSocket, szBuf, NETPERF_LEN_PREFIX + cchParams);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to send connection parameters: %Rrc\n", rc);
+
+ /*
+ * Wait for acknowledgment.
+ */
+ rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL);
+ if (RT_FAILURE(rc))
+ return RTTestIFailedRc(rc, "Failed to send parameters: %Rrc\n", rc);
+ szBuf[sizeof(g_szAck) - 1] = '\0';
+
+ if (!strcmp(szBuf, g_szNegative))
+ return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Server failed to accept packet size of %u bytes.\n", pParams->cbPacket);
+ if (strcmp(szBuf, g_szAck))
+ return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid response from server '%s'\n", szBuf);
+
+ /*
+ * Take action according to our mode.
+ */
+ switch (pParams->enmMode)
+ {
+ case NETPERFMODE_LATENCY:
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the latency test for %u seconds.\n",
+ pszServer, pParams->uPort, pParams->cSecTimeout);
+ rc = netperfTCPClientDoLatency(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT:
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput test for %u seconds in each direction.\n",
+ pszServer, pParams->uPort, pParams->cSecTimeout);
+ rc = netperfTCPClientDoThroughput(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT_XMIT:
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-xmit test for %u seconds.\n",
+ pszServer, pParams->uPort, pParams->cSecTimeout);
+ rc = netperfTCPClientDoThroughputXmit(pParams);
+ break;
+
+ case NETPERFMODE_THROUGHPUT_RECV:
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-recv test for %u seconds.\n",
+ pszServer, pParams->uPort, pParams->cSecTimeout);
+ rc = netperfTCPClientDoThroughputRecv(pParams);
+ break;
+
+ case NETPERFMODE_INVALID:
+ rc = VERR_INTERNAL_ERROR;
+ break;
+
+ /* no default! */
+ }
+ return rc;
+}
+
+/**
+ * The client part.
+ *
+ * @returns Exit code.
+ * @param enmProto The protocol.
+ * @param pszServer The server name.
+ * @param pvUser The parameter block as opaque user data.
+ */
+static RTEXITCODE netperfClient(NETPERFPROTO enmProto, const char *pszServer, void *pvUser)
+{
+ switch (enmProto)
+ {
+ case NETPERFPROTO_TCP:
+ {
+ NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser;
+ int rc = netperfTCPClient(pszServer, pParams);
+ if (pParams->hSocket != NIL_RTSOCKET)
+ {
+ RTTcpClientClose(pParams->hSocket);
+ pParams->hSocket = NIL_RTSOCKET;
+ }
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+ }
+
+ default:
+ RTTestIFailed("Protocol not supported.\n");
+ return RTEXITCODE_FAILURE;
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init IPRT and globals.
+ */
+ int rc = RTTestInitAndCreate("NetPerf", &g_hTest);
+ if (rc)
+ return rc;
+
+ /*
+ * Special case.
+ */
+ if (argc < 2)
+ {
+ RTTestFailed(g_hTest, "No arguments given.");
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+
+ /*
+ * Default values.
+ */
+ NETPERFPROTO enmProtocol = NETPERFPROTO_TCP;
+ bool fServer = true;
+ bool fDaemonize = false;
+ bool fDaemonized = false;
+ bool fPacketSizeSet = false;
+ const char *pszServerAddress= NULL;
+
+ NETPERFPARAMS Params;
+ Params.uPort = NETPERF_DEFAULT_PORT;
+ Params.fServerStats = false;
+ Params.fSingleClient = false;
+
+ Params.fNoDelay = false;
+ Params.fCheckData = false;
+ Params.enmMode = NETPERFMODE_LATENCY;
+ Params.cSecTimeout = NETPERF_DEFAULT_TIMEOUT;
+ Params.cMsWarmup = NETPERF_DEFAULT_WARMUP;
+ Params.cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN;
+ Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY;
+ Params.cbBufferSize = 0;
+
+ Params.hSocket = NIL_RTSOCKET;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 's':
+ fServer = true;
+ break;
+
+ case 'c':
+ fServer = false;
+ pszServerAddress = ValueUnion.psz;
+ break;
+
+ case 'd':
+ fDaemonize = true;
+ break;
+
+ case 'D':
+ fDaemonized = true;
+ break;
+
+ case 'i':
+ Params.cSecTimeout = ValueUnion.u32;
+ if ( Params.cSecTimeout < NETPERF_MIN_TIMEOUT
+ || Params.cSecTimeout > NETPERF_MAX_TIMEOUT)
+ {
+ RTTestFailed(g_hTest, "Invalid interval %u s, valid range: %u-%u\n",
+ Params.cbPacket, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ break;
+
+ case 'l':
+ Params.cbPacket = ValueUnion.u32;
+ if ( Params.cbPacket < NETPERF_MIN_PKT_SIZE
+ || Params.cbPacket > NETPERF_MAX_PKT_SIZE)
+ {
+ RTTestFailed(g_hTest, "Invalid packet size %u bytes, valid range: %u-%u\n",
+ Params.cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ fPacketSizeSet = true;
+ break;
+
+ case 'm':
+ Params.enmMode = netperfModeFromString(ValueUnion.psz);
+ if (Params.enmMode == NETPERFMODE_INVALID)
+ {
+ RTTestFailed(g_hTest, "Invalid test mode: \"%s\"\n", ValueUnion.psz);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ if (!fPacketSizeSet)
+ switch (Params.enmMode)
+ {
+ case NETPERFMODE_LATENCY:
+ Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY;
+ break;
+ case NETPERFMODE_THROUGHPUT:
+ case NETPERFMODE_THROUGHPUT_XMIT:
+ case NETPERFMODE_THROUGHPUT_RECV:
+ Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT;
+ break;
+ case NETPERFMODE_INVALID:
+ break;
+ /* no default! */
+ }
+ break;
+
+ case 'p':
+ Params.uPort = ValueUnion.u32;
+ break;
+
+ case 'N':
+ Params.fNoDelay = true;
+ break;
+
+ case 'S':
+ Params.fServerStats = true;
+ break;
+
+ case '1':
+ Params.fSingleClient = true;
+ break;
+
+ case 'v':
+ g_uVerbosity++;
+ break;
+
+ case 'h':
+ Usage(g_pStdOut);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'w':
+ Params.cMsWarmup = ValueUnion.u32;
+ if ( Params.cMsWarmup < NETPERF_MIN_WARMUP
+ || Params.cMsWarmup > NETPERF_MAX_WARMUP)
+ {
+ RTTestFailed(g_hTest, "invalid warmup time %u ms, valid range: %u-%u\n",
+ Params.cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ break;
+
+ case 'W':
+ Params.cMsCoolDown = ValueUnion.u32;
+ if ( Params.cMsCoolDown < NETPERF_MIN_COOL_DOWN
+ || Params.cMsCoolDown > NETPERF_MAX_COOL_DOWN)
+ {
+ RTTestFailed(g_hTest, "invalid cool down time %u ms, valid range: %u-%u\n",
+ Params.cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ break;
+
+ case 'C':
+ Params.fCheckData = true;
+ break;
+
+ case 'b':
+ Params.cbBufferSize = ValueUnion.u32;
+ if ( ( Params.cbBufferSize < NETPERF_MIN_BUF_SIZE
+ || Params.cbBufferSize > NETPERF_MAX_BUF_SIZE)
+ && Params.cbBufferSize != 0)
+ {
+ RTTestFailed(g_hTest, "Invalid packet size %u bytes, valid range: %u-%u or 0\n",
+ Params.cbBufferSize, NETPERF_MIN_BUF_SIZE, NETPERF_MAX_BUF_SIZE);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ break;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Handle the server process daemoniziation.
+ */
+ if (fDaemonize && !fDaemonized && fServer)
+ {
+ rc = RTProcDaemonize(argv, "--daemonized");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize failed: %Rrc\n", rc);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /*
+ * Get down to business.
+ */
+ RTTestBanner(g_hTest);
+ if (fServer)
+ rc = netperfServer(enmProtocol, &Params);
+ else if (pszServerAddress)
+ rc = netperfClient(enmProtocol, pszServerAddress, &Params);
+ else
+ RTTestFailed(g_hTest, "missing server address to connect to\n");
+
+ RTEXITCODE rc2 = RTTestSummaryAndDestroy(g_hTest);
+ return rc2 != RTEXITCODE_FAILURE ? (RTEXITCODE)rc2 : rc;
+}
+