VBoxNetFlt-darwin.cpp revision 6dd8f5023a9ba7588212331db90059553136fe33
/* $Id$ */
/** @file
* VBoxNetFlt - Network Filter Driver (Host), Darwin Specific Code.
*/
/*
* Copyright (C) 2006-2008 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
/*
* Deal with conflicts first.
* PVM - BSD mess, that FreeBSD has correct a long time ago.
*/
#define LOG_GROUP LOG_GROUP_NET_FLT_DRV
#include <VBox/intnetinline.h>
#include <iprt/initterm.h>
#include <iprt/spinlock.h>
#include <iprt/semaphore.h>
#include <sys/kern_event.h>
#include <net/kpi_interface.h>
RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */
#include <sys/kpi_mbuf.h>
#include <net/kpi_interfacefilter.h>
#define VBOXNETFLT_OS_SPECFIC 1
#include "../VBoxNetFltInternal.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The maximum number of SG segments.
* Used to prevent stack overflow and similar bad stuff. */
#define VBOXNETFLT_DARWIN_MAX_SEGS 32
#if 0
/** For testing extremely segmented frames. */
#define VBOXNETFLT_DARWIN_TEST_SEG_SIZE 14
#endif
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* The mbuf tag data.
*
* We have to associate the ethernet header with each packet we're sending
* because things like icmp will inherit the tag it self so the tag along
* isn't sufficent to identify our mbufs. For the icmp scenario the ethernet
* header naturarlly changes before the packet is send pack, so let check it.
*/
typedef struct VBOXNETFLTTAG
{
/** The ethernet header of the outgoing frame. */
/** Pointer to a VBoxNetFlt mbuf tag. */
typedef VBOXNETFLTTAG *PVBOXNETFLTTAG;
/** Pointer to a const VBoxNetFlt mbuf tag. */
typedef VBOXNETFLTTAG const *PCVBOXNETFLTTAG;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* Declare the module stuff.
*/
/**
* The (common) global data.
*/
static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals;
/** The unique tag id for this module.
* This is basically a unique string hash that lives on untill reboot.
* It is used for tagging mbufs. */
static mbuf_tag_id_t g_idTag;
/** the offset of the struct ifnet::if_pcount variable. */
static unsigned g_offIfNetPCount = sizeof(void *) * (1 /*if_softc*/ + 1 /*if_name*/ + 2 /*if_link*/ + 2 /*if_addrhead*/ + 1 /*if_check_multi*/)
+ sizeof(u_long) /*if_refcnt*/;
/** Macro for accessing ifnet::if_pcount. */
/**
* Start the kernel module.
*/
{
int rc;
/*
* Initialize IPRT and find our module tag id.
* (IPRT is shared with VBoxDrv, it creates the loggers.)
*/
if (RT_SUCCESS(rc))
{
Log(("VBoxNetFltDarwinStart\n"));
if (!err)
{
/*
* Initialize the globals and connect to the support driver.
*
* This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv)
* for establishing the connect to the support driver.
*/
if (RT_SUCCESS(rc))
{
return KMOD_RETURN_SUCCESS;
}
}
else
RTR0Term();
}
else
return KMOD_RETURN_FAILURE;
}
/**
* Stop the kernel module.
*/
{
Log(("VBoxNetFltDarwinStop\n"));
/*
* Refuse to unload if anyone is currently using the filter driver.
* This is important as I/O kit / xnu will to be able to do usage
* tracking for us!
*/
if (RT_FAILURE(rc))
{
Log(("VBoxNetFltDarwinStop - failed, busy.\n"));
return KMOD_RETURN_FAILURE;
}
/*
* Undo the work done during start (in reverse order).
*/
RTR0Term();
return KMOD_RETURN_SUCCESS;
}
/**
* Reads and retains the host interface handle.
*
* @returns The handle, NULL if detached.
* @param pThis
*/
{
/*
* Be careful here to avoid problems racing the detached callback.
*/
{
if (pIfNet)
}
return pIfNet;
}
/**
* Release the host interface handle previously retained
* by vboxNetFltDarwinRetainIfNet.
*
* @param pThis The instance.
* @param pIfNet The vboxNetFltDarwinRetainIfNet return value, NULL is fine.
*/
{
if (pIfNet)
}
/**
* Checks whether this is an mbuf created by vboxNetFltDarwinMBufFromSG,
* i.e. a buffer which we're pushing and should be ignored by the filter callbacks.
*
* @returns true / false accordingly.
* @param pThis The instance.
* @param pMBuf The mbuf.
* @param pvFrame The frame pointer, optional.
*/
{
/*
* Lookup the tag set by vboxNetFltDarwinMBufFromSG.
*/
if (err)
return false;
/*
* Dig out the ethernet header from the mbuf.
*/
if (!pEthHdr)
if (!pEthHdr)
/* ASSUMING that there is enough data to work on! */
{
Log3(("tagged, but the ethernet header has changed\n"));
return false;
}
return true;
}
/**
* Internal worker that create a darwin mbuf for a (scatter/)gather list.
*
* @returns Pointer to the mbuf.
* @param pThis The instance.
* @param pSG The (scatter/)gather list.
*/
{
/// @todo future? mbuf_how_t How = preemtion enabled ? MBUF_DONTWAIT : MBUF_WAITOK;
/*
* We can't make use of the physical addresses on darwin because the way the
* mbuf / cluster stuffe works (see mbuf_data_to_physical and mcl_to_paddr).
* So, because we're lazy, we will ASSUME that all SGs coming from INTNET
* will only contain one single segment.
*/
/*
* We need some way of getting back to our instance data when
* the mbuf is freed, so use pvUserData for this.
* -- this is not relevant anylonger! --
*/
/*
* Allocate a packet and copy over the data.
*
* Using mbuf_attachcluster() here would've been nice but there are two
* issues with it: (1) it's 10.5.x only, and (2) the documentation indicates
* that it's not supposed to be used for really external buffers. The 2nd
* point might be argued against considering that the only m_clattach user
* is mallocs memory for the ext mbuf and not doing what's stated in the docs.
* However, it's hard to tell if these m_clattach buffers actually makes it
* to the NICs or not, and even if they did, the NIC would need the physical
* addresses for the pages they contain and might end up copying the data
* to a new mbuf anyway.
*
* So, in the end it's better to just do it the simple way that will work
* 100%, even if it involes some extra work (alloc + copy) we really wished
* to avoid.
*/
if (!err)
{
/* Skip zero sized memory buffers (paranoia). */
/* Set the required packet header attributes. */
/* Special case the single buffer copy. */
{
}
else
{
/* Multi buffer copying. */
{
/* advance */
}
}
if (!err)
{
/*
* Tag the packet and return successfully.
*/
err = mbuf_tag_allocate(pPkt, g_idTag, 0 /* type */, sizeof(VBOXNETFLTTAG) /* tag len */, How, (void **)&pTagData);
if (!err)
{
return pPkt;
}
/* bailout: */
}
}
else
return NULL;
}
/**
* Calculates the number of segments required to represent the mbuf.
*
* @returns Number of segments.
* @param pThis The instance.
* @param pMBuf The mbuf.
* @param pvFrame The frame pointer, optional.
*/
DECLINLINE(unsigned) vboxNetFltDarwinMBufCalcSGSegs(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame)
{
/*
* Count the buffers in the chain.
*/
unsigned cSegs = 0;
cSegs++;
else if ( !cSegs
&& pvFrame
cSegs++;
#ifdef PADD_RUNT_FRAMES_FROM_HOST
/*
* Add one buffer if the total is less than the ethernet minimum 60 bytes.
* This may allocate a segment too much if the ethernet header is separated,
* but that shouldn't harm us much.
*/
cSegs++;
#endif
/* maximize the number of segments. */
#endif
}
/**
* Initializes a SG list from an mbuf.
*
* @returns Number of segments.
* @param pThis The instance.
* @param pMBuf The mbuf.
* @param pSG The SG.
* @param pvFrame The frame pointer, optional.
* @param cSegs The number of segments allocated for the SG.
* This should match the number in the mbuf exactly!
* @param fSrc The source of the frame.
*/
DECLINLINE(void) vboxNetFltDarwinMBufToSG(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc)
{
/*
* Walk the chain and convert the buffers to segments. Works INTNETSG::cbTotal.
*/
unsigned iSeg = 0;
{
if (cbSeg)
{
/* deal with pvFrame */
{
Assert(pvStart && pvSeg && offSeg < mbuf_maxlen(pMBuf) && offSegEnd <= mbuf_maxlen(pMBuf)); NOREF(offSegEnd);
{
}
else
AssertMsgFailed(("pvFrame=%p pvStart=%p pvSeg=%p offSeg=%p cbSeg=%#zx offSegEnd=%p offFrame=%p maxlen=%#zx\n",
}
iSeg++;
}
/* The pvFrame might be in a now empty buffer. */
else if ( !iSeg
&& pvFrame
{
iSeg++;
}
}
#ifdef PADD_RUNT_FRAMES_FROM_HOST
/*
* Add a trailer if the frame is too small.
*
* Since we're getting to the packet before it is framed, it has not
* yet been padded. The current solution is to add a segment pointing
* to a buffer containing all zeros and pray that works for all frames...
*/
{
}
#endif
/*
* Redistribute the segments.
*/
{
/* copy the segments to the end. */
while (iSrc > 0)
{
iDst--;
iSrc--;
}
/* create small segments from the start. */
iDst = 0;
{
{
}
{
break;
}
iDst++;
}
}
#endif
}
/**
*
* @see iff_detached_func in the darwin kpi.
*/
{
LogFlow(("vboxNetFltDarwinIffDetached: pThis=%p NanoTS=%RU64 (%d)\n",
/*
* If we've put it into promiscuous mode, undo that now. If we don't
* the if_pcount will go all wrong when it's replugged.
*/
/*
* We carefully take the spinlock and increase the interface reference
* behind it in order to avoid problematic races with the detached callback.
*/
pThis->u.s.fSetPromiscuous = false;
if (pIfNet)
}
/**
*
* @see iff_ioctl_func in the darwin kpi.
*/
static errno_t vboxNetFltDarwinIffIoCtl(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, u_long uCmd, void *pvArg)
{
/*
* Update fOtherPromiscuous.
*/
/** @todo we'll have to find the offset of if_pcount to get this right! */
//if (uCmd == SIOCSIFFLAGS)
//{
//
//}
/*
* We didn't handle it, continue processing.
*/
return EOPNOTSUPP;
}
/**
*
* @see iff_event_func in the darwin kpi.
*/
static void vboxNetFltDarwinIffEvent(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, const struct kev_msg *pEvMsg)
{
/*
* Watch out for the interface going online / offline.
*/
{
{
{
{
/* failed to bring it online. */
if (!err)
{
Log(("vboxNetFltDarwinIffEvent: enabled promiscuous mode on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet)));
}
else
Log(("vboxNetFltDarwinIffEvent: ifnet_set_promiscuous failed on %s, err=%d (%d)\n", pThis->szName, err, VBOX_GET_PCOUNT(pIfNet)));
}
{
/* Try fix the inconsistency. */
if (!err)
Log(("vboxNetFltDarwinIffEvent: fixed IFF_PROMISC on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet)));
else
Log(("vboxNetFltDarwinIffEvent: failed to fix IFF_PROMISC on %s, err=%d flags=%#x (%d)\n",
}
else
Log(("vboxNetFltDarwinIffEvent: online, '%s'. flags=%#x (%d)\n", pThis->szName, ifnet_flags(pIfNet), VBOX_GET_PCOUNT(pIfNet)));
}
}
else
Log(("vboxNetFltDarwinIffEvent: pThis->u.s.pIfNet=%p pIfNet=%p (%d)\n", pThis->u.s.pIfNet, pIfNet, VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : -1));
}
Log(("vboxNetFltDarwinIffEvent: vendor_code=%#x kev_class=%#x kev_subclass=%#x event_code=%#x\n",
}
/**
* Internal worker for vboxNetFltDarwinIffInput and vboxNetFltDarwinIffOutput,
*
* @returns 0 or EJUSTRETURN.
* @param pThis The instance.
* @param pMBuf The mbuf.
* @param pvFrame The start of the frame, optional.
* @param fSrc Where the packet (allegedly) comes from, one INTNETTRUNKDIR_* value.
* @param eProtocol The protocol.
*/
static errno_t vboxNetFltDarwinIffInputOutputWorker(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame,
{
/*
* Drop it immediately?
*/
Log2(("vboxNetFltDarwinIffInputOutputWorker: pThis=%p pMBuf=%p pvFrame=%p fSrc=%#x cbPkt=%x\n",
if (!pMBuf)
return 0;
#if 0 /* debugging lost icmp packets */
{
}
#endif
return 0;
/*
* Active? Retain the instance and increment the busy counter.
*/
if (fActive)
if (!fActive)
return 0;
/*
* Finalize out-bound packets since the stack puts off finalizing
* ASSUMES this only applies to outbound IP packets.
*/
if ( (fSrc & INTNETTRUNKDIR_HOST)
{
}
/*
* Create a (scatter/)gather list for the mbuf and feed it to the internal network.
*/
bool fDropIt = false;
if (cSegs < VBOXNETFLT_DARWIN_MAX_SEGS)
{
if (fDropIt)
}
return fDropIt ? EJUSTRETURN : 0;
}
/**
* From the host.
*
* @see iff_output_func in the darwin kpi.
*/
static errno_t vboxNetFltDarwinIffOutput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf)
{
/** @todo there was some note about the ethernet header here or something like that... */
return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, NULL, INTNETTRUNKDIR_HOST, eProtocol);
}
/**
* From the wire.
*
* @see iff_input_func in the darwin kpi.
*/
static errno_t vboxNetFltDarwinIffInput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf, char **ppchFrame)
{
return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, *ppchFrame, INTNETTRUNKDIR_WIRE, eProtocol);
}
/**
* Internal worker for vboxNetFltOsInitInstance and vboxNetFltOsMaybeRediscovered.
*
* @returns VBox status code.
* @param pThis The instance.
* @param fRediscovery If set we're doing a rediscovery attempt, so, don't
* flood the release log.
*/
{
/*
* Locate the interface first.
*
* The pIfNet member is updated before iflt_attach is called and used
* to deal with the hypothetical case where someone rips out the
* interface immediately after our iflt_attach call.
*/
if (err)
{
if (!fRediscovery)
else
return VERR_INTNET_FLT_IF_NOT_FOUND;
}
/*
* Get the mac address while we still have a valid ifnet reference.
*/
if (!err)
{
/*
* Try attach the filter.
*/
struct iff_filter RegRec;
RegRec.iff_protocol = 0;
{
}
}
/* Release the interface on failure. */
if (pIfNet)
if (RT_SUCCESS(rc))
LogRel(("VBoxFltDrv: attached to '%s' / %.*Rhxs\n", pThis->szName, sizeof(pThis->u.s.Mac), &pThis->u.s.Mac));
else
return rc;
}
{
}
{
int rc = VINF_SUCCESS;
if (pIfNet)
{
/*
* Create a mbuf for the gather list and push it onto the wire.
*/
if (fDst & INTNETTRUNKDIR_WIRE)
{
if (pMBuf)
{
if (err)
}
else
rc = VERR_NO_MEMORY;
}
/*
* Create a mbuf for the gather list and push it onto the host stack.
*/
if (fDst & INTNETTRUNKDIR_HOST)
{
if (pMBuf)
{
/* This is what IONetworkInterface::inputPacket does. */
unsigned const cbEthHdr = 14;
if (err)
}
else
rc = VERR_NO_MEMORY;
}
}
return rc;
}
{
bool fRc = false;
if (pIfNet)
{
/* gather the data */
/* calc the return. */
&& cPromisc > fSetPromiscuous;
}
return fRc;
}
{
}
{
/* ASSUMES that the MAC address never changes. */
}
{
if (pIfNet)
{
if (pThis->fDisablePromiscuous)
{
/*
* Promiscuous mode should not be used (wireless), we just need to
* make sure the interface is up.
*/
if (fActive)
{
{
}
}
}
else
{
/*
* This api is a bit weird, the best reference is the code.
*
* Also, we have a bit or race conditions wrt the maintance of
* host the interface promiscuity for vboxNetFltPortOsIsPromiscuous.
*/
if (fActive)
{
/*
* Try bring the interface up and running if it's down.
*/
{
if (!err)
}
/*
* Is it already up? If it isn't, leave it to the link event or
* we'll upset if_pcount (as stated above, ifnet_set_promiscuous is weird).
*/
{
if (!err)
{
/* check if it actually worked, this stuff is not always behaving well. */
{
if (!err)
if (!err)
Log(("vboxNetFlt: fixed IFF_PROMISC on %s (%d->%d)\n", pThis->szName, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet)));
else
Log(("VBoxNetFlt: failed to fix IFF_PROMISC on %s, err=%d (%d->%d)\n",
}
}
else
Log(("VBoxNetFlt: ifnet_set_promiscuous -> err=%d grr! (%d->%d)\n", err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet)));
}
else if (!err)
Log(("VBoxNetFlt: Waiting for the link to come up... (%d->%d)\n", cPromiscBefore, VBOX_GET_PCOUNT(pIfNet)));
if (err)
LogRel(("VBoxNetFlt: Failed to put '%s' into promiscuous mode, err=%d (%d->%d)\n", pThis->szName, err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet)));
}
else
{
if (pThis->u.s.fSetPromiscuous)
{
}
pThis->u.s.fSetPromiscuous = false;
}
}
}
}
{
/* Nothing to do here. */
return VINF_SUCCESS;
}
{
/* Nothing to do here. */
return VINF_SUCCESS;
}
{
/*
* Carefully obtain the interface filter reference and detach it.
*/
if (pIfFilter)
if (pIfFilter)
}
{
}
{
/*
* Init the darwin specific members.
*/
pThis->u.s.fSetPromiscuous = false;
pThis->u.s.fNeedSetPromiscuous = false;
//pThis->u.s.Mac = {0};
return VINF_SUCCESS;
}