From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../src-server/generic/AutostartDb-generic.cpp | 271 +++++ src/VBox/Main/src-server/generic/Makefile.kup | 0 src/VBox/Main/src-server/generic/NetIf-generic.cpp | 432 ++++++++ .../src-server/generic/USBProxyBackendUsbIp.cpp | 1078 ++++++++++++++++++++ 4 files changed, 1781 insertions(+) create mode 100644 src/VBox/Main/src-server/generic/AutostartDb-generic.cpp create mode 100644 src/VBox/Main/src-server/generic/Makefile.kup create mode 100644 src/VBox/Main/src-server/generic/NetIf-generic.cpp create mode 100644 src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp (limited to 'src/VBox/Main/src-server/generic') diff --git a/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp b/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp new file mode 100644 index 00000000..f58a4865 --- /dev/null +++ b/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp @@ -0,0 +1,271 @@ +/* $Id: AutostartDb-generic.cpp $ */ +/** @file + * VirtualBox Main - Autostart implementation. + */ + +/* + * Copyright (C) 2009-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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AutostartDb.h" + +#if defined(RT_OS_LINUX) +/** + * Modifies the autostart database. + * + * @returns VBox status code. + * @param fAutostart Flag whether the autostart or autostop database is modified. + * @param fAddVM Flag whether a VM is added or removed from the database. + */ +int AutostartDb::autostartModifyDb(bool fAutostart, bool fAddVM) +{ + int vrc = VINF_SUCCESS; + char *pszUser = NULL; + + /* Check if the path is set. */ + if (!m_pszAutostartDbPath) + return VERR_PATH_NOT_FOUND; + + vrc = RTProcQueryUsernameA(RTProcSelf(), &pszUser); + if (RT_SUCCESS(vrc)) + { + char *pszFile; + uint64_t fOpen = RTFILE_O_DENY_ALL | RTFILE_O_READWRITE; + RTFILE hAutostartFile; + + AssertPtr(pszUser); + + if (fAddVM) + fOpen |= RTFILE_O_OPEN_CREATE; + else + fOpen |= RTFILE_O_OPEN; + + vrc = RTStrAPrintf(&pszFile, "%s/%s.%s", m_pszAutostartDbPath, pszUser, fAutostart ? "start" : "stop"); + if (RT_SUCCESS(vrc)) + { + vrc = RTFileOpen(&hAutostartFile, pszFile, fOpen); + if (RT_SUCCESS(vrc)) + { + uint64_t cbFile; + + /* + * Files with more than 16 bytes are rejected because they just contain + * a number of the amount of VMs with autostart configured, so they + * should be really really small. Anything else is bogus. + */ + vrc = RTFileQuerySize(hAutostartFile, &cbFile); + if ( RT_SUCCESS(vrc) + && cbFile <= 16) + { + char abBuf[16 + 1]; /* trailing \0 */ + uint32_t cAutostartVms = 0; + + RT_ZERO(abBuf); + + /* Check if the file was just created. */ + if (cbFile) + { + vrc = RTFileRead(hAutostartFile, abBuf, (size_t)cbFile, NULL); + if (RT_SUCCESS(vrc)) + { + vrc = RTStrToUInt32Ex(abBuf, NULL, 10 /* uBase */, &cAutostartVms); + if ( vrc == VWRN_TRAILING_CHARS + || vrc == VWRN_TRAILING_SPACES) + vrc = VINF_SUCCESS; + } + } + + if (RT_SUCCESS(vrc)) + { + size_t cbBuf; + + /* Modify VM counter and write back. */ + if (fAddVM) + cAutostartVms++; + else + cAutostartVms--; + + if (cAutostartVms > 0) + { + cbBuf = RTStrPrintf(abBuf, sizeof(abBuf), "%u", cAutostartVms); + vrc = RTFileSetSize(hAutostartFile, cbBuf); + if (RT_SUCCESS(vrc)) + vrc = RTFileWriteAt(hAutostartFile, 0, abBuf, cbBuf, NULL); + } + else + { + /* Just delete the file if there are no VMs left. */ + RTFileClose(hAutostartFile); + RTFileDelete(pszFile); + hAutostartFile = NIL_RTFILE; + } + } + } + else if (RT_SUCCESS(vrc)) + vrc = VERR_FILE_TOO_BIG; + + if (hAutostartFile != NIL_RTFILE) + RTFileClose(hAutostartFile); + } + RTStrFree(pszFile); + } + + RTStrFree(pszUser); + } + + return vrc; +} + +#endif + +AutostartDb::AutostartDb() +{ +#ifdef RT_OS_LINUX + int vrc = RTCritSectInit(&this->CritSect); + NOREF(vrc); + m_pszAutostartDbPath = NULL; +#endif +} + +AutostartDb::~AutostartDb() +{ +#ifdef RT_OS_LINUX + RTCritSectDelete(&this->CritSect); + if (m_pszAutostartDbPath) + RTStrFree(m_pszAutostartDbPath); +#endif +} + +int AutostartDb::setAutostartDbPath(const char *pszAutostartDbPathNew) +{ +#if defined(RT_OS_LINUX) + char *pszAutostartDbPathTmp = NULL; + + if (pszAutostartDbPathNew) + { + pszAutostartDbPathTmp = RTStrDup(pszAutostartDbPathNew); + if (!pszAutostartDbPathTmp) + return VERR_NO_MEMORY; + } + + RTCritSectEnter(&this->CritSect); + if (m_pszAutostartDbPath) + RTStrFree(m_pszAutostartDbPath); + + m_pszAutostartDbPath = pszAutostartDbPathTmp; + RTCritSectLeave(&this->CritSect); + return VINF_SUCCESS; +#else + NOREF(pszAutostartDbPathNew); + return VERR_NOT_SUPPORTED; +#endif +} + +int AutostartDb::addAutostartVM(const char *pszVMId) +{ + int vrc = VERR_NOT_SUPPORTED; + +#if defined(RT_OS_LINUX) + NOREF(pszVMId); /* Not needed */ + + RTCritSectEnter(&this->CritSect); + vrc = autostartModifyDb(true /* fAutostart */, true /* fAddVM */); + RTCritSectLeave(&this->CritSect); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) + NOREF(pszVMId); /* Not needed */ + vrc = VINF_SUCCESS; +#else + NOREF(pszVMId); + vrc = VERR_NOT_SUPPORTED; +#endif + + return vrc; +} + +int AutostartDb::removeAutostartVM(const char *pszVMId) +{ + int vrc = VINF_SUCCESS; + +#if defined(RT_OS_LINUX) + NOREF(pszVMId); /* Not needed */ + RTCritSectEnter(&this->CritSect); + vrc = autostartModifyDb(true /* fAutostart */, false /* fAddVM */); + RTCritSectLeave(&this->CritSect); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) + NOREF(pszVMId); /* Not needed */ + vrc = VINF_SUCCESS; +#else + NOREF(pszVMId); + vrc = VERR_NOT_SUPPORTED; +#endif + + return vrc; +} + +int AutostartDb::addAutostopVM(const char *pszVMId) +{ + int vrc = VINF_SUCCESS; + +#if defined(RT_OS_LINUX) + NOREF(pszVMId); /* Not needed */ + RTCritSectEnter(&this->CritSect); + vrc = autostartModifyDb(false /* fAutostart */, true /* fAddVM */); + RTCritSectLeave(&this->CritSect); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS) + NOREF(pszVMId); /* Not needed */ + vrc = VINF_SUCCESS; +#else + NOREF(pszVMId); + vrc = VERR_NOT_SUPPORTED; +#endif + + return vrc; +} + +int AutostartDb::removeAutostopVM(const char *pszVMId) +{ + int vrc = VINF_SUCCESS; + +#if defined(RT_OS_LINUX) + NOREF(pszVMId); /* Not needed */ + RTCritSectEnter(&this->CritSect); + vrc = autostartModifyDb(false /* fAutostart */, false /* fAddVM */); + RTCritSectLeave(&this->CritSect); +#elif defined(RT_OS_DARWIN) || defined (RT_OS_WINDOWS) + NOREF(pszVMId); /* Not needed */ + vrc = VINF_SUCCESS; +#else + NOREF(pszVMId); + vrc = VERR_NOT_SUPPORTED; +#endif + + return vrc; +} + diff --git a/src/VBox/Main/src-server/generic/Makefile.kup b/src/VBox/Main/src-server/generic/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/Main/src-server/generic/NetIf-generic.cpp b/src/VBox/Main/src-server/generic/NetIf-generic.cpp new file mode 100644 index 00000000..1e2eb618 --- /dev/null +++ b/src/VBox/Main/src-server/generic/NetIf-generic.cpp @@ -0,0 +1,432 @@ +/* $Id: NetIf-generic.cpp $ */ +/** @file + * VirtualBox Main - Generic NetIf implementation. + */ + +/* + * Copyright (C) 2009-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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(RT_OS_SOLARIS) +# include +#endif + +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) +# include +#endif + +#include "HostNetworkInterfaceImpl.h" +#include "ProgressImpl.h" +#include "VirtualBoxImpl.h" +#include "VBoxNls.h" +#include "Global.h" +#include "netif.h" + +#define VBOXNETADPCTL_NAME "VBoxNetAdpCtl" + +DECLARE_TRANSLATION_CONTEXT(NetIfGeneric); + + +static int NetIfAdpCtl(const char * pcszIfName, const char *pszAddr, const char *pszOption, const char *pszMask) +{ + const char *args[] = { NULL, pcszIfName, pszAddr, pszOption, pszMask, NULL }; + + char szAdpCtl[RTPATH_MAX]; + int vrc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME)); + if (RT_FAILURE(vrc)) + { + LogRel(("NetIfAdpCtl: failed to get program path, vrc=%Rrc.\n", vrc)); + return vrc; + } + strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME); + args[0] = szAdpCtl; + if (!RTPathExists(szAdpCtl)) + { + LogRel(("NetIfAdpCtl: path %s does not exist. Failed to run " VBOXNETADPCTL_NAME " helper.\n", + szAdpCtl)); + return VERR_FILE_NOT_FOUND; + } + + RTPROCESS pid; + vrc = RTProcCreate(szAdpCtl, args, RTENV_DEFAULT, 0, &pid); + if (RT_SUCCESS(vrc)) + { + RTPROCSTATUS Status; + vrc = RTProcWait(pid, 0, &Status); + if (RT_SUCCESS(vrc)) + { + if ( Status.iStatus == 0 + && Status.enmReason == RTPROCEXITREASON_NORMAL) + return VINF_SUCCESS; + LogRel(("NetIfAdpCtl: failed to create process for %s: iStats=%d enmReason=%d\n", + szAdpCtl, Status.iStatus, Status.enmReason)); + vrc = -Status.iStatus; + } + } + else + LogRel(("NetIfAdpCtl: failed to create process for %s: %Rrc\n", szAdpCtl, vrc)); + return vrc; +} + +static int NetIfAdpCtl(HostNetworkInterface * pIf, const char *pszAddr, const char *pszOption, const char *pszMask) +{ + Bstr interfaceName; + pIf->COMGETTER(Name)(interfaceName.asOutParam()); + Utf8Str strName(interfaceName); + return NetIfAdpCtl(strName.c_str(), pszAddr, pszOption, pszMask); +} + +int NetIfAdpCtlOut(const char * pcszName, const char * pcszCmd, char *pszBuffer, size_t cBufSize) +{ + char szAdpCtl[RTPATH_MAX]; + int vrc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME " ") - strlen(pcszCmd)); + if (RT_FAILURE(vrc)) + { + LogRel(("NetIfAdpCtlOut: Failed to get program path, vrc=%Rrc\n", vrc)); + return VERR_INVALID_PARAMETER; + } + strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME " "); + if (pcszName && strlen(pcszName) <= RTPATH_MAX - strlen(szAdpCtl) - 1 - strlen(pcszCmd)) + { + strcat(szAdpCtl, pcszName); + strcat(szAdpCtl, " "); + strcat(szAdpCtl, pcszCmd); + } + else + { + LogRel(("NetIfAdpCtlOut: Command line is too long: %s%s %s\n", szAdpCtl, pcszName, pcszCmd)); + return VERR_INVALID_PARAMETER; + } + if (strlen(szAdpCtl) < RTPATH_MAX - sizeof(" 2>&1")) + strcat(szAdpCtl, " 2>&1"); + FILE *fp = popen(szAdpCtl, "r"); + if (fp) + { + if (fgets(pszBuffer, (int)cBufSize, fp)) + { + if (!strncmp(VBOXNETADPCTL_NAME ":", pszBuffer, sizeof(VBOXNETADPCTL_NAME))) + { + LogRel(("NetIfAdpCtlOut: %s", pszBuffer)); + vrc = VERR_INTERNAL_ERROR; + } + } + else + { + LogRel(("NetIfAdpCtlOut: No output from " VBOXNETADPCTL_NAME)); + vrc = VERR_INTERNAL_ERROR; + } + pclose(fp); + } + return vrc; +} + +int NetIfEnableStaticIpConfig(VirtualBox * /* vBox */, HostNetworkInterface * pIf, ULONG aOldIp, ULONG aNewIp, ULONG aMask) +{ + const char *pszOption, *pszMask; + char szAddress[16]; /* 4*3 + 3*1 + 1 */ + char szNetMask[16]; /* 4*3 + 3*1 + 1 */ + uint8_t *pu8Addr = (uint8_t *)&aNewIp; + uint8_t *pu8Mask = (uint8_t *)&aMask; + if (aNewIp == 0) + { + pu8Addr = (uint8_t *)&aOldIp; + pszOption = "remove"; + pszMask = NULL; + } + else + { + pszOption = "netmask"; + pszMask = szNetMask; + RTStrPrintf(szNetMask, sizeof(szNetMask), "%d.%d.%d.%d", + pu8Mask[0], pu8Mask[1], pu8Mask[2], pu8Mask[3]); + } + RTStrPrintf(szAddress, sizeof(szAddress), "%d.%d.%d.%d", + pu8Addr[0], pu8Addr[1], pu8Addr[2], pu8Addr[3]); + return NetIfAdpCtl(pIf, szAddress, pszOption, pszMask); +} + +int NetIfEnableStaticIpConfigV6(VirtualBox * /* vBox */, HostNetworkInterface * pIf, const Utf8Str &aOldIPV6Address, + const Utf8Str &aIPV6Address, ULONG aIPV6MaskPrefixLength) +{ + char szAddress[5*8 + 1 + 5 + 1]; + if (aIPV6Address.length()) + { + RTStrPrintf(szAddress, sizeof(szAddress), "%s/%d", + aIPV6Address.c_str(), aIPV6MaskPrefixLength); + return NetIfAdpCtl(pIf, szAddress, NULL, NULL); + } + else + { + RTStrPrintf(szAddress, sizeof(szAddress), "%s", + aOldIPV6Address.c_str()); + return NetIfAdpCtl(pIf, szAddress, "remove", NULL); + } +} + +int NetIfEnableDynamicIpConfig(VirtualBox * /* vBox */, HostNetworkInterface * /* pIf */) +{ + return VERR_NOT_IMPLEMENTED; +} + + +int NetIfCreateHostOnlyNetworkInterface(VirtualBox *pVirtualBox, + IHostNetworkInterface **aHostNetworkInterface, + IProgress **aProgress, + const char *pcszName) +{ +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + /* create a progress object */ + ComObjPtr progress; + HRESULT hrc = progress.createObject(); + AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc)); + + /* Note vrc and hrc are competing about tracking the error state here. */ + int vrc = VINF_SUCCESS; + ComPtr host; + hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam()); + if (SUCCEEDED(hrc)) + { + hrc = progress->init(pVirtualBox, host, + NetIfGeneric::tr("Creating host only network interface"), + FALSE /* aCancelable */); + if (SUCCEEDED(hrc)) + { + progress.queryInterfaceTo(aProgress); + + char szAdpCtl[RTPATH_MAX]; + vrc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME " add")); + if (RT_FAILURE(vrc)) + { + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to get program path, vrc=%Rrc\n"), vrc); + return vrc; + } + strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME " "); + if (pcszName && strlen(pcszName) <= RTPATH_MAX - strlen(szAdpCtl) - sizeof(" add")) + { + strcat(szAdpCtl, pcszName); + strcat(szAdpCtl, " add"); + } + else + strcat(szAdpCtl, "add"); + if (strlen(szAdpCtl) < RTPATH_MAX - sizeof(" 2>&1")) + strcat(szAdpCtl, " 2>&1"); + + FILE *fp = popen(szAdpCtl, "r"); + if (fp) + { + char szBuf[128]; /* We are not interested in long error messages. */ + if (fgets(szBuf, sizeof(szBuf), fp)) + { + /* Remove trailing new line characters. */ + char *pLast = szBuf + strlen(szBuf) - 1; + if (pLast >= szBuf && *pLast == '\n') + *pLast = 0; + + if (!strncmp(VBOXNETADPCTL_NAME ":", szBuf, sizeof(VBOXNETADPCTL_NAME))) + { + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + "%s", szBuf); + pclose(fp); + return Global::vboxStatusCodeFromCOM(E_FAIL); + } + + size_t cbNameLen = strlen(szBuf) + 1; + PNETIFINFO pInfo = (PNETIFINFO)RTMemAllocZ(RT_UOFFSETOF_DYN(NETIFINFO, szName[cbNameLen])); + if (!pInfo) + vrc = VERR_NO_MEMORY; + else + { + strcpy(pInfo->szShortName, szBuf); + strcpy(pInfo->szName, szBuf); + vrc = NetIfGetConfigByName(pInfo); + if (RT_FAILURE(vrc)) + { + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to get config info for %s (as reported by 'VBoxNetAdpCtl add')\n"), + szBuf); + } + else + { + Utf8Str IfName(szBuf); + /* create a new uninitialized host interface object */ + ComObjPtr iface; + iface.createObject(); + iface->init(IfName, HostNetworkInterfaceType_HostOnly, pInfo); + iface->i_setVirtualBox(pVirtualBox); + iface.queryInterfaceTo(aHostNetworkInterface); + } + RTMemFree(pInfo); + } + if ((vrc = pclose(fp)) != 0) + { + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to execute '%s' - exit status: %d"), szAdpCtl, vrc); + vrc = VERR_INTERNAL_ERROR; + } + } + else + { + /* Failed to add an interface */ + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to execute '%s' (errno %d). Check permissions!"), + szAdpCtl, errno); + pclose(fp); + vrc = VERR_PERMISSION_DENIED; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to execute '%s' (errno %d / %Rrc). Check permissions!"), + szAdpCtl, errno, vrc); + } + if (RT_SUCCESS(vrc)) + progress->i_notifyComplete(S_OK); + else + hrc = E_FAIL; + } + } + + return RT_FAILURE(vrc) ? vrc : SUCCEEDED(hrc) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc); + +#else + NOREF(pVirtualBox); + NOREF(aHostNetworkInterface); + NOREF(aProgress); + NOREF(pcszName); + return VERR_NOT_IMPLEMENTED; +#endif +} + +int NetIfRemoveHostOnlyNetworkInterface(VirtualBox *pVirtualBox, const Guid &aId, + IProgress **aProgress) +{ +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + /* create a progress object */ + ComObjPtr progress; + HRESULT hrc = progress.createObject(); + AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc)); + + ComPtr host; + int vrc = VINF_SUCCESS; + hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam()); + if (SUCCEEDED(hrc)) + { + ComPtr iface; + if (FAILED(host->FindHostNetworkInterfaceById(aId.toUtf16().raw(), iface.asOutParam()))) + return VERR_INVALID_PARAMETER; + + Bstr ifname; + iface->COMGETTER(Name)(ifname.asOutParam()); + if (ifname.isEmpty()) + return VERR_INTERNAL_ERROR; + Utf8Str strIfName(ifname); + + hrc = progress->init(pVirtualBox, host, NetIfGeneric::tr("Removing host network interface"), FALSE /* aCancelable */); + if (SUCCEEDED(hrc)) + { + progress.queryInterfaceTo(aProgress); + vrc = NetIfAdpCtl(strIfName.c_str(), "remove", NULL, NULL); + if (RT_FAILURE(vrc)) + progress->i_notifyComplete(E_FAIL, + COM_IIDOF(IHostNetworkInterface), + HostNetworkInterface::getStaticComponentName(), + NetIfGeneric::tr("Failed to execute 'VBoxNetAdpCtl %s remove' (%Rrc)"), + strIfName.c_str(), vrc); + else + progress->i_notifyComplete(S_OK); + } + else + vrc = Global::vboxStatusCodeFromCOM(hrc); + } + else + vrc = Global::vboxStatusCodeFromCOM(hrc); + return vrc; +#else + NOREF(pVirtualBox); + NOREF(aId); + NOREF(aProgress); + return VERR_NOT_IMPLEMENTED; +#endif +} + +int NetIfGetConfig(HostNetworkInterface * /* pIf */, NETIFINFO *) +{ + return VERR_NOT_IMPLEMENTED; +} + +int NetIfDhcpRediscover(VirtualBox * /* pVBox */, HostNetworkInterface * /* pIf */) +{ + return VERR_NOT_IMPLEMENTED; +} + +/** + * Obtain the current state of the interface. + * + * @returns VBox status code. + * + * @param pcszIfName Interface name. + * @param penmState Where to store the retrieved state. + */ +int NetIfGetState(const char *pcszIfName, NETIFSTATUS *penmState) +{ + int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock < 0) + return VERR_OUT_OF_RESOURCES; + struct ifreq Req; + RT_ZERO(Req); + RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pcszIfName); + if (ioctl(sock, SIOCGIFFLAGS, &Req) < 0) + { + Log(("NetIfGetState: ioctl(SIOCGIFFLAGS) -> %d\n", errno)); + *penmState = NETIF_S_UNKNOWN; + } + else + *penmState = (Req.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN; + close(sock); + return VINF_SUCCESS; +} diff --git a/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp b/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp new file mode 100644 index 00000000..29b84033 --- /dev/null +++ b/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp @@ -0,0 +1,1078 @@ +/* $Id: USBProxyBackendUsbIp.cpp $ */ +/** @file + * VirtualBox USB Proxy Backend, USB/IP. + */ + +/* + * Copyright (C) 2015-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_MAIN_USBPROXYBACKEND +#include "USBProxyService.h" +#include "USBGetDevices.h" +#include "LoggingNew.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** The USB/IP default port to connect to. */ +#define USBIP_PORT_DEFAULT 3240 +/** The USB version number used for the protocol. */ +#define USBIP_VERSION UINT16_C(0x0111) +/** Request indicator in the command code. */ +#define USBIP_INDICATOR_REQ RT_BIT(15) + +/** Command/Reply code for OP_REQ/RET_DEVLIST. */ +#define USBIP_REQ_RET_DEVLIST UINT16_C(5) + +/** @todo Duplicate code in USBProxyDevice-usbip.cpp */ +/** + * Exported device entry in the OP_RET_DEVLIST reply. + */ +#pragma pack(1) +typedef struct UsbIpExportedDevice +{ + /** Path of the device, zero terminated string. */ + char szPath[256]; + /** Bus ID of the exported device, zero terminated string. */ + char szBusId[32]; + /** Bus number. */ + uint32_t u32BusNum; + /** Device number. */ + uint32_t u32DevNum; + /** Speed indicator of the device. */ + uint32_t u32Speed; + /** Vendor ID of the device. */ + uint16_t u16VendorId; + /** Product ID of the device. */ + uint16_t u16ProductId; + /** Device release number. */ + uint16_t u16BcdDevice; + /** Device class. */ + uint8_t bDeviceClass; + /** Device Subclass. */ + uint8_t bDeviceSubClass; + /** Device protocol. */ + uint8_t bDeviceProtocol; + /** Configuration value. */ + uint8_t bConfigurationValue; + /** Current configuration value of the device. */ + uint8_t bNumConfigurations; + /** Number of interfaces for the device. */ + uint8_t bNumInterfaces; +} UsbIpExportedDevice; +/** Pointer to a exported device entry. */ +typedef UsbIpExportedDevice *PUsbIpExportedDevice; +#pragma pack() +AssertCompileSize(UsbIpExportedDevice, 312); + +/** + * Interface descriptor entry for an exported device. + */ +#pragma pack(1) +typedef struct UsbIpDeviceInterface +{ + /** Intefrace class. */ + uint8_t bInterfaceClass; + /** Interface sub class. */ + uint8_t bInterfaceSubClass; + /** Interface protocol identifier. */ + uint8_t bInterfaceProtocol; + /** Padding byte for alignment. */ + uint8_t bPadding; +} UsbIpDeviceInterface; +/** Pointer to an interface descriptor entry. */ +typedef UsbIpDeviceInterface *PUsbIpDeviceInterface; +#pragma pack() + +/** + * USB/IP device list request. + */ +#pragma pack(1) +typedef struct UsbIpReqDevList +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t i32Status; +} UsbIpReqDevList; +/** Pointer to a device list request. */ +typedef UsbIpReqDevList *PUsbIpReqDevList; +#pragma pack() + +/** + * USB/IP Import reply. + * + * This is only the header, for successful + * requests the device details are sent to as + * defined in UsbIpExportedDevice. + */ +#pragma pack(1) +typedef struct UsbIpRetDevList +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t i32Status; + /** Number of exported devices. */ + uint32_t u32DevicesExported; +} UsbIpRetDevList; +/** Pointer to a import reply. */ +typedef UsbIpRetDevList *PUsbIpRetDevList; +#pragma pack() + +/** Pollset id of the socket. */ +#define USBIP_POLL_ID_SOCKET 0 +/** Pollset id of the pipe. */ +#define USBIP_POLL_ID_PIPE 1 + +/** @name USB/IP error codes. + * @{ */ +/** Success indicator. */ +#define USBIP_STATUS_SUCCESS INT32_C(0) +/** @} */ + +/** @name USB/IP device speeds. + * @{ */ +/** Unknown speed. */ +#define USBIP_SPEED_UNKNOWN 0 +/** Low (1.0) speed. */ +#define USBIP_SPEED_LOW 1 +/** Full (1.1) speed. */ +#define USBIP_SPEED_FULL 2 +/** High (2.0) speed. */ +#define USBIP_SPEED_HIGH 3 +/** Variable (CWUSB) speed. */ +#define USBIP_SPEED_WIRELESS 4 +/** Super (3.0) speed. */ +#define USBIP_SPEED_SUPER 5 +/** @} */ + +/** + * Private USB/IP proxy backend data. + */ +struct USBProxyBackendUsbIp::Data +{ + Data() + : hSocket(NIL_RTSOCKET), + hWakeupPipeR(NIL_RTPIPE), + hWakeupPipeW(NIL_RTPIPE), + hPollSet(NIL_RTPOLLSET), + uPort(USBIP_PORT_DEFAULT), + pszHost(NULL), + hMtxDevices(NIL_RTSEMFASTMUTEX), + cUsbDevicesCur(0), + pUsbDevicesCur(NULL), + enmRecvState(kUsbIpRecvState_Invalid), + cbResidualRecv(0), + pbRecvBuf(NULL), + cDevicesLeft(0), + pHead(NULL), + ppNext(&pHead) + { } + + /** Socket handle to the server. */ + RTSOCKET hSocket; + /** Pipe used to interrupt wait(), the read end. */ + RTPIPE hWakeupPipeR; + /** Pipe used to interrupt wait(), the write end. */ + RTPIPE hWakeupPipeW; + /** Pollset for the socket and wakeup pipe. */ + RTPOLLSET hPollSet; + /** Port of the USB/IP host to connect to. */ + uint32_t uPort; + /** USB/IP host address. */ + char *pszHost; + /** Mutex protecting the device list against concurrent access. */ + RTSEMFASTMUTEX hMtxDevices; + /** Number of devices in the list. */ + uint32_t cUsbDevicesCur; + /** The current list of devices to compare with. */ + PUSBDEVICE pUsbDevicesCur; + /** Current receive state. */ + USBIPRECVSTATE enmRecvState; + /** Scratch space for holding the data until it was completely received. + * Which one to access is based on the current receive state. */ + union + { + UsbIpRetDevList RetDevList; + UsbIpExportedDevice ExportedDevice; + UsbIpDeviceInterface DeviceInterface; + /** Byte view. */ + uint8_t abRecv[1]; + } Scratch; + /** Residual number of bytes to receive before we can work with the data. */ + size_t cbResidualRecv; + /** Current pointer into the scratch buffer. */ + uint8_t *pbRecvBuf; + /** Number of devices left to receive for the current request. */ + uint32_t cDevicesLeft; + /** Number of interfaces to skip during receive. */ + uint32_t cInterfacesLeft; + /** The current head pointer for the new device list. */ + PUSBDEVICE pHead; + /** The next pointer to add a device to. */ + PUSBDEVICE *ppNext; + /** Current amount of devices in the list. */ + uint32_t cDevicesCur; + /** Timestamp of the last time we successfully connected. */ + uint64_t tsConnectSuccessLast; +}; + +/** + * Convert the given exported device structure from host to network byte order. + * + * @param pDevice The device structure to convert. + */ +DECLINLINE(void) usbProxyBackendUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice) +{ + pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum); + pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum); + pDevice->u32Speed = RT_N2H_U32(pDevice->u32Speed); + pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId); + pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId); + pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice); +} + +/** + * Initialize data members. + */ +USBProxyBackendUsbIp::USBProxyBackendUsbIp() + : USBProxyBackend() +{ +} + +USBProxyBackendUsbIp::~USBProxyBackendUsbIp() +{ + +} + +/** + * Initializes the object (called right after construction). + * + * @returns S_OK on success and non-fatal failures, some COM error otherwise. + */ +int USBProxyBackendUsbIp::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId, + const com::Utf8Str &strAddress, bool fLoadingSettings) +{ + int vrc = VINF_SUCCESS; + + USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings); + + unconst(m_strBackend) = Utf8Str("USBIP"); + + m = new Data; + + m->tsConnectSuccessLast = 0; + + /* Split address into hostname and port. */ + RTCList lstAddress = strAddress.split(":"); + if (lstAddress.size() < 1) + return VERR_INVALID_PARAMETER; + m->pszHost = RTStrDup(lstAddress[0].c_str()); + if (!m->pszHost) + return VERR_NO_STR_MEMORY; + if (lstAddress.size() == 2) + { + m->uPort = lstAddress[1].toUInt32(); + if (!m->uPort) + return VERR_INVALID_PARAMETER; + } + + /* Setup wakeup pipe and poll set first. */ + vrc = RTSemFastMutexCreate(&m->hMtxDevices); + if (RT_SUCCESS(vrc)) + { + vrc = RTPipeCreate(&m->hWakeupPipeR, &m->hWakeupPipeW, 0); + if (RT_SUCCESS(vrc)) + { + vrc = RTPollSetCreate(&m->hPollSet); + if (RT_SUCCESS(vrc)) + { + vrc = RTPollSetAddPipe(m->hPollSet, m->hWakeupPipeR, RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE); + if (RT_SUCCESS(vrc)) + { + /* + * Connect to the USB/IP host. Be more graceful to connection errors + * if we are instantiated while the settings are loaded to let + * VBoxSVC start. + * + * The worker thread keeps trying to connect every few seconds until + * either the USB source is removed by the user or the USB server is + * reachable. + */ + vrc = reconnect(); + if (RT_SUCCESS(vrc) || fLoadingSettings) + vrc = start(); /* Start service thread. */ + } + + if (RT_FAILURE(vrc)) + { + RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_PIPE); + int vrc2 = RTPollSetDestroy(m->hPollSet); + AssertRC(vrc2); + m->hPollSet = NIL_RTPOLLSET; + } + } + + if (RT_FAILURE(vrc)) + { + int vrc2 = RTPipeClose(m->hWakeupPipeR); + AssertRC(vrc2); + vrc2 = RTPipeClose(m->hWakeupPipeW); + AssertRC(vrc2); + m->hWakeupPipeR = m->hWakeupPipeW = NIL_RTPIPE; + } + } + if (RT_FAILURE(vrc)) + { + RTSemFastMutexDestroy(m->hMtxDevices); + m->hMtxDevices = NIL_RTSEMFASTMUTEX; + } + } + + return vrc; +} + +/** + * Stop all service threads and free the device chain. + */ +void USBProxyBackendUsbIp::uninit() +{ + LogFlowThisFunc(("\n")); + + /* + * Stop the service. + */ + if (isActive()) + stop(); + + /* + * Free resources. + */ + if (m->hPollSet != NIL_RTPOLLSET) + { + disconnect(); + + int vrc = RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_PIPE); + AssertRC(vrc); + vrc = RTPollSetDestroy(m->hPollSet); + AssertRC(vrc); + vrc = RTPipeClose(m->hWakeupPipeR); + AssertRC(vrc); + vrc = RTPipeClose(m->hWakeupPipeW); + AssertRC(vrc); + + m->hPollSet = NIL_RTPOLLSET; + m->hWakeupPipeR = NIL_RTPIPE; + m->hWakeupPipeW = NIL_RTPIPE; + } + + if (m->pszHost) + RTStrFree(m->pszHost); + if (m->hMtxDevices != NIL_RTSEMFASTMUTEX) + { + RTSemFastMutexDestroy(m->hMtxDevices); + m->hMtxDevices = NIL_RTSEMFASTMUTEX; + } + + delete m; + USBProxyBackend::uninit(); +} + + +int USBProxyBackendUsbIp::captureDevice(HostUSBDevice *aDevice) +{ + AssertReturn(aDevice, VERR_GENERAL_FAILURE); + AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE); + + AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str())); + + /* + * We don't need to do anything when the device is held... fake it. + */ + Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing); + devLock.release(); + + return VINF_SUCCESS; +} + + +int USBProxyBackendUsbIp::releaseDevice(HostUSBDevice *aDevice) +{ + AssertReturn(aDevice, VERR_GENERAL_FAILURE); + AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE); + + AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str())); + + /* + * We're not really holding it atm., just fake it. + */ + Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost); + devLock.release(); + + return VINF_SUCCESS; +} + + +bool USBProxyBackendUsbIp::isFakeUpdateRequired() +{ + return true; +} + + +int USBProxyBackendUsbIp::wait(RTMSINTERVAL aMillies) +{ + int vrc = VINF_SUCCESS; + bool fDeviceListChangedOrWokenUp = false; + + /* Don't start any possibly lengthy operation if we are supposed to return immediately again. */ + if (!aMillies) + return VINF_SUCCESS; + + /* Try to reconnect once when we enter if we lost the connection earlier. */ + if (m->hSocket == NIL_RTSOCKET) + reconnect(); + + /* Query a new device list upon entering. */ + if ( m->hSocket != NIL_RTSOCKET + && m->enmRecvState == kUsbIpRecvState_None) + { + vrc = startListExportedDevicesReq(); + if (RT_FAILURE(vrc)) + disconnect(); + } + + /* + * Because the USB/IP protocol doesn't specify a way to get notified about + * new or removed exported devices we have to poll the host periodically for + * a new device list and compare it with the previous one notifying the proxy + * service about changes. + */ + while ( !fDeviceListChangedOrWokenUp + && (aMillies == RT_INDEFINITE_WAIT || aMillies > 0) + && RT_SUCCESS(vrc)) + { + RTMSINTERVAL msWait = aMillies; + uint64_t msPollStart = RTTimeMilliTS(); + uint32_t uIdReady = 0; + uint32_t fEventsRecv = 0; + + /* Limit the waiting time to 3sec so we can either reconnect or get a new device list. */ + if (m->hSocket == NIL_RTSOCKET || m->enmRecvState == kUsbIpRecvState_None) + msWait = RT_MIN(3000, aMillies); + + vrc = RTPoll(m->hPollSet, msWait, &fEventsRecv, &uIdReady); + if (RT_SUCCESS(vrc)) + { + if (uIdReady == USBIP_POLL_ID_PIPE) + { + /* Drain the wakeup pipe. */ + char bRead = 0; + size_t cbRead = 0; + + vrc = RTPipeRead(m->hWakeupPipeR, &bRead, 1, &cbRead); + Assert(RT_SUCCESS(vrc) && cbRead == 1); + fDeviceListChangedOrWokenUp = true; + } + else if (uIdReady == USBIP_POLL_ID_SOCKET) + { + if (fEventsRecv & RTPOLL_EVT_READ) + vrc = receiveData(); + if ( RT_SUCCESS(vrc) + && (fEventsRecv & RTPOLL_EVT_ERROR)) + vrc = VERR_NET_SHUTDOWN; + + /* + * If we are in the none state again we received the previous request + * and have a new device list to compare the old against. + */ + if (m->enmRecvState == kUsbIpRecvState_None) + { + if (hasDevListChanged(m->pHead)) + fDeviceListChangedOrWokenUp = true; + + /* Update to the new list in any case now that we have it anyway. */ + RTSemFastMutexRequest(m->hMtxDevices); + freeDeviceList(m->pUsbDevicesCur); + m->cUsbDevicesCur = m->cDevicesCur; + m->pUsbDevicesCur = m->pHead; + RTSemFastMutexRelease(m->hMtxDevices); + + m->pHead = NULL; + resetRecvState(); + } + + /* Current USB/IP server closes the connection after each request, don't abort but try again. */ + if (vrc == VERR_NET_SHUTDOWN || vrc == VERR_BROKEN_PIPE || vrc == VERR_NET_CONNECTION_RESET_BY_PEER) + { + Log(("USB/IP: Lost connection to host \"%s\", trying to reconnect...\n", m->pszHost)); + disconnect(); + vrc = VINF_SUCCESS; + } + } + else + { + AssertMsgFailed(("Invalid poll ID returned\n")); + vrc = VERR_INVALID_STATE; + } + aMillies -= (RTMSINTERVAL)(RTTimeMilliTS() - msPollStart); + } + else if (vrc == VERR_TIMEOUT) + { + aMillies -= msWait; + if (aMillies) + { + /* Try to reconnect and start a new request if we lost the connection before. */ + if (m->hSocket == NIL_RTSOCKET) + { + vrc = reconnect(); + if (RT_SUCCESS(vrc)) + vrc = startListExportedDevicesReq(); + else if ( vrc == VERR_NET_SHUTDOWN + || vrc == VERR_BROKEN_PIPE + || vrc == VERR_NET_CONNECTION_RESET_BY_PEER + || vrc == VERR_NET_CONNECTION_REFUSED) + { + if (hasDevListChanged(m->pHead)) + fDeviceListChangedOrWokenUp = true; + vrc = VINF_SUCCESS; + } + } + } + } + } + + LogFlowFunc(("return vrc=%Rrc\n", vrc)); + return vrc; +} + + +int USBProxyBackendUsbIp::interruptWait(void) +{ + AssertReturn(!isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = RTPipeWriteBlocking(m->hWakeupPipeW, "", 1, NULL); + if (RT_SUCCESS(vrc)) + RTPipeFlush(m->hWakeupPipeW); + LogFlowFunc(("returning %Rrc\n", vrc)); + return vrc; +} + + +PUSBDEVICE USBProxyBackendUsbIp::getDevices(void) +{ + PUSBDEVICE pFirst = NULL; + PUSBDEVICE *ppNext = &pFirst; + + LogFlowThisFunc(("\n")); + + /* Create a deep copy of the device list. */ + RTSemFastMutexRequest(m->hMtxDevices); + PUSBDEVICE pCur = m->pUsbDevicesCur; + while (pCur) + { + PUSBDEVICE pNew = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE)); + if (pNew) + { + pNew->pszManufacturer = RTStrDup(pCur->pszManufacturer); + pNew->pszProduct = RTStrDup(pCur->pszProduct); + if (pCur->pszSerialNumber) + pNew->pszSerialNumber = RTStrDup(pCur->pszSerialNumber); + pNew->pszBackend = RTStrDup(pCur->pszBackend); + pNew->pszAddress = RTStrDup(pCur->pszAddress); + + pNew->idVendor = pCur->idVendor; + pNew->idProduct = pCur->idProduct; + pNew->bcdDevice = pCur->bcdDevice; + pNew->bcdUSB = pCur->bcdUSB; + pNew->bDeviceClass = pCur->bDeviceClass; + pNew->bDeviceSubClass = pCur->bDeviceSubClass; + pNew->bDeviceProtocol = pCur->bDeviceProtocol; + pNew->bNumConfigurations = pCur->bNumConfigurations; + pNew->enmState = pCur->enmState; + pNew->u64SerialHash = pCur->u64SerialHash; + pNew->bBus = pCur->bBus; + pNew->bPort = pCur->bPort; + pNew->enmSpeed = pCur->enmSpeed; + + /* link it */ + pNew->pNext = NULL; + pNew->pPrev = *ppNext; + *ppNext = pNew; + ppNext = &pNew->pNext; + } + + pCur = pCur->pNext; + } + RTSemFastMutexRelease(m->hMtxDevices); + + LogFlowThisFunc(("returning %#p\n", pFirst)); + return pFirst; +} + +/** + * Frees a given device list. + * + * @param pHead The head of the device list to free. + */ +void USBProxyBackendUsbIp::freeDeviceList(PUSBDEVICE pHead) +{ + PUSBDEVICE pNext = pHead; + while (pNext) + { + PUSBDEVICE pFree = pNext; + pNext = pNext->pNext; + freeDevice(pFree); + } +} + +/** + * Resets the receive state to the idle state. + */ +void USBProxyBackendUsbIp::resetRecvState() +{ + LogFlowFunc(("\n")); + freeDeviceList(m->pHead); + m->pHead = NULL; + m->ppNext = &m->pHead; + m->cDevicesCur = 0; + m->enmRecvState = kUsbIpRecvState_None; + m->cbResidualRecv = 0; + m->pbRecvBuf = &m->Scratch.abRecv[0]; + m->cDevicesLeft = 0; + LogFlowFunc(("\n")); +} + +/** + * Disconnects from the host and resets the receive state. + */ +void USBProxyBackendUsbIp::disconnect() +{ + LogFlowFunc(("\n")); + + if (m->hSocket != NIL_RTSOCKET) + { + int vrc = RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_SOCKET); + NOREF(vrc); + Assert(RT_SUCCESS(vrc) || vrc == VERR_POLL_HANDLE_ID_NOT_FOUND); + + RTTcpClientCloseEx(m->hSocket, false /*fGracefulShutdown*/); + m->hSocket = NIL_RTSOCKET; + } + + resetRecvState(); + LogFlowFunc(("returns\n")); +} + +/** + * Tries to reconnect to the USB/IP host. + * + * @returns VBox status code. + */ +int USBProxyBackendUsbIp::reconnect() +{ + LogFlowFunc(("\n")); + + /* Make sure we are disconnected. */ + disconnect(); + + /* Connect to the USB/IP host. */ + int vrc = RTTcpClientConnect(m->pszHost, m->uPort, &m->hSocket); + if (RT_SUCCESS(vrc)) + { + vrc = RTTcpSetSendCoalescing(m->hSocket, false); + if (RT_FAILURE(vrc)) + LogRelMax(5, ("USB/IP: Disabling send coalescing failed (vrc=%Rrc), continuing nevertheless but expect increased latency\n", vrc)); + + vrc = RTPollSetAddSocket(m->hPollSet, m->hSocket, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, USBIP_POLL_ID_SOCKET); + if (RT_FAILURE(vrc)) + { + RTTcpClientCloseEx(m->hSocket, false /*fGracefulShutdown*/); + m->hSocket = NIL_RTSOCKET; + } + else + { + LogFlowFunc(("Connected to host \"%s\"\n", m->pszHost)); + m->tsConnectSuccessLast = RTTimeMilliTS(); + } + } + else if (m->tsConnectSuccessLast + 10 * RT_MS_1SEC < RTTimeMilliTS()) + { + /* Make sure the device list is clear if we failed to reconnect for some time. */ + RTSemFastMutexRequest(m->hMtxDevices); + if (m->pUsbDevicesCur) + { + freeDeviceList(m->pUsbDevicesCur); + m->cUsbDevicesCur = 0; + m->pUsbDevicesCur = NULL; + } + RTSemFastMutexRelease(m->hMtxDevices); + } + + LogFlowFunc(("returns vrc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Initiates a new List Exported Devices request. + * + * @returns VBox status code. + */ +int USBProxyBackendUsbIp::startListExportedDevicesReq() +{ + int vrc = VINF_SUCCESS; + + LogFlowFunc(("\n")); + + /* + * Reset the current state and reconnect in case we were called in the middle + * of another transfer (which should not happen). + */ + Assert(m->enmRecvState == kUsbIpRecvState_None); + if (m->enmRecvState != kUsbIpRecvState_None) + vrc = reconnect(); + + if (RT_SUCCESS(vrc)) + { + /* Send of the request. */ + UsbIpReqDevList ReqDevList; + ReqDevList.u16Version = RT_H2N_U16(USBIP_VERSION); + ReqDevList.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_DEVLIST); + ReqDevList.i32Status = RT_H2N_S32(0); + + vrc = RTTcpWrite(m->hSocket, &ReqDevList, sizeof(ReqDevList)); + if (RT_SUCCESS(vrc)) + advanceState(kUsbIpRecvState_Hdr); + } + + LogFlowFunc(("returns vrc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Advances the state machine to the given state. + * + * @param enmRecvState The new receive state. + */ +void USBProxyBackendUsbIp::advanceState(USBIPRECVSTATE enmRecvState) +{ + LogFlowFunc(("enmRecvState=%u\n", enmRecvState)); + + switch (enmRecvState) + { + case kUsbIpRecvState_None: + break; + case kUsbIpRecvState_Hdr: + { + m->cbResidualRecv = sizeof(UsbIpRetDevList); + m->pbRecvBuf = (uint8_t *)&m->Scratch.RetDevList; + break; + } + case kUsbIpRecvState_ExportedDevice: + { + m->cbResidualRecv = sizeof(UsbIpExportedDevice); + m->pbRecvBuf = (uint8_t *)&m->Scratch.ExportedDevice; + break; + } + case kUsbIpRecvState_DeviceInterface: + { + m->cbResidualRecv = sizeof(UsbIpDeviceInterface); + m->pbRecvBuf = (uint8_t *)&m->Scratch.DeviceInterface; + break; + } + default: + AssertMsgFailed(("Invalid USB/IP receive state %d\n", enmRecvState)); + return; + } + + m->enmRecvState = enmRecvState; + LogFlowFunc(("returns\n")); +} + +/** + * Receives data from the USB/IP host and processes it when everything for the current + * state was received. + * + * @returns VBox status code. + */ +int USBProxyBackendUsbIp::receiveData() +{ + int vrc = VINF_SUCCESS; + size_t cbRecvd = 0; + + LogFlowFunc(("\n")); + + do + { + vrc = RTTcpReadNB(m->hSocket, m->pbRecvBuf, m->cbResidualRecv, &cbRecvd); + + LogFlowFunc(("RTTcpReadNB(%#p, %#p, %zu, %zu) -> %Rrc\n", m->hSocket, m->pbRecvBuf, m->cbResidualRecv, cbRecvd, vrc)); + + if ( vrc == VINF_SUCCESS + && cbRecvd > 0) + { + m->cbResidualRecv -= cbRecvd; + m->pbRecvBuf += cbRecvd; + /* In case we received everything for the current state process the data. */ + if (!m->cbResidualRecv) + { + vrc = processData(); + if ( RT_SUCCESS(vrc) + && m->enmRecvState == kUsbIpRecvState_None) + break; + } + } + else if (vrc == VINF_TRY_AGAIN) + Assert(!cbRecvd); + + } while (vrc == VINF_SUCCESS && cbRecvd > 0); + + if (vrc == VINF_TRY_AGAIN) + vrc = VINF_SUCCESS; + + LogFlowFunc(("returns vrc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Processes the data in the scratch buffer based on the current state. + * + * @returns VBox status code. + */ +int USBProxyBackendUsbIp::processData() +{ + int vrc = VINF_SUCCESS; + + switch (m->enmRecvState) + { + case kUsbIpRecvState_Hdr: + { + /* Check that the reply matches our expectations. */ + if ( RT_N2H_U16(m->Scratch.RetDevList.u16Version) == USBIP_VERSION + && RT_N2H_U16(m->Scratch.RetDevList.u16Cmd) == USBIP_REQ_RET_DEVLIST + && RT_N2H_S32(m->Scratch.RetDevList.i32Status) == USBIP_STATUS_SUCCESS) + + { + /* Populate the number of exported devices in the list and go to the next state. */ + m->cDevicesLeft = RT_N2H_U32(m->Scratch.RetDevList.u32DevicesExported); + if (m->cDevicesLeft) + advanceState(kUsbIpRecvState_ExportedDevice); + else + advanceState(kUsbIpRecvState_None); + } + else + { + LogRelMax(10, ("USB/IP: Host sent an invalid reply to the list exported device request (Version: %#x Cmd: %#x Status: %#x)\n", + RT_N2H_U16(m->Scratch.RetDevList.u16Version), RT_N2H_U16(m->Scratch.RetDevList.u16Cmd), + RT_N2H_S32(m->Scratch.RetDevList.i32Status))); + /* Disconnect and start over. */ + advanceState(kUsbIpRecvState_None); + disconnect(); + vrc = VERR_NET_SHUTDOWN; + } + break; + } + case kUsbIpRecvState_ExportedDevice: + { + /* Create a new device and add it to the list. */ + usbProxyBackendUsbIpExportedDeviceN2H(&m->Scratch.ExportedDevice); + vrc = addDeviceToList(&m->Scratch.ExportedDevice); + if (RT_SUCCESS(vrc)) + { + m->cInterfacesLeft = m->Scratch.ExportedDevice.bNumInterfaces; + if (m->cInterfacesLeft) + advanceState(kUsbIpRecvState_DeviceInterface); + else + { + m->cDevicesLeft--; + if (m->cDevicesLeft) + advanceState(kUsbIpRecvState_ExportedDevice); + else + advanceState(kUsbIpRecvState_None); + } + } + break; + } + case kUsbIpRecvState_DeviceInterface: + { + /* + * If all interfaces for the current device were received receive the next device + * if there is another one left, if not we are done with the current request. + */ + m->cInterfacesLeft--; + if (m->cInterfacesLeft) + advanceState(kUsbIpRecvState_DeviceInterface); + else + { + m->cDevicesLeft--; + if (m->cDevicesLeft) + advanceState(kUsbIpRecvState_ExportedDevice); + else + advanceState(kUsbIpRecvState_None); + } + break; + } + case kUsbIpRecvState_None: + default: + AssertMsgFailed(("Invalid USB/IP receive state %d\n", m->enmRecvState)); + return VERR_INVALID_STATE; + } + + return vrc; +} + +/** + * Creates a new USB device and adds it to the list. + * + * @returns VBox status code. + * @param pDev Pointer to the USB/IP exported device structure to take + * the information for the new device from. + */ +int USBProxyBackendUsbIp::addDeviceToList(PUsbIpExportedDevice pDev) +{ + int vrc = VINF_SUCCESS; + PUSBDEVICE pNew = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE)); + if (!pNew) + return VERR_NO_MEMORY; + + pNew->pszManufacturer = RTStrDup(""); + pNew->pszProduct = RTStrDup(""); + pNew->pszSerialNumber = NULL; + pNew->pszBackend = RTStrDup("usbip"); + + /* Make sure the Bus id is 0 terminated. */ + pDev->szBusId[31] = '\0'; + pNew->pszAddress = RTStrAPrintf2("usbip://%s:%u:%s", m->pszHost, m->uPort, &pDev->szBusId[0]); + if (RT_LIKELY(pNew->pszAddress)) + { + pNew->idVendor = pDev->u16VendorId; + pNew->idProduct = pDev->u16ProductId; + pNew->bcdDevice = pDev->u16BcdDevice; + pNew->bDeviceClass = pDev->bDeviceClass; + pNew->bDeviceSubClass = pDev->bDeviceSubClass; + pNew->bDeviceProtocol = pDev->bDeviceProtocol; + pNew->bNumConfigurations = pDev->bNumConfigurations; + pNew->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + pNew->u64SerialHash = 0; + /** @todo The following is not correct but is required to to get USB testing working + * because only the port can be part of a filter (adding the required attributes for the bus + * breaks API and ABI compatibility). + * Filtering by port number is required for USB testing to connect to the correct device + * in case there are multiple ones. + */ + pNew->bBus = (uint8_t)pDev->u32DevNum; + pNew->bPort = (uint8_t)pDev->u32BusNum; + + switch (pDev->u32Speed) + { + case USBIP_SPEED_LOW: + pNew->enmSpeed = USBDEVICESPEED_LOW; + pNew->bcdUSB = 1 << 8; + break; + case USBIP_SPEED_FULL: + pNew->enmSpeed = USBDEVICESPEED_FULL; + pNew->bcdUSB = 1 << 8; + break; + case USBIP_SPEED_HIGH: + pNew->enmSpeed = USBDEVICESPEED_HIGH; + pNew->bcdUSB = 2 << 8; + break; + case USBIP_SPEED_WIRELESS: + pNew->enmSpeed = USBDEVICESPEED_VARIABLE; + pNew->bcdUSB = 1 << 8; + break; + case USBIP_SPEED_SUPER: + pNew->enmSpeed = USBDEVICESPEED_SUPER; + pNew->bcdUSB = 3 << 8; + break; + case USBIP_SPEED_UNKNOWN: + default: + pNew->bcdUSB = 1 << 8; + pNew->enmSpeed = USBDEVICESPEED_UNKNOWN; + } + + /* link it */ + pNew->pNext = NULL; + pNew->pPrev = *m->ppNext; + *m->ppNext = pNew; + m->ppNext = &pNew->pNext; + m->cDevicesCur++; + } + else + vrc = VERR_NO_STR_MEMORY; + + if (RT_FAILURE(vrc)) + { + if (pNew->pszManufacturer) + RTStrFree((char *)pNew->pszManufacturer); + if (pNew->pszProduct) + RTStrFree((char *)pNew->pszProduct); + if (pNew->pszBackend) + RTStrFree((char *)pNew->pszBackend); + if (pNew->pszAddress) + RTStrFree((char *)pNew->pszAddress); + RTMemFree(pNew); + } + + return vrc; +} + +/** + * Compares the given device list with the current one and returns whether it has + * changed. + * + * @returns flag whether the device list has changed compared to the current one. + * @param pDevices The device list to compare the current one against. + */ +bool USBProxyBackendUsbIp::hasDevListChanged(PUSBDEVICE pDevices) +{ + /** @todo */ + NOREF(pDevices); + return true; +} + -- cgit v1.2.3