VBoxNetFlt.c revision 479cb78f51993b710c1403ac9d21a30b53cd7693
* VBoxNetFlt - Network Filter Driver (Host), Common Code. * Copyright (C) 2008 Sun Microsystems, Inc. * Sun Microsystems, Inc. confidential /** @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_msc Locking / Sequence Diagrams * 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 it's reappearance. The latter two events * 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 * @subsection subsec_netflt_msc_dis_rel Disconnect from the network and release * 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 [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"]; * @subsection subsec_netflt_msc_hif_rm Host Interface Removal * 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. * 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"]; * @subsection subsec_netflt_msc_hif_rm Host Interface Rediscovery * 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. * 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"]; /******************************************************************************* *******************************************************************************/ #
error "The static setup needs a full check up wrt assumptions and incomplete code."/******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ * 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. * Finds a instance by its name, will request the mutex. * @returns Pointer to the instance by the given name. NULL if not found. * @param pGlobals The globals. * @param pszName The name of the instance. * Unlinks an instance from the chain. * @param pGlobals The globals. * @param pToUnlink The instance to unlink. * Sets the enmState member atomically. * @param pThis The instance. * @param enmNewState The new value. * Gets the enmState member atomically. * @returns The enmState value. * @param pThis The instance. * Performs interface rediscovery if it was disconnected from the host. * @returns true if successfully rediscovered and connected, false if not. * @param pThis The instance. * Rediscovered already? Time to try again? * 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. #
error "UNSUPPORTED (RT_WITH_W64_UNWIND_HACK)" * @copydoc INTNETTRUNKIFPORT::pfnXmit * Do a busy retain and then make sure we're connected to the interface * before invoking the OS specific code. * @copydoc INTNETTRUNKIFPORT::pfnIsPromiscuous * Ask the OS specific code. * @copydoc INTNETTRUNKIFPORT::pfnGetMacAddress * Forward the question to the OS specific code. * @copydoc INTNETTRUNKIFPORT::pfnIsHostMac * Ask the OS specific code. * @copydoc INTNETTRUNKIFPORT::pfnWaitForIdle * Go to sleep on the semaphore after checking the busy count. * @copydoc INTNETTRUNKIFPORT::pfnSetActive * We're assuming that the caller is serializing the calls, so we don't * have to be extremely careful here. Just update first and then call * the OS specific code, the update must be serialized for various reasons. * @copydoc INTNETTRUNKIFPORT::pfnDisconnectAndRelease * Disconnect and release it. * Destroy a device that has been disconnected from the switch. * @param pThis The instance to be destroyed. This is * no longer valid when this function returns. * Make sure the state is 'disconnecting' and let the OS specific code * do its part of the cleanup outside the mutex. * Unlink the instance and free up its resources. * 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. * The object reference counting. * @copydoc INTNETTRUNKIFPORT::pfnRetain * 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. * @copydoc INTNETTRUNKIFPORT::pfnRetain * 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. * Note that we're calling the os stuff while owning the semaphore here. * 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 ppIfPort Where to store the pointer to our port interface (dynamic only). * Allocate and initialize a new instance before requesting the mutex. * Insert the instance into the chain, checking for * duplicates first of course (race). * Call the OS specific initialization code. * Connect it as well, the OS specific bits has to be done outside * the lock as they may call back to into intnet. * @copydoc INTNETTRUNKFACTORY::pfnCreateAndConnect * Static: Find instance, check if busy, connect if not. * Dynamic: Check for duplicate / busy interface instance. LogFlow((
"vboxNetFltFactoryCreateAndConnect: returns %Rrc\n",
rc));
* Dynamically create a new instance. LogFlow((
"vboxNetFltFactoryCreateAndConnect: returns %Rrc\n",
rc));
* @copydoc INTNETTRUNKFACTORY::pfnRelease 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 componet factory registration structure. * @param pSession The session - unused. * @param pszInterfaceUuid The factory interface id. * Convert the UUID strings and compare them. /* else if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_V1_UUID_STR)) Log(("VBoxNetFlt: V1 factory query\n")); * 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. * Called by the native part when the OS wants the driver to unload. * @returns VINF_SUCCESS on succes, VERR_WRONG_ORDER if we're busy. * @param pGlobals Pointer to the globals. * Check before trying to deregister the factory. * Disconnect from SUPDRV and check that nobody raced us, * reconnect if that should happen. * 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. * @returns VBox status code. * @param pGlobals Pointer to the globals. * Initialize the common portions of the structure. * Establish a connection to SUPDRV and register our component factory. LogRel((
"VBoxNetFlt: Failed to register component factory, rc=%Rrc\n",
rc));