/* Copyright (C) 2015 Srivats P. This file is part of "Ostinato" This 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, either version 3 of the License, or (at your option) any later version. 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 */ #include "devicemanager.h" #include "abstractport.h" #include "emuldevice.h" #include "../common/emulation.h" #include "hostdevice.h" #include "interfaceinfo.h" #include "nulldevice.h" #include "packetbuffer.h" #include "../common/emulproto.pb.h" #include const quint64 kBcastMac = 0xffffffffffffULL; inline UInt128 UINT128(OstEmul::Ip6Address x) { return UInt128(x.hi(), x.lo()); } inline bool isMacMcast(quint64 mac) { return ((mac >> 40) & 0x01) == 0x01; } // XXX: Port owning DeviceManager already uses locks, so we don't use any // locks within DeviceManager to protect deviceGroupList_ et.al. DeviceManager::DeviceManager(AbstractPort *parent) { port_ = parent; } void DeviceManager::createHostDevices(void) { const InterfaceInfo *ifInfo = port_->interfaceInfo(); if (!ifInfo) return; Device *device = HostDevice::create(port_->name(), this); device->setMac(ifInfo->mac); int count = ifInfo->ip4.size(); for (int i = 0; i < count; i++) { // XXX: Since we can't support multiple IPs with same mac, // skip link-local IP - unless it is the only one if (((ifInfo->ip4.at(i).address & 0xffff0000) == 0xa9fe0000) && (count > 1)) continue; device->setIp4(ifInfo->ip4.at(i).address, ifInfo->ip4.at(i).prefixLength, ifInfo->ip4.at(i).gateway); break; // TODO: support multiple IPs with same mac } count = ifInfo->ip6.size(); for (int i = 0; i < count; i++) { // XXX: Since we can't support multiple IPs with same mac, // skip link-local IP - unless it is the only one if (((ifInfo->ip6.at(i).address.hi64() >> 48) == 0xfe80) && (count > 1)) continue; device->setIp6(ifInfo->ip6.at(i).address, ifInfo->ip6.at(i).prefixLength, ifInfo->ip6.at(i).gateway); break; // TODO: support multiple IPs with same mac } if (deviceList_.contains(device->key())) { qWarning("%s: error adding host device %s (EEXIST)", __FUNCTION__, qPrintable(device->config())); delete device; return; } hostDeviceList_.append(device); insertDevice(device->key(), device); qDebug("host(add): %s", qPrintable(device->config())); } DeviceManager::~DeviceManager() { // Delete *all* devices - host and enumerated foreach(Device *dev, deviceList_) delete dev; foreach(OstProto::DeviceGroup *devGrp, deviceGroupList_) delete devGrp; } int DeviceManager::deviceGroupCount() { return deviceGroupList_.size(); } const OstProto::DeviceGroup* DeviceManager::deviceGroupAtIndex(int index) { if ((index < 0) || (index >= deviceGroupCount())) { qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, index, deviceGroupCount() - 1); return NULL; } // Sort List by 'id', get the id at 'index' and then corresponding devGrp return deviceGroupList_.value(deviceGroupList_.uniqueKeys().value(index)); } const OstProto::DeviceGroup* DeviceManager::deviceGroup(uint deviceGroupId) { return deviceGroupList_.value(deviceGroupId); } bool DeviceManager::addDeviceGroup(uint deviceGroupId) { OstProto::DeviceGroup *deviceGroup; if (deviceGroupList_.contains(deviceGroupId)) { qWarning("%s: deviceGroup id %u already exists", __FUNCTION__, deviceGroupId); return false; } deviceGroup = newDeviceGroup(port_->id()); deviceGroup->mutable_device_group_id()->set_id(deviceGroupId); deviceGroupList_.insert(deviceGroupId, deviceGroup); enumerateDevices(deviceGroup, kAdd); // Start emulation when first device group is added // NOTE: Host devices don't have a deviceGroup and don't need emulation if ((deviceGroupCount() == 1) && port_) port_->startDeviceEmulation(); return true; } bool DeviceManager::deleteDeviceGroup(uint deviceGroupId) { OstProto::DeviceGroup *deviceGroup; if (!deviceGroupList_.contains(deviceGroupId)) { qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, deviceGroupId); return false; } deviceGroup = deviceGroupList_.take(deviceGroupId); enumerateDevices(deviceGroup, kDelete); delete deviceGroup; // Stop emulation if no device groups remain // NOTE: Host devices don't have a deviceGroup and don't need emulation if ((deviceGroupCount() == 0) && port_) port_->stopDeviceEmulation(); return true; } bool DeviceManager::modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup) { quint32 id = deviceGroup->device_group_id().id(); OstProto::DeviceGroup *myDeviceGroup = deviceGroupList_.value(id); if (!myDeviceGroup) { qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, id); return false; } enumerateDevices(myDeviceGroup, kDelete); myDeviceGroup->CopyFrom(*deviceGroup); // If mac step is 0, silently override to 1 - otherwise we won't have // unique DeviceKeys if (myDeviceGroup->GetExtension(OstEmul::mac).step() == 0) myDeviceGroup->MutableExtension(OstEmul::mac)->set_step(1); // Default value for ip6 step should be 1 (not 0) if (myDeviceGroup->HasExtension(OstEmul::ip6) && !myDeviceGroup->GetExtension(OstEmul::ip6).has_step()) myDeviceGroup->MutableExtension(OstEmul::ip6) ->mutable_step()->set_lo(1); enumerateDevices(myDeviceGroup, kAdd); return true; } int DeviceManager::deviceCount() { return deviceList_.size(); } void DeviceManager::getDeviceList( OstProto::PortDeviceList *deviceList) { foreach(Device *device, sortedDeviceList_) { OstEmul::Device *dev = deviceList->AddExtension(OstEmul::device); device->getConfig(dev); } } void DeviceManager::receivePacket(PacketBuffer *pktBuf) { QMutexLocker locker(&listLock_); uchar *pktData = pktBuf->data(); int offset = 0; EmulDevice dk(this); Device *device; quint64 dstMac; quint16 ethType; quint16 vlan; int idx = 0; // We assume pkt is ethernet // TODO: extend for other link layer types // All frames we are interested in should be at least 32 bytes if (pktBuf->length() < 32) { qWarning("short frame of %d bytes, skipping ...", pktBuf->length()); goto _exit; } // Extract dstMac dstMac = qFromBigEndian(pktData + offset); offset += 4; dstMac = (dstMac << 16) | qFromBigEndian(pktData + offset); qDebug("dstMac %012llx", dstMac); // XXX: Treat multicast as bcast if (isMacMcast(dstMac)) dstMac = kBcastMac; dk.setMac(dstMac); offset += 2; // Skip srcMac - don't care offset += 6; _eth_type: // Extract EthType ethType = qFromBigEndian(pktData + offset); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); if (tpidList_.contains(ethType)) { offset += 2; vlan = qFromBigEndian(pktData + offset); dk.setVlan(idx++, vlan); offset += 2; qDebug("%s: idx: %d vlan: 0x%04x/%d", __FUNCTION__, idx, vlan, vlan & 0x0fff); goto _eth_type; } pktBuf->pull(offset); if (dstMac == kBcastMac) { QList list = bcastList_.values(dk.key()); // FIXME: We need to clone the pktBuf before passing to each // device, otherwise only the first device gets the original // packet - all subsequent ones get the modified packet! // NOTE: modification may not be in the pkt data buffer but // in the HDTE pointers - which is bad as well! foreach(Device *device, list) device->receivePacket(pktBuf); goto _exit; } // Is it destined for us? device = deviceList_.value(dk.key()); if (!device) { qDebug("%s: dstMac %012llx is not us", __FUNCTION__, dstMac); goto _exit; } device->receivePacket(pktBuf); _exit: delete pktBuf; } void DeviceManager::transmitPacket(PacketBuffer *pktBuf) { port_->sendEmulationPacket(pktBuf); } void DeviceManager::resolveDeviceGateways() { foreach(Device *device, deviceList_) { device->resolveGateway(); } } void DeviceManager::clearDeviceNeighbors(Device::NeighborSet set) { foreach(Device *device, deviceList_) device->clearNeighbors(set); } void DeviceManager::getDeviceNeighbors( OstProto::PortNeighborList *neighborList) { int count = 0; foreach(Device *device, sortedDeviceList_) { OstEmul::DeviceNeighborList *neighList = neighborList->AddExtension(OstEmul::device_neighbor); neighList->set_device_index(count++); device->getNeighbors(neighList); } } void DeviceManager::resolveDeviceNeighbor(PacketBuffer *pktBuf) { Device *device = originDevice(pktBuf); if (device) device->resolveNeighbor(pktBuf); } quint64 DeviceManager::deviceMacAddress(PacketBuffer *pktBuf) { Device *device = originDevice(pktBuf); return device ? device->mac() : 0; } quint64 DeviceManager::neighborMacAddress(PacketBuffer *pktBuf) { Device *device = originDevice(pktBuf); return device ? device->neighborMac(pktBuf) : 0; } // ------------------------------------ // // Private Methods // ------------------------------------ // Device* DeviceManager::originDevice(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); int offset = 12; // start parsing after mac addresses EmulDevice dk(this); quint16 ethType; quint16 vlan; int idx = 0; // Do we have any devices at all? if (!deviceCount()) return NULL; // pktBuf will not have the correct dstMac populated, so use bcastMac // and search for device by IP dk.setMac(kBcastMac); _eth_type: ethType = qFromBigEndian(pktData + offset); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); if (tpidList_.contains(ethType)) { offset += 2; vlan = qFromBigEndian(pktData + offset); dk.setVlan(idx++, vlan); offset += 2; qDebug("%s: idx: %d vlan: 0x%04x/%d", __FUNCTION__, idx, vlan, vlan & 0x0fff); goto _eth_type; } pktBuf->pull(offset); foreach(Device *device, bcastList_.values(dk.key())) { if (device->isOrigin(pktBuf)) return device; } qDebug("couldn't find origin device for packet"); return NULL; } void DeviceManager::enumerateDevices( const OstProto::DeviceGroup *deviceGroup, Operation oper) { QMutexLocker locker(&listLock_); EmulDevice dk(this); OstEmul::VlanEmulation pbVlan = deviceGroup->encap() .GetExtension(OstEmul::vlan); int numTags = pbVlan.stack_size(); int n = 1; QList vlanCount; bool hasIp4 = deviceGroup->HasExtension(OstEmul::ip4); bool hasIp6 = deviceGroup->HasExtension(OstEmul::ip6); OstEmul::MacEmulation mac = deviceGroup->GetExtension(OstEmul::mac); OstEmul::Ip4Emulation ip4 = deviceGroup->GetExtension(OstEmul::ip4); OstEmul::Ip6Emulation ip6 = deviceGroup->GetExtension(OstEmul::ip6); /* * vlanCount[] stores the number of unique vlans at each tag level * e.g. for a 3-tag config with 2, 3, 4 vlans at each level respectively * vlanCount = [24, 12, 4] * 0 - 0, 0, 0 * 1 - 0, 0, 1 * 2 - 0, 0, 2 * 3 - 0, 0, 3 * 4 - 0, 1, 0 * 5 - 0, 1, 1 * 6 - 0, 1, 2 * 7 - 0, 1, 3 * 8 - 0, 2, 0 * 9 - 0, 2, 1 * 10 - 0, 2, 2 * 11 - 0, 2, 3 * 12 - 1, 0, 0 * 13 - 1, 0, 1 * 14 - 1, 0, 2 * 15 - 1, 0, 3 * 16 - 1, 1, 0 * 17 - 1, 1, 1 * 18 - 1, 1, 2 * 19 - 1, 1, 3 * 21 - 1, 2, 0 * 21 - 1, 2, 1 * 22 - 1, 2, 2 * 23 - 1, 2, 3 * * Note that vlanCount[0] repesents total-number-of-vlans * * Another way to think about this is that at a particular vlan tag * level, we need to repeat a particular vlan-id as many times as the * next level's count before we can increment the vlan-id at that level * * We use this list to calculate the vlan ids for each tag level for * all the vlans. * * For implementation convenience we append a '1' as the last element */ vlanCount.append(n); for (int i = numTags - 1; i >= 0 ; i--) { OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(i); n *= vlan.count(); vlanCount.prepend(n); // Update TPID list switch (oper) { case kAdd: tpidList_[vlan.tpid()]++; break; case kDelete: tpidList_[vlan.tpid()]--; if (tpidList_[vlan.tpid()] == 0) tpidList_.remove(vlan.tpid()); break; default: Q_ASSERT(0); // Unreachable } } QHash::const_iterator iter = tpidList_.constBegin(); qDebug("Port %s TPID List:", port_->name()); while (iter != tpidList_.constEnd()) { qDebug("tpid: %x (%d)", iter.key(), iter.value()); iter++; } for (int i = 0; i < vlanCount.at(0); i++) { for (int j = 0; j < numTags; j++) { OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j); quint16 vlanAdd = (i/vlanCount.at(j+1) % vlan.count())*vlan.step(); dk.setVlan(j, vlan.vlan_tag() + vlanAdd, vlan.tpid()); } for (uint k = 0; k < deviceGroup->device_count(); k++) { Device *device; quint64 macAdd = k * mac.step(); quint32 ip4Add = k * ip4.step(); UInt128 ip6Add = UINT128(ip6.step()) * k; dk.setMac(mac.address() + macAdd); if (hasIp4) dk.setIp4(ip4.address() + ip4Add, ip4.prefix_length(), ip4.default_gateway()); if (hasIp6) dk.setIp6(UINT128(ip6.address()) + ip6Add, ip6.prefix_length(), UINT128(ip6.default_gateway())); switch (oper) { case kAdd: if (deviceList_.contains(dk.key())) { qWarning("%s: error adding device %s (EEXIST)", __FUNCTION__, qPrintable(dk.config())); break; } device = new EmulDevice(this); *device = dk; insertDevice(dk.key(), device); qDebug("enumerate(add): %p %s", device, qPrintable(device->config())); break; case kDelete: device = deviceList_.value(dk.key()); if (!device) { qWarning("%s: error deleting device %s (NOTFOUND)", __FUNCTION__, qPrintable(dk.config())); break; } qDebug("enumerate(del): %p %s", device, qPrintable(device->config())); deleteDevice(dk.key()); break; default: Q_ASSERT(0); // Unreachable } } // foreach device } // foreach vlan } bool DeviceManager::insertDevice(DeviceKey key, Device *device) { deviceList_.insert(key, device); sortedDeviceList_.insert(key, device); NullDevice bcastDevice = *(static_cast(device)); bcastDevice.setMac(kBcastMac); bcastList_.insert(bcastDevice.key(), device); return true; } bool DeviceManager::deleteDevice(DeviceKey key) { Device *device = deviceList_.take(key); if (!device) return false; sortedDeviceList_.take(key); NullDevice bcastDevice = *(static_cast(device)); bcastDevice.setMac(kBcastMac); bcastList_.take(bcastDevice.key()); delete device; return true; }