/*
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 "device.h"
#include "packetbuffer.h"
#include "../common/emulproto.pb.h"
#include
#define __STDC_FORMAT_MACROS
#include
const quint64 kBcastMac = 0xffffffffffffULL;
// 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;
}
DeviceManager::~DeviceManager()
{
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 = new OstProto::DeviceGroup;
deviceGroup->mutable_device_group_id()->set_id(deviceGroupId);
deviceGroupList_.insert(deviceGroupId, deviceGroup);
enumerateDevices(deviceGroup, kAdd);
// Start emulation when first device is added
if ((deviceCount() == 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 devices remain
if ((deviceCount() == 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);
enumerateDevices(myDeviceGroup, kAdd);
return true;
}
int DeviceManager::deviceCount()
{
return deviceList_.size();
}
void DeviceManager::getDeviceList(
OstProto::PortDeviceList *deviceList)
{
foreach(Device *device, deviceList_) {
OstEmul::Device *dev =
deviceList->AddExtension(OstEmul::port_device);
device->getConfig(dev);
}
}
void DeviceManager::receivePacket(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
int offset = 0;
Device 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());
return;
}
// Extract dstMac
dstMac = qFromBigEndian(pktData + offset);
offset += 4;
dstMac = (dstMac << 16) | qFromBigEndian(pktData + offset);
dk.setMac(dstMac);
offset += 2;
// Skip srcMac - don't care
offset += 6;
qDebug("dstMac %012" PRIx64, dstMac);
_eth_type:
// Extract EthType
ethType = qFromBigEndian(pktData + offset);
qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
if (ethType == 0x8100) {
offset += 2;
vlan = qFromBigEndian(pktData + offset);
dk.setVlan(idx++, vlan);
offset += 2;
qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
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:
return;
}
void DeviceManager::transmitPacket(PacketBuffer *pktBuf)
{
port_->sendEmulationPacket(pktBuf);
}
void DeviceManager::clearDeviceNeighbors()
{
foreach(Device *device, deviceList_)
device->clearNeighbors();
}
void DeviceManager::getDeviceNeighbors(
OstProto::PortNeighborList *neighborList)
{
int count = 0;
foreach(Device *device, deviceList_) {
OstEmul::DeviceNeighborList *neighList =
neighborList->AddExtension(OstEmul::devices);
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
Device dk(this);
quint16 ethType;
quint16 vlan;
int idx = 0;
// 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 (ethType == 0x8100) {
offset += 2;
vlan = qFromBigEndian(pktData + offset);
dk.setVlan(idx++, vlan);
offset += 2;
qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
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)
{
Device dk(this);
OstEmul::VlanEmulation pbVlan = deviceGroup->GetExtension(OstEmul::encap)
.vlan();
int numTags = pbVlan.stack_size();
int vlanCount = 1;
OstEmul::MacEmulation mac = deviceGroup->GetExtension(OstEmul::mac);
OstEmul::Ip4Emulation ip4 = deviceGroup->GetExtension(OstEmul::ip4);
for (int i = 0; i < numTags; i++)
vlanCount *= pbVlan.stack(i).count();
// If we have no vlans, we still have the non-vlan-segmented LAN
if (vlanCount == 0)
vlanCount = 1;
for (int i = 0; i < vlanCount; i++) {
for (int j = 0; j < numTags; j++) {
OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j);
quint16 vlanAdd = i*vlan.step();
dk.setVlan(j, vlan.vlan_tag() + vlanAdd);
}
for (uint k = 0; k < deviceGroup->device_count(); k++) {
Device *device;
quint64 macAdd = k * mac.step();
quint32 ip4Add = k * ip4.step();
dk.setMac(mac.address() + macAdd);
dk.setIp4(ip4.address() + ip4Add,
ip4.prefix_length(),
ip4.default_gateway());
// TODO: fill in other pbDevice data
switch (oper) {
case kAdd:
device = new Device(this);
*device = dk;
deviceList_.insert(dk.key(), device);
dk.setMac(kBcastMac);
bcastList_.insert(dk.key(), device);
qDebug("enumerate(add): %s", qPrintable(device->config()));
break;
case kDelete:
device = deviceList_.take(dk.key());
qDebug("enumerate(del): %s", qPrintable(device->config()));
delete device;
dk.setMac(kBcastMac);
bcastList_.take(dk.key()); // device already freed above
break;
default:
Q_ASSERT(0); // Unreachable
}
} // foreach device
} // foreach vlan
}