IONetworkStack.cpp [plain text]
extern "C" {
#include <net/if.h>
#include <sys/sockio.h>
}
#include <IOKit/assert.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/network/IONetworkInterface.h>
#include <IOKit/network/IOEthernetController.h> // for setAggressiveness()
#include "IONetworkStack.h"
#include "IONetworkDebug.h"
#define super IOService
OSDefineMetaClassAndFinalStructors( IONetworkStack, IOService )
#define NIF_VAR(n) ((n)->_clientVar[0])
#define NIF_SET(n, x) (NIF_VAR(n) |= (x))
#define NIF_CLR(n, x) (NIF_VAR(n) &= ~(x))
#define NIF_TEST(n, x) (NIF_VAR(n) & (x))
#define NIF_CAST(x) ((IONetworkInterface *) x)
#define NIF_SAFECAST(x) (OSDynamicCast(IONetworkInterface, x))
#define NIF_NO_BSD_ATTACH(n) ((n)->_clientVar[2])
#define LOCK() IOLockLock(_stateLock)
#define UNLOCK() IOLockUnlock(_stateLock)
#define kDetachStateKey "detach state"
static IONetworkStack * gIONetworkStack = 0;
enum {
kInterfaceStateInactive = 0x01, kInterfaceStatePublished = 0x02, kInterfaceStateNamed = 0x04, kInterfaceStateAttaching = 0x08, kInterfaceStateAttached = 0x10, kInterfaceStateAbandoned = 0x20 };
#include <IOKit/IOUserClient.h>
class IONetworkStackUserClient : public IOUserClient
{
OSDeclareFinalStructors( IONetworkStackUserClient )
protected:
IONetworkStack * _provider;
public:
virtual bool initWithTask( task_t owningTask,
void * securityID,
UInt32 type,
OSDictionary * properties );
virtual bool start( IOService * provider );
virtual IOReturn clientClose( void );
virtual IOReturn clientDied( void );
virtual IOReturn setProperties( OSObject * properties );
};
bool IONetworkStack::start( IOService * provider )
{
OSDictionary * matching;
IOService * rootDomain;
const OSSymbol * ucClassName;
DLOG("IONetworkStack::start(%p) %p\n", provider, this);
if (gIONetworkStack)
goto fail;
if (super::start(provider) == false)
goto fail;
_stateLock = IOLockAlloc();
if (!_stateLock)
goto fail;
_ifListNaming = OSSet::withCapacity(8);
if (!_ifListNaming)
goto fail;
_ifPrefixDict = OSDictionary::withCapacity(4);
if (!_ifPrefixDict)
goto fail;
_asyncThread = thread_call_allocate(
OSMemberFunctionCast(thread_call_func_t, this,
&IONetworkStack::asyncWork), this);
if (!_asyncThread)
goto fail;
_noBSDAttachSymbol = OSSymbol::withCStringNoCopy(kIONetworkNoBSDAttachKey);
if (!_noBSDAttachSymbol)
goto fail;
if ((rootDomain = (IOService *) getPMRootDomain()))
{
rootDomain->IOService::setAggressiveness(kPMEthernetWakeOnLANSettings,
kIOEthernetWakeOnMagicPacket | kIOEthernetWakeOnPacketAddressMatch);
}
matching = serviceMatching(kIONetworkInterfaceClass);
if (!matching)
goto fail;
gIONetworkStack = this;
_ifNotifier = addMatchingNotification(
gIOPublishNotification,
matching,
OSMemberFunctionCast(
IOServiceMatchingNotificationHandler,
this, &IONetworkStack::interfacePublished),
this,
this,
1000 );
matching->release();
if (!_ifNotifier)
goto fail;
ucClassName = OSSymbol::withCStringNoCopy("IONetworkStackUserClient");
if (ucClassName)
{
setProperty(gIOUserClientClassKey, (OSObject *) ucClassName);
ucClassName->release();
}
registerService();
return true;
fail:
LOG("IONetworkStack::start(%p) %p failed\n", provider, this);
return false;
}
void IONetworkStack::free( void )
{
LOG("IONetworkStack::free() %p\n", this);
if (this == gIONetworkStack)
gIONetworkStack = 0;
if ( _ifNotifier )
{
_ifNotifier->remove();
_ifNotifier = 0;
}
if (_ifPrefixDict)
{
_ifPrefixDict->release();
_ifPrefixDict = 0;
}
if (_ifListNaming)
{
_ifListNaming->release();
_ifListNaming = 0;
}
if (_ifListDetach)
{
_ifListDetach->release();
_ifListDetach = 0;
}
if (_ifListAttach)
{
_ifListAttach->release();
_ifListAttach = 0;
}
if (_noBSDAttachSymbol)
{
_noBSDAttachSymbol->release();
_noBSDAttachSymbol = 0;
}
if (_asyncThread)
{
thread_call_free(_asyncThread);
_asyncThread = 0;
}
if (_stateLock)
{
IOLockFree(_stateLock);
_stateLock = 0;
}
super::free();
}
bool IONetworkStack::insertNetworkInterface( IONetworkInterface * netif )
{
const char * prefix = netif->getNamePrefix();
OSOrderedSet * set;
bool ok = true;
if (prefix == 0)
return false;
set = (OSOrderedSet *) _ifPrefixDict->getObject(prefix);
if (!set && ((set = OSOrderedSet::withCapacity(8, orderNetworkInterfaces))))
{
ok = _ifPrefixDict->setObject(prefix, set);
set->release();
}
ok = (set && ok) ? set->setObject(netif) : false;
if (set && (set->getCount() == 0))
{
_ifPrefixDict->removeObject(prefix);
}
return ok;
}
void IONetworkStack::removeNetworkInterface( IONetworkInterface * netif )
{
const char * prefix = netif->getNamePrefix();
OSOrderedSet * set;
if ( prefix )
{
set = (OSOrderedSet *) _ifPrefixDict->getObject(prefix);
if ( set )
{
set->removeObject(netif);
DLOG("IONetworkStack::removeNetworkInterface %s count = %d\n",
prefix, set->getCount());
if (set->getCount() == 0)
_ifPrefixDict->removeObject(prefix);
}
}
}
uint32_t IONetworkStack::getNextAvailableUnitNumber(
const char * prefix,
uint32_t startingUnit )
{
IONetworkInterface * netif;
OSOrderedSet * set;
assert(prefix);
set = (OSOrderedSet *) _ifPrefixDict->getObject(prefix);
netif = set ? NIF_CAST(set->getLastObject()) : 0;
if (!netif || (netif->getUnitNumber() < startingUnit))
{
}
else if (netif->getUnitNumber() == startingUnit)
{
startingUnit++;
}
else if (set)
{
for ( uint32_t index = 0; ; index++ )
{
netif = NIF_CAST(set->getObject(index));
if (!netif || (netif->getUnitNumber() > startingUnit))
break;
else if (netif->getUnitNumber() < startingUnit)
continue;
else
startingUnit = netif->getUnitNumber() + 1;
}
}
return startingUnit;
}
bool IONetworkStack::interfacePublished(
void * refCon __unused,
IOService * service,
IONotifier * notifier __unused )
{
IONetworkInterface * netif = NIF_SAFECAST(service);
bool isNew, ok = false;
if (!netif)
return false;
DLOG("IONetworkStack::interfacePublished(%p, state 0x%x, parent %d, noattach %d)\n",
netif, (uint32_t) NIF_VAR(netif), isParent(netif, gIOServicePlane),
(uint32_t) NIF_NO_BSD_ATTACH(netif));
isNew = (false == isParent(netif, gIOServicePlane));
if (isNew && !attach(netif))
return false;
LOCK();
do {
if (isNew)
{
if (NIF_VAR(netif) != 0)
break;
if (_ifListNaming->containsObject(netif))
{
ok = true;
break;
}
_ifListNaming->setObject(netif);
if (netif->getProperty(_noBSDAttachSymbol) == kOSBooleanTrue)
{
NIF_NO_BSD_ATTACH(netif) = true;
}
NIF_VAR(netif) = kInterfaceStatePublished;
ok = true;
}
else
{
ok = true;
if (NIF_NO_BSD_ATTACH(netif) == false)
break;
if (netif->getProperty(_noBSDAttachSymbol) == kOSBooleanTrue)
break;
if (NIF_TEST(netif,
kInterfaceStateInactive | kInterfaceStateAttached |
kInterfaceStateAttaching | kInterfaceStateAbandoned ))
break;
NIF_NO_BSD_ATTACH(netif) = false;
if (!NIF_TEST(netif, kInterfaceStateNamed))
{
break;
}
NIF_SET(netif, kInterfaceStateAttaching);
if (!_ifListAttach)
_ifListAttach = OSArray::withCapacity(1);
if (_ifListAttach)
{
_ifListAttach->setObject(netif);
thread_call_enter(_asyncThread);
}
}
}
while (false);
UNLOCK();
if (isNew && !ok)
{
netif->setProperty(kDetachStateKey, NIF_VAR(netif), 32);
detach(netif);
}
return ok;
}
void IONetworkStack::asyncWork( void )
{
IONetworkInterface * d_netif;
IONetworkInterface * a_netif;
uint32_t varBits;
while (1)
{
d_netif = a_netif = 0;
LOCK();
if (_ifListDetach)
{
d_netif = NIF_CAST(_ifListDetach->getObject(0));
if (d_netif)
{
d_netif->retain();
_ifListDetach->removeObject(0);
varBits = NIF_VAR(d_netif);
}
}
if (_ifListAttach)
{
a_netif = NIF_CAST(_ifListAttach->getObject(0));
if (a_netif)
{
a_netif->retain();
_ifListAttach->removeObject(0);
}
}
UNLOCK();
if (!d_netif && !a_netif)
break;
if (d_netif)
{
DLOG("IONetworkStack::asyncWork detach %s %p, state 0x%x\n",
d_netif->getName(), d_netif, varBits);
if (varBits & kInterfaceStateAttached)
{
d_netif->setInterfaceState( 0, kIONetworkInterfaceRegisteredState );
d_netif->detachFromDataLinkLayer(0, 0);
}
LOCK();
assert(NIF_TEST(d_netif, kInterfaceStateInactive));
assert(NIF_TEST(d_netif, kInterfaceStateNamed));
assert(!NIF_TEST(d_netif, kInterfaceStateAttaching));
removeNetworkInterface(d_netif);
UNLOCK();
d_netif->close( this );
d_netif->release();
}
if (a_netif)
{
DLOG("IONetworkStack::asyncWork attach %s %p, state 0x%x\n",
a_netif->getName(), a_netif, (uint32_t) NIF_VAR(a_netif));
assert(NIF_TEST(a_netif, kInterfaceStateAttaching));
attachNetworkInterfaceToBSD(a_netif);
a_netif->release();
}
}
}
bool IONetworkStack::didTerminate(
IOService * provider, IOOptionBits options, bool * defer )
{
IONetworkInterface * netif = NIF_SAFECAST(provider);
bool wakeThread = false;
DLOG("IONetworkStack::didTerminate(%s %p state 0x%x, 0x%x)\n",
provider->getName(), provider, (uint32_t) options,
(uint32_t) NIF_VAR(netif));
if (!netif)
return true;
LOCK();
do {
NIF_SET(netif, kInterfaceStateInactive);
_ifListNaming->removeObject(netif);
if (NIF_TEST(netif, kInterfaceStateAttaching))
break;
if (NIF_TEST(netif, kInterfaceStateAttached) ||
NIF_TEST(netif, kInterfaceStateNamed))
{
if (!_ifListDetach)
_ifListDetach = OSArray::withCapacity(8);
if (_ifListDetach)
{
_ifListDetach->setObject(netif);
wakeThread = true;
}
}
else
{
super::didTerminate(provider, options, defer);
}
}
while ( false );
UNLOCK();
if (wakeThread)
thread_call_enter(_asyncThread);
return true;
}
bool IONetworkStack::reserveInterfaceUnitNumber(
IONetworkInterface * netif,
uint32_t unit,
bool isUnitFixed,
bool * attachToBSD )
{
const char * prefix = netif->getNamePrefix();
uint32_t inUnit = unit;
bool opened = false;
bool ok = false;
DLOG("IONetworkStack::reserveInterfaceUnitNumber(%p, %s, %u)\n",
netif, prefix ? prefix : "", unit);
*attachToBSD = false;
LOCK();
_ifListNaming->removeObject(netif);
do {
if (!prefix)
{
LOG("interface name prefix is null\n");
break;
}
if (NIF_VAR(netif) != kInterfaceStatePublished)
{
LOG("unable to name interface in state 0x%x as %s%u\n",
(uint32_t) NIF_VAR(netif), prefix, inUnit);
break;
}
unit = getNextAvailableUnitNumber(prefix, unit);
if ((isUnitFixed == true) && (unit != inUnit))
{
LOG("interface name %s%u is unavailable\n", prefix, inUnit);
break;
}
UNLOCK();
ok = netif->open(this);
LOCK();
if (!ok)
{
LOG("interface %s%u open failed\n", prefix, inUnit);
break;
}
opened = true;
ok = false;
if (NIF_TEST(netif, kInterfaceStateInactive))
{
LOG("interface %s%u became inactive\n", prefix, inUnit);
break;
}
if ((netif->setUnitNumber(unit) == false) ||
(insertNetworkInterface(netif) == false))
{
LOG("interface %s%u name assigment failed\n", prefix, inUnit);
break;
}
ok = true;
}
while ( false );
if (ok)
{
NIF_SET(netif, kInterfaceStateNamed);
if (NIF_NO_BSD_ATTACH(netif) == false)
{
NIF_SET(netif, kInterfaceStateAttaching);
*attachToBSD = true;
}
}
UNLOCK();
if (!ok && opened)
{
netif->close(this);
}
return ok;
}
IOReturn IONetworkStack::attachNetworkInterfaceToBSD( IONetworkInterface * netif )
{
bool attachOK = false;
bool detachWakeup = false;
bool pingMatching = false;
char ifname[32];
IOReturn result;
assert( netif );
if ((result = netif->attachToDataLinkLayer(0, 0)) == kIOReturnSuccess)
{
ifnet_ioctl(netif->getIfnet(), 0, SIOCSIFFLAGS, 0);
netif->setInterfaceState( kIONetworkInterfaceRegisteredState );
snprintf(ifname, sizeof(ifname), "%s%u",
netif->getNamePrefix(), netif->getUnitNumber());
netif->setProperty(kIOBSDNameKey, ifname);
attachOK = true;
}
LOCK();
assert(NIF_TEST(netif, kInterfaceStateNamed));
assert(( NIF_VAR(netif) &
( kInterfaceStateAttaching | kInterfaceStateAttached )) ==
kInterfaceStateAttaching );
NIF_CLR( netif, kInterfaceStateAttaching );
if (attachOK)
{
NIF_SET( netif, kInterfaceStateAttached );
pingMatching = true;
if (NIF_TEST(netif, kInterfaceStateInactive))
{
if (!_ifListDetach)
_ifListDetach = OSArray::withCapacity(8);
if (_ifListDetach)
{
_ifListDetach->setObject(netif);
detachWakeup = true;
}
pingMatching = false;
}
}
else
{
removeNetworkInterface(netif);
NIF_SET(netif, kInterfaceStateAbandoned);
}
UNLOCK();
if (!attachOK)
{
netif->setProperty(kDetachStateKey, NIF_VAR(netif), 32);
netif->close( this );
detach(netif);
}
if (detachWakeup)
{
thread_call_enter(_asyncThread);
}
if (pingMatching)
{
netif->registerService();
}
return result;
}
IOReturn IONetworkStack::registerAllNetworkInterfaces( void )
{
IONetworkInterface * netif;
OSSet * list;
bool attachToBSD;
LOCK();
if (!_ifListNaming || (_ifListNaming->getCount() == 0))
{
UNLOCK();
return kIOReturnSuccess;
}
list = OSSet::withSet( _ifListNaming );
UNLOCK();
if (list == 0)
{
return kIOReturnNoMemory;
}
while ((netif = NIF_CAST(list->getAnyObject())))
{
if (reserveInterfaceUnitNumber( netif, 0, false, &attachToBSD ) &&
attachToBSD)
attachNetworkInterfaceToBSD( netif );
list->removeObject(netif);
}
list->release();
return kIOReturnSuccess;
}
IOReturn IONetworkStack::registerNetworkInterface(
IONetworkInterface * netif,
uint32_t unit,
bool isUnitFixed )
{
IOReturn result = kIOReturnNoSpace;
bool attachToBSD = false;
if (reserveInterfaceUnitNumber( netif, unit, isUnitFixed, &attachToBSD ))
{
result = (attachToBSD) ? attachNetworkInterfaceToBSD( netif ) :
kIOReturnSuccess;
}
return result;
}
SInt32 IONetworkStack::orderNetworkInterfaces(
const OSMetaClassBase * obj1,
const OSMetaClassBase * obj2,
void * ref )
{
const IONetworkInterface * netif1 = (const IONetworkInterface *) obj1;
const IONetworkInterface * netif2 = (const IONetworkInterface *) obj2;
assert( netif1 && netif2 );
return (netif2->getUnitNumber() - netif1->getUnitNumber());
}
IOReturn IONetworkStack::setProperties( OSObject * properties )
{
IONetworkInterface * netif = 0;
OSDictionary * dict = OSDynamicCast(OSDictionary, properties);
OSString * path;
OSNumber * num;
OSNumber * unit;
OSData * data;
uint32_t cmdType;
IOReturn error = kIOReturnBadArgument;
do {
if (!dict)
break;
num = OSDynamicCast( OSNumber,
dict->getObject(kIONetworkStackUserCommandKey));
if (num)
cmdType = num->unsigned32BitValue();
else
cmdType = kIONetworkStackRegisterInterfaceWithLowestUnit;
if (kIONetworkStackRegisterInterfaceAll == cmdType)
{
error = registerAllNetworkInterfaces();
break;
}
unit = OSDynamicCast(OSNumber, dict->getObject(kIOInterfaceUnit));
if (!unit)
break;
data = OSDynamicCast(OSData, dict->getObject(kIORegistryEntryIDKey));
path = OSDynamicCast(OSString, dict->getObject(kIOPathMatchKey));
if (data && (data->getLength() == sizeof(uint64_t)))
{
OSDictionary * matching;
OSIterator * iter;
uint64_t entryID = *(uint64_t *) data->getBytesNoCopy();
matching = registryEntryIDMatching(entryID);
if (!matching)
{
error = kIOReturnNoMemory;
break;
}
iter = getMatchingServices(matching);
matching->release();
if (!iter)
{
error = kIOReturnNotFound;
break;
}
if (iter)
{
netif = OSDynamicCast(IONetworkInterface, iter->getNextObject());
if (netif)
netif->retain();
iter->release();
}
}
else if (path)
{
IORegistryEntry * entry;
entry = IORegistryEntry::fromPath(path->getCStringNoCopy());
if (entry && OSDynamicCast(IONetworkInterface, entry))
netif = (IONetworkInterface *) entry;
else if (entry)
entry->release();
}
else
{
break;
}
if (!netif)
{
error = kIOReturnNoDevice;
break;
}
switch ( cmdType )
{
case kIONetworkStackRegisterInterfaceWithUnit:
error = registerNetworkInterface(
netif,
unit->unsigned32BitValue(),
true );
break;
case kIONetworkStackRegisterInterfaceWithLowestUnit:
error = registerNetworkInterface(
netif,
unit->unsigned32BitValue(),
false );
break;
default:
error = kIOReturnUnsupported;
break;
}
} while (false);
if (netif)
netif->release();
return error;
}
#undef super
#define super IOUserClient
OSDefineMetaClassAndFinalStructors( IONetworkStackUserClient, IOUserClient )
bool IONetworkStackUserClient::initWithTask( task_t owningTask,
void * securityID,
UInt32 type,
OSDictionary * properties )
{
if (!super::initWithTask(owningTask, securityID, type, properties))
return false;
if (IOUserClient::clientHasPrivilege(
securityID, kIOClientPrivilegeAdministrator) != kIOReturnSuccess)
return false;
return true;
}
bool IONetworkStackUserClient::start( IOService * provider )
{
if ( super::start(provider) == false )
return false;
if ( provider->open(this) == false )
return false;
_provider = OSDynamicCast(IONetworkStack, provider);
if (!_provider)
{
provider->close(this);
return false;
}
return true;
}
IOReturn IONetworkStackUserClient::clientClose( void )
{
if (_provider)
{
_provider->close(this);
detach(_provider);
}
return kIOReturnSuccess;
}
IOReturn IONetworkStackUserClient::clientDied( void )
{
return clientClose();
}
IOReturn IONetworkStackUserClient::setProperties( OSObject * properties )
{
if (kIOReturnSuccess != IOUserClient::clientHasPrivilege(
current_task(), kIOClientPrivilegeAdministrator))
{
return kIOReturnNotPrivileged;
}
return ( _provider ) ?
_provider->setProperties( properties ) :
kIOReturnNotReady;
}