diff options
Diffstat (limited to 'src/VBox/Devices/Network/DrvCloudTunnel.cpp')
-rw-r--r-- | src/VBox/Devices/Network/DrvCloudTunnel.cpp | 1825 |
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 +}; + |