/* $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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_CTUN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "VBoxDD.h" #ifdef RT_OS_WINDOWS # include typedef int socklen_t; #else # include 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 #include /********************************************************************************************************************************* * 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 };