summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/generic
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-server/generic
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-server/generic')
-rw-r--r--src/VBox/Main/src-server/generic/AutostartDb-generic.cpp271
-rw-r--r--src/VBox/Main/src-server/generic/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/generic/NetIf-generic.cpp432
-rw-r--r--src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp1078
4 files changed, 1781 insertions, 0 deletions
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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/process.h>
+#include <iprt/path.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#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
--- /dev/null
+++ b/src/VBox/Main/src-server/generic/Makefile.kup
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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <errno.h>
+#include <unistd.h>
+
+#if defined(RT_OS_SOLARIS)
+# include <sys/sockio.h>
+#endif
+
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+# include <cstdio>
+#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> 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<IHost> 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<HostNetworkInterface> 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> progress;
+ HRESULT hrc = progress.createObject();
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ ComPtr<IHost> host;
+ int vrc = VINF_SUCCESS;
+ hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IHostNetworkInterface> 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <VBox/err.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/tcp.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/pipe.h>
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+#include <iprt/time.h>
+
+/** 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<RTCString> 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;
+}
+