Feature: Device Emulation - first cut working code
This commit is contained in:
parent
ee9d6e7c55
commit
ab433dc22b
@ -18,6 +18,7 @@
|
||||
import os
|
||||
from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError
|
||||
import protocols.protocol_pb2 as ost_pb
|
||||
import protocols.emulproto_pb2 as emul # FIXME: change name?
|
||||
from __init__ import __version__
|
||||
|
||||
class DroneProxy(object):
|
||||
|
66
common/emulproto.proto
Normal file
66
common/emulproto.proto
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import "protocol.proto";
|
||||
|
||||
package OstEmul;
|
||||
|
||||
message MacEmulation {
|
||||
optional uint64 addr = 10;
|
||||
}
|
||||
|
||||
extend OstProto.Device {
|
||||
optional MacEmulation mac = 200;
|
||||
}
|
||||
|
||||
message VlanEmulation {
|
||||
message Vlan {
|
||||
optional uint32 tpid = 1 [default = 0x8100];
|
||||
optional uint32 vlan_tag = 2; // includes prio, cfi and vlanid
|
||||
}
|
||||
// FIXME: rename as just stack?
|
||||
repeated Vlan vlan_stack = 11; // outer to inner
|
||||
}
|
||||
|
||||
extend OstProto.Device {
|
||||
optional VlanEmulation vlan = 201;
|
||||
}
|
||||
|
||||
message Ip4Emulation {
|
||||
optional uint32 addr = 2;
|
||||
optional uint32 prefix_length = 3;
|
||||
optional uint32 gateway = 4; // FIXME: rename to default_gateway?
|
||||
}
|
||||
|
||||
extend OstProto.Device {
|
||||
optional Ip4Emulation ip4 = 300;
|
||||
}
|
||||
|
||||
message Ip6Emulation {
|
||||
optional uint64 addr_hi = 31;
|
||||
optional uint64 addr_lo = 32;
|
||||
optional uint32 prefix_length = 33;
|
||||
optional uint64 gateway_hi = 34;
|
||||
optional uint64 gateway_lo = 35;
|
||||
}
|
||||
|
||||
extend OstProto.Device {
|
||||
optional Ip6Emulation ip6 = 301;
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ LIBS += \
|
||||
|
||||
PROTOS = \
|
||||
protocol.proto \
|
||||
emulproto.proto
|
||||
|
||||
PROTOS += \
|
||||
mac.proto \
|
||||
payload.proto \
|
||||
eth2.proto \
|
||||
@ -34,7 +37,7 @@ PROTOS = \
|
||||
textproto.proto \
|
||||
userscript.proto \
|
||||
hexdump.proto \
|
||||
sample.proto
|
||||
sample.proto
|
||||
|
||||
HEADERS = \
|
||||
abstractprotocol.h \
|
||||
|
@ -273,6 +273,39 @@ message Notification {
|
||||
optional PortIdList port_id_list = 6;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Protocol Emulation
|
||||
* FIXME: review/fix tag numbers
|
||||
* FIXME: move xxxEmulation to their own .proto files?
|
||||
* FIXME: What will be the contents of a default device created by addDevice()?
|
||||
* FIXME: decide default values for device and protoEmulations
|
||||
*/
|
||||
message DeviceId {
|
||||
required uint32 id = 1;
|
||||
}
|
||||
|
||||
message DeviceCore {
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
message DeviceIdList {
|
||||
required PortId port_id = 1;
|
||||
repeated DeviceId device_id = 2;
|
||||
}
|
||||
|
||||
message Device {
|
||||
required DeviceId device_id = 1;
|
||||
optional DeviceCore core = 2;
|
||||
|
||||
extensions 200 to 500; // For use by Protocol Emulations
|
||||
}
|
||||
|
||||
message DeviceConfigList {
|
||||
required PortId port_id = 1;
|
||||
repeated Device device = 2;
|
||||
}
|
||||
|
||||
service OstService {
|
||||
rpc getPortIdList(Void) returns (PortIdList);
|
||||
rpc getPortConfig(PortIdList) returns (PortConfigList);
|
||||
@ -295,5 +328,12 @@ service OstService {
|
||||
rpc clearStats(PortIdList) returns (Ack);
|
||||
|
||||
rpc checkVersion(VersionInfo) returns (VersionCompatibility);
|
||||
|
||||
// Device Protocol Emulation
|
||||
rpc getDeviceIdList(PortId) returns (DeviceIdList);
|
||||
rpc getDeviceConfig(DeviceIdList) returns (DeviceConfigList);
|
||||
rpc addDevice(DeviceIdList) returns (Ack);
|
||||
rpc deleteDevice(DeviceIdList) returns (Ack);
|
||||
rpc modifyDevice(DeviceConfigList) returns (Ack);
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "abstractport.h"
|
||||
|
||||
#include "../common/streambase.h"
|
||||
#include "../common/abstractprotocol.h"
|
||||
#include "../common/streambase.h"
|
||||
#include "devicemanager.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
@ -46,6 +47,8 @@ AbstractPort::AbstractPort(int id, const char *device)
|
||||
linkState_ = OstProto::LinkStateUnknown;
|
||||
minPacketSetSize_ = 1;
|
||||
|
||||
deviceManager_ = new DeviceManager(this);
|
||||
|
||||
maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats
|
||||
memset((void*) &stats_, 0, sizeof(stats_));
|
||||
resetStats();
|
||||
@ -53,6 +56,7 @@ AbstractPort::AbstractPort(int id, const char *device)
|
||||
|
||||
AbstractPort::~AbstractPort()
|
||||
{
|
||||
delete deviceManager_;
|
||||
}
|
||||
|
||||
void AbstractPort::init()
|
||||
@ -83,6 +87,11 @@ bool AbstractPort::modify(const OstProto::Port &port)
|
||||
return ret;
|
||||
}
|
||||
|
||||
DeviceManager* AbstractPort::deviceManager()
|
||||
{
|
||||
return deviceManager_;
|
||||
}
|
||||
|
||||
StreamBase* AbstractPort::streamAtIndex(int index)
|
||||
{
|
||||
Q_ASSERT(index < streamList_.size());
|
||||
|
@ -25,7 +25,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "../common/protocol.pb.h"
|
||||
|
||||
class DeviceManager;
|
||||
class StreamBase;
|
||||
class PacketBuffer;
|
||||
class QIODevice;
|
||||
|
||||
class AbstractPort
|
||||
@ -93,6 +95,11 @@ public:
|
||||
virtual bool isCaptureOn() = 0;
|
||||
virtual QIODevice* captureData() = 0;
|
||||
|
||||
DeviceManager* deviceManager();
|
||||
virtual void startDeviceEmulation() = 0;
|
||||
virtual void stopDeviceEmulation() = 0;
|
||||
virtual int sendEmulationPacket(PacketBuffer *pktBuf) = 0;
|
||||
|
||||
void stats(PortStats *stats);
|
||||
void resetStats() { epochStats_ = stats_; }
|
||||
|
||||
@ -111,6 +118,8 @@ protected:
|
||||
struct PortStats stats_;
|
||||
//! \todo Need lock for stats access/update
|
||||
|
||||
DeviceManager *deviceManager_;
|
||||
|
||||
private:
|
||||
bool isSendQueueDirty_;
|
||||
|
||||
|
287
server/device.cpp
Normal file
287
server/device.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "device.h"
|
||||
|
||||
#include "../common/emulproto.pb.h"
|
||||
#include "devicemanager.h"
|
||||
#include "packetbuffer.h"
|
||||
|
||||
#include <qendian.h>
|
||||
|
||||
QHash<quint64, Device*> Device::macHash_;
|
||||
QHash<quint32, Device*> Device::ip4Hash_;
|
||||
|
||||
Device::Device(quint32 id, DeviceManager *deviceManager)
|
||||
{
|
||||
deviceManager_ = deviceManager;
|
||||
data_.mutable_device_id()->set_id(id);
|
||||
// FIXME: choose a better default mac address
|
||||
data_.MutableExtension(OstEmul::mac)->set_addr(0x001122330000ULL + id);
|
||||
|
||||
Device::macHash_.insert(myMac(), this);
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
macHash_.remove(myMac());
|
||||
ip4Hash_.remove(myIp4());
|
||||
}
|
||||
|
||||
quint32 Device::id()
|
||||
{
|
||||
return data_.device_id().id();
|
||||
}
|
||||
|
||||
void Device::protoDataCopyFrom(const OstProto::Device &device)
|
||||
{
|
||||
quint64 oldMac, newMac;
|
||||
quint32 oldIp4, newIp4;
|
||||
|
||||
// Save old mac and ip before updating the device data
|
||||
oldMac = myMac();
|
||||
oldIp4 = myIp4();
|
||||
|
||||
data_.CopyFrom(device);
|
||||
|
||||
// Get new mac and ip for comparison
|
||||
newMac = myMac();
|
||||
newIp4 = myIp4();
|
||||
|
||||
// Update MacHash if mac has changed
|
||||
if (newMac != oldMac) {
|
||||
macHash_.remove(oldMac);
|
||||
macHash_.insert(newMac, this);
|
||||
}
|
||||
|
||||
// Update Ip4Hash if ip4 has changed
|
||||
if (newIp4 != oldIp4) {
|
||||
ip4Hash_.remove(oldIp4);
|
||||
ip4Hash_.insert(newIp4, this);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::protoDataCopyInto(OstProto::Device &device) const
|
||||
{
|
||||
device.CopyFrom(data_);
|
||||
}
|
||||
|
||||
int Device::encapSize()
|
||||
{
|
||||
int size = 14; // ethernet header size
|
||||
|
||||
if (data_.HasExtension(OstEmul::vlan))
|
||||
size += 4 * data_.GetExtension(OstEmul::vlan).vlan_stack_size();
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type)
|
||||
{
|
||||
quint64 srcMac = myMac();
|
||||
uchar *p = pktBuf->push(encapSize());
|
||||
|
||||
if (!p) {
|
||||
qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__,
|
||||
encapSize(), pktBuf->head(), pktBuf->data());
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
*(quint32*)(p ) = qToBigEndian(quint32(dstMac >> 16));
|
||||
*(quint16*)(p + 4) = qToBigEndian(quint16(dstMac & 0xffff));
|
||||
*(quint32*)(p + 6) = qToBigEndian(quint32(srcMac >> 16));
|
||||
*(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff));
|
||||
// TODO: Vlan Encap
|
||||
*(quint16*)(p + 12) = qToBigEndian(type);
|
||||
|
||||
_exit:
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
quint64 Device::myMac()
|
||||
{
|
||||
if (data_.HasExtension(OstEmul::mac))
|
||||
return data_.GetExtension(OstEmul::mac).addr();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
quint32 Device::myIp4()
|
||||
{
|
||||
if (data_.HasExtension(OstEmul::ip4))
|
||||
return data_.GetExtension(OstEmul::ip4).addr();
|
||||
|
||||
return 0; // FIXME: how to indicate that we don't have a IP?
|
||||
}
|
||||
|
||||
void Device::transmitPacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
deviceManager_->transmitPacket(pktBuf);
|
||||
}
|
||||
|
||||
void Device::receivePacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
uchar *pktData = pktBuf->data();
|
||||
int offset = 0;
|
||||
Device *device;
|
||||
const quint64 bcastMac = 0xffffffffffffULL;
|
||||
quint64 dstMac;
|
||||
quint16 ethType;
|
||||
|
||||
// We assume pkt is ethernet
|
||||
// TODO: extend for other link layer types
|
||||
|
||||
// Extract dstMac
|
||||
dstMac = qFromBigEndian<quint32>(pktData + offset);
|
||||
offset += 4;
|
||||
dstMac = (dstMac << 16) | qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
|
||||
// Skip srcMac - don't care
|
||||
offset += 6;
|
||||
qDebug("dstMac %llx", dstMac);
|
||||
|
||||
// Is it destined for us?
|
||||
device = macHash_.value(dstMac);
|
||||
if (!device && (dstMac != bcastMac)) {
|
||||
qDebug("%s: dstMac %llx is not us", __FUNCTION__, dstMac);
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
ethType = qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
qDebug("%s: ethType 0x%x", __FUNCTION__, ethType);
|
||||
|
||||
switch(ethType)
|
||||
{
|
||||
case 0x0806: // ARP
|
||||
pktBuf->pull(offset);
|
||||
receiveArp(device, pktBuf);
|
||||
break;
|
||||
|
||||
case 0x8100: // VLAN
|
||||
case 0x0800: // IPv4
|
||||
case 0x86dd: // IPv6
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Device::receiveArp(Device *device, PacketBuffer *pktBuf)
|
||||
{
|
||||
uchar *pktData = pktBuf->data();
|
||||
int offset = 0;
|
||||
quint16 hwType, protoType;
|
||||
quint8 hwAddrLen, protoAddrLen;
|
||||
quint16 opCode;
|
||||
quint64 srcMac, tgtMac;
|
||||
quint32 srcIp, tgtIp;
|
||||
|
||||
// Extract annd verify ARP packet contents
|
||||
hwType = qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
if (hwType != 1) // Mac
|
||||
goto _invalid_exit;
|
||||
|
||||
protoType = qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
if (protoType != 0x0800) // IPv4
|
||||
goto _invalid_exit;
|
||||
|
||||
hwAddrLen = pktData[offset];
|
||||
offset += 1;
|
||||
if (hwAddrLen != 6)
|
||||
goto _invalid_exit;
|
||||
|
||||
protoAddrLen = pktData[offset];
|
||||
offset += 1;
|
||||
if (protoAddrLen != 4)
|
||||
goto _invalid_exit;
|
||||
|
||||
opCode = qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
|
||||
srcMac = qFromBigEndian<quint32>(pktData + offset);
|
||||
offset += 4;
|
||||
srcMac = (srcMac << 16) | qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
|
||||
srcIp = qFromBigEndian<quint32>(pktData + offset);
|
||||
offset += 4;
|
||||
|
||||
tgtMac = qFromBigEndian<quint32>(pktData + offset);
|
||||
offset += 4;
|
||||
tgtMac = (tgtMac << 16) | qFromBigEndian<quint16>(pktData + offset);
|
||||
offset += 2;
|
||||
|
||||
tgtIp = qFromBigEndian<quint32>(pktData + offset);
|
||||
offset += 4;
|
||||
|
||||
switch (opCode)
|
||||
{
|
||||
case 1: // ARP Request
|
||||
if (!device)
|
||||
device = ip4Hash_.value(tgtIp);
|
||||
if (device->myIp4() == tgtIp) {
|
||||
PacketBuffer *pktBuf = new PacketBuffer;
|
||||
uchar *p;
|
||||
|
||||
pktBuf->reserve(device->encapSize());
|
||||
p = pktBuf->put(28); // FIXME: hardcoding
|
||||
if (p) {
|
||||
// HTYP, PTYP
|
||||
*(quint32*)(p ) = qToBigEndian(quint32(0x00010800));
|
||||
// HLEN, PLEN, OPER
|
||||
*(quint32*)(p+ 4) = qToBigEndian(quint32(0x06040002));
|
||||
// Source H/W Addr, Proto Addr
|
||||
*(quint32*)(p+ 8) = qToBigEndian(
|
||||
quint32(device->myMac() >> 16));
|
||||
*(quint16*)(p+12) = qToBigEndian(
|
||||
quint16(device->myMac() & 0xffff));
|
||||
*(quint32*)(p+14) = qToBigEndian(tgtIp);
|
||||
// Target H/W Addr, Proto Addr
|
||||
*(quint32*)(p+18) = qToBigEndian(quint32(srcMac >> 16));
|
||||
*(quint16*)(p+22) = qToBigEndian(quint16(srcMac & 0xffff));
|
||||
*(quint32*)(p+24) = qToBigEndian(srcIp);
|
||||
}
|
||||
|
||||
device->encap(pktBuf, srcMac, 0x0806);
|
||||
device->transmitPacket(pktBuf);
|
||||
|
||||
qDebug("Sent ARP Reply for srcIp/tgtIp=0x%x/0x%x",
|
||||
srcIp, tgtIp);
|
||||
}
|
||||
break;
|
||||
case 2: // ARP Response
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
_invalid_exit:
|
||||
qWarning("Invalid ARP content");
|
||||
return;
|
||||
}
|
67
server/device.h
Normal file
67
server/device.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_H
|
||||
#define _DEVICE_H
|
||||
|
||||
#include "../common/protocol.pb.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
class DeviceManager;
|
||||
class PacketBuffer;
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
Device(quint32 id, DeviceManager *deviceManager);
|
||||
~Device();
|
||||
|
||||
quint32 id();
|
||||
|
||||
void protoDataCopyFrom(const OstProto::Device &device);
|
||||
void protoDataCopyInto(OstProto::Device &device) const;
|
||||
|
||||
int encapSize();
|
||||
void encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type);
|
||||
|
||||
// receivePacket() is a class method 'coz we don't have the target
|
||||
// device yet; transmitPacket() is always from a particular device
|
||||
void transmitPacket(PacketBuffer *pktBuf);
|
||||
static void receivePacket(PacketBuffer *pktBuf);
|
||||
|
||||
private: // methods
|
||||
// receiveArp() is a class method 'coz ARP request is broadcast, so
|
||||
// we can't identify the target device till we parse the ARP header
|
||||
static void receiveArp(Device *device, PacketBuffer *pktBuf);
|
||||
|
||||
quint64 myMac();
|
||||
quint32 myIp4();
|
||||
|
||||
private: // data
|
||||
// Class data
|
||||
static QHash<quint64, Device*> macHash_;
|
||||
static QHash<quint32, Device*> ip4Hash_;
|
||||
|
||||
DeviceManager *deviceManager_;
|
||||
OstProto::Device data_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
117
server/devicemanager.cpp
Normal file
117
server/devicemanager.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "devicemanager.h"
|
||||
|
||||
#include "abstractport.h"
|
||||
#include "device.h"
|
||||
#include "packetbuffer.h"
|
||||
|
||||
#include <qendian.h>
|
||||
|
||||
// FIXME: add lock to protect deviceList_ operations?
|
||||
|
||||
DeviceManager::DeviceManager(AbstractPort *parent)
|
||||
{
|
||||
port_ = parent;
|
||||
}
|
||||
|
||||
DeviceManager::~DeviceManager()
|
||||
{
|
||||
foreach(Device *dev, deviceList_)
|
||||
delete dev;
|
||||
}
|
||||
|
||||
int DeviceManager::deviceCount()
|
||||
{
|
||||
return deviceList_.size();
|
||||
}
|
||||
|
||||
Device* DeviceManager::deviceAtIndex(int index)
|
||||
{
|
||||
if ((index < 0) || (index >= deviceCount())) {
|
||||
qWarning("%s: index %d out of range (max %d)", __FUNCTION__,
|
||||
index, deviceCount());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return deviceList_.value(deviceList_.uniqueKeys().value(index));
|
||||
}
|
||||
|
||||
Device* DeviceManager::device(uint deviceId)
|
||||
{
|
||||
return deviceList_.value(deviceId);
|
||||
}
|
||||
|
||||
bool DeviceManager::addDevice(uint deviceId)
|
||||
{
|
||||
Device *device;
|
||||
|
||||
if (deviceList_.contains(deviceId)) {
|
||||
qWarning("%s: device id %u already exists", __FUNCTION__, deviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
device = new Device(deviceId, this);
|
||||
deviceList_.insert(deviceId, device);
|
||||
|
||||
if ((deviceCount() == 1) && port_)
|
||||
port_->startDeviceEmulation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceManager::deleteDevice(uint deviceId)
|
||||
{
|
||||
if (!deviceList_.contains(deviceId)) {
|
||||
qWarning("%s: device id %u does not exist", __FUNCTION__, deviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
delete deviceList_.take(deviceId);
|
||||
|
||||
if ((deviceCount() == 0) && port_)
|
||||
port_->stopDeviceEmulation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceManager::modifyDevice(const OstProto::Device *device)
|
||||
{
|
||||
quint32 id = device->device_id().id();
|
||||
Device *myDevice = deviceList_.value(id);
|
||||
if (!myDevice) {
|
||||
qWarning("%s: device id %u does not exist", __FUNCTION__, id);
|
||||
return false;
|
||||
}
|
||||
|
||||
myDevice->protoDataCopyFrom(*device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceManager::receivePacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
Device::receivePacket(pktBuf);
|
||||
}
|
||||
|
||||
void DeviceManager::transmitPacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
port_->sendEmulationPacket(pktBuf);
|
||||
}
|
54
server/devicemanager.h
Normal file
54
server/devicemanager.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_MANAGER_H
|
||||
#define _DEVICE_MANAGER_H
|
||||
|
||||
#include "../common/protocol.pb.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QtGlobal>
|
||||
|
||||
class AbstractPort;
|
||||
class Device;
|
||||
class PacketBuffer;
|
||||
|
||||
class DeviceManager
|
||||
{
|
||||
public:
|
||||
DeviceManager(AbstractPort *parent = 0);
|
||||
~DeviceManager();
|
||||
|
||||
int deviceCount();
|
||||
Device* deviceAtIndex(int index);
|
||||
Device* device(uint deviceId);
|
||||
|
||||
bool addDevice(uint deviceId);
|
||||
bool deleteDevice(uint deviceId);
|
||||
bool modifyDevice(const OstProto::Device *device);
|
||||
|
||||
void receivePacket(PacketBuffer *pktBuf);
|
||||
void transmitPacket(PacketBuffer *pktBuf);
|
||||
private:
|
||||
AbstractPort *port_;
|
||||
QHash<uint, Device*> deviceList_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -33,6 +33,8 @@ LIBS += -lprotobuf
|
||||
HEADERS += drone.h \
|
||||
myservice.h
|
||||
SOURCES += \
|
||||
device.cpp \
|
||||
devicemanager.cpp \
|
||||
drone_main.cpp \
|
||||
drone.cpp \
|
||||
portmanager.cpp \
|
||||
@ -43,6 +45,7 @@ SOURCES += \
|
||||
winpcapport.cpp
|
||||
SOURCES += myservice.cpp
|
||||
SOURCES += pcapextra.cpp
|
||||
SOURCES += packetbuffer.cpp
|
||||
|
||||
QMAKE_DISTCLEAN += object_script.*
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2010 Srivats P.
|
||||
Copyright (C) 2010-2015 Srivats P.
|
||||
|
||||
This file is part of "Ostinato"
|
||||
|
||||
@ -31,6 +31,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "../common/streambase.h"
|
||||
#include "../rpc/pbrpccontroller.h"
|
||||
#include "device.h"
|
||||
#include "devicemanager.h"
|
||||
#include "portmanager.h"
|
||||
|
||||
#include <QStringList>
|
||||
@ -591,3 +593,224 @@ _invalid_version:
|
||||
controller->SetFailed("invalid version information");
|
||||
done->Run();
|
||||
}
|
||||
|
||||
/*
|
||||
* ===================================================================
|
||||
* Device Emulation
|
||||
* FIXME: Locking for these functions is at Port level, should it be
|
||||
* moved to inside DeviceManager instead? In other words, are
|
||||
* streams/ports and devices independent?
|
||||
* ===================================================================
|
||||
*/
|
||||
void MyService::getDeviceIdList(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::PortId* request,
|
||||
::OstProto::DeviceIdList* response,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
DeviceManager *devMgr;
|
||||
int portId;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
portId = request->id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
goto _invalid_port;
|
||||
|
||||
devMgr = portInfo[portId]->deviceManager();
|
||||
|
||||
response->mutable_port_id()->set_id(portId);
|
||||
portLock[portId]->lockForRead();
|
||||
for (int i = 0; i < devMgr->deviceCount(); i++)
|
||||
{
|
||||
OstProto::DeviceId *d;
|
||||
|
||||
d = response->add_device_id();
|
||||
d->set_id(devMgr->deviceAtIndex(i)->id());
|
||||
}
|
||||
portLock[portId]->unlock();
|
||||
|
||||
done->Run();
|
||||
return;
|
||||
|
||||
_invalid_port:
|
||||
controller->SetFailed("Invalid Port Id");
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::getDeviceConfig(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::DeviceConfigList* response,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
DeviceManager *devMgr;
|
||||
int portId;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
portId = request->port_id().id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
goto _invalid_port;
|
||||
|
||||
devMgr = portInfo[portId]->deviceManager();
|
||||
|
||||
response->mutable_port_id()->set_id(portId);
|
||||
portLock[portId]->lockForRead();
|
||||
for (int i = 0; i < request->device_id_size(); i++)
|
||||
{
|
||||
Device *device;
|
||||
OstProto::Device *d;
|
||||
|
||||
device = devMgr->device(request->device_id(i).id());
|
||||
if (!device)
|
||||
continue; //! \todo(LOW): Partial status of RPC
|
||||
|
||||
d = response->add_device();
|
||||
device->protoDataCopyInto(*d);
|
||||
}
|
||||
portLock[portId]->unlock();
|
||||
|
||||
done->Run();
|
||||
return;
|
||||
|
||||
_invalid_port:
|
||||
controller->SetFailed("invalid portid");
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::addDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::Ack* /*response*/,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
DeviceManager *devMgr;
|
||||
int portId;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
portId = request->port_id().id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
goto _invalid_port;
|
||||
|
||||
devMgr = portInfo[portId]->deviceManager();
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
if (portInfo[portId]->isTransmitOn())
|
||||
goto _port_busy;
|
||||
#endif
|
||||
|
||||
portLock[portId]->lockForWrite();
|
||||
for (int i = 0; i < request->device_id_size(); i++)
|
||||
{
|
||||
quint32 id = request->device_id(i).id();
|
||||
Device *device = devMgr->device(id);
|
||||
|
||||
// If device with same id as in request exists already ==> error!!
|
||||
if (device)
|
||||
continue; //! \todo (LOW): Partial status of RPC
|
||||
|
||||
devMgr->addDevice(id);
|
||||
}
|
||||
portLock[portId]->unlock();
|
||||
|
||||
//! \todo (LOW): fill-in response "Ack"????
|
||||
|
||||
done->Run();
|
||||
return;
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
_port_busy:
|
||||
controller->SetFailed("Port Busy");
|
||||
goto _exit;
|
||||
#endif
|
||||
|
||||
_invalid_port:
|
||||
controller->SetFailed("invalid portid");
|
||||
_exit:
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::deleteDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::Ack* /*response*/,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
DeviceManager *devMgr;
|
||||
int portId;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
portId = request->port_id().id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
goto _invalid_port;
|
||||
|
||||
devMgr = portInfo[portId]->deviceManager();
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
if (portInfo[portId]->isTransmitOn())
|
||||
goto _port_busy;
|
||||
#endif
|
||||
|
||||
portLock[portId]->lockForWrite();
|
||||
for (int i = 0; i < request->device_id_size(); i++)
|
||||
devMgr->deleteDevice(request->device_id(i).id());
|
||||
portLock[portId]->unlock();
|
||||
|
||||
//! \todo (LOW): fill-in response "Ack"????
|
||||
|
||||
done->Run();
|
||||
return;
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
_port_busy:
|
||||
controller->SetFailed("Port Busy");
|
||||
goto _exit;
|
||||
#endif
|
||||
_invalid_port:
|
||||
controller->SetFailed("invalid portid");
|
||||
_exit:
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::modifyDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceConfigList* request,
|
||||
::OstProto::Ack* /*response*/,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
DeviceManager *devMgr;
|
||||
int portId;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
portId = request->port_id().id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
goto _invalid_port;
|
||||
|
||||
devMgr = portInfo[portId]->deviceManager();
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
if (portInfo[portId]->isTransmitOn())
|
||||
goto _port_busy;
|
||||
#endif
|
||||
|
||||
portLock[portId]->lockForWrite();
|
||||
for (int i = 0; i < request->device_size(); i++)
|
||||
devMgr->modifyDevice(&request->device(i));
|
||||
portLock[portId]->unlock();
|
||||
|
||||
// FIXME: check for overlaps between devices?
|
||||
|
||||
//! \todo(LOW): fill-in response "Ack"????
|
||||
|
||||
done->Run();
|
||||
return;
|
||||
|
||||
#if 0 // FIXME: needed?
|
||||
_port_busy:
|
||||
controller->SetFailed("Port Busy");
|
||||
goto _exit;
|
||||
#endif
|
||||
_invalid_port:
|
||||
controller->SetFailed("invalid portid");
|
||||
_exit:
|
||||
done->Run();
|
||||
}
|
||||
|
@ -105,6 +105,27 @@ public:
|
||||
::OstProto::VersionCompatibility* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
// Device and Protocol Emulation
|
||||
virtual void getDeviceIdList(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::PortId* request,
|
||||
::OstProto::DeviceIdList* response,
|
||||
::google::protobuf::Closure* done);
|
||||
virtual void getDeviceConfig(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::DeviceConfigList* response,
|
||||
::google::protobuf::Closure* done);
|
||||
virtual void addDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::Ack* response,
|
||||
::google::protobuf::Closure* done);
|
||||
virtual void deleteDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceIdList* request,
|
||||
::OstProto::Ack* response,
|
||||
::google::protobuf::Closure* done);
|
||||
virtual void modifyDevice(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::DeviceConfigList* request,
|
||||
::OstProto::Ack* response,
|
||||
::google::protobuf::Closure* done);
|
||||
signals:
|
||||
void notification(int notifType, SharedProtobufMessage notifData);
|
||||
|
||||
|
111
server/packetbuffer.cpp
Normal file
111
server/packetbuffer.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "packetbuffer.h"
|
||||
|
||||
// PacketBuffer with full control
|
||||
PacketBuffer::PacketBuffer(int size)
|
||||
{
|
||||
if (size == 0)
|
||||
size = 1600;
|
||||
|
||||
buffer_ = new uchar[size];
|
||||
is_own_buffer_ = true;
|
||||
|
||||
head_ = data_ = tail_ = buffer_;
|
||||
end_ = head_ + size;
|
||||
}
|
||||
|
||||
// PacketBuffer wrapping already existing const buffer
|
||||
PacketBuffer::PacketBuffer(const uchar *buffer, int size)
|
||||
{
|
||||
// FIXME: ugly const_cast hack!!
|
||||
buffer_ = const_cast<uchar*>(buffer);
|
||||
is_own_buffer_ = false;
|
||||
|
||||
head_ = data_ = buffer_;
|
||||
tail_ = end_ = buffer_ + size;
|
||||
}
|
||||
|
||||
PacketBuffer::~PacketBuffer()
|
||||
{
|
||||
if (is_own_buffer_)
|
||||
delete[] buffer_;
|
||||
}
|
||||
|
||||
int PacketBuffer::length()
|
||||
{
|
||||
return tail_ - head_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::head()
|
||||
{
|
||||
return head_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::tail()
|
||||
{
|
||||
return tail_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::end()
|
||||
{
|
||||
return end_;
|
||||
}
|
||||
|
||||
void PacketBuffer::reserve(int len)
|
||||
{
|
||||
// FIXME: add validation
|
||||
data_ += len;
|
||||
tail_ += len;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::pull(int len)
|
||||
{
|
||||
if ((tail_ - data_) < len)
|
||||
return NULL;
|
||||
data_ += len;
|
||||
|
||||
return data_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::push(int len)
|
||||
{
|
||||
if ((data_ - head_) < len)
|
||||
return NULL;
|
||||
data_ -= len;
|
||||
|
||||
return data_;
|
||||
}
|
||||
|
||||
uchar* PacketBuffer::put(int len)
|
||||
{
|
||||
uchar *oldTail = tail_;
|
||||
|
||||
if ((end_ - tail_) < len)
|
||||
return NULL;
|
||||
tail_ += len;
|
||||
|
||||
return oldTail;
|
||||
}
|
51
server/packetbuffer.h
Normal file
51
server/packetbuffer.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _PACKET_BUFFER_H
|
||||
#define _PACKET_BUFFER_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
class PacketBuffer
|
||||
{
|
||||
public:
|
||||
PacketBuffer(int size = 0);
|
||||
PacketBuffer(const uchar *buffer, int size);
|
||||
~PacketBuffer();
|
||||
|
||||
int length();
|
||||
|
||||
uchar* head();
|
||||
uchar* data();
|
||||
uchar* tail();
|
||||
uchar* end();
|
||||
|
||||
void reserve(int len);
|
||||
uchar* pull(int len);
|
||||
uchar* push(int len);
|
||||
uchar* put(int len);
|
||||
|
||||
private:
|
||||
uchar *buffer_;
|
||||
bool is_own_buffer_;
|
||||
uchar *head_, *data_, *tail_, *end_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -19,6 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "pcapport.h"
|
||||
|
||||
#include "devicemanager.h"
|
||||
#include "packetbuffer.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
@ -81,6 +84,7 @@ PcapPort::PcapPort(int id, const char *device)
|
||||
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
|
||||
transmitter_ = new PortTransmitter(device);
|
||||
capturer_ = new PortCapturer(device);
|
||||
receiver_ = new PortReceiver(device, deviceManager_);
|
||||
|
||||
if (!monitorRx_->handle() || !monitorTx_->handle())
|
||||
isUsable_ = false;
|
||||
@ -133,6 +137,7 @@ PcapPort::~PcapPort()
|
||||
if (monitorTx_)
|
||||
monitorTx_->stop();
|
||||
|
||||
delete receiver_;
|
||||
delete capturer_;
|
||||
delete transmitter_;
|
||||
|
||||
@ -167,6 +172,27 @@ void PcapPort::updateNotes()
|
||||
arg(notes).toStdString());
|
||||
}
|
||||
|
||||
void PcapPort::startDeviceEmulation()
|
||||
{
|
||||
receiver_->start();
|
||||
}
|
||||
|
||||
void PcapPort::stopDeviceEmulation()
|
||||
{
|
||||
receiver_->stop();
|
||||
}
|
||||
|
||||
int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
return receiver_->transmitPacket(pktBuf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Monitor
|
||||
* ------------------------------------------------------------------- *
|
||||
*/
|
||||
PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction,
|
||||
AbstractPort::PortStats *stats)
|
||||
{
|
||||
@ -312,6 +338,11 @@ void PcapPort::PortMonitor::stop()
|
||||
pcap_breakloop(handle());
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Transmitter
|
||||
* ------------------------------------------------------------------- *
|
||||
*/
|
||||
PcapPort::PortTransmitter::PortTransmitter(const char *device)
|
||||
{
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
@ -721,6 +752,11 @@ void PcapPort::PortTransmitter::udelay(long usec)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Capturer
|
||||
* ------------------------------------------------------------------- *
|
||||
*/
|
||||
PcapPort::PortCapturer::PortCapturer(const char *device)
|
||||
{
|
||||
device_ = QString::fromAscii(device);
|
||||
@ -855,3 +891,161 @@ QFile* PcapPort::PortCapturer::captureFile()
|
||||
{
|
||||
return &capFile_;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Receiver
|
||||
* ------------------------------------------------------------------- *
|
||||
*/
|
||||
PcapPort::PortReceiver::PortReceiver(const char *device,
|
||||
DeviceManager *deviceManager)
|
||||
{
|
||||
device_ = QString::fromAscii(device);
|
||||
deviceManager_ = deviceManager;
|
||||
stop_ = false;
|
||||
state_ = kNotStarted;
|
||||
handle_ = NULL;
|
||||
}
|
||||
|
||||
PcapPort::PortReceiver::~PortReceiver()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void PcapPort::PortReceiver::run()
|
||||
{
|
||||
int flag = PCAP_OPENFLAG_PROMISCUOUS;
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
struct bpf_program bpf;
|
||||
const int optimize = 1;
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
_retry:
|
||||
// FIXME: use 0 timeout value?
|
||||
handle_ = pcap_open_live(device_.toAscii().constData(), 65535,
|
||||
flag, 1000 /* ms */, errbuf);
|
||||
|
||||
if (handle_ == NULL)
|
||||
{
|
||||
if (flag && QString(errbuf).contains("promiscuous"))
|
||||
{
|
||||
// FIXME: warn user that device emulation will not work
|
||||
qDebug("%s:can't set promiscuous mode, trying non-promisc",
|
||||
device_.toAscii().constData());
|
||||
flag = 0;
|
||||
goto _retry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: warn user that device emulation will not work
|
||||
qDebug("%s: Error opening port %s: %s\n", __FUNCTION__,
|
||||
device_.toAscii().constData(), errbuf);
|
||||
goto _exit;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: hardcoded filter
|
||||
if (pcap_compile(handle_, &bpf, "arp", optimize, 0) < 0)
|
||||
{
|
||||
qWarning("%s: error compiling filter: %s", qPrintable(device_),
|
||||
pcap_geterr(handle_));
|
||||
goto _skip_filter;
|
||||
}
|
||||
|
||||
if (pcap_setfilter(handle_, &bpf) < 0)
|
||||
{
|
||||
qWarning("%s: error setting filter: %s", qPrintable(device_),
|
||||
pcap_geterr(handle_));
|
||||
goto _skip_filter;
|
||||
}
|
||||
|
||||
_skip_filter:
|
||||
state_ = kRunning;
|
||||
while (1)
|
||||
{
|
||||
int ret;
|
||||
struct pcap_pkthdr *hdr;
|
||||
const uchar *data;
|
||||
|
||||
ret = pcap_next_ex(handle_, &hdr, &data);
|
||||
switch (ret)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
PacketBuffer *pktBuf = new PacketBuffer(data, hdr->caplen);
|
||||
|
||||
// XXX: deviceManager should free pktBuf before returning
|
||||
// from this call; if it needs to process the pkt async
|
||||
// it should make a copy as the pktBuf's data buffer is
|
||||
// owned by libpcap which does not guarantee data will
|
||||
// persist across calls to pcap_next_ex()
|
||||
deviceManager_->receivePacket(pktBuf);
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
// timeout: just go back to the loop
|
||||
break;
|
||||
case -1:
|
||||
qWarning("%s: error reading packet (%d): %s",
|
||||
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
|
||||
break;
|
||||
case -2:
|
||||
default:
|
||||
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
|
||||
ret);
|
||||
}
|
||||
|
||||
if (stop_)
|
||||
{
|
||||
qDebug("user requested receiver stop\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
pcap_close(handle_);
|
||||
handle_ = NULL;
|
||||
stop_ = false;
|
||||
|
||||
_exit:
|
||||
state_ = kFinished;
|
||||
}
|
||||
|
||||
void PcapPort::PortReceiver::start()
|
||||
{
|
||||
// FIXME: return error
|
||||
if (state_ == kRunning) {
|
||||
qWarning("Receive start requested but is already running!");
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = kNotStarted;
|
||||
QThread::start();
|
||||
|
||||
while (state_ == kNotStarted)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
|
||||
void PcapPort::PortReceiver::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
else {
|
||||
// FIXME: return error
|
||||
qWarning("Receive stop requested but is not running!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool PcapPort::PortReceiver::isRunning()
|
||||
{
|
||||
return (state_ == kRunning);
|
||||
}
|
||||
|
||||
int PcapPort::PortReceiver::transmitPacket(PacketBuffer *pktBuf)
|
||||
{
|
||||
return pcap_sendpacket(handle_, pktBuf->data(), pktBuf->length());
|
||||
}
|
||||
|
@ -68,6 +68,10 @@ public:
|
||||
virtual bool isCaptureOn() { return capturer_->isRunning(); }
|
||||
virtual QIODevice* captureData() { return capturer_->captureFile(); }
|
||||
|
||||
virtual void startDeviceEmulation();
|
||||
virtual void stopDeviceEmulation();
|
||||
virtual int sendEmulationPacket(PacketBuffer *pktBuf);
|
||||
|
||||
protected:
|
||||
enum Direction
|
||||
{
|
||||
@ -221,6 +225,33 @@ protected:
|
||||
volatile State state_;
|
||||
};
|
||||
|
||||
// FIXME: rename? not just a 'receiver' but also 'transmitter'!
|
||||
class PortReceiver: public QThread
|
||||
{
|
||||
public:
|
||||
PortReceiver(const char *device, DeviceManager *deviceManager);
|
||||
~PortReceiver();
|
||||
void run();
|
||||
void start();
|
||||
void stop();
|
||||
bool isRunning();
|
||||
int transmitPacket(PacketBuffer *pktBuf);
|
||||
|
||||
private:
|
||||
enum State
|
||||
{
|
||||
kNotStarted,
|
||||
kRunning,
|
||||
kFinished
|
||||
};
|
||||
|
||||
QString device_;
|
||||
DeviceManager *deviceManager_;
|
||||
volatile bool stop_;
|
||||
pcap_t *handle_;
|
||||
volatile State state_;
|
||||
};
|
||||
|
||||
PortMonitor *monitorRx_;
|
||||
PortMonitor *monitorTx_;
|
||||
|
||||
@ -229,6 +260,7 @@ protected:
|
||||
private:
|
||||
PortTransmitter *transmitter_;
|
||||
PortCapturer *capturer_;
|
||||
PortReceiver *receiver_;
|
||||
|
||||
static pcap_if_t *deviceList_;
|
||||
};
|
||||
|
297
test/emultest.py
Normal file
297
test/emultest.py
Normal file
@ -0,0 +1,297 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# standard modules
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from fabric.api import run, env, sudo
|
||||
from harness import Test, TestSuite
|
||||
|
||||
sys.path.insert(1, '../binding')
|
||||
from core import ost_pb, emul, DroneProxy
|
||||
from rpc import RpcError
|
||||
from protocols.mac_pb2 import mac
|
||||
from protocols.ip4_pb2 import ip4, Ip4
|
||||
|
||||
use_defaults = False
|
||||
|
||||
# initialize defaults - drone
|
||||
host_name = '127.0.0.1'
|
||||
tx_port_number = -1
|
||||
rx_port_number = -1
|
||||
#FIXME:drone_version = ['0', '0', '0']
|
||||
|
||||
if sys.platform == 'win32':
|
||||
tshark = r'C:\Program Files\Wireshark\tshark.exe'
|
||||
else:
|
||||
tshark = 'tshark'
|
||||
|
||||
# initialize defaults - DUT
|
||||
env.use_shell = False
|
||||
env.user = 'tc'
|
||||
env.password = 'tc'
|
||||
env.host_string = 'localhost:50022'
|
||||
dut_rx_port = 'eth1'
|
||||
dut_tx_port = 'eth2'
|
||||
|
||||
# setup logging
|
||||
log = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# command-line option/arg processing
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] in ('-d', '--use-defaults'):
|
||||
use_defaults = True
|
||||
if sys.argv[1] in ('-h', '--help'):
|
||||
print('%s [OPTION]...' % (sys.argv[0]))
|
||||
print('Options:')
|
||||
print(' -d --use-defaults run using default values')
|
||||
print(' -h --help show this help')
|
||||
sys.exit(0)
|
||||
|
||||
print('')
|
||||
print('This test uses the following topology -')
|
||||
print('')
|
||||
print(' +-------+ +-------+')
|
||||
print(' | |Tx--->---Rx|-+ |')
|
||||
print(' | Drone | | v DUT |')
|
||||
print(' | |Rx---<---Tx|-+ |')
|
||||
print(' +-------+ +-------+')
|
||||
print('')
|
||||
print('Drone has 2 ports connected to DUT. Packets sent on the Tx port')
|
||||
print('are expected to be forwarded by the DUT and received back on the')
|
||||
print('Rx port')
|
||||
print('')
|
||||
|
||||
suite = TestSuite()
|
||||
if not use_defaults:
|
||||
s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name))
|
||||
host_name = s or host_name
|
||||
drone = DroneProxy(host_name)
|
||||
|
||||
try:
|
||||
# ----------------------------------------------------------------- #
|
||||
# Baseline Configuration for subsequent testcases
|
||||
# ----------------------------------------------------------------- #
|
||||
|
||||
# FIXME: get inputs for dut rx/tx ports
|
||||
|
||||
# configure the DUT
|
||||
sudo('sysctl -w net.ipv4.ip_forward=1')
|
||||
sudo('ifconfig ' + dut_rx_port + ' 10.10.1.1 netmask 255.255.255.0')
|
||||
sudo('ifconfig ' + dut_tx_port + ' 10.10.2.1 netmask 255.255.255.0')
|
||||
|
||||
# connect to drone
|
||||
log.info('connecting to drone(%s:%d)'
|
||||
% (drone.hostName(), drone.portNumber()))
|
||||
drone.connect()
|
||||
|
||||
# retreive port id list
|
||||
log.info('retreiving port list')
|
||||
port_id_list = drone.getPortIdList()
|
||||
|
||||
# retreive port config list
|
||||
log.info('retreiving port config for all ports')
|
||||
port_config_list = drone.getPortConfig(port_id_list)
|
||||
|
||||
if len(port_config_list.port) == 0:
|
||||
log.warning('drone has no ports!')
|
||||
sys.exit(1)
|
||||
|
||||
# print port list and find default tx/rx ports
|
||||
print('Port List')
|
||||
print('---------')
|
||||
for port in port_config_list.port:
|
||||
print('%d.%s (%s)' % (port.port_id.id, port.name, port.description))
|
||||
# use a vhost port as default tx/rx port
|
||||
if ('vhost' in port.name or 'sun' in port.description.lower()):
|
||||
if tx_port_number < 0:
|
||||
tx_port_number = port.port_id.id
|
||||
elif rx_port_number < 0:
|
||||
rx_port_number = port.port_id.id
|
||||
|
||||
if not use_defaults:
|
||||
p = raw_input('Tx Port Id [%d]: ' % (tx_port_number))
|
||||
if p:
|
||||
tx_port_number = int(p)
|
||||
|
||||
p = raw_input('Rx Port Id [%d]: ' % (rx_port_number))
|
||||
if p:
|
||||
rx_port_number = int(p)
|
||||
|
||||
if tx_port_number < 0 or rx_port_number < 0:
|
||||
log.warning('invalid tx/rx port')
|
||||
sys.exit(1)
|
||||
|
||||
print('Using port %d as tx port(s)' % tx_port_number)
|
||||
print('Using port %d as rx port(s)' % rx_port_number)
|
||||
|
||||
tx_port = ost_pb.PortIdList()
|
||||
tx_port.port_id.add().id = tx_port_number;
|
||||
|
||||
rx_port = ost_pb.PortIdList()
|
||||
rx_port.port_id.add().id = rx_port_number;
|
||||
|
||||
#---------------------------------------------#
|
||||
# configure emulated device(s) on tx/rx ports #
|
||||
#---------------------------------------------#
|
||||
# delete existing devices, if any, on tx port
|
||||
did_list = drone.getDeviceIdList(tx_port.port_id[0])
|
||||
drone.deleteDevice(did_list)
|
||||
|
||||
# add a emulated device on tx port
|
||||
device_id = ost_pb.DeviceIdList()
|
||||
device_id.port_id.CopyFrom(tx_port.port_id[0])
|
||||
device_id.device_id.add().id = 1
|
||||
log.info('adding tx_device %d' % device_id.device_id[0].id)
|
||||
drone.addDevice(device_id)
|
||||
|
||||
# configure the device
|
||||
device_cfg = ost_pb.DeviceConfigList()
|
||||
device_cfg.port_id.CopyFrom(tx_port.port_id[0])
|
||||
d = device_cfg.device.add()
|
||||
d.device_id.id = device_id.device_id[0].id
|
||||
d.core.name = "Host1"
|
||||
d.Extensions[emul.mac].addr = 0x000102030001
|
||||
ip = d.Extensions[emul.ip4]
|
||||
ip.addr = 0x0a0a0164
|
||||
ip.prefix_length = 24
|
||||
ip.gateway = 0x0a0a0101
|
||||
|
||||
drone.modifyDevice(device_cfg)
|
||||
|
||||
# delete existing devices, if any, on rx port
|
||||
did_list = drone.getDeviceIdList(rx_port.port_id[0])
|
||||
drone.deleteDevice(did_list)
|
||||
|
||||
# add a emulated device on rx port
|
||||
device_id = ost_pb.DeviceIdList()
|
||||
device_id.port_id.CopyFrom(rx_port.port_id[0])
|
||||
device_id.device_id.add().id = 1
|
||||
log.info('adding rx_device %d' % device_id.device_id[0].id)
|
||||
drone.addDevice(device_id)
|
||||
|
||||
# configure the device
|
||||
device_cfg = ost_pb.DeviceConfigList()
|
||||
device_cfg.port_id.CopyFrom(rx_port.port_id[0])
|
||||
d = device_cfg.device.add()
|
||||
d.device_id.id = device_id.device_id[0].id
|
||||
d.core.name = "Host2"
|
||||
d.Extensions[emul.mac].addr = 0x000102030002
|
||||
ip = d.Extensions[emul.ip4]
|
||||
ip.addr = 0x0a0a0264
|
||||
ip.prefix_length = 24
|
||||
ip.gateway = 0x0a0a0201
|
||||
|
||||
drone.modifyDevice(device_cfg)
|
||||
|
||||
#--------------------------------------#
|
||||
# configure traffic stream(s)
|
||||
#--------------------------------------#
|
||||
# delete existing streams, if any, on tx port
|
||||
sid_list = drone.getStreamIdList(tx_port.port_id[0])
|
||||
drone.deleteStream(sid_list)
|
||||
|
||||
# add a stream
|
||||
stream_id = ost_pb.StreamIdList()
|
||||
stream_id.port_id.CopyFrom(tx_port.port_id[0])
|
||||
stream_id.stream_id.add().id = 1
|
||||
log.info('adding tx_stream %d' % stream_id.stream_id[0].id)
|
||||
drone.addStream(stream_id)
|
||||
|
||||
# configure the stream
|
||||
stream_cfg = ost_pb.StreamConfigList()
|
||||
stream_cfg.port_id.CopyFrom(tx_port.port_id[0])
|
||||
s = stream_cfg.stream.add()
|
||||
s.stream_id.id = stream_id.stream_id[0].id
|
||||
s.core.is_enabled = True
|
||||
#s.core.frame_len = 128
|
||||
s.control.packets_per_sec = 20
|
||||
s.control.num_packets = 100
|
||||
|
||||
# setup stream protocols as mac:eth2:ip4:udp:payload
|
||||
p = s.protocol.add()
|
||||
p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber
|
||||
p.Extensions[mac].dst_mac = 0x0800278df2b4 #FIXME: hardcoding
|
||||
p.Extensions[mac].src_mac = 0x00aabbccddee
|
||||
|
||||
p = s.protocol.add()
|
||||
p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
|
||||
|
||||
p = s.protocol.add()
|
||||
p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
|
||||
# reduce typing by creating a shorter reference to p.Extensions[ip4]
|
||||
ip = p.Extensions[ip4]
|
||||
ip.src_ip = 0x0a0a0164
|
||||
ip.dst_ip = 0x0a0a0264
|
||||
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber
|
||||
|
||||
log.info('configuring tx_stream %d' % stream_id.stream_id[0].id)
|
||||
drone.modifyStream(stream_cfg)
|
||||
|
||||
# all test cases will use this stream by modifying it as per its needs
|
||||
|
||||
# clear tx/rx stats
|
||||
log.info('clearing tx/rx stats')
|
||||
drone.clearStats(tx_port)
|
||||
drone.clearStats(rx_port)
|
||||
|
||||
# ----------------------------------------------------------------- #
|
||||
# TESTCASE: FIXME
|
||||
# ----------------------------------------------------------------- #
|
||||
passed = False
|
||||
suite.test_begin('startTransmitDuringTransmitIsNopNotRestart')
|
||||
# clear arp on DUT
|
||||
sudo('arp -d ' + '10.10.1.100', warn_only=True)
|
||||
sudo('arp -d ' + '10.10.2.100', warn_only=True)
|
||||
run('arp -a')
|
||||
drone.startCapture(rx_port)
|
||||
drone.startTransmit(tx_port)
|
||||
try:
|
||||
log.info('waiting for transmit to finish ...')
|
||||
time.sleep(7)
|
||||
drone.stopTransmit(tx_port)
|
||||
drone.stopCapture(rx_port)
|
||||
|
||||
buff = drone.getCaptureBuffer(rx_port.port_id[0])
|
||||
drone.saveCaptureBuffer(buff, 'capture.pcap')
|
||||
log.info('dumping Rx capture buffer')
|
||||
cap_pkts = subprocess.check_output([tshark, '-r', 'capture.pcap'])
|
||||
print(cap_pkts)
|
||||
if '10.10.2.100' in cap_pkts:
|
||||
passed = True
|
||||
os.remove('capture.pcap')
|
||||
except RpcError as e:
|
||||
raise
|
||||
finally:
|
||||
drone.stopTransmit(tx_port)
|
||||
run('arp -a')
|
||||
suite.test_end(passed)
|
||||
|
||||
suite.complete()
|
||||
|
||||
# delete streams
|
||||
log.info('deleting tx_stream %d' % stream_id.stream_id[0].id)
|
||||
drone.deleteStream(stream_id)
|
||||
|
||||
# delete devices
|
||||
did_list = drone.getDeviceIdList(tx_port.port_id[0])
|
||||
drone.deleteDevice(did_list)
|
||||
did_list = drone.getDeviceIdList(rx_port.port_id[0])
|
||||
drone.deleteDevice(did_list)
|
||||
|
||||
# bye for now
|
||||
drone.disconnect()
|
||||
|
||||
except Exception as ex:
|
||||
log.exception(ex)
|
||||
|
||||
finally:
|
||||
suite.report()
|
||||
if not suite.passed:
|
||||
sys.exit(2);
|
Loading…
Reference in New Issue
Block a user