/** * Reads a string from the socket. * * @returns VBox status code. * * @param pState The teleporter state structure. * @param pszBuf The output buffer. * @param cchBuf The size of the output buffer. * */ static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf) { char *pszStart = pszBuf; RTSOCKET Sock = pState->mhSocket; AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR); *pszBuf = '\0'; /* dead simple approach. */ for (;;) { char ch; int rc = RTTcpRead(Sock, &ch, sizeof(ch), NULL); if (RT_FAILURE(rc)) { LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", rc, pszStart)); return rc; } if ( ch == '\n' || ch == '\0') return VINF_SUCCESS; if (cchBuf <= 1) { LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart)); return VERR_BUFFER_OVERFLOW; } *pszBuf++ = ch; *pszBuf = '\0'; cchBuf--; } }
/** * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt} */ static DECLCALLBACK(int) txsTcpRecvPkt(PPTXSPKTHDR ppPktHdr) { int rc = VINF_SUCCESS; *ppPktHdr = NULL; /* * Do we have to wait for a client to connect? */ RTSOCKET hTcpClient = g_hTcpClient; if (hTcpClient == NIL_RTSOCKET) { rc = txsTcpConnect(); if (RT_FAILURE(rc)) return rc; hTcpClient = g_hTcpClient; Assert(hTcpClient != NIL_RTSOCKET); } /* * Read state. */ size_t offData = 0; size_t cbData = 0; size_t cbDataAlloced; uint8_t *pbData = NULL; /* * Any stashed data? */ if (g_cbTcpStashedAlloced) { offData = g_cbTcpStashed; cbDataAlloced = g_cbTcpStashedAlloced; pbData = g_pbTcpStashed; g_cbTcpStashed = 0; g_cbTcpStashedAlloced = 0; g_pbTcpStashed = NULL; } else { cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT); pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); if (!pbData) return VERR_NO_MEMORY; } /* * Read and valid the length. */ while (offData < sizeof(uint32_t)) { size_t cbRead; rc = RTTcpRead(hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); if (RT_FAILURE(rc)) break; if (cbRead == 0) { Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); rc = VERR_NET_NOT_CONNECTED; break; } offData += cbRead; } if (RT_SUCCESS(rc)) { ASMCompilerBarrier(); /* paranoia^3 */ cbData = *(uint32_t volatile *)pbData; if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE) { /* * Align the length and reallocate the return packet it necessary. */ cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT); if (cbData > cbDataAlloced) { void *pvNew = RTMemRealloc(pbData, cbData); if (pvNew) { pbData = (uint8_t *)pvNew; cbDataAlloced = cbData; } else rc = VERR_NO_MEMORY; } if (RT_SUCCESS(rc)) { /* * Read the remainder of the data. */ while (offData < cbData) { size_t cbRead; rc = RTTcpRead(hTcpClient, pbData + offData, cbData - offData, &cbRead); if (RT_FAILURE(rc)) break; if (cbRead == 0) { Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); rc = VERR_NET_NOT_CONNECTED; break; } offData += cbRead; } } } else rc = VERR_NET_PROTOCOL_ERROR; } if (RT_SUCCESS(rc)) *ppPktHdr = (PTXSPKTHDR)pbData; else { /* * Deal with errors. */ if (rc == VERR_INTERRUPTED) { /* stash it away for the next call. */ g_cbTcpStashed = cbData; g_cbTcpStashedAlloced = cbDataAlloced; g_pbTcpStashed = pbData; } else { RTMemFree(pbData); /* assume fatal connection error. */ Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient)); txsTcpDisconnectClient(); } } return rc; }
/** * Do the teleporter. * * @returns VBox status code. * @param pState The teleporter state. */ HRESULT Console::teleporterSrc(TeleporterStateSrc *pState) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); /* * Wait for Console::Teleport to change the state. */ { AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); } BOOL fCanceled = TRUE; HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled); if (FAILED(hrc)) return hrc; if (fCanceled) return setError(E_FAIL, tr("canceled")); /* * Try connect to the destination machine, disable Nagle. * (Note. The caller cleans up mhSocket, so we can return without worries.) */ int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket); if (RT_FAILURE(vrc)) return setError(E_FAIL, tr("Failed to connect to port %u on '%s': %Rrc"), pState->muPort, pState->mstrHostname.c_str(), vrc); vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/); AssertRC(vrc); /* Read and check the welcome message. */ char szLine[RT_MAX(128, sizeof(g_szWelcome))]; RT_ZERO(szLine); vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL); if (RT_FAILURE(vrc)) return setError(E_FAIL, tr("Failed to read welcome message: %Rrc"), vrc); if (strcmp(szLine, g_szWelcome)) return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine); /* password */ pState->mstrPassword.append('\n'); vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length()); if (RT_FAILURE(vrc)) return setError(E_FAIL, tr("Failed to send password: %Rrc"), vrc); /* ACK */ hrc = teleporterSrcReadACK(pState, "password", tr("Invalid password")); if (FAILED(hrc)) return hrc; /* * Start loading the state. * * Note! The saved state includes vital configuration data which will be * verified against the VM config on the other end. This is all done * in the first pass, so we should fail pretty promptly on misconfig. */ hrc = teleporterSrcSubmitCommand(pState, "load"); if (FAILED(hrc)) return hrc; RTSocketRetain(pState->mhSocket); void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState)); vrc = VMR3Teleport(VMR3GetVM(pState->mpUVM), pState->mcMsMaxDowntime, &g_teleporterTcpOps, pvUser, teleporterProgressCallback, pvUser, &pState->mfSuspendedByUs); RTSocketRelease(pState->mhSocket); if (RT_FAILURE(vrc)) { if ( vrc == VERR_SSM_CANCELLED && RT_SUCCESS(RTTcpSelectOne(pState->mhSocket, 1))) { hrc = teleporterSrcReadACK(pState, "load-complete"); if (FAILED(hrc)) return hrc; } return setError(E_FAIL, tr("VMR3Teleport -> %Rrc"), vrc); } hrc = teleporterSrcReadACK(pState, "load-complete"); if (FAILED(hrc)) return hrc; /* * We're at the point of no return. */ if (!pState->mptrProgress->notifyPointOfNoReturn()) { teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/); return E_FAIL; } /* * Hand over any media which we might be sharing. * * Note! This is only important on localhost teleportations. */ /** @todo Maybe we should only do this if it's a local teleportation... */ hrc = mControl->UnlockMedia(); if (FAILED(hrc)) return hrc; pState->mfUnlockedMedia = true; hrc = teleporterSrcSubmitCommand(pState, "lock-media"); if (FAILED(hrc)) return hrc; /* * The FINAL step is giving the target instructions how to proceed with the VM. */ if ( vrc == VINF_SSM_LIVE_SUSPENDED || pState->menmOldMachineState == MachineState_Paused) hrc = teleporterSrcSubmitCommand(pState, "hand-over-paused"); else hrc = teleporterSrcSubmitCommand(pState, "hand-over-resume"); if (FAILED(hrc)) return hrc; /* * teleporterSrcThreadWrapper will do the automatic power off because it * has to release the AutoVMCaller. */ return S_OK; }
/** * @copydoc SSMSTRMOPS::pfnRead */ static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) { TeleporterState *pState = (TeleporterState *)pvUser; AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE); for (;;) { int rc; /* * Check for various conditions and may have been signalled. */ if (pState->mfEndOfStream) return VERR_EOF; if (pState->mfStopReading) return VERR_EOF; if (pState->mfIOError) return VERR_IO_GEN_FAILURE; /* * If there is no more data in the current block, read the next * block header. */ if (!pState->mcbReadBlock) { rc = teleporterTcpReadSelect(pState); if (RT_FAILURE(rc)) return rc; TELEPORTERTCPHDR Hdr; rc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL); if (RT_FAILURE(rc)) { pState->mfIOError = true; LogRel(("Teleporter/TCP: Header read error: %Rrc\n", rc)); return rc; } if (RT_UNLIKELY( Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE || Hdr.cb == 0)) { if ( Hdr.u32Magic == TELEPORTERTCPHDR_MAGIC && ( Hdr.cb == 0 || Hdr.cb == UINT32_MAX) ) { pState->mfEndOfStream = true; pState->mcbReadBlock = 0; return Hdr.cb ? VERR_SSM_CANCELLED : VERR_EOF; } pState->mfIOError = true; LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb)); return VERR_IO_GEN_FAILURE; } pState->mcbReadBlock = Hdr.cb; if (pState->mfStopReading) return VERR_EOF; } /* * Read more data. */ rc = teleporterTcpReadSelect(pState); if (RT_FAILURE(rc)) return rc; uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead); rc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead); if (RT_FAILURE(rc)) { pState->mfIOError = true; LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", rc, cb)); return rc; } if (pcbRead) { cb = (uint32_t)*pcbRead; pState->moffStream += cb; pState->mcbReadBlock -= cb; return VINF_SUCCESS; } pState->moffStream += cb; pState->mcbReadBlock -= cb; if (cbToRead == cb) return VINF_SUCCESS; /* Advance to the next block. */ cbToRead -= cb; pvBuf = (uint8_t *)pvBuf + cb; } }
Console::teleporterTrgServeConnection(RTSOCKET Sock, void *pvUser) { TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser; pState->mhSocket = Sock; /* * Disable Nagle and say hello. */ int vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/); AssertRC(vrc); vrc = RTTcpWrite(Sock, g_szWelcome, sizeof(g_szWelcome) - 1); if (RT_FAILURE(vrc)) { LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc)); return VINF_SUCCESS; } /* * Password (includes '\n', see teleporterTrg). */ const char *pszPassword = pState->mstrPassword.c_str(); unsigned off = 0; while (pszPassword[off]) { char ch; vrc = RTTcpRead(Sock, &ch, sizeof(ch), NULL); if ( RT_FAILURE(vrc) || pszPassword[off] != ch) { if (RT_FAILURE(vrc)) LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc)); else LogRel(("Teleporter: Invalid password (off=%u)\n", off)); teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE); return VINF_SUCCESS; } off++; } vrc = teleporterTcpWriteACK(pState); if (RT_FAILURE(vrc)) return VINF_SUCCESS; /* * Update the progress bar, with peer name if available. */ HRESULT hrc; RTNETADDR Addr; vrc = RTTcpGetPeerAddress(Sock, &Addr); if (RT_SUCCESS(vrc)) { LogRel(("Teleporter: Incoming VM from %RTnaddr!\n", &Addr)); hrc = pState->mptrProgress->SetNextOperation(BstrFmt(tr("Teleporting VM from %RTnaddr"), &Addr).raw(), 8); } else { LogRel(("Teleporter: Incoming VM!\n")); hrc = pState->mptrProgress->SetNextOperation(Bstr(tr("Teleporting VM")).raw(), 8); } AssertMsg(SUCCEEDED(hrc) || hrc == E_FAIL, ("%Rhrc\n", hrc)); /* * Stop the server and cancel the timeout timer. * * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior * to it we must not return that value! */ RTTcpServerShutdown(pState->mhServer); RTTimerLRDestroy(*pState->mphTimerLR); *pState->mphTimerLR = NIL_RTTIMERLR; /* * Command processing loop. */ bool fDone = false; for (;;) { char szCmd[128]; vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd)); if (RT_FAILURE(vrc)) break; if (!strcmp(szCmd, "load")) { vrc = teleporterTcpWriteACK(pState); if (RT_FAILURE(vrc)) break; int vrc2 = VMR3AtErrorRegisterU(pState->mpUVM, Console::genericVMSetErrorCallback, &pState->mErrorText); AssertRC(vrc2); RTSocketRetain(pState->mhSocket); /* For concurrent access by I/O thread and EMT. */ pState->moffStream = 0; void *pvUser2 = static_cast<void *>(static_cast<TeleporterState *>(pState)); vrc = VMR3LoadFromStream(VMR3GetVM(pState->mpUVM), &g_teleporterTcpOps, pvUser2, teleporterProgressCallback, pvUser2); RTSocketRelease(pState->mhSocket); vrc2 = VMR3AtErrorDeregister(VMR3GetVM(pState->mpUVM), Console::genericVMSetErrorCallback, &pState->mErrorText); AssertRC(vrc2); if (RT_FAILURE(vrc)) { LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc)); teleporterTcpWriteNACK(pState, vrc, pState->mErrorText.c_str()); break; } /* The EOS might not have been read, make sure it is. */ pState->mfStopReading = false; size_t cbRead; vrc = teleporterTcpOpRead(pvUser2, pState->moffStream, szCmd, 1, &cbRead); if (vrc != VERR_EOF) { LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc)); teleporterTcpWriteNACK(pState, vrc); break; } vrc = teleporterTcpWriteACK(pState); } else if (!strcmp(szCmd, "cancel")) { /* Don't ACK this. */ LogRel(("Teleporter: Received cancel command.\n")); vrc = VERR_SSM_CANCELLED; } else if (!strcmp(szCmd, "lock-media")) { hrc = pState->mpControl->LockMedia(); if (SUCCEEDED(hrc)) { pState->mfLockedMedia = true; vrc = teleporterTcpWriteACK(pState); } else { vrc = VERR_FILE_LOCK_FAILED; teleporterTcpWriteNACK(pState, vrc); } } else if ( !strcmp(szCmd, "hand-over-resume") || !strcmp(szCmd, "hand-over-paused")) { /* * Point of no return. * * Note! Since we cannot tell whether a VMR3Resume failure is * destructive for the source or not, we have little choice * but to ACK it first and take any failures locally. * * Ideally, we should try resume it first and then ACK (or * NACK) the request since this would reduce latency and * make it possible to recover from some VMR3Resume failures. */ if ( pState->mptrProgress->notifyPointOfNoReturn() && pState->mfLockedMedia) { vrc = teleporterTcpWriteACK(pState); if (RT_SUCCESS(vrc)) { if (!strcmp(szCmd, "hand-over-resume")) vrc = VMR3Resume(VMR3GetVM(pState->mpUVM)); else pState->mptrConsole->setMachineState(MachineState_Paused); fDone = true; break; } } else { vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED; teleporterTcpWriteNACK(pState, vrc); } } else { LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd)); vrc = VERR_NOT_IMPLEMENTED; teleporterTcpWriteNACK(pState, vrc); } if (RT_FAILURE(vrc)) break; } if (RT_SUCCESS(vrc) && !fDone) vrc = VERR_WRONG_ORDER; if (RT_FAILURE(vrc)) teleporterTrgUnlockMedia(pState); pState->mRc = vrc; pState->mhSocket = NIL_RTSOCKET; LogFlowFunc(("returns mRc=%Rrc\n", vrc)); return VERR_TCP_SERVER_STOP; }