summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Network/DrvCloudTunnel.cpp
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/Devices/Network/DrvCloudTunnel.cpp
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/Devices/Network/DrvCloudTunnel.cpp')
-rw-r--r--src/VBox/Devices/Network/DrvCloudTunnel.cpp1825
1 files changed, 1825 insertions, 0 deletions
diff --git a/src/VBox/Devices/Network/DrvCloudTunnel.cpp b/src/VBox/Devices/Network/DrvCloudTunnel.cpp
new file mode 100644
index 00000000..458d89b9
--- /dev/null
+++ b/src/VBox/Devices/Network/DrvCloudTunnel.cpp
@@ -0,0 +1,1825 @@
+/* $Id: DrvCloudTunnel.cpp $ */
+/** @file
+ * DrvCloudTunnel - Cloud tunnel network transport driver
+ *
+ * Based on code contributed by Christophe Devriese
+ */
+
+/*
+ * Copyright (C) 2022-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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_CTUN
+#include <VBox/log.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmnetifs.h>
+#include <VBox/vmm/pdmnetinline.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/uuid.h>
+#include <iprt/req.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/critsect.h>
+
+#include "VBoxDD.h"
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+typedef int socklen_t;
+#else
+# include <errno.h>
+ typedef int SOCKET;
+# define closesocket close
+# define INVALID_SOCKET -1
+# define SOCKET_ERROR -1
+DECLINLINE(int) WSAGetLastError() { return errno; }
+#endif
+
+/* Prevent inclusion of Winsock2.h */
+#define _WINSOCK2API_
+#include <libssh/libssh.h>
+#include <libssh/callbacks.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Cloud tunnel driver instance data.
+ *
+ * @implements PDMINETWORKUP
+ */
+typedef struct DRVCLOUDTUNNEL
+{
+ /** The network interface. */
+ PDMINETWORKUP INetworkUp;
+ /** The network interface. */
+ PPDMINETWORKDOWN pIAboveNet;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Cloud instance private key. */
+ ssh_key SshKey;
+ /** Cloud instance user. */
+ char *pszUser;
+ /** Cloud instance primary IP address. */
+ char *pszPrimaryIP;
+ /** Cloud instance primary IP address. */
+ char *pszSecondaryIP;
+ /** MAC address to set on cloud primary interface. */
+ RTMAC targetMac;
+ /** SSH connection timeout in seconds. */
+ long ulTimeoutInSecounds;
+
+ /** Primary proxy type. */
+ char *pszPrimaryProxyType;
+ /** Primary proxy server IP address. */
+ char *pszPrimaryProxyHost;
+ /** Primary proxy server port. */
+ uint16_t u16PrimaryProxyPort;
+ /** Primary proxy user. */
+ char *pszPrimaryProxyUser;
+ /** Primary proxy password. */
+ char *pszPrimaryProxyPassword;
+
+ /** Secondary proxy type. */
+ char *pszSecondaryProxyType;
+ /** Secondary proxy server IP address. */
+ char *pszSecondaryProxyHost;
+ /** Secondary proxy server port. */
+ uint16_t u16SecondaryProxyPort;
+ /** Secondary proxy user. */
+ char *pszSecondaryProxyUser;
+ /** Secondary proxy password. */
+ char *pszSecondaryProxyPassword;
+
+ /** Cloud tunnel instance string. */
+ char *pszInstance;
+ /** Cloud tunnel I/O thread unique name. */
+ char *pszInstanceIo;
+ /** Cloud tunnel device thread unique name. */
+ char *pszInstanceDev;
+
+ /** Command assembly buffer. */
+ char *pszCommandBuffer;
+ /** Command output buffer. */
+ char *pszOutputBuffer;
+ /** Name of primary interface of cloud instance. */
+ char *pszCloudPrimaryInterface;
+
+ /** Cloud destination address. */
+ RTNETADDR DestAddress;
+ /** Transmit lock used by drvCloudTunnelUp_BeginXmit. */
+ RTCRITSECT XmitLock;
+ /** Server data structure for Cloud communication. */
+// PRTCLOUDSERVER pServer;
+
+ /** RX thread for delivering packets to attached device. */
+ PPDMTHREAD pDevThread;
+ /** Queue for device-thread requests. */
+ RTREQQUEUE hDevReqQueue;
+ /** I/O thread for tunnel channel. */
+ PPDMTHREAD pIoThread;
+ /** Queue for I/O-thread requests. */
+ RTREQQUEUE hIoReqQueue;
+ /** I/O thread notification socket pair (in). */
+ SOCKET iSocketIn;
+ /** I/O thread notification socket pair (out). */
+ SOCKET iSocketOut;
+
+ /** SSH private key. */
+
+ /** SSH Log Verbosity: 0 - No log, 1 - warnings, 2 - protocol, 3 - packet, 4 - functions */
+ int iSshVerbosity;
+ /** SSH Session. */
+ ssh_session pSshSession;
+ /** SSH Tunnel Channel. */
+ ssh_channel pSshChannel;
+ /** SSH Packet Receive Callback Structure. */
+ struct ssh_channel_callbacks_struct Callbacks;
+
+ /** Flag whether the link is down. */
+ bool volatile fLinkDown;
+
+#ifdef VBOX_WITH_STATISTICS
+ /** Number of sent packets. */
+ STAMCOUNTER StatPktSent;
+ /** Number of sent bytes. */
+ STAMCOUNTER StatPktSentBytes;
+ /** Number of received packets. */
+ STAMCOUNTER StatPktRecv;
+ /** Number of received bytes. */
+ STAMCOUNTER StatPktRecvBytes;
+ /** Profiling packet transmit runs. */
+ STAMPROFILEADV StatTransmit;
+ /** Profiling packet receive runs. */
+ STAMPROFILEADV StatReceive;
+ /** Profiling packet receive device (both actual receive and waiting). */
+ STAMPROFILE StatDevRecv;
+ /** Profiling packet receive device waiting. */
+ STAMPROFILE StatDevRecvWait;
+#endif /* VBOX_WITH_STATISTICS */
+
+#ifdef LOG_ENABLED
+ /** The nano ts of the last transfer. */
+ uint64_t u64LastTransferTS;
+ /** The nano ts of the last receive. */
+ uint64_t u64LastReceiveTS;
+#endif
+} DRVCLOUDTUNNEL, *PDRVCLOUDTUNNEL;
+
+
+/** Converts a pointer to CLOUDTUNNEL::INetworkUp to a PRDVCLOUDTUNNEL. */
+#define PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface) ( (PDRVCLOUDTUNNEL)((uintptr_t)pInterface - RT_UOFFSETOF(DRVCLOUDTUNNEL, INetworkUp)) )
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnBeginXmit}
+ */
+static DECLCALLBACK(int) drvCloudTunnelUp_BeginXmit(PPDMINETWORKUP pInterface, bool fOnWorkerThread)
+{
+ RT_NOREF(fOnWorkerThread);
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+ int rc = RTCritSectTryEnter(&pThis->XmitLock);
+ if (RT_FAILURE(rc))
+ {
+ /** @todo XMIT thread */
+ rc = VERR_TRY_AGAIN;
+ }
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnAllocBuf}
+ */
+static DECLCALLBACK(int) drvCloudTunnelUp_AllocBuf(PPDMINETWORKUP pInterface, size_t cbMin,
+ PCPDMNETWORKGSO pGso, PPPDMSCATTERGATHER ppSgBuf)
+{
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+ Assert(RTCritSectIsOwner(&pThis->XmitLock)); NOREF(pThis);
+
+ /*
+ * Allocate a scatter / gather buffer descriptor that is immediately
+ * followed by the buffer space of its single segment. The GSO context
+ * comes after that again.
+ */
+ PPDMSCATTERGATHER pSgBuf = (PPDMSCATTERGATHER)RTMemAlloc( RT_ALIGN_Z(sizeof(*pSgBuf), 16)
+ + RT_ALIGN_Z(cbMin, 16)
+ + (pGso ? RT_ALIGN_Z(sizeof(*pGso), 16) : 0));
+ if (!pSgBuf)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Initialize the S/G buffer and return.
+ */
+ pSgBuf->fFlags = PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_1;
+ pSgBuf->cbUsed = 0;
+ pSgBuf->cbAvailable = RT_ALIGN_Z(cbMin, 16);
+ pSgBuf->pvAllocator = NULL;
+ if (!pGso)
+ pSgBuf->pvUser = NULL;
+ else
+ {
+ pSgBuf->pvUser = (uint8_t *)(pSgBuf + 1) + pSgBuf->cbAvailable;
+ *(PPDMNETWORKGSO)pSgBuf->pvUser = *pGso;
+ }
+ pSgBuf->cSegs = 1;
+ pSgBuf->aSegs[0].cbSeg = pSgBuf->cbAvailable;
+ pSgBuf->aSegs[0].pvSeg = pSgBuf + 1;
+
+#if 0 /* poison */
+ memset(pSgBuf->aSegs[0].pvSeg, 'F', pSgBuf->aSegs[0].cbSeg);
+#endif
+ *ppSgBuf = pSgBuf;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnFreeBuf}
+ */
+static DECLCALLBACK(int) drvCloudTunnelUp_FreeBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf)
+{
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+ Assert(RTCritSectIsOwner(&pThis->XmitLock)); NOREF(pThis);
+ if (pSgBuf)
+ {
+ Assert((pSgBuf->fFlags & PDMSCATTERGATHER_FLAGS_MAGIC_MASK) == PDMSCATTERGATHER_FLAGS_MAGIC);
+ pSgBuf->fFlags = 0;
+ RTMemFree(pSgBuf);
+ }
+ return VINF_SUCCESS;
+}
+
+static int createConnectedSockets(PDRVCLOUDTUNNEL pThis)
+{
+ LogFlow(("%s: creating a pair of connected sockets...\n", pThis->pszInstance));
+ struct sockaddr_in inaddr;
+ struct sockaddr addr;
+ SOCKET lst = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ memset(&inaddr, 0, sizeof(inaddr));
+ memset(&addr, 0, sizeof(addr));
+ inaddr.sin_family = AF_INET;
+ inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ inaddr.sin_port = 0;
+ int yes = 1;
+ setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
+ bind(lst, (struct sockaddr *)&inaddr, sizeof(inaddr));
+ listen(lst, 1);
+ socklen_t len=sizeof(inaddr);
+ getsockname(lst, &addr, &len);
+ pThis->iSocketOut = socket(AF_INET, SOCK_STREAM, 0);
+ connect(pThis->iSocketOut, &addr, len);
+ pThis->iSocketIn = accept(lst, 0, 0);
+ closesocket(lst);
+ Log2(("%s: socket(%d) <= socket(%d) created successfully.\n", pThis->pszInstance, pThis->iSocketIn, pThis->iSocketOut));
+ return VINF_SUCCESS;
+}
+
+
+static void destroyConnectedSockets(PDRVCLOUDTUNNEL pThis)
+{
+ if (pThis->iSocketOut != INVALID_SOCKET)
+ {
+ LogFlow(("%s: destroying output socket (%d)...\n", pThis->pszInstance, pThis->iSocketOut));
+ closesocket(pThis->iSocketOut);
+ }
+ if (pThis->iSocketIn != INVALID_SOCKET)
+ {
+ LogFlow(("%s: destroying input socket (%d)...\n", pThis->pszInstance, pThis->iSocketIn));
+ closesocket(pThis->iSocketIn);
+ }
+}
+
+
+DECLINLINE(void) drvCloudTunnelFreeSgBuf(PDRVCLOUDTUNNEL pThis, PPDMSCATTERGATHER pSgBuf)
+{
+ RT_NOREF(pThis);
+ RTMemFree(pSgBuf);
+}
+
+DECLINLINE(void) drvCloudTunnelNotifyIoThread(PDRVCLOUDTUNNEL pThis, const char *pszWho)
+{
+ RT_NOREF(pszWho);
+ int cBytes = send(pThis->iSocketOut, " ", 1, 0);
+ if (cBytes == SOCKET_ERROR)
+ LogRel(("Failed to send a signalling packet, error code %d", WSAGetLastError())); // @todo!
+
+}
+
+
+/**
+ * Worker function for sending packets on I/O thread.
+ *
+ * @param pThis Pointer to the cloud tunnel instance.
+ * @param pSgBuf The scatter/gather buffer.
+ * @thread I/O
+ */
+static DECLCALLBACK(void) drvCloudTunnelSendWorker(PDRVCLOUDTUNNEL pThis, PPDMSCATTERGATHER pSgBuf)
+{
+ // int rc = VINF_SUCCESS;
+ if (!pSgBuf->pvUser)
+ {
+#ifdef LOG_ENABLED
+ uint64_t u64Now = RTTimeProgramNanoTS();
+ LogFunc(("%-4d bytes at %llu ns deltas: r=%llu t=%llu\n",
+ pSgBuf->cbUsed, u64Now, u64Now - pThis->u64LastReceiveTS, u64Now - pThis->u64LastTransferTS));
+ pThis->u64LastTransferTS = u64Now;
+#endif
+ Log2(("writing to tunnel channel: pSgBuf->aSegs[0].pvSeg=%p pSgBuf->cbUsed=%#x\n%.*Rhxd\n",
+ pSgBuf->aSegs[0].pvSeg, pSgBuf->cbUsed, pSgBuf->cbUsed, pSgBuf->aSegs[0].pvSeg));
+
+ int cBytes = ssh_channel_write(pThis->pSshChannel, pSgBuf->aSegs[0].pvSeg, (uint32_t)pSgBuf->cbUsed);
+ if (cBytes == SSH_ERROR)
+ LogRel(("%s: ssh_channel_write failed\n", pThis->pszInstance));
+ }
+ else
+ {
+ uint8_t abHdrScratch[256];
+ uint8_t const *pbFrame = (uint8_t const *)pSgBuf->aSegs[0].pvSeg;
+ PCPDMNETWORKGSO pGso = (PCPDMNETWORKGSO)pSgBuf->pvUser;
+ uint32_t const cSegs = PDMNetGsoCalcSegmentCount(pGso, pSgBuf->cbUsed); Assert(cSegs > 1);
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ {
+ uint32_t cbSegFrame;
+ void *pvSegFrame = PDMNetGsoCarveSegmentQD(pGso, (uint8_t *)pbFrame, pSgBuf->cbUsed, abHdrScratch,
+ iSeg, cSegs, &cbSegFrame);
+ Log2(("writing to tunnel channel: pvSegFrame=%p cbSegFrame=%#x\n%.*Rhxd\n",
+ pvSegFrame, cbSegFrame, cbSegFrame, pvSegFrame));
+ int cBytes = ssh_channel_write(pThis->pSshChannel, pvSegFrame, cbSegFrame);
+ if (cBytes == SSH_ERROR)
+ LogRel(("%s: ssh_channel_write failed\n", pThis->pszInstance));
+ }
+ }
+
+ pSgBuf->fFlags = 0;
+ RTMemFree(pSgBuf);
+
+ STAM_PROFILE_ADV_STOP(&pThis->StatTransmit, a);
+ // AssertRC(rc);
+ // if (RT_FAILURE(rc))
+ // {
+ // if (rc == VERR_NO_MEMORY)
+ // rc = VERR_NET_NO_BUFFER_SPACE;
+ // else
+ // rc = VERR_NET_DOWN;
+ // }
+ // return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnSendBuf}
+ */
+static DECLCALLBACK(int) drvCloudTunnelUp_SendBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf, bool fOnWorkerThread)
+{
+ RT_NOREF(fOnWorkerThread);
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+ STAM_COUNTER_INC(&pThis->StatPktSent);
+ STAM_COUNTER_ADD(&pThis->StatPktSentBytes, pSgBuf->cbUsed);
+ STAM_PROFILE_ADV_START(&pThis->StatTransmit, a);
+
+ AssertPtr(pSgBuf);
+ Assert((pSgBuf->fFlags & PDMSCATTERGATHER_FLAGS_MAGIC_MASK) == PDMSCATTERGATHER_FLAGS_MAGIC);
+ Assert(RTCritSectIsOwner(&pThis->XmitLock));
+
+ int rc = VINF_SUCCESS;
+ if (pThis->pIoThread && pThis->pIoThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ Log2(("%s: submitting TX request (pvSeg=%p, %u bytes) to I/O queue...\n",
+ pThis->pszInstance, pSgBuf->aSegs[0].pvSeg, pSgBuf->cbUsed));
+ rc = RTReqQueueCallEx(pThis->hIoReqQueue, NULL /*ppReq*/, 0 /*cMillies*/,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCloudTunnelSendWorker, 2, pThis, pSgBuf);
+
+ if (RT_SUCCESS(rc))
+ {
+ drvCloudTunnelNotifyIoThread(pThis, "drvCloudTunnelUp_SendBuf");
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_NET_NO_BUFFER_SPACE;
+ }
+ else
+ rc = VERR_NET_DOWN;
+ drvCloudTunnelFreeSgBuf(pThis, pSgBuf);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnEndXmit}
+ */
+static DECLCALLBACK(void) drvCloudTunnelUp_EndXmit(PPDMINETWORKUP pInterface)
+{
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+ RTCritSectLeave(&pThis->XmitLock);
+}
+
+
+/**
+ * @interface_method_impl{PDMINETWORKUP,pfnSetPromiscuousMode}
+ */
+static DECLCALLBACK(void) drvCloudTunnelUp_SetPromiscuousMode(PPDMINETWORKUP pInterface, bool fPromiscuous)
+{
+ RT_NOREF(pInterface, fPromiscuous);
+ LogFlowFunc(("fPromiscuous=%d\n", fPromiscuous));
+ /* nothing to do */
+}
+
+
+/**
+ * Notification on link status changes.
+ *
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param enmLinkState The new link state.
+ * @thread EMT
+ */
+static DECLCALLBACK(void) drvCloudTunnelUp_NotifyLinkChanged(PPDMINETWORKUP pInterface, PDMNETWORKLINKSTATE enmLinkState)
+{
+ LogFlowFunc(("enmLinkState=%d\n", enmLinkState));
+ PDRVCLOUDTUNNEL pThis = PDMINETWORKUP_2_DRVCLOUDTUNNEL(pInterface);
+
+ bool fLinkDown;
+ switch (enmLinkState)
+ {
+ case PDMNETWORKLINKSTATE_DOWN:
+ case PDMNETWORKLINKSTATE_DOWN_RESUME:
+ fLinkDown = true;
+ break;
+ default:
+ AssertMsgFailed(("enmLinkState=%d\n", enmLinkState));
+ RT_FALL_THRU();
+ case PDMNETWORKLINKSTATE_UP:
+ fLinkDown = false;
+ break;
+ }
+ ASMAtomicXchgSize(&pThis->fLinkDown, fLinkDown);
+}
+
+
+
+/* -=-=-=-=- PDMIBASE -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvCloudTunnelQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKUP, &pThis->INetworkUp);
+ return NULL;
+}
+
+
+/**
+ * I/O thread handling the libssh I/O.
+ *
+ * The libssh implementation is single-threaded so we perform I/O in a
+ * dedicated thread. We take care that this thread does not become the
+ * bottleneck: If the guest wants to send, a request is enqueued into the
+ * hIoReqQueue and is handled asynchronously by this thread. TODO:If this thread
+ * wants to deliver packets to the guest, it enqueues a request into
+ * hRecvReqQueue which is later handled by the Recv thread.
+ */
+static DECLCALLBACK(int) drvCloudTunnelIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+ // int nFDs = -1;
+
+ LogFlow(("%s: started I/O thread %p\n", pThis->pszInstance, pThread));
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ // if (pThis->enmLinkStateWant != pThis->enmLinkState)
+ // drvNATNotifyLinkChangedWorker(pThis, pThis->enmLinkStateWant);
+
+ /*
+ * Polling loop.
+ */
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ /*
+ * To prevent concurrent execution of sending/receiving threads
+ */
+//#ifndef RT_OS_WINDOWS
+ // /* process _all_ outstanding requests but don't wait */
+ // RTReqQueueProcess(pThis->hIoReqQueue, 0);
+ // RTMemFree(polls);
+//#else /* RT_OS_WINDOWS */
+
+ struct timeval timeout;
+ ssh_channel in_channels[2], out_channels[2];
+ fd_set fds;
+ int maxfd;
+
+ timeout.tv_sec = 30;
+ timeout.tv_usec = 0;
+ in_channels[0] = pThis->pSshChannel;
+ in_channels[1] = NULL;
+ FD_ZERO(&fds);
+ FD_SET(pThis->iSocketIn, &fds);
+ maxfd = pThis->iSocketIn + 1;
+
+ ssh_select(in_channels, out_channels, maxfd, &fds, &timeout);
+
+ /* Poll will call the receive callback on each packet coming from the tunnel. */
+ if (out_channels[0] != NULL)
+ ssh_channel_poll(pThis->pSshChannel, false);
+
+ /* Did we get notified by drvCloudTunnelNotifyIoThread() via connected sockets? */
+ if (FD_ISSET(pThis->iSocketIn, &fds))
+ {
+ char buf[2];
+ recv(pThis->iSocketIn, buf, 1, 0);
+ /* process all outstanding requests but don't wait */
+ RTReqQueueProcess(pThis->hIoReqQueue, 0);
+ }
+//#endif /* RT_OS_WINDOWS */
+ }
+
+ LogFlow(("%s: I/O thread %p terminated\n", pThis->pszInstance, pThread));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the I/O thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The pcnet device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) drvCloudTunnelIoWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+
+ LogFlow(("%s: waking up I/O thread %p...\n", pThis->pszInstance, pThread));
+
+ drvCloudTunnelNotifyIoThread(pThis, "drvCloudTunnelIoWakeup");
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Remove the following cut&paste code after a while, when
+ * we are positive that no frames get coalesced!
+ */
+#define VBOX_CTUN_COALESCED_FRAME_DETECTION
+#ifdef VBOX_CTUN_COALESCED_FRAME_DETECTION
+struct ssh_buffer_struct {
+ bool secure;
+ size_t used;
+ size_t allocated;
+ size_t pos;
+ uint8_t *data;
+};
+
+/** @internal
+ * Describes the different possible states in a
+ * outgoing (client) channel request
+ */
+enum ssh_channel_request_state_e {
+ /** No request has been made */
+ SSH_CHANNEL_REQ_STATE_NONE = 0,
+ /** A request has been made and answer is pending */
+ SSH_CHANNEL_REQ_STATE_PENDING,
+ /** A request has been replied and accepted */
+ SSH_CHANNEL_REQ_STATE_ACCEPTED,
+ /** A request has been replied and refused */
+ SSH_CHANNEL_REQ_STATE_DENIED,
+ /** A request has been replied and an error happend */
+ SSH_CHANNEL_REQ_STATE_ERROR
+};
+
+enum ssh_channel_state_e {
+ SSH_CHANNEL_STATE_NOT_OPEN = 0,
+ SSH_CHANNEL_STATE_OPENING,
+ SSH_CHANNEL_STATE_OPEN_DENIED,
+ SSH_CHANNEL_STATE_OPEN,
+ SSH_CHANNEL_STATE_CLOSED
+};
+
+/* The channel has been closed by the remote side */
+#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x0001
+
+/* The channel has been closed locally */
+#define SSH_CHANNEL_FLAG_CLOSED_LOCAL 0x0002
+
+/* The channel has been freed by the calling program */
+#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x0004
+
+/* the channel has not yet been bound to a remote one */
+#define SSH_CHANNEL_FLAG_NOT_BOUND 0x0008
+
+struct ssh_channel_struct {
+ ssh_session session; /* SSH_SESSION pointer */
+ uint32_t local_channel;
+ uint32_t local_window;
+ int local_eof;
+ uint32_t local_maxpacket;
+
+ uint32_t remote_channel;
+ uint32_t remote_window;
+ int remote_eof; /* end of file received */
+ uint32_t remote_maxpacket;
+ enum ssh_channel_state_e state;
+ int delayed_close;
+ int flags;
+ ssh_buffer stdout_buffer;
+ ssh_buffer stderr_buffer;
+ void *userarg;
+ int exit_status;
+ enum ssh_channel_request_state_e request_state;
+ struct ssh_list *callbacks; /* list of ssh_channel_callbacks */
+
+ /* counters */
+ ssh_counter counter;
+};
+#endif /* VBOX_CTUN_COALESCED_FRAME_DETECTION */
+
+/**
+ * Worker function for delivering receive packets to the attached device.
+ *
+ * @param pThis Pointer to the cloud tunnel instance.
+ * @param pbData Packet data.
+ * @param u32Len Packet length.
+ * @thread Dev
+ */
+static DECLCALLBACK(void) drvCloudTunnelReceiveWorker(PDRVCLOUDTUNNEL pThis, uint8_t *pbData, uint32_t u32Len)
+{
+ AssertPtrReturnVoid(pbData);
+ AssertReturnVoid(u32Len!=0);
+
+ STAM_PROFILE_START(&pThis->StatDevRecv, a);
+
+ Log2(("%s: waiting until device is ready to receive...\n", pThis->pszInstance));
+ STAM_PROFILE_START(&pThis->StatDevRecvWait, b);
+ int rc = pThis->pIAboveNet->pfnWaitReceiveAvail(pThis->pIAboveNet, RT_INDEFINITE_WAIT);
+ STAM_PROFILE_STOP(&pThis->StatDevRecvWait, b);
+
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("%s: delivering %u-byte packet to attached device...\n", pThis->pszInstance, u32Len));
+ rc = pThis->pIAboveNet->pfnReceive(pThis->pIAboveNet, pbData, u32Len);
+ AssertRC(rc);
+ }
+
+ RTMemFree(pbData);
+ STAM_PROFILE_STOP(&pThis->StatDevRecv, a);
+ STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a);
+}
+
+static int drvCloudTunnelReceiveCallback(ssh_session session, ssh_channel channel, void* data, uint32_t len, int is_stderr, void* userdata)
+{
+ RT_NOREF(session);
+ PDRVCLOUDTUNNEL pThis = (PDRVCLOUDTUNNEL)userdata;
+
+ Log2(("drvCloudTunnelReceiveCallback: len=%d is_stderr=%s\n", len, is_stderr ? "true" : "false"));
+ if (ASMAtomicReadBool(&pThis->fLinkDown))
+ {
+ Log2(("drvCloudTunnelReceiveCallback: ignoring packet as the link is down\n"));
+ return len;
+ }
+
+#ifdef VBOX_CTUN_COALESCED_FRAME_DETECTION
+ if (channel->stdout_buffer->data != data)
+ LogRel(("drvCloudTunnelReceiveCallback: coalesced frames!\n"));
+#endif /* VBOX_CTUN_COALESCED_FRAME_DETECTION */
+
+ if (is_stderr)
+ {
+ LogRel(("%s: [REMOTE] %.*s", pThis->pszInstance, len, data));
+ return 0;
+ }
+
+ STAM_PROFILE_ADV_START(&pThis->StatReceive, a);
+
+ if (pThis->iSshVerbosity >= SSH_LOG_PACKET)
+ Log2(("%.*Rhxd\n", len, data));
+
+ /** @todo Validate len! */
+ void *pvPacket = RTMemDup(data, len);
+ if (!pvPacket)
+ {
+ LogRel(("%s: failed to allocate %d bytes\n", pThis->pszInstance, len));
+ STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a);
+ return len;
+ }
+ int rc = RTReqQueueCallEx(pThis->hDevReqQueue, NULL /*ppReq*/, 0 /*cMillies*/,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCloudTunnelReceiveWorker, 3, pThis, pvPacket, len);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("%s: failed to enqueue device request - %Rrc\n", pThis->pszInstance, rc));
+ STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a);
+ }
+
+ return len;
+}
+
+
+/* See ssh_channel_write_wontblock_callback in libssh/callbacks.h. */
+#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,10,0)
+static int channelWriteWontblockCallback(ssh_session, ssh_channel, uint32_t, void *)
+#else
+static int channelWriteWontblockCallback(ssh_session, ssh_channel, size_t, void *)
+#endif
+{
+ return 0;
+}
+
+
+
+/**
+ * This thread feeds the attached device with the packets received from the tunnel.
+ *
+ * This thread is needed because we cannot block I/O thread waiting for the attached
+ * device to become ready to receive packets coming from the tunnel.
+ */
+static DECLCALLBACK(int) drvCloudTunnelDevThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+
+ LogFlow(("%s: device thread %p started\n", pThis->pszInstance, pThread));
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ /*
+ * Request processing loop.
+ */
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ int rc = RTReqQueueProcess(pThis->hDevReqQueue, RT_INDEFINITE_WAIT);
+ Log2(("drvCloudTunnelDevThread: RTReqQueueProcess returned '%Rrc'\n", rc));
+ if (RT_FAILURE(rc))
+ LogRel(("%s: failed to process device request with '%Rrc'\n", pThis->pszInstance, rc));
+ }
+
+ LogFlow(("%s: device thread %p terminated\n", pThis->pszInstance, pThread));
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) drvCloudTunnelReceiveWakeup(PDRVCLOUDTUNNEL pThis)
+{
+ NOREF(pThis);
+ /* Returning a VINF_* will cause RTReqQueueProcess return. */
+ return VWRN_STATE_CHANGED;
+}
+
+/**
+ * Unblock the I/O thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The pcnet device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) drvCloudTunnelDevWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+ LogFlow(("%s: waking up device thread %p...\n", pThis->pszInstance, pThread));
+
+ /* Wake up device thread. */
+ PRTREQ pReq;
+ int rc = RTReqQueueCall(pThis->hDevReqQueue, &pReq, 10000 /*cMillies*/,
+ (PFNRT)drvCloudTunnelReceiveWakeup, 1, pThis);
+ if (RT_FAILURE(rc))
+ LogRel(("%s: failed to wake up device thread - %Rrc\n", pThis->pszInstance, rc));
+ if (RT_SUCCESS(rc))
+ RTReqRelease(pReq);
+
+ return rc;
+}
+
+#define DRVCLOUDTUNNEL_COMMAND_BUFFER_SIZE 1024
+#define DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE 65536
+
+static int drvCloudTunnelExecuteRemoteCommandNoOutput(PDRVCLOUDTUNNEL pThis, const char *pcszCommand, ...)
+{
+ va_list va;
+ va_start(va, pcszCommand);
+
+ size_t cb = RTStrPrintfV(pThis->pszCommandBuffer, DRVCLOUDTUNNEL_COMMAND_BUFFER_SIZE, pcszCommand, va);
+ if (cb == 0)
+ {
+ Log(("%s: Failed to process '%s'\n", pThis->pszInstance, pcszCommand));
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to compose command line"));
+ }
+
+ LogFlow(("%s: [REMOTE] executing '%s'...\n", pThis->pszInstance, pThis->pszCommandBuffer));
+
+ ssh_channel channel = ssh_channel_new(pThis->pSshSession);
+ if (channel == NULL)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to allocate new channel"));
+
+ int rc = ssh_channel_open_session(channel);
+ if (rc != SSH_OK)
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to open session channel"));
+ else
+ {
+ rc = ssh_channel_request_exec(channel, pThis->pszCommandBuffer);
+ if (rc != SSH_OK)
+ {
+ LogRel(("%s: Failed to execute '%s'\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ Log(("%s: Failed to execute '%s'\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Execute request failed with %d"), rc);
+ }
+ ssh_channel_close(channel);
+ }
+ ssh_channel_free(channel);
+
+ return VINF_SUCCESS;
+}
+
+
+static int drvCloudTunnelExecuteRemoteCommand(PDRVCLOUDTUNNEL pThis, const char *pcszCommand, ...)
+{
+ va_list va;
+ va_start(va, pcszCommand);
+
+ size_t cb = RTStrPrintfV(pThis->pszCommandBuffer, DRVCLOUDTUNNEL_COMMAND_BUFFER_SIZE, pcszCommand, va);
+ if (cb == 0)
+ {
+ Log(("%s: Failed to process '%s'\n", pThis->pszInstance, pcszCommand));
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to compose command line"));
+ }
+
+ LogFlow(("%s: [REMOTE] executing '%s'...\n", pThis->pszInstance, pThis->pszCommandBuffer));
+
+ ssh_channel channel = ssh_channel_new(pThis->pSshSession);
+ if (channel == NULL)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to allocate new channel"));
+
+ int rc = ssh_channel_open_session(channel);
+ if (rc != SSH_OK)
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to open session channel"));
+ else
+ {
+ rc = ssh_channel_request_exec(channel, pThis->pszCommandBuffer);
+ if (rc != SSH_OK)
+ {
+ LogRel(("%s: Failed to execute '%s'\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ Log(("%s: Failed to execute '%s'\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Execute request failed with %d"), rc);
+ }
+ else
+ {
+ int cbSpaceLeft = DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE;
+ int cbStdOut = 0;
+ char *pszBuffer = pThis->pszOutputBuffer;
+ int cBytes = ssh_channel_read_timeout(channel, pszBuffer, cbSpaceLeft, 0, 60000 /* ms */); /* Is 60 seconds really enough? */
+ while (cBytes > 0)
+ {
+ cbStdOut += cBytes;
+ pszBuffer += cBytes;
+ cbSpaceLeft -= cBytes;
+ if (cbSpaceLeft <= 0)
+ break;
+ cBytes = ssh_channel_read_timeout(channel, pszBuffer, cbSpaceLeft, 0, 60000 /* ms */); /* Is 60 seconds really enough? */
+ }
+ if (cBytes < 0)
+ {
+ LogRel(("%s: while executing '%s' ssh_channel_read_timeout returned error\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ Log(("%s: while executing '%s' ssh_channel_read_timeout returned error\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ /* Make sure the buffer is terminated. */
+ if (cbStdOut < DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE)
+ if (cbStdOut > 1 && pThis->pszOutputBuffer[cbStdOut - 1] == '\n')
+ pThis->pszOutputBuffer[cbStdOut - 1] = 0; /* Trim newline */
+ else
+ pThis->pszOutputBuffer[cbStdOut] = 0;
+ else
+ pThis->pszOutputBuffer[DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE - 1] = 0; /* No choice but to eat up last character. Could have returned warning though. */
+ if (cbStdOut == 0)
+ Log(("%s: received no output from remote console\n", pThis->pszInstance));
+ else
+ Log(("%s: received output from remote console:\n%s\n", pThis->pszInstance, pThis->pszOutputBuffer));
+ rc = VINF_SUCCESS;
+
+ char *pszErrorBuffer = (char *)RTMemAlloc(DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE);
+ if (pszErrorBuffer == NULL)
+ {
+ LogRel(("%s: Failed to allocate error buffer\n", pThis->pszInstance));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ /* Report errors if there were any */
+ cBytes = ssh_channel_read_timeout(channel, pszErrorBuffer, DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE, 1, 0); /* Peek at stderr */
+ if (cBytes > 0)
+ {
+ LogRel(("%s: WARNING! While executing '%s' remote console reported errors:\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ Log(("%s: WARNING! While executing '%s' remote console reported errors:\n", pThis->pszInstance, pThis->pszCommandBuffer));
+ }
+ while (cBytes > 0)
+ {
+ LogRel(("%.*s", cBytes, pszErrorBuffer));
+ Log(("%.*s", cBytes, pszErrorBuffer));
+ cBytes = ssh_channel_read_timeout(channel, pszErrorBuffer, DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE, 1, 1000); /* Wait for a second for more error output */
+ }
+ RTMemFree(pszErrorBuffer);
+ }
+ }
+ ssh_channel_send_eof(channel);
+ }
+ ssh_channel_close(channel);
+ }
+ ssh_channel_free(channel);
+
+ return VINF_SUCCESS;
+}
+
+
+static int drvCloudTunnelCloudInstanceInitialConfig(PDRVCLOUDTUNNEL pThis)
+{
+ LogFlow(("%s: configuring cloud instance...\n", pThis->pszInstance));
+
+ int rc = drvCloudTunnelExecuteRemoteCommand(pThis, "python3 -c \"from oci_utils.vnicutils import VNICUtils; cfg = VNICUtils().get_network_config(); print('CONFIG:', [i['IFACE'] for i in cfg if 'IS_PRIMARY' in i][0], [i['IFACE']+' '+i['VIRTRT'] for i in cfg if not 'IS_PRIMARY' in i][0])\"");
+ if (RT_FAILURE(rc))
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to get network config via console channel"));
+ else
+ {
+ char *pszConfig = RTStrStr(pThis->pszOutputBuffer, "CONFIG: ");
+ if (!pszConfig)
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to parse network config"));
+ else
+ {
+ char **ppapszTokens;
+ size_t cTokens;
+ rc = RTStrSplit(pszConfig + 8, DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE - (pszConfig - pThis->pszOutputBuffer) - 8,
+ " ", &ppapszTokens, &cTokens);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * There should be exactly three tokens:
+ * 1) Primary network interface name;
+ * 2) Secondary network interface name;
+ * 3) Secondary network gateway address.
+ */
+ if (cTokens != 3)
+ Log(("%s: Got %u tokes instead of three while parsing '%s'\n", pThis->pszInstance, cTokens, pThis->pszOutputBuffer));
+ else
+ {
+ char *pszSecondaryInterface = NULL;
+ char *pszSecondaryGateway = NULL;
+
+ if (pThis->pszCloudPrimaryInterface)
+ RTStrFree(pThis->pszCloudPrimaryInterface);
+ pThis->pszCloudPrimaryInterface = RTStrDup(ppapszTokens[0]);
+ pszSecondaryInterface = ppapszTokens[1];
+ pszSecondaryGateway = ppapszTokens[2];
+ Log(("%s: primary=%s secondary=%s gateway=%s\n", pThis->pszInstance, pThis->pszCloudPrimaryInterface, pszSecondaryInterface, pszSecondaryGateway));
+
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo oci-network-config -c");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip tuntap add dev tap0 mod tap user opc");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo sh -c 'echo \"PermitTunnel yes\" >> /etc/ssh/sshd_config'");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo kill -SIGHUP $(pgrep -f \"sshd -D\")");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link add name br0 type bridge");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev tap0 master br0");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommandNoOutput(pThis, "sudo ip route change default via %s dev %s", pszSecondaryGateway, pszSecondaryInterface);
+ if (RT_FAILURE(rc))
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to execute network config command via console channel"));
+ }
+
+ for (size_t i = 0; i < cTokens; i++)
+ RTStrFree(ppapszTokens[i]);
+ RTMemFree(ppapszTokens);
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+static int drvCloudTunnelCloudInstanceFinalConfig(PDRVCLOUDTUNNEL pThis)
+{
+ if (pThis->pszCloudPrimaryInterface == NULL)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to finalize cloud instance config because of unknown primary interface name!"));
+
+ LogFlow(("%s: finalizing cloud instance configuration...\n", pThis->pszInstance));
+
+ int rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev %s down", pThis->pszCloudPrimaryInterface);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev %s address %RTmac", pThis->pszCloudPrimaryInterface, pThis->targetMac.au8);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ifconfig %s 0.0.0.0", pThis->pszCloudPrimaryInterface); /* Make sure no IP is configured on primary */
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev %s master br0", pThis->pszCloudPrimaryInterface);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev %s up", pThis->pszCloudPrimaryInterface);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev tap0 up");
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelExecuteRemoteCommand(pThis, "sudo ip link set dev br0 up");
+ if (RT_FAILURE(rc))
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to execute network config command via console channel"));
+
+ return rc;
+}
+
+
+static int drvCloudTunnelOpenTunnelChannel(PDRVCLOUDTUNNEL pThis)
+{
+ LogFlow(("%s: opening tunnel channel...\n", pThis->pszInstance));
+ pThis->pSshChannel = ssh_channel_new(pThis->pSshSession);
+ if (pThis->pSshChannel == NULL)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to allocate new channel"));
+ int rc = ssh_channel_open_tunnel(pThis->pSshChannel, 0);
+ if (rc < 0)
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to open tunnel channel"));
+ else
+ {
+ /* Set packet receive callback. */
+ rc = ssh_set_channel_callbacks(pThis->pSshChannel, &pThis->Callbacks);
+ if (rc != SSH_OK)
+ rc = PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set packet receive callback"));
+ }
+
+ return rc;
+}
+
+
+static void closeTunnelChannel(PDRVCLOUDTUNNEL pThis)
+{
+ if (pThis->pSshChannel)
+ {
+ LogFlow(("%s: closing tunnel channel %p\n", pThis->pszInstance, pThis->pSshChannel));
+ ssh_channel_close(pThis->pSshChannel);
+ ssh_channel_free(pThis->pSshChannel);
+ pThis->pSshChannel = NULL;
+ }
+}
+
+
+static int drvCloudTunnelStartIoThread(PDRVCLOUDTUNNEL pThis)
+{
+ LogFlow(("%s: starting I/O thread...\n", pThis->pszInstance));
+ int rc = createConnectedSockets(pThis);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to create a pair of connected sockets"));
+
+ /*
+ * Start the cloud I/O thread.
+ */
+ rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pIoThread,
+ pThis, drvCloudTunnelIoThread, drvCloudTunnelIoWakeup,
+ 64 * _1K, RTTHREADTYPE_IO, pThis->pszInstanceIo);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to start I/O thread"));
+
+ return rc;
+}
+
+static void drvCloudTunnelStopIoThread(PDRVCLOUDTUNNEL pThis)
+{
+ if (pThis->pIoThread)
+ {
+ LogFlow(("%s: stopping I/O thread...\n", pThis->pszInstance));
+ int rc = PDMDrvHlpThreadDestroy(pThis->pDrvIns, pThis->pIoThread, NULL);
+ AssertRC(rc);
+ pThis->pIoThread = NULL;
+ }
+ destroyConnectedSockets(pThis);
+
+}
+
+static int destroyTunnel(PDRVCLOUDTUNNEL pThis)
+{
+ if (pThis->pSshChannel)
+ {
+ int rc = ssh_remove_channel_callbacks(pThis->pSshChannel, &pThis->Callbacks);
+ if (rc != SSH_OK)
+ LogRel(("%s: WARNING! Failed to remove tunnel channel callbacks.\n", pThis->pszInstance));
+ }
+ drvCloudTunnelStopIoThread(pThis);
+ closeTunnelChannel(pThis);
+ ssh_disconnect(pThis->pSshSession);
+ ssh_free(pThis->pSshSession);
+ pThis->pSshSession = NULL;
+ return VINF_SUCCESS;
+}
+
+
+static int drvCloudTunnelNewSession(PDRVCLOUDTUNNEL pThis, bool fPrimary)
+{
+ pThis->pSshSession = ssh_new();
+ if (pThis->pSshSession == NULL)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to allocate new SSH session"));
+ if (ssh_options_set(pThis->pSshSession, SSH_OPTIONS_LOG_VERBOSITY, &pThis->iSshVerbosity) < 0)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set SSH_OPTIONS_LOG_VERBOSITY"));
+ if (ssh_options_set(pThis->pSshSession, SSH_OPTIONS_USER, pThis->pszUser) < 0)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set SSH_OPTIONS_USER"));
+ if (ssh_options_set(pThis->pSshSession, SSH_OPTIONS_HOST, fPrimary ? pThis->pszPrimaryIP : pThis->pszSecondaryIP) < 0)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set SSH_OPTIONS_HOST"));
+
+ if (ssh_options_set(pThis->pSshSession, SSH_OPTIONS_TIMEOUT, &pThis->ulTimeoutInSecounds) < 0)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set SSH_OPTIONS_TIMEOUT"));
+
+ const char *pcszProxyType = fPrimary ? pThis->pszPrimaryProxyType : pThis->pszSecondaryProxyType;
+ if (pcszProxyType)
+ {
+ char szProxyCmd[1024];
+
+ const char *pcszProxyUser = fPrimary ? pThis->pszPrimaryProxyUser : pThis->pszSecondaryProxyUser;
+ if (pcszProxyUser)
+ RTStrPrintf(szProxyCmd, sizeof(szProxyCmd), "#VBoxProxy%s %s %u %s %s",
+ fPrimary ? pThis->pszPrimaryProxyType : pThis->pszSecondaryProxyType,
+ fPrimary ? pThis->pszPrimaryProxyHost : pThis->pszSecondaryProxyHost,
+ fPrimary ? pThis->u16PrimaryProxyPort : pThis->u16SecondaryProxyPort,
+ fPrimary ? pThis->pszPrimaryProxyUser : pThis->pszSecondaryProxyUser,
+ fPrimary ? pThis->pszPrimaryProxyPassword : pThis->pszSecondaryProxyPassword);
+ else
+ RTStrPrintf(szProxyCmd, sizeof(szProxyCmd), "#VBoxProxy%s %s %u",
+ fPrimary ? pThis->pszPrimaryProxyType : pThis->pszSecondaryProxyType,
+ fPrimary ? pThis->pszPrimaryProxyHost : pThis->pszSecondaryProxyHost,
+ fPrimary ? pThis->u16PrimaryProxyPort : pThis->u16SecondaryProxyPort);
+ LogRel(("%s: using proxy command '%s'\n", pThis->pszInstance, szProxyCmd));
+ if (ssh_options_set(pThis->pSshSession, SSH_OPTIONS_PROXYCOMMAND, szProxyCmd) < 0)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to set SSH_OPTIONS_PROXYCOMMAND"));
+ }
+
+ int rc = ssh_connect(pThis->pSshSession);
+ for (int cAttempt = 1; rc != SSH_OK && cAttempt <= 5; cAttempt++)
+ {
+ ssh_disconnect(pThis->pSshSession);
+ /* One more time, just to be sure. */
+ LogRel(("%s: failed to connect to %s, retrying(#%d)...\n", pThis->pszInstance,
+ fPrimary ? pThis->pszPrimaryIP : pThis->pszSecondaryIP, cAttempt));
+ RTThreadSleep(10000); /* Sleep 10 seconds, then retry */
+ rc = ssh_connect(pThis->pSshSession);
+ }
+ if (rc != SSH_OK)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to connect to %s interface"), fPrimary ? "primary" : "secondary");
+
+ rc = ssh_userauth_publickey(pThis->pSshSession, NULL, pThis->SshKey);
+ if (rc != SSH_AUTH_SUCCESS)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("Failed to authenticate with public key"));
+
+ return VINF_SUCCESS;
+}
+
+static int drvCloudTunnelSwitchToSecondary(PDRVCLOUDTUNNEL pThis)
+{
+ int rc = drvCloudTunnelNewSession(pThis, true /* fPrimary */);
+ /*
+ * Establish temporary console channel and configure the cloud instance
+ * to bridge the tunnel channel to instance's primary interface.
+ */
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelCloudInstanceInitialConfig(pThis);
+
+ ssh_disconnect(pThis->pSshSession);
+ ssh_free(pThis->pSshSession);
+ pThis->pSshSession = NULL;
+
+ return rc;
+}
+
+
+static int establishTunnel(PDRVCLOUDTUNNEL pThis)
+{
+ int rc = drvCloudTunnelNewSession(pThis, false /* fPrimary */);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelCloudInstanceFinalConfig(pThis);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelOpenTunnelChannel(pThis);
+ if (RT_SUCCESS(rc))
+ rc = drvCloudTunnelStartIoThread(pThis);
+ if (RT_FAILURE(rc))
+ {
+ destroyTunnel(pThis);
+ return rc;
+ }
+
+ return rc;
+}
+
+
+static DECL_NOTHROW(void) drvCloudTunnelSshLogCallback(int priority, const char *function, const char *buffer, void *userdata)
+{
+ PDRVCLOUDTUNNEL pThis = (PDRVCLOUDTUNNEL)userdata;
+#ifdef LOG_ENABLED
+ const char *pcszVerbosity;
+ switch (priority)
+ {
+ case SSH_LOG_WARNING:
+ pcszVerbosity = "WARNING";
+ break;
+ case SSH_LOG_PROTOCOL:
+ pcszVerbosity = "PROTOCOL";
+ break;
+ case SSH_LOG_PACKET:
+ pcszVerbosity = "PACKET";
+ break;
+ case SSH_LOG_FUNCTIONS:
+ pcszVerbosity = "FUNCTIONS";
+ break;
+ default:
+ pcszVerbosity = "UNKNOWN";
+ break;
+ }
+ Log3(("%s: SSH-%s: %s: %s\n", pThis->pszInstance, pcszVerbosity, function, buffer));
+#else
+ RT_NOREF(priority);
+ LogRel(("%s: SSH %s: %s\n", pThis->pszInstance, function, buffer));
+#endif
+}
+
+/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
+
+DECLINLINE(void) drvCloudTunnelStrFree(char **ppszString)
+{
+ if (*ppszString)
+ {
+ RTStrFree(*ppszString);
+ *ppszString = NULL;
+ }
+}
+
+DECLINLINE(void) drvCloudTunnelHeapFree(PPDMDRVINS pDrvIns, char **ppszString)
+{
+ if (*ppszString)
+ {
+ PDMDrvHlpMMHeapFree(pDrvIns, *ppszString);
+ *ppszString = NULL;
+ }
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvCloudTunnelDestruct(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ ASMAtomicXchgSize(&pThis->fLinkDown, true);
+
+ destroyTunnel(pThis);
+
+ if (pThis->hIoReqQueue != NIL_RTREQQUEUE)
+ {
+ RTReqQueueDestroy(pThis->hIoReqQueue);
+ pThis->hIoReqQueue = NIL_RTREQQUEUE;
+ }
+
+ drvCloudTunnelStrFree(&pThis->pszCloudPrimaryInterface);
+
+ drvCloudTunnelHeapFree(pDrvIns, &pThis->pszPrimaryProxyType);
+ drvCloudTunnelStrFree(&pThis->pszPrimaryProxyHost);
+ drvCloudTunnelHeapFree(pDrvIns, &pThis->pszPrimaryProxyUser);
+ drvCloudTunnelStrFree(&pThis->pszPrimaryProxyPassword);
+
+ drvCloudTunnelHeapFree(pDrvIns, &pThis->pszSecondaryProxyType);
+ drvCloudTunnelStrFree(&pThis->pszSecondaryProxyHost);
+ drvCloudTunnelHeapFree(pDrvIns, &pThis->pszSecondaryProxyUser);
+ drvCloudTunnelStrFree(&pThis->pszSecondaryProxyPassword);
+
+ drvCloudTunnelStrFree(&pThis->pszSecondaryIP);
+ drvCloudTunnelStrFree(&pThis->pszPrimaryIP);
+ drvCloudTunnelStrFree(&pThis->pszUser);
+
+ drvCloudTunnelStrFree(&pThis->pszInstanceDev);
+ drvCloudTunnelStrFree(&pThis->pszInstanceIo);
+ drvCloudTunnelStrFree(&pThis->pszInstance);
+
+ drvCloudTunnelStrFree(&pThis->pszOutputBuffer);
+ drvCloudTunnelStrFree(&pThis->pszCommandBuffer);
+
+ ssh_key_free(pThis->SshKey);
+
+ ssh_finalize();
+ //OPENSSL_cleanup();
+
+ // if (pThis->pServer)
+ // {
+ // RTUdpServerDestroy(pThis->pServer);
+ // pThis->pServer = NULL;
+ // }
+
+ /*
+ * Kill the xmit lock.
+ */
+ if (RTCritSectIsInitialized(&pThis->XmitLock))
+ RTCritSectDelete(&pThis->XmitLock);
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Deregister statistics.
+ */
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatPktSent);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatPktSentBytes);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatPktRecv);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatPktRecvBytes);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatTransmit);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReceive);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatDevRecv);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatDevRecvWait);
+#endif /* VBOX_WITH_STATISTICS */
+}
+
+
+/**
+ * Construct a Cloud tunnel network transport driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvCloudTunnelConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pszCommandBuffer = NULL;
+ pThis->pszOutputBuffer = NULL;
+ pThis->pszInstance = NULL;
+ pThis->pszPrimaryIP = NULL;
+ pThis->pszSecondaryIP = NULL;
+ pThis->pszUser = NULL;
+ pThis->SshKey = 0;
+
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvCloudTunnelQueryInterface;
+ /* INetwork */
+ pThis->INetworkUp.pfnBeginXmit = drvCloudTunnelUp_BeginXmit;
+ pThis->INetworkUp.pfnAllocBuf = drvCloudTunnelUp_AllocBuf;
+ pThis->INetworkUp.pfnFreeBuf = drvCloudTunnelUp_FreeBuf;
+ pThis->INetworkUp.pfnSendBuf = drvCloudTunnelUp_SendBuf;
+ pThis->INetworkUp.pfnEndXmit = drvCloudTunnelUp_EndXmit;
+ pThis->INetworkUp.pfnSetPromiscuousMode = drvCloudTunnelUp_SetPromiscuousMode;
+ pThis->INetworkUp.pfnNotifyLinkChanged = drvCloudTunnelUp_NotifyLinkChanged;
+
+ /* ??? */
+ pThis->iSocketIn = INVALID_SOCKET;
+ pThis->iSocketOut = INVALID_SOCKET;
+ pThis->pSshSession = 0;
+ pThis->pSshChannel = 0;
+
+ pThis->pDevThread = 0;
+ pThis->pIoThread = 0;
+ pThis->hIoReqQueue = NIL_RTREQQUEUE;
+
+ pThis->fLinkDown = false;
+
+ pThis->pszCloudPrimaryInterface = NULL;
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Statistics.
+ */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatPktSent, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of sent packets.", "/Drivers/CloudTunnel%d/Packets/Sent", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatPktSentBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of sent bytes.", "/Drivers/CloudTunnel%d/Bytes/Sent", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatPktRecv, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of received packets.", "/Drivers/CloudTunnel%d/Packets/Received", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatPktRecvBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of received bytes.", "/Drivers/CloudTunnel%d/Bytes/Received", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatTransmit, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling packet transmit runs.", "/Drivers/CloudTunnel%d/Transmit", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReceive, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling packet receive runs.", "/Drivers/CloudTunnel%d/Receive", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatDevRecv, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling device receive runs.", "/Drivers/CloudTunnel%d/DeviceReceive", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatDevRecvWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling device receive waits.", "/Drivers/CloudTunnel%d/DeviceReceiveWait", pDrvIns->iInstance);
+#endif /* VBOX_WITH_STATISTICS */
+
+ /*
+ * Validate the config.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "SshKey"
+ "|PrimaryIP"
+ "|SecondaryIP"
+ "|TargetMAC"
+
+ "|PrimaryProxyType"
+ "|PrimaryProxyHost"
+ "|PrimaryProxyPort"
+ "|PrimaryProxyUser"
+ "|PrimaryProxyPassword"
+ "|SecondaryProxyType"
+ "|SecondaryProxyHost"
+ "|SecondaryProxyPort"
+ "|SecondaryProxyUser"
+ "|SecondaryProxyPassword"
+
+ ,"");
+
+ /*
+ * Check that no-one is attached to us.
+ */
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Query the network port interface.
+ */
+ pThis->pIAboveNet = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMINETWORKDOWN);
+ if (!pThis->pIAboveNet)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE,
+ N_("Configuration error: The above device/driver didn't export the network port interface"));
+
+ /*
+ * Read the configuration.
+ */
+ int rc;
+
+ char szVal[2048];
+ RTNETADDRIPV4 tmpAddr;
+ rc = pHlp->pfnCFGMQueryString(pCfg, "PrimaryIP", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryIP\" as string failed"));
+ rc = RTNetStrToIPv4Addr(szVal, &tmpAddr);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"PrimaryIP\" is not valid"));
+ else
+ pThis->pszPrimaryIP = RTStrDup(szVal);
+
+ rc = pHlp->pfnCFGMQueryString(pCfg, "SecondaryIP", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryIP\" as string failed"));
+ rc = RTNetStrToIPv4Addr(szVal, &tmpAddr);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"SecondaryIP\" is not valid"));
+ else
+ pThis->pszSecondaryIP = RTStrDup(szVal);
+ rc = pHlp->pfnCFGMQueryBytes(pCfg, "TargetMAC", pThis->targetMac.au8, sizeof(pThis->targetMac.au8));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Failed to get target MAC address"));
+ /** @todo In the near future we will want to include proxy settings here! */
+ // Do we want to pass the user name via CFGM?
+ pThis->pszUser = RTStrDup("opc");
+ // Is it safe to expose verbosity via CFGM?
+#ifdef LOG_ENABLED
+ pThis->iSshVerbosity = SSH_LOG_PACKET; //SSH_LOG_FUNCTIONS;
+#else
+ pThis->iSshVerbosity = SSH_LOG_WARNING;
+#endif
+
+ pThis->ulTimeoutInSecounds = 30; /* The default 10-second timeout is too short? */
+
+ rc = pHlp->pfnCFGMQueryPassword(pCfg, "SshKey", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SshKey\" as password failed"));
+ rc = ssh_pki_import_privkey_base64(szVal, NULL, NULL, NULL, &pThis->SshKey);
+ RTMemWipeThoroughly(szVal, sizeof(szVal), 10);
+ if (rc != SSH_OK)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_INVALID_BASE64_ENCODING,
+ N_("DrvCloudTunnel: Configuration error: Converting \"SshKey\" from base64 failed"));
+
+ /* PrimaryProxyType is optional */
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "PrimaryProxyType", &pThis->pszPrimaryProxyType, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryProxyType\" as string failed"));
+ if (pThis->pszPrimaryProxyType)
+ {
+ rc = pHlp->pfnCFGMQueryString(pCfg, "PrimaryProxyHost", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryProxyHost\" as string failed"));
+ rc = RTNetStrToIPv4Addr(szVal, &tmpAddr);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"PrimaryProxyHost\" is not valid"));
+ else
+ pThis->pszPrimaryProxyHost = RTStrDup(szVal);
+
+ uint64_t u64Val;
+ rc = pHlp->pfnCFGMQueryInteger(pCfg, "PrimaryProxyPort", &u64Val);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryProxyPort\" as integer failed"));
+ if (u64Val > 0xFFFF)
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"PrimaryProxyPort\" is not valid"));
+ pThis->u16PrimaryProxyPort = (uint16_t)u64Val;
+
+ /* PrimaryProxyUser is optional */
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "PrimaryProxyUser", &pThis->pszPrimaryProxyUser, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryProxyUser\" as string failed"));
+ /* PrimaryProxyPassword must be present if PrimaryProxyUser is present */
+ if (pThis->pszPrimaryProxyUser)
+ {
+ rc = pHlp->pfnCFGMQueryPassword(pCfg, "PrimaryProxyPassword", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"PrimaryProxyPassword\" as string failed"));
+ pThis->pszPrimaryProxyPassword = RTStrDup(szVal);
+ }
+ }
+
+ /* SecondaryProxyType is optional */
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "SecondaryProxyType", &pThis->pszSecondaryProxyType, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyType\" as string failed"));
+ if (pThis->pszSecondaryProxyType)
+ {
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyType\" as string failed"));
+
+ rc = pHlp->pfnCFGMQueryString(pCfg, "SecondaryProxyHost", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyHost\" as string failed"));
+ rc = RTNetStrToIPv4Addr(szVal, &tmpAddr);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"SecondaryProxyHost\" is not valid"));
+ else
+ pThis->pszSecondaryProxyHost = RTStrDup(szVal);
+
+ uint64_t u64Val;
+ rc = pHlp->pfnCFGMQueryInteger(pCfg, "SecondaryProxyPort", &u64Val);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyPort\" as integer failed"));
+ if (u64Val > 0xFFFF)
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: \"SecondaryProxyPort\" is not valid"));
+ pThis->u16SecondaryProxyPort = (uint16_t)u64Val;
+
+ /* SecondaryProxyUser is optional */
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "SecondaryProxyUser", &pThis->pszSecondaryProxyUser, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyUser\" as string failed"));
+ /* SecondaryProxyPassword must be present if SecondaryProxyUser is present */
+ if (pThis->pszSecondaryProxyUser)
+ {
+ rc = pHlp->pfnCFGMQueryPassword(pCfg, "SecondaryProxyPassword", szVal, sizeof(szVal));
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvCloudTunnel: Configuration error: Querying \"SecondaryProxyPassword\" as string failed"));
+ pThis->pszSecondaryProxyPassword = RTStrDup(szVal);
+ }
+ }
+
+ pThis->pszCommandBuffer = (char *)RTMemAlloc(DRVCLOUDTUNNEL_COMMAND_BUFFER_SIZE);
+ if (pThis->pszCommandBuffer == NULL)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_HIF_OPEN_FAILED,
+ N_("DrvCloudTunnel: Failed to allocate command buffer"));
+ pThis->pszOutputBuffer = (char *)RTMemAlloc(DRVCLOUDTUNNEL_OUTPUT_BUFFER_SIZE);
+ if (pThis->pszOutputBuffer == NULL)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_HIF_OPEN_FAILED,
+ N_("DrvCloudTunnel: Failed to allocate output buffer"));
+ /*
+ * Create unique instance name for logging.
+ */
+ rc = RTStrAPrintf(&pThis->pszInstance, "CT#%d", pDrvIns->iInstance);
+ AssertRC(rc);
+
+ LogRel(("%s: primary=%s secondary=%s target-mac=%RTmac\n", pThis->pszInstance, pThis->pszPrimaryIP, pThis->pszSecondaryIP, pThis->targetMac.au8));
+
+ /*
+ * Create unique thread name for cloud I/O.
+ */
+ rc = RTStrAPrintf(&pThis->pszInstanceIo, "CTunIO%d", pDrvIns->iInstance);
+ AssertRC(rc);
+
+ /*
+ * Create unique thread name for device receive function.
+ */
+ rc = RTStrAPrintf(&pThis->pszInstanceDev, "CTunDev%d", pDrvIns->iInstance);
+ AssertRC(rc);
+
+ /*
+ * Create the transmit lock.
+ */
+ rc = RTCritSectInit(&pThis->XmitLock);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the request queue for I/O requests.
+ */
+ rc = RTReqQueueCreate(&pThis->hIoReqQueue);
+ AssertLogRelRCReturn(rc, rc);
+
+ /*
+ * Create the request queue for attached device requests.
+ */
+ rc = RTReqQueueCreate(&pThis->hDevReqQueue);
+ AssertLogRelRCReturn(rc, rc);
+
+ /*
+ * Start the device output thread.
+ */
+ rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pDevThread,
+ pThis, drvCloudTunnelDevThread, drvCloudTunnelDevWakeup,
+ 64 * _1K, RTTHREADTYPE_IO, pThis->pszInstanceDev);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to start device thread"));
+
+ rc = ssh_init();
+ if (rc != SSH_OK)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to initialize libssh"));
+
+ memset(&pThis->Callbacks, 0, sizeof(pThis->Callbacks));
+#ifdef PACKET_CAPTURE_ENABLED
+ pThis->Callbacks.channel_data_function = drvCloudTunnelReceiveCallbackWithPacketCapture;
+#else
+ pThis->Callbacks.channel_data_function = drvCloudTunnelReceiveCallback;
+#endif
+ pThis->Callbacks.userdata = pThis;
+ pThis->Callbacks.channel_write_wontblock_function = channelWriteWontblockCallback;
+ ssh_callbacks_init(&pThis->Callbacks);
+
+ rc = ssh_set_log_callback(drvCloudTunnelSshLogCallback);
+ if (rc != SSH_OK)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to set libssh log callback"));
+ rc = ssh_set_log_userdata(pThis);
+ if (rc != SSH_OK)
+ return PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to set libssh log userdata"));
+
+ rc = drvCloudTunnelSwitchToSecondary(pThis);
+ if (RT_SUCCESS(rc))
+ rc = establishTunnel(pThis);
+
+ return rc;
+}
+
+
+#if 0
+/**
+ * Suspend notification.
+ *
+ * @param pDrvIns The driver instance.
+ */
+static DECLCALLBACK(void) drvCloudTunnelSuspend(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+
+ RT_NOREF(pThis);
+ // if (pThis->pServer)
+ // {
+ // RTUdpServerDestroy(pThis->pServer);
+ // pThis->pServer = NULL;
+ // }
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @param pDrvIns The driver instance.
+ */
+static DECLCALLBACK(void) drvCloudTunnelResume(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PDRVCLOUDTUNNEL pThis = PDMINS_2_DATA(pDrvIns, PDRVCLOUDTUNNEL);
+
+ int rc = RTUdpServerCreate("", pThis->uSrcPort, RTTHREADTYPE_IO, pThis->pszInstance,
+ drvCloudTunnelReceive, pDrvIns, &pThis->pServer);
+ if (RT_FAILURE(rc))
+ PDMDrvHlpVMSetError(pThis->pDrvIns, VERR_PDM_HIF_OPEN_FAILED, RT_SRC_POS,
+ N_("CloudTunnel: Failed to start the Cloud tunnel server"));
+
+}
+#endif
+
+/**
+ * Cloud tunnel network transport driver registration record.
+ */
+const PDMDRVREG g_DrvCloudTunnel =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "CloudTunnel",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Cloud Tunnel Network Transport Driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_NETWORK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVCLOUDTUNNEL),
+ /* pfnConstruct */
+ drvCloudTunnelConstruct,
+ /* pfnDestruct */
+ drvCloudTunnelDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL, // drvCloudTunnelSuspend,
+ /* pfnResume */
+ NULL, // drvCloudTunnelResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+