diff options
Diffstat (limited to 'src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c')
-rw-r--r-- | src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c | 1591 |
1 files changed, 1591 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c new file mode 100644 index 00000000..720fe0b8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c @@ -0,0 +1,1591 @@ +/* $Id: VBoxNetFlt.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Common Code. + */ + +/* + * Copyright (C) 2008-2022 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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_netflt VBoxNetFlt - Network Interface Filter + * + * This is a kernel module that attaches to a real interface on the host and + * filters and injects packets. + * + * In the big picture we're one of the three trunk interface on the internal + * network, the one named "NIC Filter Driver": @image html Networking_Overview.gif + * + * + * @section sec_netflt_locking Locking and Potential Races + * + * The main challenge here is to make sure the netfilter and internal network + * instances won't be destroyed while someone is calling into them. + * + * The main calls into or out of of the filter driver are: + * - Send. + * - Async send completion (not implemented yet) + * - Release by the internal network. + * - Receive. + * - Disappearance of the host networking interface. + * - Reappearance of the host networking interface. + * + * The latter two calls are can be caused by driver unloading/loading or the + * device being physical unplugged (e.g. a USB network device). Actually, the + * unload scenario must fervently be prevent as it will cause panics because the + * internal network will assume the trunk is around until it releases it. + * @todo Need to figure which host allow unloading and block/fix it. + * + * Currently the netfilter instance lives until the internal network releases + * it. So, it is the internal networks responsibility to make sure there are no + * active calls when it releases the trunk and destroys the network. The + * netfilter assists in this by providing INTNETTRUNKIFPORT::pfnSetState and + * INTNETTRUNKIFPORT::pfnWaitForIdle. The trunk state is used to enable/disable + * promiscuous mode on the hardware NIC (or similar activation) as well + * indicating that disconnect is imminent and no further calls shall be made + * into the internal network. After changing the state to disconnecting and + * prior to invoking INTNETTRUNKIFPORT::pfnDisconnectAndRelease, the internal + * network will use INTNETTRUNKIFPORT::pfnWaitForIdle to wait for any still + * active calls to complete. + * + * The netfilter employs a busy counter and an internal state in addition to the + * public trunk state. All these variables are protected using a spinlock. + * + * + * @section sec_netflt_msc Locking / Sequence Diagrams - OBSOLETE + * + * !OBSOLETE! - THIS WAS THE OLD APPROACH! + * + * This secion contains a few sequence diagrams describing the problematic + * transitions of a host interface filter instance. + * + * The thing that makes it all a bit problematic is that multiple events may + * happen at the same time, and that we have to be very careful to avoid + * deadlocks caused by mixing our locks with the ones in the host kernel. The + * main events are receive, send, async send completion, disappearance of the + * host networking interface and its reappearance. The latter two events are + * can be caused by driver unloading/loading or the device being physical + * unplugged (e.g. a USB network device). + * + * The strategy for dealing with these issues are: + * - Use a simple state machine. + * - Require the user (IntNet) to serialize all its calls to us, + * while at the same time not owning any lock used by any of the + * the callbacks we might call on receive and async send completion. + * - Make sure we're 100% idle before disconnecting, and have a + * disconnected status on both sides to fend off async calls. + * - Protect the host specific interface handle and the state variables + * using a spinlock. + * + * + * @subsection subsec_netflt_msc_dis_rel Disconnect from the network and release - OBSOLETE + * + * @msc + * VM, IntNet, NetFlt, Kernel, Wire; + * + * VM->IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * Kernel->Wire [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * + * --- [label="Suspending the trunk interface"]; + * IntNet=>IntNet [label="Lock Network"]; + * + * Wire->Kernel [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * NetFlt=>>IntNet [label="pkt1 recv - blocks", linecolor="red", textcolor="red"]; + * + * IntNet=>IntNet [label="Mark Trunk Suspended"]; + * IntNet=>IntNet [label="Unlock Network"]; + * + * IntNet=>NetFlt [label="pfnSetActive(false)"]; + * NetFlt=>NetFlt [label="Mark inactive (atomic)"]; + * IntNet<<NetFlt; + * IntNet=>NetFlt [label="pfnWaitForIdle(forever)"]; + * + * IntNet=>>NetFlt [label="pkt1 to host", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt1 to host", linecolor="red", textcolor="red"]; + * + * Kernel<-Wire [label="pkt0 on wire", linecolor="green", textcolor="green"]; + * NetFlt<<Kernel [label="pkt0 on wire", linecolor="green", textcolor="green"]; + * IntNet<<=NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet<<=IntNet [label="Lock Net, free SG, Unlock Net", linecolor="green", textcolor="green"]; + * IntNet>>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * NetFlt<-NetFlt [label="idle", linecolor="green", textcolor="green"]; + * + * IntNet<<NetFlt [label="idle (pfnWaitForIdle)"]; + * + * Wire->Kernel [label="pkt2", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt2", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt2 to host", linecolor="red", textcolor="red"]; + * + * VM->IntNet [label="pkt3", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> drop", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * + * --- [label="The trunk interface is idle now, disconnect it"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Unlink Trunk"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pfnDisconnectAndRelease"]; + * NetFlt=>Kernel [label="iflt_detach"]; + * NetFlt<<=Kernel [label="iff_detached"]; + * NetFlt>>Kernel [label="iff_detached"]; + * NetFlt<<Kernel [label="iflt_detach"]; + * NetFlt=>NetFlt [label="Release"]; + * IntNet<<NetFlt [label="pfnDisconnectAndRelease"]; + * + * @endmsc + * + * + * + * @subsection subsec_netflt_msc_hif_rm Host Interface Removal - OBSOLETE + * + * The ifnet_t (pIf) is a tricky customer as any reference to it can potentially + * race the filter detaching. The simple way of solving it on Darwin is to guard + * all access to the pIf member with a spinlock. The other host systems will + * probably have similar race conditions, so the spinlock is a generic thing. + * + * @msc + * VM, IntNet, NetFlt, Kernel; + * + * VM->IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="ifnet_reference w/ spinlock", linecolor="green", textcolor="green" ]; + * NetFlt<<Kernel [label="ifnet_reference", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="pkt0 to wire (blocks)", linecolor="green", textcolor="green" ]; + * + * --- [label="The host interface is being disconnected"]; + * Kernel->NetFlt [label="iff_detached"]; + * NetFlt=>Kernel [label="ifnet_release w/ spinlock"]; + * NetFlt<<Kernel [label="ifnet_release"]; + * NetFlt=>NetFlt [label="fDisconnectedFromHost=true"]; + * NetFlt>>Kernel [label="iff_detached"]; + * + * NetFlt<<Kernel [label="dropped", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="Acquire spinlock", linecolor="green", textcolor="green"]; + * NetFlt=>Kernel [label="ifnet_release", linecolor="green", textcolor="green"]; + * NetFlt<<Kernel [label="ifnet_release", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="pIf=NULL", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="Release spinlock", linecolor="green", textcolor="green"]; + * IntNet<=NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet>>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet<<NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * + * @endmsc + * + * + * + * @subsection subsec_netflt_msc_hif_rd Host Interface Rediscovery - OBSOLETE + * + * The rediscovery is performed when we receive a send request and a certain + * period have elapsed since the last attempt, i.e. we're polling it. We + * synchronize the rediscovery with disconnection from the internal network + * by means of the pfnWaitForIdle call, so no special handling is required. + * + * @msc + * VM2, VM1, IntNet, NetFlt, Kernel, Wire; + * + * --- [label="Rediscovery conditions are not met"]; + * VM1->IntNet [label="pkt0"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt0 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * IntNet<<NetFlt [label="pkt0 to wire (dropped)"]; + * + * --- [label="Rediscovery conditions"]; + * VM1->IntNet [label="pkt1"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt1 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * NetFlt=>NetFlt [label="fRediscoveryPending=true w/ spinlock"]; + * NetFlt=>Kernel [label="ifnet_find_by_name"]; + * NetFlt<<Kernel [label="ifnet_find_by_name (success)"]; + * + * VM2->IntNet [label="pkt2", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Lock Network", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Unlock Network", linecolor="red", textcolor="red"]; + * IntNet=>NetFlt [label="pkt2 to wire", linecolor="red", textcolor="red"]; + * NetFlt=>NetFlt [label="!pIf || fRediscoveryPending (w/ spinlock)", linecolor="red", textcolor="red"]; + * IntNet<<NetFlt [label="pkt2 to wire (dropped)", linecolor="red", textcolor="red"]; + + * NetFlt=>Kernel [label="iflt_attach"]; + * NetFlt<<Kernel [label="iflt_attach (success)"]; + * NetFlt=>NetFlt [label="Acquire spinlock"]; + * NetFlt=>NetFlt [label="Set pIf and update flags"]; + * NetFlt=>NetFlt [label="Release spinlock"]; + * + * NetFlt=>Kernel [label="pkt1 to wire"]; + * Kernel->Wire [label="pkt1 to wire"]; + * NetFlt<<Kernel [label="pkt1 to wire"]; + * IntNet<<NetFlt [label="pkt1 to wire"]; + * + * + * @endmsc + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV +#include "VBoxNetFltInternal.h" + +#include <VBox/sup.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/spinlock.h> +#include <iprt/uuid.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define IFPORT_2_VBOXNETFLTINS(pIfPort) \ + ( (PVBOXNETFLTINS)((uint8_t *)(pIfPort) - RT_UOFFSETOF(VBOXNETFLTINS, MyPort)) ) + + +AssertCompileMemberSize(VBOXNETFLTINS, enmState, sizeof(uint32_t)); + +/** + * Sets the enmState member atomically. + * + * Used for all updates. + * + * @param pThis The instance. + * @param enmNewState The new value. + */ +DECLINLINE(void) vboxNetFltSetState(PVBOXNETFLTINS pThis, VBOXNETFTLINSSTATE enmNewState) +{ + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, enmNewState); +} + + +/** + * Gets the enmState member atomically. + * + * Used for all reads. + * + * @returns The enmState value. + * @param pThis The instance. + */ +DECLINLINE(VBOXNETFTLINSSTATE) vboxNetFltGetState(PVBOXNETFLTINS pThis) +{ + return (VBOXNETFTLINSSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmState); +} + + +/** + * Finds a instance by its name, the caller does the locking. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +static PVBOXNETFLTINS vboxNetFltFindInstanceLocked(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (!strcmp(pszName, pCur->szName)) + return pCur; + return NULL; +} + + +/** + * Finds a instance by its name, will request the mutex. + * + * No reference to the instance is retained, we're assuming the caller to + * already have one but just for some reason doesn't have the pointer to it. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +DECLHIDDEN(PVBOXNETFLTINS) vboxNetFltFindInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pRet; + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, NULL); + + pRet = vboxNetFltFindInstanceLocked(pGlobals, pszName); + + rc = RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return pRet; +} + + +/** + * Unlinks an instance from the chain. + * + * @param pGlobals The globals. + * @param pToUnlink The instance to unlink. + */ +static void vboxNetFltUnlinkLocked(PVBOXNETFLTGLOBALS pGlobals, PVBOXNETFLTINS pToUnlink) +{ + if (pGlobals->pInstanceHead == pToUnlink) + pGlobals->pInstanceHead = pToUnlink->pNext; + else + { + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (pCur->pNext == pToUnlink) + { + pCur->pNext = pToUnlink->pNext; + break; + } + Assert(pCur); + } + pToUnlink->pNext = NULL; +} + + +/** + * Performs interface rediscovery if it was disconnected from the host. + * + * @returns true if successfully rediscovered and connected, false if not. + * @param pThis The instance. + */ +static bool vboxNetFltMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + uint64_t Now; + bool fRediscovered; + bool fDoIt; + + /* + * Don't do rediscovery if we're called with preemption disabled. + * + * Note! This may cause trouble if we're always called with preemption + * disabled and vboxNetFltOsMaybeRediscovered actually does some real + * work. For the time being though, only Darwin and FreeBSD depends + * on these call outs and neither supports sending with preemption + * disabled. + */ + if (!RTThreadPreemptIsEnabled(NIL_RTTHREAD)) + return false; + + /* + * Rediscovered already? Time to try again? + */ + Now = RTTimeNanoTS(); + RTSpinlockAcquire(pThis->hSpinlock); + + fRediscovered = !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); + fDoIt = !fRediscovered + && !ASMAtomicUoReadBool(&pThis->fRediscoveryPending) + && Now - ASMAtomicUoReadU64(&pThis->NanoTSLastRediscovery) > UINT64_C(5000000000); /* 5 sec */ + if (fDoIt) + ASMAtomicWriteBool(&pThis->fRediscoveryPending, true); + + RTSpinlockRelease(pThis->hSpinlock); + + /* + * Call the OS specific code to do the job. + * Update the state when the call returns, that is everything except for + * the fDisconnectedFromHost flag which the OS specific code shall set. + */ + if (fDoIt) + { + fRediscovered = vboxNetFltOsMaybeRediscovered(pThis); + + Assert(!fRediscovered || !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)); + + ASMAtomicUoWriteU64(&pThis->NanoTSLastRediscovery, RTTimeNanoTS()); + ASMAtomicWriteBool(&pThis->fRediscoveryPending, false); + + if (fRediscovered) + /** @todo this isn't 100% serialized. */ + vboxNetFltPortOsSetActive(pThis, pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE); + } + + return fRediscovered; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnXmit + */ +static DECLCALLBACK(int) vboxNetFltPortXmit(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc = VINF_SUCCESS; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pSG); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + + /* + * Do a busy retain and then make sure we're connected to the interface + * before invoking the OS specific code. + */ + if (RT_LIKELY(vboxNetFltTryRetainBusyActive(pThis))) + { + if ( !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost) + || vboxNetFltMaybeRediscovered(pThis)) + rc = vboxNetFltPortOsXmit(pThis, pvIfData, pSG, fDst); + vboxNetFltRelease(pThis, true /* fBusy */); + } + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnWaitForIdle + */ +static DECLCALLBACK(int) vboxNetFltPortWaitForIdle(PINTNETTRUNKIFPORT pIfPort, uint32_t cMillies) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + AssertReturn(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING, VERR_INVALID_STATE); + + /* + * Go to sleep on the semaphore after checking the busy count. + */ + vboxNetFltRetain(pThis, false /* fBusy */); + + rc = VINF_SUCCESS; + while (pThis->cBusy && RT_SUCCESS(rc)) + rc = RTSemEventWait(pThis->hEventIdle, cMillies); /** @todo make interruptible? */ + + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnSetState + */ +static DECLCALLBACK(INTNETTRUNKIFSTATE) vboxNetFltPortSetState(PINTNETTRUNKIFPORT pIfPort, INTNETTRUNKIFSTATE enmState) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + INTNETTRUNKIFSTATE enmOldTrunkState; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pThis->pGlobals); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, INTNETTRUNKIFSTATE_INVALID); + AssertReturn(enmState > INTNETTRUNKIFSTATE_INVALID && enmState < INTNETTRUNKIFSTATE_END, + INTNETTRUNKIFSTATE_INVALID); + + /* + * Take the lock and change the state. + */ + RTSpinlockAcquire(pThis->hSpinlock); + enmOldTrunkState = pThis->enmTrunkState; + if (enmOldTrunkState != enmState) + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmTrunkState, enmState); + RTSpinlockRelease(pThis->hSpinlock); + + /* + * If the state change indicates that the trunk has become active or + * inactive, call the OS specific part so they can work the promiscuous + * settings and such. + * Note! The caller makes sure there are no concurrent pfnSetState calls. + */ + if ((enmOldTrunkState == INTNETTRUNKIFSTATE_ACTIVE) != (enmState == INTNETTRUNKIFSTATE_ACTIVE)) + vboxNetFltPortOsSetActive(pThis, (enmState == INTNETTRUNKIFSTATE_ACTIVE)); + + return enmOldTrunkState; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnNotifyMacAddress + */ +static DECLCALLBACK(void) vboxNetFltPortNotifyMacAddress(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PCRTMAC pMac) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + vboxNetFltPortOsNotifyMacAddress(pThis, pvIfData, pMac); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnConnectInterface + */ +static DECLCALLBACK(int) vboxNetFltPortConnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIf, void **ppvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsConnectInterface(pThis, pvIf, ppvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectInterface + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsDisconnectInterface(pThis, pvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + AssertRC(rc); /** @todo fix vboxNetFltPortOsDisconnectInterface. */ +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectAndRelease + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectAndRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Serious paranoia. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected); + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); + + /* + * Disconnect and release it. + */ + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSpinlockRelease(pThis->hSpinlock); + + vboxNetFltOsDisconnectIt(pThis); + pThis->pSwitchPort = NULL; + +#ifdef VBOXNETFLT_STATIC_CONFIG + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Unconnected); + RTSpinlockRelease(pThis->hSpinlock); +#endif + + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * Destroy a device that has been disconnected from the switch. + * + * @returns true if the instance is destroyed, false otherwise. + * @param pThis The instance to be destroyed. This is + * no longer valid when this function returns. + */ +static bool vboxNetFltDestroyInstance(PVBOXNETFLTINS pThis) +{ + PVBOXNETFLTGLOBALS pGlobals = pThis->pGlobals; + uint32_t cRefs = ASMAtomicUoReadU32((uint32_t volatile *)&pThis->cRefs); + int rc; + LogFlow(("vboxNetFltDestroyInstance: pThis=%p (%s)\n", pThis, pThis->szName)); + + /* + * Validate the state. + */ +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert( vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting + || vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cRefs); + Assert(!pThis->cBusy); + Assert(!pThis->pSwitchPort); + + /* + * Make sure the state is 'disconnecting' / 'destroying' and let the OS + * specific code do its part of the cleanup outside the mutex. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + vboxNetFltOsDeleteInstance(pThis); + + /* + * Unlink the instance and free up its resources. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Destroyed); + vboxNetFltUnlinkLocked(pGlobals, pThis); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + RTSemEventDestroy(pThis->hEventIdle); + pThis->hEventIdle = NIL_RTSEMEVENT; + RTSpinlockDestroy(pThis->hSpinlock); + pThis->hSpinlock = NIL_RTSPINLOCK; + RTMemFree(pThis); + + NOREF(cRefs); + + return true; +} + + +/** + * Releases a reference to the specified instance. + * + * This method will destroy the instance when the count reaches 0. + * It will also take care of decrementing the counter and idle wakeup. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be decremented too. + */ +DECLHIDDEN(void) vboxNetFltRelease(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicDecU32(&pThis->cBusy); + if (!cRefs) + { + int rc = RTSemEventSignal(pThis->hEventIdle); + AssertRC(rc); + } + else + Assert(cRefs < UINT32_MAX / 2); + } + + /* + * The object reference counting. + */ + cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + vboxNetFltDestroyInstance(pThis); + else + Assert(cRefs < UINT32_MAX / 2); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltPortRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @callback_method_impl{FNINTNETTRUNKIFPORTRELEASEBUSY} + */ +DECL_HIDDEN_CALLBACK(void) vboxNetFltPortReleaseBusy(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, true /*fBusy*/); +} + + +/** + * Retains a reference to the specified instance and a busy reference too. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be incremented as well. + */ +DECLHIDDEN(void) vboxNetFltRetain(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Retain the object. + */ + cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1 && cRefs < UINT32_MAX / 2); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicIncU32(&pThis->cBusy); + Assert(cRefs > 0 && cRefs < UINT32_MAX / 2); + } + + NOREF(cRefs); +} + + +/** + * Tries to retain the device as busy if the trunk is active. + * + * This is used before calling pfnRecv or pfnPreRecv. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyActive(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * Tries to retain the device as busy if the trunk is not disconnecting. + * + * This is used before reporting stuff to the internal network. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyNotDisconnected(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE + || pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRetain + */ +static DECLCALLBACK(void) vboxNetFltPortRetain(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRetain(pThis, false /* fBusy */); +} + + +/** + * Connects the instance to the specified switch port. + * + * Called while owning the lock. We're ASSUMING that the internal + * networking code is already owning an recursive mutex, so, there + * will be no deadlocks when vboxNetFltOsConnectIt calls back into + * it for setting preferences. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pSwitchPort The port on the internal network 'switch'. + * @param ppIfPort Where to return our port interface. + */ +static int vboxNetFltConnectIt(PVBOXNETFLTINS pThis, PINTNETTRUNKSWPORT pSwitchPort, PINTNETTRUNKIFPORT *ppIfPort) +{ + int rc; + + /* + * Validate state. + */ + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Initializing); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + + /* + * Do the job. + * Note that we're calling the os stuff while owning the semaphore here. + */ + pThis->pSwitchPort = pSwitchPort; + rc = vboxNetFltOsConnectIt(pThis); + if (RT_SUCCESS(rc)) + { + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Connected); + *ppIfPort = &pThis->MyPort; + } + else + pThis->pSwitchPort = NULL; + + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + return rc; +} + + +/** + * Creates a new instance. + * + * The new instance will be in the suspended state in a dynamic config and in + * the inactive in a static one. + * + * Called without owning the lock, but will request is several times. + * + * @returns VBox status code. + * @param pGlobals The globals. + * @param pszName The instance name. + * @param pSwitchPort The port on the switch that we're connected with (dynamic only). + * @param fNoPromisc Do not attempt going into promiscuous mode. + * @param pvContext Context argument for vboxNetFltOsInitInstance. + * @param ppIfPort Where to store the pointer to our port interface (dynamic only). + */ +static int vboxNetFltNewInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PINTNETTRUNKSWPORT pSwitchPort, + bool fNoPromisc, void *pvContext, PINTNETTRUNKIFPORT *ppIfPort) +{ + /* + * Allocate and initialize a new instance before requesting the mutex. + * Note! That in a static config we'll initialize the trunk state to + * disconnecting and flip it in vboxNetFltFactoryCreateAndConnect + * later on. This better reflext the state and it works better with + * assertions in the destruction path. + */ + int rc; + size_t const cchName = strlen(pszName); + PVBOXNETFLTINS pNew = (PVBOXNETFLTINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1])); + if (!pNew) + return VERR_INTNET_FLT_IF_FAILED; + AssertMsg(((uintptr_t)pNew & 7) == 0, ("%p LB %#x\n", pNew, RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1]))); + pNew->pNext = NULL; + pNew->MyPort.u32Version = INTNETTRUNKIFPORT_VERSION; + pNew->MyPort.pfnRetain = vboxNetFltPortRetain; + pNew->MyPort.pfnRelease = vboxNetFltPortRelease; + pNew->MyPort.pfnDisconnectAndRelease= vboxNetFltPortDisconnectAndRelease; + pNew->MyPort.pfnSetState = vboxNetFltPortSetState; + pNew->MyPort.pfnWaitForIdle = vboxNetFltPortWaitForIdle; + pNew->MyPort.pfnXmit = vboxNetFltPortXmit; + pNew->MyPort.pfnNotifyMacAddress = vboxNetFltPortNotifyMacAddress; + pNew->MyPort.pfnConnectInterface = vboxNetFltPortConnectInterface; + pNew->MyPort.pfnDisconnectInterface = vboxNetFltPortDisconnectInterface; + pNew->MyPort.u32VersionEnd = INTNETTRUNKIFPORT_VERSION; + pNew->pSwitchPort = pSwitchPort; + pNew->pGlobals = pGlobals; + pNew->hSpinlock = NIL_RTSPINLOCK; + pNew->enmState = kVBoxNetFltInsState_Initializing; +#ifdef VBOXNETFLT_STATIC_CONFIG + pNew->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; +#else + pNew->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; +#endif + pNew->fDisconnectedFromHost = false; + pNew->fRediscoveryPending = false; + pNew->fDisablePromiscuous = fNoPromisc; + pNew->NanoTSLastRediscovery = INT64_MAX; + pNew->cRefs = 1; + pNew->cBusy = 0; + pNew->hEventIdle = NIL_RTSEMEVENT; + memcpy(pNew->szName, pszName, cchName + 1); + + rc = RTSpinlockCreate(&pNew->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxNetFltNewInstance"); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pNew->hEventIdle); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltOsPreInitInstance(pNew); + if (RT_SUCCESS(rc)) + { + /* + * Insert the instance into the chain, checking for + * duplicates first of course (race). + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + if (!vboxNetFltFindInstanceLocked(pGlobals, pszName)) + { + pNew->pNext = pGlobals->pInstanceHead; + pGlobals->pInstanceHead = pNew; + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Call the OS specific initialization code. + */ + rc = vboxNetFltOsInitInstance(pNew, pvContext); + RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* + * Static instances are unconnected at birth. + */ + Assert(!pSwitchPort); + pNew->enmState = kVBoxNetFltInsState_Unconnected; + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppIfPort = &pNew->MyPort; + return rc; + +#else /* !VBOXNETFLT_STATIC_CONFIG */ + /* + * Connect it as well, the OS specific bits has to be done outside + * the lock as they may call back to into intnet. + */ + rc = vboxNetFltConnectIt(pNew, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + Assert(*ppIfPort == &pNew->MyPort); + return rc; + } + + /* Bail out (failed). */ + vboxNetFltOsDeleteInstance(pNew); +#endif /* !VBOXNETFLT_STATIC_CONFIG */ + } + vboxNetFltUnlinkLocked(pGlobals, pNew); + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); + } + } + RTSemEventDestroy(pNew->hEventIdle); + } + RTSpinlockDestroy(pNew->hSpinlock); + } + + RTMemFree(pNew); + return rc; +} + + +#ifdef VBOXNETFLT_STATIC_CONFIG +/** + * Searches for the NetFlt instance by its name and creates the new one if not found. + * + * @returns VBox status code. + * @retval VINF_SUCCESS and *ppInstance if a new instance was created. + * @retval VINF_ALREADY_INITIALIZED and *ppInstance if an instance already exists. + * + * @param pGlobal Pointer to the globals. + * @param pszName The instance name. + * @param ppInstance Where to return the instance pointer on success. + * @param pvContext Context which needs to be passed along to vboxNetFltOsInitInstance. + */ +DECLHIDDEN(int) vboxNetFltSearchCreateInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PVBOXNETFLTINS *ppInstance, void *pvContext) +{ + PINTNETTRUNKIFPORT pIfPort; + PVBOXNETFLTINS pCur; + VBOXNETFTLINSSTATE enmState; + int rc; + + *ppInstance = NULL; + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* + * Look for an existing instance in the list. + * + * There might be an existing one in the list if the driver was unbound + * while it was connected to an internal network. We're running into + * a destruction race that is a bit similar to the one in + * vboxNetFltFactoryCreateAndConnect, only the roles are reversed + * and we're not in a position to back down. Instead of backing down + * we'll delay a bit giving the other thread time to complete the + * destructor. + */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + while (pCur) + { + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + enmState = vboxNetFltGetState(pCur); + switch (enmState) + { + case kVBoxNetFltInsState_Unconnected: + case kVBoxNetFltInsState_Connected: + case kVBoxNetFltInsState_Disconnecting: + if (pCur->fDisconnectedFromHost) + { + /* Wait for it to exit the transitional disconnecting + state. It might otherwise be running the risk of + upsetting the OS specific code... */ + /** @todo This reconnect stuff should be serialized correctly for static + * devices. Shouldn't it? In the dynamic case we're using the INTNET + * outbound thunk lock, but that doesn't quite cut it here, or does + * it? We could either transition to initializing or make a callback + * while owning the mutex here... */ + if (enmState == kVBoxNetFltInsState_Disconnecting) + { + do + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + RTSemFastMutexRequest(pGlobals->hFastMtx); + enmState = vboxNetFltGetState(pCur); + } + while (enmState == kVBoxNetFltInsState_Disconnecting); + AssertMsg(enmState == kVBoxNetFltInsState_Unconnected, ("%d\n", enmState)); + Assert(pCur->fDisconnectedFromHost); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppInstance = pCur; + return VINF_ALREADY_INITIALIZED; + } + /* fall thru */ + + default: + { + bool fDfH = pCur->fDisconnectedFromHost; + RTSemFastMutexRelease(pGlobals->hFastMtx); + vboxNetFltRelease(pCur, false /* fBusy */); + LogRel(("VBoxNetFlt: Huh? An instance of '%s' already exists! [pCur=%p cRefs=%d fDfH=%RTbool enmState=%d]\n", + pszName, pCur, cRefs - 1, fDfH, enmState)); + *ppInstance = NULL; + return VERR_INTNET_FLT_IF_BUSY; + } + } + } + + /* Zero references, it's being destroyed. Delay a bit so the destructor + can finish its work and try again. (vboxNetFltNewInstance will fail + with duplicate name if we don't.) */ +# ifdef RT_STRICT + Assert(cRefs == 1); + enmState = vboxNetFltGetState(pCur); + AssertMsg( enmState == kVBoxNetFltInsState_Unconnected + || enmState == kVBoxNetFltInsState_Disconnecting + || enmState == kVBoxNetFltInsState_Destroyed, ("%d\n", enmState)); +# endif + ASMAtomicDecU32(&pCur->cRefs); + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* try again */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Try create a new instance. + * (fNoPromisc is overridden in the vboxNetFltFactoryCreateAndConnect path, so pass true here.) + */ + rc = vboxNetFltNewInstance(pGlobals, pszName, NULL, true /* fNoPromisc */, pvContext, &pIfPort); + if (RT_SUCCESS(rc)) + *ppInstance = IFPORT_2_VBOXNETFLTINS(pIfPort); + else + *ppInstance = NULL; + + return rc; +} +#endif /* VBOXNETFLT_STATIC_CONFIG */ + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnCreateAndConnect + */ +static DECLCALLBACK(int) vboxNetFltFactoryCreateAndConnect(PINTNETTRUNKFACTORY pIfFactory, const char *pszName, + PINTNETTRUNKSWPORT pSwitchPort, uint32_t fFlags, + PINTNETTRUNKIFPORT *ppIfPort) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + PVBOXNETFLTINS pCur; + int rc; + + LogFlow(("vboxNetFltFactoryCreateAndConnect: pszName=%p:{%s} fFlags=%#x\n", pszName, pszName, fFlags)); + Assert(pGlobals->cFactoryRefs > 0); + AssertMsgReturn(!(fFlags & ~(INTNETTRUNKFACTORY_FLAG_NO_PROMISC)), + ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Static: Find instance, check if busy, connect if not. + * Dynamic: Check for duplicate / busy interface instance. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + +//#if defined(VBOXNETADP) && defined(RT_OS_WINDOWS) +// /* temporary hack to pick up the first adapter */ +// pCur = pGlobals->pInstanceHead; /** @todo Don't for get to remove this temporary hack... :-) */ +//#else + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); +//#endif + if (pCur) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* Try grab a reference. If the count had already reached zero we're racing the + destructor code and must back down. */ + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + if (vboxNetFltGetState(pCur) == kVBoxNetFltInsState_Unconnected) + { + pCur->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; /** @todo protect me? */ + pCur->fDisablePromiscuous = !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC); + rc = vboxNetFltConnectIt(pCur, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + pCur = NULL; /* Don't release it, reference given to the caller. */ + else + pCur->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + } + else + { + Assert(cRefs == 1); + ASMAtomicDecU32(&pCur->cRefs); + pCur = NULL; /* nothing to release */ + rc = VERR_INTNET_FLT_IF_NOT_FOUND; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + if (pCur) + vboxNetFltRelease(pCur, false /* fBusy */); +#else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + +#ifdef VBOXNETFLT_STATIC_CONFIG + rc = VERR_INTNET_FLT_IF_NOT_FOUND; +#else + /* + * Dynamically create a new instance. + */ + rc = vboxNetFltNewInstance(pGlobals, + pszName, + pSwitchPort, + !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC), + NULL, + ppIfPort); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; +} + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltFactoryRelease(PINTNETTRUNKFACTORY pIfFactory) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + + int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); + Assert(cRefs >= 0); NOREF(cRefs); + LogFlow(("vboxNetFltFactoryRelease: cRefs=%d (new)\n", cRefs)); +} + + +/** + * Implements the SUPDRV component factor interface query method. + * + * @returns Pointer to an interface. NULL if not supported. + * + * @param pSupDrvFactory Pointer to the component factory registration structure. + * @param pSession The session - unused. + * @param pszInterfaceUuid The factory interface id. + */ +static DECLCALLBACK(void *) vboxNetFltQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, + const char *pszInterfaceUuid) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pSupDrvFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, SupDrvFactory)); + + /* + * Convert the UUID strings and compare them. + */ + RTUUID UuidReq; + int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); + if (RT_SUCCESS(rc)) + { + if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_UUID_STR)) + { + ASMAtomicIncS32(&pGlobals->cFactoryRefs); + return &pGlobals->TrunkFactory; + } +#ifdef LOG_ENABLED + /* log legacy queries */ + /* else if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_V1_UUID_STR)) + Log(("VBoxNetFlt: V1 factory query\n")); + */ + else + Log(("VBoxNetFlt: unknown factory interface query (%s)\n", pszInterfaceUuid)); +#endif + } + else + Log(("VBoxNetFlt: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); + + RT_NOREF1(pSession); + return NULL; +} + + +/** + * Checks whether the VBoxNetFlt wossname can be unloaded. + * + * This will return false if someone is currently using the module. + * + * @returns true if it's relatively safe to unload it, otherwise false. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(bool) vboxNetFltCanUnload(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + bool fRc = !pGlobals->pInstanceHead + && pGlobals->cFactoryRefs <= 0; + RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return fRc; +} + + +/** + * Try to close the IDC connection to SUPDRV if established. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + * + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + + Assert(pGlobals->hFastMtx != NIL_RTSEMFASTMUTEX); + + /* + * Check before trying to deregister the factory. + */ + if (!vboxNetFltCanUnload(pGlobals)) + return VERR_WRONG_ORDER; + + if (!pGlobals->fIDCOpen) + rc = VINF_SUCCESS; + else + { + /* + * Disconnect from SUPDRV and check that nobody raced us, + * reconnect if that should happen. + */ + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + if (!vboxNetFltCanUnload(pGlobals)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + return VERR_WRONG_ORDER; + } + + SUPR0IdcClose(&pGlobals->SupDrvIDC); + pGlobals->fIDCOpen = false; + } + + return rc; +} + + +/** + * Establishes the IDC connection to SUPDRV and registers our component factory. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + Assert(!pGlobals->fIDCOpen); + + /* + * Establish a connection to SUPDRV and register our component factory. + */ + rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + if (RT_SUCCESS(rc)) + { + pGlobals->fIDCOpen = true; + Log(("VBoxNetFlt: pSession=%p\n", SUPR0IdcGetSession(&pGlobals->SupDrvIDC))); + return rc; + } + + /* bail out. */ + LogRel(("VBoxNetFlt: Failed to register component factory, rc=%Rrc\n", rc)); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + } + + return rc; +} + + +/** + * Deletes the globals. + * + * This must be called after the IDC connection has been closed, + * see vboxNetFltTryDeleteIdc(). + * + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(void) vboxNetFltDeleteGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + Assert(!pGlobals->fIDCOpen); + + /* + * Release resources. + */ + RTSemFastMutexDestroy(pGlobals->hFastMtx); + pGlobals->hFastMtx = NIL_RTSEMFASTMUTEX; +} + + +/** + * Initializes the globals. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = RTSemFastMutexCreate(&pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + pGlobals->pInstanceHead = NULL; + + pGlobals->TrunkFactory.pfnRelease = vboxNetFltFactoryRelease; + pGlobals->TrunkFactory.pfnCreateAndConnect = vboxNetFltFactoryCreateAndConnect; +#if defined(RT_OS_WINDOWS) && defined(VBOXNETADP) + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetAdp", sizeof("VBoxNetAdp")); +#else + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetFlt", sizeof("VBoxNetFlt")); +#endif + pGlobals->SupDrvFactory.pfnQueryFactoryInterface = vboxNetFltQueryFactoryInterface; + pGlobals->fIDCOpen = false; + + return rc; + } + + return rc; +} + + +/** + * Called by the native part when the OS wants the driver to unload. + * + * @returns VINF_SUCCESS on success, VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdcAndGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = vboxNetFltTryDeleteIdc(pGlobals); + if (RT_SUCCESS(rc)) + vboxNetFltDeleteGlobals(pGlobals); + return rc; +} + + +/** + * Called by the native driver/kext module initialization routine. + * + * It will initialize the common parts of the globals, assuming the caller + * has already taken care of the OS specific bits, and establish the IDC + * connection to SUPDRV. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltInitGlobalsAndIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = vboxNetFltInitGlobals(pGlobals); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltInitIdc(pGlobals); + if (RT_SUCCESS(rc)) + return rc; + + /* bail out. */ + vboxNetFltDeleteGlobals(pGlobals); + } + + return rc; +} + |