From ab433dc22bf3192220b3d1e7424f556f63388156 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Sep 2015 18:19:52 +0530 Subject: [PATCH 001/121] Feature: Device Emulation - first cut working code --- binding/core.py | 1 + common/emulproto.proto | 66 +++++++++ common/ostproto.pro | 5 +- common/protocol.proto | 40 ++++++ server/abstractport.cpp | 11 +- server/abstractport.h | 9 ++ server/device.cpp | 287 +++++++++++++++++++++++++++++++++++++ server/device.h | 67 +++++++++ server/devicemanager.cpp | 117 +++++++++++++++ server/devicemanager.h | 54 +++++++ server/drone.pro | 3 + server/myservice.cpp | 225 ++++++++++++++++++++++++++++- server/myservice.h | 21 +++ server/packetbuffer.cpp | 111 +++++++++++++++ server/packetbuffer.h | 51 +++++++ server/pcapport.cpp | 194 +++++++++++++++++++++++++ server/pcapport.h | 32 +++++ test/emultest.py | 297 +++++++++++++++++++++++++++++++++++++++ 18 files changed, 1588 insertions(+), 3 deletions(-) create mode 100644 common/emulproto.proto create mode 100644 server/device.cpp create mode 100644 server/device.h create mode 100644 server/devicemanager.cpp create mode 100644 server/devicemanager.h create mode 100644 server/packetbuffer.cpp create mode 100644 server/packetbuffer.h create mode 100644 test/emultest.py diff --git a/binding/core.py b/binding/core.py index b8d47f6..93c542c 100644 --- a/binding/core.py +++ b/binding/core.py @@ -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): diff --git a/common/emulproto.proto b/common/emulproto.proto new file mode 100644 index 0000000..e400270 --- /dev/null +++ b/common/emulproto.proto @@ -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 +*/ + +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; +} + diff --git a/common/ostproto.pro b/common/ostproto.pro index e4467dc..b7873be 100644 --- a/common/ostproto.pro +++ b/common/ostproto.pro @@ -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 \ diff --git a/common/protocol.proto b/common/protocol.proto index deccf07..25c7005 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -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); } diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 21f443e..f5e08f7 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -21,8 +21,9 @@ along with this program. If not, see #include "abstractport.h" -#include "../common/streambase.h" #include "../common/abstractprotocol.h" +#include "../common/streambase.h" +#include "devicemanager.h" #include #include @@ -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()); diff --git a/server/abstractport.h b/server/abstractport.h index 44f0c1d..174437d 100644 --- a/server/abstractport.h +++ b/server/abstractport.h @@ -25,7 +25,9 @@ along with this program. If not, see #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_; diff --git a/server/device.cpp b/server/device.cpp new file mode 100644 index 0000000..2167e4a --- /dev/null +++ b/server/device.cpp @@ -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 +*/ + +#include "device.h" + +#include "../common/emulproto.pb.h" +#include "devicemanager.h" +#include "packetbuffer.h" + +#include + +QHash Device::macHash_; +QHash 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(pktData + offset); + offset += 4; + dstMac = (dstMac << 16) | qFromBigEndian(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(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(pktData + offset); + offset += 2; + if (hwType != 1) // Mac + goto _invalid_exit; + + protoType = qFromBigEndian(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(pktData + offset); + offset += 2; + + srcMac = qFromBigEndian(pktData + offset); + offset += 4; + srcMac = (srcMac << 16) | qFromBigEndian(pktData + offset); + offset += 2; + + srcIp = qFromBigEndian(pktData + offset); + offset += 4; + + tgtMac = qFromBigEndian(pktData + offset); + offset += 4; + tgtMac = (tgtMac << 16) | qFromBigEndian(pktData + offset); + offset += 2; + + tgtIp = qFromBigEndian(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; +} diff --git a/server/device.h b/server/device.h new file mode 100644 index 0000000..fde894c --- /dev/null +++ b/server/device.h @@ -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 +*/ + +#ifndef _DEVICE_H +#define _DEVICE_H + +#include "../common/protocol.pb.h" + +#include + +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 macHash_; + static QHash ip4Hash_; + + DeviceManager *deviceManager_; + OstProto::Device data_; +}; + +#endif + diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp new file mode 100644 index 0000000..f4f7d19 --- /dev/null +++ b/server/devicemanager.cpp @@ -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 +*/ + +#include "devicemanager.h" + +#include "abstractport.h" +#include "device.h" +#include "packetbuffer.h" + +#include + +// 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); +} diff --git a/server/devicemanager.h b/server/devicemanager.h new file mode 100644 index 0000000..5614571 --- /dev/null +++ b/server/devicemanager.h @@ -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 +*/ + +#ifndef _DEVICE_MANAGER_H +#define _DEVICE_MANAGER_H + +#include "../common/protocol.pb.h" + +#include +#include + +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 deviceList_; +}; + +#endif + diff --git a/server/drone.pro b/server/drone.pro index 5022a5b..75572be 100644 --- a/server/drone.pro +++ b/server/drone.pro @@ -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.* diff --git a/server/myservice.cpp b/server/myservice.cpp index 0a4f7ca..0ef74da 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -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 #include "../common/streambase.h" #include "../rpc/pbrpccontroller.h" +#include "device.h" +#include "devicemanager.h" #include "portmanager.h" #include @@ -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(); +} diff --git a/server/myservice.h b/server/myservice.h index ea0cc78..1ca5d24 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -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); diff --git a/server/packetbuffer.cpp b/server/packetbuffer.cpp new file mode 100644 index 0000000..d049667 --- /dev/null +++ b/server/packetbuffer.cpp @@ -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 +*/ + +#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(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; +} diff --git a/server/packetbuffer.h b/server/packetbuffer.h new file mode 100644 index 0000000..5449fee --- /dev/null +++ b/server/packetbuffer.h @@ -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 +*/ + +#ifndef _PACKET_BUFFER_H +#define _PACKET_BUFFER_H + +#include + +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 + diff --git a/server/pcapport.cpp b/server/pcapport.cpp index c46d5ad..81c6c50 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -19,6 +19,9 @@ along with this program. If not, see #include "pcapport.h" +#include "devicemanager.h" +#include "packetbuffer.h" + #include #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()); +} diff --git a/server/pcapport.h b/server/pcapport.h index b25ab5c..5ed0254 100644 --- a/server/pcapport.h +++ b/server/pcapport.h @@ -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_; }; diff --git a/test/emultest.py b/test/emultest.py new file mode 100644 index 0000000..b1e4b75 --- /dev/null +++ b/test/emultest.py @@ -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); From 246bc95c74841d46cac10b4a862b4b5d3f0ba1f3 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 20 Sep 2015 17:49:15 +0530 Subject: [PATCH 002/121] Feature (contd.): Device Emulation - user now configures a device group instead of a single device for easier creation of multiple devices; refactored and redistributed functionality between DeviceManager and Device; external functionality wise same as last commit; added initial code for vlans but not tested --- common/emulproto.proto | 80 +++++++---- common/protocol.proto | 33 ++--- server/device.cpp | 258 ++++++++++++++++----------------- server/device.h | 40 +++--- server/devicemanager.cpp | 298 ++++++++++++++++++++++++++++++--------- server/devicemanager.h | 32 +++-- server/drone.pro | 2 +- server/myservice.cpp | 67 ++++----- server/myservice.h | 29 ++-- test/emultest.py | 82 +++++------ 10 files changed, 561 insertions(+), 360 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index e400270..98b330b 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -21,46 +21,74 @@ import "protocol.proto"; package OstEmul; -message MacEmulation { - optional uint64 addr = 10; -} - -extend OstProto.Device { - optional MacEmulation mac = 200; -} - +// ====== +// VLAN +// ====== message VlanEmulation { + enum Mode { + kNoRepeat = 0; + kRepeat = 1; + } message Vlan { optional uint32 tpid = 1 [default = 0x8100]; - optional uint32 vlan_tag = 2; // includes prio, cfi and vlanid + + // includes prio, cfi and vlanid + optional uint32 vlan_tag = 2 [default = 100]; + + optional uint32 count = 10 [default = 1]; + optional uint32 step = 11 [default = 1]; + optional Mode mode = 12 [default = kRepeat]; } - // FIXME: rename as just stack? - repeated Vlan vlan_stack = 11; // outer to inner + + repeated Vlan stack = 1; // outer to inner } -extend OstProto.Device { - optional VlanEmulation vlan = 201; +extend OstProto.DeviceGroup { + optional VlanEmulation vlan = 200; +} + +// ======== +// Device +// ======== +message MacEmulation { + optional uint64 address = 1; // FIXME: default value + + optional uint64 step = 10 [default = 1]; } message Ip4Emulation { - optional uint32 addr = 2; - optional uint32 prefix_length = 3; - optional uint32 gateway = 4; // FIXME: rename to default_gateway? -} + optional uint32 address = 1; + optional uint32 prefix_length = 2; + optional uint32 default_gateway = 3; -extend OstProto.Device { - optional Ip4Emulation ip4 = 300; + optional uint64 step = 10 [default = 1]; + // FIXME: step for gateway? } 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; + optional uint64 address_hi = 1; + optional uint64 address_lo = 2; + optional uint32 prefix_length = 3; + optional uint64 default_gateway_hi = 4; + optional uint64 default_gateway_lo = 5; + + optional uint64 step_hi = 10 [default = 0]; + optional uint64 step_lo = 11 [default = 1]; } -extend OstProto.Device { - optional Ip6Emulation ip6 = 301; +message Device { + enum Mode { + kNoRepeat = 0; + kRepeat = 1; + } + optional MacEmulation mac = 1; + optional Ip4Emulation ip4 = 2; + optional Ip6Emulation ip6 = 3; + + optional uint32 count = 10 [default = 1]; + optional Mode mode = 11 [default = kRepeat]; // FIXME: per proto mode? } +extend OstProto.DeviceGroup { + optional Device device = 201; +} diff --git a/common/protocol.proto b/common/protocol.proto index 25c7005..a96d613 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -278,32 +278,33 @@ message Notification { * 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: What will be the contents of a default device created by addDeviceGroup()? * FIXME: decide default values for device and protoEmulations + * FIXME: rename 'DeviceGroup'? */ -message DeviceId { +message DeviceGroupId { required uint32 id = 1; } -message DeviceCore { +message DeviceGroupCore { optional string name = 1; } -message DeviceIdList { +message DeviceGroupIdList { required PortId port_id = 1; - repeated DeviceId device_id = 2; + repeated DeviceGroupId device_group_id = 2; } -message Device { - required DeviceId device_id = 1; - optional DeviceCore core = 2; +message DeviceGroup { + required DeviceGroupId device_group_id = 1; + optional DeviceGroupCore core = 2; extensions 200 to 500; // For use by Protocol Emulations } -message DeviceConfigList { +message DeviceGroupConfigList { required PortId port_id = 1; - repeated Device device = 2; + repeated DeviceGroup device_group = 2; } service OstService { @@ -329,11 +330,11 @@ service OstService { 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); + // Device/Protocol Emulation + rpc getDeviceGroupIdList(PortId) returns (DeviceGroupIdList); + rpc getDeviceGroupConfig(DeviceGroupIdList) returns (DeviceGroupConfigList); + rpc addDeviceGroup(DeviceGroupIdList) returns (Ack); + rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack); + rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack); } diff --git a/server/device.cpp b/server/device.cpp index 2167e4a..a487074 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -23,78 +23,99 @@ along with this program. If not, see #include "devicemanager.h" #include "packetbuffer.h" +#include #include -QHash Device::macHash_; -QHash Device::ip4Hash_; +const int kBaseHex = 16; +const int kMaxVlan = 4; -Device::Device(quint32 id, DeviceManager *deviceManager) +/* + * NOTE: + * 1. Device Key is (VLANS + MAC) - is assumed to be unique for a device + * 2. Device clients/users (viz. DeviceManager) should take care when + * setting params that change the key, if the key is used elsewhere + * (e.g. in a hash) + */ + +Device::Device(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); + for (int i = 0; i < kMaxVlan; i++) + vlan_[i] = 0; + numVlanTags_ = 0; + mac_ = 0; + ip4_ = 0; + ip4PrefixLength_ = 0; + + clearKey(); } -Device::~Device() +void Device::setVlan(int index, quint16 vlan) { - macHash_.remove(myMac()); - ip4Hash_.remove(myIp4()); -} + int ofs; -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); + if ((index < 0) || (index >= kMaxVlan)) { + qWarning("%s: vlan index %d out of range (0 - %d)", __FUNCTION__, + index, kMaxVlan - 1); + return; } - // Update Ip4Hash if ip4 has changed - if (newIp4 != oldIp4) { - ip4Hash_.remove(oldIp4); - ip4Hash_.insert(newIp4, this); - } + vlan_[index] = vlan; + + ofs = index * sizeof(quint16); + key_[ofs] = vlan >> 8; + key_[ofs+1] = vlan & 0xff; + + if (index >= numVlanTags_) + numVlanTags_ = index + 1; } -void Device::protoDataCopyInto(OstProto::Device &device) const +void Device::setMac(quint64 mac) { - device.CopyFrom(data_); + int ofs = kMaxVlan * sizeof(quint16); + + mac_ = mac; + memcpy(key_.data() + ofs, (char*)&mac, sizeof(mac)); +} + +void Device::setIp4(quint32 address, int prefixLength) +{ + ip4_ = address; + ip4PrefixLength_ = prefixLength; +} + +QString Device::config() +{ + return QString("") + .arg(vlan_[0]).arg(vlan_[1]).arg(vlan_[2]).arg(vlan_[3]) + .arg(mac_, 12, kBaseHex, QChar('0')) + .arg(QHostAddress(ip4_).toString()) + .arg(ip4PrefixLength_); +} + +DeviceKey Device::key() +{ + return key_; +} + +void Device::clearKey() +{ + key_.fill(0, kMaxVlan * sizeof(quint16) + sizeof(quint64)); } int Device::encapSize() { - int size = 14; // ethernet header size - - if (data_.HasExtension(OstEmul::vlan)) - size += 4 * data_.GetExtension(OstEmul::vlan).vlan_stack_size(); + // ethernet header + vlans + int size = 14 + 4*numVlanTags_; return size; } void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) { - quint64 srcMac = myMac(); + int ofs; + quint64 srcMac = mac_; uchar *p = pktBuf->push(encapSize()); if (!p) { @@ -107,90 +128,52 @@ void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) *(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); + ofs = 12; + for (int i = 0; i < numVlanTags_; i++) { + *(quint16*)(p + ofs) = qToBigEndian(quint32((0x8100 << 16)|vlan_[i])); + ofs += 4; + } + *(quint16*)(p + ofs) = qToBigEndian(type); + ofs += 2; + + Q_ASSERT(ofs == encapSize()); _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); } +// We expect pktBuf to point to EthType on entry void Device::receivePacket(PacketBuffer *pktBuf) { - uchar *pktData = pktBuf->data(); - int offset = 0; - Device *device; - const quint64 bcastMac = 0xffffffffffffULL; - quint64 dstMac; - quint16 ethType; + quint16 ethType = qFromBigEndian(pktBuf->data()); + pktBuf->pull(2); - // We assume pkt is ethernet - // TODO: extend for other link layer types + qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); - // Extract dstMac - dstMac = qFromBigEndian(pktData + offset); - offset += 4; - dstMac = (dstMac << 16) | qFromBigEndian(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(pktData + offset); - offset += 2; - qDebug("%s: ethType 0x%x", __FUNCTION__, ethType); - switch(ethType) { case 0x0806: // ARP - pktBuf->pull(offset); - receiveArp(device, pktBuf); + receiveArp(pktBuf); break; - case 0x8100: // VLAN case 0x0800: // IPv4 case 0x86dd: // IPv6 default: break; } - -_exit: - return; } -void Device::receiveArp(Device *device, PacketBuffer *pktBuf) +// +// Private Methods +// +void Device::receiveArp(PacketBuffer *pktBuf) { + PacketBuffer *rspPkt; uchar *pktData = pktBuf->data(); int offset = 0; quint16 hwType, protoType; @@ -199,6 +182,15 @@ void Device::receiveArp(Device *device, PacketBuffer *pktBuf) quint64 srcMac, tgtMac; quint32 srcIp, tgtIp; + // Extract tgtIp first to check quickly if this packet is for us or not + tgtIp = qFromBigEndian(pktData + 24); + if (tgtIp != ip4_) { + qDebug("tgtIp %s is not me %s", + qPrintable(QHostAddress(tgtIp).toString()), + qPrintable(QHostAddress(ip4_).toString())); + return; + } + // Extract annd verify ARP packet contents hwType = qFromBigEndian(pktData + offset); offset += 2; @@ -236,43 +228,33 @@ void Device::receiveArp(Device *device, PacketBuffer *pktBuf) tgtMac = (tgtMac << 16) | qFromBigEndian(pktData + offset); offset += 2; - tgtIp = qFromBigEndian(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); + case 1: // ARP Request + rspPkt = new PacketBuffer; + rspPkt->reserve(encapSize()); + pktData = rspPkt->put(28); + if (pktData) { + // HTYP, PTYP + *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800)); + // HLEN, PLEN, OPER + *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040002)); + // Source H/W Addr, Proto Addr + *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16)); + *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff)); + *(quint32*)(pktData+14) = qToBigEndian(ip4_); + // Target H/W Addr, Proto Addr + *(quint32*)(pktData+18) = qToBigEndian(quint32(srcMac >> 16)); + *(quint16*)(pktData+22) = qToBigEndian(quint16(srcMac & 0xffff)); + *(quint32*)(pktData+24) = qToBigEndian(srcIp); } + + encap(rspPkt, srcMac, 0x0806); + transmitPacket(rspPkt); + + qDebug("Sent ARP Reply for srcIp/tgtIp=%s/%s", + qPrintable(QHostAddress(srcIp).toString()), + qPrintable(QHostAddress(tgtIp).toString())); break; case 2: // ARP Response default: diff --git a/server/device.h b/server/device.h index fde894c..1ffc6e9 100644 --- a/server/device.h +++ b/server/device.h @@ -22,45 +22,45 @@ along with this program. If not, see #include "../common/protocol.pb.h" -#include +#include class DeviceManager; class PacketBuffer; +typedef QByteArray DeviceKey; + class Device { public: - Device(quint32 id, DeviceManager *deviceManager); - ~Device(); + Device(DeviceManager *deviceManager); - quint32 id(); + void setVlan(int index, quint16 vlan); + void setMac(quint64 mac); + void setIp4(quint32 address, int prefixLength); + QString config(); - void protoDataCopyFrom(const OstProto::Device &device); - void protoDataCopyInto(OstProto::Device &device) const; + DeviceKey key(); + void clearKey(); 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 receivePacket(PacketBuffer *pktBuf); 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(); + void receiveArp(PacketBuffer *pktBuf); private: // data - // Class data - static QHash macHash_; - static QHash ip4Hash_; - DeviceManager *deviceManager_; - OstProto::Device data_; + + int numVlanTags_; + quint16 vlan_[4]; // FIXME: vlan tpid + quint64 mac_; + quint32 ip4_; + int ip4PrefixLength_; + + DeviceKey key_; }; #endif diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index f4f7d19..87cb40c 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -23,9 +23,13 @@ along with this program. If not, see #include "device.h" #include "packetbuffer.h" +#include "../common/emulproto.pb.h" + #include -// FIXME: add lock to protect deviceList_ operations? +const quint64 kBcastMac = 0xffffffffffffULL; + +// FIXME: add lock to protect deviceGroupList_ operations? DeviceManager::DeviceManager(AbstractPort *parent) { @@ -36,6 +40,90 @@ 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() @@ -43,75 +131,155 @@ 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); + 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 + + // FIXME: validate before extracting if the offset is within pktLen + + // 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 %llx", 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 0x%x", __FUNCTION__, idx, vlan); + goto _eth_type; + } + + pktBuf->pull(offset); + + if (dstMac == kBcastMac) { + QList list = bcastList_.values(dk.key()); + 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::enumerateDevices( + const OstProto::DeviceGroup *deviceGroup, + Operation oper) +{ + Device dk(this); + OstEmul::VlanEmulation pbVlan = deviceGroup->GetExtension(OstEmul::vlan); + OstEmul::Device pbDevice = deviceGroup->GetExtension(OstEmul::device); + int numTags = pbVlan.stack_size(); + int vlanCount = 1; + + 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(); + + switch (vlan.mode()) { + case OstEmul::VlanEmulation::kNoRepeat: + /* Do nothing */ + break; + case OstEmul::VlanEmulation::kRepeat: + default: + vlanAdd %= vlan.count(); + break; + } + + dk.setVlan(j, vlan.vlan_tag() + vlanAdd); + } + + for (uint k = 0; k < pbDevice.count(); k++) { + Device *device; + quint64 macAdd = i*k*pbDevice.mac().step(); + quint32 ip4Add = i*k*pbDevice.ip4().step(); + + switch (pbDevice.mode()) { + case OstEmul::Device::kNoRepeat: + /* Do Nothing*/ + break; + case OstEmul::Device::kRepeat: + default: + macAdd %= pbDevice.count(); + ip4Add %= pbDevice.count(); + break; + } + + dk.setMac(pbDevice.mac().address() + macAdd); + dk.setIp4(pbDevice.ip4().address() + ip4Add, + pbDevice.ip4().prefix_length()); + + // 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 +} diff --git a/server/devicemanager.h b/server/devicemanager.h index 5614571..1eb4093 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -20,14 +20,17 @@ along with this program. If not, see #ifndef _DEVICE_MANAGER_H #define _DEVICE_MANAGER_H -#include "../common/protocol.pb.h" +#include "device.h" #include +#include #include class AbstractPort; -class Device; class PacketBuffer; +namespace OstProto { + class DeviceGroup; +}; class DeviceManager { @@ -35,19 +38,30 @@ public: DeviceManager(AbstractPort *parent = 0); ~DeviceManager(); - int deviceCount(); - Device* deviceAtIndex(int index); - Device* device(uint deviceId); + int deviceGroupCount(); + const OstProto::DeviceGroup* deviceGroupAtIndex(int index); + const OstProto::DeviceGroup* deviceGroup(uint deviceGroupId); - bool addDevice(uint deviceId); - bool deleteDevice(uint deviceId); - bool modifyDevice(const OstProto::Device *device); + bool addDeviceGroup(uint deviceGroupId); + bool deleteDeviceGroup(uint deviceGroupId); + bool modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup); + + int deviceCount(); void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); + private: + enum Operation { kAdd, kDelete }; + + void enumerateDevices( + const OstProto::DeviceGroup *deviceGroup, + Operation oper); + AbstractPort *port_; - QHash deviceList_; + QHash deviceGroupList_; + QHash deviceList_; + QMultiHash bcastList_; }; #endif diff --git a/server/drone.pro b/server/drone.pro index 75572be..9ba5046 100644 --- a/server/drone.pro +++ b/server/drone.pro @@ -33,8 +33,8 @@ LIBS += -lprotobuf HEADERS += drone.h \ myservice.h SOURCES += \ - device.cpp \ devicemanager.cpp \ + device.cpp \ drone_main.cpp \ drone.cpp \ portmanager.cpp \ diff --git a/server/myservice.cpp b/server/myservice.cpp index 0ef74da..6c79afe 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -602,9 +602,10 @@ _invalid_version: * streams/ports and devices independent? * =================================================================== */ -void MyService::getDeviceIdList(::google::protobuf::RpcController* controller, +void MyService::getDeviceGroupIdList( + ::google::protobuf::RpcController* controller, const ::OstProto::PortId* request, - ::OstProto::DeviceIdList* response, + ::OstProto::DeviceGroupIdList* response, ::google::protobuf::Closure* done) { DeviceManager *devMgr; @@ -620,12 +621,12 @@ void MyService::getDeviceIdList(::google::protobuf::RpcController* controller, response->mutable_port_id()->set_id(portId); portLock[portId]->lockForRead(); - for (int i = 0; i < devMgr->deviceCount(); i++) + for (int i = 0; i < devMgr->deviceGroupCount(); i++) { - OstProto::DeviceId *d; + OstProto::DeviceGroupId *dgid; - d = response->add_device_id(); - d->set_id(devMgr->deviceAtIndex(i)->id()); + dgid = response->add_device_group_id(); + dgid->CopyFrom(devMgr->deviceGroupAtIndex(i)->device_group_id()); } portLock[portId]->unlock(); @@ -637,9 +638,10 @@ _invalid_port: done->Run(); } -void MyService::getDeviceConfig(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, - ::OstProto::DeviceConfigList* response, +void MyService::getDeviceGroupConfig( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, + ::OstProto::DeviceGroupConfigList* response, ::google::protobuf::Closure* done) { DeviceManager *devMgr; @@ -655,17 +657,15 @@ void MyService::getDeviceConfig(::google::protobuf::RpcController* controller, response->mutable_port_id()->set_id(portId); portLock[portId]->lockForRead(); - for (int i = 0; i < request->device_id_size(); i++) + for (int i = 0; i < request->device_group_id_size(); i++) { - Device *device; - OstProto::Device *d; + const OstProto::DeviceGroup *dg; - device = devMgr->device(request->device_id(i).id()); - if (!device) + dg = devMgr->deviceGroup(request->device_group_id(i).id()); + if (!dg) continue; //! \todo(LOW): Partial status of RPC - d = response->add_device(); - device->protoDataCopyInto(*d); + response->add_device_group()->CopyFrom(*dg); } portLock[portId]->unlock(); @@ -677,8 +677,9 @@ _invalid_port: done->Run(); } -void MyService::addDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, +void MyService::addDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, ::OstProto::Ack* /*response*/, ::google::protobuf::Closure* done) { @@ -699,16 +700,16 @@ void MyService::addDevice(::google::protobuf::RpcController* controller, #endif portLock[portId]->lockForWrite(); - for (int i = 0; i < request->device_id_size(); i++) + for (int i = 0; i < request->device_group_id_size(); i++) { - quint32 id = request->device_id(i).id(); - Device *device = devMgr->device(id); + quint32 id = request->device_group_id(i).id(); + const OstProto::DeviceGroup *dg = devMgr->deviceGroup(id); - // If device with same id as in request exists already ==> error!! - if (device) + // If device group with same id as in request exists already ==> error! + if (dg) continue; //! \todo (LOW): Partial status of RPC - devMgr->addDevice(id); + devMgr->addDeviceGroup(id); } portLock[portId]->unlock(); @@ -729,8 +730,9 @@ _exit: done->Run(); } -void MyService::deleteDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, +void MyService::deleteDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, ::OstProto::Ack* /*response*/, ::google::protobuf::Closure* done) { @@ -751,8 +753,8 @@ void MyService::deleteDevice(::google::protobuf::RpcController* controller, #endif portLock[portId]->lockForWrite(); - for (int i = 0; i < request->device_id_size(); i++) - devMgr->deleteDevice(request->device_id(i).id()); + for (int i = 0; i < request->device_group_id_size(); i++) + devMgr->deleteDeviceGroup(request->device_group_id(i).id()); portLock[portId]->unlock(); //! \todo (LOW): fill-in response "Ack"???? @@ -771,8 +773,9 @@ _exit: done->Run(); } -void MyService::modifyDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceConfigList* request, +void MyService::modifyDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupConfigList* request, ::OstProto::Ack* /*response*/, ::google::protobuf::Closure* done) { @@ -793,8 +796,8 @@ void MyService::modifyDevice(::google::protobuf::RpcController* controller, #endif portLock[portId]->lockForWrite(); - for (int i = 0; i < request->device_size(); i++) - devMgr->modifyDevice(&request->device(i)); + for (int i = 0; i < request->device_group_size(); i++) + devMgr->modifyDeviceGroup(&request->device_group(i)); portLock[portId]->unlock(); // FIXME: check for overlaps between devices? diff --git a/server/myservice.h b/server/myservice.h index 1ca5d24..c1fe69d 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -105,25 +105,30 @@ public: ::OstProto::VersionCompatibility* response, ::google::protobuf::Closure* done); - // Device and Protocol Emulation - virtual void getDeviceIdList(::google::protobuf::RpcController* controller, + // DeviceGroup and Protocol Emulation + virtual void getDeviceGroupIdList( + ::google::protobuf::RpcController* controller, const ::OstProto::PortId* request, - ::OstProto::DeviceIdList* response, + ::OstProto::DeviceGroupIdList* response, ::google::protobuf::Closure* done); - virtual void getDeviceConfig(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, - ::OstProto::DeviceConfigList* response, + virtual void getDeviceGroupConfig( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, + ::OstProto::DeviceGroupConfigList* response, ::google::protobuf::Closure* done); - virtual void addDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, + virtual void addDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, ::OstProto::Ack* response, ::google::protobuf::Closure* done); - virtual void deleteDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceIdList* request, + virtual void deleteDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupIdList* request, ::OstProto::Ack* response, ::google::protobuf::Closure* done); - virtual void modifyDevice(::google::protobuf::RpcController* controller, - const ::OstProto::DeviceConfigList* request, + virtual void modifyDeviceGroup( + ::google::protobuf::RpcController* controller, + const ::OstProto::DeviceGroupConfigList* request, ::OstProto::Ack* response, ::google::protobuf::Closure* done); signals: diff --git a/test/emultest.py b/test/emultest.py index b1e4b75..20275ed 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -139,54 +139,54 @@ try: # 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) + dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) + drone.deleteDeviceGroup(dgid_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) + dgid_list = ost_pb.DeviceGroupIdList() + dgid_list.port_id.CopyFrom(tx_port.port_id[0]) + dgid_list.device_group_id.add().id = 1 + log.info('adding tx device_group %d' % dgid_list.device_group_id[0].id) + drone.addDeviceGroup(dgid_list) # 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 + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.device_group_id[0].id + dg.core.name = "Host1" + d = dg.Extensions[emul.device] + d.mac.address = 0x000102030001 + d.ip4.address = 0x0a0a0164 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0101 - drone.modifyDevice(device_cfg) + drone.modifyDeviceGroup(devgrp_cfg) # delete existing devices, if any, on rx port - did_list = drone.getDeviceIdList(rx_port.port_id[0]) - drone.deleteDevice(did_list) + dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) + drone.deleteDeviceGroup(dgid_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) + dgid_list = ost_pb.DeviceGroupIdList() + dgid_list.port_id.CopyFrom(rx_port.port_id[0]) + dgid_list.device_group_id.add().id = 1 + log.info('adding rx device_group %d' % dgid_list.device_group_id[0].id) + drone.addDeviceGroup(dgid_list) # 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 + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.device_group_id[0].id + dg.core.name = "Host1" + d = dg.Extensions[emul.device] + d.mac.address = 0x000102030002 + d.ip4.address = 0x0a0a0264 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0201 - drone.modifyDevice(device_cfg) + drone.modifyDeviceGroup(devgrp_cfg) #--------------------------------------# # configure traffic stream(s) @@ -210,7 +210,7 @@ try: s.core.is_enabled = True #s.core.frame_len = 128 s.control.packets_per_sec = 20 - s.control.num_packets = 100 + s.control.num_packets = 10 # setup stream protocols as mac:eth2:ip4:udp:payload p = s.protocol.add() @@ -280,10 +280,10 @@ try: 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) + dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) + drone.deleteDeviceGroup(dgid_list) + dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) + drone.deleteDeviceGroup(dgid_list) # bye for now drone.disconnect() From 0c98e30a93d895fe7d19f2c6cd6810180a4f5d33 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 3 Oct 2015 13:18:44 +0530 Subject: [PATCH 003/121] Feature (contd.): Device Emulation - added test cases for multiple ip4 devices with and without VLANs; fixed bugs discovered via these cases --- server/device.cpp | 4 +- server/devicemanager.cpp | 21 +- server/pcapport.cpp | 3 +- test/emultest.py | 477 ++++++++++++++++++++++++++++++--------- test/harness.py | 6 + 5 files changed, 397 insertions(+), 114 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index a487074..3381e82 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -130,7 +130,7 @@ void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) *(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff)); ofs = 12; for (int i = 0; i < numVlanTags_; i++) { - *(quint16*)(p + ofs) = qToBigEndian(quint32((0x8100 << 16)|vlan_[i])); + *(quint32*)(p + ofs) = qToBigEndian(quint32((0x8100 << 16)|vlan_[i])); ofs += 4; } *(quint16*)(p + ofs) = qToBigEndian(type); @@ -166,6 +166,8 @@ void Device::receivePacket(PacketBuffer *pktBuf) default: break; } + // FIXME: temporary hack till DeviceManager clones pbufs + pktBuf->push(2); } // diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 87cb40c..39681d7 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -169,7 +169,7 @@ _eth_type: vlan = qFromBigEndian(pktData + offset); dk.setVlan(idx++, vlan); offset += 2; - qDebug("%s: idx: %d vlan 0x%x", __FUNCTION__, idx, vlan); + qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan); goto _eth_type; } @@ -177,6 +177,11 @@ _eth_type: 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; @@ -228,7 +233,7 @@ void DeviceManager::enumerateDevices( break; case OstEmul::VlanEmulation::kRepeat: default: - vlanAdd %= vlan.count(); + vlanAdd %= vlan.step() * vlan.count(); break; } @@ -237,8 +242,8 @@ void DeviceManager::enumerateDevices( for (uint k = 0; k < pbDevice.count(); k++) { Device *device; - quint64 macAdd = i*k*pbDevice.mac().step(); - quint32 ip4Add = i*k*pbDevice.ip4().step(); + quint64 macAdd = (i*pbDevice.count()+k)*pbDevice.mac().step(); + quint32 ip4Add = (i*pbDevice.count()+k)*pbDevice.ip4().step(); switch (pbDevice.mode()) { case OstEmul::Device::kNoRepeat: @@ -246,8 +251,8 @@ void DeviceManager::enumerateDevices( break; case OstEmul::Device::kRepeat: default: - macAdd %= pbDevice.count(); - ip4Add %= pbDevice.count(); + macAdd %= pbDevice.mac().step() * pbDevice.count(); + ip4Add %= pbDevice.ip4().step() * pbDevice.count(); break; } @@ -265,12 +270,12 @@ void DeviceManager::enumerateDevices( dk.setMac(kBcastMac); bcastList_.insert(dk.key(), device); - qDebug("enumerate (add): %s", qPrintable(device->config())); + qDebug("enumerate(add): %s", qPrintable(device->config())); break; case kDelete: device = deviceList_.take(dk.key()); - qDebug("enumerate (del): %s", qPrintable(device->config())); + qDebug("enumerate(del): %s", qPrintable(device->config())); delete device; dk.setMac(kBcastMac); diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 81c6c50..a7078e9 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -918,6 +918,7 @@ void PcapPort::PortReceiver::run() int flag = PCAP_OPENFLAG_PROMISCUOUS; char errbuf[PCAP_ERRBUF_SIZE] = ""; struct bpf_program bpf; + const char *capture_filter = "arp or (vlan and arp)"; const int optimize = 1; qDebug("In %s", __PRETTY_FUNCTION__); @@ -947,7 +948,7 @@ _retry: } // FIXME: hardcoded filter - if (pcap_compile(handle_, &bpf, "arp", optimize, 0) < 0) + if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0) { qWarning("%s: error compiling filter: %s", qPrintable(device_), pcap_geterr(handle_)); diff --git a/test/emultest.py b/test/emultest.py index 20275ed..7039dff 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -3,18 +3,20 @@ # standard modules import logging import os +import re import subprocess import sys import time from fabric.api import run, env, sudo -from harness import Test, TestSuite +from harness import Test, TestSuite, TestPreRequisiteError 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 +from protocols.vlan_pb2 import vlan use_defaults = False @@ -22,7 +24,6 @@ use_defaults = False 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' @@ -70,19 +71,23 @@ 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 + # NOTES + # * All test cases will emulate devices on both rx and tx ports + # * Each test case will create traffic streams corresponding to + # the devices to check # ----------------------------------------------------------------- # + # FIXME: get inputs for dut rx/tx ports - # configure the DUT + # Enable IP forwarding on the DUT (aka make it a router) 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)' @@ -135,62 +140,38 @@ try: rx_port = ost_pb.PortIdList() rx_port.port_id.add().id = rx_port_number; - #---------------------------------------------# - # configure emulated device(s) on tx/rx ports # - #---------------------------------------------# + # ----------------------------------------------------------------- # + # create emulated device(s) on tx/rx ports - each test case will + # modify and reuse these devices as per its needs + # ----------------------------------------------------------------- # + # delete existing devices, if any, on tx port - dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) - drone.deleteDeviceGroup(dgid_list) + tx_dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) + drone.deleteDeviceGroup(tx_dgid_list) # add a emulated device on tx port - dgid_list = ost_pb.DeviceGroupIdList() - dgid_list.port_id.CopyFrom(tx_port.port_id[0]) - dgid_list.device_group_id.add().id = 1 - log.info('adding tx device_group %d' % dgid_list.device_group_id[0].id) - drone.addDeviceGroup(dgid_list) - - # configure the device - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = dgid_list.device_group_id[0].id - dg.core.name = "Host1" - d = dg.Extensions[emul.device] - d.mac.address = 0x000102030001 - d.ip4.address = 0x0a0a0164 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0101 - - drone.modifyDeviceGroup(devgrp_cfg) + tx_dgid_list = ost_pb.DeviceGroupIdList() + tx_dgid_list.port_id.CopyFrom(tx_port.port_id[0]) + tx_dgid_list.device_group_id.add().id = 1 + log.info('adding tx device_group %d' % tx_dgid_list.device_group_id[0].id) + drone.addDeviceGroup(tx_dgid_list) # delete existing devices, if any, on rx port - dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) - drone.deleteDeviceGroup(dgid_list) + rx_dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) + drone.deleteDeviceGroup(rx_dgid_list) # add a emulated device on rx port - dgid_list = ost_pb.DeviceGroupIdList() - dgid_list.port_id.CopyFrom(rx_port.port_id[0]) - dgid_list.device_group_id.add().id = 1 - log.info('adding rx device_group %d' % dgid_list.device_group_id[0].id) - drone.addDeviceGroup(dgid_list) + rx_dgid_list = ost_pb.DeviceGroupIdList() + rx_dgid_list.port_id.CopyFrom(rx_port.port_id[0]) + rx_dgid_list.device_group_id.add().id = 1 + log.info('adding rx device_group %d' % rx_dgid_list.device_group_id[0].id) + drone.addDeviceGroup(rx_dgid_list) - # configure the device - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = dgid_list.device_group_id[0].id - dg.core.name = "Host1" - d = dg.Extensions[emul.device] - d.mac.address = 0x000102030002 - d.ip4.address = 0x0a0a0264 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0201 + # ----------------------------------------------------------------- # + # create stream on tx port - each test case will modify and reuse + # this stream as per its needs + # ----------------------------------------------------------------- # - drone.modifyDeviceGroup(devgrp_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) @@ -202,57 +183,109 @@ try: 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 = 10 - - # 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) + # ================================================================= # + # ----------------------------------------------------------------- # + # TEST CASES + # ----------------------------------------------------------------- # + # ================================================================= # # ----------------------------------------------------------------- # - # TESTCASE: FIXME + # TESTCASE: Emulate multiple IPv4 devices (no vlans) + # DUT + # /.1 \.1 + # / \ + # 10.10.1/24 10.10.2/24 + # / \ + # /.101-105 \.101-105 + # Host1(s) Host2(s) # ----------------------------------------------------------------- # + 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) + suite.test_begin('multiEmulDev') + + num_devs = 5 try: + # configure the DUT + sudo('ip address add 10.10.1.1/24 dev ' + dut_rx_port) + sudo('ip address add 10.10.2.1/24 dev ' + dut_tx_port) + + # configure the tx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = tx_dgid_list.device_group_id[0].id + dg.core.name = "Host1" + d = dg.Extensions[emul.device] + d.count = num_devs + d.mac.address = 0x000102030a01 + d.ip4.address = 0x0a0a0165 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0101 + + drone.modifyDeviceGroup(devgrp_cfg) + + # configure the rx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = rx_dgid_list.device_group_id[0].id + dg.core.name = "Host1" + d = dg.Extensions[emul.device] + d.count = num_devs + d.mac.address = 0x000102030b01 + d.ip4.address = 0x0a0a0265 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0201 + + drone.modifyDeviceGroup(devgrp_cfg) + + # configure the tx 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.control.packets_per_sec = 20 + s.control.num_packets = 10 + + # 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 + ip = p.Extensions[ip4] + ip.src_ip = 0x0a0a0165 + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs + ip.dst_ip = 0x0a0a0265 + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs + + 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) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(tx_port) + drone.clearStats(rx_port) + + # clear arp on DUT + sudo('ip neigh flush all') + arp_cache = run('ip neigh show') + if re.search('10.10.2.10[1-5].*lladdr', arp_cache): + raise TestPreRequisiteError('ARP cache not cleared') + + drone.startCapture(rx_port) + drone.startTransmit(tx_port) log.info('waiting for transmit to finish ...') time.sleep(7) drone.stopTransmit(tx_port) @@ -260,26 +293,261 @@ try: 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']) + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) - if '10.10.2.100' in cap_pkts: + log.info('dumping Rx capture buffer (filtered)') + fail = 0 + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' + ' && (ip.dst == 10.10.2.' + str(101+i) + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+i, '02x')+')']) + print(cap_pkts) + if cap_pkts.count('\n') != s.control.num_packets/ip.src_ip_count: + fail = fail + 1 + if fail == 0: passed = True os.remove('capture.pcap') except RpcError as e: raise finally: drone.stopTransmit(tx_port) - run('arp -a') + run('ip neigh show') + # unconfigure DUT + sudo('ip address delete 10.10.1.1/24 dev ' + dut_rx_port) + sudo('ip address delete 10.10.2.1/24 dev ' + dut_tx_port) suite.test_end(passed) + # ----------------------------------------------------------------- # + # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs + # + # +- DUT -+ + # .1/ \.1 + # / \ + # 10.1.1/24 10.1.2/24 + # v[201-205] v[201-205] + # / \ + # /.101-103 \.101-103 + # Host1(s) Host2(s) + # ----------------------------------------------------------------- # + + passed = False + suite.test_begin('multiEmulDevPerVlan') + + num_vlans = 5 + vlan_base = 201 + num_devs = 3 + dev_ip_base = 101 + try: + # configure the DUT + for i in range(num_vlans): + vlan_id = vlan_base+i + vrf = 'v' + str(vlan_id) + vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) + vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) + + sudo('ip netns add ' + vrf) + + sudo('ip link add link ' + dut_rx_port + + ' name ' + vlan_rx_dev + + ' type vlan id ' + str(vlan_id)) + sudo('ip link set ' + vlan_rx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.1.1/24' + + ' dev ' + vlan_rx_dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + vlan_rx_dev + ' up') + + sudo('ip link add link ' + dut_tx_port + + ' name ' + vlan_tx_dev + + ' type vlan id ' + str(vlan_id)) + sudo('ip link set ' + vlan_tx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.2.1/24' + + ' dev ' + vlan_tx_dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + vlan_tx_dev + ' up') + + + # configure the tx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = tx_dgid_list.device_group_id[0].id + dg.core.name = "Host1" + v = dg.Extensions[emul.vlan].stack.add() + v.vlan_tag = vlan_base + v.count = num_vlans + d = dg.Extensions[emul.device] + d.count = num_devs + d.mac.address = 0x000102030a01 + d.ip4.address = 0x0a010165 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0101 + + drone.modifyDeviceGroup(devgrp_cfg) + + # configure the rx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = rx_dgid_list.device_group_id[0].id + dg.core.name = "Host1" + v = dg.Extensions[emul.vlan].stack.add() + v.vlan_tag = vlan_base + v.count = num_vlans + d = dg.Extensions[emul.device] + d.count = num_devs + #d.mode = emul.Device.kNoRepeat + d.mac.address = 0x000102030b01 + d.ip4.address = 0x0a010265 + d.ip4.prefix_length = 24 + d.ip4.default_gateway = 0x0a0a0201 + + drone.modifyDeviceGroup(devgrp_cfg) + + # configure the tx stream(s) + # we need more than one stream, so delete old one + # and create as many as we need + # FIXME: restore the single stream at end? + log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) + drone.deleteStream(stream_id) + + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(tx_port.port_id[0]) + for i in range(num_vlans): + stream_id.stream_id.add().id = i + log.info('adding tx_stream %d' % stream_id.stream_id[i].id) + + drone.addStream(stream_id) + + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) + for i in range(num_vlans): + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[i].id + s.core.is_enabled = True + s.core.ordinal = i + s.control.packets_per_sec = 10 + s.control.num_packets = num_devs + + # setup stream protocols as mac:vlan: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.kVlanFieldNumber + p.Extensions[vlan].vlan_tag = vlan_base+i + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = p.Extensions[ip4] + ip.src_ip = 0x0a010165 + ip.src_ip_mode = Ip4.e_im_inc_host + ip.dst_ip = 0x0a010265 + ip.dst_ip_mode = Ip4.e_im_inc_host + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + + drone.modifyStream(stream_cfg) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(tx_port) + drone.clearStats(rx_port) + + # clear arp on DUT + for i in range(num_vlans): + vlan_id = vlan_base + i + vrf = 'v' + str(vlan_id) + vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) + vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) + + sudo('ip netns exec ' + vrf + + ' ip neigh flush dev ' + vlan_rx_dev) + sudo('ip netns exec ' + vrf + + ' ip neigh flush dev ' + vlan_tx_dev) + sudo('ip netns exec ' + vrf + + ' ip neigh show') + + drone.startCapture(rx_port) + drone.startTransmit(tx_port) + log.info('waiting for transmit to finish ...') + time.sleep(5) + 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 (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + fail = 0 + for i in range(num_vlans): + for j in range(num_devs): + cap_pkts = subprocess.check_output( + [tshark, '-nr', 'capture.pcap', + '-R', '(vlan.id == ' + str(201+i) + ')' + ' && (ip.src == 10.1.1.' + str(101+j) + ') ' + ' && (ip.dst == 10.1.2.' + str(101+j) + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+j, '02x')+')']) + print(cap_pkts) + if cap_pkts.count('\n') != 1: + fail = fail + 1 + if fail == 0: + passed = True + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(tx_port) + # show arp on DUT + for i in range(num_vlans): + vrf = 'v' + str(vlan_base + i) + sudo('ip netns exec ' + vrf + + ' ip neigh show') + # un-configure the DUT + for i in range(num_vlans): + vlan_id = vlan_base + i + vrf = 'v' + str(vlan_id) + sudo('ip netns delete ' + vrf) + suite.test_end(passed) + + # TODO: + # ----------------------------------------------------------------- # + # TESTCASE: Emulate one IPv4 device per multiple double-tag VLANs + # vlanMode: repeat (default) + # TESTCASE: Emulate multiple IPv4 devices per multiple double-tag VLANs + # vlanMode: no-repeat; ip4Mode: repeat (default) + # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs + # vlanMode: repeat (default); ip4Mode: repeat (default) + # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs + # vlanMode: no-repeat; ip4Mode: no-repeat + # ----------------------------------------------------------------- # + suite.complete() - # delete streams + # delete stream(s) log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) drone.deleteStream(stream_id) - # delete devices + # delete device(s) dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) drone.deleteDeviceGroup(dgid_list) dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) @@ -287,6 +555,7 @@ try: # bye for now drone.disconnect() + #disconnect_all() except Exception as ex: log.exception(ex) diff --git a/test/harness.py b/test/harness.py index e6c2c92..7eb1b27 100644 --- a/test/harness.py +++ b/test/harness.py @@ -46,6 +46,12 @@ class TestSuite: def passed(self): return passed == total and self.completed +class TestPreRequisiteError(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + def extract_column(text, col): """Given a text table, return items in the specified column as a list""" lines = text.splitlines() From fcfcfe68873ce6c89ac97143c473c7f161002bc4 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 3 Oct 2015 20:15:20 +0530 Subject: [PATCH 004/121] Feature (contd.): Device Emulation - Removed 'mode' from both vlan and device; will reintroduce or replace with something else when needed --- common/emulproto.proto | 11 +---------- server/devicemanager.cpp | 25 ++----------------------- test/emultest.py | 2 +- 3 files changed, 4 insertions(+), 34 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 98b330b..31b9c74 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -25,10 +25,6 @@ package OstEmul; // VLAN // ====== message VlanEmulation { - enum Mode { - kNoRepeat = 0; - kRepeat = 1; - } message Vlan { optional uint32 tpid = 1 [default = 0x8100]; @@ -37,7 +33,6 @@ message VlanEmulation { optional uint32 count = 10 [default = 1]; optional uint32 step = 11 [default = 1]; - optional Mode mode = 12 [default = kRepeat]; } repeated Vlan stack = 1; // outer to inner @@ -74,19 +69,15 @@ message Ip6Emulation { optional uint64 step_hi = 10 [default = 0]; optional uint64 step_lo = 11 [default = 1]; + // FIXME: step for gateway? } message Device { - enum Mode { - kNoRepeat = 0; - kRepeat = 1; - } optional MacEmulation mac = 1; optional Ip4Emulation ip4 = 2; optional Ip6Emulation ip6 = 3; optional uint32 count = 10 [default = 1]; - optional Mode mode = 11 [default = kRepeat]; // FIXME: per proto mode? } extend OstProto.DeviceGroup { diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 39681d7..f54faeb 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -227,34 +227,13 @@ void DeviceManager::enumerateDevices( OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j); quint16 vlanAdd = i*vlan.step(); - switch (vlan.mode()) { - case OstEmul::VlanEmulation::kNoRepeat: - /* Do nothing */ - break; - case OstEmul::VlanEmulation::kRepeat: - default: - vlanAdd %= vlan.step() * vlan.count(); - break; - } - dk.setVlan(j, vlan.vlan_tag() + vlanAdd); } for (uint k = 0; k < pbDevice.count(); k++) { Device *device; - quint64 macAdd = (i*pbDevice.count()+k)*pbDevice.mac().step(); - quint32 ip4Add = (i*pbDevice.count()+k)*pbDevice.ip4().step(); - - switch (pbDevice.mode()) { - case OstEmul::Device::kNoRepeat: - /* Do Nothing*/ - break; - case OstEmul::Device::kRepeat: - default: - macAdd %= pbDevice.mac().step() * pbDevice.count(); - ip4Add %= pbDevice.ip4().step() * pbDevice.count(); - break; - } + quint64 macAdd = k * pbDevice.mac().step(); + quint32 ip4Add = k * pbDevice.ip4().step(); dk.setMac(pbDevice.mac().address() + macAdd); dk.setIp4(pbDevice.ip4().address() + ip4Add, diff --git a/test/emultest.py b/test/emultest.py index 7039dff..34feba8 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -201,7 +201,7 @@ try: # ----------------------------------------------------------------- # passed = False - suite.test_begin('multiEmulDev') + suite.test_begin('multiEmulDevNoVlan') num_devs = 5 try: From 8c41b536a4e395c4c9a4586a0ce9aa39bfe4b91e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 3 Oct 2015 21:27:15 +0530 Subject: [PATCH 005/121] Feature (contd.): Device Emulation - fixed bug where we receive back a transmitted emulation packet --- server/devicemanager.cpp | 5 ++++- server/pcapport.cpp | 31 +++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index f54faeb..eb3c73a 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -27,6 +27,9 @@ along with this program. If not, see #include +#define __STDC_FORMAT_MACROS +#include + const quint64 kBcastMac = 0xffffffffffffULL; // FIXME: add lock to protect deviceGroupList_ operations? @@ -157,7 +160,7 @@ void DeviceManager::receivePacket(PacketBuffer *pktBuf) // Skip srcMac - don't care offset += 6; - qDebug("dstMac %llx", dstMac); + qDebug("dstMac %012" PRIx64, dstMac); _eth_type: // Extract EthType diff --git a/server/pcapport.cpp b/server/pcapport.cpp index a7078e9..642b514 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -915,7 +915,7 @@ PcapPort::PortReceiver::~PortReceiver() void PcapPort::PortReceiver::run() { - int flag = PCAP_OPENFLAG_PROMISCUOUS; + int flags = PCAP_OPENFLAG_PROMISCUOUS; char errbuf[PCAP_ERRBUF_SIZE] = ""; struct bpf_program bpf; const char *capture_filter = "arp or (vlan and arp)"; @@ -923,21 +923,40 @@ void PcapPort::PortReceiver::run() qDebug("In %s", __PRETTY_FUNCTION__); +#ifdef Q_OS_WIN32 + flags |= PCAP_OPENFLAG_NOCAPTURE_LOCAL; +#endif + _retry: // FIXME: use 0 timeout value? - handle_ = pcap_open_live(device_.toAscii().constData(), 65535, - flag, 1000 /* ms */, errbuf); +#ifdef Q_OS_WIN32 + // NOCAPTURE_LOCAL needs windows only pcap_open() + handle_ = pcap_open(qPrintable(device_), 65535, + flags, 1000 /* ms */, NULL, errbuf); +#else + handle_ = pcap_open_live(qPrintable(device_), 65535, + flags, 1000 /* ms */, errbuf); +#endif if (handle_ == NULL) { - if (flag && QString(errbuf).contains("promiscuous")) + if (flags && 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; + qPrintable(device_)); + flags &= ~PCAP_OPENFLAG_PROMISCUOUS; goto _retry; } +#ifdef Q_OS_WIN32 + else if ((flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL) + && QString(errbuf).contains("loopback")) + { + qDebug("Can't set no local capture mode %s", qPrintable(device_)); + flags &= ~PCAP_OPENFLAG_NOCAPTURE_LOCAL; + goto _retry; + } +#endif else { // FIXME: warn user that device emulation will not work From 9302e5f17c70df7d75718c20e4e270cbfb982dbf Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 4 Nov 2015 18:50:08 +0530 Subject: [PATCH 006/121] Feature (contd.): Device Emulation - added resolveNeighbors() and related RPC code and implementation to send out ARP Requests and build the ARP Table on each device --- common/emulproto.proto | 30 ++++++++++ common/protocol.proto | 12 ++++ server/abstractport.cpp | 36 ++++++++++++ server/abstractport.h | 3 + server/device.cpp | 123 +++++++++++++++++++++++++++++++++++++-- server/device.h | 12 +++- server/devicemanager.cpp | 91 ++++++++++++++++++++++++++++- server/devicemanager.h | 4 ++ server/myservice.cpp | 82 ++++++++++++++++++++++++++ server/myservice.h | 16 +++++ server/packetbuffer.cpp | 2 +- test/emultest.py | 92 +++++++++++++++++++++++++++-- 12 files changed, 490 insertions(+), 13 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 31b9c74..0ce3b49 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -38,6 +38,8 @@ message VlanEmulation { repeated Vlan stack = 1; // outer to inner } +// FIXME: encapsulate VlanEmulation inside a new encapEmulation message? + extend OstProto.DeviceGroup { optional VlanEmulation vlan = 200; } @@ -60,6 +62,8 @@ message Ip4Emulation { // FIXME: step for gateway? } +// FIXME: should we have a 'message Ip6Address' with 'hi', 'lo' fields +// and use that everywhere instead of having to define _hi and _lo? message Ip6Emulation { optional uint64 address_hi = 1; optional uint64 address_lo = 2; @@ -72,6 +76,7 @@ message Ip6Emulation { // FIXME: step for gateway? } +// FIXME: move fields of Device directly inside 'extend OstProto.DeviceGroup' message Device { optional MacEmulation mac = 1; optional Ip4Emulation ip4 = 2; @@ -83,3 +88,28 @@ message Device { extend OstProto.DeviceGroup { optional Device device = 201; } + +// FIXME: rename xxxEntry to something better? +message ArpEntry { + optional uint32 ip4 = 1; + optional uint64 mac = 2; +// FIXME: add state/status field? +} + +message NdEntry { + optional uint64 ip6_hi = 1; + optional uint64 ip6_lo = 2; + optional uint64 mac = 3; +// FIXME: add state/status field? +} + +// FIXME: change message name +message DeviceNeighbors { + optional uint32 device_index = 1; + repeated ArpEntry arp = 2; + repeated NdEntry nd = 3; +} + +extend OstProto.DeviceNeighborList { + repeated DeviceNeighbors devices = 100; +} diff --git a/common/protocol.proto b/common/protocol.proto index a96d613..07feed8 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -281,6 +281,7 @@ message Notification { * FIXME: What will be the contents of a default device created by addDeviceGroup()? * FIXME: decide default values for device and protoEmulations * FIXME: rename 'DeviceGroup'? + * FIXME: review RPC abstractions - esp. Neighbor related */ message DeviceGroupId { required uint32 id = 1; @@ -307,6 +308,12 @@ message DeviceGroupConfigList { repeated DeviceGroup device_group = 2; } +message DeviceNeighborList { + required PortId port_id = 1; + + extensions 100 to 199; +} + service OstService { rpc getPortIdList(Void) returns (PortIdList); rpc getPortConfig(PortIdList) returns (PortConfigList); @@ -336,5 +343,10 @@ service OstService { rpc addDeviceGroup(DeviceGroupIdList) returns (Ack); rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack); rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack); + + rpc resolveDeviceNeighbors(PortIdList) returns (Ack); + rpc clearDeviceNeighbors(PortIdList) returns (Ack); + // FIXME: take PortIdList instead of PortId? + rpc getDeviceNeighbors(PortId) returns (DeviceNeighborList); } diff --git a/server/abstractport.cpp b/server/abstractport.cpp index f5e08f7..3f9b07e 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -24,6 +24,7 @@ along with this program. If not, see #include "../common/abstractprotocol.h" #include "../common/streambase.h" #include "devicemanager.h" +#include "packetbuffer.h" #include #include @@ -572,6 +573,41 @@ void AbstractPort::updatePacketListInterleaved() isSendQueueDirty_ = false; } +void AbstractPort::clearDeviceNeighbors() +{ + deviceManager_->clearDeviceNeighbors(); +} + +void AbstractPort::resolveDeviceNeighbors() +{ + // Resolve neighbor for each unique frame of each stream + // NOTE: + // 1. All the frames may have the same destination ip,but may have + // different source ip so may belong to a different emulated device; + // so we cannot optimize and send only one ARP + // 2. For a unidirectional stream, at egress, this will create ARP + // entries on the DUT for each of the source addresses + // + // TODO(optimization): Identify if stream does not vary in srcIp or dstIp + // - in which case resolve for only one frame of the stream + for (int i = 0; i < streamList_.size(); i++) + { + const StreamBase *stream = streamList_.at(i); + int frameCount = stream->frameVariableCount(); + + for (int j = 0; j < frameCount; j++) { + // TODO(optimization): we need the packet contents only uptil + // the L3 header; it would be best if protocols/streams could + // cache the frameValue() + int pktLen = stream->frameValue(pktBuf_, sizeof(pktBuf_), j); + if (pktLen) { + PacketBuffer pktBuf(pktBuf_, pktLen); + deviceManager_->resolveDeviceNeighbor(&pktBuf); + } + } + } +} + void AbstractPort::stats(PortStats *stats) { stats->rxPkts = (stats_.rxPkts >= epochStats_.rxPkts) ? diff --git a/server/abstractport.h b/server/abstractport.h index 174437d..397a22d 100644 --- a/server/abstractport.h +++ b/server/abstractport.h @@ -100,6 +100,9 @@ public: virtual void stopDeviceEmulation() = 0; virtual int sendEmulationPacket(PacketBuffer *pktBuf) = 0; + void clearDeviceNeighbors(); + void resolveDeviceNeighbors(); + void stats(PortStats *stats); void resetStats() { epochStats_ = stats_; } diff --git a/server/device.cpp b/server/device.cpp index 3381e82..3cdc724 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -28,6 +28,7 @@ along with this program. If not, see const int kBaseHex = 16; const int kMaxVlan = 4; +const quint64 kBcastMac = 0xffffffffffffULL; /* * NOTE: @@ -79,10 +80,11 @@ void Device::setMac(quint64 mac) memcpy(key_.data() + ofs, (char*)&mac, sizeof(mac)); } -void Device::setIp4(quint32 address, int prefixLength) +void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) { ip4_ = address; ip4PrefixLength_ = prefixLength; + ip4Gateway_ = gateway; } QString Device::config() @@ -142,11 +144,6 @@ _exit: return; } -void Device::transmitPacket(PacketBuffer *pktBuf) -{ - deviceManager_->transmitPacket(pktBuf); -} - // We expect pktBuf to point to EthType on entry void Device::receivePacket(PacketBuffer *pktBuf) { @@ -170,6 +167,54 @@ void Device::receivePacket(PacketBuffer *pktBuf) pktBuf->push(2); } +void Device::transmitPacket(PacketBuffer *pktBuf) +{ + deviceManager_->transmitPacket(pktBuf); +} + +void Device::clearNeighbors() +{ + arpTable.clear(); +} + +// Resolve the Neighbor IP address for this to-be-transmitted pktBuf +// We expect pktBuf to point to EthType on entry +void Device::resolveNeighbor(PacketBuffer *pktBuf) +{ + quint16 ethType = qFromBigEndian(pktBuf->data()); + pktBuf->pull(2); + + qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); + + switch(ethType) + { + case 0x0800: // IPv4 + sendArpRequest(pktBuf); + break; + + case 0x86dd: // IPv6 + default: + break; + } + // FIXME: temporary hack till DeviceManager clones pbufs + pktBuf->push(2); +} + +// Append this device's neighbors to the list +void Device::getNeighbors(OstEmul::DeviceNeighbors *neighbors) +{ + QList ipList = arpTable.keys(); + QList macList = arpTable.values(); + + Q_ASSERT(ipList.size() == macList.size()); + + for (int i = 0; i < ipList.size(); i++) { + OstEmul::ArpEntry *arp = neighbors->add_arp(); + arp->set_ip4(ipList.at(i)); + arp->set_mac(macList.at(i)); + } +} + // // Private Methods // @@ -233,6 +278,8 @@ void Device::receiveArp(PacketBuffer *pktBuf) switch (opCode) { case 1: // ARP Request + arpTable.insert(srcIp, srcMac); + rspPkt = new PacketBuffer; rspPkt->reserve(encapSize()); pktData = rspPkt->put(28); @@ -259,6 +306,9 @@ void Device::receiveArp(PacketBuffer *pktBuf) qPrintable(QHostAddress(tgtIp).toString())); break; case 2: // ARP Response + arpTable.insert(srcIp, srcMac); + break; + default: break; } @@ -269,3 +319,64 @@ _invalid_exit: qWarning("Invalid ARP content"); return; } + +// Send ARP request for the IPv4 packet in pktBuf +// pktBuf points to start of IP header +void Device::sendArpRequest(PacketBuffer *pktBuf) +{ + PacketBuffer *reqPkt; + uchar *pktData = pktBuf->data(); + int offset = 0; + int ipHdrLen = (pktData[offset] & 0x0F) << 2; + quint32 srcIp, dstIp, mask, tgtIp; + + if (pktBuf->length() < ipHdrLen) { + qDebug("incomplete IPv4 header: expected %d, actual %d", + ipHdrLen, pktBuf->length()); + return; + } + + // Extract srcIp first to check quickly that this packet originates + // from this device + srcIp = qFromBigEndian(pktData + ipHdrLen - 8); + if (srcIp != ip4_) { + qDebug("%s: srcIp %s is not me %s", __FUNCTION__, + qPrintable(QHostAddress(srcIp).toString()), + qPrintable(QHostAddress(ip4_).toString())); + return; + } + + dstIp = qFromBigEndian(pktData + ipHdrLen - 4); + + // TODO: if we have already sent a ARP request for the dst IP, do not + // resend - requires some sort of state per entry (timeout also?) + + mask = ~0 << (32 - ip4PrefixLength_); + qDebug("dst %x src %x mask %x", dstIp, srcIp, mask); + tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_; + + reqPkt = new PacketBuffer; + reqPkt->reserve(encapSize()); + pktData = reqPkt->put(28); + if (pktData) { + // HTYP, PTYP + *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800)); + // HLEN, PLEN, OPER + *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040001)); + // Source H/W Addr, Proto Addr + *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16)); + *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff)); + *(quint32*)(pktData+14) = qToBigEndian(srcIp); + // Target H/W Addr, Proto Addr + *(quint32*)(pktData+18) = qToBigEndian(quint32(0)); + *(quint16*)(pktData+22) = qToBigEndian(quint16(0)); + *(quint32*)(pktData+24) = qToBigEndian(tgtIp); + } + + encap(reqPkt, kBcastMac, 0x0806); + transmitPacket(reqPkt); + + qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s", + qPrintable(QHostAddress(srcIp).toString()), + qPrintable(QHostAddress(tgtIp).toString())); +} diff --git a/server/device.h b/server/device.h index 1ffc6e9..379d93e 100644 --- a/server/device.h +++ b/server/device.h @@ -20,9 +20,11 @@ along with this program. If not, see #ifndef _DEVICE_H #define _DEVICE_H +#include "../common/emulproto.pb.h" #include "../common/protocol.pb.h" #include +#include class DeviceManager; class PacketBuffer; @@ -36,7 +38,7 @@ public: void setVlan(int index, quint16 vlan); void setMac(quint64 mac); - void setIp4(quint32 address, int prefixLength); + void setIp4(quint32 address, int prefixLength, quint32 gateway); QString config(); DeviceKey key(); @@ -48,8 +50,13 @@ public: void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); + void clearNeighbors(); + void resolveNeighbor(PacketBuffer *pktBuf); + void getNeighbors(OstEmul::DeviceNeighbors *neighbors); + private: // methods void receiveArp(PacketBuffer *pktBuf); + void sendArpRequest(PacketBuffer *pktBuf); private: // data DeviceManager *deviceManager_; @@ -59,8 +66,11 @@ private: // data quint64 mac_; quint32 ip4_; int ip4PrefixLength_; + quint32 ip4Gateway_; DeviceKey key_; + + QHash arpTable; }; #endif diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index eb3c73a..81e9757 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -208,6 +208,94 @@ void DeviceManager::transmitPacket(PacketBuffer *pktBuf) port_->sendEmulationPacket(pktBuf); } +void DeviceManager::clearDeviceNeighbors() +{ + foreach(Device *device, deviceList_) + device->clearNeighbors(); +} + +void DeviceManager::getDeviceNeighbors( + OstProto::DeviceNeighborList *neighborList) +{ + int count = 0; + + foreach(Device *device, deviceList_) { + OstEmul::DeviceNeighbors *neighList = + neighborList->AddExtension(OstEmul::devices); + neighList->set_device_index(count++); + device->getNeighbors(neighList); + } +} + +// FIXME: This function is mostly a duplicate of receivePacket; +// can we have a single combined one? +void DeviceManager::resolveDeviceNeighbor(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + int offset = 0; + Device dk(this); + Device *device; + quint64 dstMac = kBcastMac; + quint16 ethType; + quint16 vlan; + int idx = 0; + + // NOTE: + // 1. Since resolution hasn't happened yet, dstMac will not be valid; + // so we use the Bcast address instead + // 2. We assume pkt is ethernet; TODO: extend for other link layer types + + // FIXME: validate before extracting if the offset is within pktLen + + dk.setMac(dstMac); + offset += 6; + + // 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->resolveNeighbor(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::enumerateDevices( const OstProto::DeviceGroup *deviceGroup, Operation oper) @@ -240,7 +328,8 @@ void DeviceManager::enumerateDevices( dk.setMac(pbDevice.mac().address() + macAdd); dk.setIp4(pbDevice.ip4().address() + ip4Add, - pbDevice.ip4().prefix_length()); + pbDevice.ip4().prefix_length(), + pbDevice.ip4().default_gateway()); // TODO: fill in other pbDevice data diff --git a/server/devicemanager.h b/server/devicemanager.h index 1eb4093..4ee426f 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -51,6 +51,10 @@ public: void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); + void clearDeviceNeighbors(); + void resolveDeviceNeighbor(PacketBuffer *pktBuf); + void getDeviceNeighbors(OstProto::DeviceNeighborList *neighborList); + private: enum Operation { kAdd, kDelete }; diff --git a/server/myservice.cpp b/server/myservice.cpp index 6c79afe..0f32af7 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -817,3 +817,85 @@ _invalid_port: _exit: done->Run(); } + +void MyService::resolveDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->resolveDeviceNeighbors(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::clearDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->clearDeviceNeighbors(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::getDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::DeviceNeighborList* 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(); + devMgr->getDeviceNeighbors(response); + portLock[portId]->unlock(); + + done->Run(); + return; + +_invalid_port: + controller->SetFailed("Invalid Port Id"); + done->Run(); +} diff --git a/server/myservice.h b/server/myservice.h index c1fe69d..4b6d7ef 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -131,6 +131,22 @@ public: const ::OstProto::DeviceGroupConfigList* request, ::OstProto::Ack* response, ::google::protobuf::Closure* done); + + virtual void resolveDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void clearDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void getDeviceNeighbors( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::DeviceNeighborList* response, + ::google::protobuf::Closure* done); signals: void notification(int notifType, SharedProtobufMessage notifData); diff --git a/server/packetbuffer.cpp b/server/packetbuffer.cpp index d049667..761062e 100644 --- a/server/packetbuffer.cpp +++ b/server/packetbuffer.cpp @@ -51,7 +51,7 @@ PacketBuffer::~PacketBuffer() int PacketBuffer::length() { - return tail_ - head_; + return tail_ - data_; } uchar* PacketBuffer::head() diff --git a/test/emultest.py b/test/emultest.py index 34feba8..32a9b59 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -37,7 +37,9 @@ env.password = 'tc' env.host_string = 'localhost:50022' dut_rx_port = 'eth1' dut_tx_port = 'eth2' - + +dut_dst_mac = 0x0800278df2b4 #FIXME: hardcoding + # setup logging log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -71,6 +73,8 @@ suite = TestSuite() if not use_defaults: s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name)) host_name = s or host_name + s = raw_input('DUT\'s Hostname/IP [%s]: ' % (env.host_string)) + env.host_string = s or env.host_string drone = DroneProxy(host_name) @@ -117,6 +121,10 @@ try: tx_port_number = port.port_id.id elif rx_port_number < 0: rx_port_number = port.port_id.id + if ('eth1' in port.name): + tx_port_number = port.port_id.id + if ('eth2' in port.name): + rx_port_number = port.port_id.id if not use_defaults: p = raw_input('Tx Port Id [%d]: ' % (tx_port_number)) @@ -145,6 +153,10 @@ try: # modify and reuse these devices as per its needs # ----------------------------------------------------------------- # + emul_ports = ost_pb.PortIdList() + emul_ports.port_id.add().id = tx_port_number; + emul_ports.port_id.add().id = rx_port_number; + # delete existing devices, if any, on tx port tx_dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) drone.deleteDeviceGroup(tx_dgid_list) @@ -251,7 +263,7 @@ try: # 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].dst_mac = dut_dst_mac p.Extensions[mac].src_mac = 0x00aabbccddee p = s.protocol.add() @@ -284,6 +296,74 @@ try: if re.search('10.10.2.10[1-5].*lladdr', arp_cache): raise TestPreRequisiteError('ARP cache not cleared') + # resolve ARP on tx/rx ports + log.info('resolving Neighbors on tx/rx ports ...') + drone.startCapture(emul_ports) + drone.clearDeviceNeighbors(emul_ports) + drone.resolveDeviceNeighbors(emul_ports) + time.sleep(10) + drone.stopCapture(emul_ports) + + fail = 0 + + # verify ARP Requests sent out from tx port + buff = drone.getCaptureBuffer(emul_ports.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Tx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Tx capture buffer (filtered)') + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.10.1.'+str(101+i)+')' + ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) + print(cap_pkts) + if cap_pkts.count('\n') != 1: + fail = fail + 1 + + # verify *no* ARP Requests sent out from rx port + buff = drone.getCaptureBuffer(emul_ports.port_id[1]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.10.2.'+str(101+i)+')' + ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) + print(cap_pkts) + if cap_pkts.count('\n') != 0: + fail = fail + 1 + + # retrieve and verify ARP Table on tx/rx ports + log.info('retrieving ARP entries on tx port') + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) + devices = neigh_list.Extensions[emul.devices] + # TODO: verify gateway IP is resolved for each device + # FIXME: needs device ip as part of neigh_list + log.info('ARP Table on tx port') + for device in devices: + for arp in device.arp: + # TODO: pretty print ip and mac + print('%d: %08x %012x' % + (device.device_index, arp.ip4, arp.mac)) + + log.info('retrieving ARP entries on rx port') + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP Table on rx port') + for device in devices: + # verify *no* ARPs learnt on rx port + if len(device.arp): + fail = fail + 1 + for arp in device.arp: + # TODO: pretty print ip and mac + print('%d: %08x %012x' % + (device.device_index, arp.ip4, arp.mac)) + drone.startCapture(rx_port) drone.startTransmit(tx_port) log.info('waiting for transmit to finish ...') @@ -297,7 +377,6 @@ try: cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') - fail = 0 for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' @@ -309,6 +388,8 @@ try: fail = fail + 1 if fail == 0: passed = True + else: + log.info('failed checks: %d' % fail) os.remove('capture.pcap') except RpcError as e: raise @@ -320,6 +401,9 @@ try: sudo('ip address delete 10.10.2.1/24 dev ' + dut_tx_port) suite.test_end(passed) + # FIXME: update the below test cases to resolve Neighbors and streams + # to derive src/dst mac from device + # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # @@ -438,7 +522,7 @@ try: # setup stream protocols as mac:vlan: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].dst_mac = dut_dst_mac p.Extensions[mac].src_mac = 0x00aabbccddee p = s.protocol.add() From 0db170d39319899d45376377e4fec2eb57a9bd38 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 5 Nov 2015 18:33:34 +0530 Subject: [PATCH 007/121] Added cscope.out to .gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b1dd8ef..cd152a4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,6 @@ pkg_info.json *.swp .DS_Store -# ctags +# ctags/cscope tags - +cscope.out From 280d4bedaa5a16dc59001d8bfada35de4e8612a1 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 10 Nov 2015 19:40:32 +0530 Subject: [PATCH 008/121] Feature (contd.): Device Emulation - Mac protocol can now 'resolve' src/dst mac corresponding to the packet from the Device Emulation infra --- client/stream.cpp | 10 +++++ common/mac.cpp | 41 ++++++++++++++++-- common/mac.h | 1 + common/mac.proto | 1 + common/streambase.cpp | 15 ++++++- common/streambase.h | 22 ++++++---- server/abstractport.cpp | 94 ++++++++++++++++++++++++++-------------- server/abstractport.h | 13 +++++- server/device.cpp | 73 +++++++++++++++++++++++++++++-- server/device.h | 4 ++ server/devicemanager.cpp | 75 +++++++++++++++----------------- server/devicemanager.h | 3 ++ server/drone.cpp | 5 +++ server/drone.h | 1 + server/drone_main.cpp | 2 +- server/myservice.cpp | 37 +++++++++++++++- server/myservice.h | 5 +++ server/packetbuffer.cpp | 10 ++--- server/packetbuffer.h | 10 ++--- test/emultest.py | 7 +-- 20 files changed, 323 insertions(+), 106 deletions(-) diff --git a/client/stream.cpp b/client/stream.cpp index 2305930..6bc9ab5 100644 --- a/client/stream.cpp +++ b/client/stream.cpp @@ -49,3 +49,13 @@ void Stream::storeProtocolWidgets() qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__); return; } + +quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex) +{ + return 0; +} + +quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex) +{ + return 0; +} diff --git a/common/mac.cpp b/common/mac.cpp index cfe35a6..3a7df21 100644 --- a/common/mac.cpp +++ b/common/mac.cpp @@ -19,6 +19,8 @@ along with this program. If not, see #include "mac.h" +#include "../common/streambase.h" + #include #define uintToMacStr(num) \ @@ -28,6 +30,7 @@ along with this program. If not, see MacProtocol::MacProtocol(StreamBase *stream, AbstractProtocol *parent) : AbstractProtocol(stream, parent) { + forResolve_ = false; } MacProtocol::~MacProtocol() @@ -124,6 +127,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib, data.dst_mac_step(); dstMac = data.dst_mac() - u; break; + case OstProto::Mac::e_mm_resolve: + if (forResolve_) + dstMac = 0; + else { + forResolve_ = true; + dstMac = mpStream->neighborMacAddress(streamIndex); + forResolve_ = false; + } + break; default: qWarning("Unhandled dstMac_mode %d", data.dst_mac_mode()); } @@ -169,6 +181,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib, data.src_mac_step(); srcMac = data.src_mac() - u; break; + case OstProto::Mac::e_mm_resolve: + if (forResolve_) + srcMac = 0; + else { + forResolve_ = true; + srcMac = mpStream->deviceMacAddress(streamIndex); + forResolve_ = false; + } + break; default: qWarning("Unhandled srcMac_mode %d", data.src_mac_mode()); } @@ -331,11 +352,23 @@ int MacProtocol::protocolFrameVariableCount() const { int count = AbstractProtocol::protocolFrameVariableCount(); - if (data.dst_mac_mode() != OstProto::Mac::e_mm_fixed) - count = AbstractProtocol::lcm(count, data.dst_mac_count()); + switch (data.dst_mac_mode()) { + case OstProto::Mac::e_mm_inc: + case OstProto::Mac::e_mm_dec: + count = AbstractProtocol::lcm(count, data.dst_mac_count()); + break; + default: + break; + } - if (data.src_mac_mode() != OstProto::Mac::e_mm_fixed) - count = AbstractProtocol::lcm(count, data.src_mac_count()); + switch (data.src_mac_mode()) { + case OstProto::Mac::e_mm_inc: + case OstProto::Mac::e_mm_dec: + count = AbstractProtocol::lcm(count, data.src_mac_count()); + break; + default: + break; + } return count; } diff --git a/common/mac.h b/common/mac.h index 3469d40..792f325 100644 --- a/common/mac.h +++ b/common/mac.h @@ -67,6 +67,7 @@ public: private: OstProto::Mac data; + mutable bool forResolve_; }; #endif diff --git a/common/mac.proto b/common/mac.proto index 2055223..1bd07f4 100644 --- a/common/mac.proto +++ b/common/mac.proto @@ -28,6 +28,7 @@ message Mac { e_mm_fixed = 0; e_mm_inc = 1; e_mm_dec = 2; + e_mm_resolve = 3; // dst: resolve neighbor; src: from device config } // Dst Mac diff --git a/common/streambase.cpp b/common/streambase.cpp index 0333eb9..9b640dd 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -24,8 +24,11 @@ along with this program. If not, see #include "protocolmanager.h" extern ProtocolManager *OstProtocolManager; +extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex); +extern quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex); -StreamBase::StreamBase() : +StreamBase::StreamBase(int portId) : + portId_(portId), mStreamId(new OstProto::StreamId), mCore(new OstProto::StreamCore), mControl(new OstProto::StreamControl) @@ -537,6 +540,16 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const return pktLen; } +quint64 StreamBase::deviceMacAddress(int frameIndex) const +{ + return getDeviceMacAddress(portId_, int(mStreamId->id()), frameIndex); +} + +quint64 StreamBase::neighborMacAddress(int frameIndex) const +{ + return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex); +} + bool StreamBase::preflightCheck(QString &result) const { bool pass = true; diff --git a/common/streambase.h b/common/streambase.h index 9ef3ba1..fe4d334 100644 --- a/common/streambase.h +++ b/common/streambase.h @@ -33,15 +33,8 @@ class ProtocolListIterator; class StreamBase { -private: - OstProto::StreamId *mStreamId; - OstProto::StreamCore *mCore; - OstProto::StreamControl *mControl; - - ProtocolList *currentFrameProtocols; - public: - StreamBase(); + StreamBase(int portId = -1); ~StreamBase(); void protoDataCopyFrom(const OstProto::Stream &stream); @@ -142,9 +135,22 @@ public: int frameProtocolLength(int frameIndex) const; int frameCount() const; int frameValue(uchar *buf, int bufMaxSize, int frameIndex) const; + + quint64 deviceMacAddress(int frameIndex) const; + quint64 neighborMacAddress(int frameIndex) const; + bool preflightCheck(QString &result) const; static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2); + +private: + int portId_; + + OstProto::StreamId *mStreamId; + OstProto::StreamCore *mCore; + OstProto::StreamControl *mControl; + + ProtocolList *currentFrameProtocols; }; #endif diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 3f9b07e..2ab926c 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -573,9 +573,44 @@ void AbstractPort::updatePacketListInterleaved() isSendQueueDirty_ = false; } +void AbstractPort::stats(PortStats *stats) +{ + stats->rxPkts = (stats_.rxPkts >= epochStats_.rxPkts) ? + stats_.rxPkts - epochStats_.rxPkts : + stats_.rxPkts + (maxStatsValue_ - epochStats_.rxPkts); + stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ? + stats_.rxBytes - epochStats_.rxBytes : + stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes); + stats->rxPps = stats_.rxPps; + stats->rxBps = stats_.rxBps; + + stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ? + stats_.txPkts - epochStats_.txPkts : + stats_.txPkts + (maxStatsValue_ - epochStats_.txPkts); + stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ? + stats_.txBytes - epochStats_.txBytes : + stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes); + stats->txPps = stats_.txPps; + stats->txBps = stats_.txBps; + + stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ? + stats_.rxDrops - epochStats_.rxDrops : + stats_.rxDrops + (maxStatsValue_ - epochStats_.rxDrops); + stats->rxErrors = (stats_.rxErrors >= epochStats_.rxErrors) ? + stats_.rxErrors - epochStats_.rxErrors : + stats_.rxErrors + (maxStatsValue_ - epochStats_.rxErrors); + stats->rxFifoErrors = (stats_.rxFifoErrors >= epochStats_.rxFifoErrors) ? + stats_.rxFifoErrors - epochStats_.rxFifoErrors : + stats_.rxFifoErrors + (maxStatsValue_ - epochStats_.rxFifoErrors); + stats->rxFrameErrors = (stats_.rxFrameErrors >= epochStats_.rxFrameErrors) ? + stats_.rxFrameErrors - epochStats_.rxFrameErrors : + stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors); +} + void AbstractPort::clearDeviceNeighbors() { deviceManager_->clearDeviceNeighbors(); + isSendQueueDirty_ = true; } void AbstractPort::resolveDeviceNeighbors() @@ -596,48 +631,41 @@ void AbstractPort::resolveDeviceNeighbors() int frameCount = stream->frameVariableCount(); for (int j = 0; j < frameCount; j++) { - // TODO(optimization): we need the packet contents only uptil - // the L3 header; it would be best if protocols/streams could - // cache the frameValue() - int pktLen = stream->frameValue(pktBuf_, sizeof(pktBuf_), j); + // we need the packet contents only uptil the L3 header + int pktLen = stream->frameValue(pktBuf_, kMaxL3PktSize, j); if (pktLen) { PacketBuffer pktBuf(pktBuf_, pktLen); deviceManager_->resolveDeviceNeighbor(&pktBuf); } } } + isSendQueueDirty_ = true; } -void AbstractPort::stats(PortStats *stats) +quint64 AbstractPort::deviceMacAddress(int streamId, int frameIndex) { - stats->rxPkts = (stats_.rxPkts >= epochStats_.rxPkts) ? - stats_.rxPkts - epochStats_.rxPkts : - stats_.rxPkts + (maxStatsValue_ - epochStats_.rxPkts); - stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ? - stats_.rxBytes - epochStats_.rxBytes : - stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes); - stats->rxPps = stats_.rxPps; - stats->rxBps = stats_.rxBps; + // we need the packet contents only uptil the L3 header + StreamBase *s = stream(streamId); + int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex); - stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ? - stats_.txPkts - epochStats_.txPkts : - stats_.txPkts + (maxStatsValue_ - epochStats_.txPkts); - stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ? - stats_.txBytes - epochStats_.txBytes : - stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes); - stats->txPps = stats_.txPps; - stats->txBps = stats_.txBps; + if (pktLen) { + PacketBuffer pktBuf(pktBuf_, pktLen); + return deviceManager_->deviceMacAddress(&pktBuf); + } - stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ? - stats_.rxDrops - epochStats_.rxDrops : - stats_.rxDrops + (maxStatsValue_ - epochStats_.rxDrops); - stats->rxErrors = (stats_.rxErrors >= epochStats_.rxErrors) ? - stats_.rxErrors - epochStats_.rxErrors : - stats_.rxErrors + (maxStatsValue_ - epochStats_.rxErrors); - stats->rxFifoErrors = (stats_.rxFifoErrors >= epochStats_.rxFifoErrors) ? - stats_.rxFifoErrors - epochStats_.rxFifoErrors : - stats_.rxFifoErrors + (maxStatsValue_ - epochStats_.rxFifoErrors); - stats->rxFrameErrors = (stats_.rxFrameErrors >= epochStats_.rxFrameErrors) ? - stats_.rxFrameErrors - epochStats_.rxFrameErrors : - stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors); + return 0; +} + +quint64 AbstractPort::neighborMacAddress(int streamId, int frameIndex) +{ + // we need the packet contents only uptil the L3 header + StreamBase *s = stream(streamId); + int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex); + + if (pktLen) { + PacketBuffer pktBuf(pktBuf_, pktLen); + return deviceManager_->neighborMacAddress(&pktBuf); + } + + return 0; } diff --git a/server/abstractport.h b/server/abstractport.h index 397a22d..281dd00 100644 --- a/server/abstractport.h +++ b/server/abstractport.h @@ -95,6 +95,9 @@ public: virtual bool isCaptureOn() = 0; virtual QIODevice* captureData() = 0; + void stats(PortStats *stats); + void resetStats() { epochStats_ = stats_; } + DeviceManager* deviceManager(); virtual void startDeviceEmulation() = 0; virtual void stopDeviceEmulation() = 0; @@ -103,8 +106,8 @@ public: void clearDeviceNeighbors(); void resolveDeviceNeighbors(); - void stats(PortStats *stats); - void resetStats() { epochStats_ = stats_; } + quint64 deviceMacAddress(int streamId, int frameIndex); + quint64 neighborMacAddress(int streamId, int frameIndex); protected: void addNote(QString note); @@ -129,6 +132,12 @@ private: static const int kMaxPktSize = 16384; uchar pktBuf_[kMaxPktSize]; + // When finding a corresponding device for a packet, we need to inspect + // only uptil the L3 header; in the worst case this would be - + // mac (12) + 4 x vlan (16) + ethType (2) + ipv6 (40) = 74 bytes + // let's round it up to 80 bytes + static const int kMaxL3PktSize = 80; + /*! \note StreamBase::id() and index into streamList[] are NOT same! */ QList streamList_; diff --git a/server/device.cpp b/server/device.cpp index 3cdc724..f84f1bd 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -72,6 +72,11 @@ void Device::setVlan(int index, quint16 vlan) numVlanTags_ = index + 1; } +quint64 Device::mac() +{ + return mac_; +} + void Device::setMac(quint64 mac) { int ofs = kMaxVlan * sizeof(quint16); @@ -215,6 +220,65 @@ void Device::getNeighbors(OstEmul::DeviceNeighbors *neighbors) } } +// We expect pktBuf to point to EthType on entry +bool Device::isOrigin(const PacketBuffer *pktBuf) +{ + const uchar *pktData = pktBuf->data(); + quint16 ethType = qFromBigEndian(pktData); + + qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); + pktData += 2; + + // We know only about IP packets + if (ethType == 0x0800) { // IPv4 + int ipHdrLen = (pktData[0] & 0x0F) << 2; + quint32 srcIp; + + if (pktBuf->length() < ipHdrLen) { + qDebug("incomplete IPv4 header: expected %d, actual %d", + ipHdrLen, pktBuf->length()); + return false; + } + + srcIp = qFromBigEndian(pktData + ipHdrLen - 8); + qDebug("%s: pktSrcIp/selfIp = 0x%x/0x%x", __FUNCTION__, srcIp, ip4_); + return (srcIp == ip4_); + } + + return false; +} + +// We expect pktBuf to point to EthType on entry +quint64 Device::neighborMac(const PacketBuffer *pktBuf) +{ + const uchar *pktData = pktBuf->data(); + quint16 ethType = qFromBigEndian(pktData); + + qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); + pktData += 2; + + // We know only about IP packets + if (ethType == 0x0800) { // IPv4 + int ipHdrLen = (pktData[0] & 0x0F) << 2; + quint32 dstIp, tgtIp, mask; + + if (pktBuf->length() < ipHdrLen) { + qDebug("incomplete IPv4 header: expected %d, actual %d", + ipHdrLen, pktBuf->length()); + return false; + } + + dstIp = qFromBigEndian(pktData + ipHdrLen - 4); + mask = ~0 << (32 - ip4PrefixLength_); + qDebug("dst %x self %x mask %x", dstIp, ip4_, mask); + tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; + + return arpTable.value(tgtIp); + } + + return false; +} + // // Private Methods // @@ -348,13 +412,15 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + ipHdrLen - 4); - // TODO: if we have already sent a ARP request for the dst IP, do not - // resend - requires some sort of state per entry (timeout also?) - mask = ~0 << (32 - ip4PrefixLength_); qDebug("dst %x src %x mask %x", dstIp, srcIp, mask); tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_; + // Do we already have a ARP entry (resolved or unresolved)? + // FIXME: do we need a timer to resend ARP for unresolved entries? + if (arpTable.contains(tgtIp)) + return; + reqPkt = new PacketBuffer; reqPkt->reserve(encapSize()); pktData = reqPkt->put(28); @@ -375,6 +441,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) encap(reqPkt, kBcastMac, 0x0806); transmitPacket(reqPkt); + arpTable.insert(tgtIp, 0); qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s", qPrintable(QHostAddress(srcIp).toString()), diff --git a/server/device.h b/server/device.h index 379d93e..3c5efba 100644 --- a/server/device.h +++ b/server/device.h @@ -37,6 +37,7 @@ public: Device(DeviceManager *deviceManager); void setVlan(int index, quint16 vlan); + quint64 mac(); void setMac(quint64 mac); void setIp4(quint32 address, int prefixLength, quint32 gateway); QString config(); @@ -54,6 +55,9 @@ public: void resolveNeighbor(PacketBuffer *pktBuf); void getNeighbors(OstEmul::DeviceNeighbors *neighbors); + bool isOrigin(const PacketBuffer *pktBuf); + quint64 neighborMac(const PacketBuffer *pktBuf); + private: // methods void receiveArp(PacketBuffer *pktBuf); void sendArpRequest(PacketBuffer *pktBuf); diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 81e9757..af0301c 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -227,36 +227,47 @@ void DeviceManager::getDeviceNeighbors( } } -// FIXME: This function is mostly a duplicate of receivePacket; -// can we have a single combined one? 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 = 0; + int offset = 12; // start parsing after mac addresses Device dk(this); - Device *device; - quint64 dstMac = kBcastMac; quint16 ethType; quint16 vlan; int idx = 0; - // NOTE: - // 1. Since resolution hasn't happened yet, dstMac will not be valid; - // so we use the Bcast address instead - // 2. We assume pkt is ethernet; TODO: extend for other link layer types + // pktBuf will not have the correct dstMac populated, so use bcastMac + // and search for device by IP - // FIXME: validate before extracting if the offset is within pktLen - - dk.setMac(dstMac); - offset += 6; - - // Skip srcMac - don't care - offset += 6; - - qDebug("dstMac %012" PRIx64, dstMac); + dk.setMac(kBcastMac); _eth_type: - // Extract EthType ethType = qFromBigEndian(pktData + offset); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); @@ -271,29 +282,13 @@ _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->resolveNeighbor(pktBuf); - goto _exit; + foreach(Device *device, bcastList_.values(dk.key())) { + if (device->isOrigin(pktBuf)) + return device; } - // 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; + qDebug("couldn't find origin device for packet"); + return NULL; } void DeviceManager::enumerateDevices( diff --git a/server/devicemanager.h b/server/devicemanager.h index 4ee426f..f77f6d6 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -55,9 +55,12 @@ public: void resolveDeviceNeighbor(PacketBuffer *pktBuf); void getDeviceNeighbors(OstProto::DeviceNeighborList *neighborList); + quint64 deviceMacAddress(PacketBuffer *pktBuf); + quint64 neighborMacAddress(PacketBuffer *pktBuf); private: enum Operation { kAdd, kDelete }; + Device* originDevice(PacketBuffer *pktBuf); void enumerateDevices( const OstProto::DeviceGroup *deviceGroup, Operation oper); diff --git a/server/drone.cpp b/server/drone.cpp index a6e3b12..c71168e 100644 --- a/server/drone.cpp +++ b/server/drone.cpp @@ -58,3 +58,8 @@ bool Drone::init() return true; } + +MyService* Drone::rpcService() +{ + return service; +} diff --git a/server/drone.h b/server/drone.h index ab05fe1..a12f1e7 100644 --- a/server/drone.h +++ b/server/drone.h @@ -32,6 +32,7 @@ public: Drone(QObject *parent = 0); ~Drone(); bool init(); + MyService* rpcService(); private: RpcServer *rpcServer; diff --git a/server/drone_main.cpp b/server/drone_main.cpp index e5c1abd..f80e62d 100644 --- a/server/drone_main.cpp +++ b/server/drone_main.cpp @@ -35,6 +35,7 @@ extern ProtocolManager *OstProtocolManager; extern char *version; extern char *revision; +Drone *drone; QSettings *appSettings; int myport; @@ -47,7 +48,6 @@ int main(int argc, char *argv[]) { int exitCode = 0; QCoreApplication app(argc, argv); - Drone *drone; // TODO: command line options // -v (--version) diff --git a/server/myservice.cpp b/server/myservice.cpp index 0f32af7..62c0539 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -20,6 +20,8 @@ along with this program. If not, see #include "myservice.h" +#include "drone.h" + #if 0 #include #include @@ -38,6 +40,7 @@ along with this program. If not, see #include +extern Drone *drone; extern char *version; MyService::MyService() @@ -241,7 +244,7 @@ void MyService::addStream(::google::protobuf::RpcController* controller, // Append a new "default" stream - actual contents of the new stream is // expected in a subsequent "modifyStream" request - set the stream id // now itself however!!! - stream = new StreamBase; + stream = new StreamBase(portId); stream->setId(request->stream_id(i).id()); portInfo[portId]->addStream(stream); } @@ -360,6 +363,8 @@ void MyService::startTransmit(::google::protobuf::RpcController* /*controller*/, continue; //! \todo (LOW): partial RPC? portLock[portId]->lockForWrite(); + if (portInfo[portId]->isDirty()) + portInfo[portId]->updatePacketList(); portInfo[portId]->startTransmit(); portLock[portId]->unlock(); } @@ -899,3 +904,33 @@ _invalid_port: controller->SetFailed("Invalid Port Id"); done->Run(); } + +quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex) +{ + MyService *service = drone->rpcService(); + quint64 mac; + + if (!service) + return 0; + + service->portLock[portId]->lockForWrite(); + mac = service->portInfo[portId]->deviceMacAddress(streamId, frameIndex); + service->portLock[portId]->unlock(); + + return mac; +} + +quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex) +{ + MyService *service = drone->rpcService(); + quint64 mac; + + if (!service) + return 0; + + service->portLock[portId]->lockForWrite(); + mac = service->portInfo[portId]->neighborMacAddress(streamId, frameIndex); + service->portLock[portId]->unlock(); + + return mac; +} diff --git a/server/myservice.h b/server/myservice.h index 4b6d7ef..3d43568 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -147,6 +147,11 @@ public: const ::OstProto::PortId* request, ::OstProto::DeviceNeighborList* response, ::google::protobuf::Closure* done); + + friend quint64 getDeviceMacAddress( + int portId, int streamId, int frameIndex); + friend quint64 getNeighborMacAddress( + int portId, int streamId, int frameIndex); signals: void notification(int notifType, SharedProtobufMessage notifData); diff --git a/server/packetbuffer.cpp b/server/packetbuffer.cpp index 761062e..6c4c0d2 100644 --- a/server/packetbuffer.cpp +++ b/server/packetbuffer.cpp @@ -49,27 +49,27 @@ PacketBuffer::~PacketBuffer() delete[] buffer_; } -int PacketBuffer::length() +int PacketBuffer::length() const { return tail_ - data_; } -uchar* PacketBuffer::head() +uchar* PacketBuffer::head() const { return head_; } -uchar* PacketBuffer::data() +uchar* PacketBuffer::data() const { return data_; } -uchar* PacketBuffer::tail() +uchar* PacketBuffer::tail() const { return tail_; } -uchar* PacketBuffer::end() +uchar* PacketBuffer::end() const { return end_; } diff --git a/server/packetbuffer.h b/server/packetbuffer.h index 5449fee..5b7fbe7 100644 --- a/server/packetbuffer.h +++ b/server/packetbuffer.h @@ -29,12 +29,12 @@ public: PacketBuffer(const uchar *buffer, int size); ~PacketBuffer(); - int length(); + int length() const; - uchar* head(); - uchar* data(); - uchar* tail(); - uchar* end(); + uchar* head() const; + uchar* data() const; + uchar* tail() const; + uchar* end() const; void reserve(int len); uchar* pull(int len); diff --git a/test/emultest.py b/test/emultest.py index 32a9b59..4a7a3dc 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -14,7 +14,7 @@ from harness import Test, TestSuite, TestPreRequisiteError sys.path.insert(1, '../binding') from core import ost_pb, emul, DroneProxy from rpc import RpcError -from protocols.mac_pb2 import mac +from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 from protocols.vlan_pb2 import vlan @@ -263,8 +263,8 @@ try: # 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 = dut_dst_mac - p.Extensions[mac].src_mac = 0x00aabbccddee + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber @@ -401,6 +401,7 @@ try: sudo('ip address delete 10.10.2.1/24 dev ' + dut_tx_port) suite.test_end(passed) + sys.exit(1) # FIXME: update the below test cases to resolve Neighbors and streams # to derive src/dst mac from device From 492a207ede220b42d072ac1114cfbb9c1b255bf4 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 11 Nov 2015 10:52:29 +0530 Subject: [PATCH 009/121] Feature (contd.): Device Emulation - reorganized/renamed some emulation RPCs and messages --- common/emulproto.proto | 63 +++++++++++++++++++++++----------------- common/protocol.proto | 13 +++++---- server/device.cpp | 2 +- server/device.h | 2 +- server/devicemanager.cpp | 25 +++++++++------- server/devicemanager.h | 2 +- server/myservice.cpp | 2 +- server/myservice.h | 2 +- test/emultest.py | 32 ++++++++++++-------- 9 files changed, 82 insertions(+), 61 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 0ce3b49..6712882 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -21,9 +21,9 @@ import "protocol.proto"; package OstEmul; -// ====== -// VLAN -// ====== +// ======= +// Encap +// ======= message VlanEmulation { message Vlan { optional uint32 tpid = 1 [default = 0x8100]; @@ -38,15 +38,13 @@ message VlanEmulation { repeated Vlan stack = 1; // outer to inner } -// FIXME: encapsulate VlanEmulation inside a new encapEmulation message? - -extend OstProto.DeviceGroup { - optional VlanEmulation vlan = 200; +message EncapEmulation { + optional VlanEmulation vlan = 1; } -// ======== -// Device -// ======== +// =========== +// Protocols +// =========== message MacEmulation { optional uint64 address = 1; // FIXME: default value @@ -76,40 +74,51 @@ message Ip6Emulation { // FIXME: step for gateway? } -// FIXME: move fields of Device directly inside 'extend OstProto.DeviceGroup' -message Device { - optional MacEmulation mac = 1; - optional Ip4Emulation ip4 = 2; - optional Ip6Emulation ip6 = 3; - - optional uint32 count = 10 [default = 1]; -} - extend OstProto.DeviceGroup { - optional Device device = 201; + optional EncapEmulation encap = 2000; + + optional MacEmulation mac = 2001; + + optional Ip4Emulation ip4 = 3000; + optional Ip6Emulation ip6 = 3001; } -// FIXME: rename xxxEntry to something better? +/* TODO +message Device { + optional uint64 mac = 1; + + // FIXME: move vlan into encapInfo? + repeated uint32 vlan = 2; // includes tpid 'n vlan tag + + optional uint32 ip4 = 3; + optional uint32 ip4_prefix_length = 4; + optional uint32 ip4_default_gateway = 5; + + // TODO: IPv6 fields +} + +message DeviceList { + repeated Device devices = 1; +} +*/ + message ArpEntry { optional uint32 ip4 = 1; optional uint64 mac = 2; -// FIXME: add state/status field? } message NdEntry { optional uint64 ip6_hi = 1; optional uint64 ip6_lo = 2; optional uint64 mac = 3; -// FIXME: add state/status field? } -// FIXME: change message name -message DeviceNeighbors { +message DeviceNeighborList { optional uint32 device_index = 1; repeated ArpEntry arp = 2; repeated NdEntry nd = 3; } -extend OstProto.DeviceNeighborList { - repeated DeviceNeighbors devices = 100; +extend OstProto.PortNeighborList { + repeated DeviceNeighborList devices = 100; } diff --git a/common/protocol.proto b/common/protocol.proto index 07feed8..a34426e 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -281,7 +281,6 @@ message Notification { * FIXME: What will be the contents of a default device created by addDeviceGroup()? * FIXME: decide default values for device and protoEmulations * FIXME: rename 'DeviceGroup'? - * FIXME: review RPC abstractions - esp. Neighbor related */ message DeviceGroupId { required uint32 id = 1; @@ -299,8 +298,9 @@ message DeviceGroupIdList { message DeviceGroup { required DeviceGroupId device_group_id = 1; optional DeviceGroupCore core = 2; + optional uint32 device_count = 3 [default = 1]; - extensions 200 to 500; // For use by Protocol Emulations + extensions 2000 to 5999; // For use by Protocol Emulations } message DeviceGroupConfigList { @@ -308,7 +308,7 @@ message DeviceGroupConfigList { repeated DeviceGroup device_group = 2; } -message DeviceNeighborList { +message PortNeighborList { required PortId port_id = 1; extensions 100 to 199; @@ -337,16 +337,17 @@ service OstService { rpc checkVersion(VersionInfo) returns (VersionCompatibility); - // Device/Protocol Emulation + // Device Emulation rpc getDeviceGroupIdList(PortId) returns (DeviceGroupIdList); rpc getDeviceGroupConfig(DeviceGroupIdList) returns (DeviceGroupConfigList); rpc addDeviceGroup(DeviceGroupIdList) returns (Ack); rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack); rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack); + // TODO: rpc getDeviceList(PortId) returns (DeviceList); + rpc resolveDeviceNeighbors(PortIdList) returns (Ack); rpc clearDeviceNeighbors(PortIdList) returns (Ack); - // FIXME: take PortIdList instead of PortId? - rpc getDeviceNeighbors(PortId) returns (DeviceNeighborList); + rpc getDeviceNeighbors(PortId) returns (PortNeighborList); } diff --git a/server/device.cpp b/server/device.cpp index f84f1bd..edaaa1d 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -206,7 +206,7 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf) } // Append this device's neighbors to the list -void Device::getNeighbors(OstEmul::DeviceNeighbors *neighbors) +void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors) { QList ipList = arpTable.keys(); QList macList = arpTable.values(); diff --git a/server/device.h b/server/device.h index 3c5efba..2976d99 100644 --- a/server/device.h +++ b/server/device.h @@ -53,7 +53,7 @@ public: void clearNeighbors(); void resolveNeighbor(PacketBuffer *pktBuf); - void getNeighbors(OstEmul::DeviceNeighbors *neighbors); + void getNeighbors(OstEmul::DeviceNeighborList *neighbors); bool isOrigin(const PacketBuffer *pktBuf); quint64 neighborMac(const PacketBuffer *pktBuf); diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index af0301c..afc36f0 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -215,12 +215,12 @@ void DeviceManager::clearDeviceNeighbors() } void DeviceManager::getDeviceNeighbors( - OstProto::DeviceNeighborList *neighborList) + OstProto::PortNeighborList *neighborList) { int count = 0; foreach(Device *device, deviceList_) { - OstEmul::DeviceNeighbors *neighList = + OstEmul::DeviceNeighborList *neighList = neighborList->AddExtension(OstEmul::devices); neighList->set_device_index(count++); device->getNeighbors(neighList); @@ -296,11 +296,14 @@ void DeviceManager::enumerateDevices( Operation oper) { Device dk(this); - OstEmul::VlanEmulation pbVlan = deviceGroup->GetExtension(OstEmul::vlan); - OstEmul::Device pbDevice = deviceGroup->GetExtension(OstEmul::device); + 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(); @@ -316,15 +319,15 @@ void DeviceManager::enumerateDevices( dk.setVlan(j, vlan.vlan_tag() + vlanAdd); } - for (uint k = 0; k < pbDevice.count(); k++) { + for (uint k = 0; k < deviceGroup->device_count(); k++) { Device *device; - quint64 macAdd = k * pbDevice.mac().step(); - quint32 ip4Add = k * pbDevice.ip4().step(); + quint64 macAdd = k * mac.step(); + quint32 ip4Add = k * ip4.step(); - dk.setMac(pbDevice.mac().address() + macAdd); - dk.setIp4(pbDevice.ip4().address() + ip4Add, - pbDevice.ip4().prefix_length(), - pbDevice.ip4().default_gateway()); + dk.setMac(mac.address() + macAdd); + dk.setIp4(ip4.address() + ip4Add, + ip4.prefix_length(), + ip4.default_gateway()); // TODO: fill in other pbDevice data diff --git a/server/devicemanager.h b/server/devicemanager.h index f77f6d6..df96a6f 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -53,7 +53,7 @@ public: void clearDeviceNeighbors(); void resolveDeviceNeighbor(PacketBuffer *pktBuf); - void getDeviceNeighbors(OstProto::DeviceNeighborList *neighborList); + void getDeviceNeighbors(OstProto::PortNeighborList *neighborList); quint64 deviceMacAddress(PacketBuffer *pktBuf); quint64 neighborMacAddress(PacketBuffer *pktBuf); diff --git a/server/myservice.cpp b/server/myservice.cpp index 62c0539..977b130 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -878,7 +878,7 @@ void MyService::clearDeviceNeighbors( void MyService::getDeviceNeighbors( ::google::protobuf::RpcController* controller, const ::OstProto::PortId* request, - ::OstProto::DeviceNeighborList* response, + ::OstProto::PortNeighborList* response, ::google::protobuf::Closure* done) { DeviceManager *devMgr; diff --git a/server/myservice.h b/server/myservice.h index 3d43568..c1955dc 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -145,7 +145,7 @@ public: virtual void getDeviceNeighbors( ::google::protobuf::RpcController* controller, const ::OstProto::PortId* request, - ::OstProto::DeviceNeighborList* response, + ::OstProto::PortNeighborList* response, ::google::protobuf::Closure* done); friend quint64 getDeviceMacAddress( diff --git a/test/emultest.py b/test/emultest.py index 4a7a3dc..31a165b 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -195,6 +195,12 @@ try: log.info('adding tx_stream %d' % stream_id.stream_id[0].id) drone.addStream(stream_id) + # ----------------------------------------------------------------- # + # delete all configuration on the DUT interfaces + # ----------------------------------------------------------------- # + sudo('ip address flush dev ' + dut_rx_port) + sudo('ip address flush dev ' + dut_tx_port) + # ================================================================= # # ----------------------------------------------------------------- # # TEST CASES @@ -227,12 +233,12 @@ try: dg = devgrp_cfg.device_group.add() dg.device_group_id.id = tx_dgid_list.device_group_id[0].id dg.core.name = "Host1" - d = dg.Extensions[emul.device] - d.count = num_devs - d.mac.address = 0x000102030a01 - d.ip4.address = 0x0a0a0165 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0101 + dg.device_count = num_devs + dg.Extensions[emul.mac].address = 0x000102030a01 + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0165 + ip.prefix_length = 24 + ip.default_gateway = 0x0a0a0101 drone.modifyDeviceGroup(devgrp_cfg) @@ -242,15 +248,17 @@ try: dg = devgrp_cfg.device_group.add() dg.device_group_id.id = rx_dgid_list.device_group_id[0].id dg.core.name = "Host1" - d = dg.Extensions[emul.device] - d.count = num_devs - d.mac.address = 0x000102030b01 - d.ip4.address = 0x0a0a0265 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0201 + dg.device_count = num_devs + dg.Extensions[emul.mac].address = 0x000102030b01 + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0265 + ip.prefix_length = 24 + ip.default_gateway = 0x0a0a0201 drone.modifyDeviceGroup(devgrp_cfg) + s = raw_input('Press [Enter] to continue') + # configure the tx stream stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) From 7daf75c95a7c80811bd2b8816081eac09b40b567 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 11 Nov 2015 13:05:15 +0530 Subject: [PATCH 010/121] Feature (contd.): Device Emulation - added getDeviceList() RPC --- common/emulproto.proto | 14 ++++++-------- common/protocol.proto | 10 +++++++++- server/device.cpp | 12 +++++++++++- server/device.h | 5 ++++- server/devicemanager.cpp | 12 ++++++++++++ server/devicemanager.h | 2 ++ server/myservice.cpp | 36 ++++++++++++++++++++++++++++++++++++ server/myservice.h | 6 ++++++ test/emultest.py | 25 +++++++++++++++---------- 9 files changed, 101 insertions(+), 21 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 6712882..4e97597 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -83,24 +83,21 @@ extend OstProto.DeviceGroup { optional Ip6Emulation ip6 = 3001; } -/* TODO message Device { optional uint64 mac = 1; - // FIXME: move vlan into encapInfo? repeated uint32 vlan = 2; // includes tpid 'n vlan tag - optional uint32 ip4 = 3; - optional uint32 ip4_prefix_length = 4; - optional uint32 ip4_default_gateway = 5; + optional uint32 ip4 = 10; + optional uint32 ip4_prefix_length = 11; + optional uint32 ip4_default_gateway = 12; // TODO: IPv6 fields } -message DeviceList { - repeated Device devices = 1; +extend OstProto.PortDeviceList { + repeated Device port_device = 100; } -*/ message ArpEntry { optional uint32 ip4 = 1; @@ -120,5 +117,6 @@ message DeviceNeighborList { } extend OstProto.PortNeighborList { + // FIXME: rename to device (singular) to be consistent with naming convention for other repeated fields repeated DeviceNeighborList devices = 100; } diff --git a/common/protocol.proto b/common/protocol.proto index a34426e..fff0317 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -281,6 +281,8 @@ message Notification { * FIXME: What will be the contents of a default device created by addDeviceGroup()? * FIXME: decide default values for device and protoEmulations * FIXME: rename 'DeviceGroup'? + * FIXME: merge getDeviceList() and get DeviceNeighbors() into a single + * getDeviceInfo() RPC? */ message DeviceGroupId { required uint32 id = 1; @@ -308,6 +310,12 @@ message DeviceGroupConfigList { repeated DeviceGroup device_group = 2; } +message PortDeviceList { + required PortId port_id = 1; + + extensions 100 to 199; +} + message PortNeighborList { required PortId port_id = 1; @@ -344,7 +352,7 @@ service OstService { rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack); rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack); - // TODO: rpc getDeviceList(PortId) returns (DeviceList); + rpc getDeviceList(PortId) returns (PortDeviceList); rpc resolveDeviceNeighbors(PortIdList) returns (Ack); rpc clearDeviceNeighbors(PortIdList) returns (Ack); diff --git a/server/device.cpp b/server/device.cpp index edaaa1d..a6c0f95 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -27,7 +27,6 @@ along with this program. If not, see #include const int kBaseHex = 16; -const int kMaxVlan = 4; const quint64 kBcastMac = 0xffffffffffffULL; /* @@ -92,6 +91,17 @@ void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) ip4Gateway_ = gateway; } +void Device::getConfig(OstEmul::Device *deviceConfig) +{ + for (int i = 0; i < numVlanTags_; i++) + deviceConfig->add_vlan(vlan_[i]); + + deviceConfig->set_mac(mac_); + deviceConfig->set_ip4(ip4_); + deviceConfig->set_ip4_prefix_length(ip4PrefixLength_); + deviceConfig->set_ip4_default_gateway(ip4Gateway_); +} + QString Device::config() { return QString("") diff --git a/server/device.h b/server/device.h index 2976d99..a18c940 100644 --- a/server/device.h +++ b/server/device.h @@ -40,6 +40,7 @@ public: quint64 mac(); void setMac(quint64 mac); void setIp4(quint32 address, int prefixLength, quint32 gateway); + void getConfig(OstEmul::Device *deviceConfig); QString config(); DeviceKey key(); @@ -63,10 +64,12 @@ private: // methods void sendArpRequest(PacketBuffer *pktBuf); private: // data + static const int kMaxVlan = 4; + DeviceManager *deviceManager_; int numVlanTags_; - quint16 vlan_[4]; // FIXME: vlan tpid + quint16 vlan_[kMaxVlan]; // FIXME: vlan tpid quint64 mac_; quint32 ip4_; int ip4PrefixLength_; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index afc36f0..e55d68e 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -134,6 +134,18 @@ int DeviceManager::deviceCount() return deviceList_.size(); } +void DeviceManager::getDeviceList( + OstProto::PortDeviceList *deviceList) +{ + int count = 0; + + foreach(Device *device, deviceList_) { + OstEmul::Device *dev = + deviceList->AddExtension(OstEmul::port_device); + device->getConfig(dev); + } +} + void DeviceManager::receivePacket(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); diff --git a/server/devicemanager.h b/server/devicemanager.h index df96a6f..1a9b857 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -47,6 +47,7 @@ public: bool modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup); int deviceCount(); + void getDeviceList(OstProto::PortDeviceList *deviceList); void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); @@ -57,6 +58,7 @@ public: quint64 deviceMacAddress(PacketBuffer *pktBuf); quint64 neighborMacAddress(PacketBuffer *pktBuf); + private: enum Operation { kAdd, kDelete }; diff --git a/server/myservice.cpp b/server/myservice.cpp index 977b130..0271dbe 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -823,6 +823,36 @@ _exit: done->Run(); } +void MyService::getDeviceList( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::PortDeviceList* 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(); + devMgr->getDeviceList(response); + portLock[portId]->unlock(); + + done->Run(); + return; + +_invalid_port: + controller->SetFailed("Invalid Port Id"); + done->Run(); +} + void MyService::resolveDeviceNeighbors( ::google::protobuf::RpcController* controller, const ::OstProto::PortIdList* request, @@ -905,6 +935,12 @@ _invalid_port: done->Run(); } +/* + * =================================================================== + * Friends + * TODO: Encap these global functions into a DeviceBroker singleton? + * =================================================================== + */ quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex) { MyService *service = drone->rpcService(); diff --git a/server/myservice.h b/server/myservice.h index c1955dc..acd8c63 100644 --- a/server/myservice.h +++ b/server/myservice.h @@ -132,6 +132,12 @@ public: ::OstProto::Ack* response, ::google::protobuf::Closure* done); + virtual void getDeviceList( + ::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::PortDeviceList* response, + ::google::protobuf::Closure* done); + virtual void resolveDeviceNeighbors( ::google::protobuf::RpcController* controller, const ::OstProto::PortIdList* request, diff --git a/test/emultest.py b/test/emultest.py index 31a165b..8851c55 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -257,8 +257,6 @@ try: drone.modifyDeviceGroup(devgrp_cfg) - s = raw_input('Press [Enter] to continue') - # configure the tx stream stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) @@ -348,29 +346,36 @@ try: # retrieve and verify ARP Table on tx/rx ports log.info('retrieving ARP entries on tx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] - # TODO: verify gateway IP is resolved for each device - # FIXME: needs device ip as part of neigh_list log.info('ARP Table on tx port') - for device in devices: + for dev_cfg, device in zip(device_config, devices): + resolved = False for arp in device.arp: # TODO: pretty print ip and mac - print('%d: %08x %012x' % - (device.device_index, arp.ip4, arp.mac)) + print('%08x: %08x %012x' % + (dev_cfg.ip4, arp.ip4, arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + if not resolved: + fail = fail + 1 log.info('retrieving ARP entries on rx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] log.info('ARP Table on rx port') - for device in devices: + for dev_cfg, device in zip(device_config, devices): # verify *no* ARPs learnt on rx port if len(device.arp): fail = fail + 1 for arp in device.arp: # TODO: pretty print ip and mac - print('%d: %08x %012x' % - (device.device_index, arp.ip4, arp.mac)) + print('%08x: %08x %012x' % + (dev_cfg.ip4, arp.ip4, arp.mac)) drone.startCapture(rx_port) drone.startTransmit(tx_port) From 3a5396c865d57a15d951d6bd52505d225c2e2331 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 14 Nov 2015 17:06:43 +0530 Subject: [PATCH 011/121] Feature (contd.): Device Emulation - Got rid of a bunch of FIXMEs and all trailing whitespace (in the code added for this feature) --- server/abstractport.h | 3 ++ server/device.cpp | 16 +++++----- server/devicemanager.cpp | 15 ++++++---- server/devicemanager.h | 4 +-- server/drone.pro | 2 +- server/myservice.cpp | 25 +++++----------- server/pcapport.cpp | 65 +++++++++++++++++++--------------------- server/pcapport.h | 11 ++++--- test/emultest.py | 58 +++++++++++++++++------------------ 9 files changed, 94 insertions(+), 105 deletions(-) diff --git a/server/abstractport.h b/server/abstractport.h index 281dd00..8f3c7e7 100644 --- a/server/abstractport.h +++ b/server/abstractport.h @@ -30,6 +30,9 @@ class StreamBase; class PacketBuffer; class QIODevice; +// TODO: send notification back to client(s) +#define notify qWarning + class AbstractPort { public: diff --git a/server/device.cpp b/server/device.cpp index a6c0f95..804bf0d 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -30,9 +30,9 @@ const int kBaseHex = 16; const quint64 kBcastMac = 0xffffffffffffULL; /* - * NOTE: + * NOTE: * 1. Device Key is (VLANS + MAC) - is assumed to be unique for a device - * 2. Device clients/users (viz. DeviceManager) should take care when + * 2. Device clients/users (viz. DeviceManager) should take care when * setting params that change the key, if the key is used elsewhere * (e.g. in a hash) */ @@ -117,14 +117,14 @@ DeviceKey Device::key() } void Device::clearKey() -{ +{ key_.fill(0, kMaxVlan * sizeof(quint16) + sizeof(quint64)); } int Device::encapSize() { // ethernet header + vlans - int size = 14 + 4*numVlanTags_; + int size = 14 + 4*numVlanTags_; return size; } @@ -132,7 +132,7 @@ int Device::encapSize() void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) { int ofs; - quint64 srcMac = mac_; + quint64 srcMac = mac_; uchar *p = pktBuf->push(encapSize()); if (!p) { @@ -306,7 +306,7 @@ void Device::receiveArp(PacketBuffer *pktBuf) // Extract tgtIp first to check quickly if this packet is for us or not tgtIp = qFromBigEndian(pktData + 24); if (tgtIp != ip4_) { - qDebug("tgtIp %s is not me %s", + qDebug("tgtIp %s is not me %s", qPrintable(QHostAddress(tgtIp).toString()), qPrintable(QHostAddress(ip4_).toString())); return; @@ -355,7 +355,7 @@ void Device::receiveArp(PacketBuffer *pktBuf) arpTable.insert(srcIp, srcMac); rspPkt = new PacketBuffer; - rspPkt->reserve(encapSize()); + rspPkt->reserve(encapSize()); pktData = rspPkt->put(28); if (pktData) { // HTYP, PTYP @@ -376,7 +376,7 @@ void Device::receiveArp(PacketBuffer *pktBuf) transmitPacket(rspPkt); qDebug("Sent ARP Reply for srcIp/tgtIp=%s/%s", - qPrintable(QHostAddress(srcIp).toString()), + qPrintable(QHostAddress(srcIp).toString()), qPrintable(QHostAddress(tgtIp).toString())); break; case 2: // ARP Response diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index e55d68e..e4bf9ff 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -32,7 +32,8 @@ along with this program. If not, see const quint64 kBcastMac = 0xffffffffffffULL; -// FIXME: add lock to protect deviceGroupList_ operations? +// 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) { @@ -75,7 +76,7 @@ bool DeviceManager::addDeviceGroup(uint deviceGroupId) OstProto::DeviceGroup *deviceGroup; if (deviceGroupList_.contains(deviceGroupId)) { - qWarning("%s: deviceGroup id %u already exists", __FUNCTION__, + qWarning("%s: deviceGroup id %u already exists", __FUNCTION__, deviceGroupId); return false; } @@ -97,7 +98,7 @@ bool DeviceManager::deleteDeviceGroup(uint deviceGroupId) { OstProto::DeviceGroup *deviceGroup; if (!deviceGroupList_.contains(deviceGroupId)) { - qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, + qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, deviceGroupId); return false; } @@ -137,8 +138,6 @@ int DeviceManager::deviceCount() void DeviceManager::getDeviceList( OstProto::PortDeviceList *deviceList) { - int count = 0; - foreach(Device *device, deviceList_) { OstEmul::Device *dev = deviceList->AddExtension(OstEmul::port_device); @@ -160,7 +159,11 @@ void DeviceManager::receivePacket(PacketBuffer *pktBuf) // We assume pkt is ethernet // TODO: extend for other link layer types - // FIXME: validate before extracting if the offset is within pktLen + // 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); diff --git a/server/devicemanager.h b/server/devicemanager.h index 1a9b857..8f2ca2f 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -32,7 +32,7 @@ namespace OstProto { class DeviceGroup; }; -class DeviceManager +class DeviceManager { public: DeviceManager(AbstractPort *parent = 0); @@ -66,7 +66,7 @@ private: void enumerateDevices( const OstProto::DeviceGroup *deviceGroup, Operation oper); - + AbstractPort *port_; QHash deviceGroupList_; QHash deviceList_; diff --git a/server/drone.pro b/server/drone.pro index 9ba5046..42f324e 100644 --- a/server/drone.pro +++ b/server/drone.pro @@ -45,7 +45,7 @@ SOURCES += \ winpcapport.cpp SOURCES += myservice.cpp SOURCES += pcapextra.cpp -SOURCES += packetbuffer.cpp +SOURCES += packetbuffer.cpp QMAKE_DISTCLEAN += object_script.* diff --git a/server/myservice.cpp b/server/myservice.cpp index 0271dbe..2c5fc62 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -599,12 +599,15 @@ _invalid_version: 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? + * =================================================================== + * XXX: Streams and Devices are largely non-overlapping from a RPC + * point of view but they *do* intersect e.g. when a stream is trying to + * find its associated device and info from that device such as src/dst + * mac addresses. For this reason, both set of RPCs share the common + * port level locking * =================================================================== */ void MyService::getDeviceGroupIdList( @@ -699,10 +702,8 @@ void MyService::addDeviceGroup( 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_group_id_size(); i++) @@ -723,11 +724,9 @@ void MyService::addDeviceGroup( done->Run(); return; -#if 0 // FIXME: needed? _port_busy: controller->SetFailed("Port Busy"); goto _exit; -#endif _invalid_port: controller->SetFailed("invalid portid"); @@ -752,10 +751,8 @@ void MyService::deleteDeviceGroup( 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_group_id_size(); i++) @@ -767,11 +764,9 @@ void MyService::deleteDeviceGroup( done->Run(); return; -#if 0 // FIXME: needed? _port_busy: controller->SetFailed("Port Busy"); goto _exit; -#endif _invalid_port: controller->SetFailed("invalid portid"); _exit: @@ -795,28 +790,22 @@ void MyService::modifyDeviceGroup( 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_group_size(); i++) devMgr->modifyDeviceGroup(&request->device_group(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: diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 642b514..8b372d8 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -84,7 +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_); + emulXcvr_ = new EmulationTransceiver(device, deviceManager_); if (!monitorRx_->handle() || !monitorTx_->handle()) isUsable_ = false; @@ -137,7 +137,7 @@ PcapPort::~PcapPort() if (monitorTx_) monitorTx_->stop(); - delete receiver_; + delete emulXcvr_; delete capturer_; delete transmitter_; @@ -174,17 +174,17 @@ void PcapPort::updateNotes() void PcapPort::startDeviceEmulation() { - receiver_->start(); + emulXcvr_->start(); } void PcapPort::stopDeviceEmulation() { - receiver_->stop(); + emulXcvr_->stop(); } int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf) { - return receiver_->transmitPacket(pktBuf); + return emulXcvr_->transmitPacket(pktBuf); } @@ -895,10 +895,10 @@ QFile* PcapPort::PortCapturer::captureFile() /* * ------------------------------------------------------------------- * - * Port Receiver + * Transmit+Receiver for Device/ProtocolEmulation * ------------------------------------------------------------------- * */ -PcapPort::PortReceiver::PortReceiver(const char *device, +PcapPort::EmulationTransceiver::EmulationTransceiver(const char *device, DeviceManager *deviceManager) { device_ = QString::fromAscii(device); @@ -908,19 +908,19 @@ PcapPort::PortReceiver::PortReceiver(const char *device, handle_ = NULL; } -PcapPort::PortReceiver::~PortReceiver() +PcapPort::EmulationTransceiver::~EmulationTransceiver() { stop(); } -void PcapPort::PortReceiver::run() +void PcapPort::EmulationTransceiver::run() { int flags = PCAP_OPENFLAG_PROMISCUOUS; char errbuf[PCAP_ERRBUF_SIZE] = ""; struct bpf_program bpf; const char *capture_filter = "arp or (vlan and arp)"; const int optimize = 1; - + qDebug("In %s", __PRETTY_FUNCTION__); #ifdef Q_OS_WIN32 @@ -931,10 +931,10 @@ _retry: // FIXME: use 0 timeout value? #ifdef Q_OS_WIN32 // NOCAPTURE_LOCAL needs windows only pcap_open() - handle_ = pcap_open(qPrintable(device_), 65535, + handle_ = pcap_open(qPrintable(device_), 65535, flags, 1000 /* ms */, NULL, errbuf); #else - handle_ = pcap_open_live(qPrintable(device_), 65535, + handle_ = pcap_open_live(qPrintable(device_), 65535, flags, 1000 /* ms */, errbuf); #endif @@ -942,14 +942,12 @@ _retry: { if (flags && QString(errbuf).contains("promiscuous")) { - // FIXME: warn user that device emulation will not work - qDebug("%s:can't set promiscuous mode, trying non-promisc", - qPrintable(device_)); - flags &= ~PCAP_OPENFLAG_PROMISCUOUS; - goto _retry; + notify("Unable to set promiscuous mode on <%s> - " + "device emulation will not work", qPrintable(device_)); + goto _exit; } #ifdef Q_OS_WIN32 - else if ((flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL) + else if ((flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL) && QString(errbuf).contains("loopback")) { qDebug("Can't set no local capture mode %s", qPrintable(device_)); @@ -959,22 +957,21 @@ _retry: #endif else { - // FIXME: warn user that device emulation will not work - qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, - device_.toAscii().constData(), errbuf); + notify("Unable to open <%s> [%s] - device emulation will not work", + qPrintable(device_), errbuf); goto _exit; } } // FIXME: hardcoded filter - if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0) + if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0) { qWarning("%s: error compiling filter: %s", qPrintable(device_), pcap_geterr(handle_)); goto _skip_filter; } - if (pcap_setfilter(handle_, &bpf) < 0) + if (pcap_setfilter(handle_, &bpf) < 0) { qWarning("%s: error setting filter: %s", qPrintable(device_), pcap_geterr(handle_)); @@ -992,32 +989,32 @@ _skip_filter: ret = pcap_next_ex(handle_, &hdr, &data); switch (ret) { - case 1: + 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 + // owned by libpcap which does not guarantee data will // persist across calls to pcap_next_ex() - deviceManager_->receivePacket(pktBuf); + deviceManager_->receivePacket(pktBuf); break; } case 0: // timeout: just go back to the loop break; case -1: - qWarning("%s: error reading packet (%d): %s", + 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__, + qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret); } - if (stop_) + if (stop_) { qDebug("user requested receiver stop\n"); break; @@ -1031,9 +1028,8 @@ _exit: state_ = kFinished; } -void PcapPort::PortReceiver::start() +void PcapPort::EmulationTransceiver::start() { - // FIXME: return error if (state_ == kRunning) { qWarning("Receive start requested but is already running!"); return; @@ -1046,7 +1042,7 @@ void PcapPort::PortReceiver::start() QThread::msleep(10); } -void PcapPort::PortReceiver::stop() +void PcapPort::EmulationTransceiver::stop() { if (state_ == kRunning) { stop_ = true; @@ -1054,18 +1050,17 @@ void PcapPort::PortReceiver::stop() QThread::msleep(10); } else { - // FIXME: return error qWarning("Receive stop requested but is not running!"); return; } } -bool PcapPort::PortReceiver::isRunning() +bool PcapPort::EmulationTransceiver::isRunning() { return (state_ == kRunning); } -int PcapPort::PortReceiver::transmitPacket(PacketBuffer *pktBuf) +int PcapPort::EmulationTransceiver::transmitPacket(PacketBuffer *pktBuf) { return pcap_sendpacket(handle_, pktBuf->data(), pktBuf->length()); } diff --git a/server/pcapport.h b/server/pcapport.h index 5ed0254..f0ccde0 100644 --- a/server/pcapport.h +++ b/server/pcapport.h @@ -225,12 +225,11 @@ protected: volatile State state_; }; - // FIXME: rename? not just a 'receiver' but also 'transmitter'! - class PortReceiver: public QThread + class EmulationTransceiver: public QThread { public: - PortReceiver(const char *device, DeviceManager *deviceManager); - ~PortReceiver(); + EmulationTransceiver(const char *device, DeviceManager *deviceManager); + ~EmulationTransceiver(); void run(); void start(); void stop(); @@ -238,7 +237,7 @@ protected: int transmitPacket(PacketBuffer *pktBuf); private: - enum State + enum State { kNotStarted, kRunning, @@ -260,7 +259,7 @@ protected: private: PortTransmitter *transmitter_; PortCapturer *capturer_; - PortReceiver *receiver_; + EmulationTransceiver *emulXcvr_; static pcap_if_t *deviceList_; }; diff --git a/test/emultest.py b/test/emultest.py index 8851c55..4402f27 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -23,7 +23,7 @@ use_defaults = False # initialize defaults - drone host_name = '127.0.0.1' tx_port_number = -1 -rx_port_number = -1 +rx_port_number = -1 if sys.platform == 'win32': tshark = r'C:\Program Files\Wireshark\tshark.exe' @@ -94,7 +94,7 @@ try: sudo('sysctl -w net.ipv4.ip_forward=1') # connect to drone - log.info('connecting to drone(%s:%d)' + log.info('connecting to drone(%s:%d)' % (drone.hostName(), drone.portNumber())) drone.connect() @@ -115,7 +115,7 @@ try: 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 + # 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 @@ -180,7 +180,7 @@ try: drone.addDeviceGroup(rx_dgid_list) # ----------------------------------------------------------------- # - # create stream on tx port - each test case will modify and reuse + # create stream on tx port - each test case will modify and reuse # this stream as per its needs # ----------------------------------------------------------------- # @@ -203,7 +203,7 @@ try: # ================================================================= # # ----------------------------------------------------------------- # - # TEST CASES + # TEST CASES # ----------------------------------------------------------------- # # ================================================================= # @@ -280,10 +280,10 @@ try: ip = p.Extensions[ip4] ip.src_ip = 0x0a0a0165 ip.src_ip_mode = Ip4.e_im_inc_host - ip.src_ip_count = num_devs + ip.src_ip_count = num_devs ip.dst_ip = 0x0a0a0265 ip.dst_ip_mode = Ip4.e_im_inc_host - ip.dst_ip_count = num_devs + ip.dst_ip_count = num_devs s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber @@ -443,31 +443,31 @@ try: for i in range(num_vlans): vlan_id = vlan_base+i vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) - vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) + vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) + vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) - sudo('ip netns add ' + vrf) + sudo('ip netns add ' + vrf) - sudo('ip link add link ' + dut_rx_port + sudo('ip link add link ' + dut_rx_port + ' name ' + vlan_rx_dev + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_rx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf + sudo('ip link set ' + vlan_rx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + ' ip addr add 10.1.1.1/24' + ' dev ' + vlan_rx_dev) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip link set ' + vlan_rx_dev + ' up') - sudo('ip link add link ' + dut_tx_port + sudo('ip link add link ' + dut_tx_port + ' name ' + vlan_tx_dev + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_tx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf + sudo('ip link set ' + vlan_tx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + ' ip addr add 10.1.2.1/24' + ' dev ' + vlan_tx_dev) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip link set ' + vlan_tx_dev + ' up') @@ -510,7 +510,7 @@ try: # configure the tx stream(s) # we need more than one stream, so delete old one - # and create as many as we need + # and create as many as we need # FIXME: restore the single stream at end? log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) drone.deleteStream(stream_id) @@ -529,7 +529,7 @@ try: s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id s.core.is_enabled = True - s.core.ordinal = i + s.core.ordinal = i s.control.packets_per_sec = 10 s.control.num_packets = num_devs @@ -572,14 +572,14 @@ try: for i in range(num_vlans): vlan_id = vlan_base + i vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) - vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) + vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) + vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip neigh flush dev ' + vlan_rx_dev) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip neigh flush dev ' + vlan_tx_dev) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip neigh show') drone.startCapture(rx_port) @@ -618,13 +618,13 @@ try: # show arp on DUT for i in range(num_vlans): vrf = 'v' + str(vlan_base + i) - sudo('ip netns exec ' + vrf + sudo('ip netns exec ' + vrf + ' ip neigh show') # un-configure the DUT for i in range(num_vlans): vlan_id = vlan_base + i vrf = 'v' + str(vlan_id) - sudo('ip netns delete ' + vrf) + sudo('ip netns delete ' + vrf) suite.test_end(passed) # TODO: From cad62c1fd70cf217966917863f1118054f1ece9e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 29 Nov 2015 12:52:08 +0530 Subject: [PATCH 012/121] Feature (contd.): Device Emulatiom - converted emulation tests to use the py.test framework --- common/emulproto.proto | 1 + common/protocol.proto | 3 +- test/emultest.py | 1017 +++++++++++++++++++++------------------- 3 files changed, 533 insertions(+), 488 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 4e97597..1eb3016 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -75,6 +75,7 @@ message Ip6Emulation { } extend OstProto.DeviceGroup { + // FIXME: add directly to DeviceGroup in protocol.proto optional EncapEmulation encap = 2000; optional MacEmulation mac = 2001; diff --git a/common/protocol.proto b/common/protocol.proto index fff0317..f3d4d58 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -300,7 +300,8 @@ message DeviceGroupIdList { message DeviceGroup { required DeviceGroupId device_group_id = 1; optional DeviceGroupCore core = 2; - optional uint32 device_count = 3 [default = 1]; + // FIXME: add Encap here + optional uint32 device_count = 3 [default = 1]; // per-encap extensions 2000 to 5999; // For use by Protocol Emulations } diff --git a/test/emultest.py b/test/emultest.py index 4402f27..b0dc78c 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -8,8 +8,10 @@ import subprocess import sys import time +import pytest + from fabric.api import run, env, sudo -from harness import Test, TestSuite, TestPreRequisiteError +#from harness import Test, TestSuite, TestPreRequisiteError sys.path.insert(1, '../binding') from core import ost_pb, emul, DroneProxy @@ -18,25 +20,21 @@ from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 from protocols.vlan_pb2 import vlan -use_defaults = False - -# initialize defaults - drone -host_name = '127.0.0.1' -tx_port_number = -1 -rx_port_number = -1 +use_defaults = True if sys.platform == 'win32': tshark = r'C:\Program Files\Wireshark\tshark.exe' else: tshark = 'tshark' +# initialize defaults - drone +host_name = '127.0.0.1' + # 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' dut_dst_mac = 0x0800278df2b4 #FIXME: hardcoding @@ -55,8 +53,7 @@ if len(sys.argv) > 1: print(' -h --help show this help') sys.exit(0) -print('') -print('This test uses the following topology -') +print('This module uses the following topology -') print('') print(' +-------+ +-------+') print(' | |Tx--->---Rx|-+ |') @@ -69,144 +66,210 @@ 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 s = raw_input('DUT\'s Hostname/IP [%s]: ' % (env.host_string)) env.host_string = s or env.host_string - -drone = DroneProxy(host_name) - -try: - # ----------------------------------------------------------------- # - # Baseline Configuration for subsequent testcases - # NOTES - # * All test cases will emulate devices on both rx and tx ports - # * Each test case will create traffic streams corresponding to - # the devices to check - # ----------------------------------------------------------------- # - - # FIXME: get inputs for dut rx/tx ports - # Enable IP forwarding on the DUT (aka make it a router) - sudo('sysctl -w net.ipv4.ip_forward=1') +# ================================================================= # +# ----------------------------------------------------------------- # +# FIXTURES +# ----------------------------------------------------------------- # +# ================================================================= # +# ----------------------------------------------------------------- # +# Baseline Configuration for subsequent testcases +# NOTES +# * All test cases will emulate devices on both rx and tx ports +# * Each test case will create traffic streams corresponding to +# the devices to check +# ----------------------------------------------------------------- # - # connect to drone - log.info('connecting to drone(%s:%d)' - % (drone.hostName(), drone.portNumber())) - drone.connect() +@pytest.fixture(scope='module') +def drone(request): + drn = DroneProxy(host_name) - # retreive port id list - log.info('retreiving port list') + log.info('connecting to drone(%s:%d)' % (drn.hostName(), drn.portNumber())) + drn.connect() + + def fin(): + drn.disconnect() + request.addfinalizer(fin) + + return drn + +@pytest.fixture(scope='module') +def ports(request, drone): 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) + assert len(port_config_list.port) != 0 # print port list and find default tx/rx ports + tx_number = -1 + rx_number = -1 + print port_config_list 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 tx_number < 0: + tx_number = port.port_id.id + elif rx_number < 0: + rx_number = port.port_id.id if ('eth1' in port.name): - tx_port_number = port.port_id.id + tx_number = port.port_id.id if ('eth2' in port.name): - rx_port_number = port.port_id.id + rx_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) + assert tx_number >= 0 + assert rx_number >= 0 - p = raw_input('Rx Port Id [%d]: ' % (rx_port_number)) - if p: - rx_port_number = int(p) + print('Using port %d as tx port(s)' % tx_number) + print('Using port %d as rx port(s)' % rx_number) - if tx_port_number < 0 or rx_port_number < 0: - log.warning('invalid tx/rx port') - sys.exit(1) + ports.tx = ost_pb.PortIdList() + ports.tx.port_id.add().id = tx_number; - print('Using port %d as tx port(s)' % tx_port_number) - print('Using port %d as rx port(s)' % rx_port_number) + ports.rx = ost_pb.PortIdList() + ports.rx.port_id.add().id = rx_number; + return ports - tx_port = ost_pb.PortIdList() - tx_port.port_id.add().id = tx_port_number; +@pytest.fixture(scope='module') +def dut(request): + # Enable IP forwarding on the DUT (aka make it a router) + sudo('sysctl -w net.ipv4.ip_forward=1') - rx_port = ost_pb.PortIdList() - rx_port.port_id.add().id = rx_port_number; +@pytest.fixture(scope='module') +def dut_ports(request): + dut_ports.rx = 'eth1' + dut_ports.tx = 'eth2' + # delete all configuration on the DUT interfaces + sudo('ip address flush dev ' + dut_ports.rx) + sudo('ip address flush dev ' + dut_ports.tx) + return dut_ports + +@pytest.fixture(scope='module') +def emul_ports(request, drone, ports): + emul_ports = ost_pb.PortIdList() + emul_ports.port_id.add().id = ports.tx.port_id[0].id; + emul_ports.port_id.add().id = ports.rx.port_id[0].id; + return emul_ports + +@pytest.fixture(scope='module') +def dgid_list(request, drone, ports): # ----------------------------------------------------------------- # # create emulated device(s) on tx/rx ports - each test case will # modify and reuse these devices as per its needs # ----------------------------------------------------------------- # - emul_ports = ost_pb.PortIdList() - emul_ports.port_id.add().id = tx_port_number; - emul_ports.port_id.add().id = rx_port_number; - # delete existing devices, if any, on tx port - tx_dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) - drone.deleteDeviceGroup(tx_dgid_list) + dgid_list.tx = drone.getDeviceGroupIdList(ports.tx.port_id[0]) + drone.deleteDeviceGroup(dgid_list.tx) - # add a emulated device on tx port - tx_dgid_list = ost_pb.DeviceGroupIdList() - tx_dgid_list.port_id.CopyFrom(tx_port.port_id[0]) - tx_dgid_list.device_group_id.add().id = 1 - log.info('adding tx device_group %d' % tx_dgid_list.device_group_id[0].id) - drone.addDeviceGroup(tx_dgid_list) + # add a emulated device group on tx port + dgid_list.tx = ost_pb.DeviceGroupIdList() + dgid_list.tx.port_id.CopyFrom(ports.tx.port_id[0]) + dgid_list.tx.device_group_id.add().id = 1 + log.info('adding tx device_group %d' % dgid_list.tx.device_group_id[0].id) + drone.addDeviceGroup(dgid_list.tx) # delete existing devices, if any, on rx port - rx_dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) - drone.deleteDeviceGroup(rx_dgid_list) + dgid_list.rx = drone.getDeviceGroupIdList(ports.rx.port_id[0]) + drone.deleteDeviceGroup(dgid_list.rx) - # add a emulated device on rx port - rx_dgid_list = ost_pb.DeviceGroupIdList() - rx_dgid_list.port_id.CopyFrom(rx_port.port_id[0]) - rx_dgid_list.device_group_id.add().id = 1 - log.info('adding rx device_group %d' % rx_dgid_list.device_group_id[0].id) - drone.addDeviceGroup(rx_dgid_list) + # add a emulated device group on rx port + dgid_list.rx = ost_pb.DeviceGroupIdList() + dgid_list.rx.port_id.CopyFrom(ports.rx.port_id[0]) + dgid_list.rx.device_group_id.add().id = 1 + log.info('adding rx device_group %d' % dgid_list.rx.device_group_id[0].id) + drone.addDeviceGroup(dgid_list.rx) + def fin(): + dgid_list = drone.getDeviceGroupIdList(ports.tx.port_id[0]) + drone.deleteDeviceGroup(dgid_list) + dgid_list = drone.getDeviceGroupIdList(ports.rx.port_id[0]) + drone.deleteDeviceGroup(dgid_list) + request.addfinalizer(fin) + + return dgid_list + +@pytest.fixture(scope='module') +def stream_id(request, drone, ports): # ----------------------------------------------------------------- # # create stream on tx port - each test case will modify and reuse # this stream as per its needs # ----------------------------------------------------------------- # # delete existing streams, if any, on tx port - sid_list = drone.getStreamIdList(tx_port.port_id[0]) + sid_list = drone.getStreamIdList(ports.tx.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.port_id.CopyFrom(ports.tx.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) - # ----------------------------------------------------------------- # - # delete all configuration on the DUT interfaces - # ----------------------------------------------------------------- # - sudo('ip address flush dev ' + dut_rx_port) - sudo('ip address flush dev ' + dut_tx_port) + def fin(): + drone.deleteStream(stream_id) + request.addfinalizer(fin) - # ================================================================= # - # ----------------------------------------------------------------- # - # TEST CASES - # ----------------------------------------------------------------- # - # ================================================================= # + return stream_id +@pytest.fixture +def dut_vlans(request, dut_ports): + num_vlans = getattr(request.function, 'num_vlans') + vlan_base = getattr(request.function, 'vlan_base') + for i in range(num_vlans): + vlan_id = vlan_base+i + vrf = 'v' + str(vlan_id) + vlan_rx_dev = dut_ports.rx + '.' + str(vlan_id) + vlan_tx_dev = dut_ports.tx + '.' + str(vlan_id) + + sudo('ip netns add ' + vrf) + + sudo('ip link add link ' + dut_ports.rx + + ' name ' + vlan_rx_dev + + ' type vlan id ' + str(vlan_id)) + sudo('ip link set ' + vlan_rx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.1.1/24' + + ' dev ' + vlan_rx_dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + vlan_rx_dev + ' up') + + sudo('ip link add link ' + dut_ports.tx + + ' name ' + vlan_tx_dev + + ' type vlan id ' + str(vlan_id)) + sudo('ip link set ' + vlan_tx_dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.2.1/24' + + ' dev ' + vlan_tx_dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + vlan_tx_dev + ' up') + + def fin(): + for i in range(num_vlans): + vlan_id = vlan_base + i + vrf = 'v' + str(vlan_id) + sudo('ip netns delete ' + vrf) + request.addfinalizer(fin) + +# ================================================================= # +# ----------------------------------------------------------------- # +# TEST CASES +# ----------------------------------------------------------------- # +# ================================================================= # + +def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, + emul_ports, dgid_list): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) # DUT @@ -218,206 +281,188 @@ try: # Host1(s) Host2(s) # ----------------------------------------------------------------- # - passed = False - suite.test_begin('multiEmulDevNoVlan') - num_devs = 5 - try: - # configure the DUT - sudo('ip address add 10.10.1.1/24 dev ' + dut_rx_port) - sudo('ip address add 10.10.2.1/24 dev ' + dut_tx_port) - # configure the tx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = tx_dgid_list.device_group_id[0].id - dg.core.name = "Host1" - dg.device_count = num_devs - dg.Extensions[emul.mac].address = 0x000102030a01 - ip = dg.Extensions[emul.ip4] - ip.address = 0x0a0a0165 - ip.prefix_length = 24 - ip.default_gateway = 0x0a0a0101 + # configure the DUT + sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) + sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.tx) - drone.modifyDeviceGroup(devgrp_cfg) + # configure the tx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.tx.device_group_id[0].id + dg.core.name = "Host1" + dg.device_count = num_devs + dg.Extensions[emul.mac].address = 0x000102030a01 + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0165 + ip.prefix_length = 24 + ip.default_gateway = 0x0a0a0101 - # configure the rx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = rx_dgid_list.device_group_id[0].id - dg.core.name = "Host1" - dg.device_count = num_devs - dg.Extensions[emul.mac].address = 0x000102030b01 - ip = dg.Extensions[emul.ip4] - ip.address = 0x0a0a0265 - ip.prefix_length = 24 - ip.default_gateway = 0x0a0a0201 + drone.modifyDeviceGroup(devgrp_cfg) - drone.modifyDeviceGroup(devgrp_cfg) + # configure the rx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.rx.device_group_id[0].id + dg.core.name = "Host1" + dg.device_count = num_devs + dg.Extensions[emul.mac].address = 0x000102030b01 + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0265 + ip.prefix_length = 24 + ip.default_gateway = 0x0a0a0201 - # configure the tx 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.control.packets_per_sec = 20 - s.control.num_packets = 10 + drone.modifyDeviceGroup(devgrp_cfg) - # 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_mode = Mac.e_mm_resolve - p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve + # configure the tx stream + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.control.packets_per_sec = 100 + s.control.num_packets = 10 - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + # 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_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber - ip = p.Extensions[ip4] - ip.src_ip = 0x0a0a0165 - ip.src_ip_mode = Ip4.e_im_inc_host - ip.src_ip_count = num_devs - ip.dst_ip = 0x0a0a0265 - ip.dst_ip_mode = Ip4.e_im_inc_host - ip.dst_ip_count = num_devs + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber - s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber - s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = p.Extensions[ip4] + ip.src_ip = 0x0a0a0165 + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs + ip.dst_ip = 0x0a0a0265 + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs - log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) - drone.modifyStream(stream_cfg) + s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber - # clear tx/rx stats - log.info('clearing tx/rx stats') - drone.clearStats(tx_port) - drone.clearStats(rx_port) + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + drone.modifyStream(stream_cfg) - # clear arp on DUT - sudo('ip neigh flush all') - arp_cache = run('ip neigh show') - if re.search('10.10.2.10[1-5].*lladdr', arp_cache): - raise TestPreRequisiteError('ARP cache not cleared') + # FIXME(needed?): clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(ports.tx) + drone.clearStats(ports.rx) - # resolve ARP on tx/rx ports - log.info('resolving Neighbors on tx/rx ports ...') - drone.startCapture(emul_ports) - drone.clearDeviceNeighbors(emul_ports) - drone.resolveDeviceNeighbors(emul_ports) - time.sleep(10) - drone.stopCapture(emul_ports) + # clear arp on DUT + sudo('ip neigh flush all') + arp_cache = run('ip neigh show') + assert re.search('10.10.2.10[1-5].*lladdr', arp_cache) == None - fail = 0 + # resolve ARP on tx/rx ports + log.info('resolving Neighbors on tx/rx ports ...') + drone.startCapture(emul_ports) + drone.clearDeviceNeighbors(emul_ports) + drone.resolveDeviceNeighbors(emul_ports) + time.sleep(3) + drone.stopCapture(emul_ports) - # verify ARP Requests sent out from tx port - buff = drone.getCaptureBuffer(emul_ports.port_id[0]) - drone.saveCaptureBuffer(buff, 'capture.pcap') - log.info('dumping Tx capture buffer (all)') - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + # verify ARP Requests sent out from tx port + buff = drone.getCaptureBuffer(emul_ports.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Tx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Tx capture buffer (filtered)') + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.10.1.'+str(101+i)+')' + ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) print(cap_pkts) - log.info('dumping Tx capture buffer (filtered)') - for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.1.'+str(101+i)+')' - ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) - print(cap_pkts) - if cap_pkts.count('\n') != 1: - fail = fail + 1 + assert cap_pkts.count('\n') == 1 - # verify *no* ARP Requests sent out from rx port - buff = drone.getCaptureBuffer(emul_ports.port_id[1]) - drone.saveCaptureBuffer(buff, 'capture.pcap') - log.info('dumping Rx capture buffer (all)') - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + # verify *no* ARP Requests sent out from rx port + buff = drone.getCaptureBuffer(emul_ports.port_id[1]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.10.2.'+str(101+i)+')' + ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) print(cap_pkts) - log.info('dumping Rx capture buffer (filtered)') - for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.2.'+str(101+i)+')' - ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) - print(cap_pkts) - if cap_pkts.count('\n') != 0: - fail = fail + 1 + assert cap_pkts.count('\n') == 0 - # retrieve and verify ARP Table on tx/rx ports - log.info('retrieving ARP entries on tx port') - device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] - neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) - devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on tx port') - for dev_cfg, device in zip(device_config, devices): - resolved = False - for arp in device.arp: - # TODO: pretty print ip and mac - print('%08x: %08x %012x' % - (dev_cfg.ip4, arp.ip4, arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): - resolved = True - if not resolved: - fail = fail + 1 + # retrieve and verify ARP Table on tx/rx ports + log.info('retrieving ARP entries on tx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP Table on tx port') + for dev_cfg, device in zip(device_config, devices): + resolved = False + for arp in device.arp: + # TODO: pretty print ip and mac + print('%08x: %08x %012x' % + (dev_cfg.ip4, arp.ip4, arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved - log.info('retrieving ARP entries on rx port') - device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] - neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) - devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on rx port') - for dev_cfg, device in zip(device_config, devices): - # verify *no* ARPs learnt on rx port - if len(device.arp): - fail = fail + 1 - for arp in device.arp: - # TODO: pretty print ip and mac - print('%08x: %08x %012x' % - (dev_cfg.ip4, arp.ip4, arp.mac)) + log.info('retrieving ARP entries on rx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP Table on rx port') + for dev_cfg, device in zip(device_config, devices): + # verify *no* ARPs learnt on rx port + assert len(device.arp) == 0 + for arp in device.arp: + # TODO: pretty print ip and mac + print('%08x: %08x %012x' % + (dev_cfg.ip4, arp.ip4, arp.mac)) - drone.startCapture(rx_port) - drone.startTransmit(tx_port) - log.info('waiting for transmit to finish ...') - time.sleep(7) - drone.stopTransmit(tx_port) - drone.stopCapture(rx_port) + # We are all set now - so transmit the stream now + drone.startCapture(ports.rx) + drone.startTransmit(ports.tx) + log.info('waiting for transmit to finish ...') + time.sleep(3) + drone.stopTransmit(ports.tx) + drone.stopCapture(ports.rx) - buff = drone.getCaptureBuffer(rx_port.port_id[0]) - drone.saveCaptureBuffer(buff, 'capture.pcap') - log.info('dumping Rx capture buffer (all)') - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + buff = drone.getCaptureBuffer(ports.rx.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + for i in range(num_devs): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' + ' && (ip.dst == 10.10.2.' + str(101+i) + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+i, '02x')+')']) print(cap_pkts) - log.info('dumping Rx capture buffer (filtered)') - for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' - ' && (ip.dst == 10.10.2.' + str(101+i) + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+i, '02x')+')']) - print(cap_pkts) - if cap_pkts.count('\n') != s.control.num_packets/ip.src_ip_count: - fail = fail + 1 - if fail == 0: - passed = True - else: - log.info('failed checks: %d' % fail) - os.remove('capture.pcap') - except RpcError as e: - raise - finally: - drone.stopTransmit(tx_port) - run('ip neigh show') - # unconfigure DUT - sudo('ip address delete 10.10.1.1/24 dev ' + dut_rx_port) - sudo('ip address delete 10.10.2.1/24 dev ' + dut_tx_port) - suite.test_end(passed) + assert cap_pkts.count('\n') == s.control.num_packets/ip.src_ip_count + os.remove('capture.pcap') - sys.exit(1) - # FIXME: update the below test cases to resolve Neighbors and streams - # to derive src/dst mac from device + drone.stopTransmit(ports.tx) + run('ip neigh show') + # unconfigure DUT + sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx) + sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx) + +def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, + emul_ports, dgid_list): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # @@ -431,234 +476,232 @@ try: # Host1(s) Host2(s) # ----------------------------------------------------------------- # - passed = False - suite.test_begin('multiEmulDevPerVlan') + test_multiEmulDevPerVlan.num_vlans = num_vlans = 5 + test_multiEmulDevPerVlan.vlan_base = vlan_base = 201 + num_devs_per_vlan = 3 + tx_ip_base = 0x0a010165 + rx_ip_base = 0x0a010265 - num_vlans = 5 - vlan_base = 201 - num_devs = 3 - dev_ip_base = 101 - try: - # configure the DUT - for i in range(num_vlans): - vlan_id = vlan_base+i - vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) - vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) + # configure vlans on the DUT + dut_vlans(request, dut_ports) - sudo('ip netns add ' + vrf) + # configure the tx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.tx.device_group_id[0].id + dg.core.name = "Host1" + v = dg.Extensions[emul.encap].vlan.stack.add() + v.vlan_tag = vlan_base + v.count = num_vlans + dg.device_count = num_devs_per_vlan + dg.Extensions[emul.mac].address = 0x000102030a01 + ip = dg.Extensions[emul.ip4] + ip.address = tx_ip_base + ip.prefix_length = 24 + ip.default_gateway = (tx_ip_base & 0xffffff00) | 0x01 - sudo('ip link add link ' + dut_rx_port - + ' name ' + vlan_rx_dev - + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_rx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf - + ' ip addr add 10.1.1.1/24' - + ' dev ' + vlan_rx_dev) - sudo('ip netns exec ' + vrf - + ' ip link set ' + vlan_rx_dev + ' up') + drone.modifyDeviceGroup(devgrp_cfg) - sudo('ip link add link ' + dut_tx_port - + ' name ' + vlan_tx_dev - + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_tx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf - + ' ip addr add 10.1.2.1/24' - + ' dev ' + vlan_tx_dev) - sudo('ip netns exec ' + vrf - + ' ip link set ' + vlan_tx_dev + ' up') + # configure the rx device(s) + devgrp_cfg = ost_pb.DeviceGroupConfigList() + devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) + dg = devgrp_cfg.device_group.add() + dg.device_group_id.id = dgid_list.rx.device_group_id[0].id + dg.core.name = "Host1" + v = dg.Extensions[emul.encap].vlan.stack.add() + v.vlan_tag = vlan_base + v.count = num_vlans + dg.device_count = num_devs_per_vlan + dg.Extensions[emul.mac].address = 0x000102030b01 + ip = dg.Extensions[emul.ip4] + ip.address = rx_ip_base + ip.prefix_length = 24 + ip.default_gateway = (rx_ip_base & 0xffffff00) | 0x01 + drone.modifyDeviceGroup(devgrp_cfg) - # configure the tx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(tx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = tx_dgid_list.device_group_id[0].id - dg.core.name = "Host1" - v = dg.Extensions[emul.vlan].stack.add() - v.vlan_tag = vlan_base - v.count = num_vlans - d = dg.Extensions[emul.device] - d.count = num_devs - d.mac.address = 0x000102030a01 - d.ip4.address = 0x0a010165 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0101 - - drone.modifyDeviceGroup(devgrp_cfg) - - # configure the rx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(rx_port.port_id[0]) - dg = devgrp_cfg.device_group.add() - dg.device_group_id.id = rx_dgid_list.device_group_id[0].id - dg.core.name = "Host1" - v = dg.Extensions[emul.vlan].stack.add() - v.vlan_tag = vlan_base - v.count = num_vlans - d = dg.Extensions[emul.device] - d.count = num_devs - #d.mode = emul.Device.kNoRepeat - d.mac.address = 0x000102030b01 - d.ip4.address = 0x0a010265 - d.ip4.prefix_length = 24 - d.ip4.default_gateway = 0x0a0a0201 - - drone.modifyDeviceGroup(devgrp_cfg) - - # configure the tx stream(s) - # we need more than one stream, so delete old one - # and create as many as we need - # FIXME: restore the single stream at end? - log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) - drone.deleteStream(stream_id) - - stream_id = ost_pb.StreamIdList() - stream_id.port_id.CopyFrom(tx_port.port_id[0]) - for i in range(num_vlans): - stream_id.stream_id.add().id = i - log.info('adding tx_stream %d' % stream_id.stream_id[i].id) - - drone.addStream(stream_id) - - stream_cfg = ost_pb.StreamConfigList() - stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) - for i in range(num_vlans): - s = stream_cfg.stream.add() - s.stream_id.id = stream_id.stream_id[i].id - s.core.is_enabled = True - s.core.ordinal = i - s.control.packets_per_sec = 10 - s.control.num_packets = num_devs - - # setup stream protocols as mac:vlan:eth2:ip4:udp:payload - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber - p.Extensions[mac].dst_mac = dut_dst_mac - p.Extensions[mac].src_mac = 0x00aabbccddee - - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber - p.Extensions[vlan].vlan_tag = vlan_base+i - - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber - - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber - ip = p.Extensions[ip4] - ip.src_ip = 0x0a010165 - ip.src_ip_mode = Ip4.e_im_inc_host - ip.dst_ip = 0x0a010265 - ip.dst_ip_mode = Ip4.e_im_inc_host - - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber - - log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) - - drone.modifyStream(stream_cfg) - - # clear tx/rx stats - log.info('clearing tx/rx stats') - drone.clearStats(tx_port) - drone.clearStats(rx_port) - - # clear arp on DUT - for i in range(num_vlans): - vlan_id = vlan_base + i - vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_rx_port + '.' + str(vlan_id) - vlan_tx_dev = dut_tx_port + '.' + str(vlan_id) - - sudo('ip netns exec ' + vrf - + ' ip neigh flush dev ' + vlan_rx_dev) - sudo('ip netns exec ' + vrf - + ' ip neigh flush dev ' + vlan_tx_dev) - sudo('ip netns exec ' + vrf - + ' ip neigh show') - - drone.startCapture(rx_port) - drone.startTransmit(tx_port) - log.info('waiting for transmit to finish ...') - time.sleep(5) - 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 (all)') - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) - print(cap_pkts) - log.info('dumping Rx capture buffer (filtered)') - fail = 0 - for i in range(num_vlans): - for j in range(num_devs): - cap_pkts = subprocess.check_output( - [tshark, '-nr', 'capture.pcap', - '-R', '(vlan.id == ' + str(201+i) + ')' - ' && (ip.src == 10.1.1.' + str(101+j) + ') ' - ' && (ip.dst == 10.1.2.' + str(101+j) + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+j, '02x')+')']) - print(cap_pkts) - if cap_pkts.count('\n') != 1: - fail = fail + 1 - if fail == 0: - passed = True - os.remove('capture.pcap') - except RpcError as e: - raise - finally: - drone.stopTransmit(tx_port) - # show arp on DUT - for i in range(num_vlans): - vrf = 'v' + str(vlan_base + i) - sudo('ip netns exec ' + vrf - + ' ip neigh show') - # un-configure the DUT - for i in range(num_vlans): - vlan_id = vlan_base + i - vrf = 'v' + str(vlan_id) - sudo('ip netns delete ' + vrf) - suite.test_end(passed) - - # TODO: - # ----------------------------------------------------------------- # - # TESTCASE: Emulate one IPv4 device per multiple double-tag VLANs - # vlanMode: repeat (default) - # TESTCASE: Emulate multiple IPv4 devices per multiple double-tag VLANs - # vlanMode: no-repeat; ip4Mode: repeat (default) - # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs - # vlanMode: repeat (default); ip4Mode: repeat (default) - # TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs - # vlanMode: no-repeat; ip4Mode: no-repeat - # ----------------------------------------------------------------- # - - suite.complete() - - # delete stream(s) + # configure the tx stream(s) + # we need more than one stream, so delete old one + # and create as many as we need + # FIXME: restore the single stream at end? log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) drone.deleteStream(stream_id) - # delete device(s) - dgid_list = drone.getDeviceGroupIdList(tx_port.port_id[0]) - drone.deleteDeviceGroup(dgid_list) - dgid_list = drone.getDeviceGroupIdList(rx_port.port_id[0]) - drone.deleteDeviceGroup(dgid_list) + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(ports.tx.port_id[0]) + for i in range(num_vlans): + stream_id.stream_id.add().id = i + log.info('adding tx_stream %d' % stream_id.stream_id[i].id) - # bye for now - drone.disconnect() - #disconnect_all() + drone.addStream(stream_id) + + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + for i in range(num_vlans): + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[i].id + s.core.is_enabled = True + s.core.ordinal = i + s.control.packets_per_sec = 100 + s.control.num_packets = num_devs_per_vlan + + # setup stream protocols as mac:vlan:eth2:ip4:udp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber + p.Extensions[vlan].vlan_tag = vlan_base+i + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = p.Extensions[ip4] + ip.src_ip = tx_ip_base + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs_per_vlan + ip.dst_ip = rx_ip_base + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs_per_vlan + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + + drone.modifyStream(stream_cfg) + + # clear arp on DUT + for i in range(num_vlans): + vlan_id = vlan_base + i + vrf = 'v' + str(vlan_id) + vlan_rx_dev = dut_ports.rx + '.' + str(vlan_id) + vlan_tx_dev = dut_ports.tx + '.' + str(vlan_id) + + sudo('ip netns exec ' + vrf + + ' ip neigh flush all') + arp_cache = sudo('ip netns exec ' + vrf + + ' ip neigh show') + assert re.search('10.1.[1-2].20[1-5].*lladdr', arp_cache) == None + + # resolve ARP on tx/rx ports + log.info('resolving Neighbors on tx/rx ports ...') + drone.startCapture(emul_ports) + drone.clearDeviceNeighbors(emul_ports) + drone.resolveDeviceNeighbors(emul_ports) + time.sleep(3) + drone.stopCapture(emul_ports) + + # verify ARP Requests sent out from tx port + buff = drone.getCaptureBuffer(emul_ports.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Tx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Tx capture buffer (filtered)') + for i in range(num_vlans): + for j in range(num_devs_per_vlan): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(vlan.id == '+str(vlan_base+i)+')' + ' && (arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.1.1.' + +str(101+j)+')' + ' && (arp.dst.proto_ipv4 == 10.1.1.1)']) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 + + # verify *no* ARP Requests sent out from rx port + buff = drone.getCaptureBuffer(emul_ports.port_id[1]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + for i in range(num_vlans): + for j in range(num_devs_per_vlan): + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-R', '(vlan.id == '+str(vlan_base+i)+')' + ' && (arp.opcode == 1)' + ' && (arp.src.proto_ipv4 == 10.10.2.' + +str(101+j)+')' + ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) + print(cap_pkts) + assert cap_pkts.count('\n') == 0 + + # retrieve and verify ARP Table on tx/rx ports + log.info('retrieving ARP entries on tx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP Table on tx port') + for dev_cfg, device in zip(device_config, devices): + resolved = False + for arp in device.arp: + # TODO: pretty print ip and mac + print('v%d|%08x: %08x %012x' % + (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved + + log.info('retrieving ARP entries on rx port') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP Table on rx port') + for dev_cfg, device in zip(device_config, devices): + # verify *no* ARPs learnt on rx port + assert len(device.arp) == 0 + for arp in device.arp: + # TODO: pretty print ip and mac + print('v%d|%08x: %08x %012x' % + (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) + + drone.startCapture(ports.rx) + drone.startTransmit(ports.tx) + log.info('waiting for transmit to finish ...') + time.sleep(5) + drone.stopTransmit(ports.tx) + drone.stopCapture(ports.rx) + + buff = drone.getCaptureBuffer(ports.rx.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + log.info('dumping Rx capture buffer (filtered)') + for i in range(num_vlans): + for j in range(num_devs_per_vlan): + cap_pkts = subprocess.check_output( + [tshark, '-nr', 'capture.pcap', + '-R', '(vlan.id == ' + str(vlan_base+i) + ')' + ' && (ip.src == 10.1.1.' + str(101+j) + ') ' + ' && (ip.dst == 10.1.2.' + str(101+j) + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+j, '02x')+')']) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 + os.remove('capture.pcap') + +# TODO: +# ----------------------------------------------------------------- # +# TESTCASE: Emulate one IPv4 device per multiple double-tag VLANs +# vlanMode: repeat (default) +# TESTCASE: Emulate multiple IPv4 devices per multiple double-tag VLANs +# vlanMode: no-repeat; ip4Mode: repeat (default) +# TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs +# vlanMode: repeat (default); ip4Mode: repeat (default) +# TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs +# vlanMode: no-repeat; ip4Mode: no-repeat +# ----------------------------------------------------------------- # -except Exception as ex: - log.exception(ex) -finally: - suite.report() - if not suite.passed: - sys.exit(2); From dc28dfefd6b665ec5a08c4393cd69a7033cb23f2 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 29 Nov 2015 21:18:31 +0530 Subject: [PATCH 013/121] Feature (contd.): Device Emulation - sort the device list returned by getDeviceList() RPC --- server/device.cpp | 15 +++++++++++++++ server/device.h | 5 ++++- server/devicemanager.cpp | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/server/device.cpp b/server/device.cpp index 804bf0d..c174f05 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -457,3 +457,18 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) qPrintable(QHostAddress(srcIp).toString()), qPrintable(QHostAddress(tgtIp).toString())); } + +bool operator<(const DeviceKey &a1, const DeviceKey &a2) +{ + int i = 0; + + while (i < a1.size()) { + if (uchar(a1.at(i)) < uchar(a2.at(i))) + return true; + if (uchar(a1.at(i)) > uchar(a2.at(i))) + return false; + i++; + } + + return false; +} diff --git a/server/device.h b/server/device.h index a18c940..4ef0192 100644 --- a/server/device.h +++ b/server/device.h @@ -29,7 +29,9 @@ along with this program. If not, see class DeviceManager; class PacketBuffer; -typedef QByteArray DeviceKey; +class DeviceKey: public QByteArray +{ +}; class Device { @@ -80,5 +82,6 @@ private: // data QHash arpTable; }; +bool operator<(const DeviceKey &a1, const DeviceKey &a2); #endif diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index e4bf9ff..bff84b4 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -25,6 +25,7 @@ along with this program. If not, see #include "../common/emulproto.pb.h" +#include #include #define __STDC_FORMAT_MACROS @@ -138,7 +139,16 @@ int DeviceManager::deviceCount() void DeviceManager::getDeviceList( OstProto::PortDeviceList *deviceList) { + // We want to return a sorted deviceList. However, deviceList_ + // is a QHash (unsorted) instead of a QMap (sorted) because + // QHash is faster. So here we use a temporary QMap to sort the + // list that will be returned + QMap list; foreach(Device *device, deviceList_) { + list.insert(device->key(), device); + } + + foreach(Device *device, list) { OstEmul::Device *dev = deviceList->AddExtension(OstEmul::port_device); device->getConfig(dev); From cb1e16976d70e5b748571e81ee5a91c12528a29c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 13 Dec 2015 18:41:39 +0530 Subject: [PATCH 014/121] Feature (contd.): Device Emulation - refactored the vlan test case to work for multiple tags and parametrized it to verify 1 to 4 tags --- test/emultest.py | 204 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 55 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index b0dc78c..6c8359b 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -223,44 +223,95 @@ def stream_id(request, drone, ports): @pytest.fixture def dut_vlans(request, dut_ports): - num_vlans = getattr(request.function, 'num_vlans') - vlan_base = getattr(request.function, 'vlan_base') - for i in range(num_vlans): - vlan_id = vlan_base+i - vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_ports.rx + '.' + str(vlan_id) - vlan_tx_dev = dut_ports.tx + '.' + str(vlan_id) + class Devices(object): + pass - sudo('ip netns add ' + vrf) + def create_vdev(devices, vlancfgs): + # device(s) with ALL vlan tags are what we configure and add to netns; + # those with intermediate no of vlans are created but not configured + if len(vlancfgs) == 0: + assert len(devices.rx) == len(devices.tx) + dut_vlans.vlans = [] + for i in range(len(devices.rx)): + vrf = 'vrf' + str(i+1) + sudo('ip netns add ' + vrf) - sudo('ip link add link ' + dut_ports.rx - + ' name ' + vlan_rx_dev - + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_rx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf - + ' ip addr add 10.1.1.1/24' - + ' dev ' + vlan_rx_dev) - sudo('ip netns exec ' + vrf - + ' ip link set ' + vlan_rx_dev + ' up') + dev = devices.rx[i] + sudo('ip link set ' + dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.1.1/24' + + ' dev ' + dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + dev + ' up') - sudo('ip link add link ' + dut_ports.tx - + ' name ' + vlan_tx_dev - + ' type vlan id ' + str(vlan_id)) - sudo('ip link set ' + vlan_tx_dev - + ' netns ' + vrf) - sudo('ip netns exec ' + vrf - + ' ip addr add 10.1.2.1/24' - + ' dev ' + vlan_tx_dev) - sudo('ip netns exec ' + vrf - + ' ip link set ' + vlan_tx_dev + ' up') + dev = devices.tx[i] + sudo('ip link set ' + dev + + ' netns ' + vrf) + sudo('ip netns exec ' + vrf + + ' ip addr add 10.1.2.1/24' + + ' dev ' + dev) + sudo('ip netns exec ' + vrf + + ' ip link set ' + dev + ' up') - def fin(): - for i in range(num_vlans): - vlan_id = vlan_base + i - vrf = 'v' + str(vlan_id) + dut_vlans.vlans.append(dev[dev.find('.')+1:]) + return dut_vlans.vlans + vcfg = vlancfgs[0] + new_devs = Devices() + new_devs.tx = [] + new_devs.rx = [] + for dev in devices.rx+devices.tx: + for k in range(vcfg['count']): + vlan_id = vcfg['base'] + k + dev_name = dev + '.' + str(vlan_id) + sudo('ip link add link ' + dev + + ' name ' + dev_name + + ' type vlan id ' + str(vlan_id)) + sudo('ip link set ' + dev_name + ' up') + if dev in devices.rx: + new_devs.rx.append(dev_name) + else: + new_devs.tx.append(dev_name) + print (str(len(devices.rx)*2*vcfg['count'])+' devices created') + + create_vdev(new_devs, vlancfgs[1:]) + + def delete_vdev(): + if len(vlancfgs) == 0: + return + + vrf_count = 1 + for vcfg in vlancfgs: + vrf_count = vrf_count * vcfg['count'] + + # deleting netns will also delete the devices inside it + for i in range(vrf_count): + vrf = 'vrf' + str(i+1) sudo('ip netns delete ' + vrf) - request.addfinalizer(fin) + + # if we have only single tagged vlans, then we are done + if len(vlancfgs) == 1: + return + + # if we have 2 or more stacked vlan tags, we need to delete the + # intermediate stacked vlan devices which are in the root namespace; + # deleting first level tag vlan devices will delete vlan devices + # stacked on top of it also + vcfg = vlancfgs[0] + for dev in devices.tx+devices.rx: + for k in range(vcfg['count']): + vdev = dev + '.' + str(vcfg['base']+k) + sudo('ip link delete ' + vdev) + + + vlancfgs = getattr(request.function, 'vlan_cfg') + devices = Devices() + devices.tx = [dut_ports.tx] + devices.rx = [dut_ports.rx] + create_vdev(devices, vlancfgs) + + request.addfinalizer(delete_vdev) + # ================================================================= # # ----------------------------------------------------------------- # @@ -461,8 +512,23 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx) sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx) +@pytest.mark.parametrize('vlan_cfg', [ + [{'base': 11, 'count': 5}], + + [{'base': 11, 'count': 2}, + {'base': 21, 'count': 3}], + + [{'base': 11, 'count': 2}, + {'base': 21, 'count': 3}, + {'base': 31, 'count': 2}], + + [{'base': 11, 'count': 2}, + {'base': 21, 'count': 3}, + {'base': 31, 'count': 2}, + {'base': 1, 'count': 3}], +]) def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, - emul_ports, dgid_list): + emul_ports, dgid_list, vlan_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # @@ -470,14 +536,16 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, # .1/ \.1 # / \ # 10.1.1/24 10.1.2/24 - # v[201-205] v[201-205] + # [vlans] [vlans] # / \ # /.101-103 \.101-103 # Host1(s) Host2(s) # ----------------------------------------------------------------- # - test_multiEmulDevPerVlan.num_vlans = num_vlans = 5 - test_multiEmulDevPerVlan.vlan_base = vlan_base = 201 + num_vlans = 1 + for vcfg in vlan_cfg: + num_vlans = num_vlans * vcfg['count'] + test_multiEmulDevPerVlan.vlan_cfg = vlan_cfg num_devs_per_vlan = 3 tx_ip_base = 0x0a010165 rx_ip_base = 0x0a010265 @@ -485,15 +553,29 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, # configure vlans on the DUT dut_vlans(request, dut_ports) + assert len(dut_vlans.vlans) == num_vlans + vlan_filter = [] + for i in range(len(dut_vlans.vlans)): + vlan_filter.append('') + ids = dut_vlans.vlans[i].split('.') + for j in range(len(ids)): + filter = '(frame[:4]==8100:)' \ + .replace('', str(12+j*4)) \ + .replace('', '{:04x}'.format(int(ids[j]))) + if len(vlan_filter[i]) > 0: + vlan_filter[i] += ' && ' + vlan_filter[i] += filter + # configure the tx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) dg = devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.tx.device_group_id[0].id dg.core.name = "Host1" - v = dg.Extensions[emul.encap].vlan.stack.add() - v.vlan_tag = vlan_base - v.count = num_vlans + for vcfg in vlan_cfg: + v = dg.Extensions[emul.encap].vlan.stack.add() + v.vlan_tag = vcfg['base'] + v.count = vcfg['count'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030a01 ip = dg.Extensions[emul.ip4] @@ -509,9 +591,10 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, dg = devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.rx.device_group_id[0].id dg.core.name = "Host1" - v = dg.Extensions[emul.encap].vlan.stack.add() - v.vlan_tag = vlan_base - v.count = num_vlans + for vcfg in vlan_cfg: + v = dg.Extensions[emul.encap].vlan.stack.add() + v.vlan_tag = vcfg['base'] + v.count = vcfg['count'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030b01 ip = dg.Extensions[emul.ip4] @@ -552,9 +635,10 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber - p.Extensions[vlan].vlan_tag = vlan_base+i + for vcfg in vlan_cfg: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber + p.Extensions[vlan].vlan_tag = vcfg['base']+(i % vcfg['count']) p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber @@ -580,10 +664,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, # clear arp on DUT for i in range(num_vlans): - vlan_id = vlan_base + i - vrf = 'v' + str(vlan_id) - vlan_rx_dev = dut_ports.rx + '.' + str(vlan_id) - vlan_tx_dev = dut_ports.tx + '.' + str(vlan_id) + vrf = 'vrf' + str(i+1) sudo('ip netns exec ' + vrf + ' ip neigh flush all') @@ -605,11 +686,15 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, log.info('dumping Tx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) + log.info('dumping Tx capture buffer (all pkts - vlans only)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Tfields', '-eframe.number', '-evlan.id']) + print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') for i in range(num_vlans): for j in range(num_devs_per_vlan): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(vlan.id == '+str(vlan_base+i)+')' + '-R', vlan_filter[i] + ' && (arp.opcode == 1)' ' && (arp.src.proto_ipv4 == 10.1.1.' +str(101+j)+')' @@ -623,15 +708,19 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) + log.info('dumping Tx capture buffer (all pkts - vlans only)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Tfields', '-eframe.number', '-evlan.id']) + print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): for j in range(num_devs_per_vlan): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(vlan.id == '+str(vlan_base+i)+')' + '-R', vlan_filter[i] + ' && (arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.2.' + ' && (arp.src.proto_ipv4 == 10.1.2.' +str(101+j)+')' - ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) + ' && (arp.dst.proto_ipv4 == 10.1.2.1)']) print(cap_pkts) assert cap_pkts.count('\n') == 0 @@ -673,17 +762,22 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, drone.stopTransmit(ports.tx) drone.stopCapture(ports.rx) + # verify data packets received on rx port buff = drone.getCaptureBuffer(ports.rx.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) + log.info('dumping Tx capture buffer (all pkts - vlans only)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Tfields', '-eframe.number', '-evlan.id']) + print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): for j in range(num_devs_per_vlan): cap_pkts = subprocess.check_output( [tshark, '-nr', 'capture.pcap', - '-R', '(vlan.id == ' + str(vlan_base+i) + ')' + '-R', vlan_filter[i] + ' && (ip.src == 10.1.1.' + str(101+j) + ') ' ' && (ip.dst == 10.1.2.' + str(101+j) + ')' ' && (eth.dst == 00:01:02:03:0b:' From ad1fb5fc374469a177682d8ce85b993ec0220870 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Dec 2015 21:04:58 +0530 Subject: [PATCH 015/121] Feature (contd.): Device Emulation - fixed problems in code and in test case for multi-tagged vlans --- server/devicemanager.cpp | 58 +++++++++++++++++++++++++++++++++------- server/pcapport.cpp | 35 +++++++++++++++++++++++- test/emultest.py | 11 +++++--- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index bff84b4..4a3baa9 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -324,22 +324,62 @@ void DeviceManager::enumerateDevices( OstEmul::VlanEmulation pbVlan = deviceGroup->GetExtension(OstEmul::encap) .vlan(); int numTags = pbVlan.stack_size(); - int vlanCount = 1; + int n = 1; + QList vlanCount; 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(); + /* + * 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--) { + n *= pbVlan.stack(i).count(); + vlanCount.prepend(n); + } - // 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 i = 0; i < vlanCount.at(0); i++) { for (int j = 0; j < numTags; j++) { OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j); - quint16 vlanAdd = i*vlan.step(); + quint16 vlanAdd = (i/vlanCount.at(j+1) % vlan.count())*vlan.step(); dk.setVlan(j, vlan.vlan_tag() + vlanAdd); } diff --git a/server/pcapport.cpp b/server/pcapport.cpp index e853568..beb1169 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -946,7 +946,40 @@ void PcapPort::EmulationTransceiver::run() int flags = PCAP_OPENFLAG_PROMISCUOUS; char errbuf[PCAP_ERRBUF_SIZE] = ""; struct bpf_program bpf; - const char *capture_filter = "arp or (vlan and arp)"; +#if 0 +/* + Ideally we should use the below filter, but the 'vlan' capture filter + in libpcap is implemented as a kludge. From the pcap-filter man page - + + vlan [vlan_id] + Note that the first vlan keyword encountered in expression changes + the decoding offsets for the remainder of expression on the + assumption that the packet is a VLAN packet. + + The vlan [vlan_id] expression may be used more than once, to filter on + VLAN hierarchies. Each use of that expression increments the filter + offsets by 4. + + See https://ask.wireshark.org/questions/31953/unusual-behavior-with-stacked-vlan-tags-and-capture-filter + + So we use the modified filter expression that works as we intend. If ever + libpcap changes their implementation, this will need to change as well. +*/ + const char *capture_filter = + "arp or " + "(vlan and arp) or " + "(vlan and vlan and arp) or " + "(vlan and vlan and vlan and arp) or " + "(vlan and vlan and vlan and vlan and arp)"; +#else + const char *capture_filter = + "arp or " + "(vlan and arp) or " + "(vlan and arp) or " + "(vlan and arp) or " + "(vlan and arp)"; +#endif + const int optimize = 1; qDebug("In %s", __PRETTY_FUNCTION__); diff --git a/test/emultest.py b/test/emultest.py index 6c8359b..3ddaaaf 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -624,6 +624,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, for i in range(num_vlans): s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id + s.core.name = 'stream ' + str(s.stream_id.id) s.core.is_enabled = True s.core.ordinal = i s.control.packets_per_sec = 100 @@ -635,10 +636,11 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - for vcfg in vlan_cfg: + ids = dut_vlans.vlans[i].split('.') + for id in ids: p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber - p.Extensions[vlan].vlan_tag = vcfg['base']+(i % vcfg['count']) + p.Extensions[vlan].vlan_tag = int(id) p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber @@ -658,7 +660,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber - log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + log.info('configuring tx_stream %d' % stream_id.stream_id[i].id) drone.modifyStream(stream_cfg) @@ -708,7 +710,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) - log.info('dumping Tx capture buffer (all pkts - vlans only)') + log.info('dumping Rx capture buffer (all pkts - vlans only)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Tfields', '-eframe.number', '-evlan.id']) print(cap_pkts) @@ -734,6 +736,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, for dev_cfg, device in zip(device_config, devices): resolved = False for arp in device.arp: + # TODO: print all configured vlans, not just the first # TODO: pretty print ip and mac print('v%d|%08x: %08x %012x' % (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) From 71d04b63bc941ded08cd4406e4280d10a25ca99c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 16 Dec 2015 20:48:17 +0530 Subject: [PATCH 016/121] Feature (contd.): Device Emulation - refactored and moved EncapEmulation as a native field of DeviceGroup instead of a extension - now the device_count comment of "per encap" makes more sense with encap at the same level as device_count --- common/emulproto.proto | 7 ++----- common/protocol.proto | 12 +++++++++--- server/devicemanager.cpp | 4 ++-- test/emultest.py | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 1eb3016..eb08d55 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -38,8 +38,8 @@ message VlanEmulation { repeated Vlan stack = 1; // outer to inner } -message EncapEmulation { - optional VlanEmulation vlan = 1; +extend OstProto.EncapEmulation { + optional VlanEmulation vlan = 1000; } // =========== @@ -75,9 +75,6 @@ message Ip6Emulation { } extend OstProto.DeviceGroup { - // FIXME: add directly to DeviceGroup in protocol.proto - optional EncapEmulation encap = 2000; - optional MacEmulation mac = 2001; optional Ip4Emulation ip4 = 3000; diff --git a/common/protocol.proto b/common/protocol.proto index f3d4d58..3b041bf 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -297,13 +297,19 @@ message DeviceGroupIdList { repeated DeviceGroupId device_group_id = 2; } +message EncapEmulation { + // Encap Protocols implemented as extensions + extensions 1000 to 1999; +} + message DeviceGroup { required DeviceGroupId device_group_id = 1; optional DeviceGroupCore core = 2; - // FIXME: add Encap here - optional uint32 device_count = 3 [default = 1]; // per-encap + optional EncapEmulation encap = 3; + optional uint32 device_count = 4 [default = 1]; // per-encap - extensions 2000 to 5999; // For use by Protocol Emulations + // Device Protocols implemented as extensions + extensions 2000 to 5999; } message DeviceGroupConfigList { diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 4a3baa9..79b3444 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -321,8 +321,8 @@ void DeviceManager::enumerateDevices( Operation oper) { Device dk(this); - OstEmul::VlanEmulation pbVlan = deviceGroup->GetExtension(OstEmul::encap) - .vlan(); + OstEmul::VlanEmulation pbVlan = deviceGroup->encap() + .GetExtension(OstEmul::vlan); int numTags = pbVlan.stack_size(); int n = 1; QList vlanCount; diff --git a/test/emultest.py b/test/emultest.py index 3ddaaaf..3b82c37 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -573,7 +573,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, dg.device_group_id.id = dgid_list.tx.device_group_id[0].id dg.core.name = "Host1" for vcfg in vlan_cfg: - v = dg.Extensions[emul.encap].vlan.stack.add() + v = dg.encap.Extensions[emul.vlan].stack.add() v.vlan_tag = vcfg['base'] v.count = vcfg['count'] dg.device_count = num_devs_per_vlan @@ -592,7 +592,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, dg.device_group_id.id = dgid_list.rx.device_group_id[0].id dg.core.name = "Host1" for vcfg in vlan_cfg: - v = dg.Extensions[emul.encap].vlan.stack.add() + v = dg.encap.Extensions[emul.vlan].stack.add() v.vlan_tag = vcfg['base'] v.count = vcfg['count'] dg.device_count = num_devs_per_vlan From 6b5a8105e62d4b0de5410514c8fd4a9c0904c0c0 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 19 Dec 2015 20:51:04 +0530 Subject: [PATCH 017/121] Feature (contd.): Device Emulation - parameterized test case to check for IP step value other than 1 (default) --- test/emultest.py | 61 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 3b82c37..6ff15c4 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -36,8 +36,6 @@ env.user = 'tc' env.password = 'tc' env.host_string = 'localhost:50022' -dut_dst_mac = 0x0800278df2b4 #FIXME: hardcoding - # setup logging log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -319,8 +317,12 @@ def dut_vlans(request, dut_ports): # ----------------------------------------------------------------- # # ================================================================= # -def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, - emul_ports, dgid_list): +@pytest.mark.parametrize('dev_cfg', [ + {'step': 1}, + {'step': 5}, +]) +def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, + emul_ports, dgid_list, dev_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) # DUT @@ -333,6 +335,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, # ----------------------------------------------------------------- # num_devs = 5 + ip_step = dev_cfg['step'] # configure the DUT sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) @@ -349,6 +352,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, ip = dg.Extensions[emul.ip4] ip.address = 0x0a0a0165 ip.prefix_length = 24 + if (ip_step != 1): + ip.step = ip_step ip.default_gateway = 0x0a0a0101 drone.modifyDeviceGroup(devgrp_cfg) @@ -364,6 +369,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, ip = dg.Extensions[emul.ip4] ip.address = 0x0a0a0265 ip.prefix_length = 24 + if (ip_step != 1): + ip.step = ip_step ip.default_gateway = 0x0a0a0201 drone.modifyDeviceGroup(devgrp_cfg) @@ -390,11 +397,33 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber ip = p.Extensions[ip4] ip.src_ip = 0x0a0a0165 - ip.src_ip_mode = Ip4.e_im_inc_host - ip.src_ip_count = num_devs + if ip_step == 1: + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 12 + vf.mask = 0x000000FF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs ip.dst_ip = 0x0a0a0265 - ip.dst_ip_mode = Ip4.e_im_inc_host - ip.dst_ip_count = num_devs + if ip_step == 1: + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 16 + vf.mask = 0x000000FF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber @@ -410,7 +439,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, # clear arp on DUT sudo('ip neigh flush all') arp_cache = run('ip neigh show') - assert re.search('10.10.2.10[1-5].*lladdr', arp_cache) == None + assert re.search('10.10.[1-2].1\d\d.*lladdr', arp_cache) == None # resolve ARP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') @@ -430,7 +459,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.1.'+str(101+i)+')' + ' && (arp.src.proto_ipv4 == 10.10.1.' + +str(101+i*ip_step)+')' ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) print(cap_pkts) assert cap_pkts.count('\n') == 1 @@ -445,7 +475,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.2.'+str(101+i)+')' + ' && (arp.src.proto_ipv4 == 10.10.2.' + +str(101+i*ip_step)+')' ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) print(cap_pkts) assert cap_pkts.count('\n') == 0 @@ -497,12 +528,12 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(ip.src == 10.10.1.' + str(101+i) + ') ' - ' && (ip.dst == 10.10.2.' + str(101+i) + ')' + '-R', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' + ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' ' && (eth.dst == 00:01:02:03:0b:' + format(1+i, '02x')+')']) print(cap_pkts) - assert cap_pkts.count('\n') == s.control.num_packets/ip.src_ip_count + assert cap_pkts.count('\n') == s.control.num_packets/num_devs os.remove('capture.pcap') drone.stopTransmit(ports.tx) @@ -527,7 +558,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, {'base': 31, 'count': 2}, {'base': 1, 'count': 3}], ]) -def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, +def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, emul_ports, dgid_list, vlan_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs From 03f427ce9156df21b43a33894dadce2d8e743248 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 20 Dec 2015 19:33:02 +0530 Subject: [PATCH 018/121] Feature (contd.): Device Emulation - Test case and code to support non default vlan TPID --- server/device.cpp | 31 +++++++++++++++++++++++++++---- server/device.h | 7 +++++-- server/devicemanager.cpp | 30 ++++++++++++++++++++++++++---- server/devicemanager.h | 1 + test/emultest.py | 30 ++++++++++++++++++++++++------ 5 files changed, 83 insertions(+), 16 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index c174f05..e4eadd7 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -51,7 +51,7 @@ Device::Device(DeviceManager *deviceManager) clearKey(); } -void Device::setVlan(int index, quint16 vlan) +void Device::setVlan(int index, quint16 vlan, quint16 tpid) { int ofs; @@ -61,7 +61,7 @@ void Device::setVlan(int index, quint16 vlan) return; } - vlan_[index] = vlan; + vlan_[index] = (tpid << 16) | vlan; ofs = index * sizeof(quint16); key_[ofs] = vlan >> 8; @@ -105,7 +105,30 @@ void Device::getConfig(OstEmul::Device *deviceConfig) QString Device::config() { return QString("") - .arg(vlan_[0]).arg(vlan_[1]).arg(vlan_[2]).arg(vlan_[3]) + .arg((vlan_[0] >> 16) != kVlanTpid ? + QString("0x%1-%2") + .arg(vlan_[0] >> 16, 4, kBaseHex, QChar('0')) + .arg(vlan_[0] & 0xFFFF) : + QString("%1") + .arg(vlan_[0] & 0xFFFF)) + .arg((vlan_[1] >> 16) != kVlanTpid ? + QString("0x%1-%2") + .arg(vlan_[1] >> 16, 4, kBaseHex, QChar('0')) + .arg(vlan_[1] & 0xFFFF) : + QString("%1") + .arg(vlan_[1] & 0xFFFF)) + .arg((vlan_[2] >> 16) != kVlanTpid ? + QString("0x%1-%2") + .arg(vlan_[2] >> 16, 4, kBaseHex, QChar('0')) + .arg(vlan_[2] & 0xFFFF) : + QString("%1") + .arg(vlan_[2] & 0xFFFF)) + .arg((vlan_[3] >> 16) != kVlanTpid ? + QString("0x%1-%2") + .arg(vlan_[3] >> 16, 4, kBaseHex, QChar('0')) + .arg(vlan_[3] & 0xFFFF) : + QString("%1") + .arg(vlan_[3] & 0xFFFF)) .arg(mac_, 12, kBaseHex, QChar('0')) .arg(QHostAddress(ip4_).toString()) .arg(ip4PrefixLength_); @@ -147,7 +170,7 @@ void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) *(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff)); ofs = 12; for (int i = 0; i < numVlanTags_; i++) { - *(quint32*)(p + ofs) = qToBigEndian(quint32((0x8100 << 16)|vlan_[i])); + *(quint32*)(p + ofs) = qToBigEndian(vlan_[i]); ofs += 4; } *(quint16*)(p + ofs) = qToBigEndian(type); diff --git a/server/device.h b/server/device.h index 4ef0192..4e173ae 100644 --- a/server/device.h +++ b/server/device.h @@ -35,10 +35,13 @@ class DeviceKey: public QByteArray class Device { +public: + static const quint16 kVlanTpid = 0x8100; + public: Device(DeviceManager *deviceManager); - void setVlan(int index, quint16 vlan); + void setVlan(int index, quint16 vlan, quint16 tpid = kVlanTpid); quint64 mac(); void setMac(quint64 mac); void setIp4(quint32 address, int prefixLength, quint32 gateway); @@ -71,7 +74,7 @@ private: // data DeviceManager *deviceManager_; int numVlanTags_; - quint16 vlan_[kMaxVlan]; // FIXME: vlan tpid + quint32 vlan_[kMaxVlan]; quint64 mac_; quint32 ip4_; int ip4PrefixLength_; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 79b3444..f28e4e5 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -192,7 +192,7 @@ _eth_type: ethType = qFromBigEndian(pktData + offset); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); - if (ethType == 0x8100) { + if (tpidList_.contains(ethType)) { offset += 2; vlan = qFromBigEndian(pktData + offset); dk.setVlan(idx++, vlan); @@ -296,7 +296,7 @@ _eth_type: ethType = qFromBigEndian(pktData + offset); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); - if (ethType == 0x8100) { + if (tpidList_.contains(ethType)) { offset += 2; vlan = qFromBigEndian(pktData + offset); dk.setVlan(idx++, vlan); @@ -372,8 +372,30 @@ void DeviceManager::enumerateDevices( */ vlanCount.append(n); for (int i = numTags - 1; i >= 0 ; i--) { - n *= pbVlan.stack(i).count(); + 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++) { @@ -381,7 +403,7 @@ void DeviceManager::enumerateDevices( 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); + dk.setVlan(j, vlan.vlan_tag() + vlanAdd, vlan.tpid()); } for (uint k = 0; k < deviceGroup->device_count(); k++) { diff --git a/server/devicemanager.h b/server/devicemanager.h index 8f2ca2f..e2658b9 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -71,6 +71,7 @@ private: QHash deviceGroupList_; QHash deviceList_; QMultiHash bcastList_; + QHash tpidList_; // Key: TPID, Value: RefCount }; #endif diff --git a/test/emultest.py b/test/emultest.py index 6ff15c4..b9786a3 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -261,10 +261,15 @@ def dut_vlans(request, dut_ports): for dev in devices.rx+devices.tx: for k in range(vcfg['count']): vlan_id = vcfg['base'] + k + if 'tpid' in vcfg and vcfg['tpid'] == 0x88a8: + tpid = '802.1ad' + else: + tpid = '802.1q' dev_name = dev + '.' + str(vlan_id) sudo('ip link add link ' + dev + ' name ' + dev_name - + ' type vlan id ' + str(vlan_id)) + + ' type vlan id ' + str(vlan_id) + + ' proto ' + tpid) sudo('ip link set ' + dev_name + ' up') if dev in devices.rx: new_devs.rx.append(dev_name) @@ -549,6 +554,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, [{'base': 11, 'count': 2}, {'base': 21, 'count': 3}], + [{'base': 11, 'count': 2, 'tpid': 0x88a8}, + {'base': 21, 'count': 3}], + [{'base': 11, 'count': 2}, {'base': 21, 'count': 3}, {'base': 31, 'count': 2}], @@ -590,12 +598,15 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, vlan_filter.append('') ids = dut_vlans.vlans[i].split('.') for j in range(len(ids)): - filter = '(frame[:4]==8100:)' \ + filter = '(frame[:4]==:)' \ .replace('', str(12+j*4)) \ + .replace('', '{:04x}'.format( + vlan_cfg[j].get('tpid', 0x8100))) \ .replace('', '{:04x}'.format(int(ids[j]))) if len(vlan_filter[i]) > 0: vlan_filter[i] += ' && ' vlan_filter[i] += filter + print i, vlan_filter[i] # configure the tx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() @@ -607,6 +618,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, v = dg.encap.Extensions[emul.vlan].stack.add() v.vlan_tag = vcfg['base'] v.count = vcfg['count'] + if 'tpid' in vcfg: + v.tpid = vcfg['tpid'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030a01 ip = dg.Extensions[emul.ip4] @@ -626,6 +639,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, v = dg.encap.Extensions[emul.vlan].stack.add() v.vlan_tag = vcfg['base'] v.count = vcfg['count'] + if 'tpid' in vcfg: + v.tpid = vcfg['tpid'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030b01 ip = dg.Extensions[emul.ip4] @@ -668,10 +683,13 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve ids = dut_vlans.vlans[i].split('.') - for id in ids: + for id, j in zip(ids, range(len(ids))): p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber p.Extensions[vlan].vlan_tag = int(id) + if 'tpid' in vlan_cfg[j]: + p.Extensions[vlan].tpid = vlan_cfg[j]['tpid'] + p.Extensions[vlan].is_override_tpid = True p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber @@ -721,7 +739,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, print(cap_pkts) log.info('dumping Tx capture buffer (all pkts - vlans only)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Tfields', '-eframe.number', '-evlan.id']) + '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id']) print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') for i in range(num_vlans): @@ -743,7 +761,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, print(cap_pkts) log.info('dumping Rx capture buffer (all pkts - vlans only)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Tfields', '-eframe.number', '-evlan.id']) + '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): @@ -804,7 +822,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, print(cap_pkts) log.info('dumping Tx capture buffer (all pkts - vlans only)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Tfields', '-eframe.number', '-evlan.id']) + '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): From baef6c2ea66b1c10b72954e833978b2056a11e11 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 21 Dec 2015 18:28:35 +0530 Subject: [PATCH 019/121] Feature (contd.): Device Emulation - add test for non default mac step value --- test/emultest.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index b9786a3..18a856a 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -323,8 +323,8 @@ def dut_vlans(request, dut_ports): # ================================================================= # @pytest.mark.parametrize('dev_cfg', [ - {'step': 1}, - {'step': 5}, + {'mac_step': 1, 'ip_step': 1}, + {'mac_step': 2, 'ip_step': 5}, ]) def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, emul_ports, dgid_list, dev_cfg): @@ -340,7 +340,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, # ----------------------------------------------------------------- # num_devs = 5 - ip_step = dev_cfg['step'] + mac_step = dev_cfg['mac_step'] + ip_step = dev_cfg['ip_step'] # configure the DUT sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) @@ -354,6 +355,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, dg.core.name = "Host1" dg.device_count = num_devs dg.Extensions[emul.mac].address = 0x000102030a01 + if (mac_step != 1): + dg.Extensions[emul.mac].step = mac_step ip = dg.Extensions[emul.ip4] ip.address = 0x0a0a0165 ip.prefix_length = 24 @@ -371,6 +374,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, dg.core.name = "Host1" dg.device_count = num_devs dg.Extensions[emul.mac].address = 0x000102030b01 + if (mac_step != 1): + dg.Extensions[emul.mac].step = mac_step ip = dg.Extensions[emul.ip4] ip.address = 0x0a0a0265 ip.prefix_length = 24 @@ -428,8 +433,6 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, vf.mode = ost_pb.VariableField.kIncrement vf.count = num_devs - - s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber @@ -536,7 +539,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, '-R', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' ' && (eth.dst == 00:01:02:03:0b:' - + format(1+i, '02x')+')']) + + format(1+i*mac_step, '02x')+')']) print(cap_pkts) assert cap_pkts.count('\n') == s.control.num_packets/num_devs os.remove('capture.pcap') From b135cb8df75fe8723ea52e98db58a3406810129e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 21 Dec 2015 18:41:46 +0530 Subject: [PATCH 020/121] Feature (contd.): Device Emulation - add test case for non-default VLAN step value --- test/emultest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 18a856a..0649b31 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -260,7 +260,7 @@ def dut_vlans(request, dut_ports): new_devs.rx = [] for dev in devices.rx+devices.tx: for k in range(vcfg['count']): - vlan_id = vcfg['base'] + k + vlan_id = vcfg['base'] + k*vcfg.get('step', 1) if 'tpid' in vcfg and vcfg['tpid'] == 0x88a8: tpid = '802.1ad' else: @@ -561,8 +561,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, {'base': 21, 'count': 3}], [{'base': 11, 'count': 2}, - {'base': 21, 'count': 3}, - {'base': 31, 'count': 2}], + {'base': 21, 'count': 3, 'step': 2}, + {'base': 31, 'count': 2, 'step': 3}], [{'base': 11, 'count': 2}, {'base': 21, 'count': 3}, @@ -623,6 +623,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, v.count = vcfg['count'] if 'tpid' in vcfg: v.tpid = vcfg['tpid'] + if 'step' in vcfg: + v.step = vcfg['step'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030a01 ip = dg.Extensions[emul.ip4] @@ -644,6 +646,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, v.count = vcfg['count'] if 'tpid' in vcfg: v.tpid = vcfg['tpid'] + if 'step' in vcfg: + v.step = vcfg['step'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030b01 ip = dg.Extensions[emul.ip4] From ea44e55e78a7ebaffff5974de0e188f1fa6bca3a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 23 Dec 2015 21:18:25 +0530 Subject: [PATCH 021/121] Feature (contd.): Device Emulation - Test case and code to support sending ping echo reply in response to received ping echo request --- server/device.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++++ server/device.h | 5 ++ server/pcapport.cpp | 20 ++++---- test/emultest.py | 51 ++++++++++++++++---- 4 files changed, 166 insertions(+), 20 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index e4eadd7..fa495da 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -197,6 +197,9 @@ void Device::receivePacket(PacketBuffer *pktBuf) break; case 0x0800: // IPv4 + receiveIp4(pktBuf); + break; + case 0x86dd: // IPv6 default: break; @@ -481,6 +484,113 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) qPrintable(QHostAddress(tgtIp).toString())); } +void Device::receiveIp4(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + uchar ipProto; + quint32 dstIp; + + if (pktData[0] != 0x45) { + qDebug("%s: Unsupported IP version or options (%02x) ", __FUNCTION__, + pktData[0]); + goto _invalid_exit; + } + + if (pktBuf->length() < 20) { + qDebug("incomplete IPv4 header: expected 20, actual %d", + pktBuf->length()); + goto _invalid_exit; + } + + // XXX: We don't verify IP Header checksum + + dstIp = qFromBigEndian(pktData + 16); + if (dstIp != ip4_) { + qDebug("%s: dstIp %x is not me (%x)", __FUNCTION__, dstIp, ip4_); + goto _invalid_exit; + } + + ipProto = pktData[9]; + switch (ipProto) { + case 1: // ICMP + pktBuf->pull(20); + receiveIcmp4(pktBuf); + break; + default: + break; + } + +_invalid_exit: + return; +} + +// This function assumes we are replying back to the same IP +// that originally sent us the packet and therefore we can reuse the +// ingress packet for egress; in other words, it assumes the +// original IP header is intact and will just reuse it after +// minimal modifications +void Device::sendIp4Reply(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->push(20); + uchar origTtl = pktData[8]; + uchar ipProto = pktData[9]; + quint32 srcIp, dstIp; + quint32 sum; + + // Swap src/dst IP addresses + dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt + srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt + + if (!arpTable.contains(dstIp)) + return; + + *(quint32*)(pktData + 12) = qToBigEndian(srcIp); + *(quint32*)(pktData + 16) = qToBigEndian(dstIp); + + // Reset TTL + pktData[8] = 64; + + // Incremental checksum update (RFC 1624 [Eqn.3]) + // HC' = ~(~HC + ~m + m') + sum = quint16(~qFromBigEndian(pktData + 10)); // old cksum + sum += quint16(~quint16(origTtl << 8 | ipProto)); // old value + sum += quint16(pktData[8] << 8 | ipProto); // new value + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum)); + + encap(pktBuf, arpTable.value(dstIp), 0x0800); + transmitPacket(pktBuf); +} + +void Device::receiveIcmp4(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + quint32 sum; + + // XXX: We don't verify icmp checksum + + // We handle only ping request + if (pktData[0] != 8) { // Echo Request + qDebug("%s: Ignoring non echo request (%d)", __FUNCTION__, pktData[0]); + return; + } + + pktData[0] = 0; // Echo Reply + + // Incremental checksum update (RFC 1624 [Eqn.3]) + // HC' = ~(~HC + ~m + m') + sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum + sum += quint16(~quint16(8 << 8 | pktData[1])); // old value + sum += quint16(0 << 8 | pktData[1]); // new value + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum)); + + sendIp4Reply(pktBuf); + qDebug("Sent ICMP Echo Reply"); +} + bool operator<(const DeviceKey &a1, const DeviceKey &a2) { int i = 0; diff --git a/server/device.h b/server/device.h index 4e173ae..267130a 100644 --- a/server/device.h +++ b/server/device.h @@ -68,6 +68,11 @@ private: // methods void receiveArp(PacketBuffer *pktBuf); void sendArpRequest(PacketBuffer *pktBuf); + void receiveIp4(PacketBuffer *pktBuf); + void sendIp4Reply(PacketBuffer *pktBuf); + + void receiveIcmp4(PacketBuffer *pktBuf); + private: // data static const int kMaxVlan = 4; diff --git a/server/pcapport.cpp b/server/pcapport.cpp index beb1169..272fcb2 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -966,18 +966,18 @@ void PcapPort::EmulationTransceiver::run() libpcap changes their implementation, this will need to change as well. */ const char *capture_filter = - "arp or " - "(vlan and arp) or " - "(vlan and vlan and arp) or " - "(vlan and vlan and vlan and arp) or " - "(vlan and vlan and vlan and vlan and arp)"; + "arp or icmp or " + "(vlan and (arp or icmp)) or " + "(vlan and vlan and (arp or icmp)) or " + "(vlan and vlan and vlan and (arp or icmp)) or " + "(vlan and vlan and vlan and vlan and (arp or icmp))"; #else const char *capture_filter = - "arp or " - "(vlan and arp) or " - "(vlan and arp) or " - "(vlan and arp) or " - "(vlan and arp)"; + "arp or icmp or " + "(vlan and (arp or icmp)) or " + "(vlan and (arp or icmp)) or " + "(vlan and (arp or icmp)) or " + "(vlan and (arp or icmp))"; #endif const int optimize = 1; diff --git a/test/emultest.py b/test/emultest.py index 0649b31..ff138e3 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -219,6 +219,16 @@ def stream_id(request, drone, ports): return stream_id +@pytest.fixture +def dut_ip(request, dut_ports): + sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) + sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.tx) + + def fin(): + sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx) + sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx) + request.addfinalizer(fin) + @pytest.fixture def dut_vlans(request, dut_ports): class Devices(object): @@ -326,8 +336,8 @@ def dut_vlans(request, dut_ports): {'mac_step': 1, 'ip_step': 1}, {'mac_step': 2, 'ip_step': 5}, ]) -def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, - emul_ports, dgid_list, dev_cfg): +def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, + stream_id, emul_ports, dgid_list, dev_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) # DUT @@ -343,10 +353,6 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, mac_step = dev_cfg['mac_step'] ip_step = dev_cfg['ip_step'] - # configure the DUT - sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) - sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.tx) - # configure the tx device(s) devgrp_cfg = ost_pb.DeviceGroupConfigList() devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) @@ -520,6 +526,16 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, print('%08x: %08x %012x' % (dev_cfg.ip4, arp.ip4, arp.mac)) + # ping the tx devices from the DUT + for i in range(num_devs): + out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + + # ping the tx devices from the DUT + for i in range(num_devs): + out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + # We are all set now - so transmit the stream now drone.startCapture(ports.rx) drone.startTransmit(ports.tx) @@ -547,10 +563,6 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, stream_id, drone.stopTransmit(ports.tx) run('ip neigh show') - # unconfigure DUT - sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx) - sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx) - @pytest.mark.parametrize('vlan_cfg', [ [{'base': 11, 'count': 5}], @@ -814,6 +826,23 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, print('v%d|%08x: %08x %012x' % (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) + # ping the tx devices from the DUT + for i in range(num_vlans): + vrf = 'vrf' + str(i+1) + for j in range(num_devs_per_vlan): + out = sudo('ip netns exec ' + vrf + + ' ping -c3 10.1.1.'+str(101+j), warn_only=True) + assert '100% packet loss' not in out + + # ping the tx devices from the DUT + for i in range(num_vlans): + vrf = 'vrf' + str(i+1) + for j in range(num_devs_per_vlan): + out = sudo('ip netns exec ' + vrf + + ' ping -c3 10.1.2.'+str(101+j), warn_only=True) + assert '100% packet loss' not in out + + # we are all set - send data stream(s) drone.startCapture(ports.rx) drone.startTransmit(ports.tx) log.info('waiting for transmit to finish ...') @@ -857,4 +886,6 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, # vlanMode: no-repeat; ip4Mode: no-repeat # ----------------------------------------------------------------- # +import pytest +pytest.main(__file__) From e0f9e6a70315cf9007f65d17323fb86d23933322 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 24 Dec 2015 18:28:47 +0530 Subject: [PATCH 022/121] Feature (contd.): Device Emulation - minor cleanup of emultest.py --- test/emultest.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index ff138e3..4659b81 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -834,7 +834,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, + ' ping -c3 10.1.1.'+str(101+j), warn_only=True) assert '100% packet loss' not in out - # ping the tx devices from the DUT + # ping the rx devices from the DUT for i in range(num_vlans): vrf = 'vrf' + str(i+1) for j in range(num_devs_per_vlan): @@ -874,18 +874,6 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, assert cap_pkts.count('\n') == 1 os.remove('capture.pcap') -# TODO: -# ----------------------------------------------------------------- # -# TESTCASE: Emulate one IPv4 device per multiple double-tag VLANs -# vlanMode: repeat (default) -# TESTCASE: Emulate multiple IPv4 devices per multiple double-tag VLANs -# vlanMode: no-repeat; ip4Mode: repeat (default) -# TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs -# vlanMode: repeat (default); ip4Mode: repeat (default) -# TESTCASE: Emulate multiple IPv4 devices per multiple quad-tag VLANs -# vlanMode: no-repeat; ip4Mode: no-repeat -# ----------------------------------------------------------------- # - import pytest pytest.main(__file__) From 12d17ef6de49fc4177b7e5cc46e4a51e411c13ad Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 24 Dec 2015 19:00:32 +0530 Subject: [PATCH 023/121] Feature (contd.): Device Emulation - added/updated IPv6 fields in emulproto --- common/emulproto.proto | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index eb08d55..0d0de76 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -60,17 +60,17 @@ message Ip4Emulation { // FIXME: step for gateway? } -// FIXME: should we have a 'message Ip6Address' with 'hi', 'lo' fields -// and use that everywhere instead of having to define _hi and _lo? -message Ip6Emulation { - optional uint64 address_hi = 1; - optional uint64 address_lo = 2; - optional uint32 prefix_length = 3; - optional uint64 default_gateway_hi = 4; - optional uint64 default_gateway_lo = 5; +message Ip6Address { + optional uint64 hi = 1; + optional uint64 lo = 2; +} - optional uint64 step_hi = 10 [default = 0]; - optional uint64 step_lo = 11 [default = 1]; +message Ip6Emulation { + optional Ip6Address address = 1; + optional uint32 prefix_length = 2; + optional Ip6Address default_gateway = 3; + + optional Ip6Address step = 10; // FIXME: step for gateway? } @@ -90,7 +90,9 @@ message Device { optional uint32 ip4_prefix_length = 11; optional uint32 ip4_default_gateway = 12; - // TODO: IPv6 fields + optional Ip6Address ip6 = 20; + optional uint32 ip6_prefix_length = 21; + optional Ip6Address ip6_default_gateway = 22; } extend OstProto.PortDeviceList { @@ -103,9 +105,8 @@ message ArpEntry { } message NdEntry { - optional uint64 ip6_hi = 1; - optional uint64 ip6_lo = 2; - optional uint64 mac = 3; + optional Ip6Address ip6 = 1; + optional uint64 mac = 2; } message DeviceNeighborList { From ea68b42059a89c84ff9f00eb5b7b44d960addd9b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 30 Dec 2015 18:25:13 +0530 Subject: [PATCH 024/121] Feature (contd.): Device Emulation - added test case for IPv6 - currently failing pending IPv6 implementation --- test/emultest.py | 329 +++++++++++++++++++++++++++++++---------------- 1 file changed, 219 insertions(+), 110 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 4659b81..e4da1dd 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -1,6 +1,7 @@ #! /usr/bin/env python # standard modules +import ipaddress import logging import os import re @@ -18,12 +19,13 @@ from core import ost_pb, emul, DroneProxy from rpc import RpcError from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 +from protocols.ip6_pb2 import ip6, Ip6 from protocols.vlan_pb2 import vlan use_defaults = True if sys.platform == 'win32': - tshark = r'C:\Program Files\Wireshark\tshark.exe' + tshark = r'C:\PortableTools\WiresharkPortable\App\Wireshark\tshark.exe' else: tshark = 'tshark' @@ -71,6 +73,24 @@ if not use_defaults: env.host_string = s or env.host_string # FIXME: get inputs for dut rx/tx ports + +# Convenience class to interwork with OstEmul::Ip6Address() and +# the python ipaddress module +class ip6_address(ipaddress.IPv6Interface): + def __init__(self, addr): + super(ip6_address, self).__init__(unicode(addr)) + self.ip6 = emul.Ip6Address() + self.ip6.hi = int(self) >> 64 + self.ip6.lo = int(self) & 0xffffffffffffffff + + self.prefixlen = self.network.prefixlen + + # we assume gateway is the lowest IP host address in the network + gateway = self.network.network_address + 1 + self.gateway = emul.Ip6Address() + self.ip6.hi = int(gateway) >> 64 + self.ip6.lo = int(gateway) & 0xffffffffffffffff + # ================================================================= # # ----------------------------------------------------------------- # # FIXTURES @@ -196,37 +216,25 @@ def dgid_list(request, drone, ports): return dgid_list @pytest.fixture(scope='module') -def stream_id(request, drone, ports): - # ----------------------------------------------------------------- # - # create stream on tx port - each test case will modify and reuse - # this stream as per its needs - # ----------------------------------------------------------------- # - +def stream_clear(request, drone, ports): # delete existing streams, if any, on tx port sid_list = drone.getStreamIdList(ports.tx.port_id[0]) drone.deleteStream(sid_list) - # add a stream - stream_id = ost_pb.StreamIdList() - stream_id.port_id.CopyFrom(ports.tx.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) - - def fin(): - drone.deleteStream(stream_id) - request.addfinalizer(fin) - - return stream_id - @pytest.fixture def dut_ip(request, dut_ports): sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx) sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.tx) + sudo('ip -6 address add 1234:1::1/96 dev ' + dut_ports.rx) + sudo('ip -6 address add 1234:2::1/96 dev ' + dut_ports.tx) + def fin(): sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx) sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx) + + sudo('ip -6 address delete 1234:1::1/96 dev ' + dut_ports.rx) + sudo('ip -6 address delete 1234:2::1/96 dev ' + dut_ports.tx) request.addfinalizer(fin) @pytest.fixture @@ -333,23 +341,26 @@ def dut_vlans(request, dut_ports): # ================================================================= # @pytest.mark.parametrize('dev_cfg', [ - {'mac_step': 1, 'ip_step': 1}, - {'mac_step': 2, 'ip_step': 5}, + {'ip_ver': [6], 'mac_step': 1, 'ip_step': 1}, + {'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5}, ]) def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, - stream_id, emul_ports, dgid_list, dev_cfg): + stream_clear, emul_ports, dgid_list, dev_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) # DUT # /.1 \.1 # / \ # 10.10.1/24 10.10.2/24 + # 1234::1/96 1234::2/96 # / \ # /.101-105 \.101-105 # Host1(s) Host2(s) # ----------------------------------------------------------------- # num_devs = 5 + has_ip4 = True if 4 in dev_cfg['ip_ver'] else False + has_ip6 = True if 6 in dev_cfg['ip_ver'] else False mac_step = dev_cfg['mac_step'] ip_step = dev_cfg['ip_step'] @@ -363,12 +374,21 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, dg.Extensions[emul.mac].address = 0x000102030a01 if (mac_step != 1): dg.Extensions[emul.mac].step = mac_step - ip = dg.Extensions[emul.ip4] - ip.address = 0x0a0a0165 - ip.prefix_length = 24 - if (ip_step != 1): - ip.step = ip_step - ip.default_gateway = 0x0a0a0101 + if has_ip4: + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0165 + ip.prefix_length = 24 + if (ip_step != 1): + ip.step = ip_step + ip.default_gateway = 0x0a0a0101 + if has_ip6: + ip = dg.Extensions[emul.ip6] + ip6addr = ip6_address('1234:1::65/96') + ip.address.CopyFrom(ip6addr.ip6) + ip.prefix_length = ip6addr.prefixlen + if (ip_step != 1): + ip.step.CopyFrom(ip6_address(ip_step).ip6) + ip.default_gateway.CopyFrom(ip6addr.gateway) drone.modifyDeviceGroup(devgrp_cfg) @@ -382,67 +402,134 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, dg.Extensions[emul.mac].address = 0x000102030b01 if (mac_step != 1): dg.Extensions[emul.mac].step = mac_step - ip = dg.Extensions[emul.ip4] - ip.address = 0x0a0a0265 - ip.prefix_length = 24 - if (ip_step != 1): - ip.step = ip_step - ip.default_gateway = 0x0a0a0201 + if has_ip4: + ip = dg.Extensions[emul.ip4] + ip.address = 0x0a0a0265 + ip.prefix_length = 24 + if (ip_step != 1): + ip.step = ip_step + ip.default_gateway = 0x0a0a0201 + if has_ip6: + ip = dg.Extensions[emul.ip6] + ip6addr = ip6_address('1234:2::65/96') + ip.address.CopyFrom(ip6addr.ip6) + ip.prefix_length = ip6addr.prefixlen + if (ip_step != 1): + ip.step.CopyFrom(ip6_address(ip_step).ip6) + ip.default_gateway.CopyFrom(ip6addr.gateway) drone.modifyDeviceGroup(devgrp_cfg) - # configure the tx stream + # TODO: reuse dev_cfg['ip_ver'] + ip_versions = [] + if has_ip4: + ip_versions.append('ip4') + if has_ip6: + ip_versions.append('ip6') + + # add the tx stream(s) - we may need more than one + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(ports.tx.port_id[0]) + for i in range(len(ip_versions)): + stream_id.stream_id.add().id = i + log.info('adding tx_stream %d' % stream_id.stream_id[i].id) + + drone.addStream(stream_id) + + # configure the tx stream(s) stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(ports.tx.port_id[0]) - s = stream_cfg.stream.add() - s.stream_id.id = stream_id.stream_id[0].id - s.core.is_enabled = True - s.control.packets_per_sec = 100 - s.control.num_packets = 10 + for i in range(len(ip_versions)): + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[i].id + s.core.is_enabled = True + s.control.packets_per_sec = 100 + s.control.num_packets = 10 - # 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_mode = Mac.e_mm_resolve - p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve + # setup stream protocols as mac:eth2:ip:udp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber - ip = p.Extensions[ip4] - ip.src_ip = 0x0a0a0165 - if ip_step == 1: - ip.src_ip_mode = Ip4.e_im_inc_host - ip.src_ip_count = num_devs - else: - vf = p.variable_field.add() - vf.type = ost_pb.VariableField.kCounter32 - vf.offset = 12 - vf.mask = 0x000000FF - vf.value = 0x00000065 - vf.step = ip_step - vf.mode = ost_pb.VariableField.kIncrement - vf.count = num_devs - ip.dst_ip = 0x0a0a0265 - if ip_step == 1: - ip.dst_ip_mode = Ip4.e_im_inc_host - ip.dst_ip_count = num_devs - else: - vf = p.variable_field.add() - vf.type = ost_pb.VariableField.kCounter32 - vf.offset = 16 - vf.mask = 0x000000FF - vf.value = 0x00000065 - vf.step = ip_step - vf.mode = ost_pb.VariableField.kIncrement - vf.count = num_devs + if ip_versions[i] == 'ip4': + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = None + ip = p.Extensions[ip4] + ip.src_ip = 0x0a0a0165 + if ip_step == 1: + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 12 + vf.mask = 0x000000FF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs + ip.dst_ip = 0x0a0a0265 + if ip_step == 1: + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 16 + vf.mask = 0x000000FF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs + elif ip_versions[i] == 'ip6': + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber + ip = p.Extensions[ip6] + ip6addr = ip6_address('1234:1::65/96') + ip.src_addr_hi = ip6addr.ip6.hi + ip.src_addr_lo = ip6addr.ip6.lo + if ip_step == 1: + ip.src_addr_mode = Ip6.kIncHost + ip.src_addr_count = num_devs + ip.src_addr_prefix = ip6addr.prefixlen + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 20 + vf.mask = 0xFFFFFFFF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs + ip6addr = ip6_address('1234:2::65/96') + ip.dst_addr_hi = ip6addr.ip6.hi + ip.dst_addr_lo = ip6addr.ip6.lo + if ip_step == 1: + ip.dst_addr_mode = Ip6.kIncHost + ip.dst_addr_count = num_devs + ip.dst_addr_prefix = ip6addr.prefixlen + else: + vf = p.variable_field.add() + vf.type = ost_pb.VariableField.kCounter32 + vf.offset = 20 + vf.mask = 0xFFFFFFFF + vf.value = 0x00000065 + vf.step = ip_step + vf.mode = ost_pb.VariableField.kIncrement + vf.count = num_devs + else: + assert False # unreachable - s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber - s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + 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[i].id) - log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) drone.modifyStream(stream_cfg) # FIXME(needed?): clear tx/rx stats @@ -450,10 +537,11 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, drone.clearStats(ports.tx) drone.clearStats(ports.rx) - # clear arp on DUT + # clear arp/ndp on DUT sudo('ip neigh flush all') arp_cache = run('ip neigh show') assert re.search('10.10.[1-2].1\d\d.*lladdr', arp_cache) == None + assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None # resolve ARP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') @@ -470,14 +558,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') - for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.1.' - +str(101+i*ip_step)+')' - ' && (arp.dst.proto_ipv4 == 10.10.1.1)']) - print(cap_pkts) - assert cap_pkts.count('\n') == 1 + for i in range(len(ip_versions)): + for j in range(num_devs): + if ip_versions[i] == 'ip4': + filter = '(arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.10.1.' \ + + str(101+j*ip_step) + ')' \ + ' && (arp.dst.proto_ipv4 == 10.10.1.1)' + elif ip_versions[i] == 'ip6': + filter = '(icmpv6.type == 135)' \ + ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \ + ' && (icmpv6.nd.ns.target_address == 1234:1::' \ + + format(0x65+i*ip_step, 'x')+')' + print filter + else: + assert False # unreachable + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 # verify *no* ARP Requests sent out from rx port buff = drone.getCaptureBuffer(emul_ports.port_id[1]) @@ -486,14 +585,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') - for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.10.2.' - +str(101+i*ip_step)+')' - ' && (arp.dst.proto_ipv4 == 10.10.2.1)']) - print(cap_pkts) - assert cap_pkts.count('\n') == 0 + for i in range(len(ip_versions)): + for j in range(num_devs): + if ip_versions[i] == 'ip4': + filter = '(arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.10.2.' \ + + str(101+j*ip_step) + ')' \ + ' && (arp.dst.proto_ipv4 == 10.10.2.1)' + elif ip_versions[i] == 'ip6': + filter = '(icmpv6.type == 135)' \ + ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \ + ' && (icmpv6.nd.ns.target_address == 1234:2::' \ + + format(0x65+i*ip_step, 'x')+')' + else: + assert False # unreachable + print filter + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 0 # retrieve and verify ARP Table on tx/rx ports log.info('retrieving ARP entries on tx port') @@ -505,11 +615,13 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, for dev_cfg, device in zip(device_config, devices): resolved = False for arp in device.arp: - # TODO: pretty print ip and mac - print('%08x: %08x %012x' % - (dev_cfg.ip4, arp.ip4, arp.mac)) + print('%s: %s %012x' % + (str(ipaddress.ip_address(dev_cfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), + arp.mac)) if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): resolved = True + # TODO: ip6/ndp assert resolved log.info('retrieving ARP entries on rx port') @@ -525,16 +637,19 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # TODO: pretty print ip and mac print('%08x: %08x %012x' % (dev_cfg.ip4, arp.ip4, arp.mac)) + # TODO: ip6/ndp # ping the tx devices from the DUT for i in range(num_devs): out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) assert '100% packet loss' not in out + # TODO: ip6/ndp # ping the tx devices from the DUT for i in range(num_devs): out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) assert '100% packet loss' not in out + # TODO: ip6/ndp # We are all set now - so transmit the stream now drone.startCapture(ports.rx) @@ -552,7 +667,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' + '-Y', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' ' && (eth.dst == 00:01:02:03:0b:' + format(1+i*mac_step, '02x')+')']) @@ -581,8 +696,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, {'base': 31, 'count': 2}, {'base': 1, 'count': 3}], ]) -def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, - emul_ports, dgid_list, vlan_cfg): +def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, + stream_clear, emul_ports, dgid_list, vlan_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # @@ -669,13 +784,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, stream_id, drone.modifyDeviceGroup(devgrp_cfg) - # configure the tx stream(s) - # we need more than one stream, so delete old one - # and create as many as we need - # FIXME: restore the single stream at end? - log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) - drone.deleteStream(stream_id) - + # add the tx stream(s) - we need more than one stream stream_id = ost_pb.StreamIdList() stream_id.port_id.CopyFrom(ports.tx.port_id[0]) for i in range(num_vlans): From aaf6dbcbf25f0f905264ef5589ccfa6b488f412a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 31 Dec 2015 20:17:56 +0530 Subject: [PATCH 025/121] Device Emulation (contd.) - Implemented IPv6 device creation and deletion --- common/uint128.h | 83 ++++++++++++++++++++++++++++++++++++++++ server/device.cpp | 37 ++++++++++++++++-- server/device.h | 10 +++++ server/devicemanager.cpp | 21 ++++++++++ test/emultest.py | 4 +- 5 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 common/uint128.h diff --git a/common/uint128.h b/common/uint128.h new file mode 100644 index 0000000..d91ea44 --- /dev/null +++ b/common/uint128.h @@ -0,0 +1,83 @@ +/* +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 +*/ + +#ifndef _UINT128_H +#define _UINT128_H + +#include +#include + +class UInt128 +{ +public: + UInt128(); + UInt128(quint64 hi, quint64 lo); + + quint8* toArray() const; + + UInt128 operator+(const UInt128 &other); + UInt128 operator*(const uint &other); + +private: + quint64 hi_; + quint64 lo_; + quint8 array_[16]; +}; + +inline UInt128::UInt128() +{ + // Do nothing - value will be garbage like any other uint +} + +inline UInt128::UInt128(quint64 hi, quint64 lo) +{ + hi_ = hi; + lo_ = lo; +} + +inline quint8* UInt128::toArray() const +{ + *(quint64*)(array_ + 0) = qToBigEndian(hi_); + *(quint64*)(array_ + 8) = qToBigEndian(lo_); + + return (quint8*)array_; +} + +inline UInt128 UInt128::operator+(const UInt128 &other) +{ + UInt128 sum; + + sum.lo_ = lo_ + other.lo_; + sum.hi_ = hi_ + other.hi_ + (sum.lo_ < lo_); + + return sum; +} + +inline UInt128 UInt128::operator*(const uint &other) +{ + UInt128 product; + + // FIXME + product.hi_ = 0; + product.lo_ = lo_ * other; + + return product; +} + +#endif diff --git a/server/device.cpp b/server/device.cpp index fa495da..8549bb5 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -45,9 +45,13 @@ Device::Device(DeviceManager *deviceManager) vlan_[i] = 0; numVlanTags_ = 0; mac_ = 0; - ip4_ = 0; + + ip4_ = ip4Gateway_ = 0; ip4PrefixLength_ = 0; + ip6_ = ip6Gateway_ = UInt128(0, 0); + ip6PrefixLength_ = 0; + clearKey(); } @@ -91,20 +95,33 @@ void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) ip4Gateway_ = gateway; } +void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) +{ + ip6_ = address; + ip6PrefixLength_ = prefixLength; + ip6Gateway_ = gateway; +} + void Device::getConfig(OstEmul::Device *deviceConfig) { for (int i = 0; i < numVlanTags_; i++) deviceConfig->add_vlan(vlan_[i]); - deviceConfig->set_mac(mac_); + deviceConfig->set_ip4(ip4_); deviceConfig->set_ip4_prefix_length(ip4PrefixLength_); deviceConfig->set_ip4_default_gateway(ip4Gateway_); + +#if 0 // FIXME + deviceConfig->set_ip6(ip6_); + deviceConfig->set_ip6_prefix_length(ip6PrefixLength_); + deviceConfig->set_ip6_default_gateway(ip6Gateway_); +#endif } QString Device::config() { - return QString("") + return QString("") .arg((vlan_[0] >> 16) != kVlanTpid ? QString("0x%1-%2") .arg(vlan_[0] >> 16, 4, kBaseHex, QChar('0')) @@ -131,7 +148,9 @@ QString Device::config() .arg(vlan_[3] & 0xFFFF)) .arg(mac_, 12, kBaseHex, QChar('0')) .arg(QHostAddress(ip4_).toString()) - .arg(ip4PrefixLength_); + .arg(ip4PrefixLength_) + .arg(QHostAddress(ip6_.toArray()).toString()) + .arg(ip6PrefixLength_); } DeviceKey Device::key() @@ -234,6 +253,9 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf) break; case 0x86dd: // IPv6 + sendNeighborSolicit(pktBuf); + break; + default: break; } @@ -591,6 +613,13 @@ void Device::receiveIcmp4(PacketBuffer *pktBuf) qDebug("Sent ICMP Echo Reply"); } +// Send NS for the IPv6 packet in pktBuf +// pktBuf points to start of IP header +void Device::sendNeighborSolicit(PacketBuffer *pktBuf) +{ + // TODO +} + bool operator<(const DeviceKey &a1, const DeviceKey &a2) { int i = 0; diff --git a/server/device.h b/server/device.h index 267130a..32d848c 100644 --- a/server/device.h +++ b/server/device.h @@ -22,6 +22,7 @@ along with this program. If not, see #include "../common/emulproto.pb.h" #include "../common/protocol.pb.h" +#include "../common/uint128.h" #include #include @@ -45,6 +46,7 @@ public: quint64 mac(); void setMac(quint64 mac); void setIp4(quint32 address, int prefixLength, quint32 gateway); + void setIp6(UInt128 address, int prefixLength, UInt128 gateway); void getConfig(OstEmul::Device *deviceConfig); QString config(); @@ -73,6 +75,9 @@ private: // methods void receiveIcmp4(PacketBuffer *pktBuf); + void sendNeighborSolicit(PacketBuffer *pktBuf); + void sendIp6(PacketBuffer *pktBuf); + private: // data static const int kMaxVlan = 4; @@ -81,10 +86,15 @@ private: // data int numVlanTags_; quint32 vlan_[kMaxVlan]; quint64 mac_; + quint32 ip4_; int ip4PrefixLength_; quint32 ip4Gateway_; + UInt128 ip6_; + int ip6PrefixLength_; + UInt128 ip6Gateway_; + DeviceKey key_; QHash arpTable; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index f28e4e5..5df1391 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -31,6 +31,11 @@ along with this program. If not, see #define __STDC_FORMAT_MACROS #include +inline UInt128 UINT128(OstEmul::Ip6Address x) +{ + return UInt128(x.hi(), x.lo()); +} + const quint64 kBcastMac = 0xffffffffffffULL; // XXX: Port owning DeviceManager already uses locks, so we don't use any @@ -125,7 +130,18 @@ bool DeviceManager::modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup) } 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; @@ -329,6 +345,7 @@ void DeviceManager::enumerateDevices( 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 @@ -410,11 +427,15 @@ void DeviceManager::enumerateDevices( Device *device; quint64 macAdd = k * mac.step(); quint32 ip4Add = k * ip4.step(); + UInt128 ip6Add = UINT128(ip6.step()) * k; dk.setMac(mac.address() + macAdd); dk.setIp4(ip4.address() + ip4Add, ip4.prefix_length(), ip4.default_gateway()); + dk.setIp6(UINT128(ip6.address()) + ip6Add, + ip6.prefix_length(), + UINT128(ip6.default_gateway())); // TODO: fill in other pbDevice data diff --git a/test/emultest.py b/test/emultest.py index e4da1dd..5a114e1 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -88,8 +88,8 @@ class ip6_address(ipaddress.IPv6Interface): # we assume gateway is the lowest IP host address in the network gateway = self.network.network_address + 1 self.gateway = emul.Ip6Address() - self.ip6.hi = int(gateway) >> 64 - self.ip6.lo = int(gateway) & 0xffffffffffffffff + self.gateway.hi = int(gateway) >> 64 + self.gateway.lo = int(gateway) & 0xffffffffffffffff # ================================================================= # # ----------------------------------------------------------------- # From 0b573d572e7f149c98834dfce757e03bd1579c46 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 1 Jan 2016 11:59:31 +0530 Subject: [PATCH 026/121] Device Emulation (contd.) - added intelligence for single or dual ip stack; fixed pktBuf memory leak --- common/uint128.h | 12 +++++ server/device.cpp | 98 +++++++++++++++++++++------------------- server/device.h | 2 + server/devicemanager.cpp | 20 ++++---- 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/common/uint128.h b/common/uint128.h index d91ea44..6d3f307 100644 --- a/common/uint128.h +++ b/common/uint128.h @@ -29,6 +29,8 @@ public: UInt128(); UInt128(quint64 hi, quint64 lo); + quint64 hi64() const; + quint64 lo64() const; quint8* toArray() const; UInt128 operator+(const UInt128 &other); @@ -51,6 +53,16 @@ inline UInt128::UInt128(quint64 hi, quint64 lo) lo_ = lo; } +inline quint64 UInt128::hi64() const +{ + return hi_; +} + +inline quint64 UInt128::lo64() const +{ + return lo_; +} + inline quint8* UInt128::toArray() const { *(quint64*)(array_ + 0) = qToBigEndian(hi_); diff --git a/server/device.cpp b/server/device.cpp index 8549bb5..f736de4 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -46,11 +46,8 @@ Device::Device(DeviceManager *deviceManager) numVlanTags_ = 0; mac_ = 0; - ip4_ = ip4Gateway_ = 0; - ip4PrefixLength_ = 0; - - ip6_ = ip6Gateway_ = UInt128(0, 0); - ip6PrefixLength_ = 0; + hasIp4_ = false; + hasIp6_ = false; clearKey(); } @@ -93,6 +90,7 @@ void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) ip4_ = address; ip4PrefixLength_ = prefixLength; ip4Gateway_ = gateway; + hasIp4_ = true; } void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) @@ -100,6 +98,7 @@ void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) ip6_ = address; ip6PrefixLength_ = prefixLength; ip6Gateway_ = gateway; + hasIp6_ = true; } void Device::getConfig(OstEmul::Device *deviceConfig) @@ -108,49 +107,48 @@ void Device::getConfig(OstEmul::Device *deviceConfig) deviceConfig->add_vlan(vlan_[i]); deviceConfig->set_mac(mac_); - deviceConfig->set_ip4(ip4_); - deviceConfig->set_ip4_prefix_length(ip4PrefixLength_); - deviceConfig->set_ip4_default_gateway(ip4Gateway_); + if (hasIp4_) { + deviceConfig->set_ip4(ip4_); + deviceConfig->set_ip4_prefix_length(ip4PrefixLength_); + deviceConfig->set_ip4_default_gateway(ip4Gateway_); + } -#if 0 // FIXME - deviceConfig->set_ip6(ip6_); - deviceConfig->set_ip6_prefix_length(ip6PrefixLength_); - deviceConfig->set_ip6_default_gateway(ip6Gateway_); -#endif + if (hasIp6_) { + deviceConfig->mutable_ip6()->set_hi(ip6_.hi64()); + deviceConfig->mutable_ip6()->set_lo(ip6_.lo64()); + deviceConfig->set_ip6_prefix_length(ip6PrefixLength_); + deviceConfig->mutable_ip6_default_gateway()->set_hi(ip6Gateway_.hi64()); + deviceConfig->mutable_ip6_default_gateway()->set_lo(ip6Gateway_.lo64()); + } } QString Device::config() { - return QString("") - .arg((vlan_[0] >> 16) != kVlanTpid ? + QString config; + + for (int i = 0; i < numVlanTags_; i++) { + config.append(i == 0 ? "vlans=" : "|"); + config.append( + (vlan_[i] >> 16) != kVlanTpid ? QString("0x%1-%2") - .arg(vlan_[0] >> 16, 4, kBaseHex, QChar('0')) - .arg(vlan_[0] & 0xFFFF) : + .arg(vlan_[i] >> 16, 4, kBaseHex, QChar('0')) + .arg(vlan_[i] & 0xFFFF) : QString("%1") - .arg(vlan_[0] & 0xFFFF)) - .arg((vlan_[1] >> 16) != kVlanTpid ? - QString("0x%1-%2") - .arg(vlan_[1] >> 16, 4, kBaseHex, QChar('0')) - .arg(vlan_[1] & 0xFFFF) : - QString("%1") - .arg(vlan_[1] & 0xFFFF)) - .arg((vlan_[2] >> 16) != kVlanTpid ? - QString("0x%1-%2") - .arg(vlan_[2] >> 16, 4, kBaseHex, QChar('0')) - .arg(vlan_[2] & 0xFFFF) : - QString("%1") - .arg(vlan_[2] & 0xFFFF)) - .arg((vlan_[3] >> 16) != kVlanTpid ? - QString("0x%1-%2") - .arg(vlan_[3] >> 16, 4, kBaseHex, QChar('0')) - .arg(vlan_[3] & 0xFFFF) : - QString("%1") - .arg(vlan_[3] & 0xFFFF)) - .arg(mac_, 12, kBaseHex, QChar('0')) - .arg(QHostAddress(ip4_).toString()) - .arg(ip4PrefixLength_) - .arg(QHostAddress(ip6_.toArray()).toString()) - .arg(ip6PrefixLength_); + .arg(vlan_[i] & 0xFFFF)); + } + + config.append(QString(" mac=%1") + .arg(mac_, 12, kBaseHex, QChar('0'))); + if (hasIp4_) + config.append(QString(" ip4=%1/%2") + .arg(QHostAddress(ip4_).toString()) + .arg(ip4PrefixLength_)); + if (hasIp6_) + config.append(QString(" ip6=%1/%2") + .arg(QHostAddress(ip6_.toArray()).toString()) + .arg(ip6PrefixLength_)); + + return config; } DeviceKey Device::key() @@ -212,11 +210,13 @@ void Device::receivePacket(PacketBuffer *pktBuf) switch(ethType) { case 0x0806: // ARP - receiveArp(pktBuf); + if (hasIp4_) + receiveArp(pktBuf); break; case 0x0800: // IPv4 - receiveIp4(pktBuf); + if (hasIp4_) + receiveIp4(pktBuf); break; case 0x86dd: // IPv6 @@ -249,11 +249,13 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf) switch(ethType) { case 0x0800: // IPv4 - sendArpRequest(pktBuf); + if (hasIp4_) + sendArpRequest(pktBuf); break; case 0x86dd: // IPv6 - sendNeighborSolicit(pktBuf); + if (hasIp6_) + sendNeighborSolicit(pktBuf); break; default: @@ -278,6 +280,7 @@ void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors) } } +// Are we the source of the given packet? // We expect pktBuf to point to EthType on entry bool Device::isOrigin(const PacketBuffer *pktBuf) { @@ -288,7 +291,7 @@ bool Device::isOrigin(const PacketBuffer *pktBuf) pktData += 2; // We know only about IP packets - if (ethType == 0x0800) { // IPv4 + if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 srcIp; @@ -306,6 +309,7 @@ bool Device::isOrigin(const PacketBuffer *pktBuf) return false; } +// Return the mac address corresponding to the dstIp of the given packet // We expect pktBuf to point to EthType on entry quint64 Device::neighborMac(const PacketBuffer *pktBuf) { @@ -316,7 +320,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) pktData += 2; // We know only about IP packets - if (ethType == 0x0800) { // IPv4 + if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 dstIp, tgtIp, mask; diff --git a/server/device.h b/server/device.h index 32d848c..e2bb69d 100644 --- a/server/device.h +++ b/server/device.h @@ -87,10 +87,12 @@ private: // data quint32 vlan_[kMaxVlan]; quint64 mac_; + bool hasIp4_; quint32 ip4_; int ip4PrefixLength_; quint32 ip4Gateway_; + bool hasIp6_; UInt128 ip6_; int ip6PrefixLength_; UInt128 ip6Gateway_; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 5df1391..d3393f7 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -188,7 +188,7 @@ void DeviceManager::receivePacket(PacketBuffer *pktBuf) // 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; + goto _exit; } // Extract dstMac @@ -241,7 +241,7 @@ _eth_type: device->receivePacket(pktBuf); _exit: - return; + delete pktBuf; } void DeviceManager::transmitPacket(PacketBuffer *pktBuf) @@ -343,6 +343,8 @@ void DeviceManager::enumerateDevices( 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); @@ -430,12 +432,14 @@ void DeviceManager::enumerateDevices( UInt128 ip6Add = UINT128(ip6.step()) * k; dk.setMac(mac.address() + macAdd); - dk.setIp4(ip4.address() + ip4Add, - ip4.prefix_length(), - ip4.default_gateway()); - dk.setIp6(UINT128(ip6.address()) + ip6Add, - ip6.prefix_length(), - UINT128(ip6.default_gateway())); + 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())); // TODO: fill in other pbDevice data From d9be523827bbb3874a12a10a5194fed27d90c159 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 1 Jan 2016 20:17:54 +0530 Subject: [PATCH 027/121] Device Emulation (contd.) - Implemented sending of IPv6 Neighbor Solicitation packets for IPv6 resolution --- common/uint128.h | 64 +++++++++++++++-- server/device.cpp | 179 +++++++++++++++++++++++++++++++++++++++++----- server/device.h | 6 +- test/emultest.py | 47 ++++++------ 4 files changed, 253 insertions(+), 43 deletions(-) diff --git a/common/uint128.h b/common/uint128.h index 6d3f307..80ccf78 100644 --- a/common/uint128.h +++ b/common/uint128.h @@ -20,6 +20,8 @@ along with this program. If not, see #ifndef _UINT128_H #define _UINT128_H +#include + #include #include @@ -33,8 +35,12 @@ public: quint64 lo64() const; quint8* toArray() const; - UInt128 operator+(const UInt128 &other); - UInt128 operator*(const uint &other); + bool operator==(const UInt128 &other) const; + UInt128 operator+(const UInt128 &other) const; + UInt128 operator*(const uint &other) const; + UInt128 operator<<(const int &shift) const; + UInt128 operator~() const; + UInt128 operator&(const UInt128 &other) const; private: quint64 hi_; @@ -71,7 +77,12 @@ inline quint8* UInt128::toArray() const return (quint8*)array_; } -inline UInt128 UInt128::operator+(const UInt128 &other) +inline bool UInt128::operator==(const UInt128 &other) const +{ + return ((hi_ == other.hi_) && (lo_ == other.lo_)); +} + +inline UInt128 UInt128::operator+(const UInt128 &other) const { UInt128 sum; @@ -81,7 +92,7 @@ inline UInt128 UInt128::operator+(const UInt128 &other) return sum; } -inline UInt128 UInt128::operator*(const uint &other) +inline UInt128 UInt128::operator*(const uint &other) const { UInt128 product; @@ -92,4 +103,49 @@ inline UInt128 UInt128::operator*(const uint &other) return product; } +inline UInt128 UInt128::operator<<(const int &shift) const +{ + UInt128 shifted; + + if (shift < 64) + return UInt128((hi_<>(64-shift)), lo_ << shift); + + return UInt128(hi_<<(shift-64), 0); +} + +inline UInt128 UInt128::operator~() const +{ + return UInt128(~hi_, ~lo_); +} + +inline UInt128 UInt128::operator&(const UInt128 &other) const +{ + return UInt128(hi_ & other.hi_, lo_ & other.lo_); +} + +template <> inline UInt128 qFromBigEndian(const uchar *src) +{ + quint64 hi, lo; + + hi = qFromBigEndian(src); + lo = qFromBigEndian(src+8); + + return UInt128(hi, lo); +} + +template <> inline UInt128 qToBigEndian(const UInt128 src) +{ + quint64 hi, lo; + + hi = qToBigEndian(src.hi64()); + lo = qToBigEndian(src.lo64()); + + return UInt128(hi, lo); +} + +inline uint qHash(const UInt128 &key) +{ + return qHash(key.hi64()) ^ qHash(key.lo64()); +} + #endif diff --git a/server/device.cpp b/server/device.cpp index f736de4..0fa731a 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -28,6 +28,10 @@ along with this program. If not, see const int kBaseHex = 16; const quint64 kBcastMac = 0xffffffffffffULL; +const quint16 kEthTypeIp4 = 0x0800; +const quint16 kEthTypeIp6 = 0x86dd; +const int kIp6HdrLen = 40; +const quint8 kIpProtoIcmp6 = 58; /* * NOTE: @@ -37,6 +41,17 @@ const quint64 kBcastMac = 0xffffffffffffULL; * (e.g. in a hash) */ +inline quint32 sumUInt128(UInt128 value) +{ + quint8 *arr = value.toArray(); + quint32 sum = 0; + + for (int i = 0; i < 16; i += 2) + sum += qToBigEndian(*((quint16*)(arr + i))); + + return sum; +} + Device::Device(DeviceManager *deviceManager) { deviceManager_ = deviceManager; @@ -81,8 +96,8 @@ void Device::setMac(quint64 mac) { int ofs = kMaxVlan * sizeof(quint16); - mac_ = mac; - memcpy(key_.data() + ofs, (char*)&mac, sizeof(mac)); + mac_ = mac & ~(0xffffULL << 48); + memcpy(key_.data() + ofs, (char*)&mac_, sizeof(mac_)); } void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) @@ -234,7 +249,7 @@ void Device::transmitPacket(PacketBuffer *pktBuf) void Device::clearNeighbors() { - arpTable.clear(); + arpTable_.clear(); } // Resolve the Neighbor IP address for this to-be-transmitted pktBuf @@ -268,8 +283,8 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf) // Append this device's neighbors to the list void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors) { - QList ipList = arpTable.keys(); - QList macList = arpTable.values(); + QList ipList = arpTable_.keys(); + QList macList = arpTable_.values(); Q_ASSERT(ipList.size() == macList.size()); @@ -290,12 +305,13 @@ bool Device::isOrigin(const PacketBuffer *pktBuf) qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); pktData += 2; - // We know only about IP packets + // We know only about IP packets - adjust for ethType length (2 bytes) + // when checking that we have a complete IP header if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 srcIp; - if (pktBuf->length() < ipHdrLen) { + if (pktBuf->length() < (ipHdrLen+2)) { qDebug("incomplete IPv4 header: expected %d, actual %d", ipHdrLen, pktBuf->length()); return false; @@ -305,6 +321,19 @@ bool Device::isOrigin(const PacketBuffer *pktBuf) qDebug("%s: pktSrcIp/selfIp = 0x%x/0x%x", __FUNCTION__, srcIp, ip4_); return (srcIp == ip4_); } + else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6 + UInt128 srcIp; + if (pktBuf->length() < (kIp6HdrLen+2)) { + qDebug("incomplete IPv6 header: expected %d, actual %d", + kIp6HdrLen, pktBuf->length()-2); + return false; + } + + srcIp = qFromBigEndian(pktData + 8); + qDebug("%s: pktSrcIp6/selfIp6 = %llx-%llx/%llx-%llx", __FUNCTION__, + srcIp.hi64(), srcIp.lo64(), ip6_.hi64(), ip6_.lo64()); + return (srcIp == ip6_); + } return false; } @@ -335,7 +364,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) qDebug("dst %x self %x mask %x", dstIp, ip4_, mask); tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; - return arpTable.value(tgtIp); + return arpTable_.value(tgtIp); } return false; @@ -344,6 +373,11 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) // // Private Methods // +/* + * --------------------------------------------------------- + * IPv4 related private methods + * --------------------------------------------------------- + */ void Device::receiveArp(PacketBuffer *pktBuf) { PacketBuffer *rspPkt; @@ -404,7 +438,7 @@ void Device::receiveArp(PacketBuffer *pktBuf) switch (opCode) { case 1: // ARP Request - arpTable.insert(srcIp, srcMac); + arpTable_.insert(srcIp, srcMac); rspPkt = new PacketBuffer; rspPkt->reserve(encapSize()); @@ -432,7 +466,7 @@ void Device::receiveArp(PacketBuffer *pktBuf) qPrintable(QHostAddress(tgtIp).toString())); break; case 2: // ARP Response - arpTable.insert(srcIp, srcMac); + arpTable_.insert(srcIp, srcMac); break; default: @@ -462,6 +496,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) return; } + // FIXME: not required - caller is checking for origin anyway // Extract srcIp first to check quickly that this packet originates // from this device srcIp = qFromBigEndian(pktData + ipHdrLen - 8); @@ -480,7 +515,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) // Do we already have a ARP entry (resolved or unresolved)? // FIXME: do we need a timer to resend ARP for unresolved entries? - if (arpTable.contains(tgtIp)) + if (arpTable_.contains(tgtIp)) return; reqPkt = new PacketBuffer; @@ -503,7 +538,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) encap(reqPkt, kBcastMac, 0x0806); transmitPacket(reqPkt); - arpTable.insert(tgtIp, 0); + arpTable_.insert(tgtIp, 0); qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s", qPrintable(QHostAddress(srcIp).toString()), @@ -567,7 +602,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt - if (!arpTable.contains(dstIp)) + if (!arpTable_.contains(dstIp)) return; *(quint32*)(pktData + 12) = qToBigEndian(srcIp); @@ -585,7 +620,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) sum = (sum & 0xFFFF) + (sum >> 16); *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum)); - encap(pktBuf, arpTable.value(dstIp), 0x0800); + encap(pktBuf, arpTable_.value(dstIp), 0x0800); transmitPacket(pktBuf); } @@ -617,11 +652,123 @@ void Device::receiveIcmp4(PacketBuffer *pktBuf) qDebug("Sent ICMP Echo Reply"); } +/* + * --------------------------------------------------------- + * IPV6 related private methods + * --------------------------------------------------------- + */ + +// pktBuf should point to start of IP payload +bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) +{ + int payloadLen = pktBuf->length(); + uchar *p = pktBuf->push(kIp6HdrLen); + quint64 dstMac = kBcastMac; + + if (!p) { + qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, + kIp6HdrLen, pktBuf->head(), pktBuf->data()); + goto _error_exit; + } + + // Ver(4), TrfClass(8), FlowLabel(8) + *(quint32*)(p ) = qToBigEndian(quint32(0x60000000)); + *(quint16*)(p+ 4) = qToBigEndian(quint16(payloadLen)); + p[6] = protocol; + p[7] = 255; // HopLimit + memcpy(p+ 8, ip6_.toArray(), 16); // Source IP + memcpy(p+24, dstIp.toArray(), 16); // Destination IP + + // In case of mcast, derive dstMac + if ((dstIp.hi64() >> 56) == 0xff) + dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); + + // FIXME: both these functions should return success/failure + encap(pktBuf, dstMac, kEthTypeIp6); + transmitPacket(pktBuf); + + return true; + +_error_exit: + return false; +} + // Send NS for the IPv6 packet in pktBuf -// pktBuf points to start of IP header +// caller is responsible to check that pktBuf originates from this device +// pktBuf should point to start of IP header void Device::sendNeighborSolicit(PacketBuffer *pktBuf) { - // TODO + PacketBuffer *reqPkt; + uchar *pktData = pktBuf->data(); + UInt128 srcIp, dstIp, mask, tgtIp; + + if (pktBuf->length() < kIp6HdrLen) { + qDebug("incomplete IPv6 header: expected %d, actual %d", + kIp6HdrLen, pktBuf->length()); + return; + } + + srcIp = qFromBigEndian(pktData + 8); + dstIp = qFromBigEndian(pktData + 24); + + mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + qDebug("%s: dst %s src %s mask %s", __FUNCTION__, + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(srcIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString())); + tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_; + + // Do we already have a NDP entry (resolved or unresolved)? + // FIXME: do we need a timer to resend NS for unresolved entries? + if (ndpTable_.contains(tgtIp)) + return; + + // Form the solicited node address to be used as dstIp + // ff02::1:ffXX:XXXX/104 + dstIp = UInt128((quint64(0xff02) << 48), + (quint64(0x01ff) << 24) | (tgtIp.lo64() & 0xFFFFFF)); + + reqPkt = new PacketBuffer; + reqPkt->reserve(encapSize() + kIp6HdrLen); + pktData = reqPkt->put(32); + if (pktData) { + // Calculate checksum first - + // start with fixed fields in ICMP Header and IPv6 Pseudo Header ... + quint32 sum = 0x8700 + 0x0101 + 32 + kIpProtoIcmp6; + + // then variable fields from ICMP header ... + sum += sumUInt128(tgtIp); + sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff); + + // and variable fields from IPv6 pseudo header + sum += sumUInt128(ip6_); + sum += sumUInt128(dstIp); + + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Type, Code + *(quint16*)(pktData ) = qToBigEndian(quint16(0x8700)); + // Checksum + *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum)); + // Reserved + *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0)); + // Target IP + memcpy(pktData+ 8, tgtIp.toArray(), 16); + // Source Addr TLV + MacAddr + *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0101)); + *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16)); + *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff)); + } + + if (!sendIp6(reqPkt, dstIp , kIpProtoIcmp6)) + return; + + ndpTable_.insert(tgtIp, 0); + + qDebug("Sent NDP Request for srcIp/tgtIp=%s/%s", + qPrintable(QHostAddress(srcIp.toArray()).toString()), + qPrintable(QHostAddress(tgtIp.toArray()).toString())); } bool operator<(const DeviceKey &a1, const DeviceKey &a2) diff --git a/server/device.h b/server/device.h index e2bb69d..e0c1455 100644 --- a/server/device.h +++ b/server/device.h @@ -75,8 +75,9 @@ private: // methods void receiveIcmp4(PacketBuffer *pktBuf); + bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol); + void sendNeighborSolicit(PacketBuffer *pktBuf); - void sendIp6(PacketBuffer *pktBuf); private: // data static const int kMaxVlan = 4; @@ -99,7 +100,8 @@ private: // data DeviceKey key_; - QHash arpTable; + QHash arpTable_; + QHash ndpTable_; }; bool operator<(const DeviceKey &a1, const DeviceKey &a2); diff --git a/test/emultest.py b/test/emultest.py index 5a114e1..a788a12 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -159,6 +159,7 @@ def ports(request, drone): def dut(request): # Enable IP forwarding on the DUT (aka make it a router) sudo('sysctl -w net.ipv4.ip_forward=1') + sudo('sysctl -w net.ipv6.conf.all.forwarding=1') @pytest.fixture(scope='module') def dut_ports(request): @@ -559,20 +560,22 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') for i in range(len(ip_versions)): + if ip_versions[i] == 'ip4': + filter = '(arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.10.1.)' \ + ' && (arp.dst.proto_ipv4 == 10.10.1.1)' \ + ' && !expert.severity' + elif ip_versions[i] == 'ip6': + filter = '(icmpv6.type == 135)' \ + ' && (ipv6.src == 1234:1::)' \ + ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \ + ' && !expert.severity' for j in range(num_devs): if ip_versions[i] == 'ip4': - filter = '(arp.opcode == 1)' \ - ' && (arp.src.proto_ipv4 == 10.10.1.' \ - + str(101+j*ip_step) + ')' \ - ' && (arp.dst.proto_ipv4 == 10.10.1.1)' + filter = filter.replace('', str(101+j*ip_step)) elif ip_versions[i] == 'ip6': - filter = '(icmpv6.type == 135)' \ - ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \ - ' && (icmpv6.nd.ns.target_address == 1234:1::' \ - + format(0x65+i*ip_step, 'x')+')' - print filter - else: - assert False # unreachable + filter = filter.replace('', format(0x65+j*ip_step, 'x')) + #print filter cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Y', filter]) print(cap_pkts) @@ -586,19 +589,21 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(len(ip_versions)): + if ip_versions[i] == 'ip4': + filter = '(arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.10.2.)' \ + ' && (arp.dst.proto_ipv4 == 10.10.2.1)' \ + ' && !expert.severity' + elif ip_versions[i] == 'ip6': + filter = '(icmpv6.type == 135)' \ + ' && (ipv6.src == 1234:2::)' \ + ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \ + ' && !expert.severity' for j in range(num_devs): if ip_versions[i] == 'ip4': - filter = '(arp.opcode == 1)' \ - ' && (arp.src.proto_ipv4 == 10.10.2.' \ - + str(101+j*ip_step) + ')' \ - ' && (arp.dst.proto_ipv4 == 10.10.2.1)' + filter = filter.replace('', str(101+j*ip_step)) elif ip_versions[i] == 'ip6': - filter = '(icmpv6.type == 135)' \ - ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \ - ' && (icmpv6.nd.ns.target_address == 1234:2::' \ - + format(0x65+i*ip_step, 'x')+')' - else: - assert False # unreachable + filter = filter.replace('', format(0x65+j*ip_step, 'x')) print filter cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Y', filter]) From eff603304efd6498e1206aa71faa51020f36c41d Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 6 Jan 2016 18:10:28 +0530 Subject: [PATCH 028/121] Device Emulation (contd.): Receive, parse IPv6 Neigh Advt and update NDP Table --- common/emulproto.proto | 4 +- common/uint128.h | 6 ++ server/device.cpp | 141 +++++++++++++++++++++++++++++++++++++++-- server/device.h | 4 ++ server/pcapport.cpp | 20 +++--- test/emultest.py | 68 +++++++++++++------- 6 files changed, 204 insertions(+), 39 deletions(-) diff --git a/common/emulproto.proto b/common/emulproto.proto index 0d0de76..2c4430a 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -104,7 +104,7 @@ message ArpEntry { optional uint64 mac = 2; } -message NdEntry { +message NdpEntry { optional Ip6Address ip6 = 1; optional uint64 mac = 2; } @@ -112,7 +112,7 @@ message NdEntry { message DeviceNeighborList { optional uint32 device_index = 1; repeated ArpEntry arp = 2; - repeated NdEntry nd = 3; + repeated NdpEntry ndp = 3; } extend OstProto.PortNeighborList { diff --git a/common/uint128.h b/common/uint128.h index 80ccf78..3e6601b 100644 --- a/common/uint128.h +++ b/common/uint128.h @@ -36,6 +36,7 @@ public: quint8* toArray() const; bool operator==(const UInt128 &other) const; + bool operator!=(const UInt128 &other) const; UInt128 operator+(const UInt128 &other) const; UInt128 operator*(const uint &other) const; UInt128 operator<<(const int &shift) const; @@ -82,6 +83,11 @@ inline bool UInt128::operator==(const UInt128 &other) const return ((hi_ == other.hi_) && (lo_ == other.lo_)); } +inline bool UInt128::operator!=(const UInt128 &other) const +{ + return ((hi_ != other.hi_) || (lo_ != other.lo_)); +} + inline UInt128 UInt128::operator+(const UInt128 &other) const { UInt128 sum; diff --git a/server/device.cpp b/server/device.cpp index 0fa731a..1323a33 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -235,6 +235,10 @@ void Device::receivePacket(PacketBuffer *pktBuf) break; case 0x86dd: // IPv6 + if (hasIp6_) + receiveIp6(pktBuf); + break; + default: break; } @@ -283,16 +287,28 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf) // Append this device's neighbors to the list void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors) { - QList ipList = arpTable_.keys(); - QList macList = arpTable_.values(); + QList ip4List = arpTable_.keys(); + QList ip6List = ndpTable_.keys(); + QList macList; - Q_ASSERT(ipList.size() == macList.size()); + macList = arpTable_.values(); + Q_ASSERT(ip4List.size() == macList.size()); - for (int i = 0; i < ipList.size(); i++) { + for (int i = 0; i < ip4List.size(); i++) { OstEmul::ArpEntry *arp = neighbors->add_arp(); - arp->set_ip4(ipList.at(i)); + arp->set_ip4(ip4List.at(i)); arp->set_mac(macList.at(i)); } + + macList = ndpTable_.values(); + Q_ASSERT(ip6List.size() == macList.size()); + + for (int i = 0; i < ip6List.size(); i++) { + OstEmul::NdpEntry *ndp = neighbors->add_ndp(); + ndp->mutable_ip6()->set_hi(ip6List.at(i).hi64()); + ndp->mutable_ip6()->set_lo(ip6List.at(i).lo64()); + ndp->set_mac(macList.at(i)); + } } // Are we the source of the given packet? @@ -658,6 +674,46 @@ void Device::receiveIcmp4(PacketBuffer *pktBuf) * --------------------------------------------------------- */ +void Device::receiveIp6(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + uchar ipProto; + UInt128 dstIp; + + if ((pktData[0] & 0xF0) != 0x60) { + qDebug("%s: Unsupported IP version (%02x) ", __FUNCTION__, + pktData[0]); + goto _invalid_exit; + } + + if (pktBuf->length() < kIp6HdrLen) { + qDebug("incomplete IPv6 header: expected %d, actual %d", + kIp6HdrLen, pktBuf->length()); + goto _invalid_exit; + } + + dstIp = qFromBigEndian(pktData + 24); + if (dstIp != ip6_) { + qDebug("%s: dstIp %s is not me (%s)", __FUNCTION__, + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + goto _invalid_exit; + } + + ipProto = pktData[6]; + switch (ipProto) { + case kIpProtoIcmp6: + pktBuf->pull(kIp6HdrLen); + receiveIcmp6(pktBuf); + break; + default: + break; + } + +_invalid_exit: + return; +} + // pktBuf should point to start of IP payload bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) { @@ -693,6 +749,81 @@ _error_exit: return false; } +void Device::receiveIcmp6(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + quint8 type = pktData[0]; + + // XXX: We don't verify icmp checksum + + switch (type) { + case 135: // Neigh Solicit + case 136: // Neigh Advt + receiveNdp(pktBuf); + break; + default: + break; + } +} + +void Device::receiveNdp(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->data(); + quint8 type = pktData[0]; + + if (pktBuf->length() < 32) { + qDebug("%s: incomplete NA header: expected 32, actual %d", + __FUNCTION__, pktBuf->length()); + goto _invalid_exit; + } + + switch (type) + { + case 135: { // Neigh Solicit +#if 0 + quint32 sum; + pktData[0] = 0; // Echo Reply + + // Incremental checksum update (RFC 1624 [Eqn.3]) + // HC' = ~(~HC + ~m + m') + sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum + sum += quint16(~quint16(8 << 8 | pktData[1])); // old value + sum += quint16(0 << 8 | pktData[1]); // new value + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum)); + + sendIp4Reply(pktBuf); + qDebug("Sent ICMP Echo Reply"); +#endif + break; + } + case 136: { // Neigh Advt + quint8 flags = pktData[4]; + const quint8 kSFlag = 0x40; + const quint8 kOFlag = 0x20; + UInt128 tgtIp = qFromBigEndian(pktData + 8); + quint64 mac = ndpTable_.value(tgtIp); + + // Update NDP table only for solicited responses + if (!(flags & kSFlag)) + break; + + if ((flags & kOFlag) || (mac == 0)) { + // Check if we have a Target Link-Layer TLV + if ((pktData[24] != 2) || (pktData[25] != 1)) + goto _invalid_exit; + mac = qFromBigEndian(pktData + 26); + mac = (mac << 16) | qFromBigEndian(pktData + 30); + ndpTable_.insert(tgtIp, mac); + } + break; + } + } +_invalid_exit: + return; +} + // Send NS for the IPv6 packet in pktBuf // caller is responsible to check that pktBuf originates from this device // pktBuf should point to start of IP header diff --git a/server/device.h b/server/device.h index e0c1455..d7738d8 100644 --- a/server/device.h +++ b/server/device.h @@ -75,8 +75,12 @@ private: // methods void receiveIcmp4(PacketBuffer *pktBuf); + void receiveIp6(PacketBuffer *pktBuf); bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol); + void receiveIcmp6(PacketBuffer *pktBuf); + + void receiveNdp(PacketBuffer *pktBuf); void sendNeighborSolicit(PacketBuffer *pktBuf); private: // data diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 272fcb2..22036b6 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -966,18 +966,18 @@ void PcapPort::EmulationTransceiver::run() libpcap changes their implementation, this will need to change as well. */ const char *capture_filter = - "arp or icmp or " - "(vlan and (arp or icmp)) or " - "(vlan and vlan and (arp or icmp)) or " - "(vlan and vlan and vlan and (arp or icmp)) or " - "(vlan and vlan and vlan and vlan and (arp or icmp))"; + "arp or icmp or icmp6" + "(vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))"; #else const char *capture_filter = - "arp or icmp or " - "(vlan and (arp or icmp)) or " - "(vlan and (arp or icmp)) or " - "(vlan and (arp or icmp)) or " - "(vlan and (arp or icmp))"; + "arp or icmp or icmp6 " + "(vlan and (arp or icmp or icmp6)) or " + "(vlan and (arp or icmp or icmp6)) or " + "(vlan and (arp or icmp or icmp6)) or " + "(vlan and (arp or icmp or icmp6))"; #endif const int optimize = 1; diff --git a/test/emultest.py b/test/emultest.py index a788a12..a0da466 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -78,7 +78,10 @@ if not use_defaults: # the python ipaddress module class ip6_address(ipaddress.IPv6Interface): def __init__(self, addr): - super(ip6_address, self).__init__(unicode(addr)) + if type(addr) is str: + super(ip6_address, self).__init__(unicode(addr)) + else: + super(ip6_address, self).__init__(addr.hi << 64 | addr.lo) self.ip6 = emul.Ip6Address() self.ip6.hi = int(self) >> 64 self.ip6.lo = int(self) & 0xffffffffffffffff @@ -544,6 +547,10 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, assert re.search('10.10.[1-2].1\d\d.*lladdr', arp_cache) == None assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None + # wait for interface to do DAD? Otherwise we don't get replies for NS + # FIXME: find alternative to sleep + time.sleep(5) + # resolve ARP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') drone.startCapture(emul_ports) @@ -610,39 +617,56 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) assert cap_pkts.count('\n') == 0 - # retrieve and verify ARP Table on tx/rx ports - log.info('retrieving ARP entries on tx port') + # retrieve and verify ARP/NDP Table on tx/rx ports + log.info('retrieving ARP/NDP entries on tx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on tx port') + log.info('ARP/NDP Table on tx port') for dev_cfg, device in zip(device_config, devices): resolved = False - for arp in device.arp: - print('%s: %s %012x' % - (str(ipaddress.ip_address(dev_cfg.ip4)), - str(ipaddress.ip_address(arp.ip4)), - arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): - resolved = True - # TODO: ip6/ndp - assert resolved + if has_ip4: + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(dev_cfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), + arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved + if has_ip6: + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(dev_cfg.ip6)), + str(ip6_address(ndp.ip6)), + ndp.mac)) + if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + resolved = True + assert resolved - log.info('retrieving ARP entries on rx port') + log.info('retrieving ARP/NDP entries on rx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on rx port') + log.info('ARP/NDP Table on rx port') for dev_cfg, device in zip(device_config, devices): - # verify *no* ARPs learnt on rx port - assert len(device.arp) == 0 - for arp in device.arp: - # TODO: pretty print ip and mac - print('%08x: %08x %012x' % - (dev_cfg.ip4, arp.ip4, arp.mac)) - # TODO: ip6/ndp + # verify *no* ARPs/NDPs learnt on rx port + if has_ip4: + assert len(device.arp) == 0 + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(dev_cfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), + arp.mac)) + if has_ip6: + assert len(device.ndp) == 0 + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(dev_cfg.ip6)), + str(ip6_address(ndp.ip6)), + ndp.mac)) # ping the tx devices from the DUT for i in range(num_devs): From 04147076c4cdc3c1954eb0db8cf8c11b3472968e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 8 Jan 2016 20:01:42 +0530 Subject: [PATCH 029/121] Device Emulation (contd.): Implemented responding IPv6 NS with NA --- server/device.cpp | 106 +++++++++++++++++++++++++++++++-------- server/device.h | 1 + server/devicemanager.cpp | 17 +++++-- test/emultest.py | 22 +++++--- 4 files changed, 116 insertions(+), 30 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index 1323a33..572c3da 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -52,6 +52,11 @@ inline quint32 sumUInt128(UInt128 value) return sum; } +inline bool isIp6Mcast(UInt128 ip) +{ + return (ip.hi64() >> 56) == 0xff; +} + Device::Device(DeviceManager *deviceManager) { deviceManager_ = deviceManager; @@ -588,12 +593,14 @@ void Device::receiveIp4(PacketBuffer *pktBuf) } ipProto = pktData[9]; + qDebug("%s: ipProto = %d", __FUNCTION__, ipProto); switch (ipProto) { case 1: // ICMP pktBuf->pull(20); receiveIcmp4(pktBuf); break; default: + qWarning("%s: Unsupported ipProto %d", __FUNCTION__, ipProto); break; } @@ -692,8 +699,9 @@ void Device::receiveIp6(PacketBuffer *pktBuf) goto _invalid_exit; } + // FIXME: check for specific mcast address(es) instead of any mcast? dstIp = qFromBigEndian(pktData + 24); - if (dstIp != ip6_) { + if (!isIp6Mcast(dstIp) && (dstIp != ip6_)) { qDebug("%s: dstIp %s is not me (%s)", __FUNCTION__, qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(ip6_.toArray()).toString())); @@ -770,32 +778,20 @@ void Device::receiveNdp(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); quint8 type = pktData[0]; + int len = pktBuf->length(); + int minLen = 24 + (type == 136 ? 8 : 0); // NA should have the Target TLV - if (pktBuf->length() < 32) { - qDebug("%s: incomplete NA header: expected 32, actual %d", - __FUNCTION__, pktBuf->length()); + if (len < minLen) { + qDebug("%s: incomplete NS/NA header: expected %d, actual %d", + __FUNCTION__, minLen, pktBuf->length()); goto _invalid_exit; } switch (type) { case 135: { // Neigh Solicit -#if 0 - quint32 sum; - pktData[0] = 0; // Echo Reply - - // Incremental checksum update (RFC 1624 [Eqn.3]) - // HC' = ~(~HC + ~m + m') - sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum - sum += quint16(~quint16(8 << 8 | pktData[1])); // old value - sum += quint16(0 << 8 | pktData[1]); // new value - while(sum >> 16) - sum = (sum & 0xFFFF) + (sum >> 16); - *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum)); - - sendIp4Reply(pktBuf); - qDebug("Sent ICMP Echo Reply"); -#endif + // TODO: Validation as per RFC 4861 + sendNeighborAdvertisement(pktBuf); break; } case 136: { // Neigh Advt @@ -902,6 +898,76 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf) qPrintable(QHostAddress(tgtIp.toArray()).toString())); } +// Send NA for the NS packet in pktBuf +// pktBuf should point to start of ICMPv6 header +void Device::sendNeighborAdvertisement(PacketBuffer *pktBuf) +{ + PacketBuffer *naPkt; + uchar *pktData = pktBuf->data(); + quint16 flags = 0x6000; // solicit = 1; overide = 1 + uchar *ip6Hdr; + UInt128 tgtIp, srcIp; + + tgtIp = qFromBigEndian(pktData + 8); + if (tgtIp != ip6_) { + qDebug("%s: NS tgtIp %s is not us %s", __FUNCTION__, + qPrintable(QHostAddress(tgtIp.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + ip6Hdr = pktBuf->push(kIp6HdrLen); + return; + } + + ip6Hdr = pktBuf->push(kIp6HdrLen); + srcIp = qFromBigEndian(ip6Hdr + 8); + + if (srcIp == UInt128(0, 0)) { + // reset solicit flag + flags &= ~0x4000; + // NA should be sent to All nodes address + srcIp = UInt128(quint64(0xff02) << 48, quint64(1)); + } + + naPkt = new PacketBuffer; + naPkt->reserve(encapSize() + kIp6HdrLen); + pktData = naPkt->put(32); + if (pktData) { + // Calculate checksum first - + // start with fixed fields in ICMP Header and IPv6 Pseudo Header ... + quint32 sum = (0x8800 + flags + 0x0201) + (32 + kIpProtoIcmp6); + + // then variable fields from ICMP header ... + sum += sumUInt128(tgtIp); + sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff); + + // and variable fields from IPv6 pseudo header + sum += sumUInt128(ip6_); + sum += sumUInt128(srcIp); + + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Type, Code + *(quint16*)(pktData ) = qToBigEndian(quint16(0x8800)); + // Checksum + *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum)); + // Flags-Reserved + *(quint32*)(pktData+ 4) = qToBigEndian(quint32(flags << 16)); + // Target IP + memcpy(pktData+ 8, tgtIp.toArray(), 16); + // Target Addr TLV + MacAddr + *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0201)); + *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16)); + *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff)); + } + + if (!sendIp6(naPkt, srcIp , kIpProtoIcmp6)) + return; + + qDebug("Sent Neigh Advt to dstIp for tgtIp=%s/%s", + qPrintable(QHostAddress(srcIp.toArray()).toString()), + qPrintable(QHostAddress(tgtIp.toArray()).toString())); +} + bool operator<(const DeviceKey &a1, const DeviceKey &a2) { int i = 0; diff --git a/server/device.h b/server/device.h index d7738d8..e747698 100644 --- a/server/device.h +++ b/server/device.h @@ -82,6 +82,7 @@ private: // methods void receiveNdp(PacketBuffer *pktBuf); void sendNeighborSolicit(PacketBuffer *pktBuf); + void sendNeighborAdvertisement(PacketBuffer *pktBuf); private: // data static const int kMaxVlan = 4; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index d3393f7..d82714b 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -31,12 +31,18 @@ along with this program. If not, see #define __STDC_FORMAT_MACROS #include +const quint64 kBcastMac = 0xffffffffffffULL; + inline UInt128 UINT128(OstEmul::Ip6Address x) { return UInt128(x.hi(), x.lo()); } -const quint64 kBcastMac = 0xffffffffffffULL; +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. @@ -195,14 +201,19 @@ void DeviceManager::receivePacket(PacketBuffer *pktBuf) dstMac = qFromBigEndian(pktData + offset); offset += 4; dstMac = (dstMac << 16) | qFromBigEndian(pktData + offset); + + qDebug("dstMac %012" PRIx64, dstMac); + + // XXX: Treat multicast as bcast + if (isMacMcast(dstMac)) + dstMac = kBcastMac; + 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); diff --git a/test/emultest.py b/test/emultest.py index a0da466..09a669c 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -670,15 +670,23 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # ping the tx devices from the DUT for i in range(num_devs): - out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) - assert '100% packet loss' not in out - # TODO: ip6/ndp + if has_ip4: + out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = run('ping -6 -c3 1234:1::'+format(101+i*ip_step, 'x'), + warn_only=True) + assert '100% packet loss' not in out - # ping the tx devices from the DUT + # ping the rx devices from the DUT for i in range(num_devs): - out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) - assert '100% packet loss' not in out - # TODO: ip6/ndp + if has_ip4: + out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = run('ping -6 -c3 1234:2::'+format(101+i*ip_step, 'x'), + warn_only=True) + assert '100% packet loss' not in out # We are all set now - so transmit the stream now drone.startCapture(ports.rx) From f88f8ebd38a25ea27607084226186574046774e7 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 8 Jan 2016 21:59:34 +0530 Subject: [PATCH 030/121] Device Emulation (contd.): Fix incorrect capture filter --- server/pcapport.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 22036b6..cd6b370 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -966,14 +966,14 @@ void PcapPort::EmulationTransceiver::run() libpcap changes their implementation, this will need to change as well. */ const char *capture_filter = - "arp or icmp or icmp6" + "arp or icmp or icmp6 or " "(vlan and (arp or icmp or icmp6)) or " "(vlan and vlan and (arp or icmp or icmp6)) or " "(vlan and vlan and vlan and (arp or icmp or icmp6)) or " "(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))"; #else const char *capture_filter = - "arp or icmp or icmp6 " + "arp or icmp or icmp6 or " "(vlan and (arp or icmp or icmp6)) or " "(vlan and (arp or icmp or icmp6)) or " "(vlan and (arp or icmp or icmp6)) or " @@ -1053,7 +1053,14 @@ _skip_filter: case 1: { PacketBuffer *pktBuf = new PacketBuffer(data, hdr->caplen); - +#if 0 + for (int i = 0; i < 64; i++) { + printf("%02x ", data[i]); + if (i % 16 == 0) + printf("\n"); + } + printf("\n"); +#endif // 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 From 3afcb72b8d24f6aa1ad1ba2def9ec80fc5f7270f Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 12 Jan 2016 19:03:52 +0530 Subject: [PATCH 031/121] Device Emulation (contd.): Implemented IPv6 ping (echo) reply --- server/device.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ server/device.h | 1 + 2 files changed, 45 insertions(+) diff --git a/server/device.cpp b/server/device.cpp index 572c3da..859a9d4 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -757,14 +757,58 @@ _error_exit: return false; } +// This function assumes we are replying back to the same IP +// that originally sent us the packet and therefore we can reuse the +// ingress packet for egress; in other words, it assumes the +// original IP header is intact and will just reuse it after +// minimal modifications +void Device::sendIp6Reply(PacketBuffer *pktBuf) +{ + uchar *pktData = pktBuf->push(kIp6HdrLen); + UInt128 srcIp, dstIp; + + // Swap src/dst IP addresses + dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt + srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt + + if (!ndpTable_.contains(dstIp)) + return; + + memcpy(pktData + 8, srcIp.toArray(), 16); // Source IP + memcpy(pktData + 24, dstIp.toArray(), 16); // Destination IP + + // Reset TTL + pktData[7] = 64; + + encap(pktBuf, ndpTable_.value(dstIp), 0x86dd); + transmitPacket(pktBuf); +} + void Device::receiveIcmp6(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); quint8 type = pktData[0]; + quint32 sum; // XXX: We don't verify icmp checksum switch (type) { + case 128: // ICMPv6 Echo Request + pktData[0] = 129; // Echo Reply + + // Incremental checksum update (RFC 1624 [Eqn.3]) + // HC' = ~(~HC + ~m + m') + sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum + sum += quint16(~quint16(128 << 8 | pktData[1])); // old value + sum += quint16(129 << 8 | pktData[1]); // new value + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum)); + + sendIp6Reply(pktBuf); + qDebug("Sent ICMPv6 Echo Reply"); + break; + case 135: // Neigh Solicit case 136: // Neigh Advt receiveNdp(pktBuf); diff --git a/server/device.h b/server/device.h index e747698..f18a9f8 100644 --- a/server/device.h +++ b/server/device.h @@ -77,6 +77,7 @@ private: // methods void receiveIp6(PacketBuffer *pktBuf); bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol); + void sendIp6Reply(PacketBuffer *pktBuf); void receiveIcmp6(PacketBuffer *pktBuf); From 46a09a82e42f18e7a9b403060325f4678e6665bc Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 12 Jan 2016 19:08:01 +0530 Subject: [PATCH 032/121] Device Emulation (contd.): Reduced pcap timeout to improve ping response times --- server/pcapport.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/pcapport.cpp b/server/pcapport.cpp index cd6b370..ae0e0c7 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -993,10 +993,10 @@ _retry: #ifdef Q_OS_WIN32 // NOCAPTURE_LOCAL needs windows only pcap_open() handle_ = pcap_open(qPrintable(device_), 65535, - flags, 1000 /* ms */, NULL, errbuf); + flags, 100 /* ms */, NULL, errbuf); #else handle_ = pcap_open_live(qPrintable(device_), 65535, - flags, 1000 /* ms */, errbuf); + flags, 100 /* ms */, errbuf); #endif if (handle_ == NULL) From 21197146e2ad8d3310b531aa304aba8363039417 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 12 Jan 2016 21:02:19 +0530 Subject: [PATCH 033/121] Device Emulation (contd.): Create/Update NDP entry when NS with source TLV received; check for NDP entry when sending IPv6 packet --- server/device.cpp | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index 859a9d4..5f67c6f 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -625,8 +625,11 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt - if (!arpTable_.contains(dstIp)) + if (!arpTable_.contains(dstIp)) { + qWarning("%s: mac not found for %s; unable to send IPv4 packet", + __FUNCTION__, qPrintable(QHostAddress(dstIp).toString())); return; + } *(quint32*)(pktData + 12) = qToBigEndian(srcIp); *(quint32*)(pktData + 16) = qToBigEndian(dstIp); @@ -727,7 +730,7 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) { int payloadLen = pktBuf->length(); uchar *p = pktBuf->push(kIp6HdrLen); - quint64 dstMac = kBcastMac; + quint64 dstMac = ndpTable_.value(dstIp); if (!p) { qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, @@ -735,6 +738,17 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) goto _error_exit; } + // In case of mcast, derive dstMac + if ((dstIp.hi64() >> 56) == 0xff) + dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); + + if (!dstMac) { + qWarning("%s: mac not found for %s; unable to send IPv6 packet", + __FUNCTION__, + qPrintable(QHostAddress(dstIp.toArray()).toString())); + goto _error_exit; + } + // Ver(4), TrfClass(8), FlowLabel(8) *(quint32*)(p ) = qToBigEndian(quint32(0x60000000)); *(quint16*)(p+ 4) = qToBigEndian(quint16(payloadLen)); @@ -743,10 +757,6 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) memcpy(p+ 8, ip6_.toArray(), 16); // Source IP memcpy(p+24, dstIp.toArray(), 16); // Destination IP - // In case of mcast, derive dstMac - if ((dstIp.hi64() >> 56) == 0xff) - dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); - // FIXME: both these functions should return success/failure encap(pktBuf, dstMac, kEthTypeIp6); transmitPacket(pktBuf); @@ -771,8 +781,12 @@ void Device::sendIp6Reply(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt - if (!ndpTable_.contains(dstIp)) + if (!ndpTable_.contains(dstIp)) { + qWarning("%s: mac not found for %s; unable to send IPv6 packet", + __FUNCTION__, + qPrintable(QHostAddress(dstIp.toArray()).toString())); return; + } memcpy(pktData + 8, srcIp.toArray(), 16); // Source IP memcpy(pktData + 24, dstIp.toArray(), 16); // Destination IP @@ -970,6 +984,14 @@ void Device::sendNeighborAdvertisement(PacketBuffer *pktBuf) // NA should be sent to All nodes address srcIp = UInt128(quint64(0xff02) << 48, quint64(1)); } + else if (pktBuf->length() >= 32) { // have TLVs? + if ((pktData[24] == 0x01) && (pktData[25] == 0x01)) { // Source TLV + quint64 mac; + mac = qFromBigEndian(pktData + 26); + mac = (mac << 16) | qFromBigEndian(pktData + 30); + ndpTable_.insert(srcIp, mac); + } + } naPkt = new PacketBuffer; naPkt->reserve(encapSize() + kIp6HdrLen); From 07dd945f5043a5ed41eccaf317234b17e46dfd99 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 13 Jan 2016 21:15:47 +0530 Subject: [PATCH 034/121] Device Emulation (contd.): Lookup NDP Cache for IPv6 neighbor mac --- server/device.cpp | 21 ++++++++++++++++++++- test/emultest.py | 26 +++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index 5f67c6f..97501ed 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -382,11 +382,30 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + ipHdrLen - 4); mask = ~0 << (32 - ip4PrefixLength_); - qDebug("dst %x self %x mask %x", dstIp, ip4_, mask); + qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; return arpTable_.value(tgtIp); } + else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6 + UInt128 dstIp, tgtIp, mask; + + if (pktBuf->length() < (kIp6HdrLen+2)) { + qDebug("incomplete IPv6 header: expected %d, actual %d", + kIp6HdrLen, pktBuf->length()-2); + return false; + } + + dstIp = qFromBigEndian(pktData + 24); + mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + qDebug("dst %s mask %s self %s", + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + + return ndpTable_.value(tgtIp); + } return false; } diff --git a/test/emultest.py b/test/emultest.py index 09a669c..067af69 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -447,6 +447,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id s.core.is_enabled = True + s.core.frame_len = 80 # FIXME: change to 128 s.control.packets_per_sec = 100 s.control.num_packets = 10 @@ -703,13 +704,24 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Y', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' - ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+i*mac_step, '02x')+')']) - print(cap_pkts) - assert cap_pkts.count('\n') == s.control.num_packets/num_devs + if has_ip4: + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' + ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+i*mac_step, '02x')+')']) + print(cap_pkts) + assert cap_pkts.count('\n') == s.control.num_packets/num_devs + if has_ip6: + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', '(ipv6.src == 1234:1::' + + format(101+i*ip_step, 'x') + ') ' + ' && (ipv6.dst == 1234:2::' + + format(101+i*ip_step, 'x') + ')' + ' && (eth.dst == 00:01:02:03:0b:' + + format(1+i*mac_step, '02x')+')']) + print(cap_pkts) + assert cap_pkts.count('\n') == s.control.num_packets/num_devs os.remove('capture.pcap') drone.stopTransmit(ports.tx) From f1ff9e26163cf045173d043c3f1e1a3553768dc6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 15 Jan 2016 09:37:43 +0530 Subject: [PATCH 035/121] Device Emulation (contd.) - StreamBase::frameValue() now returns truncated packet content and length instead of bailing out if the passed in buffer size is less than the packet length. This is useful for some of the device emulation code which needs packet content only uptil the IP header but not beyond, so we don't need to unnecessarily create the entire packet with payload for these cases --- common/streambase.cpp | 26 +++++++++++++++++--------- test/emultest.py | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common/streambase.cpp b/common/streambase.cpp index 687a328..a838c68 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -525,16 +525,18 @@ int StreamBase::frameCount() const return count; } +// Returns packet length - if bufMaxSize < frameLen(), returns truncated +// length i.e. bufMaxSize int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const { - int pktLen, len = 0; + int size, pktLen, len = 0; pktLen = frameLen(frameIndex); // pktLen is adjusted for CRC/FCS which will be added by the NIC pktLen -= kFcsSize; - if ((pktLen < 0) || (pktLen > bufMaxSize)) + if (pktLen <= 0) return 0; ProtocolListIterator *iter; @@ -548,17 +550,23 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const proto = iter->next(); ba = proto->protocolFrameValue(frameIndex); - if (len + ba.size() < bufMaxSize) - memcpy(buf+len, ba.constData(), ba.size()); - len += ba.size(); + size = qMin(ba.size(), bufMaxSize-len); + memcpy(buf+len, ba.constData(), size); + len += size; + + if (len == bufMaxSize) + break; } delete iter; - // Pad with zero, if required - if (len < pktLen) - memset(buf+len, 0, pktLen-len); + // Pad with zero, if required and if we have space + if ((len < pktLen) && (len < bufMaxSize)) { + size = qMin(pktLen-len, bufMaxSize-len); + memset(buf+len, 0, size); + len += size; + } - return pktLen; + return len; } quint64 StreamBase::deviceMacAddress(int frameIndex) const diff --git a/test/emultest.py b/test/emultest.py index 067af69..8a4528b 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -447,7 +447,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id s.core.is_enabled = True - s.core.frame_len = 80 # FIXME: change to 128 + s.core.frame_len = 1024 s.control.packets_per_sec = 100 s.control.num_packets = 10 From 6b772bfabe77f40f1ccecd7d5d5bb2ee33507871 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 15 Jan 2016 18:47:02 +0530 Subject: [PATCH 036/121] Device Emulation (contd.): Fix IPv6 test case to work with non-zero step value and vary IPv6 dst-addr correctly --- test/emultest.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 8a4528b..d4372b0 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -80,6 +80,8 @@ class ip6_address(ipaddress.IPv6Interface): def __init__(self, addr): if type(addr) is str: super(ip6_address, self).__init__(unicode(addr)) + elif type(addr) is int: + super(ip6_address, self).__init__(addr) else: super(ip6_address, self).__init__(addr.hi << 64 | addr.lo) self.ip6 = emul.Ip6Address() @@ -345,6 +347,7 @@ def dut_vlans(request, dut_ports): # ================================================================= # @pytest.mark.parametrize('dev_cfg', [ + {'ip_ver': [4], 'mac_step': 1, 'ip_step': 1}, {'ip_ver': [6], 'mac_step': 1, 'ip_step': 1}, {'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5}, ]) @@ -521,7 +524,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, else: vf = p.variable_field.add() vf.type = ost_pb.VariableField.kCounter32 - vf.offset = 20 + vf.offset = 36 vf.mask = 0xFFFFFFFF vf.value = 0x00000065 vf.step = ip_step @@ -705,21 +708,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, log.info('dumping Rx capture buffer (filtered)') for i in range(num_devs): if has_ip4: + filter = '(ip.src == 10.10.1.' + str(101+i*ip_step) + ')' \ + ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' \ + ' && (eth.dst == 00:01:02:03:0b:' \ + + format(1+i*mac_step, '02x')+')' + print('filter: %s' % filter) cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Y', '(ip.src == 10.10.1.' + str(101+i*ip_step) + ') ' - ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+i*mac_step, '02x')+')']) + '-Y', filter]) print(cap_pkts) assert cap_pkts.count('\n') == s.control.num_packets/num_devs if has_ip6: + filter = '(ipv6.src == 1234:1::' \ + + format(101+i*ip_step, 'x') + ')' \ + + ' && (ipv6.dst == 1234:2::' \ + + format(101+i*ip_step, 'x') + ')' \ + + ' && (eth.dst == 00:01:02:03:0b:' \ + + format(1+i*mac_step, '02x') + ')' + print('filter: %s' % filter) cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-Y', '(ipv6.src == 1234:1::' - + format(101+i*ip_step, 'x') + ') ' - ' && (ipv6.dst == 1234:2::' - + format(101+i*ip_step, 'x') + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+i*mac_step, '02x')+')']) + '-Y', filter]) print(cap_pkts) assert cap_pkts.count('\n') == s.control.num_packets/num_devs os.remove('capture.pcap') From efe22149e1229013c00c92dd17863b83d4e76358 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 21 Jan 2016 18:41:21 +0530 Subject: [PATCH 037/121] Device Emulation (contd.): Minor refactoring in test case --- test/emultest.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index d4372b0..a0fb88e 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -25,10 +25,13 @@ from protocols.vlan_pb2 import vlan use_defaults = True if sys.platform == 'win32': + # FIXME: remove path tshark = r'C:\PortableTools\WiresharkPortable\App\Wireshark\tshark.exe' else: tshark = 'tshark' +#FIXME: check wireshark version supports ipv6/NA/NS filters + # initialize defaults - drone host_name = '127.0.0.1' @@ -427,17 +430,10 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, drone.modifyDeviceGroup(devgrp_cfg) - # TODO: reuse dev_cfg['ip_ver'] - ip_versions = [] - if has_ip4: - ip_versions.append('ip4') - if has_ip6: - ip_versions.append('ip6') - # add the tx stream(s) - we may need more than one stream_id = ost_pb.StreamIdList() stream_id.port_id.CopyFrom(ports.tx.port_id[0]) - for i in range(len(ip_versions)): + for i in range(len(dev_cfg['ip_ver'])): stream_id.stream_id.add().id = i log.info('adding tx_stream %d' % stream_id.stream_id[i].id) @@ -446,7 +442,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # configure the tx stream(s) stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(ports.tx.port_id[0]) - for i in range(len(ip_versions)): + for i in range(len(dev_cfg['ip_ver'])): s = stream_cfg.stream.add() s.stream_id.id = stream_id.stream_id[i].id s.core.is_enabled = True @@ -463,7 +459,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber - if ip_versions[i] == 'ip4': + if dev_cfg['ip_ver'][i] == 4: p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber ip = None @@ -494,7 +490,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, vf.step = ip_step vf.mode = ost_pb.VariableField.kIncrement vf.count = num_devs - elif ip_versions[i] == 'ip6': + elif dev_cfg['ip_ver'][i] == 6: p = s.protocol.add() p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber ip = p.Extensions[ip6] @@ -570,21 +566,21 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') - for i in range(len(ip_versions)): - if ip_versions[i] == 'ip4': + for i in range(len(dev_cfg['ip_ver'])): + if dev_cfg['ip_ver'][i] == 4: filter = '(arp.opcode == 1)' \ ' && (arp.src.proto_ipv4 == 10.10.1.)' \ ' && (arp.dst.proto_ipv4 == 10.10.1.1)' \ ' && !expert.severity' - elif ip_versions[i] == 'ip6': + elif dev_cfg['ip_ver'][i] == 6: filter = '(icmpv6.type == 135)' \ ' && (ipv6.src == 1234:1::)' \ ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \ ' && !expert.severity' for j in range(num_devs): - if ip_versions[i] == 'ip4': + if dev_cfg['ip_ver'][i] == 4: filter = filter.replace('', str(101+j*ip_step)) - elif ip_versions[i] == 'ip6': + elif dev_cfg['ip_ver'][i] == 6: filter = filter.replace('', format(0x65+j*ip_step, 'x')) #print filter cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', @@ -599,21 +595,21 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') - for i in range(len(ip_versions)): - if ip_versions[i] == 'ip4': + for i in range(len(dev_cfg['ip_ver'])): + if dev_cfg['ip_ver'][i] == 4: filter = '(arp.opcode == 1)' \ ' && (arp.src.proto_ipv4 == 10.10.2.)' \ ' && (arp.dst.proto_ipv4 == 10.10.2.1)' \ ' && !expert.severity' - elif ip_versions[i] == 'ip6': + elif dev_cfg['ip_ver'][i] == 6: filter = '(icmpv6.type == 135)' \ ' && (ipv6.src == 1234:2::)' \ ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \ ' && !expert.severity' for j in range(num_devs): - if ip_versions[i] == 'ip4': + if dev_cfg['ip_ver'][i] == 4: filter = filter.replace('', str(101+j*ip_step)) - elif ip_versions[i] == 'ip6': + elif dev_cfg['ip_ver'][i] == 6: filter = filter.replace('', format(0x65+j*ip_step, 'x')) print filter cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', From 8efdb44e6aca359431a7e9122e03ce793a65019c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 23 Jan 2016 21:01:47 +0530 Subject: [PATCH 038/121] Device Emulation (contd.): Enhanced the VLAN Devices test case to include IPv6 (and dual stack) scenario(s) --- test/emultest.py | 371 +++++++++++++++++++++++++++++++---------------- 1 file changed, 248 insertions(+), 123 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index a0fb88e..7052760 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -260,6 +260,8 @@ def dut_vlans(request, dut_ports): for i in range(len(devices.rx)): vrf = 'vrf' + str(i+1) sudo('ip netns add ' + vrf) + sudo('ip netns exec ' + vrf + + ' sysctl -w net.ipv6.conf.all.forwarding=1') dev = devices.rx[i] sudo('ip link set ' + dev @@ -267,6 +269,9 @@ def dut_vlans(request, dut_ports): sudo('ip netns exec ' + vrf + ' ip addr add 10.1.1.1/24' + ' dev ' + dev) + sudo('ip netns exec ' + vrf + + ' ip -6 addr add 1234:1::1/96' + + ' dev ' + dev) sudo('ip netns exec ' + vrf + ' ip link set ' + dev + ' up') @@ -276,6 +281,9 @@ def dut_vlans(request, dut_ports): sudo('ip netns exec ' + vrf + ' ip addr add 10.1.2.1/24' + ' dev ' + dev) + sudo('ip netns exec ' + vrf + + ' ip -6 addr add 1234:2::1/96' + + ' dev ' + dev) sudo('ip netns exec ' + vrf + ' ip link set ' + dev + ' up') @@ -625,8 +633,8 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on tx port') for dev_cfg, device in zip(device_config, devices): - resolved = False if has_ip4: + resolved = False for arp in device.arp: print('%s: %s %012x' % (str(ipaddress.ip_address(dev_cfg.ip4)), @@ -636,6 +644,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, resolved = True assert resolved if has_ip6: + resolved = False for ndp in device.ndp: print('%s: %s %012x' % (str(ip6_address(dev_cfg.ip6)), @@ -654,19 +663,17 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, for dev_cfg, device in zip(device_config, devices): # verify *no* ARPs/NDPs learnt on rx port if has_ip4: - assert len(device.arp) == 0 for arp in device.arp: print('%s: %s %012x' % (str(ipaddress.ip_address(dev_cfg.ip4)), - str(ipaddress.ip_address(arp.ip4)), - arp.mac)) + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert len(device.arp) == 0 if has_ip6: - assert len(device.ndp) == 0 for ndp in device.ndp: print('%s: %s %012x' % (str(ip6_address(dev_cfg.ip6)), - str(ip6_address(ndp.ip6)), - ndp.mac)) + str(ip6_address(ndp.ip6)), ndp.mac)) + assert len(device.ndp) == 0 # ping the tx devices from the DUT for i in range(num_devs): @@ -730,26 +737,26 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, drone.stopTransmit(ports.tx) run('ip neigh show') -@pytest.mark.parametrize('vlan_cfg', [ - [{'base': 11, 'count': 5}], +@pytest.mark.parametrize('vlan_cfg,ip_ver', [ + ([{'base': 11, 'count': 5}], [6]), - [{'base': 11, 'count': 2}, - {'base': 21, 'count': 3}], + ([{'base': 11, 'count': 2}, + {'base': 21, 'count': 3}], [4]), - [{'base': 11, 'count': 2, 'tpid': 0x88a8}, - {'base': 21, 'count': 3}], + ([{'base': 11, 'count': 2, 'tpid': 0x88a8}, + {'base': 21, 'count': 3}], [4, 6]), - [{'base': 11, 'count': 2}, + ([{'base': 11, 'count': 2}, {'base': 21, 'count': 3, 'step': 2}, - {'base': 31, 'count': 2, 'step': 3}], + {'base': 31, 'count': 2, 'step': 3}], [6]), - [{'base': 11, 'count': 2}, + ([{'base': 11, 'count': 2}, {'base': 21, 'count': 3}, {'base': 31, 'count': 2}, - {'base': 1, 'count': 3}], + {'base': 1, 'count': 3}], [4]) ]) def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, - stream_clear, emul_ports, dgid_list, vlan_cfg): + stream_clear, emul_ports, dgid_list, vlan_cfg, ip_ver): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs # @@ -757,6 +764,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, # .1/ \.1 # / \ # 10.1.1/24 10.1.2/24 + # 1234:1/96 1234:2/96 # [vlans] [vlans] # / \ # /.101-103 \.101-103 @@ -768,8 +776,14 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, num_vlans = num_vlans * vcfg['count'] test_multiEmulDevPerVlan.vlan_cfg = vlan_cfg num_devs_per_vlan = 3 - tx_ip_base = 0x0a010165 - rx_ip_base = 0x0a010265 + has_ip4 = True if 4 in ip_ver else False + has_ip6 = True if 6 in ip_ver else False + if has_ip4: + tx_ip4_base = 0x0a010165 + rx_ip4_base = 0x0a010265 + if has_ip6: + tx_ip6_base = ip6_address('1234:1::65/96') + rx_ip6_base = ip6_address('1234:2::65/96') # configure vlans on the DUT dut_vlans(request, dut_ports) @@ -806,10 +820,16 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, v.step = vcfg['step'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030a01 - ip = dg.Extensions[emul.ip4] - ip.address = tx_ip_base - ip.prefix_length = 24 - ip.default_gateway = (tx_ip_base & 0xffffff00) | 0x01 + if has_ip4: + ip = dg.Extensions[emul.ip4] + ip.address = tx_ip4_base + ip.prefix_length = 24 + ip.default_gateway = (tx_ip4_base & 0xffffff00) | 0x01 + if has_ip6: + ip = dg.Extensions[emul.ip6] + ip.address.CopyFrom(tx_ip6_base.ip6) + ip.prefix_length = tx_ip6_base.prefixlen + ip.default_gateway.CopyFrom(tx_ip6_base.gateway) drone.modifyDeviceGroup(devgrp_cfg) @@ -829,71 +849,100 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, v.step = vcfg['step'] dg.device_count = num_devs_per_vlan dg.Extensions[emul.mac].address = 0x000102030b01 - ip = dg.Extensions[emul.ip4] - ip.address = rx_ip_base - ip.prefix_length = 24 - ip.default_gateway = (rx_ip_base & 0xffffff00) | 0x01 + if has_ip4: + ip = dg.Extensions[emul.ip4] + ip.address = rx_ip4_base + ip.prefix_length = 24 + ip.default_gateway = (rx_ip4_base & 0xffffff00) | 0x01 + if has_ip6: + ip = dg.Extensions[emul.ip6] + ip.address.CopyFrom(rx_ip6_base.ip6) + ip.prefix_length = rx_ip6_base.prefixlen + ip.default_gateway.CopyFrom(rx_ip6_base.gateway) drone.modifyDeviceGroup(devgrp_cfg) # add the tx stream(s) - we need more than one stream stream_id = ost_pb.StreamIdList() stream_id.port_id.CopyFrom(ports.tx.port_id[0]) - for i in range(num_vlans): + for i in range(num_vlans * len(ip_ver)): stream_id.stream_id.add().id = i log.info('adding tx_stream %d' % stream_id.stream_id[i].id) drone.addStream(stream_id) + # configure the tx stream(s) stream_cfg = ost_pb.StreamConfigList() stream_cfg.port_id.CopyFrom(ports.tx.port_id[0]) for i in range(num_vlans): - s = stream_cfg.stream.add() - s.stream_id.id = stream_id.stream_id[i].id - s.core.name = 'stream ' + str(s.stream_id.id) - s.core.is_enabled = True - s.core.ordinal = i - s.control.packets_per_sec = 100 - s.control.num_packets = num_devs_per_vlan + for j in range(len(ip_ver)): + idx = i*len(ip_ver) + j + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[idx].id + s.core.name = 'stream ' + str(s.stream_id.id) + s.core.is_enabled = True + s.core.ordinal = idx + s.core.frame_len = 1024 + s.control.packets_per_sec = 100 + s.control.num_packets = num_devs_per_vlan - # setup stream protocols as mac:vlan:eth2:ip4:udp:payload - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber - p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve - p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - - ids = dut_vlans.vlans[i].split('.') - for id, j in zip(ids, range(len(ids))): + # setup stream protocols as mac:vlan:eth2:ip4:udp:payload p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber - p.Extensions[vlan].vlan_tag = int(id) - if 'tpid' in vlan_cfg[j]: - p.Extensions[vlan].tpid = vlan_cfg[j]['tpid'] - p.Extensions[vlan].is_override_tpid = True + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + ids = dut_vlans.vlans[i].split('.') + for id, idx in zip(ids, range(len(ids))): + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber + p.Extensions[vlan].vlan_tag = int(id) + if 'tpid' in vlan_cfg[idx]: + p.Extensions[vlan].tpid = vlan_cfg[idx]['tpid'] + p.Extensions[vlan].is_override_tpid = True - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber - ip = p.Extensions[ip4] - ip.src_ip = tx_ip_base - ip.src_ip_mode = Ip4.e_im_inc_host - ip.src_ip_count = num_devs_per_vlan - ip.dst_ip = rx_ip_base - ip.dst_ip_mode = Ip4.e_im_inc_host - ip.dst_ip_count = num_devs_per_vlan + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber - p = s.protocol.add() - p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + if ip_ver[j] == 4: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = p.Extensions[ip4] + ip.src_ip = tx_ip4_base + ip.src_ip_mode = Ip4.e_im_inc_host + ip.src_ip_count = num_devs_per_vlan + ip.dst_ip = rx_ip4_base + ip.dst_ip_mode = Ip4.e_im_inc_host + ip.dst_ip_count = num_devs_per_vlan + elif ip_ver[j] == 6: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber + ip = p.Extensions[ip6] - log.info('configuring tx_stream %d' % stream_id.stream_id[i].id) + ip.src_addr_hi = tx_ip6_base.ip6.hi + ip.src_addr_lo = tx_ip6_base.ip6.lo + ip.src_addr_mode = Ip6.kIncHost + ip.src_addr_count = num_devs_per_vlan + ip.src_addr_prefix = tx_ip6_base.prefixlen + + ip.dst_addr_hi = rx_ip6_base.ip6.hi + ip.dst_addr_lo = rx_ip6_base.ip6.lo + ip.dst_addr_mode = Ip6.kIncHost + ip.dst_addr_count = num_devs_per_vlan + ip.dst_addr_prefix = rx_ip6_base.prefixlen + else: + assert false # unreachable + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[idx].id) drone.modifyStream(stream_cfg) - # clear arp on DUT + # clear arp/ndp on DUT for i in range(num_vlans): vrf = 'vrf' + str(i+1) @@ -902,8 +951,14 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, arp_cache = sudo('ip netns exec ' + vrf + ' ip neigh show') assert re.search('10.1.[1-2].20[1-5].*lladdr', arp_cache) == None + assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None - # resolve ARP on tx/rx ports + # wait for interface to do DAD? Otherwise we don't get replies for NS + # FIXME: find alternative to sleep + if has_ip6: + time.sleep(5) + + # resolve ARP/NDP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') drone.startCapture(emul_ports) drone.clearDeviceNeighbors(emul_ports) @@ -911,7 +966,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, time.sleep(3) drone.stopCapture(emul_ports) - # verify ARP Requests sent out from tx port + # verify ARP/NDP Requests sent out from tx port buff = drone.getCaptureBuffer(emul_ports.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Tx capture buffer (all)') @@ -923,18 +978,30 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, print(cap_pkts) log.info('dumping Tx capture buffer (filtered)') for i in range(num_vlans): - for j in range(num_devs_per_vlan): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', vlan_filter[i] + - ' && (arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.1.1.' - +str(101+j)+')' - ' && (arp.dst.proto_ipv4 == 10.1.1.1)']) - print(cap_pkts) - assert cap_pkts.count('\n') == 1 + for j in range(len(ip_ver)): + if ip_ver[j] == 4: + filter = vlan_filter[i] + ' && (arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.1.1.)' \ + ' && (arp.dst.proto_ipv4 == 10.1.1.1)' \ + ' && !expert.severity' + elif ip_ver[j] == 6: + filter = vlan_filter[i] + ' && (icmpv6.type == 135)' \ + ' && (ipv6.src == 1234:1::)' \ + ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \ + ' && !expert.severity' + for k in range(num_devs_per_vlan): + if ip_ver[j] == 4: + filter = filter.replace('', str(101+k)) + elif ip_ver[j] == 6: + filter = filter.replace('', format(0x65+k, 'x')) + print filter + cap_pkts = subprocess.check_output([tshark, '-nr', + 'capture.pcap', '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 - # verify *no* ARP Requests sent out from rx port - buff = drone.getCaptureBuffer(emul_ports.port_id[1]) + # verify *no* ARP/NDP Requests sent out from rx port + buff = drone.getCaptureBuffer(emul_ports.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) @@ -945,63 +1012,109 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): - for j in range(num_devs_per_vlan): - cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', - '-R', vlan_filter[i] + - ' && (arp.opcode == 1)' - ' && (arp.src.proto_ipv4 == 10.1.2.' - +str(101+j)+')' - ' && (arp.dst.proto_ipv4 == 10.1.2.1)']) - print(cap_pkts) - assert cap_pkts.count('\n') == 0 + for j in range(len(ip_ver)): + if ip_ver[j] == 4: + filter = vlan_filter[i] + ' && (arp.opcode == 1)' \ + ' && (arp.src.proto_ipv4 == 10.1.2.)' \ + ' && (arp.dst.proto_ipv4 == 10.1.2.1)' \ + ' && !expert.severity' + elif ip_ver[j] == 6: + filter = vlan_filter[i] + ' && (icmpv6.type == 135)' \ + ' && (ipv6.src == 1234:2::)' \ + ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \ + ' && !expert.severity' + for k in range(num_devs_per_vlan): + if ip_ver[j] == 4: + filter = filter.replace('', str(101+k)) + elif ip_ver[j] == 6: + filter = filter.replace('', format(0x65+k, 'x')) + print filter + cap_pkts = subprocess.check_output([tshark, '-nr', + 'capture.pcap', '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 0 - # retrieve and verify ARP Table on tx/rx ports - log.info('retrieving ARP entries on tx port') + # retrieve and verify ARP/NDP Table on tx/rx ports + log.info('retrieving ARP/NDP entries on tx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on tx port') + log.info('ARP/NDP Table on tx port') for dev_cfg, device in zip(device_config, devices): - resolved = False - for arp in device.arp: - # TODO: print all configured vlans, not just the first - # TODO: pretty print ip and mac - print('v%d|%08x: %08x %012x' % - (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): - resolved = True - assert resolved + vlans = '' + for v in dev_cfg.vlan: + vlans += str(v & 0xffff) + ' ' + if has_ip4: + resolved = False + for arp in device.arp: + print('%s%s: %s %012x' % + (vlans, str(ipaddress.ip_address(dev_cfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved + if has_ip6: + resolved = False + for ndp in device.ndp: + print('%s%s: %s %012x' % + (vlans, str(ip6_address(dev_cfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + resolved = True + assert resolved log.info('retrieving ARP entries on rx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] - log.info('ARP Table on rx port') + log.info('ARP/NDP Table on rx port') for dev_cfg, device in zip(device_config, devices): - # verify *no* ARPs learnt on rx port - assert len(device.arp) == 0 - for arp in device.arp: - # TODO: pretty print ip and mac - print('v%d|%08x: %08x %012x' % - (dev_cfg.vlan[0] & 0xffff, dev_cfg.ip4, arp.ip4, arp.mac)) + vlans = '' + for v in dev_cfg.vlan: + vlans += str(v & 0xffff) + ' ' + # verify *no* ARPs/NDPs learnt on rx port + if has_ip4: + for arp in device.arp: + print('%s%s: %s %012x' % + (vlans, str(ipaddress.ip_address(dev_cfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert len(device.arp) == 0 + if has_ip6: + for ndp in device.ndp: + print('%s%s: %s %012x' % + (vlans, str(ip6_address(dev_cfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + assert len(device.ndp) == 0 # ping the tx devices from the DUT for i in range(num_vlans): vrf = 'vrf' + str(i+1) for j in range(num_devs_per_vlan): - out = sudo('ip netns exec ' + vrf - + ' ping -c3 10.1.1.'+str(101+j), warn_only=True) - assert '100% packet loss' not in out + if has_ip4: + out = sudo('ip netns exec ' + vrf + + ' ping -c3 10.1.1.'+str(101+j), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = sudo('ip netns exec ' + vrf + + ' ping -6 -c3 1234:1::'+format(101+j, 'x'), + warn_only=True) + assert '100% packet loss' not in out # ping the rx devices from the DUT for i in range(num_vlans): vrf = 'vrf' + str(i+1) for j in range(num_devs_per_vlan): - out = sudo('ip netns exec ' + vrf - + ' ping -c3 10.1.2.'+str(101+j), warn_only=True) - assert '100% packet loss' not in out + if has_ip4: + out = sudo('ip netns exec ' + vrf + + ' ping -c3 10.1.2.'+str(101+j), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = sudo('ip netns exec ' + vrf + + ' ping -6 -c3 1234:2::'+format(101+j, 'x'), + warn_only=True) + assert '100% packet loss' not in out # we are all set - send data stream(s) drone.startCapture(ports.rx) @@ -1017,22 +1130,34 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) print(cap_pkts) - log.info('dumping Tx capture buffer (all pkts - vlans only)') + log.info('dumping Rx capture buffer (all pkts - vlans only)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id']) print(cap_pkts) log.info('dumping Rx capture buffer (filtered)') for i in range(num_vlans): for j in range(num_devs_per_vlan): - cap_pkts = subprocess.check_output( - [tshark, '-nr', 'capture.pcap', - '-R', vlan_filter[i] + - ' && (ip.src == 10.1.1.' + str(101+j) + ') ' - ' && (ip.dst == 10.1.2.' + str(101+j) + ')' - ' && (eth.dst == 00:01:02:03:0b:' - + format(1+j, '02x')+')']) - print(cap_pkts) - assert cap_pkts.count('\n') == 1 + if has_ip4: + filter = vlan_filter[i] + \ + ' && (ip.src == 10.1.1.' + str(101+j) + ')' \ + ' && (ip.dst == 10.1.2.' + str(101+j) + ')' \ + ' && (eth.dst == 00:01:02:03:0b:' + format(1+j, '02x')+')' + print('filter: %s' % filter) + cap_pkts = subprocess.check_output([tshark, '-nr', + 'capture.pcap', '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 + if has_ip6: + filter = vlan_filter[i] \ + + ' && (ipv6.src == 1234:1::' + format(101+j, 'x') + ')' \ + + ' && (ipv6.dst == 1234:2::' + format(101+j, 'x') + ')' \ + + ' && (eth.dst == 00:01:02:03:0b:' \ + + format(1+j, '02x') + ')' + print('filter: %s' % filter) + cap_pkts = subprocess.check_output([tshark, '-nr', + 'capture.pcap', '-Y', filter]) + print(cap_pkts) + assert cap_pkts.count('\n') == 1 os.remove('capture.pcap') import pytest From 83e4aeb992735ea07767a1df062a631cb1ea7d0e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 24 Jan 2016 13:45:13 +0530 Subject: [PATCH 039/121] Device Emulation: Remove path for windows' tshark - expect it to be part of user env $PATH --- test/emultest.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 7052760..5e292f1 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -24,13 +24,8 @@ from protocols.vlan_pb2 import vlan use_defaults = True -if sys.platform == 'win32': - # FIXME: remove path - tshark = r'C:\PortableTools\WiresharkPortable\App\Wireshark\tshark.exe' -else: - tshark = 'tshark' - -#FIXME: check wireshark version supports ipv6/NA/NS filters +tshark = 'tshark' +# FIXME: ensure minimum tshark version 1.4 => supports ICMPV6 NS/NA filters # initialize defaults - drone host_name = '127.0.0.1' From d309bc9362083721fe1e7f7101bdb829cd67713b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 26 Jan 2016 14:05:26 +0530 Subject: [PATCH 040/121] Device Emulation(contd.): Specify minimum tshark version that supports the filters we need for verification --- test/emultest.py | 9 ++++++--- test/utils.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test/utils.py diff --git a/test/emultest.py b/test/emultest.py index 5e292f1..ff9d85b 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -12,7 +12,8 @@ import time import pytest from fabric.api import run, env, sudo -#from harness import Test, TestSuite, TestPreRequisiteError + +from utils import get_tshark sys.path.insert(1, '../binding') from core import ost_pb, emul, DroneProxy @@ -24,8 +25,10 @@ from protocols.vlan_pb2 import vlan use_defaults = True -tshark = 'tshark' -# FIXME: ensure minimum tshark version 1.4 => supports ICMPV6 NS/NA filters +tshark = get_tshark(minversion = '1.6') +if tshark is None: + print 'tshark >= 1.6 not found' + sys.exit(1) # initialize defaults - drone host_name = '127.0.0.1' diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000..709b0b7 --- /dev/null +++ b/test/utils.py @@ -0,0 +1,22 @@ +#! /usr/bin/env python +import subprocess +import sys + +#from distutils.spawn import find_executable +from distutils.version import LooseVersion + +def get_tshark(minversion = None): + tshark = 'tshark' + #if sys.platform == 'win32': + # tshark = find_executable(tshark) + if tshark and minversion: + out = subprocess.check_output([tshark, '-v']).split(None, 2) + # we expect output to be of the form 'Tshark ..." + ver = out[1] + if LooseVersion(ver) < LooseVersion(minversion): + tshark = None + + if tshark is not None: + print('%s version %s found' % (tshark, ver)) + return tshark + From e3a3a0cf1f64a309ef1aff983b72cf50285284ec Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 27 Jan 2016 20:39:54 +0530 Subject: [PATCH 041/121] Device Emulation (contd.): Fix frames not being truncated (introduced by recent changes in StreamBase::frameValue()); add test case for the same and add checksum/error check to pktlentest(s) --- common/streambase.cpp | 12 +++-- test/pktlentest.py | 109 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/common/streambase.cpp b/common/streambase.cpp index a838c68..96ea329 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -529,7 +529,7 @@ int StreamBase::frameCount() const // length i.e. bufMaxSize int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const { - int size, pktLen, len = 0; + int maxSize, size, pktLen, len = 0; pktLen = frameLen(frameIndex); @@ -539,6 +539,8 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const if (pktLen <= 0) return 0; + maxSize = qMin(pktLen, bufMaxSize); + ProtocolListIterator *iter; iter = createProtocolListIterator(); @@ -550,18 +552,18 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const proto = iter->next(); ba = proto->protocolFrameValue(frameIndex); - size = qMin(ba.size(), bufMaxSize-len); + size = qMin(ba.size(), maxSize-len); memcpy(buf+len, ba.constData(), size); len += size; - if (len == bufMaxSize) + if (len == maxSize) break; } delete iter; // Pad with zero, if required and if we have space - if ((len < pktLen) && (len < bufMaxSize)) { - size = qMin(pktLen-len, bufMaxSize-len); + if (len < maxSize) { + size = maxSize-len; memset(buf+len, 0, size); len += size; } diff --git a/test/pktlentest.py b/test/pktlentest.py index 0e49d07..de8808d 100644 --- a/test/pktlentest.py +++ b/test/pktlentest.py @@ -9,6 +9,7 @@ import sys import time from harness import extract_column +from utils import get_tshark sys.path.insert(1, '../binding') from core import ost_pb, DroneProxy @@ -21,10 +22,7 @@ host_name = '127.0.0.1' tx_number = -1 rx_number = -1 -if sys.platform == 'win32': - tshark = r'C:\Program Files\Wireshark\tshark.exe' -else: - tshark = 'tshark' +tshark = get_tshark(minversion='1.2.0') fmt = 'column.format:"Packet#","%m","Time","%t","Source","%uns","Destination","%und","Protocol","%p","Size","%L","Info","%i","Expert","%a"' fmt_col = 7 # col# in fmt for Size/PktLength @@ -32,8 +30,10 @@ fmt_col = 7 # col# in fmt for Size/PktLength # setup protocol number dictionary proto_number = {} proto_number['mac'] = ost_pb.Protocol.kMacFieldNumber +proto_number['vlan'] = ost_pb.Protocol.kVlanFieldNumber proto_number['eth2'] = ost_pb.Protocol.kEth2FieldNumber proto_number['ip4'] = ost_pb.Protocol.kIp4FieldNumber +proto_number['ip6'] = ost_pb.Protocol.kIp6FieldNumber proto_number['udp'] = ost_pb.Protocol.kUdpFieldNumber proto_number['payload'] = ost_pb.Protocol.kPayloadFieldNumber @@ -115,8 +115,7 @@ def ports(request, drone): return ports -protolist=['mac eth2 ip4 udp payload', 'mac eth2 ip4 udp'] -@pytest.fixture(scope='module', params=protolist) +@pytest.fixture(scope='module') def stream(request, drone, ports): global proto_number @@ -136,7 +135,39 @@ def stream(request, drone, ports): s.control.packets_per_sec = 100 s.control.num_packets = 10 - # setup stream protocols as mac:eth2:ip:udp:payload + # XXX: don't setup any stream protocols + + def fin(): + # delete streams + log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) + drone.deleteStream(stream_id) + + request.addfinalizer(fin) + + return stream_cfg + +protolist=['mac eth2 ip4 udp payload', 'mac eth2 ip4 udp'] +@pytest.fixture(scope='module', params=protolist) +def stream_toggle_payload(request, drone, ports): + global proto_number + + # add a stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(ports.tx.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(ports.tx.port_id[0]) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.control.packets_per_sec = 100 + s.control.num_packets = 10 + + # setup stream protocols s.ClearField("protocol") protos = request.param.split() for p in protos: @@ -156,11 +187,12 @@ def stream(request, drone, ports): ost_pb.StreamCore.e_fl_dec, ost_pb.StreamCore.e_fl_random ]) -def test_packet_length(drone, ports, stream, mode): - """ Test random length packets """ +def test_framelen_modes(drone, ports, stream_toggle_payload, mode): + """ Test various framelen modes """ min_pkt_len = 100 max_pkt_len = 1000 + stream = stream_toggle_payload stream.stream[0].core.len_mode = mode stream.stream[0].core.frame_len_min = min_pkt_len stream.stream[0].core.frame_len_max = max_pkt_len @@ -168,6 +200,13 @@ def test_packet_length(drone, ports, stream, mode): log.info('configuring tx_stream %d' % stream.stream[0].stream_id.id) drone.modifyStream(stream) + if stream.stream[0].protocol[-1].protocol_id.id \ + == ost_pb.Protocol.kPayloadFieldNumber: + filter = 'udp && !expert.severity' + else: + filter = 'udp && eth.fcs_bad==1' \ + '&& ip.checksum_bad==0 && udp.checksum_bad==0' + # clear tx/rx stats log.info('clearing tx/rx stats') drone.clearStats(ports.tx) @@ -187,9 +226,11 @@ def test_packet_length(drone, ports, stream, mode): log.info('dumping Rx capture buffer') cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap']) print(cap_pkts) + print(filter) cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap', - '-R', 'udp', '-o', fmt]) + '-Y', filter, '-o', fmt]) print(cap_pkts) + assert cap_pkts.count('\n') == stream.stream[0].control.num_packets result = extract_column(cap_pkts, fmt_col) print(result) diffSum = 0 @@ -217,3 +258,51 @@ def test_packet_length(drone, ports, stream, mode): finally: drone.stopTransmit(ports.tx) +def test_frame_trunc(drone, ports, stream): + """ Test frame is truncated even if the protocol sizes exceed framelen """ + + #stream.stream[0].core.frame_len_min = min_pkt_len + + # setup stream protocols as mac:vlan:eth2:ip6:udp:payload + stream.stream[0].ClearField("protocol") + protos = ['mac', 'vlan', 'eth2', 'ip6', 'udp', 'payload'] + for p in protos: + stream.stream[0].protocol.add().protocol_id.id = proto_number[p] + + # note: unless truncated minimum size of payload protocol is 1 + exp_framelen = min(12+4+2+40+8+1, 60) + + log.info('configuring tx_stream %d' % stream.stream[0].stream_id.id) + drone.modifyStream(stream) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(ports.tx) + drone.clearStats(ports.rx) + + try: + drone.startCapture(ports.rx) + drone.startTransmit(ports.tx) + log.info('waiting for transmit to finish ...') + time.sleep(3) + drone.stopTransmit(ports.tx) + drone.stopCapture(ports.rx) + + log.info('getting Rx capture buffer') + buff = drone.getCaptureBuffer(ports.rx.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer') + cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap', + '-Y', 'vlan', '-o', fmt]) + print(cap_pkts) + assert cap_pkts.count('\n') == stream.stream[0].control.num_packets + result = extract_column(cap_pkts, fmt_col) + print(result) + for i in range(len(result)): + assert (int(result[i]) == exp_framelen) + + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(ports.tx) From f6c852495da07f8c14f8e3b8c42836f1f13dfbe6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 28 Jan 2016 20:01:19 +0530 Subject: [PATCH 042/121] Device Emulation (contd.): resolveNeighbors() now resolves the device gateway also in addition to looking at transmit packet content to figure out which IPs to resolve --- server/abstractport.cpp | 5 +++- server/device.cpp | 61 +++++++++++++++++++++++++++------------- server/device.h | 4 +++ server/devicemanager.cpp | 7 +++++ server/devicemanager.h | 2 ++ test/emultest.py | 44 +++++++++++++++++------------ 6 files changed, 84 insertions(+), 39 deletions(-) diff --git a/server/abstractport.cpp b/server/abstractport.cpp index f6568d6..42e636e 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -625,7 +625,10 @@ void AbstractPort::clearDeviceNeighbors() void AbstractPort::resolveDeviceNeighbors() { - // Resolve neighbor for each unique frame of each stream + // Resolve gateway for each device first ... + deviceManager_->resolveDeviceGateways(); + + // ... then resolve neighbor for each unique frame of each stream // NOTE: // 1. All the frames may have the same destination ip,but may have // different source ip so may belong to a different emulated device; diff --git a/server/device.cpp b/server/device.cpp index 97501ed..656531c 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -256,6 +256,15 @@ void Device::transmitPacket(PacketBuffer *pktBuf) deviceManager_->transmitPacket(pktBuf); } +void Device::resolveGateway() +{ + if (hasIp4_) + sendArpRequest(ip4Gateway_); + + if (hasIp6_) + sendNeighborSolicit(ip6Gateway_); +} + void Device::clearNeighbors() { arpTable_.clear(); @@ -524,11 +533,9 @@ _invalid_exit: // pktBuf points to start of IP header void Device::sendArpRequest(PacketBuffer *pktBuf) { - PacketBuffer *reqPkt; uchar *pktData = pktBuf->data(); - int offset = 0; - int ipHdrLen = (pktData[offset] & 0x0F) << 2; - quint32 srcIp, dstIp, mask, tgtIp; + int ipHdrLen = (pktData[0] & 0x0F) << 2; + quint32 srcIp = ip4_, dstIp, mask, tgtIp; if (pktBuf->length() < ipHdrLen) { qDebug("incomplete IPv4 header: expected %d, actual %d", @@ -536,25 +543,28 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) return; } - // FIXME: not required - caller is checking for origin anyway - // Extract srcIp first to check quickly that this packet originates - // from this device - srcIp = qFromBigEndian(pktData + ipHdrLen - 8); - if (srcIp != ip4_) { - qDebug("%s: srcIp %s is not me %s", __FUNCTION__, - qPrintable(QHostAddress(srcIp).toString()), - qPrintable(QHostAddress(ip4_).toString())); - return; - } - dstIp = qFromBigEndian(pktData + ipHdrLen - 4); mask = ~0 << (32 - ip4PrefixLength_); qDebug("dst %x src %x mask %x", dstIp, srcIp, mask); tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_; + sendArpRequest(tgtIp); + +} + +void Device::sendArpRequest(quint32 tgtIp) +{ + quint32 srcIp = ip4_; + PacketBuffer *reqPkt; + uchar *pktData; + + // Validate target IP + if (!tgtIp) + return; + // Do we already have a ARP entry (resolved or unresolved)? - // FIXME: do we need a timer to resend ARP for unresolved entries? + // XXX: No NDP state machine for now if (arpTable_.contains(tgtIp)) return; @@ -902,9 +912,8 @@ _invalid_exit: // pktBuf should point to start of IP header void Device::sendNeighborSolicit(PacketBuffer *pktBuf) { - PacketBuffer *reqPkt; uchar *pktData = pktBuf->data(); - UInt128 srcIp, dstIp, mask, tgtIp; + UInt128 srcIp = ip6_, dstIp, mask, tgtIp; if (pktBuf->length() < kIp6HdrLen) { qDebug("incomplete IPv6 header: expected %d, actual %d", @@ -912,7 +921,6 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf) return; } - srcIp = qFromBigEndian(pktData + 8); dstIp = qFromBigEndian(pktData + 24); mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); @@ -922,8 +930,21 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf) qPrintable(QHostAddress(mask.toArray()).toString())); tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_; + sendNeighborSolicit(tgtIp); +} + +void Device::sendNeighborSolicit(UInt128 tgtIp) +{ + UInt128 dstIp, srcIp = ip6_; + PacketBuffer *reqPkt; + uchar *pktData; + + // Validate target IP + if (tgtIp == UInt128(0, 0)) + return; + // Do we already have a NDP entry (resolved or unresolved)? - // FIXME: do we need a timer to resend NS for unresolved entries? + // XXX: No ARP state machine for now if (ndpTable_.contains(tgtIp)) return; diff --git a/server/device.h b/server/device.h index f18a9f8..fda1a2e 100644 --- a/server/device.h +++ b/server/device.h @@ -59,6 +59,8 @@ public: void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); + void resolveGateway(); + void clearNeighbors(); void resolveNeighbor(PacketBuffer *pktBuf); void getNeighbors(OstEmul::DeviceNeighborList *neighbors); @@ -69,6 +71,7 @@ public: private: // methods void receiveArp(PacketBuffer *pktBuf); void sendArpRequest(PacketBuffer *pktBuf); + void sendArpRequest(quint32 tgtIp); void receiveIp4(PacketBuffer *pktBuf); void sendIp4Reply(PacketBuffer *pktBuf); @@ -83,6 +86,7 @@ private: // methods void receiveNdp(PacketBuffer *pktBuf); void sendNeighborSolicit(PacketBuffer *pktBuf); + void sendNeighborSolicit(UInt128 tgtIp); void sendNeighborAdvertisement(PacketBuffer *pktBuf); private: // data diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index d82714b..94a38da 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -260,6 +260,13 @@ void DeviceManager::transmitPacket(PacketBuffer *pktBuf) port_->sendEmulationPacket(pktBuf); } +void DeviceManager::resolveDeviceGateways() +{ + foreach(Device *device, deviceList_) { + device->resolveGateway(); + } +} + void DeviceManager::clearDeviceNeighbors() { foreach(Device *device, deviceList_) diff --git a/server/devicemanager.h b/server/devicemanager.h index e2658b9..2a9adf5 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -52,6 +52,8 @@ public: void receivePacket(PacketBuffer *pktBuf); void transmitPacket(PacketBuffer *pktBuf); + void resolveDeviceGateways(); + void clearDeviceNeighbors(); void resolveDeviceNeighbor(PacketBuffer *pktBuf); void getDeviceNeighbors(OstProto::PortNeighborList *neighborList); diff --git a/test/emultest.py b/test/emultest.py index ff9d85b..c54730f 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -594,7 +594,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) assert cap_pkts.count('\n') == 1 - # verify *no* ARP Requests sent out from rx port + # verify ARP Requests sent out from rx port buff = drone.getCaptureBuffer(emul_ports.port_id[1]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') @@ -621,7 +621,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Y', filter]) print(cap_pkts) - assert cap_pkts.count('\n') == 0 + assert cap_pkts.count('\n') == 1 # retrieve and verify ARP/NDP Table on tx/rx ports log.info('retrieving ARP/NDP entries on tx port') @@ -636,8 +636,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, for arp in device.arp: print('%s: %s %012x' % (str(ipaddress.ip_address(dev_cfg.ip4)), - str(ipaddress.ip_address(arp.ip4)), - arp.mac)) + str(ipaddress.ip_address(arp.ip4)), arp.mac)) if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): resolved = True assert resolved @@ -646,32 +645,36 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, for ndp in device.ndp: print('%s: %s %012x' % (str(ip6_address(dev_cfg.ip6)), - str(ip6_address(ndp.ip6)), - ndp.mac)) + str(ip6_address(ndp.ip6)), ndp.mac)) if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): resolved = True assert resolved log.info('retrieving ARP/NDP entries on rx port') - device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_list = drone.getDeviceList(emul_ports.port_id[1]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on rx port') for dev_cfg, device in zip(device_config, devices): - # verify *no* ARPs/NDPs learnt on rx port if has_ip4: + resolved = False for arp in device.arp: print('%s: %s %012x' % (str(ipaddress.ip_address(dev_cfg.ip4)), str(ipaddress.ip_address(arp.ip4)), arp.mac)) - assert len(device.arp) == 0 + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved if has_ip6: + resolved = False for ndp in device.ndp: print('%s: %s %012x' % (str(ip6_address(dev_cfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - assert len(device.ndp) == 0 + if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + resolved = True + assert resolved # ping the tx devices from the DUT for i in range(num_devs): @@ -998,8 +1001,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, print(cap_pkts) assert cap_pkts.count('\n') == 1 - # verify *no* ARP/NDP Requests sent out from rx port - buff = drone.getCaptureBuffer(emul_ports.port_id[0]) + # verify ARP/NDP Requests sent out from rx port + buff = drone.getCaptureBuffer(emul_ports.port_id[1]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) @@ -1030,7 +1033,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', '-Y', filter]) print(cap_pkts) - assert cap_pkts.count('\n') == 0 + assert cap_pkts.count('\n') == 1 # retrieve and verify ARP/NDP Table on tx/rx ports log.info('retrieving ARP/NDP entries on tx port') @@ -1063,7 +1066,7 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, assert resolved log.info('retrieving ARP entries on rx port') - device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_list = drone.getDeviceList(emul_ports.port_id[1]) device_config = device_list.Extensions[emul.port_device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] @@ -1072,19 +1075,24 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, vlans = '' for v in dev_cfg.vlan: vlans += str(v & 0xffff) + ' ' - # verify *no* ARPs/NDPs learnt on rx port if has_ip4: + resolved = False for arp in device.arp: print('%s%s: %s %012x' % (vlans, str(ipaddress.ip_address(dev_cfg.ip4)), - str(ipaddress.ip_address(arp.ip4)), arp.mac)) - assert len(device.arp) == 0 + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + resolved = True + assert resolved if has_ip6: + resolved = False for ndp in device.ndp: print('%s%s: %s %012x' % (vlans, str(ip6_address(dev_cfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - assert len(device.ndp) == 0 + if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + resolved = True + assert resolved # ping the tx devices from the DUT for i in range(num_vlans): From 3040c72181b5a5f2b5bf22a9a52a5920b88c0712 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 28 Jan 2016 21:21:24 +0530 Subject: [PATCH 043/121] Device Emulation (contd.): Optimize getDeviceMac() and getNeighborMac() to return 0 if no devices are configured --- server/devicemanager.cpp | 4 ++++ server/myservice.cpp | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 94a38da..8ace3e3 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -321,6 +321,10 @@ Device* DeviceManager::originDevice(PacketBuffer *pktBuf) 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 diff --git a/server/myservice.cpp b/server/myservice.cpp index 2c5fc62..3d8531c 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -933,11 +933,18 @@ _invalid_port: quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex) { MyService *service = drone->rpcService(); + DeviceManager *devMgr = NULL; quint64 mac; if (!service) return 0; + if ((portId >= 0) && (portId < service->portInfo.size())) + devMgr = service->portInfo[portId]->deviceManager(); + + if (!devMgr || !devMgr->deviceCount()) + return 0; + service->portLock[portId]->lockForWrite(); mac = service->portInfo[portId]->deviceMacAddress(streamId, frameIndex); service->portLock[portId]->unlock(); @@ -948,11 +955,18 @@ quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex) quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex) { MyService *service = drone->rpcService(); + DeviceManager *devMgr = NULL; quint64 mac; if (!service) return 0; + if ((portId >= 0) && (portId < service->portInfo.size())) + devMgr = service->portInfo[portId]->deviceManager(); + + if (!devMgr || !devMgr->deviceCount()) + return 0; + service->portLock[portId]->lockForWrite(); mac = service->portInfo[portId]->neighborMacAddress(streamId, frameIndex); service->portLock[portId]->unlock(); From e8030bbd01e6c218dd2a99224a0e8af52f71be7d Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 2 Feb 2016 18:43:48 +0530 Subject: [PATCH 044/121] Device Emulation (contd.): Added a tab and view in the GUI for device configuration --- client/portswindow.ui | 244 +++++++++++++++++++++++++----------------- 1 file changed, 145 insertions(+), 99 deletions(-) diff --git a/client/portswindow.ui b/client/portswindow.ui index d3a2c55..9d7ee44 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -47,115 +47,161 @@ 0 - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - - - Avg pps - - - true - - - - - - - - - - Avg bps - - - - - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + + + 0 + + + + Streams + - - - Apply + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + Avg pps + + + true + + + + + + + + + + Avg bps + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Apply + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + 0 + 1 + + + + Qt::ActionsContextMenu + + + QFrame::StyledPanel + + + 1 + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + Devices + + - - - Qt::Vertical + + + + 0 + 1 + - - - 20 - 0 - + + Qt::ActionsContextMenu - + + QFrame::StyledPanel + + + 1 + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + - - - - - - - - 0 - 1 - - - - Qt::ActionsContextMenu - - - QFrame::StyledPanel - - - 1 - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - + From 6fddf0436c25592cd777ce01fc5cb120c1175530 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 5 Feb 2016 19:06:14 +0530 Subject: [PATCH 045/121] Device Emulation (contd.): Client calls RPCs for retreiving device group id and config list(s) at connect; optimisation - don't retreive streamConfig if port doesn't have any streams --- client/port.cpp | 52 ++++++++++++++ client/port.h | 15 ++++ client/portgroup.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++- client/portgroup.h | 7 ++ 4 files changed, 230 insertions(+), 2 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 9542d67..57d288d 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -605,3 +605,55 @@ _exit: mainWindow->setEnabled(true); return ret; } + +// ------------ Device Group ----------- // + +int Port::numDeviceGroups() +{ + return deviceGroups_.size(); +} + +OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) +{ + // FIXME: do we need to index? can't we use an iterator instead? + if ((index < 0) || (index >= numDeviceGroups())) { + qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, + index, numDeviceGroups() - 1); + return NULL; + } + + // Sort List by 'id', get the id at 'index' and then corresponding devGrp + return deviceGroups_.value(deviceGroups_.uniqueKeys().value(index)); +} + +bool Port::insertDeviceGroup(uint deviceGroupId) +{ + OstProto::DeviceGroup *devGrp; + + if (deviceGroups_.contains(deviceGroupId)) { + qDebug("%s: deviceGroup id %u already exists", __FUNCTION__, + deviceGroupId); + return false; + } + + devGrp = new OstProto::DeviceGroup; + devGrp->mutable_device_group_id()->set_id(deviceGroupId); + deviceGroups_.insert(deviceGroupId, devGrp); + return true; +} + +bool Port::updateDeviceGroup( + uint deviceGroupId, + OstProto::DeviceGroup *deviceGroup) +{ + OstProto::DeviceGroup *devGrp = deviceGroups_.value(deviceGroupId); + + if (!devGrp) { + qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__, + deviceGroupId); + return false; + } + + devGrp->CopyFrom(*deviceGroup); + return true; +} diff --git a/client/port.h b/client/port.h index 006ba7e..9e09ef9 100644 --- a/client/port.h +++ b/client/port.h @@ -21,6 +21,7 @@ along with this program. If not, see #define _PORT_H #include +#include #include #include #include @@ -51,6 +52,8 @@ class Port : public QObject { QList mLastSyncStreamList; QList mStreams; // sorted by stream's ordinal value + QHash deviceGroups_; + uint newStreamId(); void updateStreamOrdinalsFromIndex(); void reorderStreamsByOrdinals(); @@ -145,6 +148,18 @@ public: bool openStreams(QString fileName, bool append, QString &error); bool saveStreams(QString fileName, QString fileType, QString &error); + // ------------ Device Group ----------- // + + int numDeviceGroups(); + OstProto::DeviceGroup* deviceGroupByIndex(int index); + + //! Used by MyService::Stub to update from config received from server + //@{ + bool insertDeviceGroup(uint deviceGroupId); + bool updateDeviceGroup(uint deviceGroupId, + OstProto::DeviceGroup *deviceGroup); + //@} + signals: void portRateChanged(int portGroupId, int portId); void portDataChanged(int portGroupId, int portId); diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 1742fe3..362cff4 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -365,8 +365,10 @@ void PortGroup::processPortConfigList(PbRpcController *controller) // design emit portListChanged(mPortGroupId); - if (numPorts() > 0) + if (numPorts() > 0) { getStreamIdList(); + getDeviceGroupIdList(); + } _error_exit: delete controller; @@ -580,6 +582,9 @@ void PortGroup::getStreamConfigList() for (int portIndex = 0; portIndex < numPorts(); portIndex++) { + if (mPorts[portIndex]->numStreams() == 0) + continue; + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; OstProto::StreamConfigList *streamConfigList = new OstProto::StreamConfigList; @@ -634,11 +639,160 @@ void PortGroup::processStreamConfigList(int portIndex, streamConfigList->mutable_stream(i)); } +#if 0 + // FIXME: incorrect check - will never be true if last port does not have any streams configured // Are we done for all ports? - if (portIndex >= numPorts()) + if (portIndex >= (numPorts()-1)) { // FIXME(HI): some way to reset streammodel } +#endif + +_exit: + delete controller; +} + +void PortGroup::getDeviceGroupIdList() +{ + using OstProto::PortId; + using OstProto::DeviceGroupIdList; + + for (int portIndex = 0; portIndex < numPorts(); portIndex++) + { + PortId *portId = new PortId; + DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList; + PbRpcController *controller = new PbRpcController(portId, devGrpIdList); + + portId->set_id(mPorts[portIndex]->id()); + + serviceStub->getDeviceGroupIdList(controller, portId, devGrpIdList, + NewCallback(this, &PortGroup::processDeviceGroupIdList, + portIndex, controller)); + } +} + +void PortGroup::processDeviceGroupIdList( + int portIndex, + PbRpcController *controller) +{ + using OstProto::DeviceGroupIdList; + + DeviceGroupIdList *devGrpIdList = static_cast( + controller->response()); + + qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (devGrpIdList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + devGrpIdList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + for(int i = 0; i < devGrpIdList->device_group_id_size(); i++) + { + uint devGrpId; + + devGrpId = devGrpIdList->device_group_id(i).id(); + mPorts[portIndex]->insertDeviceGroup(devGrpId); + } + + //FIXME: mPorts[portIndex]->when_syncComplete(); + + // Are we done for all ports? + if (numPorts() && portIndex >= (numPorts()-1)) + getDeviceGroupConfigList(); + +_exit: + delete controller; +} + +void PortGroup::getDeviceGroupConfigList() +{ + using OstProto::DeviceGroupId; + using OstProto::DeviceGroupIdList; + using OstProto::DeviceGroupConfigList; + + qDebug("requesting device group config list ..."); + + for (int portIndex = 0; portIndex < numPorts(); portIndex++) + { + if (mPorts[portIndex]->numDeviceGroups() == 0) + continue; + + DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList; + DeviceGroupConfigList *devGrpCfgList = new DeviceGroupConfigList; + PbRpcController *controller = new PbRpcController( + devGrpIdList, devGrpCfgList); + + devGrpIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + for (int j = 0; j < mPorts[portIndex]->numDeviceGroups(); j++) + { + DeviceGroupId *dgid = devGrpIdList->add_device_group_id(); + dgid->set_id(mPorts[portIndex]->deviceGroupByIndex(j) + ->device_group_id().id()); + } + + serviceStub->getDeviceGroupConfig(controller, + devGrpIdList, devGrpCfgList, + NewCallback(this, &PortGroup::processDeviceGroupConfigList, + portIndex, controller)); + } +} + +void PortGroup::processDeviceGroupConfigList(int portIndex, + PbRpcController *controller) +{ + using OstProto::DeviceGroupConfigList; + + DeviceGroupConfigList *devGrpCfgList = + static_cast(controller->response()); + + qDebug("In %s", __PRETTY_FUNCTION__); + + Q_ASSERT(portIndex < numPorts()); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (devGrpCfgList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + devGrpCfgList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + for(int i = 0; i < devGrpCfgList->device_group_size(); i++) + { + uint dgid = devGrpCfgList->device_group(i).device_group_id().id(); + + mPorts[portIndex]->updateDeviceGroup(dgid, + devGrpCfgList->mutable_device_group(i)); + } + +#if 0 + // FIXME: incorrect check - will never be true if last port does not have any deviceGroups configured + // Are we done for all ports? + if (portIndex >= (numPorts()-1)) + { + // FIXME: reset deviceGroupModel? + } +#endif _exit: delete controller; diff --git a/client/portgroup.h b/client/portgroup.h index 7accbcf..b5ffa88 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -117,6 +117,13 @@ public: void processModifyStreamAck(OstProto::Ack *ack); + void getDeviceGroupIdList(); + void processDeviceGroupIdList(int portIndex, PbRpcController *controller); + void getDeviceGroupConfigList(); + void processDeviceGroupConfigList( + int portIndex, + PbRpcController *controller); + void startTx(QList *portList = NULL); void processStartTxAck(PbRpcController *controller); void stopTx(QList *portList = NULL); From c569328bb3cb7b101787985814b34643b4768c0a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 12 Feb 2016 19:10:19 +0530 Subject: [PATCH 046/121] Device Emulation (contd.): Added DeviceGroupModel on the GUI client --- client/devicegroupmodel.cpp | 173 ++++++++++++++++++++++++++++++++++++ client/devicegroupmodel.h | 49 ++++++++++ client/ostinato.pro | 2 + client/portgrouplist.cpp | 7 +- client/portgrouplist.h | 8 +- client/portswindow.cpp | 14 ++- client/portswindow.h | 6 -- common/emulproto.proto | 2 +- 8 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 client/devicegroupmodel.cpp create mode 100644 client/devicegroupmodel.h diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp new file mode 100644 index 0000000..cd86178 --- /dev/null +++ b/client/devicegroupmodel.cpp @@ -0,0 +1,173 @@ +/* +Copyright (C) 2016 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 "devicegroupmodel.h" + +#include "port.h" + +#include "emulproto.pb.h" + +enum { + kName, + kCount, + kVlan, + kIp, + kFieldCount +}; + +static QStringList columns_ = QStringList() + << "Name" + << "Count" + << "Vlan" + << "IP"; + +DeviceGroupModel::DeviceGroupModel(QObject *parent) + : QAbstractTableModel(parent) +{ + port_ = NULL; +} + +int DeviceGroupModel::rowCount(const QModelIndex &parent) const +{ + if (!port_ || parent.isValid()) + return 0; + + return port_->numDeviceGroups(); +} + +int DeviceGroupModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return columns_.size(); +} + +QVariant DeviceGroupModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: + return columns_[section]; + case Qt::Vertical: + return QString("%1").arg(section + 1); + default: + Q_ASSERT(false); // Unreachable + } + + return QVariant(); +} + +QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const +{ + if (!port_ || !index.isValid()) + return QVariant(); + + int dgIdx = index.row(); + int field = index.column(); + + Q_ASSERT(dgIdx < port_->numDeviceGroups()); + Q_ASSERT(field < kFieldCount); + + OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx); + + switch (field) { + case kName: + switch (role) { + case Qt::DisplayRole: + return QString::fromStdString(devGrp->core().name()); + default: + break; + } + return QVariant(); + + case kCount: + switch (role) { + case Qt::DisplayRole: + return devGrp->device_count(); + case Qt::TextAlignmentRole: + return Qt::AlignRight; + default: + break; + } + return QVariant(); + + case kVlan: + switch (role) { + case Qt::CheckStateRole: + if (devGrp->has_encap() + && devGrp->encap().HasExtension(OstEmul::vlan) + && devGrp->encap().GetExtension(OstEmul::vlan) + .stack_size()) + return Qt::Checked; + return Qt::Unchecked; + case Qt::TextAlignmentRole: + return Qt::AlignCenter; + default: + break; + } + return QVariant(); + + case kIp: + switch (role) { + case Qt::DisplayRole: + if (devGrp->HasExtension(OstEmul::ip4)) + if (devGrp->HasExtension(OstEmul::ip6)) + return QString("Dual Stack"); + else + return QString("IPv4"); + else if (devGrp->HasExtension(OstEmul::ip6)) + return QString("IPv6"); + else + return QString("None"); + default: + break; + } + return QVariant(); + + default: + break; + } + + qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + return QVariant(); +} + +bool DeviceGroupModel::setData( + const QModelIndex &index, + const QVariant &value, + int role) +{ + if (!port_) + return false; + + // FIXME + return false; +} + +void DeviceGroupModel::setPort(Port *port) +{ + port_ = port; + reset(); +} diff --git a/client/devicegroupmodel.h b/client/devicegroupmodel.h new file mode 100644 index 0000000..79da02e --- /dev/null +++ b/client/devicegroupmodel.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _DEVICE_GROUP_MODEL_H +#define _DEVICE_GROUP_MODEL_H + +#include +#include + +class Port; + +class DeviceGroupModel: public QAbstractTableModel +{ + Q_OBJECT +public: + DeviceGroupModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + + void setPort(Port *port); +private: + Port *port_; +}; + +#endif + diff --git a/client/ostinato.pro b/client/ostinato.pro index 374f663..a8073b3 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -33,6 +33,7 @@ LIBS += -lprotobuf LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 RESOURCES += ostinato.qrc HEADERS += \ + devicegroupmodel.h \ dumpview.h \ hexlineedit.h \ mainwindow.h \ @@ -67,6 +68,7 @@ FORMS += \ variablefieldswidget.ui SOURCES += \ + devicegroupmodel.cpp \ dumpview.cpp \ stream.cpp \ hexlineedit.cpp \ diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index cfdc74b..987b6fb 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -25,7 +25,8 @@ along with this program. If not, see PortGroupList::PortGroupList() : mPortGroupListModel(this), mStreamListModel(this), - mPortStatsModel(this, this) + mPortStatsModel(this, this), + mDeviceGroupModel(this) { PortGroup *pg; @@ -33,10 +34,12 @@ PortGroupList::PortGroupList() streamModelTester_ = NULL; portModelTester_ = NULL; portStatsModelTester_ = NULL; + deviceGroupModelTester_ = NULL; #else streamModelTester_ = new ModelTest(getStreamModel()); portModelTester_ = new ModelTest(getPortModel()); portStatsModelTester_ = new ModelTest(getPortStatsModel()); + deviceGroupModelTester_ = new ModelTest(getPortStatsModel()); #endif // Add the "Local" Port Group @@ -49,10 +52,10 @@ PortGroupList::~PortGroupList() delete portStatsModelTester_; delete portModelTester_; delete streamModelTester_; + delete deviceGroupModelTester_; while (!mPortGroups.isEmpty()) delete mPortGroups.takeFirst(); - } bool PortGroupList::isPortGroup(const QModelIndex& index) diff --git a/client/portgrouplist.h b/client/portgrouplist.h index 3083c26..75544ee 100644 --- a/client/portgrouplist.h +++ b/client/portgrouplist.h @@ -20,12 +20,11 @@ along with this program. If not, see #ifndef _PORT_GROUP_LIST_H #define _PORT_GROUP_LIST_H +#include "devicegroupmodel.h" #include "portgroup.h" -#include -#include #include "portmodel.h" -#include "streammodel.h" #include "portstatsmodel.h" +#include "streammodel.h" class PortModel; class StreamModel; @@ -42,10 +41,12 @@ class PortGroupList : public QObject { PortModel mPortGroupListModel; StreamModel mStreamListModel; PortStatsModel mPortStatsModel; + DeviceGroupModel mDeviceGroupModel; QObject *streamModelTester_; QObject *portModelTester_; QObject *portStatsModelTester_; + QObject *deviceGroupModelTester_; // Methods public: @@ -55,6 +56,7 @@ public: PortModel* getPortModel() { return &mPortGroupListModel; } PortStatsModel* getPortStatsModel() { return &mPortStatsModel; } StreamModel* getStreamModel() { return &mStreamListModel; } + DeviceGroupModel* getDeviceGroupModel() { return &mDeviceGroupModel; } bool isPortGroup(const QModelIndex& index); bool isPort(const QModelIndex& index); diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 21668f2..c348091 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -51,6 +51,8 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->verticalHeader()->setDefaultSectionSize( tvStreamList->verticalHeader()->minimumSectionSize()); + deviceGroupList->verticalHeader()->setDefaultSectionSize( + deviceGroupList->verticalHeader()->minimumSectionSize()); // Populate PortList Context Menu Actions tvPortList->addAction(actionNew_Port_Group); @@ -82,6 +84,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) addActions(tvStreamList->actions()); tvStreamList->setModel(plm->getStreamModel()); + deviceGroupList->setModel(plm->getDeviceGroupModel()); // XXX: It would be ideal if we only needed to do the below to // get the proxy model to do its magic. However, the QModelIndex @@ -128,6 +131,10 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); + // FIXME: hardcoding + deviceGroupList->resizeColumnToContents(1); + deviceGroupList->resizeColumnToContents(2); + // Initially we don't have any ports/streams - so send signal triggers when_portView_currentChanged(QModelIndex(), QModelIndex()); updateStreamViewActions(); @@ -203,6 +210,7 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, { QModelIndex current = currentIndex; QModelIndex previous = previousIndex; + Port *port = NULL; if (proxyPortModel) { current = proxyPortModel->mapToSource(current); @@ -215,6 +223,10 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, qDebug("In %s", __FUNCTION__); + if (plm->isPort(current)) + port = &(plm->port(current)); + plm->getDeviceGroupModel()->setPort(port); + if (previous.isValid() && plm->isPort(previous)) { disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), @@ -801,5 +813,3 @@ _retry: _exit: return; } - - diff --git a/client/portswindow.h b/client/portswindow.h index 99f81e9..560ca36 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -25,12 +25,6 @@ along with this program. If not, see #include "ui_portswindow.h" #include "portgrouplist.h" -/* TODO -HIGH -MED -LOW -*/ - class QAbstractItemDelegate; class QSortFilterProxyModel; diff --git a/common/emulproto.proto b/common/emulproto.proto index 2c4430a..720c1c7 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -56,7 +56,7 @@ message Ip4Emulation { optional uint32 prefix_length = 2; optional uint32 default_gateway = 3; - optional uint64 step = 10 [default = 1]; + optional uint32 step = 10 [default = 1]; // FIXME: step for gateway? } From 264fe20c34d5cab36f74f810387871ecde9ba023 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 16 Feb 2016 18:57:08 +0530 Subject: [PATCH 047/121] Device Emulation (contd.): Display vlan count and change device count to show total number of devices across all vlans --- client/devicegroupmodel.cpp | 49 +++++++++++++++++++++++++------------ client/devicegroupmodel.h | 6 +++++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index cd86178..a96e692 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -25,16 +25,16 @@ along with this program. If not, see enum { kName, - kCount, - kVlan, + kVlanCount, + kDeviceCount, // Across all vlans kIp, kFieldCount }; static QStringList columns_ = QStringList() << "Name" - << "Count" - << "Vlan" + << "Vlans" + << "Devices" << "IP"; DeviceGroupModel::DeviceGroupModel(QObject *parent) @@ -92,6 +92,8 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx); + Q_ASSERT(devGrp); + switch (field) { case kName: switch (role) { @@ -102,10 +104,12 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const } return QVariant(); - case kCount: + case kVlanCount: switch (role) { case Qt::DisplayRole: - return devGrp->device_count(); + if (int v = vlanCount(devGrp)) + return v; + return QString("None"); case Qt::TextAlignmentRole: return Qt::AlignRight; default: @@ -113,17 +117,12 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const } return QVariant(); - case kVlan: + case kDeviceCount: switch (role) { - case Qt::CheckStateRole: - if (devGrp->has_encap() - && devGrp->encap().HasExtension(OstEmul::vlan) - && devGrp->encap().GetExtension(OstEmul::vlan) - .stack_size()) - return Qt::Checked; - return Qt::Unchecked; + case Qt::DisplayRole: + return qMax(vlanCount(devGrp), 1)*devGrp->device_count(); case Qt::TextAlignmentRole: - return Qt::AlignCenter; + return Qt::AlignRight; default: break; } @@ -171,3 +170,23 @@ void DeviceGroupModel::setPort(Port *port) port_ = port; reset(); } + +// +// ---------------------- Private Methods ----------------------- +// +int DeviceGroupModel::vlanCount(const OstProto::DeviceGroup *deviceGroup) const +{ + if (!deviceGroup->has_encap() + || !deviceGroup->encap().HasExtension(OstEmul::vlan)) + return 0; + + OstEmul::VlanEmulation vlan = deviceGroup->encap() + .GetExtension(OstEmul::vlan); + int numTags = vlan.stack_size(); + int count = 1; + + for (int i = 0; i < numTags; i++) + count *= vlan.stack(i).count(); + + return count; +} diff --git a/client/devicegroupmodel.h b/client/devicegroupmodel.h index 79da02e..ea4f287 100644 --- a/client/devicegroupmodel.h +++ b/client/devicegroupmodel.h @@ -24,6 +24,9 @@ along with this program. If not, see #include class Port; +namespace OstProto { + class DeviceGroup; +}; class DeviceGroupModel: public QAbstractTableModel { @@ -41,7 +44,10 @@ public: int role = Qt::EditRole); void setPort(Port *port); + private: + int vlanCount(const OstProto::DeviceGroup *deviceGroup) const; + Port *port_; }; From c63528ebae717f828762810880d35b751600a0a7 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 17 Feb 2016 21:13:30 +0530 Subject: [PATCH 048/121] Device Emulation (contd.): Added first cut code for the Device Group Configuration Dialog --- client/devicegroupdialog.cpp | 68 ++++++ client/devicegroupdialog.h | 41 ++++ client/devicegroupdialog.ui | 399 +++++++++++++++++++++++++++++++++++ client/ostinato.pro | 3 + client/portswindow.cpp | 13 ++ client/portswindow.h | 2 + 6 files changed, 526 insertions(+) create mode 100644 client/devicegroupdialog.cpp create mode 100644 client/devicegroupdialog.h create mode 100644 client/devicegroupdialog.ui diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp new file mode 100644 index 0000000..587fdc3 --- /dev/null +++ b/client/devicegroupdialog.cpp @@ -0,0 +1,68 @@ +/* +Copyright (C) 2016 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 "devicegroupdialog.h" + +enum { kIpNone, kIp4, kIp6, kIpDual }; +static QStringList ipStackItems = QStringList() + << "None" << "IPv4" << "IPv6" << "Dual"; + +DeviceGroupDialog::DeviceGroupDialog(QWidget *parent, Qt::WindowFlags flags) + : QDialog(parent, flags) +{ + // Setup the Dialog + setupUi(this); + vlanTagCount->setRange(0, kMaxVlanTags); + ipStack->insertItems(0, ipStackItems); +} + +// +// Private Slots +// +void DeviceGroupDialog::on_vlanTagCount_valueChanged(int value) +{ + Q_ASSERT((value >= 0) && (value <= kMaxVlanTags)); + + vlans->setVisible(value > 0); +} + +void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index) +{ + switch (index) { + case kIpNone: + ip4->hide(); + ip6->hide(); + break; + case kIp4: + ip4->show(); + ip6->hide(); + break; + case kIp6: + ip4->hide(); + ip6->show(); + break; + case kIpDual: + ip4->show(); + ip6->show(); + break; + default: + Q_ASSERT(false); // Unreachable! + break; + } +} diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h new file mode 100644 index 0000000..15d5815 --- /dev/null +++ b/client/devicegroupdialog.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _DEVICE_GROUP_DIALOG_H +#define _DEVICE_GROUP_DIALOG_H + +#include "ui_devicegroupdialog.h" + +#include + +class DeviceGroupDialog: public QDialog, private Ui::DeviceGroupDialog +{ + Q_OBJECT +public: + DeviceGroupDialog(QWidget *parent = NULL, Qt::WindowFlags flags = 0); + +private slots: + void on_vlanTagCount_valueChanged(int value); + void on_ipStack_currentIndexChanged(int index); + +private: + static const int kMaxVlanTags = 4; +}; + +#endif diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui new file mode 100644 index 0000000..37d5024 --- /dev/null +++ b/client/devicegroupdialog.ui @@ -0,0 +1,399 @@ + + DeviceGroupDialog + + + + 0 + 0 + 504 + 465 + + + + Dialog + + + + + + + + Name + + + + + + + + + + Vlan Tags + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + # + + + + + Vlan Id + + + + + Count + + + + + Step + + + + + CFI/DE + + + + + Prio + + + + + TPID + + + + + + + + + + Total Vlans + + + + + + + false + + + + + + + Devices Per Vlan + + + devicePerVlanCount + + + + + + + + + + Total Devices + + + + + + + false + + + + + + + Mac Address + + + + + + + + + + Step + + + + + + + + + + IP Stack + + + + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + + + IPv4 Address + + + + + + + + + + + 0 + 0 + + + + / + + + + + + + Step + + + + + + + + + + Gateway + + + + + + + + + + 1 + + + 32 + + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + + + IPv6 Address + + + + + + + + + + + 0 + 0 + + + + / + + + + + + + Step + + + + + + + + + + Gateway + + + + + + + + + + 1 + + + 128 + + + + + + + + + + + + + + < + + + + + + + > + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + name + vlanTagCount + vlans + devicePerVlanCount + macAddress + macStep + ipStack + ip4Address + ip4PrefixLength + ip4Step + ip4Gateway + ip6Address + ip6PrefixLength + ip6Step + ip6Gateway + prev + next + buttonBox + + + + + buttonBox + accepted() + DeviceGroupDialog + accept() + + + 227 + 289 + + + 157 + 274 + + + + + buttonBox + rejected() + DeviceGroupDialog + reject() + + + 295 + 295 + + + 286 + 274 + + + + + diff --git a/client/ostinato.pro b/client/ostinato.pro index a8073b3..2d9e3a0 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -33,6 +33,7 @@ LIBS += -lprotobuf LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 RESOURCES += ostinato.qrc HEADERS += \ + devicegroupdialog.h \ devicegroupmodel.h \ dumpview.h \ hexlineedit.h \ @@ -58,6 +59,7 @@ HEADERS += \ FORMS += \ about.ui \ + devicegroupdialog.ui \ mainwindow.ui \ portconfigdialog.ui \ portstatsfilter.ui \ @@ -68,6 +70,7 @@ FORMS += \ variablefieldswidget.ui SOURCES += \ + devicegroupdialog.cpp \ devicegroupmodel.cpp \ dumpview.cpp \ stream.cpp \ diff --git a/client/portswindow.cpp b/client/portswindow.cpp index c348091..0de2756 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "portswindow.h" #include "abstractfileformat.h" +#include "devicegroupdialog.h" #include "portconfigdialog.h" #include "settings.h" #include "streamconfigdialog.h" @@ -813,3 +814,15 @@ _retry: _exit: return; } + +// +// DeviceGroup slots +// +void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + DeviceGroupDialog dgd(this); + dgd.exec(); +} diff --git a/client/portswindow.h b/client/portswindow.h index 560ca36..7c12ab1 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -80,6 +80,8 @@ private slots: void on_actionSave_Streams_triggered(); void streamModelDataChanged(); + + void on_deviceGroupList_activated(const QModelIndex &index); }; #endif From 6d9327c9d413dd6978613480856eaf7cc5f0d878 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 22 Feb 2016 18:47:51 +0530 Subject: [PATCH 049/121] Device Emulation (contd.): Implemented new/edit/delete actions for device groups in the GUI --- client/devicegroupmodel.cpp | 50 +++++++++++++++++++++++++++++++ client/devicegroupmodel.h | 4 +++ client/port.cpp | 60 ++++++++++++++++++++++++++++++++----- client/port.h | 13 ++++++-- client/portswindow.cpp | 52 +++++++++++++++++++++++++++++++- client/portswindow.h | 3 ++ client/portswindow.ui | 15 ++++++++++ 7 files changed, 187 insertions(+), 10 deletions(-) diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index a96e692..4bbdf36 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -165,6 +165,56 @@ bool DeviceGroupModel::setData( return false; } +bool DeviceGroupModel::insertRows( + int row, + int count, + const QModelIndex &parent) +{ + int c = 0; + + Q_ASSERT(!parent.isValid()); + + beginInsertRows(parent, row, row+count-1); + for (int i = 0; i < count; i++) { + if (port_->newDeviceGroupAt(row)) + c++; + } + endInsertRows(); + + if (c != count) { + qWarning("failed to insert rows in DeviceGroupModel at row %d; " + "requested = %d, actual = %d", row, count, c); + return false; + } + + return true; +} + +bool DeviceGroupModel::removeRows( + int row, + int count, + const QModelIndex &parent) +{ + int c = 0; + + Q_ASSERT(!parent.isValid()); + + beginRemoveRows(parent, row, row+count-1); + for (int i = 0; i < count; i++) { + if (port_->deleteDeviceGroupAt(row)) + c++; + } + endRemoveRows(); + + if (c != count) { + qWarning("failed to delete rows in DeviceGroupModel at row %d; " + "requested = %d, actual = %d", row, count, c); + return false; + } + + return true; +} + void DeviceGroupModel::setPort(Port *port) { port_ = port; diff --git a/client/devicegroupmodel.h b/client/devicegroupmodel.h index ea4f287..9c0ec3f 100644 --- a/client/devicegroupmodel.h +++ b/client/devicegroupmodel.h @@ -42,6 +42,10 @@ public: QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + bool insertRows (int row, int count, + const QModelIndex &parent = QModelIndex()); + bool removeRows (int row, int count, + const QModelIndex &parent = QModelIndex()); void setPort(Port *port); diff --git a/client/port.cpp b/client/port.cpp index 57d288d..6445abb 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -31,6 +31,7 @@ along with this program. If not, see extern QMainWindow *mainWindow; uint Port::mAllocStreamId = 0; +uint Port::allocDeviceGroupId_ = 1; static const int kEthOverhead = 20; @@ -608,6 +609,11 @@ _exit: // ------------ Device Group ----------- // +uint Port::newDeviceGroupId() +{ + return allocDeviceGroupId_++; +} + int Port::numDeviceGroups() { return deviceGroups_.size(); @@ -615,22 +621,62 @@ int Port::numDeviceGroups() OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) { - // FIXME: do we need to index? can't we use an iterator instead? - if ((index < 0) || (index >= numDeviceGroups())) { + if ((index < 0) || (index >= numDeviceGroups())) { qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, index, numDeviceGroups() - 1); return NULL; } - // Sort List by 'id', get the id at 'index' and then corresponding devGrp - return deviceGroups_.value(deviceGroups_.uniqueKeys().value(index)); + return deviceGroups_.at(index); +} + +OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId) +{ + for (int i = 0; i < deviceGroups_.size(); i++) { + OstProto::DeviceGroup *devGrp = deviceGroups_.at(i); + if (devGrp->device_group_id().id() == deviceGroupId) + return devGrp; + } + + return NULL; +} + +bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup) +{ + if (index < 0 || index > numDeviceGroups()) + return false; + + OstProto::DeviceGroup *devGrp = new OstProto::DeviceGroup; + + if (!devGrp) { + qWarning("failed allocating a new device group"); + return false; + } + + if (deviceGroup) + devGrp->CopyFrom(*deviceGroup); + + devGrp->mutable_device_group_id()->set_id(newDeviceGroupId()); + deviceGroups_.insert(index, devGrp); + + return true; +} + +bool Port::deleteDeviceGroupAt(int index) +{ + if (index < 0 || index >= deviceGroups_.size()) + return false; + + delete deviceGroups_.takeAt(index); + + return true; } bool Port::insertDeviceGroup(uint deviceGroupId) { OstProto::DeviceGroup *devGrp; - if (deviceGroups_.contains(deviceGroupId)) { + if (deviceGroupById(deviceGroupId)) { qDebug("%s: deviceGroup id %u already exists", __FUNCTION__, deviceGroupId); return false; @@ -638,7 +684,7 @@ bool Port::insertDeviceGroup(uint deviceGroupId) devGrp = new OstProto::DeviceGroup; devGrp->mutable_device_group_id()->set_id(deviceGroupId); - deviceGroups_.insert(deviceGroupId, devGrp); + deviceGroups_.append(devGrp); return true; } @@ -646,7 +692,7 @@ bool Port::updateDeviceGroup( uint deviceGroupId, OstProto::DeviceGroup *deviceGroup) { - OstProto::DeviceGroup *devGrp = deviceGroups_.value(deviceGroupId); + OstProto::DeviceGroup *devGrp = deviceGroupById(deviceGroupId); if (!devGrp) { qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__, diff --git a/client/port.h b/client/port.h index 9e09ef9..430a171 100644 --- a/client/port.h +++ b/client/port.h @@ -21,7 +21,6 @@ along with this program. If not, see #define _PORT_H #include -#include #include #include #include @@ -35,6 +34,7 @@ class Port : public QObject { Q_OBJECT static uint mAllocStreamId; + static uint allocDeviceGroupId_; OstProto::Port d; OstProto::PortStats stats; @@ -52,7 +52,7 @@ class Port : public QObject { QList mLastSyncStreamList; QList mStreams; // sorted by stream's ordinal value - QHash deviceGroups_; + QList deviceGroups_; uint newStreamId(); void updateStreamOrdinalsFromIndex(); @@ -150,8 +150,17 @@ public: // ------------ Device Group ----------- // + uint newDeviceGroupId(); int numDeviceGroups(); OstProto::DeviceGroup* deviceGroupByIndex(int index); + OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId); + + //! Used by StreamModel + //@{ + bool newDeviceGroupAt(int index, + const OstProto::DeviceGroup *deviceGroup = NULL); + bool deleteDeviceGroupAt(int index); + //@} //! Used by MyService::Stub to update from config received from server //@{ diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 0de2756..6eef46d 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -77,12 +77,22 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->addAction(actionOpen_Streams); tvStreamList->addAction(actionSave_Streams); - // PortList and StreamList actions combined make this window's actions + // Populate DeviceGroup Context Menu Actions + deviceGroupList->addAction(actionNewDeviceGroup); + deviceGroupList->addAction(actionEditDeviceGroup); + deviceGroupList->addAction(actionDeleteDeviceGroup); + + // PortList, StreamList, DeviceGroupList actions combined + // make this window's actions addActions(tvPortList->actions()); sep = new QAction(this); sep->setSeparator(true); addAction(sep); addActions(tvStreamList->actions()); + sep = new QAction(this); + sep->setSeparator(true); + addAction(sep); + addActions(deviceGroupList->actions()); tvStreamList->setModel(plm->getStreamModel()); deviceGroupList->setModel(plm->getDeviceGroupModel()); @@ -818,6 +828,46 @@ _exit: // // DeviceGroup slots // +void PortsWindow::on_actionNewDeviceGroup_triggered() +{ + // In case nothing is selected, insert 1 row at the top + int row = 0, count = 1; + QItemSelection selection = deviceGroupList->selectionModel()->selection(); + + // In case we have a single range selected; insert as many rows as + // in the singe selected range before the top of the selected range + if (selection.size() == 1) + { + row = selection.at(0).top(); + count = selection.at(0).height(); + } + + plm->getDeviceGroupModel()->insertRows(row, count); +} + +void PortsWindow::on_actionDeleteDeviceGroup_triggered() +{ + QModelIndex index; + + if (deviceGroupList->selectionModel()->hasSelection()) + { + while(deviceGroupList->selectionModel()->selectedRows().size()) + { + index = deviceGroupList->selectionModel()->selectedRows().at(0); + plm->getDeviceGroupModel()->removeRows(index.row(), 1); + } + } +} + +void PortsWindow::on_actionEditDeviceGroup_triggered() +{ + QItemSelection selection = deviceGroupList->selectionModel()->selection(); + + // Ensure we have only one range selected which contains only one row + if ((selection.size() == 1) && (selection.at(0).height() == 1)) + on_deviceGroupList_activated(selection.at(0).topLeft()); +} + void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index) { if (!index.isValid()) diff --git a/client/portswindow.h b/client/portswindow.h index 7c12ab1..b601a43 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -81,6 +81,9 @@ private slots: void streamModelDataChanged(); + void on_actionNewDeviceGroup_triggered(); + void on_actionDeleteDeviceGroup_triggered(); + void on_actionEditDeviceGroup_triggered(); void on_deviceGroupList_activated(const QModelIndex &index); }; diff --git a/client/portswindow.ui b/client/portswindow.ui index 9d7ee44..3e7b15d 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -312,6 +312,21 @@ Duplicate Stream + + + New Device Group + + + + + Delete Device Group + + + + + Edit Device Group + + From c1d5ca90acf6b8c41724c06bf42b28916fd3845a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 22 Feb 2016 20:19:33 +0530 Subject: [PATCH 050/121] Device Emulation (contd.): Enable/Disable Device View actions based on selections --- client/portswindow.cpp | 64 +++++++++++++++++++++++++++++++++++++++++- client/portswindow.h | 1 + 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 6eef46d..eff4468 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -142,13 +142,29 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); + connect(plm->getDeviceGroupModel(), + SIGNAL(rowsInserted(QModelIndex, int, int)), + SLOT(updateDeviceViewActions())); + connect(plm->getDeviceGroupModel(), + SIGNAL(rowsRemoved(QModelIndex, int, int)), + SLOT(updateDeviceViewActions())); + + connect(deviceGroupList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(updateDeviceViewActions())); + connect(deviceGroupList->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + SLOT(updateDeviceViewActions())); + // FIXME: hardcoding deviceGroupList->resizeColumnToContents(1); deviceGroupList->resizeColumnToContents(2); - // Initially we don't have any ports/streams - so send signal triggers + // Initially we don't have any ports/streams/devices + // - so send signal triggers when_portView_currentChanged(QModelIndex(), QModelIndex()); updateStreamViewActions(); + updateDeviceViewActions(); connect(plm->getStreamModel(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), @@ -231,6 +247,7 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, plm->getStreamModel()->setCurrentPortIndex(current); updatePortViewActions(currentIndex); updateStreamViewActions(); + updateDeviceViewActions(); qDebug("In %s", __FUNCTION__); @@ -391,6 +408,51 @@ void PortsWindow::updateStreamViewActions() actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); } +void PortsWindow::updateDeviceViewActions() +{ + QModelIndex current = tvPortList->currentIndex(); + QItemSelectionModel *devSel = deviceGroupList->selectionModel(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + // For some reason hasSelection() returns true even if selection size is 0 + // so additional check for size introduced + if (devSel->hasSelection() && (devSel->selection().size() > 0)) + { + // If more than one non-contiguous ranges selected, + // disable "New" and "Edit" + if (devSel->selection().size() > 1) + { + actionNewDeviceGroup->setDisabled(true); + actionEditDeviceGroup->setDisabled(true); + } + else + { + actionNewDeviceGroup->setEnabled(true); + + // Enable "Edit" only if the single range has a single row + if (devSel->selection().at(0).height() > 1) + actionEditDeviceGroup->setDisabled(true); + else + actionEditDeviceGroup->setEnabled(true); + } + + // Delete is always enabled as long as we have a selection + actionDeleteDeviceGroup->setEnabled(true); + } + else + { + qDebug("No device selection"); + if (plm->isPort(current)) + actionNewDeviceGroup->setEnabled(true); + else + actionNewDeviceGroup->setDisabled(true); + actionEditDeviceGroup->setDisabled(true); + actionDeleteDeviceGroup->setDisabled(true); + } +} + void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex) { QModelIndex current = currentIndex; diff --git a/client/portswindow.h b/client/portswindow.h index b601a43..ca6f210 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -50,6 +50,7 @@ public slots: private slots: void updatePortViewActions(const QModelIndex& currentIndex); void updateStreamViewActions(); + void updateDeviceViewActions(); void on_averagePacketsPerSec_editingFinished(); void on_averageBitsPerSec_editingFinished(); From a4a654fb022d3c1e63adec0e1e5ee877827eb7d9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 25 Feb 2016 19:03:00 +0530 Subject: [PATCH 051/121] Device Emulation (contd.): Implemented load/store for the DeviceGroupDialog and supporting code - incomplete --- client/devicegroupdialog.cpp | 176 ++++++++++++++++++++++++++++++++++- client/devicegroupdialog.h | 13 ++- client/portswindow.cpp | 6 +- common/uint128.h | 22 +++++ 4 files changed, 213 insertions(+), 4 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 587fdc3..90c2f2c 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -19,17 +19,72 @@ along with this program. If not, see #include "devicegroupdialog.h" +#include "port.h" + +#include "emulproto.pb.h" +#include "uint128.h" + +#include +#include + +#define uintToMacStr(num) \ + QString("%1").arg(num, 6*2, 16, QChar('0')) \ + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper() +#define macStrToUInt(str) \ + str.remove(QChar(' ')).toULongLong() + enum { kIpNone, kIp4, kIp6, kIpDual }; static QStringList ipStackItems = QStringList() << "None" << "IPv4" << "IPv6" << "Dual"; -DeviceGroupDialog::DeviceGroupDialog(QWidget *parent, Qt::WindowFlags flags) - : QDialog(parent, flags) +inline UInt128 UINT128(OstEmul::Ip6Address x) { + return UInt128(x.hi(), x.lo()); +} + +inline QString IP4STR(quint32 ip) +{ + return QHostAddress(ip).toString(); +} + +inline QString IP6STR(OstEmul::Ip6Address ip) +{ + return QHostAddress(UINT128(ip).toArray()).toString(); +} + +DeviceGroupDialog::DeviceGroupDialog( + Port *port, + int deviceGroupIndex, + QWidget *parent, + Qt::WindowFlags flags) + : QDialog(parent, flags), port_(port), index_(deviceGroupIndex) +{ + QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); + // Setup the Dialog setupUi(this); vlanTagCount->setRange(0, kMaxVlanTags); +#if 0 // FIXME: not working + qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); + qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); + vlans->verticalHeader()->setDefaultSectionSize( + vlans->verticalHeader()->minimumSectionSize()); + qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); + qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); +#endif + macAddress->setValidator(new QRegExpValidator(reMac, this)); + macStep->setValidator(new QRegExpValidator(reMac, this)); ipStack->insertItems(0, ipStackItems); + + layout()->setSizeConstraint(QLayout::SetFixedSize); + + loadDeviceGroup(); +} + +void DeviceGroupDialog::accept() +{ + storeDeviceGroup(); + QDialog::accept(); } // @@ -66,3 +121,120 @@ void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index) break; } } + +void DeviceGroupDialog::loadDeviceGroup() +{ + OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); + int tagCount = 0; + int totalVlans; + + Q_ASSERT(devGrp); + + name->setText(QString::fromStdString(devGrp->core().name())); + + if (devGrp->has_encap() && devGrp->encap().HasExtension(OstEmul::vlan)) + tagCount = devGrp->encap().GetExtension(OstEmul::vlan).stack_size(); + vlanTagCount->setValue(tagCount); + + // FIXME: vlan table widget + + totalVlans = totalVlanCount(); + vlanCount->setText(QString::number(totalVlans)); + devicePerVlanCount->setValue(devGrp->device_count()); + totalDeviceCount->setText( + QString::number(totalVlans * devGrp->device_count())); + + OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac); + macAddress->setText(uintToMacStr(mac.address())); + macStep->setText(uintToMacStr(mac.step())); + + OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4); + ip4Address->setText(IP4STR(ip4.address())); + ip4Step->setText(IP4STR(ip4.step())); + ip4Gateway->setText(IP4STR(ip4.default_gateway())); + + OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6); + ip6Address->setText(IP6STR(ip6.address())); + ip6Step->setText(IP6STR(ip6.step())); + ip6Gateway->setText(IP6STR(ip6.default_gateway())); + + int stk = kIpNone; + if (devGrp->HasExtension(OstEmul::ip4)) + if (devGrp->HasExtension(OstEmul::ip6)) + stk = kIpDual; + else + stk = kIp4; + else if (devGrp->HasExtension(OstEmul::ip6)) + stk = kIp6; + ipStack->setCurrentIndex(stk); +} + +void DeviceGroupDialog::storeDeviceGroup() +{ + OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); + int tagCount = 0; + + Q_ASSERT(devGrp); + + devGrp->mutable_core()->set_name(name->text().toStdString()); + + tagCount = vlanTagCount->value(); + // FIXME: vlan table widget + + devGrp->set_device_count(devicePerVlanCount->value()); + + OstEmul::MacEmulation *mac = devGrp->MutableExtension(OstEmul::mac); + mac->set_address(macStrToUInt(macAddress->text())); + mac->set_step(macStrToUInt(macStep->text())); + + if (ipStack->currentIndex() == kIp4 + || ipStack->currentIndex() == kIpDual) { + OstEmul::Ip4Emulation *ip4 = devGrp->MutableExtension(OstEmul::ip4); + ip4->set_address(QHostAddress(ip4Address->text()).toIPv4Address()); + ip4->set_prefix_length(ip4PrefixLength->value()); + ip4->set_default_gateway( + QHostAddress(ip4Gateway->text()).toIPv4Address()); + ip4->set_step(QHostAddress(ip4Step->text()).toIPv4Address()); + + if (ipStack->currentIndex() == kIp4) + devGrp->ClearExtension(OstEmul::ip6); + } + + if (ipStack->currentIndex() == kIp6 + || ipStack->currentIndex() == kIpDual) { + OstEmul::Ip6Emulation *ip6 = devGrp->MutableExtension(OstEmul::ip6); + Q_IPV6ADDR w; + UInt128 x; + + w = QHostAddress(ip6Address->text()).toIPv6Address(); + x = UInt128((quint8*)&w); + ip6->mutable_address()->set_hi(x.hi64()); + ip6->mutable_address()->set_lo(x.lo64()); + + ip6->set_prefix_length(ip4PrefixLength->value()); + + w = QHostAddress(ip6Gateway->text()).toIPv6Address(); + x = UInt128((quint8*)&w); + ip6->mutable_default_gateway()->set_hi(x.hi64()); + ip6->mutable_default_gateway()->set_lo(x.lo64()); + + w = QHostAddress(ip6Step->text()).toIPv6Address(); + x = UInt128((quint8*)&w); + ip6->mutable_step()->set_hi(x.hi64()); + ip6->mutable_step()->set_lo(x.lo64()); + + if (ipStack->currentIndex() == kIp6) + devGrp->ClearExtension(OstEmul::ip4); + } + + if (ipStack->currentIndex() == kIpNone) { + devGrp->ClearExtension(OstEmul::ip4); + devGrp->ClearExtension(OstEmul::ip6); + } +} + +int DeviceGroupDialog::totalVlanCount() +{ + // FIXME + return 1; +} diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h index 15d5815..46983ed 100644 --- a/client/devicegroupdialog.h +++ b/client/devicegroupdialog.h @@ -24,18 +24,29 @@ along with this program. If not, see #include +class Port; + class DeviceGroupDialog: public QDialog, private Ui::DeviceGroupDialog { Q_OBJECT public: - DeviceGroupDialog(QWidget *parent = NULL, Qt::WindowFlags flags = 0); + DeviceGroupDialog(Port *port, int deviceGroupIndex, + QWidget *parent = NULL, Qt::WindowFlags flags = 0); + virtual void accept(); private slots: void on_vlanTagCount_valueChanged(int value); void on_ipStack_currentIndexChanged(int index); + void loadDeviceGroup(); + void storeDeviceGroup(); private: + int totalVlanCount(); + static const int kMaxVlanTags = 4; + + Port *port_; + int index_; }; #endif diff --git a/client/portswindow.cpp b/client/portswindow.cpp index eff4468..4a669d6 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -935,6 +935,10 @@ void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index) if (!index.isValid()) return; - DeviceGroupDialog dgd(this); + QModelIndex currentPort = tvPortList->currentIndex(); + if (proxyPortModel) + currentPort = proxyPortModel->mapToSource(currentPort); + + DeviceGroupDialog dgd(&plm->port(currentPort), index.row(), this); dgd.exec(); } diff --git a/common/uint128.h b/common/uint128.h index 3e6601b..7bbe89c 100644 --- a/common/uint128.h +++ b/common/uint128.h @@ -30,6 +30,7 @@ class UInt128 public: UInt128(); UInt128(quint64 hi, quint64 lo); + UInt128(quint8 *value); quint64 hi64() const; quint64 lo64() const; @@ -60,6 +61,27 @@ inline UInt128::UInt128(quint64 hi, quint64 lo) lo_ = lo; } +inline UInt128::UInt128(quint8 *value) +{ + hi_ = (quint64(value[0]) << 56) + | (quint64(value[1]) << 48) + | (quint64(value[2]) << 40) + | (quint64(value[3]) << 32) + | (quint64(value[4]) << 24) + | (quint64(value[5]) << 16) + | (quint64(value[6]) << 8) + | (quint64(value[7]) << 0); + + lo_ = (quint64(value[ 8]) << 56) + | (quint64(value[ 9]) << 48) + | (quint64(value[10]) << 40) + | (quint64(value[11]) << 32) + | (quint64(value[12]) << 24) + | (quint64(value[13]) << 16) + | (quint64(value[14]) << 8) + | (quint64(value[15]) << 0); +} + inline quint64 UInt128::hi64() const { return hi_; From 9619439e6a210fb0ef744adc48fe82d22ab02e04 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 29 Feb 2016 20:12:02 +0530 Subject: [PATCH 052/121] Device Emulation (contd.): Use a default Mac address as per RFC 4814; define and use new MacEdit class --- client/devicegroupdialog.cpp | 34 +++++++++++++---------- client/devicegroupdialog.ui | 11 ++++++-- common/emulproto.proto | 3 +-- common/macedit.h | 52 ++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 common/macedit.h diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 90c2f2c..4d5f49c 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -24,15 +24,10 @@ along with this program. If not, see #include "emulproto.pb.h" #include "uint128.h" +#include "macedit.h" #include #include -#define uintToMacStr(num) \ - QString("%1").arg(num, 6*2, 16, QChar('0')) \ - .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper() -#define macStrToUInt(str) \ - str.remove(QChar(' ')).toULongLong() - enum { kIpNone, kIp4, kIp6, kIpDual }; static QStringList ipStackItems = QStringList() << "None" << "IPv4" << "IPv6" << "Dual"; @@ -59,8 +54,6 @@ DeviceGroupDialog::DeviceGroupDialog( Qt::WindowFlags flags) : QDialog(parent, flags), port_(port), index_(deviceGroupIndex) { - QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); - // Setup the Dialog setupUi(this); vlanTagCount->setRange(0, kMaxVlanTags); @@ -72,8 +65,6 @@ DeviceGroupDialog::DeviceGroupDialog( qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); #endif - macAddress->setValidator(new QRegExpValidator(reMac, this)); - macStep->setValidator(new QRegExpValidator(reMac, this)); ipStack->insertItems(0, ipStackItems); layout()->setSizeConstraint(QLayout::SetFixedSize); @@ -145,8 +136,23 @@ void DeviceGroupDialog::loadDeviceGroup() QString::number(totalVlans * devGrp->device_count())); OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac); - macAddress->setText(uintToMacStr(mac.address())); - macStep->setText(uintToMacStr(mac.step())); + if (!mac.has_address()) { + // Mac address as per RFC 4814 Sec 4.2 + // (RR & 0xFC):PP:PP:RR:RR:RR + // where RR is a random number, PP:PP is 1-indexed port index + // NOTE: although qrand() return type is a int, the max value + // is RAND_MAX (stdlib.h) which is often 16-bit only, so we + // use two random numbers + quint32 r1 = qrand(), r2 = qrand(); + quint64 mac; + mac = quint64(r1 & 0xfc00) << 32 + | quint64(port_->id() + 1) << 24 + | quint64((r1 & 0xff) << 16 | (r2 & 0xffff)); + macAddress->setValue(mac); + } + else + macAddress->setValue(mac.address()); + macStep->setValue(mac.step()); OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4); ip4Address->setText(IP4STR(ip4.address())); @@ -184,8 +190,8 @@ void DeviceGroupDialog::storeDeviceGroup() devGrp->set_device_count(devicePerVlanCount->value()); OstEmul::MacEmulation *mac = devGrp->MutableExtension(OstEmul::mac); - mac->set_address(macStrToUInt(macAddress->text())); - mac->set_step(macStrToUInt(macStep->text())); + mac->set_address(macAddress->value()); + mac->set_step(macStep->value()); if (ipStack->currentIndex() == kIp4 || ipStack->currentIndex() == kIpDual) { diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index 37d5024..0c16a8b 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -140,7 +140,7 @@ - + @@ -150,7 +150,7 @@ - + @@ -341,6 +341,13 @@ + + + MacEdit + QLineEdit +
macedit.h
+
+
name vlanTagCount diff --git a/common/emulproto.proto b/common/emulproto.proto index 720c1c7..7808f67 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -46,8 +46,7 @@ extend OstProto.EncapEmulation { // Protocols // =========== message MacEmulation { - optional uint64 address = 1; // FIXME: default value - + optional uint64 address = 1; // no default - need unique value optional uint64 step = 10 [default = 1]; } diff --git a/common/macedit.h b/common/macedit.h new file mode 100644 index 0000000..9fd39f0 --- /dev/null +++ b/common/macedit.h @@ -0,0 +1,52 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _MAC_EDIT_H +#define _MAC_EDIT_H + +class MacEdit: public QLineEdit +{ +public: + MacEdit(QWidget *parent = 0); + + quint64 value(); + void setValue(quint64 val); +}; + +inline MacEdit::MacEdit(QWidget *parent) + : QLineEdit(parent) +{ + QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); + + setValidator(new QRegExpValidator(reMac, this)); +} + +inline quint64 MacEdit::value() +{ + return text().remove(QChar(':')).toULongLong(NULL, 16); +} + +inline void MacEdit::setValue(quint64 val) +{ + setText(QString("%1").arg(val, 6*2, 16, QChar('0')) + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper()); +} + +#endif + From 0edfee8cdf06a9ec5f5f84202b956292c79206f6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 1 Mar 2016 18:48:35 +0530 Subject: [PATCH 053/121] Device Emulation (contd.): Add default values for IPv4 address/gateway, update gateway as address/pfxlen are edited; define and use a Ip4Edit class --- client/devicegroupdialog.cpp | 31 ++++++++++++--------- client/devicegroupdialog.h | 2 ++ client/devicegroupdialog.ui | 11 +++++--- common/emulproto.proto | 7 ++--- common/ip4edit.h | 52 ++++++++++++++++++++++++++++++++++++ common/macedit.h | 2 ++ 6 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 common/ip4edit.h diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 4d5f49c..34cbfc4 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -25,6 +25,7 @@ along with this program. If not, see #include "uint128.h" #include "macedit.h" +#include "ip4edit.h" #include #include @@ -37,11 +38,6 @@ inline UInt128 UINT128(OstEmul::Ip6Address x) return UInt128(x.hi(), x.lo()); } -inline QString IP4STR(quint32 ip) -{ - return QHostAddress(ip).toString(); -} - inline QString IP6STR(OstEmul::Ip6Address ip) { return QHostAddress(UINT128(ip).toArray()).toString(); @@ -69,6 +65,11 @@ DeviceGroupDialog::DeviceGroupDialog( layout()->setSizeConstraint(QLayout::SetFixedSize); + connect(ip4Address, SIGNAL(textEdited(const QString&)), + this, SLOT(updateIp4Gateway())); + connect(ip4PrefixLength, SIGNAL(valueChanged(const QString&)), + this, SLOT(updateIp4Gateway())); + loadDeviceGroup(); } @@ -113,6 +114,12 @@ void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index) } } +void DeviceGroupDialog::updateIp4Gateway() +{ + quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value())); + ip4Gateway->setValue(net | 0x01); +} + void DeviceGroupDialog::loadDeviceGroup() { OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); @@ -155,9 +162,10 @@ void DeviceGroupDialog::loadDeviceGroup() macStep->setValue(mac.step()); OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4); - ip4Address->setText(IP4STR(ip4.address())); - ip4Step->setText(IP4STR(ip4.step())); - ip4Gateway->setText(IP4STR(ip4.default_gateway())); + ip4Address->setValue(ip4.address()); + ip4PrefixLength->setValue(ip4.prefix_length()); + ip4Step->setValue(ip4.step()); + ip4Gateway->setValue(ip4.default_gateway()); OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6); ip6Address->setText(IP6STR(ip6.address())); @@ -196,11 +204,10 @@ void DeviceGroupDialog::storeDeviceGroup() if (ipStack->currentIndex() == kIp4 || ipStack->currentIndex() == kIpDual) { OstEmul::Ip4Emulation *ip4 = devGrp->MutableExtension(OstEmul::ip4); - ip4->set_address(QHostAddress(ip4Address->text()).toIPv4Address()); + ip4->set_address(ip4Address->value()); ip4->set_prefix_length(ip4PrefixLength->value()); - ip4->set_default_gateway( - QHostAddress(ip4Gateway->text()).toIPv4Address()); - ip4->set_step(QHostAddress(ip4Step->text()).toIPv4Address()); + ip4->set_default_gateway(ip4Gateway->value()); + ip4->set_step(ip4Step->value()); if (ipStack->currentIndex() == kIp4) devGrp->ClearExtension(OstEmul::ip6); diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h index 46983ed..b66922f 100644 --- a/client/devicegroupdialog.h +++ b/client/devicegroupdialog.h @@ -38,6 +38,8 @@ private slots: void on_vlanTagCount_valueChanged(int value); void on_ipStack_currentIndexChanged(int index); + void updateIp4Gateway(); + void loadDeviceGroup(); void storeDeviceGroup(); private: diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index 0c16a8b..c116317 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -181,7 +181,7 @@ - + @@ -204,7 +204,7 @@ - + @@ -214,7 +214,7 @@ - + @@ -347,6 +347,11 @@ QLineEdit
macedit.h
+ + Ip4Edit + QLineEdit +
ip4edit.h
+
name diff --git a/common/emulproto.proto b/common/emulproto.proto index 7808f67..30fc3b2 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -51,9 +51,10 @@ message MacEmulation { } message Ip4Emulation { - optional uint32 address = 1; - optional uint32 prefix_length = 2; - optional uint32 default_gateway = 3; + // Use RFC 2544 reserved address for default - 198.18.0.2/24 + optional uint32 address = 1 [default = 0xc6120002]; + optional uint32 prefix_length = 2 [default = 24]; + optional uint32 default_gateway = 3 [default = 0xc6120001]; optional uint32 step = 10 [default = 1]; // FIXME: step for gateway? diff --git a/common/ip4edit.h b/common/ip4edit.h new file mode 100644 index 0000000..1efc717 --- /dev/null +++ b/common/ip4edit.h @@ -0,0 +1,52 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _IP4_EDIT_H +#define _IP4_EDIT_H + +#include +#include + +class Ip4Edit: public QLineEdit +{ +public: + Ip4Edit(QWidget *parent = 0); + + quint32 value(); + void setValue(quint32 val); +}; + +inline Ip4Edit::Ip4Edit(QWidget *parent) + : QLineEdit(parent) +{ + setInputMask(QString("000.000.000.000; ")); +} + +inline quint32 Ip4Edit::value() +{ + return QHostAddress(text()).toIPv4Address(); +} + +inline void Ip4Edit::setValue(quint32 val) +{ + setText(QHostAddress(val).toString()); +} + +#endif + diff --git a/common/macedit.h b/common/macedit.h index 9fd39f0..390c6a3 100644 --- a/common/macedit.h +++ b/common/macedit.h @@ -20,6 +20,8 @@ along with this program. If not, see #ifndef _MAC_EDIT_H #define _MAC_EDIT_H +#include + class MacEdit: public QLineEdit { public: From ff757d59c639729ff32ba7b723c4a95eeb60760c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 2 Mar 2016 06:13:56 +0530 Subject: [PATCH 054/121] Device Emulation (contd.): Add default values for IPv6 fields, update IPv6 gateway if addr/pfxlen changes; define and use class Ip6Edit --- client/devicegroupdialog.cpp | 57 ++++++++++++++------------ client/devicegroupdialog.h | 1 + client/devicegroupdialog.ui | 11 +++-- common/emulproto.proto | 4 +- common/ip6edit.h | 79 ++++++++++++++++++++++++++++++++++++ common/uint128.h | 6 +++ 6 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 common/ip6edit.h diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 34cbfc4..cf8a9c3 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -24,8 +24,6 @@ along with this program. If not, see #include "emulproto.pb.h" #include "uint128.h" -#include "macedit.h" -#include "ip4edit.h" #include #include @@ -38,9 +36,13 @@ inline UInt128 UINT128(OstEmul::Ip6Address x) return UInt128(x.hi(), x.lo()); } -inline QString IP6STR(OstEmul::Ip6Address ip) +inline OstEmul::Ip6Address IP6ADDR(UInt128 x) { - return QHostAddress(UINT128(ip).toArray()).toString(); + OstEmul::Ip6Address ip; + + ip.set_hi(x.hi64()); + ip.set_lo(x.lo64()); + return ip; } DeviceGroupDialog::DeviceGroupDialog( @@ -69,6 +71,10 @@ DeviceGroupDialog::DeviceGroupDialog( this, SLOT(updateIp4Gateway())); connect(ip4PrefixLength, SIGNAL(valueChanged(const QString&)), this, SLOT(updateIp4Gateway())); + connect(ip6Address, SIGNAL(textEdited(const QString&)), + this, SLOT(updateIp6Gateway())); + connect(ip6PrefixLength, SIGNAL(valueChanged(const QString&)), + this, SLOT(updateIp6Gateway())); loadDeviceGroup(); } @@ -120,6 +126,13 @@ void DeviceGroupDialog::updateIp4Gateway() ip4Gateway->setValue(net | 0x01); } +void DeviceGroupDialog::updateIp6Gateway() +{ + UInt128 net = ip6Address->value() + & (~UInt128(0, 0) << (128 - ip6PrefixLength->value())); + ip6Gateway->setValue(net | UInt128(0, 1)); +} + void DeviceGroupDialog::loadDeviceGroup() { OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); @@ -168,9 +181,16 @@ void DeviceGroupDialog::loadDeviceGroup() ip4Gateway->setValue(ip4.default_gateway()); OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6); - ip6Address->setText(IP6STR(ip6.address())); - ip6Step->setText(IP6STR(ip6.step())); - ip6Gateway->setText(IP6STR(ip6.default_gateway())); + // ip6 fields don't have default values defined in the .proto + // because protobuf doesn't allow different default values for + // embedded message fields, so assign them here + // Use address 2001:0200::/64 from the RFC 5180 range + ip6Address->setValue(ip6.has_address() ? + UINT128(ip6.address()) : UInt128(0x20010200ULL << 32, 2)); + ip6PrefixLength->setValue(ip6.prefix_length()); + ip6Step->setValue(ip6.has_step() ? UINT128(ip6.step()) : UInt128(0, 1)); + ip6Gateway->setValue(ip6.has_default_gateway() ? + UINT128(ip6.default_gateway()) : UInt128(0x20010200ULL << 32, 1)); int stk = kIpNone; if (devGrp->HasExtension(OstEmul::ip4)) @@ -216,25 +236,10 @@ void DeviceGroupDialog::storeDeviceGroup() if (ipStack->currentIndex() == kIp6 || ipStack->currentIndex() == kIpDual) { OstEmul::Ip6Emulation *ip6 = devGrp->MutableExtension(OstEmul::ip6); - Q_IPV6ADDR w; - UInt128 x; - - w = QHostAddress(ip6Address->text()).toIPv6Address(); - x = UInt128((quint8*)&w); - ip6->mutable_address()->set_hi(x.hi64()); - ip6->mutable_address()->set_lo(x.lo64()); - - ip6->set_prefix_length(ip4PrefixLength->value()); - - w = QHostAddress(ip6Gateway->text()).toIPv6Address(); - x = UInt128((quint8*)&w); - ip6->mutable_default_gateway()->set_hi(x.hi64()); - ip6->mutable_default_gateway()->set_lo(x.lo64()); - - w = QHostAddress(ip6Step->text()).toIPv6Address(); - x = UInt128((quint8*)&w); - ip6->mutable_step()->set_hi(x.hi64()); - ip6->mutable_step()->set_lo(x.lo64()); + ip6->mutable_address()->CopyFrom(IP6ADDR(ip6Address->value())); + ip6->set_prefix_length(ip6PrefixLength->value()); + ip6->mutable_step()->CopyFrom(IP6ADDR(ip6Step->value())); + ip6->mutable_default_gateway()->CopyFrom(IP6ADDR(ip6Gateway->value())); if (ipStack->currentIndex() == kIp6) devGrp->ClearExtension(OstEmul::ip4); diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h index b66922f..a581db2 100644 --- a/client/devicegroupdialog.h +++ b/client/devicegroupdialog.h @@ -39,6 +39,7 @@ private slots: void on_ipStack_currentIndexChanged(int index); void updateIp4Gateway(); + void updateIp6Gateway(); void loadDeviceGroup(); void storeDeviceGroup(); diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index c116317..cd4fd22 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -246,7 +246,7 @@
- + @@ -269,7 +269,7 @@ - + @@ -279,7 +279,7 @@ - + @@ -352,6 +352,11 @@ QLineEdit
ip4edit.h
+ + Ip6Edit + QLineEdit +
ip6edit.h
+
name diff --git a/common/emulproto.proto b/common/emulproto.proto index 30fc3b2..5751f35 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -66,8 +66,10 @@ message Ip6Address { } message Ip6Emulation { + // no defaults since we can't set different default values + // for an embedded message field optional Ip6Address address = 1; - optional uint32 prefix_length = 2; + optional uint32 prefix_length = 2 [default = 64]; optional Ip6Address default_gateway = 3; optional Ip6Address step = 10; diff --git a/common/ip6edit.h b/common/ip6edit.h new file mode 100644 index 0000000..d1c2e81 --- /dev/null +++ b/common/ip6edit.h @@ -0,0 +1,79 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _IP6_EDIT_H +#define _IP6_EDIT_H + +#include "ipv6addressvalidator.h" +#include "UInt128.h" + +#include +#include + +class Ip6Edit: public QLineEdit +{ +public: + Ip6Edit(QWidget *parent = 0); + + UInt128 value(); + quint64 valueHi64(); + quint64 valueLo64(); + void setValue(UInt128 val); + void setValue(quint64 hi, quint64 lo); + void setValue(const QString &val); +}; + +inline Ip6Edit::Ip6Edit(QWidget *parent) + : QLineEdit(parent) +{ + setValidator(new IPv6AddressValidator(this)); +} + +inline UInt128 Ip6Edit::value() +{ + Q_IPV6ADDR addr = QHostAddress(text()).toIPv6Address(); + return UInt128((quint8*)&addr); +} + +inline quint64 Ip6Edit::valueHi64() +{ + return value().hi64(); +} + +inline quint64 Ip6Edit::valueLo64() +{ + return value().lo64(); +} + +inline void Ip6Edit::setValue(UInt128 val) +{ + setText(QHostAddress(val.toArray()).toString()); +} + +inline void Ip6Edit::setValue(quint64 hi, quint64 lo) +{ + UInt128 ip(hi, lo); + setValue(ip); +} + +inline void Ip6Edit::setValue(const QString &val) +{ + setText(QHostAddress(val).toString()); +} +#endif diff --git a/common/uint128.h b/common/uint128.h index 7bbe89c..d39e133 100644 --- a/common/uint128.h +++ b/common/uint128.h @@ -43,6 +43,7 @@ public: UInt128 operator<<(const int &shift) const; UInt128 operator~() const; UInt128 operator&(const UInt128 &other) const; + UInt128 operator|(const UInt128 &other) const; private: quint64 hi_; @@ -151,6 +152,11 @@ inline UInt128 UInt128::operator&(const UInt128 &other) const return UInt128(hi_ & other.hi_, lo_ & other.lo_); } +inline UInt128 UInt128::operator|(const UInt128 &other) const +{ + return UInt128(hi_ | other.hi_, lo_ | other.lo_); +} + template <> inline UInt128 qFromBigEndian(const uchar *src) { quint64 hi, lo; From a5fd26792eb97ff353523795b6d6b7a6f0dfc5f2 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 2 Mar 2016 21:10:29 +0530 Subject: [PATCH 055/121] Device Emulation (contd.): DeviceGroupDialog - populate/load/store vlan table, update totalVlanCount and totalDeviceCount as other fields affecting these change --- client/devicegroupdialog.cpp | 112 ++++++++++++++++++++++++++++++----- client/devicegroupdialog.h | 5 +- client/devicegroupdialog.ui | 38 +----------- 3 files changed, 100 insertions(+), 55 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index cf8a9c3..59215f0 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -27,6 +27,11 @@ along with this program. If not, see #include #include +enum { kVlanId, kVlanCount, kVlanStep, kVlanCfi, kVlanPrio, kVlanTpid, + kVlanColumns }; +static QStringList vlanTableColumnHeaders = QStringList() + << "Vlan Id" << "Count" << "Step" << "CFI/DE" << "Prio" << "TPID"; + enum { kIpNone, kIp4, kIp6, kIpDual }; static QStringList ipStackItems = QStringList() << "None" << "IPv4" << "IPv6" << "Dual"; @@ -63,10 +68,38 @@ DeviceGroupDialog::DeviceGroupDialog( qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); #endif + // Populate the Vlan Table with placeholders - we do this so that + // user entered values are retained during the lifetime of the dialog + // even if user is playing around with number of vlan tags + // TODO: use spinbox delegate with rangecheck for validation + vlans->setRowCount(kMaxVlanTags); + vlans->setColumnCount(kVlanColumns); + vlans->setHorizontalHeaderLabels(vlanTableColumnHeaders); + for (int i = 0; i < kMaxVlanTags; i++) { + vlans->setItem(i, kVlanId, + new QTableWidgetItem(QString::number(100*(i+1)))); + vlans->setItem(i, kVlanCount, + new QTableWidgetItem(QString::number(10))); + vlans->setItem(i, kVlanStep, + new QTableWidgetItem(QString::number(1))); + vlans->setItem(i, kVlanCfi, + new QTableWidgetItem(QString::number(0))); + vlans->setItem(i, kVlanPrio, + new QTableWidgetItem(QString::number(0))); + vlans->setItem(i, kVlanTpid, + new QTableWidgetItem(QString::number(0x8100, 16))); + } + // Set vlan tag count *after* adding items so connected slots + // can access the items + vlanTagCount->setValue(kMaxVlanTags); + ipStack->insertItems(0, ipStackItems); layout()->setSizeConstraint(QLayout::SetFixedSize); + connect(devicePerVlanCount, SIGNAL(valueChanged(const QString&)), + this, SLOT(updateTotalDeviceCount())); + connect(ip4Address, SIGNAL(textEdited(const QString&)), this, SLOT(updateIp4Gateway())); connect(ip4PrefixLength, SIGNAL(valueChanged(const QString&)), @@ -92,7 +125,22 @@ void DeviceGroupDialog::on_vlanTagCount_valueChanged(int value) { Q_ASSERT((value >= 0) && (value <= kMaxVlanTags)); + for (int row = 0; row < kMaxVlanTags; row++) + vlans->setRowHidden(row, row >= value); + vlans->setVisible(value > 0); + updateTotalVlanCount(); +} + +void DeviceGroupDialog::on_vlans_cellChanged(int row, int col) +{ + if (col != kVlanCount) + return; + + if (vlans->isRowHidden(row)) + return; + + updateTotalVlanCount(); } void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index) @@ -120,6 +168,23 @@ void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index) } } +void DeviceGroupDialog::updateTotalVlanCount() +{ + int count = vlanTagCount->value() ? 1 : 0; + for (int i = 0; i < vlanTagCount->value(); i++) + count *= vlans->item(i, kVlanCount)->text().toUInt(); + vlanCount->setText(QString::number(count)); + + updateTotalDeviceCount(); +} + +void DeviceGroupDialog::updateTotalDeviceCount() +{ + totalDeviceCount->setText(QString::number( + qMax(vlanCount->text().toInt(), 1) + * devicePerVlanCount->value())); +} + void DeviceGroupDialog::updateIp4Gateway() { quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value())); @@ -137,23 +202,32 @@ void DeviceGroupDialog::loadDeviceGroup() { OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); int tagCount = 0; - int totalVlans; Q_ASSERT(devGrp); name->setText(QString::fromStdString(devGrp->core().name())); - if (devGrp->has_encap() && devGrp->encap().HasExtension(OstEmul::vlan)) - tagCount = devGrp->encap().GetExtension(OstEmul::vlan).stack_size(); + if (devGrp->has_encap() && devGrp->encap().HasExtension(OstEmul::vlan)) { + OstEmul::VlanEmulation vlan = devGrp->encap() + .GetExtension(OstEmul::vlan); + tagCount = vlan.stack_size(); + for (int i = 0; i < tagCount; i++) { + OstEmul::VlanEmulation::Vlan v = vlan.stack(i); + vlans->item(i, kVlanPrio)->setText( + QString::number((v.vlan_tag() >> 13) & 0x7)); + vlans->item(i, kVlanCfi)->setText( + QString::number((v.vlan_tag() >> 12) & 0x1)); + vlans->item(i, kVlanId)->setText( + QString::number(v.vlan_tag() & 0x0fff)); + vlans->item(i, kVlanCount)->setText(QString::number(v.count())); + vlans->item(i, kVlanStep)->setText(QString::number(v.step())); + vlans->item(i, kVlanTpid)->setText(QString::number(v.tpid(), 16)); + } + } vlanTagCount->setValue(tagCount); - // FIXME: vlan table widget - - totalVlans = totalVlanCount(); - vlanCount->setText(QString::number(totalVlans)); + updateTotalVlanCount(); devicePerVlanCount->setValue(devGrp->device_count()); - totalDeviceCount->setText( - QString::number(totalVlans * devGrp->device_count())); OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac); if (!mac.has_address()) { @@ -212,8 +286,20 @@ void DeviceGroupDialog::storeDeviceGroup() devGrp->mutable_core()->set_name(name->text().toStdString()); + OstEmul::VlanEmulation *vlan = devGrp->mutable_encap() + ->MutableExtension(OstEmul::vlan); + vlan->clear_stack(); tagCount = vlanTagCount->value(); - // FIXME: vlan table widget + for (int i = 0; i < tagCount; i++) { + OstEmul::VlanEmulation::Vlan *v = vlan->add_stack(); + v->set_vlan_tag( + vlans->item(i, kVlanPrio)->text().toUInt() << 13 + | vlans->item(i, kVlanCfi)->text().toUInt() << 12 + | vlans->item(i, kVlanId)->text().toUInt()); + v->set_count(vlans->item(i, kVlanCount)->text().toUInt()); + v->set_step(vlans->item(i, kVlanStep)->text().toUInt()); + v->set_tpid(vlans->item(i, kVlanTpid)->text().toUInt(NULL, 16)); + } devGrp->set_device_count(devicePerVlanCount->value()); @@ -250,9 +336,3 @@ void DeviceGroupDialog::storeDeviceGroup() devGrp->ClearExtension(OstEmul::ip6); } } - -int DeviceGroupDialog::totalVlanCount() -{ - // FIXME - return 1; -} diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h index a581db2..41f2358 100644 --- a/client/devicegroupdialog.h +++ b/client/devicegroupdialog.h @@ -36,16 +36,17 @@ public: virtual void accept(); private slots: void on_vlanTagCount_valueChanged(int value); + void on_vlans_cellChanged(int row, int col); void on_ipStack_currentIndexChanged(int index); + void updateTotalVlanCount(); + void updateTotalDeviceCount(); void updateIp4Gateway(); void updateIp6Gateway(); void loadDeviceGroup(); void storeDeviceGroup(); private: - int totalVlanCount(); - static const int kMaxVlanTags = 4; Port *port_; diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index cd4fd22..bd22c7e 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -51,43 +51,7 @@
- - - - # - - - - - Vlan Id - - - - - Count - - - - - Step - - - - - CFI/DE - - - - - Prio - - - - - TPID - - - + From 2d8510cd3aee5e6b61b5d602f79a1477c68ae9f9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 2 Mar 2016 21:41:56 +0530 Subject: [PATCH 056/121] Device Emulation (contd.): Fix build break due to case typo --- common/ip6edit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/ip6edit.h b/common/ip6edit.h index d1c2e81..891461b 100644 --- a/common/ip6edit.h +++ b/common/ip6edit.h @@ -21,7 +21,7 @@ along with this program. If not, see #define _IP6_EDIT_H #include "ipv6addressvalidator.h" -#include "UInt128.h" +#include "uint128.h" #include #include From 0ef0c6cfc0f647ad2751a1ebfc32a9cf56cb6516 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 2 Mar 2016 22:14:14 +0530 Subject: [PATCH 057/121] Device Emulation (contd.): Added a IntEdit class and used in DeviceGroup Dialog --- client/devicegroupdialog.cpp | 7 +++--- client/devicegroupdialog.ui | 9 ++++++-- common/intedit.h | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 common/intedit.h diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 59215f0..a978595 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -173,16 +173,15 @@ void DeviceGroupDialog::updateTotalVlanCount() int count = vlanTagCount->value() ? 1 : 0; for (int i = 0; i < vlanTagCount->value(); i++) count *= vlans->item(i, kVlanCount)->text().toUInt(); - vlanCount->setText(QString::number(count)); + vlanCount->setValue(count); updateTotalDeviceCount(); } void DeviceGroupDialog::updateTotalDeviceCount() { - totalDeviceCount->setText(QString::number( - qMax(vlanCount->text().toInt(), 1) - * devicePerVlanCount->value())); + totalDeviceCount->setValue(qMax(vlanCount->value(), 1) + * devicePerVlanCount->value()); } void DeviceGroupDialog::updateIp4Gateway() diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index bd22c7e..c04dcf0 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -63,7 +63,7 @@ - + false @@ -90,7 +90,7 @@ - + false @@ -321,6 +321,11 @@ QLineEdit
ip6edit.h
+ + IntEdit + QSpinBox +
intedit.h
+
name diff --git a/common/intedit.h b/common/intedit.h new file mode 100644 index 0000000..e5392e8 --- /dev/null +++ b/common/intedit.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _INT_EDIT_H +#define _INT_EDIT_H + +#include + +#include + +class IntEdit: public QSpinBox +{ +public: + IntEdit(QWidget *parent = 0); +}; + +inline IntEdit::IntEdit(QWidget *parent) + : QSpinBox(parent) +{ + setRange(INT_MIN, INT_MAX); + setButtonSymbols(QAbstractSpinBox::NoButtons); +} + +#endif + From 98c8149fbf29d8266a4300fb94ad618c2e995410 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 2 Mar 2016 22:32:21 +0530 Subject: [PATCH 058/121] Device Emulation (contd.): Added SpinBox Delegate (Qt Example) - unmodified code --- common/spinboxdelegate.cpp | 92 ++++++++++++++++++++++++++++++++++++++ common/spinboxdelegate.h | 69 ++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 common/spinboxdelegate.cpp create mode 100644 common/spinboxdelegate.h diff --git a/common/spinboxdelegate.cpp b/common/spinboxdelegate.cpp new file mode 100644 index 0000000..c1b2e33 --- /dev/null +++ b/common/spinboxdelegate.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + delegate.cpp + + A delegate that allows the user to change integer values from the model + using a spin box widget. +*/ + +#include + +#include "delegate.h" + +SpinBoxDelegate::SpinBoxDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +QWidget *SpinBoxDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &/* option */, + const QModelIndex &/* index */) const +{ + QSpinBox *editor = new QSpinBox(parent); + editor->setMinimum(0); + editor->setMaximum(100); + + return editor; +} + +void SpinBoxDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + int value = index.model()->data(index, Qt::EditRole).toInt(); + + QSpinBox *spinBox = static_cast(editor); + spinBox->setValue(value); +} + +void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + QSpinBox *spinBox = static_cast(editor); + spinBox->interpretText(); + int value = spinBox->value(); + + model->setData(index, value, Qt::EditRole); +} + +void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + diff --git a/common/spinboxdelegate.h b/common/spinboxdelegate.h new file mode 100644 index 0000000..008bc74 --- /dev/null +++ b/common/spinboxdelegate.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DELEGATE_H +#define DELEGATE_H + +#include +#include +#include +#include +#include + +class SpinBoxDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + SpinBoxDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +#endif + From 586a1773327b7d2b61a30e7b561a6e54667fa524 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 3 Mar 2016 18:23:25 +0530 Subject: [PATCH 059/121] Device Emulation (contd.): Change SpinBoxDelegate for our needs and relicense under GPLv3; use SpinBoxDelegate for the Vlan Table validation --- client/devicegroupdialog.cpp | 14 ++++- common/ostprotogui.pro | 4 +- common/spinboxdelegate.cpp | 118 ++++++++++++++++++++--------------- common/spinboxdelegate.h | 106 +++++++++++++++++++------------ 4 files changed, 150 insertions(+), 92 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index a978595..c77a6b4 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "devicegroupdialog.h" #include "port.h" +#include "spinboxdelegate.h" #include "emulproto.pb.h" #include "uint128.h" @@ -71,7 +72,6 @@ DeviceGroupDialog::DeviceGroupDialog( // Populate the Vlan Table with placeholders - we do this so that // user entered values are retained during the lifetime of the dialog // even if user is playing around with number of vlan tags - // TODO: use spinbox delegate with rangecheck for validation vlans->setRowCount(kMaxVlanTags); vlans->setColumnCount(kVlanColumns); vlans->setHorizontalHeaderLabels(vlanTableColumnHeaders); @@ -89,6 +89,18 @@ DeviceGroupDialog::DeviceGroupDialog( vlans->setItem(i, kVlanTpid, new QTableWidgetItem(QString::number(0x8100, 16))); } + + // Set SpinBoxDelegate for all columns except TPID + SpinBoxDelegate *spd = new SpinBoxDelegate(this); + spd->setColumnRange(kVlanId, 0, 4095); + spd->setColumnRange(kVlanStep, 0, 4095); + spd->setColumnRange(kVlanCfi, 0, 1); + spd->setColumnRange(kVlanPrio, 0, 7); + for (int i = 0; i < kVlanColumns; i++) { + if (i != kVlanTpid) + vlans->setItemDelegateForColumn(i, spd); + } + // Set vlan tag count *after* adding items so connected slots // can access the items vlanTagCount->setValue(kMaxVlanTags); diff --git a/common/ostprotogui.pro b/common/ostprotogui.pro index a91beea..b39526e 100644 --- a/common/ostprotogui.pro +++ b/common/ostprotogui.pro @@ -44,7 +44,8 @@ HEADERS = \ pythonfileformat.h \ pdmlprotocol.h \ pdmlprotocols.h \ - pdmlreader.h + pdmlreader.h \ + spinboxdelegate.h HEADERS += \ abstractprotocolconfig.h \ @@ -87,6 +88,7 @@ SOURCES += \ pdmlprotocol.cpp \ pdmlprotocols.cpp \ pdmlreader.cpp \ + spinboxdelegate.cpp SOURCES += \ protocolwidgetfactory.cpp \ diff --git a/common/spinboxdelegate.cpp b/common/spinboxdelegate.cpp index c1b2e33..d0376c2 100644 --- a/common/spinboxdelegate.cpp +++ b/common/spinboxdelegate.cpp @@ -1,53 +1,66 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - /* - delegate.cpp +Copyright (C) 2016 Srivats P. - A delegate that allows the user to change integer values from the model - using a spin box widget. +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 + +This file incorporates work covered by the following copyright and +permission notice: + + Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + This file is part of the examples of the Qt Toolkit. + + $QT_BEGIN_LICENSE:BSD$ + You may use this file under the terms of the BSD license as follows: + + "Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + Neither the name of The Qt Company Ltd nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + $QT_END_LICENSE$ */ -#include +//#include -#include "delegate.h" +#include "spinboxdelegate.h" + +#include SpinBoxDelegate::SpinBoxDelegate(QObject *parent) : QItemDelegate(parent) @@ -56,15 +69,23 @@ SpinBoxDelegate::SpinBoxDelegate(QObject *parent) QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, - const QModelIndex &/* index */) const + const QModelIndex &index) const { QSpinBox *editor = new QSpinBox(parent); - editor->setMinimum(0); - editor->setMaximum(100); + editor->setMinimum(colMin_.contains(index.column()) ? + colMin_.value(index.column()) : 0); + editor->setMaximum(colMax_.contains(index.column()) ? + colMax_.value(index.column()) : INT_MAX); return editor; } +void SpinBoxDelegate::setColumnRange(int col, int min, int max) +{ + colMin_.insert(col, min); + colMax_.insert(col, max); +} + void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { @@ -89,4 +110,3 @@ void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, { editor->setGeometry(option.rect); } - diff --git a/common/spinboxdelegate.h b/common/spinboxdelegate.h index 008bc74..a253623 100644 --- a/common/spinboxdelegate.h +++ b/common/spinboxdelegate.h @@ -1,46 +1,65 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +/* +Copyright (C) 2016 Srivats P. -#ifndef DELEGATE_H -#define DELEGATE_H +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 + +This file incorporates work covered by the following copyright and +permission notice: + + Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + This file is part of the examples of the Qt Toolkit. + + $QT_BEGIN_LICENSE:BSD$ + You may use this file under the terms of the BSD license as follows: + + "Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + Neither the name of The Qt Company Ltd nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + $QT_END_LICENSE$ +*/ + +#ifndef _SPIN_BOX_DELEGATE_H +#define _SPIN_BOX_DELEGATE_H + +#include #include #include #include @@ -54,6 +73,8 @@ class SpinBoxDelegate : public QItemDelegate public: SpinBoxDelegate(QObject *parent = 0); + void setColumnRange(int col, int min, int max); + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; @@ -63,6 +84,9 @@ public: void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; +private: + QHash colMin_; + QHash colMax_; }; #endif From c2df526aaf9b054d411de1a99ec9c9d5b13916f9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 3 Mar 2016 20:07:10 +0530 Subject: [PATCH 060/121] Device Emulation (contd.): DeviceGroup Dialog -fit/stretch the rows/cols of vlan Table plus minor stuff --- client/devicegroupdialog.cpp | 19 ++++++++----------- client/devicegroupdialog.ui | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index c77a6b4..ad3a13f 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -61,14 +61,6 @@ DeviceGroupDialog::DeviceGroupDialog( // Setup the Dialog setupUi(this); vlanTagCount->setRange(0, kMaxVlanTags); -#if 0 // FIXME: not working - qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); - qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); - vlans->verticalHeader()->setDefaultSectionSize( - vlans->verticalHeader()->minimumSectionSize()); - qDebug("vlan def size: %d", vlans->verticalHeader()->defaultSectionSize()); - qDebug("vlan min size: %d", vlans->verticalHeader()->minimumSectionSize()); -#endif // Populate the Vlan Table with placeholders - we do this so that // user entered values are retained during the lifetime of the dialog // even if user is playing around with number of vlan tags @@ -87,7 +79,7 @@ DeviceGroupDialog::DeviceGroupDialog( vlans->setItem(i, kVlanPrio, new QTableWidgetItem(QString::number(0))); vlans->setItem(i, kVlanTpid, - new QTableWidgetItem(QString::number(0x8100, 16))); + new QTableWidgetItem(QString("0x8100"))); } // Set SpinBoxDelegate for all columns except TPID @@ -101,12 +93,16 @@ DeviceGroupDialog::DeviceGroupDialog( vlans->setItemDelegateForColumn(i, spd); } - // Set vlan tag count *after* adding items so connected slots + vlans->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + vlans->resizeRowsToContents(); + + // Set vlan tag count *after* adding all items, so connected slots // can access the items vlanTagCount->setValue(kMaxVlanTags); ipStack->insertItems(0, ipStackItems); + // setup dialog to auto-resize as widgets are hidden or shown layout()->setSizeConstraint(QLayout::SetFixedSize); connect(devicePerVlanCount, SIGNAL(valueChanged(const QString&)), @@ -232,7 +228,8 @@ void DeviceGroupDialog::loadDeviceGroup() QString::number(v.vlan_tag() & 0x0fff)); vlans->item(i, kVlanCount)->setText(QString::number(v.count())); vlans->item(i, kVlanStep)->setText(QString::number(v.step())); - vlans->item(i, kVlanTpid)->setText(QString::number(v.tpid(), 16)); + vlans->item(i, kVlanTpid)->setText(QString("0x%1") + .arg(v.tpid(), 0, 16)); } } vlanTagCount->setValue(tagCount); diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui index c04dcf0..cd9bf87 100644 --- a/client/devicegroupdialog.ui +++ b/client/devicegroupdialog.ui @@ -10,7 +10,7 @@ - Dialog + Devices From f38567d33f44e43d99bfa08652296475c9bc0ff7 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 3 Mar 2016 20:52:22 +0530 Subject: [PATCH 061/121] Device Emulation (contd.): Hide/identify nice-to-have TODOs for DeviceGroup Dialog and call it done for now - phew! --- client/devicegroupdialog.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index ad3a13f..ac8158f 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -60,7 +60,9 @@ DeviceGroupDialog::DeviceGroupDialog( { // Setup the Dialog setupUi(this); + vlanTagCount->setRange(0, kMaxVlanTags); + // Populate the Vlan Table with placeholders - we do this so that // user entered values are retained during the lifetime of the dialog // even if user is playing around with number of vlan tags @@ -102,6 +104,13 @@ DeviceGroupDialog::DeviceGroupDialog( ipStack->insertItems(0, ipStackItems); + // TODO: DeviceGroup Traversal; hide buttons for now + prev->setHidden(true); + next->setHidden(true); + + // TODO: Preview devices expanded from deviceGroup configuration + // for user convenience + // setup dialog to auto-resize as widgets are hidden or shown layout()->setSizeConstraint(QLayout::SetFixedSize); From 73485fb3bc1ab4bf0beebbc5cfb4bcabe5070d5b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 4 Mar 2016 18:41:36 +0530 Subject: [PATCH 062/121] Device Emulation (contd.): UI changes so that the 'Apply' button is seen to be common for both streams and devices; provisional UI for device information --- client/portswindow.cpp | 9 ++ client/portswindow.h | 1 + client/portswindow.ui | 215 ++++++++++++++++++++++++----------------- 3 files changed, 139 insertions(+), 86 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 4a669d6..021735b 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -45,6 +45,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) plm = pgl; setupUi(this); + refresh->setVisible(deviceInfo->isChecked()); tvPortList->header()->hide(); @@ -890,6 +891,14 @@ _exit: // // DeviceGroup slots // + +void PortsWindow::on_deviceInfo_toggled(bool checked) +{ + refresh->setVisible(checked); + + // TODO: toggle between deviceGroup config and deviceInfo +} + void PortsWindow::on_actionNewDeviceGroup_triggered() { // In case nothing is selected, insert 1 row at the top diff --git a/client/portswindow.h b/client/portswindow.h index ca6f210..cbdaed8 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -82,6 +82,7 @@ private slots: void streamModelDataChanged(); + void on_deviceInfo_toggled(bool checked); void on_actionNewDeviceGroup_triggered(); void on_actionDeleteDeviceGroup_triggered(); void on_actionEditDeviceGroup_triggered(); diff --git a/client/portswindow.ui b/client/portswindow.ui index 3e7b15d..28efa35 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -34,7 +34,7 @@ 0 - + 0 @@ -47,7 +47,51 @@ 0 - + + + + QFrame::Panel + + + QFrame::Raised + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply + + + + + + + 0 @@ -60,86 +104,44 @@ - - - - 0 - 0 - + + + Avg pps - - QFrame::StyledPanel + + true - - QFrame::Sunken - - - - - - Avg pps - - - true - - - - - - - - - - Avg bps - - - - - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Apply - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - + + + + + + Avg bps + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + @@ -174,7 +176,48 @@ Devices - + + + + + + + Configuration + + + true + + + + + + + Information + + + + + + + Qt::Horizontal + + + + 131 + 23 + + + + + + + + Refresh + + + + + @@ -339,12 +382,12 @@ setEnabled(bool) - 313 - 28 + 326 + 80 - 380 - 28 + 454 + 79 @@ -355,12 +398,12 @@ setEnabled(bool) - 333 - 55 + 523 + 80 - 395 - 56 + 651 + 88 From c75e369840db7bd55daf3e1594dd96f70d1b18e1 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 4 Mar 2016 18:46:59 +0530 Subject: [PATCH 063/121] Device Emulation (contd.): minor stuff --- client/devicegroupdialog.cpp | 3 +++ client/devicegroupmodel.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index ac8158f..4f25a48 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -105,6 +105,9 @@ DeviceGroupDialog::DeviceGroupDialog( ipStack->insertItems(0, ipStackItems); // TODO: DeviceGroup Traversal; hide buttons for now + // NOTE for implementation: Use a QHash + // to store modified values while traversing; in accept() + // update port->deviceGroups[] from the QHash prev->setHidden(true); next->setHidden(true); diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index 4bbdf36..16526dc 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -35,7 +35,7 @@ static QStringList columns_ = QStringList() << "Name" << "Vlans" << "Devices" - << "IP"; + << "IP Stack"; DeviceGroupModel::DeviceGroupModel(QObject *parent) : QAbstractTableModel(parent) From bb69e644b12e6b71da49242ef223d03e8bef4ab6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 4 Mar 2016 18:53:08 +0530 Subject: [PATCH 064/121] Device Emulation (contd.): remove model from deviceGroupList if deviceInfo is selected - until deviceInfo model is implemented --- client/portswindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 021735b..e8670e1 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -896,7 +896,8 @@ void PortsWindow::on_deviceInfo_toggled(bool checked) { refresh->setVisible(checked); - // TODO: toggle between deviceGroup config and deviceInfo + // TODO: deviceInfo + deviceGroupList->setModel(checked ? NULL : plm->getDeviceGroupModel()); } void PortsWindow::on_actionNewDeviceGroup_triggered() From 0ccb3e2fbdcb74812a04401df6c9a1df27727e1e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 5 Mar 2016 04:06:36 +0530 Subject: [PATCH 065/121] Device Emulation (contd.): Apply for deviceGroups implemented. This code has revealed bugs in other parts of the code which will be fixed in subsequent commits --- client/port.cpp | 38 ++++++++++++++++++++++++ client/port.h | 8 +++++ client/portgroup.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++-- client/portgroup.h | 4 +++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 6445abb..afb59c8 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -409,6 +409,38 @@ void Port::getModifiedStreamsSinceLastSync( qDebug("Done %s", __FUNCTION__); } +void Port::getDeletedDeviceGroupsSinceLastSync( + OstProto::DeviceGroupIdList &deviceGroupIdList) +{ + deviceGroupIdList.clear_device_group_id(); + foreach(int id, lastSyncDeviceGroupList_) { + if (!deviceGroupById(id)) + deviceGroupIdList.add_device_group_id()->set_id(id); + } +} + +void Port::getNewDeviceGroupsSinceLastSync( + OstProto::DeviceGroupIdList &deviceGroupIdList) +{ + deviceGroupIdList.clear_device_group_id(); + foreach(OstProto::DeviceGroup *dg, deviceGroups_) { + quint32 dgid = dg->device_group_id().id(); + if (!lastSyncDeviceGroupList_.contains(dgid)) + deviceGroupIdList.add_device_group_id()->set_id(dgid); + } +} + +void Port::getModifiedDeviceGroupsSinceLastSync( + OstProto::DeviceGroupConfigList &deviceGroupConfigList) +{ + // FIXME: we currently don't have any mechanism to check + // if a DeviceGroup was modified since last sync, so we + // include all DeviceGroups + deviceGroupConfigList.clear_device_group(); + foreach(OstProto::DeviceGroup *dg, deviceGroups_) + deviceGroupConfigList.add_device_group()->CopyFrom(*dg); +} + void Port::when_syncComplete() { //reorderStreamsByOrdinals(); @@ -416,6 +448,12 @@ void Port::when_syncComplete() mLastSyncStreamList.clear(); for (int i=0; iid()); + + lastSyncDeviceGroupList_.clear(); + for (int i = 0; i < deviceGroups_.size(); i++) { + lastSyncDeviceGroupList_.append( + deviceGroups_.at(i)->device_group_id().id()); + } } void Port::updateStats(OstProto::PortStats *portStats) diff --git a/client/port.h b/client/port.h index 430a171..f3092cf 100644 --- a/client/port.h +++ b/client/port.h @@ -52,6 +52,7 @@ class Port : public QObject { QList mLastSyncStreamList; QList mStreams; // sorted by stream's ordinal value + QList lastSyncDeviceGroupList_; QList deviceGroups_; uint newStreamId(); @@ -134,6 +135,13 @@ public: void getModifiedStreamsSinceLastSync( OstProto::StreamConfigList &streamConfigList); + void getDeletedDeviceGroupsSinceLastSync( + OstProto::DeviceGroupIdList &streamIdList); + void getNewDeviceGroupsSinceLastSync( + OstProto::DeviceGroupIdList &streamIdList); + void getModifiedDeviceGroupsSinceLastSync( + OstProto::DeviceGroupConfigList &streamConfigList); + void when_syncComplete(); void setAveragePacketRate(double packetsPerSec); diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 362cff4..fb8b247 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -389,6 +389,13 @@ void PortGroup::when_configApply(int portIndex) QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); mainWindow->setDisabled(true); + // FIXME: as currently written this code will make unnecessary RPCs + // even if the request contains no data; the fix will need to take + // care to identify when sync is complete + + // + // Update/Sync Streams + // qDebug("applying 'deleted streams' ..."); streamIdList = new OstProto::StreamIdList; ack = new OstProto::Ack; @@ -422,6 +429,47 @@ void PortGroup::when_configApply(int portIndex) serviceStub->modifyStream(controller, streamConfigList, ack, NewCallback(this, &PortGroup::processModifyStreamAck, portIndex, controller)); + + // + // Update/Sync DeviceGroups + // + OstProto::DeviceGroupIdList *deviceGroupIdList; + OstProto::DeviceGroupConfigList *deviceGroupConfigList; + + qDebug("applying 'deleted deviceGroups' ..."); + deviceGroupIdList = new OstProto::DeviceGroupIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupIdList, ack); + + deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getDeletedDeviceGroupsSinceLastSync(*deviceGroupIdList); + + serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack, + NewCallback(this, &PortGroup::processDeleteDeviceGroupAck, controller)); + + qDebug("applying 'new deviceGroups' ..."); + deviceGroupIdList = new OstProto::DeviceGroupIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupIdList, ack); + + deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getNewDeviceGroupsSinceLastSync(*deviceGroupIdList); + + serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack, + NewCallback(this, &PortGroup::processAddDeviceGroupAck, controller)); + + qDebug("applying 'modified deviceGroups' ..."); + deviceGroupConfigList = new OstProto::DeviceGroupConfigList; + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupConfigList, ack); + + deviceGroupConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getModifiedDeviceGroupsSinceLastSync( + *deviceGroupConfigList); + + serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack, + NewCallback(this, &PortGroup::processModifyDeviceGroupAck, + portIndex, controller)); } void PortGroup::processAddStreamAck(PbRpcController *controller) @@ -440,6 +488,25 @@ void PortGroup::processModifyStreamAck(int portIndex, PbRpcController *controller) { qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processAddDeviceGroupAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processDeleteDeviceGroupAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processModifyDeviceGroupAck(int portIndex, + PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); qDebug("apply completed"); mPorts[portIndex]->when_syncComplete(); @@ -563,8 +630,6 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) mPorts[portIndex]->insertStream(streamId); } - mPorts[portIndex]->when_syncComplete(); - // Are we done for all ports? if (numPorts() && portIndex >= (numPorts()-1)) { @@ -706,7 +771,7 @@ void PortGroup::processDeviceGroupIdList( mPorts[portIndex]->insertDeviceGroup(devGrpId); } - //FIXME: mPorts[portIndex]->when_syncComplete(); + mPorts[portIndex]->when_syncComplete(); // Are we done for all ports? if (numPorts() && portIndex >= (numPorts()-1)) diff --git a/client/portgroup.h b/client/portgroup.h index b5ffa88..632e9c6 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -106,6 +106,10 @@ public: void processDeleteStreamAck(PbRpcController *controller); void processModifyStreamAck(int portIndex, PbRpcController *controller); + void processAddDeviceGroupAck(PbRpcController *controller); + void processDeleteDeviceGroupAck(PbRpcController *controller); + void processModifyDeviceGroupAck(int portIndex, PbRpcController *controller); + void modifyPort(int portId, OstProto::Port portConfig); void processModifyPortAck(PbRpcController *controller); void processUpdatedPortConfig(PbRpcController *controller); From 11e9d0f6ef6dc609d026a3beb3f3f0642af0c120 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 5 Mar 2016 04:31:29 +0530 Subject: [PATCH 066/121] Device Emulation (contd.): fix deviceGroupId alloc bug --- client/port.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/client/port.cpp b/client/port.cpp index afb59c8..0ce1502 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -37,6 +37,15 @@ static const int kEthOverhead = 20; uint Port::newStreamId() { + // We use a monotonically increasing class variable to generate + // unique id. To ensure that we take into account the already + // existing ids at drone, we update this variable to be greater + // than the last id that we fetched + // FIXME: In some cases e.g. wrap around or more likely multiple + // clients, we will still run into trouble - to fix this we should + // check here that the same id does not already exist; but currently + // we can only do a linear search - fix this when the lookup/search + // is optimized return mAllocStreamId++; } @@ -649,6 +658,15 @@ _exit: uint Port::newDeviceGroupId() { + // We use a monotonically increasing class variable to generate + // unique id. To ensure that we take into account the already + // existing ids at drone, we update this variable to be greater + // than the last id that we fetched + // FIXME: In some cases e.g. wrap around or more likely multiple + // clients, we will still run into trouble - to fix this we should + // check here that the same id does not already exist; but currently + // we can only do a linear search - fix this when the lookup/search + // is optimized return allocDeviceGroupId_++; } @@ -723,6 +741,12 @@ bool Port::insertDeviceGroup(uint deviceGroupId) devGrp = new OstProto::DeviceGroup; devGrp->mutable_device_group_id()->set_id(deviceGroupId); deviceGroups_.append(devGrp); + + // Update allocDeviceGroupId_ to take into account the deviceGroup id + // received from server - this is required to make sure newly allocated + // deviceGroup ids are unique + if (allocDeviceGroupId_ <= deviceGroupId) + allocDeviceGroupId_ = deviceGroupId + 1; return true; } From 600bdc1946b051010a53b06f33d42d6161993922 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 7 Mar 2016 18:42:14 +0530 Subject: [PATCH 067/121] Device Emulation (contd.): Add check for duplicate/non-existing devices when deviceManager enumerates devices --- server/devicemanager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 8ace3e3..abfa083 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -467,6 +467,11 @@ void DeviceManager::enumerateDevices( switch (oper) { case kAdd: + if (deviceList_.contains(dk.key())) { + qWarning("%s: error adding device %s (EEXIST)", + __FUNCTION__, qPrintable(dk.config())); + break; + } device = new Device(this); *device = dk; deviceList_.insert(dk.key(), device); @@ -478,6 +483,11 @@ void DeviceManager::enumerateDevices( case kDelete: device = deviceList_.take(dk.key()); + if (!device) { + qWarning("%s: error deleting device %s (NOTFOUND)", + __FUNCTION__, qPrintable(dk.config())); + break; + } qDebug("enumerate(del): %s", qPrintable(device->config())); delete device; From db8ad92738b1c6aeb9854819f504a3f76ea9ce53 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 7 Mar 2016 21:40:48 +0530 Subject: [PATCH 068/121] Device Emulation (contd.): Assign a random mac address to a DeviceGroup at alloc time to ensure unique device keys when enumerating devices from device groups --- client/devicegroupdialog.cpp | 18 ++---------- client/main.cpp | 3 ++ client/port.cpp | 5 ++-- common/emulation.h | 55 ++++++++++++++++++++++++++++++++++++ rpc/rpcconn.cpp | 2 ++ server/devicemanager.cpp | 3 +- 6 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 common/emulation.h diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 4f25a48..de0443a 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -250,22 +250,8 @@ void DeviceGroupDialog::loadDeviceGroup() devicePerVlanCount->setValue(devGrp->device_count()); OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac); - if (!mac.has_address()) { - // Mac address as per RFC 4814 Sec 4.2 - // (RR & 0xFC):PP:PP:RR:RR:RR - // where RR is a random number, PP:PP is 1-indexed port index - // NOTE: although qrand() return type is a int, the max value - // is RAND_MAX (stdlib.h) which is often 16-bit only, so we - // use two random numbers - quint32 r1 = qrand(), r2 = qrand(); - quint64 mac; - mac = quint64(r1 & 0xfc00) << 32 - | quint64(port_->id() + 1) << 24 - | quint64((r1 & 0xff) << 16 | (r2 & 0xffff)); - macAddress->setValue(mac); - } - else - macAddress->setValue(mac.address()); + Q_ASSERT(mac.has_address()); + macAddress->setValue(mac.address()); macStep->setValue(mac.step()); OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4); diff --git a/client/main.cpp b/client/main.cpp index 65864f0..34f689e 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -25,8 +25,10 @@ along with this program. If not, see #include "settings.h" #include +#include #include #include +#include #include @@ -68,6 +70,7 @@ int main(int argc, char* argv[]) appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); Preferences::initDefaults(); + qsrand(QDateTime::currentDateTime().toTime_t()); mainWindow = new MainWindow; mainWindow->show(); diff --git a/client/port.cpp b/client/port.cpp index 0ce1502..9f7de6a 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "port.h" #include "abstractfileformat.h" +#include "emulation.h" #include #include @@ -702,7 +703,7 @@ bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup) if (index < 0 || index > numDeviceGroups()) return false; - OstProto::DeviceGroup *devGrp = new OstProto::DeviceGroup; + OstProto::DeviceGroup *devGrp = newDeviceGroup(id()); if (!devGrp) { qWarning("failed allocating a new device group"); @@ -738,7 +739,7 @@ bool Port::insertDeviceGroup(uint deviceGroupId) return false; } - devGrp = new OstProto::DeviceGroup; + devGrp = newDeviceGroup(id()); devGrp->mutable_device_group_id()->set_id(deviceGroupId); deviceGroups_.append(devGrp); diff --git a/common/emulation.h b/common/emulation.h new file mode 100644 index 0000000..919849d --- /dev/null +++ b/common/emulation.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _EMULATION_H +#define _EMULATION_H + +#include "emulproto.pb.h" + +#include + +static inline OstProto::DeviceGroup* newDeviceGroup(uint portId) +{ + OstProto::DeviceGroup *devGrp = new OstProto::DeviceGroup; + + // To ensure that DeviceGroup have a unique key, we assign + // a random mac address upon creation; ideally, it would + // have been good to inherit OstProto::DeviceGroup and define + // a new constructor, but ProtoBuf forbids against inheriting + // generated classes, so we use this helper function instead + // + // Create a mac address as per RFC 4814 Sec 4.2 + // (RR & 0xFC):PP:PP:RR:RR:RR + // where RR is a random number, PP:PP is 1-indexed port index + // NOTE: although qrand() return type is a int, the max value + // is RAND_MAX (stdlib.h) which is often 16-bit only, so we + // use two random numbers + quint32 r1 = qrand(), r2 = qrand(); + quint64 mac; + mac = quint64(r1 & 0xfc00) << 32 + | quint64(portId + 1) << 24 + | quint64((r1 & 0xff) << 16 | (r2 & 0xffff)); + devGrp->MutableExtension(OstEmul::mac)->set_address(mac); + + return devGrp; +} + + +#endif + diff --git a/rpc/rpcconn.cpp b/rpc/rpcconn.cpp index bd42c3c..db1c428 100644 --- a/rpc/rpcconn.cpp +++ b/rpc/rpcconn.cpp @@ -28,6 +28,7 @@ along with this program. If not, see #include #include +#include #include #include #include @@ -82,6 +83,7 @@ void RpcConnection::start() return; } qDebug("clientSock Thread = %p", clientSock->thread()); + qsrand(QDateTime::currentDateTime().toTime_t()); connId.setLocalData(new QString(id.arg(clientSock->peerAddress().toString()) .arg(clientSock->peerPort()))); diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index abfa083..6e140f2 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -21,6 +21,7 @@ along with this program. If not, see #include "abstractport.h" #include "device.h" +#include "../common/emulation.h" #include "packetbuffer.h" #include "../common/emulproto.pb.h" @@ -93,7 +94,7 @@ bool DeviceManager::addDeviceGroup(uint deviceGroupId) return false; } - deviceGroup = new OstProto::DeviceGroup; + deviceGroup = newDeviceGroup(port_->id()); deviceGroup->mutable_device_group_id()->set_id(deviceGroupId); deviceGroupList_.insert(deviceGroupId, deviceGroup); From 065698369f336539c24fa2086ec0dc25dac025cd Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 8 Mar 2016 18:27:10 +0530 Subject: [PATCH 069/121] Device Emulation (contd.): If vlan tag count is zero, clear vlan encap --- client/devicegroupdialog.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index de0443a..9ef91fc 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -286,7 +286,7 @@ void DeviceGroupDialog::loadDeviceGroup() void DeviceGroupDialog::storeDeviceGroup() { OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); - int tagCount = 0; + int tagCount = vlanTagCount->value(); Q_ASSERT(devGrp); @@ -295,7 +295,6 @@ void DeviceGroupDialog::storeDeviceGroup() OstEmul::VlanEmulation *vlan = devGrp->mutable_encap() ->MutableExtension(OstEmul::vlan); vlan->clear_stack(); - tagCount = vlanTagCount->value(); for (int i = 0; i < tagCount; i++) { OstEmul::VlanEmulation::Vlan *v = vlan->add_stack(); v->set_vlan_tag( @@ -307,6 +306,9 @@ void DeviceGroupDialog::storeDeviceGroup() v->set_tpid(vlans->item(i, kVlanTpid)->text().toUInt(NULL, 16)); } + if (!tagCount) + devGrp->clear_encap(); + devGrp->set_device_count(devicePerVlanCount->value()); OstEmul::MacEmulation *mac = devGrp->MutableExtension(OstEmul::mac); From 10e1c5211da5b8dea127f6fabea7f27a4eb1885d Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 8 Mar 2016 18:49:28 +0530 Subject: [PATCH 070/121] Device Emulation (contd.): Display IPv4/IPv6 (base) address in DeviceGroupListView --- client/devicegroupmodel.cpp | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index 16526dc..969524b 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -22,12 +22,17 @@ along with this program. If not, see #include "port.h" #include "emulproto.pb.h" +#include "uint128.h" + +#include enum { kName, kVlanCount, kDeviceCount, // Across all vlans kIp, + kIp4Address, + kIp6Address, kFieldCount }; @@ -35,7 +40,9 @@ static QStringList columns_ = QStringList() << "Name" << "Vlans" << "Devices" - << "IP Stack"; + << "IP Stack" + << "IPv4 Address" + << "IPv6 Address"; DeviceGroupModel::DeviceGroupModel(QObject *parent) : QAbstractTableModel(parent) @@ -145,7 +152,39 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const } return QVariant(); + case kIp4Address: + switch (role) { + case Qt::DisplayRole: + if (devGrp->HasExtension(OstEmul::ip4)) + return QHostAddress( + devGrp->MutableExtension(OstEmul::ip4) + ->address()).toString(); + else + return QString("--"); + default: + break; + } + return QVariant(); + + case kIp6Address: + switch (role) { + case Qt::DisplayRole: + if (devGrp->HasExtension(OstEmul::ip6)) { + OstEmul::Ip6Address ip = devGrp->MutableExtension( + OstEmul::ip6)->address(); + return QHostAddress( + UInt128(ip.hi(), ip.lo()).toArray()) + .toString(); + } + else + return QString("--"); + default: + break; + } + return QVariant(); + default: + Q_ASSERT(false); // unreachable! break; } From e7571b202211c8d53e37b8673dba720abac5b76a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 8 Mar 2016 20:51:02 +0530 Subject: [PATCH 071/121] Device Emulation (contd.): Add 'resolve' mode for src/dst mac address for GUI configuration of stream --- common/mac.ui | 10 +++++++++ common/macconfig.cpp | 50 ++++++++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/common/mac.ui b/common/mac.ui index 821cf00..5ed2fed 100644 --- a/common/mac.ui +++ b/common/mac.ui @@ -81,6 +81,11 @@ Decrement + + + Resolve + + @@ -143,6 +148,11 @@ Decrement + + + Resolve + + diff --git a/common/macconfig.cpp b/common/macconfig.cpp index f17c140..3728b3d 100644 --- a/common/macconfig.cpp +++ b/common/macconfig.cpp @@ -46,29 +46,43 @@ MacConfigForm* MacConfigForm::createInstance() void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index) { - if (index == OstProto::Mac::e_mm_fixed) - { - leDstMacCount->setEnabled(false); - leDstMacStep->setEnabled(false); - } - else - { - leDstMacCount->setEnabled(true); - leDstMacStep->setEnabled(true); + switch (index) { + case OstProto::Mac::e_mm_resolve: + leDstMac->setEnabled(false); + leDstMacCount->setEnabled(false); + leDstMacStep->setEnabled(false); + break; + case OstProto::Mac::e_mm_fixed: + leDstMac->setEnabled(true); + leDstMacCount->setEnabled(false); + leDstMacStep->setEnabled(false); + break; + default: + leDstMac->setEnabled(true); + leDstMacCount->setEnabled(true); + leDstMacStep->setEnabled(true); + break; } } void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) { - if (index == OstProto::Mac::e_mm_fixed) - { - leSrcMacCount->setEnabled(false); - leSrcMacStep->setEnabled(false); - } - else - { - leSrcMacCount->setEnabled(true); - leSrcMacStep->setEnabled(true); + switch (index) { + case OstProto::Mac::e_mm_resolve: + leSrcMac->setEnabled(false); + leSrcMacCount->setEnabled(false); + leSrcMacStep->setEnabled(false); + break; + case OstProto::Mac::e_mm_fixed: + leSrcMac->setEnabled(true); + leSrcMacCount->setEnabled(false); + leSrcMacStep->setEnabled(false); + break; + default: + leSrcMac->setEnabled(true); + leSrcMacCount->setEnabled(true); + leSrcMacStep->setEnabled(true); + break; } } From d55f44cab24d6885a17b586054ed5daebdb3cf8e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 9 Mar 2016 21:20:11 +0530 Subject: [PATCH 072/121] Device Emulation (contd.): Add buttons (and their implementation) for resolve/clear device neighbors to the port stats window --- client/portgroup.cpp | 72 ++++++++++++++++++++++++++++++++++++++ client/portgroup.h | 5 +++ client/portstatswindow.cpp | 30 ++++++++++++++++ client/portstatswindow.h | 3 ++ client/portstatswindow.ui | 33 +++++++++++++++++ 5 files changed, 143 insertions(+) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index fb8b247..bd33c65 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -1061,6 +1061,78 @@ _exit: delete controller; } +void PortGroup::resolveDeviceNeighbors(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + return; + + if ((portList == NULL) || (portList->size() == 0)) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->resolveDeviceNeighbors(controller, portIdList, ack, + NewCallback(this, &PortGroup::processResolveDeviceNeighborsAck, + controller)); + } +_exit: + return; +} + +void PortGroup::processResolveDeviceNeighborsAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + +void PortGroup::clearDeviceNeighbors(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + return; + + if ((portList == NULL) || (portList->size() == 0)) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->clearDeviceNeighbors(controller, portIdList, ack, + NewCallback(this, &PortGroup::processClearDeviceNeighborsAck, + controller)); + } +_exit: + return; +} + +void PortGroup::processClearDeviceNeighborsAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + void PortGroup::getPortStats() { //qDebug("In %s", __FUNCTION__); diff --git a/client/portgroup.h b/client/portgroup.h index 632e9c6..c7b5c68 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -140,6 +140,11 @@ public: void viewCapture(QList *portList = NULL); void processViewCaptureAck(PbRpcController *controller); + void resolveDeviceNeighbors(QList *portList = NULL); + void processResolveDeviceNeighborsAck(PbRpcController *controller); + void clearDeviceNeighbors(QList *portList = NULL); + void processClearDeviceNeighborsAck(PbRpcController *controller); + void getPortStats(); void processPortStatsList(); void clearPortStats(QList *portList = NULL); diff --git a/client/portstatswindow.cpp b/client/portstatswindow.cpp index 3e2cb9a..0e7b73f 100644 --- a/client/portstatswindow.cpp +++ b/client/portstatswindow.cpp @@ -148,6 +148,36 @@ void PortStatsWindow::on_tbViewCapture_clicked() } } +void PortStatsWindow::on_tbResolveNeighbors_clicked() +{ + QList portList; + + // Get selected ports + model->portListFromIndex(selectedColumns(), portList); + + // Resolve ARP/ND for selected ports, portgroup by portgroup + for (int i = 0; i < portList.size(); i++) + { + pgl->portGroupByIndex(portList.at(i).portGroupId). + resolveDeviceNeighbors(&portList[i].portList); + } +} + +void PortStatsWindow::on_tbClearNeighbors_clicked() +{ + QList portList; + + // Get selected ports + model->portListFromIndex(selectedColumns(), portList); + + // Clear ARP/ND for ports, portgroup by portgroup + for (int i = 0; i < portList.size(); i++) + { + pgl->portGroupByIndex(portList.at(i).portGroupId). + clearDeviceNeighbors(&portList[i].portList); + } +} + void PortStatsWindow::on_tbClear_clicked() { QList portList; diff --git a/client/portstatswindow.h b/client/portstatswindow.h index 39a2efb..dfb5fbf 100644 --- a/client/portstatswindow.h +++ b/client/portstatswindow.h @@ -50,6 +50,9 @@ private slots: void on_tbClear_clicked(); void on_tbClearAll_clicked(); + void on_tbResolveNeighbors_clicked(); + void on_tbClearNeighbors_clicked(); + void on_tbFilter_clicked(); private: diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui index 8c6aed4..ed10bb5 100644 --- a/client/portstatswindow.ui +++ b/client/portstatswindow.ui @@ -141,6 +141,39 @@
+ + + + Resolve Neighbors + + + Resolve Device Neighbors on selected port(s) + + + Resolve Neighbors + + + + + + + Clear Neighbors + + + Clear Device Neighbors on selected port(s) + + + Clear Neighbors + + + + + + + Qt::Vertical + + + From e9759fde2643ca6444fb113e65fc3f40d946f58f Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 9 Mar 2016 21:54:01 +0530 Subject: [PATCH 073/121] Device Emulation (contd.): Add icons for resolve/clear neighbor buttons --- client/icons/neighbor_clear.png | Bin 0 -> 775 bytes client/icons/neighbor_resolve.png | Bin 0 -> 770 bytes client/ostinato.qrc | 2 ++ client/portstatswindow.ui | 6 ++++++ 4 files changed, 8 insertions(+) create mode 100644 client/icons/neighbor_clear.png create mode 100644 client/icons/neighbor_resolve.png diff --git a/client/icons/neighbor_clear.png b/client/icons/neighbor_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..d9eefc22513d4e100b3eea40d529bdc600913f88 GIT binary patch literal 775 zcmV+i1Ni)jP)5nA-%a4+}V@4MeQ=X-`y3jgzYw>wNE z5*85;QH)9{yJ+p8psh^O`jOT<6p7*CVPX8KfWSR;_8=4r^@hV?1cN~Y0s-iIg+c*q zYin3oSU@(LeL-jE{vwFSxdb>guXioAv$#n*<*5Hy`WXQQPW*6cdmY2Ttw-e0$D< zqaN8z#w|mGW3d>h51UPtLPm+;LQQZlc;O8^zCL=zMme9t5H5`|cvg>BpLcs$jf zxCG0uPt=msOa7)qbpR=MGBleFu}53-T&&w)_lJ{Jx~u`zUtWTQOqxag+{>}`{DQbNeQdIy48b9rSiL# zC6`Zp{;*Nvjc6UXh2@nJxZ_mw+E|fD1lnZjbb3?4R`T=Xp3_u9+hV@?dRh&Pb$`hO z^{g$+a)J7F4c1oHE}WT}$&jIjs!sg$u&Y-IA=}T-C#R>U_mRYF6eHB}20eG}zFaPc za=EO5_oL1E-$)@F1-w)3&R|k zpe8{Z8<~c8GL+Q$?>DkG77MrB>ib*oIq``rr5B#_9^P}_=lwfJ$Ye4I1OislR|zeK zVbqYWCeYUSM0$zz3qqi|xmm|wBKZCO<8;X*Nm5-|Ss83L8wv^vz=$_CHjqpv5e|nD zi^V>Y?WWu9Ue99i`F!tXS!UrfyNRl*nP4!OdF<_2YxlP1Fme+XT27Rfmg0ZnbUKmN zQchLT0^98(MdnP?)6=lq?HnZ~C0I!=;+^~kYsqD3nhs6X;Vdi{?VA1UEzfmtU&~-q z7JwZi{gIM{t3{_E4UR1%)L*WJg$bH{MmVw30fE`e?I2IEj?l`hAghYH3 zt$W+>w6P28pTBBza%f!nsWhE`b~-<+1YOrT3B$cZ4>?gZ8b$I)lH1swJjJS$^EqF~ zm_fejtPITPCdn3|s^RUIA%3SpXiSi^{?8N`Oh=PY(SA|m3=BsGkWx0eT~QP?b~cPr zKhDrN8M!R7Nbz_a^b+|5(&_XcW=k#xvLap&a?YNRF|tvD>5s;|fF{5;0S zC%l5k;M1&07*qoM6N<$ficons/logo.png icons/magnifier.png icons/name.png + icons/neighbor_clear.png + icons/neighbor_resolve.png icons/portgroup_add.png icons/portgroup_connect.png icons/portgroup_delete.png diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui index ed10bb5..870633e 100644 --- a/client/portstatswindow.ui +++ b/client/portstatswindow.ui @@ -152,6 +152,9 @@ Resolve Neighbors + + :/icons/neighbor_resolve.png + @@ -165,6 +168,9 @@ Clear Neighbors + + :/icons/neighbor_clear.png + From f742cdbc7a870185d277b7b115e97a8f601868df Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 10 Mar 2016 18:20:55 +0530 Subject: [PATCH 074/121] Device Emulation (contd.): Trigger Device RPCs before Stream RPCs because Drone updates its packet list at the end of modifyStream() --- client/portgroup.cpp | 113 ++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index bd33c65..9d67a3e 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -366,8 +366,8 @@ void PortGroup::processPortConfigList(PbRpcController *controller) emit portListChanged(mPortGroupId); if (numPorts() > 0) { - getStreamIdList(); getDeviceGroupIdList(); + getStreamIdList(); } _error_exit: @@ -392,43 +392,9 @@ void PortGroup::when_configApply(int portIndex) // FIXME: as currently written this code will make unnecessary RPCs // even if the request contains no data; the fix will need to take // care to identify when sync is complete - - // - // Update/Sync Streams - // - qDebug("applying 'deleted streams' ..."); - streamIdList = new OstProto::StreamIdList; - ack = new OstProto::Ack; - controller = new PbRpcController(streamIdList, ack); - - streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); - mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList); - - serviceStub->deleteStream(controller, streamIdList, ack, - NewCallback(this, &PortGroup::processDeleteStreamAck, controller)); - - qDebug("applying 'new streams' ..."); - streamIdList = new OstProto::StreamIdList; - ack = new OstProto::Ack; - controller = new PbRpcController(streamIdList, ack); - - streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); - mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList); - - serviceStub->addStream(controller, streamIdList, ack, - NewCallback(this, &PortGroup::processAddStreamAck, controller)); - - qDebug("applying 'modified streams' ..."); - streamConfigList = new OstProto::StreamConfigList; - ack = new OstProto::Ack; - controller = new PbRpcController(streamConfigList, ack); - - streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); - mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList); - - serviceStub->modifyStream(controller, streamConfigList, ack, - NewCallback(this, &PortGroup::processModifyStreamAck, - portIndex, controller)); + // Also, drone currently updates its packet list at the end of + // modifyStream() implicitly assuming that will be the last API + // called - this will also need to be fixed // // Update/Sync DeviceGroups @@ -470,25 +436,44 @@ void PortGroup::when_configApply(int portIndex) serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack, NewCallback(this, &PortGroup::processModifyDeviceGroupAck, portIndex, controller)); -} -void PortGroup::processAddStreamAck(PbRpcController *controller) -{ - qDebug("In %s", __FUNCTION__); - delete controller; -} + // + // Update/Sync Streams + // + qDebug("applying 'deleted streams' ..."); + streamIdList = new OstProto::StreamIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList, ack); -void PortGroup::processDeleteStreamAck(PbRpcController *controller) -{ - qDebug("In %s", __FUNCTION__); - delete controller; -} + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList); + + serviceStub->deleteStream(controller, streamIdList, ack, + NewCallback(this, &PortGroup::processDeleteStreamAck, controller)); + + qDebug("applying 'new streams' ..."); + streamIdList = new OstProto::StreamIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList, ack); + + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList); + + serviceStub->addStream(controller, streamIdList, ack, + NewCallback(this, &PortGroup::processAddStreamAck, controller)); + + qDebug("applying 'modified streams' ..."); + streamConfigList = new OstProto::StreamConfigList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamConfigList, ack); + + streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList); + + serviceStub->modifyStream(controller, streamConfigList, ack, + NewCallback(this, &PortGroup::processModifyStreamAck, + portIndex, controller)); -void PortGroup::processModifyStreamAck(int portIndex, - PbRpcController *controller) -{ - qDebug("In %s", __FUNCTION__); - delete controller; } void PortGroup::processAddDeviceGroupAck(PbRpcController *controller) @@ -507,13 +492,31 @@ void PortGroup::processModifyDeviceGroupAck(int portIndex, PbRpcController *controller) { qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processAddStreamAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processDeleteStreamAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processModifyStreamAck(int portIndex, + PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); qDebug("apply completed"); mPorts[portIndex]->when_syncComplete(); mainWindow->setEnabled(true); QApplication::restoreOverrideCursor(); - delete controller; } From 0503c8acafae84df3258f9fff2392d3eb6442da6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 11 Mar 2016 18:58:08 +0530 Subject: [PATCH 075/121] Device Emulation (contd.): Get and display Device List --- client/devicemodel.cpp | 211 +++++++++++++++++++++++++++++++++++++++ client/devicemodel.h | 49 +++++++++ client/ostinato.pro | 2 + client/port.cpp | 36 +++++++ client/port.h | 15 +++ client/portgroup.cpp | 57 +++++++++++ client/portgroup.h | 3 + client/portgrouplist.cpp | 7 +- client/portgrouplist.h | 4 + client/portswindow.cpp | 25 ++++- client/portswindow.h | 3 + 11 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 client/devicemodel.cpp create mode 100644 client/devicemodel.h diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp new file mode 100644 index 0000000..3929e64 --- /dev/null +++ b/client/devicemodel.cpp @@ -0,0 +1,211 @@ +/* +Copyright (C) 2016 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 "devicemodel.h" + +#include "port.h" + +#include "emulproto.pb.h" +#include "uint128.h" + +#include + +enum { + kMacAddress, + kVlans, + kIp4Address, + kIp4Gateway, + kIp6Address, + kIp6Gateway, + kFieldCount +}; + +static QStringList columns_ = QStringList() + << "Mac" + << "Vlans" + << "IPv4 Address" + << "IPv4 Gateway" + << "IPv6 Address" + << "IPv6 Gateway"; + +DeviceModel::DeviceModel(QObject *parent) + : QAbstractTableModel(parent) +{ + port_ = NULL; +} + +int DeviceModel::rowCount(const QModelIndex &parent) const +{ + if (!port_ || parent.isValid()) + return 0; + + return port_->numDevices(); +} + +int DeviceModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return columns_.size(); +} + +QVariant DeviceModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: + return columns_[section]; + case Qt::Vertical: + return QString("%1").arg(section + 1); + default: + Q_ASSERT(false); // Unreachable + } + + return QVariant(); +} + +QVariant DeviceModel::data(const QModelIndex &index, int role) const +{ + QString str; + + if (!port_ || !index.isValid()) + return QVariant(); + + int devIdx = index.row(); + int field = index.column(); + + Q_ASSERT(devIdx < port_->numDevices()); + Q_ASSERT(field < kFieldCount); + + const OstEmul::Device *dev = port_->deviceByIndex(devIdx); + + Q_ASSERT(dev); + + switch (field) { + case kMacAddress: + switch (role) { + case Qt::DisplayRole: + return QString("%1").arg(dev->mac(), 6*2, 16, QChar('0')) + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:") + .toUpper(); + default: + break; + } + return QVariant(); + + case kVlans: + switch (role) { + case Qt::DisplayRole: + if (!dev->vlan_size()) + return QString("None"); + for (int i = 0; i < dev->vlan_size(); i++) + str.append(i == 0 ? "" : ", ") + .append(QString::number(dev->vlan(i) & 0xfff)); + return str; + default: + break; + } + return QVariant(); + + case kIp4Address: + switch (role) { + case Qt::DisplayRole: + if (dev->has_ip4_prefix_length()) + return QString("%1/%2") + .arg(QHostAddress(dev->ip4()).toString()) + .arg(dev->ip4_prefix_length()); + else + return QString("--"); + default: + break; + } + return QVariant(); + + case kIp4Gateway: + switch (role) { + case Qt::DisplayRole: + if (dev->has_ip4_prefix_length()) + return QHostAddress(dev->ip4_default_gateway()) + .toString(); + else + return QString("--"); + default: + break; + } + return QVariant(); + + case kIp6Address: + switch (role) { + case Qt::DisplayRole: + if (dev->has_ip6_prefix_length()) { + OstEmul::Ip6Address ip = dev->ip6(); + return QHostAddress( + UInt128(ip.hi(), ip.lo()).toArray()) + .toString(); + } + else + return QString("--"); + default: + break; + } + return QVariant(); + + case kIp6Gateway: + switch (role) { + case Qt::DisplayRole: + if (dev->has_ip6_prefix_length()) { + OstEmul::Ip6Address ip = dev->ip6_default_gateway(); + return QHostAddress( + UInt128(ip.hi(), ip.lo()).toArray()) + .toString(); + } + else + return QString("--"); + default: + break; + } + return QVariant(); + + default: + Q_ASSERT(false); // unreachable! + break; + } + + qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + return QVariant(); +} + +void DeviceModel::setPort(Port *port) +{ + port_ = port; + if (port_) + connect(port_, SIGNAL(deviceListChanged()), SLOT(updateDeviceList())); + reset(); +} + +void DeviceModel::updateDeviceList() +{ + reset(); +} diff --git a/client/devicemodel.h b/client/devicemodel.h new file mode 100644 index 0000000..5e161ff --- /dev/null +++ b/client/devicemodel.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _DEVICE_MODEL_H +#define _DEVICE_MODEL_H + +#include + +class Port; +class DeviceModel: public QAbstractTableModel +{ + Q_OBJECT +public: + DeviceModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + + void setPort(Port *port); + +public slots: + void updateDeviceList(); + +private: + Port *port_; +}; + +#endif + diff --git a/client/ostinato.pro b/client/ostinato.pro index 2d9e3a0..228f818 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -35,6 +35,7 @@ RESOURCES += ostinato.qrc HEADERS += \ devicegroupdialog.h \ devicegroupmodel.h \ + devicemodel.h \ dumpview.h \ hexlineedit.h \ mainwindow.h \ @@ -72,6 +73,7 @@ FORMS += \ SOURCES += \ devicegroupdialog.cpp \ devicegroupmodel.cpp \ + devicemodel.cpp \ dumpview.cpp \ stream.cpp \ hexlineedit.cpp \ diff --git a/client/port.cpp b/client/port.cpp index 9f7de6a..1ca3e96 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -766,3 +766,39 @@ bool Port::updateDeviceGroup( devGrp->CopyFrom(*deviceGroup); return true; } + +// ------------ Devices ----------- // + +int Port::numDevices() +{ + return devices_.size(); +} + +const OstEmul::Device* Port::deviceByIndex(int index) +{ + if ((index < 0) || (index >= numDevices())) { + qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, + index, numDevices() - 1); + return NULL; + } + + return devices_.at(index); +} + +void Port::clearDeviceList() +{ + while (devices_.size()) + delete devices_.takeFirst(); +} + +void Port::insertDevice(const OstEmul::Device &device) +{ + OstEmul::Device *dev = new OstEmul::Device(device); + devices_.append(dev); +} + +void Port::deviceListRefreshed() +{ + emit deviceListChanged(); +} + diff --git a/client/port.h b/client/port.h index f3092cf..8d49fda 100644 --- a/client/port.h +++ b/client/port.h @@ -28,6 +28,9 @@ along with this program. If not, see #include "stream.h" //class StreamModel; +namespace OstEmul { + class Device; +} class Port : public QObject { @@ -54,6 +57,7 @@ class Port : public QObject { QList lastSyncDeviceGroupList_; QList deviceGroups_; + QList devices_; uint newStreamId(); void updateStreamOrdinalsFromIndex(); @@ -177,10 +181,21 @@ public: OstProto::DeviceGroup *deviceGroup); //@} + // ------------ Device ----------- // + + int numDevices(); + const OstEmul::Device* deviceByIndex(int index); + + //! Used by MyService::Stub to update from config received from server + void clearDeviceList(); + void insertDevice(const OstEmul::Device &device); + void deviceListRefreshed(); + signals: void portRateChanged(int portGroupId, int portId); void portDataChanged(int portGroupId, int portId); void streamListChanged(int portGroupId, int portId); + void deviceListChanged(); }; diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 9d67a3e..5cca442 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -21,6 +21,8 @@ along with this program. If not, see #include "settings.h" +#include "emulproto.pb.h" + #include #include #include @@ -520,6 +522,61 @@ void PortGroup::processModifyStreamAck(int portIndex, delete controller; } +void PortGroup::getDeviceList(int portIndex) +{ + OstProto::PortId *portId; + OstProto::PortDeviceList *deviceList; + PbRpcController *controller; + + Q_ASSERT(portIndex < mPorts.size()); + + if (state() != QAbstractSocket::ConnectedState) + return; + + portId = new OstProto::PortId; + portId->set_id(mPorts[portIndex]->id()); + deviceList = new OstProto::PortDeviceList; + controller = new PbRpcController(portId, deviceList); + + serviceStub->getDeviceList(controller, portId, deviceList, + NewCallback(this, &PortGroup::processDeviceList, + portIndex, controller)); +} + +void PortGroup::processDeviceList(int portIndex, PbRpcController *controller) +{ + OstProto::PortDeviceList *deviceList + = static_cast(controller->response()); + + qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (deviceList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + deviceList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + mPorts[portIndex]->clearDeviceList(); + for(int i = 0; i < deviceList->ExtensionSize(OstEmul::port_device); i++) { + mPorts[portIndex]->insertDevice( + deviceList->GetExtension(OstEmul::port_device, i)); + } + mPorts[portIndex]->deviceListRefreshed(); + +_exit: + delete controller; +} + void PortGroup::modifyPort(int portIndex, OstProto::Port portConfig) { OstProto::PortConfigList *portConfigList = new OstProto::PortConfigList; diff --git a/client/portgroup.h b/client/portgroup.h index c7b5c68..82c27ca 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -110,6 +110,8 @@ public: void processDeleteDeviceGroupAck(PbRpcController *controller); void processModifyDeviceGroupAck(int portIndex, PbRpcController *controller); + void processDeviceList(int portIndex, PbRpcController *controller); + void modifyPort(int portId, OstProto::Port portConfig); void processModifyPortAck(PbRpcController *controller); void processUpdatedPortConfig(PbRpcController *controller); @@ -170,6 +172,7 @@ private slots: public slots: void when_configApply(int portIndex); + void getDeviceList(int portIndex); }; diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index 987b6fb..c2ea811 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -26,7 +26,8 @@ PortGroupList::PortGroupList() : mPortGroupListModel(this), mStreamListModel(this), mPortStatsModel(this, this), - mDeviceGroupModel(this) + mDeviceGroupModel(this), + mDeviceModel(this) { PortGroup *pg; @@ -35,11 +36,13 @@ PortGroupList::PortGroupList() portModelTester_ = NULL; portStatsModelTester_ = NULL; deviceGroupModelTester_ = NULL; + deviceModelTester_ = NULL; #else streamModelTester_ = new ModelTest(getStreamModel()); portModelTester_ = new ModelTest(getPortModel()); portStatsModelTester_ = new ModelTest(getPortStatsModel()); - deviceGroupModelTester_ = new ModelTest(getPortStatsModel()); + deviceGroupModelTester_ = new ModelTest(getDeviceGroupModel()); + deviceModelTester_ = new ModelTest(getDeviceModel()); #endif // Add the "Local" Port Group diff --git a/client/portgrouplist.h b/client/portgrouplist.h index 75544ee..4a2305d 100644 --- a/client/portgrouplist.h +++ b/client/portgrouplist.h @@ -21,6 +21,7 @@ along with this program. If not, see #define _PORT_GROUP_LIST_H #include "devicegroupmodel.h" +#include "devicemodel.h" #include "portgroup.h" #include "portmodel.h" #include "portstatsmodel.h" @@ -42,11 +43,13 @@ class PortGroupList : public QObject { StreamModel mStreamListModel; PortStatsModel mPortStatsModel; DeviceGroupModel mDeviceGroupModel; + DeviceModel mDeviceModel; QObject *streamModelTester_; QObject *portModelTester_; QObject *portStatsModelTester_; QObject *deviceGroupModelTester_; + QObject *deviceModelTester_; // Methods public: @@ -57,6 +60,7 @@ public: PortStatsModel* getPortStatsModel() { return &mPortStatsModel; } StreamModel* getStreamModel() { return &mStreamListModel; } DeviceGroupModel* getDeviceGroupModel() { return &mDeviceGroupModel; } + DeviceModel* getDeviceModel() { return &mDeviceModel; } bool isPortGroup(const QModelIndex& index); bool isPort(const QModelIndex& index); diff --git a/client/portswindow.cpp b/client/portswindow.cpp index e8670e1..f7d9eea 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -255,6 +255,7 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, if (plm->isPort(current)) port = &(plm->port(current)); plm->getDeviceGroupModel()->setPort(port); + plm->getDeviceModel()->setPort(port); if (previous.isValid() && plm->isPort(previous)) { @@ -896,8 +897,10 @@ void PortsWindow::on_deviceInfo_toggled(bool checked) { refresh->setVisible(checked); - // TODO: deviceInfo - deviceGroupList->setModel(checked ? NULL : plm->getDeviceGroupModel()); + if (checked) + deviceGroupList->setModel(plm->getDeviceModel()); + else + deviceGroupList->setModel(plm->getDeviceGroupModel()); } void PortsWindow::on_actionNewDeviceGroup_triggered() @@ -952,3 +955,21 @@ void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index) DeviceGroupDialog dgd(&plm->port(currentPort), index.row(), this); dgd.exec(); } + +void PortsWindow::on_refresh_clicked() +{ + QModelIndex curPort; + QModelIndex curPortGroup; + + curPort = tvPortList->selectionModel()->currentIndex(); + if (proxyPortModel) + curPort = proxyPortModel->mapToSource(curPort); + Q_ASSERT(curPort.isValid()); + Q_ASSERT(plm->isPort(curPort)); + + curPortGroup = plm->getPortModel()->parent(curPort); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(plm->isPortGroup(curPortGroup)); + + plm->portGroup(curPortGroup).getDeviceList(plm->port(curPort).id()); +} diff --git a/client/portswindow.h b/client/portswindow.h index cbdaed8..a6d3e62 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -83,10 +83,13 @@ private slots: void streamModelDataChanged(); void on_deviceInfo_toggled(bool checked); + void on_actionNewDeviceGroup_triggered(); void on_actionDeleteDeviceGroup_triggered(); void on_actionEditDeviceGroup_triggered(); void on_deviceGroupList_activated(const QModelIndex &index); + + void on_refresh_clicked(); }; #endif From 7c87e2130ab1308d4c66b73c68a6fb8d9fcd1ab0 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 11 Mar 2016 21:05:07 +0530 Subject: [PATCH 076/121] Device Emulation (contd.): Use two different TableViews for DeviceGroupList and DeviceList --- client/portswindow.cpp | 16 ++++---- client/portswindow.ui | 90 ++++++++++++++++++++++++++++++++---------- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index f7d9eea..4f7f4e6 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -55,6 +55,8 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->verticalHeader()->minimumSectionSize()); deviceGroupList->verticalHeader()->setDefaultSectionSize( deviceGroupList->verticalHeader()->minimumSectionSize()); + deviceList->verticalHeader()->setDefaultSectionSize( + deviceList->verticalHeader()->minimumSectionSize()); // Populate PortList Context Menu Actions tvPortList->addAction(actionNew_Port_Group); @@ -97,6 +99,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->setModel(plm->getStreamModel()); deviceGroupList->setModel(plm->getDeviceGroupModel()); + deviceList->setModel(plm->getDeviceModel()); // XXX: It would be ideal if we only needed to do the below to // get the proxy model to do its magic. However, the QModelIndex @@ -158,8 +161,11 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) SLOT(updateDeviceViewActions())); // FIXME: hardcoding - deviceGroupList->resizeColumnToContents(1); - deviceGroupList->resizeColumnToContents(2); + deviceGroupList->resizeColumnToContents(1); // Vlan Count + deviceGroupList->resizeColumnToContents(2); // Device Count + + // FIXME: hardcoding + deviceList->resizeColumnToContents(1); // Vlan Id(s) // Initially we don't have any ports/streams/devices // - so send signal triggers @@ -896,11 +902,7 @@ _exit: void PortsWindow::on_deviceInfo_toggled(bool checked) { refresh->setVisible(checked); - - if (checked) - deviceGroupList->setModel(plm->getDeviceModel()); - else - deviceGroupList->setModel(plm->getDeviceGroupModel()); + devicesWidget->setCurrentIndex(checked ? 1 : 0); } void PortsWindow::on_actionNewDeviceGroup_triggered() diff --git a/client/portswindow.ui b/client/portswindow.ui index 28efa35..4d61998 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -219,28 +219,76 @@ - - - - 0 - 1 - - - - Qt::ActionsContextMenu - - - QFrame::StyledPanel - - - 1 - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + Qt::ActionsContextMenu + + + QFrame::StyledPanel + + + 1 + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + From 941d522451f527bd5f802a8d4b468461f0838ee6 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 12 Mar 2016 18:56:35 +0530 Subject: [PATCH 077/121] Device Emulation (contd.): GUI now fetches ARP/NDP also from server and display summary counts - resolved/total --- client/devicemodel.cpp | 30 ++++++++++++++-- client/port.cpp | 77 ++++++++++++++++++++++++++++++++++++++++-- client/port.h | 20 +++++++++-- client/portgroup.cpp | 49 +++++++++++++++++++++++++-- client/portgroup.h | 3 +- client/portswindow.cpp | 4 ++- 6 files changed, 173 insertions(+), 10 deletions(-) diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp index 3929e64..c99faf7 100644 --- a/client/devicemodel.cpp +++ b/client/devicemodel.cpp @@ -33,6 +33,8 @@ enum { kIp4Gateway, kIp6Address, kIp6Gateway, + kArpInfo, + kNdpInfo, kFieldCount }; @@ -42,7 +44,9 @@ static QStringList columns_ = QStringList() << "IPv4 Address" << "IPv4 Gateway" << "IPv6 Address" - << "IPv6 Gateway"; + << "IPv6 Gateway" + << "ARP" + << "NDP"; DeviceModel::DeviceModel(QObject *parent) : QAbstractTableModel(parent) @@ -188,6 +192,28 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const } return QVariant(); + case kArpInfo: + switch (role) { + case Qt::DisplayRole: + return QString("%1/%2") + .arg(port_->numArpResolved(devIdx)) + .arg(port_->numArp(devIdx)); + default: + break; + } + return QVariant(); + + case kNdpInfo: + switch (role) { + case Qt::DisplayRole: + return QString("%1/%2") + .arg(port_->numNdpResolved(devIdx)) + .arg(port_->numNdp(devIdx)); + default: + break; + } + return QVariant(); + default: Q_ASSERT(false); // unreachable! break; @@ -201,7 +227,7 @@ void DeviceModel::setPort(Port *port) { port_ = port; if (port_) - connect(port_, SIGNAL(deviceListChanged()), SLOT(updateDeviceList())); + connect(port_, SIGNAL(deviceInfoChanged()), SLOT(updateDeviceList())); reset(); } diff --git a/client/port.cpp b/client/port.cpp index 1ca3e96..ebd4a3b 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -797,8 +797,81 @@ void Port::insertDevice(const OstEmul::Device &device) devices_.append(dev); } -void Port::deviceListRefreshed() +// ------------- Device Neighbors (ARP/NDP) ------------- // + +const OstEmul::DeviceNeighborList* Port::deviceNeighbors(int deviceIndex) { - emit deviceListChanged(); + if ((deviceIndex < 0) || (deviceIndex >= numDevices())) { + qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, + deviceIndex, numDevices() - 1); + return NULL; + } + + return deviceNeighbors_.value(deviceIndex); +} + +int Port::numArp(int deviceIndex) +{ + if (deviceNeighbors_.contains(deviceIndex)) + return deviceNeighbors_.value(deviceIndex)->arp_size(); + + return 0; +} + +int Port::numArpResolved(int deviceIndex) +{ + if (arpResolvedCount_.contains(deviceIndex)) + return arpResolvedCount_.value(deviceIndex); + + return 0; +} + +int Port::numNdp(int deviceIndex) +{ + if (deviceNeighbors_.contains(deviceIndex)) + return deviceNeighbors_.value(deviceIndex)->ndp_size(); + + return 0; +} + +int Port::numNdpResolved(int deviceIndex) +{ + if (ndpResolvedCount_.contains(deviceIndex)) + return ndpResolvedCount_.value(deviceIndex); + + return 0; +} + +void Port::clearDeviceNeighbors() +{ + arpResolvedCount_.clear(); + ndpResolvedCount_.clear(); + qDeleteAll(deviceNeighbors_); + deviceNeighbors_.clear(); +} + +void Port::insertDeviceNeighbors(const OstEmul::DeviceNeighborList &neighList) +{ + int count; + OstEmul::DeviceNeighborList *neighbors = + new OstEmul::DeviceNeighborList(neighList); + deviceNeighbors_.insert(neighList.device_index(), neighbors); + + count = 0; + for (int i = 0; i < neighbors->arp_size(); i++) + if (neighbors->arp(i).mac()) + count++; + arpResolvedCount_.insert(neighbors->device_index(), count); + + count = 0; + for (int i = 0; i < neighbors->ndp_size(); i++) + if (neighbors->ndp(i).mac()) + count++; + ndpResolvedCount_.insert(neighbors->device_index(), count); +} + +void Port::deviceInfoRefreshed() +{ + emit deviceInfoChanged(); } diff --git a/client/port.h b/client/port.h index 8d49fda..55bc325 100644 --- a/client/port.h +++ b/client/port.h @@ -21,6 +21,7 @@ along with this program. If not, see #define _PORT_H #include +#include #include #include #include @@ -30,6 +31,7 @@ along with this program. If not, see //class StreamModel; namespace OstEmul { class Device; + class DeviceNeighborList; } class Port : public QObject { @@ -58,6 +60,9 @@ class Port : public QObject { QList lastSyncDeviceGroupList_; QList deviceGroups_; QList devices_; + QHash deviceNeighbors_; + QHash arpResolvedCount_; + QHash ndpResolvedCount_; uint newStreamId(); void updateStreamOrdinalsFromIndex(); @@ -189,13 +194,24 @@ public: //! Used by MyService::Stub to update from config received from server void clearDeviceList(); void insertDevice(const OstEmul::Device &device); - void deviceListRefreshed(); + + const OstEmul::DeviceNeighborList* deviceNeighbors(int deviceIndex); + int numArp(int deviceIndex); + int numArpResolved(int deviceIndex); + int numNdp(int deviceIndex); + int numNdpResolved(int deviceIndex); + + //! Used by MyService::Stub to update from config received from server + void clearDeviceNeighbors(); + void insertDeviceNeighbors(const OstEmul::DeviceNeighborList &neighList); + + void deviceInfoRefreshed(); signals: void portRateChanged(int portGroupId, int portId); void portDataChanged(int portGroupId, int portId); void streamListChanged(int portGroupId, int portId); - void deviceListChanged(); + void deviceInfoChanged(); }; diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 5cca442..14d3d4e 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -522,10 +522,11 @@ void PortGroup::processModifyStreamAck(int portIndex, delete controller; } -void PortGroup::getDeviceList(int portIndex) +void PortGroup::getDeviceInfo(int portIndex) { OstProto::PortId *portId; OstProto::PortDeviceList *deviceList; + OstProto::PortNeighborList *neighList; PbRpcController *controller; Q_ASSERT(portIndex < mPorts.size()); @@ -541,6 +542,15 @@ void PortGroup::getDeviceList(int portIndex) serviceStub->getDeviceList(controller, portId, deviceList, NewCallback(this, &PortGroup::processDeviceList, portIndex, controller)); + + portId = new OstProto::PortId; + portId->set_id(mPorts[portIndex]->id()); + neighList = new OstProto::PortNeighborList; + controller = new PbRpcController(portId, neighList); + + serviceStub->getDeviceNeighbors(controller, portId, neighList, + NewCallback(this, &PortGroup::processDeviceNeighbors, + portIndex, controller)); } void PortGroup::processDeviceList(int portIndex, PbRpcController *controller) @@ -571,7 +581,42 @@ void PortGroup::processDeviceList(int portIndex, PbRpcController *controller) mPorts[portIndex]->insertDevice( deviceList->GetExtension(OstEmul::port_device, i)); } - mPorts[portIndex]->deviceListRefreshed(); + +_exit: + delete controller; +} + +void PortGroup::processDeviceNeighbors( + int portIndex, PbRpcController *controller) +{ + OstProto::PortNeighborList *neighList + = static_cast(controller->response()); + + qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (neighList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + neighList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + mPorts[portIndex]->clearDeviceNeighbors(); + for(int i = 0; i < neighList->ExtensionSize(OstEmul::devices); i++) { + mPorts[portIndex]->insertDeviceNeighbors( + neighList->GetExtension(OstEmul::devices, i)); // FIXME: change extn id + } + + mPorts[portIndex]->deviceInfoRefreshed(); _exit: delete controller; diff --git a/client/portgroup.h b/client/portgroup.h index 82c27ca..eb0e72a 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -111,6 +111,7 @@ public: void processModifyDeviceGroupAck(int portIndex, PbRpcController *controller); void processDeviceList(int portIndex, PbRpcController *controller); + void processDeviceNeighbors(int portIndex, PbRpcController *controller); void modifyPort(int portId, OstProto::Port portConfig); void processModifyPortAck(PbRpcController *controller); @@ -172,7 +173,7 @@ private slots: public slots: void when_configApply(int portIndex); - void getDeviceList(int portIndex); + void getDeviceInfo(int portIndex); }; diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 4f7f4e6..4d21367 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -166,6 +166,8 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) // FIXME: hardcoding deviceList->resizeColumnToContents(1); // Vlan Id(s) + deviceList->resizeColumnToContents(6); // ARP Info + deviceList->resizeColumnToContents(7); // NDP Info // Initially we don't have any ports/streams/devices // - so send signal triggers @@ -973,5 +975,5 @@ void PortsWindow::on_refresh_clicked() Q_ASSERT(curPortGroup.isValid()); Q_ASSERT(plm->isPortGroup(curPortGroup)); - plm->portGroup(curPortGroup).getDeviceList(plm->port(curPort).id()); + plm->portGroup(curPortGroup).getDeviceInfo(plm->port(curPort).id()); } From 24a93a50253b030fef1eedc06c4b7c14490ff7ba Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 13 Mar 2016 19:51:32 +0530 Subject: [PATCH 078/121] Device Emulation (contd.): PortsWindow - refactored all device related widgets into a separate DevicesWidget for better modularity. --- client/deviceswidget.cpp | 217 +++++++++++++++++++++++++++++++++++++++ client/deviceswidget.h | 57 ++++++++++ client/deviceswidget.ui | 110 ++++++++++++++++++++ client/ostinato.pro | 3 + client/portswindow.cpp | 181 ++------------------------------ client/portswindow.h | 14 +-- client/portswindow.ui | 138 ++----------------------- 7 files changed, 411 insertions(+), 309 deletions(-) create mode 100644 client/deviceswidget.cpp create mode 100644 client/deviceswidget.h create mode 100644 client/deviceswidget.ui diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp new file mode 100644 index 0000000..9aa5bf5 --- /dev/null +++ b/client/deviceswidget.cpp @@ -0,0 +1,217 @@ +/* +Copyright (C) 2016 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 "deviceswidget.h" + +#include "devicegroupdialog.h" +#include "port.h" +#include "portgrouplist.h" + +#include + +DevicesWidget::DevicesWidget(QWidget *parent) + : QWidget(parent), portGroups_(NULL) +{ + setupUi(this); + deviceGroupList->setVisible(deviceConfig->isChecked()); + deviceList->setVisible(deviceInfo->isChecked()); + refresh->setVisible(deviceInfo->isChecked()); + + deviceGroupList->verticalHeader()->setDefaultSectionSize( + deviceGroupList->verticalHeader()->minimumSectionSize()); + deviceList->verticalHeader()->setDefaultSectionSize( + deviceList->verticalHeader()->minimumSectionSize()); + + // Populate DeviceGroup Context Menu Actions + deviceGroupList->addAction(actionNewDeviceGroup); + deviceGroupList->addAction(actionEditDeviceGroup); + deviceGroupList->addAction(actionDeleteDeviceGroup); + + // DevicesWidget's actions is an aggegate of all sub-widget's actions + addActions(deviceGroupList->actions()); +} + +void DevicesWidget::setPortGroupList(PortGroupList *portGroups) +{ + portGroups_ = portGroups; + + deviceGroupList->setModel(portGroups_->getDeviceGroupModel()); + deviceList->setModel(portGroups_->getDeviceModel()); + + connect(portGroups_->getDeviceGroupModel(), + SIGNAL(rowsInserted(QModelIndex, int, int)), + SLOT(updateDeviceViewActions())); + connect(portGroups_->getDeviceGroupModel(), + SIGNAL(rowsRemoved(QModelIndex, int, int)), + SLOT(updateDeviceViewActions())); + + connect(deviceGroupList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(updateDeviceViewActions())); + connect(deviceGroupList->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + SLOT(updateDeviceViewActions())); + + // FIXME: hardcoding + deviceGroupList->resizeColumnToContents(1); // Vlan Count + deviceGroupList->resizeColumnToContents(2); // Device Count + + // FIXME: hardcoding + deviceList->resizeColumnToContents(1); // Vlan Id(s) + deviceList->resizeColumnToContents(6); // ARP Info + deviceList->resizeColumnToContents(7); // NDP Info + + updateDeviceViewActions(); +} + +void DevicesWidget::setCurrentPortIndex(const QModelIndex &portIndex) +{ + Port *port = NULL; + + if (!portGroups_) + return; + + // XXX: We assume portIndex corresponds to sourceModel, not proxyModel + if (portGroups_->isPort(portIndex)) + port = &portGroups_->port(portIndex); + + portGroups_->getDeviceGroupModel()->setPort(port); + portGroups_->getDeviceModel()->setPort(port); + currentPortIndex_ = portIndex; + + updateDeviceViewActions(); +} + +void DevicesWidget::updateDeviceViewActions() +{ + QItemSelectionModel *devSel = deviceGroupList->selectionModel(); + + if (!portGroups_) + return; + + // For some reason hasSelection() returns true even if selection size is 0 + // so additional check for size introduced + if (devSel->hasSelection() && (devSel->selection().size() > 0)) { + // If more than one non-contiguous ranges selected, + // disable "New" and "Edit" + if (devSel->selection().size() > 1) { + actionNewDeviceGroup->setDisabled(true); + actionEditDeviceGroup->setDisabled(true); + } + else { + actionNewDeviceGroup->setEnabled(true); + + // Enable "Edit" only if the single range has a single row + if (devSel->selection().at(0).height() > 1) + actionEditDeviceGroup->setDisabled(true); + else + actionEditDeviceGroup->setEnabled(true); + } + + // Delete is always enabled as long as we have a selection + actionDeleteDeviceGroup->setEnabled(true); + } + else { + qDebug("No device selection"); + if (portGroups_->isPort(currentPortIndex_)) + actionNewDeviceGroup->setEnabled(true); + else + actionNewDeviceGroup->setDisabled(true); + actionEditDeviceGroup->setDisabled(true); + actionDeleteDeviceGroup->setDisabled(true); + } +} + +// +// DeviceGroup slots +// + +void DevicesWidget::on_deviceInfo_toggled(bool checked) +{ + refresh->setVisible(checked); + deviceGroupList->setHidden(checked); + deviceList->setVisible(checked); +} + +void DevicesWidget::on_actionNewDeviceGroup_triggered() +{ + // In case nothing is selected, insert 1 row at the top + int row = 0, count = 1; + QItemSelection selection = deviceGroupList->selectionModel()->selection(); + + if (!portGroups_) + return; + + // In case we have a single range selected; insert as many rows as + // in the singe selected range before the top of the selected range + if (selection.size() == 1) { + row = selection.at(0).top(); + count = selection.at(0).height(); + } + + portGroups_->getDeviceGroupModel()->insertRows(row, count); +} + +void DevicesWidget::on_actionDeleteDeviceGroup_triggered() +{ + QModelIndex index; + + if (!portGroups_) + return; + + if (deviceGroupList->selectionModel()->hasSelection()) { + while(deviceGroupList->selectionModel()->selectedRows().size()) { + index = deviceGroupList->selectionModel()->selectedRows().at(0); + portGroups_->getDeviceGroupModel()->removeRows(index.row(), 1); + } + } +} + +void DevicesWidget::on_actionEditDeviceGroup_triggered() +{ + QItemSelection selection = deviceGroupList->selectionModel()->selection(); + + // Ensure we have only one range selected which contains only one row + if ((selection.size() == 1) && (selection.at(0).height() == 1)) + on_deviceGroupList_activated(selection.at(0).topLeft()); +} + +void DevicesWidget::on_deviceGroupList_activated(const QModelIndex &index) +{ + if (!index.isValid() || !portGroups_) + return; + + DeviceGroupDialog dgd(&portGroups_->port(currentPortIndex_), index.row()); + dgd.exec(); +} + +void DevicesWidget::on_refresh_clicked() +{ + if (!portGroups_) + return; + + Q_ASSERT(portGroups_->isPort(currentPortIndex_)); + QModelIndex curPortGroup = portGroups_->getPortModel() + ->parent(currentPortIndex_); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(portGroups_->isPortGroup(curPortGroup)); + + portGroups_->portGroup(curPortGroup) + .getDeviceInfo(portGroups_->port(currentPortIndex_).id()); +} diff --git a/client/deviceswidget.h b/client/deviceswidget.h new file mode 100644 index 0000000..fb15ef6 --- /dev/null +++ b/client/deviceswidget.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _DEVICES_WIDGET_H +#define _DEVICES_WIDGET_H + +#include "ui_deviceswidget.h" +#include + +class PortGroupList; + +class DevicesWidget: public QWidget, private Ui::DevicesWidget +{ + Q_OBJECT + +public: + DevicesWidget(QWidget *parent = NULL); + void setPortGroupList(PortGroupList *portGroups); + +public slots: + void setCurrentPortIndex(const QModelIndex &portIndex); + +private slots: + void updateDeviceViewActions(); + + void on_deviceInfo_toggled(bool checked); + + void on_actionNewDeviceGroup_triggered(); + void on_actionDeleteDeviceGroup_triggered(); + void on_actionEditDeviceGroup_triggered(); + void on_deviceGroupList_activated(const QModelIndex &index); + + void on_refresh_clicked(); + +private: + PortGroupList *portGroups_; + QModelIndex currentPortIndex_; +}; + +#endif + diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui new file mode 100644 index 0000000..7753e8f --- /dev/null +++ b/client/deviceswidget.ui @@ -0,0 +1,110 @@ + + DevicesWidget + + + + 0 + 0 + 675 + 328 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Configuration + + + true + + + + + + + Information + + + + + + + Qt::Horizontal + + + + 131 + 23 + + + + + + + + Refresh + + + + + + + + + Qt::ActionsContextMenu + + + QFrame::StyledPanel + + + 1 + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + + + New Device Group + + + + + Delete Device Group + + + + + Edit Device Group + + + + + + diff --git a/client/ostinato.pro b/client/ostinato.pro index 228f818..7c20029 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -36,6 +36,7 @@ HEADERS += \ devicegroupdialog.h \ devicegroupmodel.h \ devicemodel.h \ + deviceswidget.h \ dumpview.h \ hexlineedit.h \ mainwindow.h \ @@ -61,6 +62,7 @@ HEADERS += \ FORMS += \ about.ui \ devicegroupdialog.ui \ + deviceswidget.ui \ mainwindow.ui \ portconfigdialog.ui \ portstatsfilter.ui \ @@ -74,6 +76,7 @@ SOURCES += \ devicegroupdialog.cpp \ devicegroupmodel.cpp \ devicemodel.cpp \ + deviceswidget.cpp \ dumpview.cpp \ stream.cpp \ hexlineedit.cpp \ diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 4d21367..5d62225 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -20,7 +20,7 @@ along with this program. If not, see #include "portswindow.h" #include "abstractfileformat.h" -#include "devicegroupdialog.h" +#include "deviceswidget.h" #include "portconfigdialog.h" #include "settings.h" #include "streamconfigdialog.h" @@ -45,7 +45,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) plm = pgl; setupUi(this); - refresh->setVisible(deviceInfo->isChecked()); + devicesWidget->setPortGroupList(plm); tvPortList->header()->hide(); @@ -53,10 +53,6 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->verticalHeader()->setDefaultSectionSize( tvStreamList->verticalHeader()->minimumSectionSize()); - deviceGroupList->verticalHeader()->setDefaultSectionSize( - deviceGroupList->verticalHeader()->minimumSectionSize()); - deviceList->verticalHeader()->setDefaultSectionSize( - deviceList->verticalHeader()->minimumSectionSize()); // Populate PortList Context Menu Actions tvPortList->addAction(actionNew_Port_Group); @@ -67,7 +63,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvPortList->addAction(actionExclusive_Control); tvPortList->addAction(actionPort_Configuration); - // Populate StramList Context Menu Actions + // Populate StreamList Context Menu Actions tvStreamList->addAction(actionNew_Stream); tvStreamList->addAction(actionEdit_Stream); tvStreamList->addAction(actionDuplicate_Stream); @@ -80,12 +76,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->addAction(actionOpen_Streams); tvStreamList->addAction(actionSave_Streams); - // Populate DeviceGroup Context Menu Actions - deviceGroupList->addAction(actionNewDeviceGroup); - deviceGroupList->addAction(actionEditDeviceGroup); - deviceGroupList->addAction(actionDeleteDeviceGroup); - - // PortList, StreamList, DeviceGroupList actions combined + // PortList, StreamList, DeviceWidget actions combined // make this window's actions addActions(tvPortList->actions()); sep = new QAction(this); @@ -95,11 +86,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) sep = new QAction(this); sep->setSeparator(true); addAction(sep); - addActions(deviceGroupList->actions()); + addActions(devicesWidget->actions()); tvStreamList->setModel(plm->getStreamModel()); - deviceGroupList->setModel(plm->getDeviceGroupModel()); - deviceList->setModel(plm->getDeviceModel()); // XXX: It would be ideal if we only needed to do the below to // get the proxy model to do its magic. However, the QModelIndex @@ -130,6 +119,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(when_portView_currentChanged(const QModelIndex&, const QModelIndex&))); + connect(this, + SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)), + devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&))); connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(updateStreamViewActions())); @@ -146,34 +138,10 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); - connect(plm->getDeviceGroupModel(), - SIGNAL(rowsInserted(QModelIndex, int, int)), - SLOT(updateDeviceViewActions())); - connect(plm->getDeviceGroupModel(), - SIGNAL(rowsRemoved(QModelIndex, int, int)), - SLOT(updateDeviceViewActions())); - - connect(deviceGroupList->selectionModel(), - SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), - SLOT(updateDeviceViewActions())); - connect(deviceGroupList->selectionModel(), - SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - SLOT(updateDeviceViewActions())); - - // FIXME: hardcoding - deviceGroupList->resizeColumnToContents(1); // Vlan Count - deviceGroupList->resizeColumnToContents(2); // Device Count - - // FIXME: hardcoding - deviceList->resizeColumnToContents(1); // Vlan Id(s) - deviceList->resizeColumnToContents(6); // ARP Info - deviceList->resizeColumnToContents(7); // NDP Info - // Initially we don't have any ports/streams/devices // - so send signal triggers when_portView_currentChanged(QModelIndex(), QModelIndex()); updateStreamViewActions(); - updateDeviceViewActions(); connect(plm->getStreamModel(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), @@ -246,7 +214,6 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, { QModelIndex current = currentIndex; QModelIndex previous = previousIndex; - Port *port = NULL; if (proxyPortModel) { current = proxyPortModel->mapToSource(current); @@ -256,15 +223,9 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, plm->getStreamModel()->setCurrentPortIndex(current); updatePortViewActions(currentIndex); updateStreamViewActions(); - updateDeviceViewActions(); qDebug("In %s", __FUNCTION__); - if (plm->isPort(current)) - port = &(plm->port(current)); - plm->getDeviceGroupModel()->setPort(port); - plm->getDeviceModel()->setPort(port); - if (previous.isValid() && plm->isPort(previous)) { disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), @@ -290,6 +251,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, SLOT(updatePortRates())); } } + + emit currentPortChanged(current, previous); } void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft, @@ -418,51 +381,6 @@ void PortsWindow::updateStreamViewActions() actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); } -void PortsWindow::updateDeviceViewActions() -{ - QModelIndex current = tvPortList->currentIndex(); - QItemSelectionModel *devSel = deviceGroupList->selectionModel(); - - if (proxyPortModel) - current = proxyPortModel->mapToSource(current); - - // For some reason hasSelection() returns true even if selection size is 0 - // so additional check for size introduced - if (devSel->hasSelection() && (devSel->selection().size() > 0)) - { - // If more than one non-contiguous ranges selected, - // disable "New" and "Edit" - if (devSel->selection().size() > 1) - { - actionNewDeviceGroup->setDisabled(true); - actionEditDeviceGroup->setDisabled(true); - } - else - { - actionNewDeviceGroup->setEnabled(true); - - // Enable "Edit" only if the single range has a single row - if (devSel->selection().at(0).height() > 1) - actionEditDeviceGroup->setDisabled(true); - else - actionEditDeviceGroup->setEnabled(true); - } - - // Delete is always enabled as long as we have a selection - actionDeleteDeviceGroup->setEnabled(true); - } - else - { - qDebug("No device selection"); - if (plm->isPort(current)) - actionNewDeviceGroup->setEnabled(true); - else - actionNewDeviceGroup->setDisabled(true); - actionEditDeviceGroup->setDisabled(true); - actionDeleteDeviceGroup->setDisabled(true); - } -} - void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex) { QModelIndex current = currentIndex; @@ -897,83 +815,4 @@ _exit: return; } -// -// DeviceGroup slots -// -void PortsWindow::on_deviceInfo_toggled(bool checked) -{ - refresh->setVisible(checked); - devicesWidget->setCurrentIndex(checked ? 1 : 0); -} - -void PortsWindow::on_actionNewDeviceGroup_triggered() -{ - // In case nothing is selected, insert 1 row at the top - int row = 0, count = 1; - QItemSelection selection = deviceGroupList->selectionModel()->selection(); - - // In case we have a single range selected; insert as many rows as - // in the singe selected range before the top of the selected range - if (selection.size() == 1) - { - row = selection.at(0).top(); - count = selection.at(0).height(); - } - - plm->getDeviceGroupModel()->insertRows(row, count); -} - -void PortsWindow::on_actionDeleteDeviceGroup_triggered() -{ - QModelIndex index; - - if (deviceGroupList->selectionModel()->hasSelection()) - { - while(deviceGroupList->selectionModel()->selectedRows().size()) - { - index = deviceGroupList->selectionModel()->selectedRows().at(0); - plm->getDeviceGroupModel()->removeRows(index.row(), 1); - } - } -} - -void PortsWindow::on_actionEditDeviceGroup_triggered() -{ - QItemSelection selection = deviceGroupList->selectionModel()->selection(); - - // Ensure we have only one range selected which contains only one row - if ((selection.size() == 1) && (selection.at(0).height() == 1)) - on_deviceGroupList_activated(selection.at(0).topLeft()); -} - -void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index) -{ - if (!index.isValid()) - return; - - QModelIndex currentPort = tvPortList->currentIndex(); - if (proxyPortModel) - currentPort = proxyPortModel->mapToSource(currentPort); - - DeviceGroupDialog dgd(&plm->port(currentPort), index.row(), this); - dgd.exec(); -} - -void PortsWindow::on_refresh_clicked() -{ - QModelIndex curPort; - QModelIndex curPortGroup; - - curPort = tvPortList->selectionModel()->currentIndex(); - if (proxyPortModel) - curPort = proxyPortModel->mapToSource(curPort); - Q_ASSERT(curPort.isValid()); - Q_ASSERT(plm->isPort(curPort)); - - curPortGroup = plm->getPortModel()->parent(curPort); - Q_ASSERT(curPortGroup.isValid()); - Q_ASSERT(plm->isPortGroup(curPortGroup)); - - plm->portGroup(curPortGroup).getDeviceInfo(plm->port(curPort).id()); -} diff --git a/client/portswindow.h b/client/portswindow.h index a6d3e62..9d8e6ef 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -39,6 +39,10 @@ public: PortsWindow(PortGroupList *pgl, QWidget *parent = 0); ~PortsWindow(); +signals: + void currentPortChanged(const QModelIndex ¤t, + const QModelIndex &previous); + private: QString lastNewPortGroup; QAbstractItemDelegate *delegate; @@ -50,7 +54,6 @@ public slots: private slots: void updatePortViewActions(const QModelIndex& currentIndex); void updateStreamViewActions(); - void updateDeviceViewActions(); void on_averagePacketsPerSec_editingFinished(); void on_averageBitsPerSec_editingFinished(); @@ -81,15 +84,6 @@ private slots: void on_actionSave_Streams_triggered(); void streamModelDataChanged(); - - void on_deviceInfo_toggled(bool checked); - - void on_actionNewDeviceGroup_triggered(); - void on_actionDeleteDeviceGroup_triggered(); - void on_actionEditDeviceGroup_triggered(); - void on_deviceGroupList_activated(const QModelIndex &index); - - void on_refresh_clicked(); }; #endif diff --git a/client/portswindow.ui b/client/portswindow.ui index 4d61998..7835329 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -5,7 +5,7 @@ 0 0 - 710 + 663 352 @@ -178,118 +178,7 @@ - - - - - Configuration - - - true - - - - - - - Information - - - - - - - Qt::Horizontal - - - - 131 - 23 - - - - - - - - Refresh - - - - - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 1 - - - - Qt::ActionsContextMenu - - - QFrame::StyledPanel - - - 1 - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - + @@ -403,22 +292,15 @@ Duplicate Stream - - - New Device Group - - - - - Delete Device Group - - - - - Edit Device Group - - + + + DevicesWidget + QWidget +
deviceswidget.h
+ 1 +
+
From 853802b99728465b57f4389c624cfa9499fabe27 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Mar 2016 20:11:40 +0530 Subject: [PATCH 079/121] Device Emulation (contd.): Implemented display of ARP Cache Device Detail; styled "drillable" fields in DeviceModel suitably --- client/arpstatusmodel.cpp | 151 ++++++++++++++++++++++++++++++++++++++ client/arpstatusmodel.h | 55 ++++++++++++++ client/devicemodel.cpp | 55 +++++++++++++- client/devicemodel.h | 6 ++ client/deviceswidget.cpp | 20 +++++ client/deviceswidget.h | 2 + client/deviceswidget.ui | 19 ++++- client/ostinato.pro | 2 + 8 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 client/arpstatusmodel.cpp create mode 100644 client/arpstatusmodel.h diff --git a/client/arpstatusmodel.cpp b/client/arpstatusmodel.cpp new file mode 100644 index 0000000..cd6af5d --- /dev/null +++ b/client/arpstatusmodel.cpp @@ -0,0 +1,151 @@ +/* +Copyright (C) 2016 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 "arpstatusmodel.h" + +#include "port.h" + +#include "emulproto.pb.h" + +#include + +enum { + kIp4Address, + kMacAddress, + kStatus, + kFieldCount +}; + +static QStringList columns_ = QStringList() + << "IPv4 Address" + << "Mac Address" + << "Status"; + +ArpStatusModel::ArpStatusModel(QObject *parent) + : QAbstractTableModel(parent) +{ + port_ = NULL; + deviceIndex_ = -1; + neighbors_ = NULL; +} + +int ArpStatusModel::rowCount(const QModelIndex &parent) const +{ + if (!port_ || deviceIndex_ < 0 || parent.isValid()) + return 0; + + return port_->numArp(deviceIndex_); +} + +int ArpStatusModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return columns_.size(); +} + +QVariant ArpStatusModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: + return columns_[section]; + case Qt::Vertical: + return QString("%1").arg(section + 1); + default: + Q_ASSERT(false); // Unreachable + } + + return QVariant(); +} + +QVariant ArpStatusModel::data(const QModelIndex &index, int role) const +{ + QString str; + + if (!port_ || deviceIndex_ < 0 || !index.isValid()) + return QVariant(); + + int arpIdx = index.row(); + int field = index.column(); + + Q_ASSERT(arpIdx < port_->numArp(deviceIndex_)); + Q_ASSERT(field < kFieldCount); + + const OstEmul::ArpEntry &arp = neighbors_->arp(arpIdx); + + switch (field) { + case kIp4Address: + switch (role) { + case Qt::DisplayRole: + return QHostAddress(arp.ip4()).toString(); + default: + break; + } + return QVariant(); + + case kMacAddress: + switch (role) { + case Qt::DisplayRole: + return QString("%1").arg(arp.mac(), 6*2, 16, QChar('0')) + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:") + .toUpper(); + default: + break; + } + return QVariant(); + + case kStatus: + switch (role) { + case Qt::DisplayRole: + return arp.mac() ? + QString("Resolved") : QString("Failed"); + default: + break; + } + return QVariant(); + + default: + Q_ASSERT(false); // unreachable! + break; + } + + qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + return QVariant(); +} + +void ArpStatusModel::setDeviceIndex(Port *port, int deviceIndex) +{ + port_ = port; + deviceIndex_ = deviceIndex; + if (port_) + neighbors_ = port_->deviceNeighbors(deviceIndex); + reset(); +} + +void ArpStatusModel::updateArpStatus() +{ + reset(); +} diff --git a/client/arpstatusmodel.h b/client/arpstatusmodel.h new file mode 100644 index 0000000..64debaa --- /dev/null +++ b/client/arpstatusmodel.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _ARP_STATUS_MODEL_H +#define _ARP_STATUS_MODEL_H + +#include + +class Port; +namespace OstEmul { + class DeviceNeighborList; +} + +class ArpStatusModel: public QAbstractTableModel +{ + Q_OBJECT +public: + ArpStatusModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + + void setDeviceIndex(Port *port, int deviceIndex); + +public slots: + void updateArpStatus(); + +private: + Port *port_; + int deviceIndex_; + const OstEmul::DeviceNeighborList *neighbors_; +}; + +#endif + diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp index c99faf7..a261b4a 100644 --- a/client/devicemodel.cpp +++ b/client/devicemodel.cpp @@ -19,11 +19,15 @@ along with this program. If not, see #include "devicemodel.h" +#include "arpstatusmodel.h" #include "port.h" #include "emulproto.pb.h" #include "uint128.h" +#include +#include +#include #include enum { @@ -52,6 +56,7 @@ DeviceModel::DeviceModel(QObject *parent) : QAbstractTableModel(parent) { port_ = NULL; + arpStatusModel_ = new ArpStatusModel(this); } int DeviceModel::rowCount(const QModelIndex &parent) const @@ -195,10 +200,15 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const case kArpInfo: switch (role) { case Qt::DisplayRole: - return QString("%1/%2") + if (dev->has_ip4_prefix_length()) + return QString("%1/%2") .arg(port_->numArpResolved(devIdx)) .arg(port_->numArp(devIdx)); + else + return QString("--"); default: + if (dev->has_ip4_prefix_length()) + return drillableStyle(role); break; } return QVariant(); @@ -206,10 +216,15 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const case kNdpInfo: switch (role) { case Qt::DisplayRole: - return QString("%1/%2") + if (dev->has_ip6_prefix_length()) + return QString("%1/%2") .arg(port_->numNdpResolved(devIdx)) .arg(port_->numNdp(devIdx)); + else + return QString("--"); default: + if (dev->has_ip6_prefix_length()) + return drillableStyle(role); break; } return QVariant(); @@ -220,6 +235,8 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const } qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + +_exit: return QVariant(); } @@ -231,7 +248,41 @@ void DeviceModel::setPort(Port *port) reset(); } +QAbstractItemModel* DeviceModel::detailModel(const QModelIndex &index) +{ + if (!index.isValid()) + return NULL; + + switch(index.column()) { + case kArpInfo: + arpStatusModel_->setDeviceIndex(port_, index.row()); + return arpStatusModel_; + case kNdpInfo: + default: + return NULL; + } +} + void DeviceModel::updateDeviceList() { reset(); } + +// Style roles for drillable fields +QVariant DeviceModel::drillableStyle(int role) const +{ + QFont f; + switch (role) { + case Qt::ToolTipRole: + return QString("Click for details ..."); + case Qt::ForegroundRole: + return QBrush(QColor(Qt::blue)); + case Qt::FontRole: + f.setUnderline(true); + return f; + default: + break; + } + return QVariant(); +} + diff --git a/client/devicemodel.h b/client/devicemodel.h index 5e161ff..78637d5 100644 --- a/client/devicemodel.h +++ b/client/devicemodel.h @@ -22,7 +22,9 @@ along with this program. If not, see #include +class ArpStatusModel; class Port; + class DeviceModel: public QAbstractTableModel { Q_OBJECT @@ -37,12 +39,16 @@ public: QVariant data(const QModelIndex &index, int role) const; void setPort(Port *port); + QAbstractItemModel* detailModel(const QModelIndex &index); public slots: void updateDeviceList(); private: + QVariant drillableStyle(int role) const; + Port *port_; + ArpStatusModel *arpStatusModel_; }; #endif diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 9aa5bf5..789a150 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -32,11 +32,14 @@ DevicesWidget::DevicesWidget(QWidget *parent) deviceGroupList->setVisible(deviceConfig->isChecked()); deviceList->setVisible(deviceInfo->isChecked()); refresh->setVisible(deviceInfo->isChecked()); + deviceDetail->hide(); deviceGroupList->verticalHeader()->setDefaultSectionSize( deviceGroupList->verticalHeader()->minimumSectionSize()); deviceList->verticalHeader()->setDefaultSectionSize( deviceList->verticalHeader()->minimumSectionSize()); + deviceDetail->verticalHeader()->setDefaultSectionSize( + deviceDetail->verticalHeader()->minimumSectionSize()); // Populate DeviceGroup Context Menu Actions deviceGroupList->addAction(actionNewDeviceGroup); @@ -68,6 +71,10 @@ void DevicesWidget::setPortGroupList(PortGroupList *portGroups) SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(updateDeviceViewActions())); + connect(deviceList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(when_deviceList_currentChanged(const QModelIndex&))); + // FIXME: hardcoding deviceGroupList->resizeColumnToContents(1); // Vlan Count deviceGroupList->resizeColumnToContents(2); // Device Count @@ -147,6 +154,7 @@ void DevicesWidget::on_deviceInfo_toggled(bool checked) refresh->setVisible(checked); deviceGroupList->setHidden(checked); deviceList->setVisible(checked); + deviceDetail->hide(); } void DevicesWidget::on_actionNewDeviceGroup_triggered() @@ -215,3 +223,15 @@ void DevicesWidget::on_refresh_clicked() portGroups_->portGroup(curPortGroup) .getDeviceInfo(portGroups_->port(currentPortIndex_).id()); } + +void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index) +{ + if (!index.isValid() || !portGroups_) + return; + + QAbstractItemModel *detailModel = portGroups_->getDeviceModel() + ->detailModel(index); + + deviceDetail->setModel(detailModel); + deviceDetail->setVisible(detailModel != NULL); +} diff --git a/client/deviceswidget.h b/client/deviceswidget.h index fb15ef6..8c7626e 100644 --- a/client/deviceswidget.h +++ b/client/deviceswidget.h @@ -48,6 +48,8 @@ private slots: void on_refresh_clicked(); + void when_deviceList_currentChanged(const QModelIndex &index); + private: PortGroupList *portGroups_; QModelIndex currentPortIndex_; diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 7753e8f..6ac8adf 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -86,7 +86,24 @@ - + + + + 0 + 1 + + + + QAbstractItemView::SelectRows + + + + + + + QAbstractItemView::SingleSelection + + diff --git a/client/ostinato.pro b/client/ostinato.pro index 7c20029..75f0604 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -33,6 +33,7 @@ LIBS += -lprotobuf LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 RESOURCES += ostinato.qrc HEADERS += \ + arpstatusmodel.h \ devicegroupdialog.h \ devicegroupmodel.h \ devicemodel.h \ @@ -73,6 +74,7 @@ FORMS += \ variablefieldswidget.ui SOURCES += \ + arpstatusmodel.cpp \ devicegroupdialog.cpp \ devicegroupmodel.cpp \ devicemodel.cpp \ From 7561b09c1e4a3a488f519651614d39cc50ad271a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Mar 2016 21:16:46 +0530 Subject: [PATCH 080/121] Device Emulation (contd.): Implemented display of NDP Cache Device Detail; also fixed missing clear of NDP cache on server --- client/devicemodel.cpp | 5 +- client/devicemodel.h | 2 + client/ndpstatusmodel.cpp | 155 ++++++++++++++++++++++++++++++++++++++ client/ndpstatusmodel.h | 55 ++++++++++++++ client/ostinato.pro | 2 + server/device.cpp | 1 + 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 client/ndpstatusmodel.cpp create mode 100644 client/ndpstatusmodel.h diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp index a261b4a..d917e80 100644 --- a/client/devicemodel.cpp +++ b/client/devicemodel.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "devicemodel.h" #include "arpstatusmodel.h" +#include "ndpstatusmodel.h" #include "port.h" #include "emulproto.pb.h" @@ -57,6 +58,7 @@ DeviceModel::DeviceModel(QObject *parent) { port_ = NULL; arpStatusModel_ = new ArpStatusModel(this); + ndpStatusModel_ = new NdpStatusModel(this); } int DeviceModel::rowCount(const QModelIndex &parent) const @@ -236,7 +238,6 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const qWarning("%s: Unsupported field #%d", __FUNCTION__, field); -_exit: return QVariant(); } @@ -258,6 +259,8 @@ QAbstractItemModel* DeviceModel::detailModel(const QModelIndex &index) arpStatusModel_->setDeviceIndex(port_, index.row()); return arpStatusModel_; case kNdpInfo: + ndpStatusModel_->setDeviceIndex(port_, index.row()); + return ndpStatusModel_; default: return NULL; } diff --git a/client/devicemodel.h b/client/devicemodel.h index 78637d5..a16a77d 100644 --- a/client/devicemodel.h +++ b/client/devicemodel.h @@ -23,6 +23,7 @@ along with this program. If not, see #include class ArpStatusModel; +class NdpStatusModel; class Port; class DeviceModel: public QAbstractTableModel @@ -49,6 +50,7 @@ private: Port *port_; ArpStatusModel *arpStatusModel_; + NdpStatusModel *ndpStatusModel_; }; #endif diff --git a/client/ndpstatusmodel.cpp b/client/ndpstatusmodel.cpp new file mode 100644 index 0000000..4a48694 --- /dev/null +++ b/client/ndpstatusmodel.cpp @@ -0,0 +1,155 @@ +/* +Copyright (C) 2016 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 "ndpstatusmodel.h" + +#include "port.h" + +#include "emulproto.pb.h" + +#include "uint128.h" +#include + +enum { + kIp4Address, + kMacAddress, + kStatus, + kFieldCount +}; + +static QStringList columns_ = QStringList() + << "IPv6 Address" + << "Mac Address" + << "Status"; + +NdpStatusModel::NdpStatusModel(QObject *parent) + : QAbstractTableModel(parent) +{ + port_ = NULL; + deviceIndex_ = -1; + neighbors_ = NULL; +} + +int NdpStatusModel::rowCount(const QModelIndex &parent) const +{ + if (!port_ || deviceIndex_ < 0 || parent.isValid()) + return 0; + + return port_->numNdp(deviceIndex_); +} + +int NdpStatusModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return columns_.size(); +} + +QVariant NdpStatusModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: + return columns_[section]; + case Qt::Vertical: + return QString("%1").arg(section + 1); + default: + Q_ASSERT(false); // Unreachable + } + + return QVariant(); +} + +QVariant NdpStatusModel::data(const QModelIndex &index, int role) const +{ + QString str; + + if (!port_ || deviceIndex_ < 0 || !index.isValid()) + return QVariant(); + + int ndpIdx = index.row(); + int field = index.column(); + + Q_ASSERT(ndpIdx < port_->numNdp(deviceIndex_)); + Q_ASSERT(field < kFieldCount); + + const OstEmul::NdpEntry &ndp = neighbors_->ndp(ndpIdx); + + switch (field) { + case kIp4Address: + switch (role) { + case Qt::DisplayRole: + return QHostAddress( + UInt128(ndp.ip6().hi(), + ndp.ip6().lo()).toArray()) + .toString(); + default: + break; + } + return QVariant(); + + case kMacAddress: + switch (role) { + case Qt::DisplayRole: + return QString("%1").arg(ndp.mac(), 6*2, 16, QChar('0')) + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:") + .toUpper(); + default: + break; + } + return QVariant(); + + case kStatus: + switch (role) { + case Qt::DisplayRole: + return ndp.mac() ? + QString("Resolved") : QString("Failed"); + default: + break; + } + return QVariant(); + + default: + Q_ASSERT(false); // unreachable! + break; + } + + qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + return QVariant(); +} + +void NdpStatusModel::setDeviceIndex(Port *port, int deviceIndex) +{ + port_ = port; + deviceIndex_ = deviceIndex; + if (port_) + neighbors_ = port_->deviceNeighbors(deviceIndex); + reset(); +} + +void NdpStatusModel::updateNdpStatus() +{ + reset(); +} diff --git a/client/ndpstatusmodel.h b/client/ndpstatusmodel.h new file mode 100644 index 0000000..c00c6b0 --- /dev/null +++ b/client/ndpstatusmodel.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _NDP_STATUS_MODEL_H +#define _NDP_STATUS_MODEL_H + +#include + +class Port; +namespace OstEmul { + class DeviceNeighborList; +} + +class NdpStatusModel: public QAbstractTableModel +{ + Q_OBJECT +public: + NdpStatusModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + + void setDeviceIndex(Port *port, int deviceIndex); + +public slots: + void updateNdpStatus(); + +private: + Port *port_; + int deviceIndex_; + const OstEmul::DeviceNeighborList *neighbors_; +}; + +#endif + diff --git a/client/ostinato.pro b/client/ostinato.pro index 75f0604..e644f62 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -41,6 +41,7 @@ HEADERS += \ dumpview.h \ hexlineedit.h \ mainwindow.h \ + ndpstatusmodel.h \ packetmodel.h \ port.h \ portconfigdialog.h \ @@ -84,6 +85,7 @@ SOURCES += \ hexlineedit.cpp \ main.cpp \ mainwindow.cpp \ + ndpstatusmodel.cpp \ packetmodel.cpp \ port.cpp \ portconfigdialog.cpp \ diff --git a/server/device.cpp b/server/device.cpp index 656531c..34ea989 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -268,6 +268,7 @@ void Device::resolveGateway() void Device::clearNeighbors() { arpTable_.clear(); + ndpTable_.clear(); } // Resolve the Neighbor IP address for this to-be-transmitted pktBuf From 26ceb2f9df03e92fe1b9a9aa4e50bcab8554dabe Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Mar 2016 21:39:01 +0530 Subject: [PATCH 081/121] Device Emulation (contd.): Hide Device Detail view when Escape pressed or Refresh clicked --- client/deviceswidget.cpp | 12 ++++++++++++ client/deviceswidget.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 789a150..7f84a2c 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -24,6 +24,7 @@ along with this program. If not, see #include "portgrouplist.h" #include +#include DevicesWidget::DevicesWidget(QWidget *parent) : QWidget(parent), portGroups_(NULL) @@ -87,6 +88,16 @@ void DevicesWidget::setPortGroupList(PortGroupList *portGroups) updateDeviceViewActions(); } +void DevicesWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + deviceDetail->hide(); + event->accept(); + } + else + event->ignore(); +} + void DevicesWidget::setCurrentPortIndex(const QModelIndex &portIndex) { Port *port = NULL; @@ -220,6 +231,7 @@ void DevicesWidget::on_refresh_clicked() Q_ASSERT(curPortGroup.isValid()); Q_ASSERT(portGroups_->isPortGroup(curPortGroup)); + deviceDetail->hide(); portGroups_->portGroup(curPortGroup) .getDeviceInfo(portGroups_->port(currentPortIndex_).id()); } diff --git a/client/deviceswidget.h b/client/deviceswidget.h index 8c7626e..d5ef524 100644 --- a/client/deviceswidget.h +++ b/client/deviceswidget.h @@ -33,6 +33,8 @@ public: DevicesWidget(QWidget *parent = NULL); void setPortGroupList(PortGroupList *portGroups); + virtual void keyPressEvent(QKeyEvent *event); + public slots: void setCurrentPortIndex(const QModelIndex &portIndex); From 259dafa3e92c4a32b066ba1872131557d1b8207b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 15 Mar 2016 18:15:35 +0530 Subject: [PATCH 082/121] Device Emulation (contd.): Fixed bug where DeviceNeighborList.device_index did not match the correct device in PortDeviceList --- server/devicemanager.cpp | 16 ++++------------ server/devicemanager.h | 4 +++- test/emultest.py | 6 ++++++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 6e140f2..013fe7e 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -26,7 +26,6 @@ along with this program. If not, see #include "../common/emulproto.pb.h" -#include #include #define __STDC_FORMAT_MACROS @@ -162,16 +161,7 @@ int DeviceManager::deviceCount() void DeviceManager::getDeviceList( OstProto::PortDeviceList *deviceList) { - // We want to return a sorted deviceList. However, deviceList_ - // is a QHash (unsorted) instead of a QMap (sorted) because - // QHash is faster. So here we use a temporary QMap to sort the - // list that will be returned - QMap list; - foreach(Device *device, deviceList_) { - list.insert(device->key(), device); - } - - foreach(Device *device, list) { + foreach(Device *device, sortedDeviceList_) { OstEmul::Device *dev = deviceList->AddExtension(OstEmul::port_device); device->getConfig(dev); @@ -279,7 +269,7 @@ void DeviceManager::getDeviceNeighbors( { int count = 0; - foreach(Device *device, deviceList_) { + foreach(Device *device, sortedDeviceList_) { OstEmul::DeviceNeighborList *neighList = neighborList->AddExtension(OstEmul::devices); neighList->set_device_index(count++); @@ -476,6 +466,7 @@ void DeviceManager::enumerateDevices( device = new Device(this); *device = dk; deviceList_.insert(dk.key(), device); + sortedDeviceList_.insert(dk.key(), device); dk.setMac(kBcastMac); bcastList_.insert(dk.key(), device); @@ -491,6 +482,7 @@ void DeviceManager::enumerateDevices( } qDebug("enumerate(del): %s", qPrintable(device->config())); delete device; + sortedDeviceList_.take(dk.key()); // already freed above dk.setMac(kBcastMac); bcastList_.take(dk.key()); // device already freed above diff --git a/server/devicemanager.h b/server/devicemanager.h index 2a9adf5..9d472e6 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -23,6 +23,7 @@ along with this program. If not, see #include "device.h" #include +#include #include #include @@ -71,7 +72,8 @@ private: AbstractPort *port_; QHash deviceGroupList_; - QHash deviceList_; + QHash deviceList_; // fast access to devices + QMap sortedDeviceList_; // sorted access to devices QMultiHash bcastList_; QHash tpidList_; // Key: TPID, Value: RefCount }; diff --git a/test/emultest.py b/test/emultest.py index c54730f..2760edd 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -1166,6 +1166,12 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, assert cap_pkts.count('\n') == 1 os.remove('capture.pcap') +# +# TODO +# * Verify that device_index in OstEmul.DeviceNeighborList matches the +# correct device in OstEmul.PortDeviceList +# * Verify ARP/NDP resolution in a bridging topology +# import pytest pytest.main(__file__) From 8012a9e786a804a53ab1e55f9f7b3fbba4518a0e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 16 Mar 2016 20:51:36 +0530 Subject: [PATCH 083/121] Device Emulation (contd.): Fixed DeviceGroup related RPCs to be invoked only if required by client when "Apply" is clicked --- client/devicegroupdialog.cpp | 4 +-- client/devicegroupmodel.cpp | 10 ++++---- client/port.cpp | 34 +++++++++++++++++++------ client/port.h | 9 ++++--- client/portgroup.cpp | 48 ++++++++++++++++++++++-------------- 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 9ef91fc..bbd2a2c 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -219,7 +219,7 @@ void DeviceGroupDialog::updateIp6Gateway() void DeviceGroupDialog::loadDeviceGroup() { - OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); + const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); int tagCount = 0; Q_ASSERT(devGrp); @@ -285,7 +285,7 @@ void DeviceGroupDialog::loadDeviceGroup() void DeviceGroupDialog::storeDeviceGroup() { - OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); + OstProto::DeviceGroup *devGrp = port_->mutableDeviceGroupByIndex(index_); int tagCount = vlanTagCount->value(); Q_ASSERT(devGrp); diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index 969524b..2c12113 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -97,7 +97,7 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const Q_ASSERT(dgIdx < port_->numDeviceGroups()); Q_ASSERT(field < kFieldCount); - OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx); + const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx); Q_ASSERT(devGrp); @@ -157,8 +157,8 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: if (devGrp->HasExtension(OstEmul::ip4)) return QHostAddress( - devGrp->MutableExtension(OstEmul::ip4) - ->address()).toString(); + devGrp->GetExtension(OstEmul::ip4) + .address()).toString(); else return QString("--"); default: @@ -170,8 +170,8 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::DisplayRole: if (devGrp->HasExtension(OstEmul::ip6)) { - OstEmul::Ip6Address ip = devGrp->MutableExtension( - OstEmul::ip6)->address(); + OstEmul::Ip6Address ip = devGrp->GetExtension( + OstEmul::ip6).address(); return QHostAddress( UInt128(ip.hi(), ip.lo()).toArray()) .toString(); diff --git a/client/port.cpp b/client/port.cpp index ebd4a3b..812153c 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -443,12 +443,10 @@ void Port::getNewDeviceGroupsSinceLastSync( void Port::getModifiedDeviceGroupsSinceLastSync( OstProto::DeviceGroupConfigList &deviceGroupConfigList) { - // FIXME: we currently don't have any mechanism to check - // if a DeviceGroup was modified since last sync, so we - // include all DeviceGroups deviceGroupConfigList.clear_device_group(); - foreach(OstProto::DeviceGroup *dg, deviceGroups_) - deviceGroupConfigList.add_device_group()->CopyFrom(*dg); + foreach(quint32 id, modifiedDeviceGroupList_) + deviceGroupConfigList.add_device_group() + ->CopyFrom(*deviceGroupById(id)); } void Port::when_syncComplete() @@ -464,6 +462,7 @@ void Port::when_syncComplete() lastSyncDeviceGroupList_.append( deviceGroups_.at(i)->device_group_id().id()); } + modifiedDeviceGroupList_.clear(); } void Port::updateStats(OstProto::PortStats *portStats) @@ -671,12 +670,12 @@ uint Port::newDeviceGroupId() return allocDeviceGroupId_++; } -int Port::numDeviceGroups() +int Port::numDeviceGroups() const { return deviceGroups_.size(); } -OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) +const OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) const { if ((index < 0) || (index >= numDeviceGroups())) { qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, @@ -687,6 +686,22 @@ OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) return deviceGroups_.at(index); } +OstProto::DeviceGroup* Port::mutableDeviceGroupByIndex(int index) +{ + if ((index < 0) || (index >= numDeviceGroups())) { + qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__, + index, numDeviceGroups() - 1); + return NULL; + } + + OstProto::DeviceGroup *devGrp = deviceGroups_.at(index); + + // Caller can modify DeviceGroup - assume she will + modifiedDeviceGroupList_.insert(devGrp->device_group_id().id()); + + return devGrp; +} + OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId) { for (int i = 0; i < deviceGroups_.size(); i++) { @@ -715,6 +730,7 @@ bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup) devGrp->mutable_device_group_id()->set_id(newDeviceGroupId()); deviceGroups_.insert(index, devGrp); + modifiedDeviceGroupList_.insert(devGrp->device_group_id().id()); return true; } @@ -724,7 +740,9 @@ bool Port::deleteDeviceGroupAt(int index) if (index < 0 || index >= deviceGroups_.size()) return false; - delete deviceGroups_.takeAt(index); + OstProto::DeviceGroup *devGrp = deviceGroups_.takeAt(index); + modifiedDeviceGroupList_.remove(devGrp->device_group_id().id()); + delete devGrp; return true; } diff --git a/client/port.h b/client/port.h index 55bc325..f155edc 100644 --- a/client/port.h +++ b/client/port.h @@ -23,6 +23,7 @@ along with this program. If not, see #include #include #include +#include #include #include @@ -57,7 +58,8 @@ class Port : public QObject { QList mLastSyncStreamList; QList mStreams; // sorted by stream's ordinal value - QList lastSyncDeviceGroupList_; + QList lastSyncDeviceGroupList_; + QSet modifiedDeviceGroupList_; QList deviceGroups_; QList devices_; QHash deviceNeighbors_; @@ -168,8 +170,9 @@ public: // ------------ Device Group ----------- // uint newDeviceGroupId(); - int numDeviceGroups(); - OstProto::DeviceGroup* deviceGroupByIndex(int index); + int numDeviceGroups() const; + const OstProto::DeviceGroup* deviceGroupByIndex(int index) const; + OstProto::DeviceGroup* mutableDeviceGroupByIndex(int index); OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId); //! Used by StreamModel diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 14d3d4e..29ac0b3 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -394,6 +394,8 @@ void PortGroup::when_configApply(int portIndex) // FIXME: as currently written this code will make unnecessary RPCs // even if the request contains no data; the fix will need to take // care to identify when sync is complete + // NOTE: DeviceGroup RPCs are no longer called unnecessarily; + // Stream RPCs need to be fixed similarly // Also, drone currently updates its packet list at the end of // modifyStream() implicitly assuming that will be the last API // called - this will also need to be fixed @@ -406,38 +408,46 @@ void PortGroup::when_configApply(int portIndex) qDebug("applying 'deleted deviceGroups' ..."); deviceGroupIdList = new OstProto::DeviceGroupIdList; - ack = new OstProto::Ack; - controller = new PbRpcController(deviceGroupIdList, ack); - deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getDeletedDeviceGroupsSinceLastSync(*deviceGroupIdList); - - serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack, - NewCallback(this, &PortGroup::processDeleteDeviceGroupAck, controller)); + if (deviceGroupIdList->device_group_id_size()) { + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupIdList, ack); + serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack, + NewCallback(this, &PortGroup::processDeleteDeviceGroupAck, + controller)); + } + else + delete deviceGroupIdList; qDebug("applying 'new deviceGroups' ..."); deviceGroupIdList = new OstProto::DeviceGroupIdList; - ack = new OstProto::Ack; - controller = new PbRpcController(deviceGroupIdList, ack); - deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getNewDeviceGroupsSinceLastSync(*deviceGroupIdList); - - serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack, - NewCallback(this, &PortGroup::processAddDeviceGroupAck, controller)); + if (deviceGroupIdList->device_group_id_size()) { + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupIdList, ack); + serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack, + NewCallback(this, &PortGroup::processAddDeviceGroupAck, + controller)); + } + else + delete deviceGroupIdList; qDebug("applying 'modified deviceGroups' ..."); deviceGroupConfigList = new OstProto::DeviceGroupConfigList; - ack = new OstProto::Ack; - controller = new PbRpcController(deviceGroupConfigList, ack); - deviceGroupConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getModifiedDeviceGroupsSinceLastSync( *deviceGroupConfigList); - - serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack, - NewCallback(this, &PortGroup::processModifyDeviceGroupAck, - portIndex, controller)); + if (deviceGroupConfigList->device_group_size()) { + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupConfigList, ack); + serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack, + NewCallback(this, &PortGroup::processModifyDeviceGroupAck, + portIndex, controller)); + } + else + delete deviceGroupConfigList; // // Update/Sync Streams From 6a7a17cd369efe333079f246579ff6c3934b5641 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 16 Mar 2016 21:34:36 +0530 Subject: [PATCH 084/121] Device Emulation (contd.): Refresh device info (if reqd.) when "Apply" is clicked --- client/portgroup.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 29ac0b3..8d0c481 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -405,6 +405,7 @@ void PortGroup::when_configApply(int portIndex) // OstProto::DeviceGroupIdList *deviceGroupIdList; OstProto::DeviceGroupConfigList *deviceGroupConfigList; + bool refreshReqd = false; qDebug("applying 'deleted deviceGroups' ..."); deviceGroupIdList = new OstProto::DeviceGroupIdList; @@ -416,6 +417,7 @@ void PortGroup::when_configApply(int portIndex) serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack, NewCallback(this, &PortGroup::processDeleteDeviceGroupAck, controller)); + refreshReqd = true; } else delete deviceGroupIdList; @@ -430,6 +432,7 @@ void PortGroup::when_configApply(int portIndex) serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack, NewCallback(this, &PortGroup::processAddDeviceGroupAck, controller)); + refreshReqd = true; } else delete deviceGroupIdList; @@ -445,10 +448,14 @@ void PortGroup::when_configApply(int portIndex) serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack, NewCallback(this, &PortGroup::processModifyDeviceGroupAck, portIndex, controller)); + refreshReqd = true; } else delete deviceGroupConfigList; + if (refreshReqd) + getDeviceInfo(portIndex); + // // Update/Sync Streams // From 72bab2737ff7337cbb8868391bbd2f113599c126 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 17 Mar 2016 20:12:13 +0530 Subject: [PATCH 085/121] Device Emulation (contd.): 'Resolve Neighbors' will now resend ARP/NDP requests for unresolved entries --- server/abstractport.cpp | 13 +++++++++++++ server/device.cpp | 39 +++++++++++++++++++++++++++++++++------ server/device.h | 7 ++++++- server/devicemanager.cpp | 4 ++-- server/devicemanager.h | 2 +- 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 42e636e..0a97ff9 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -625,6 +625,19 @@ void AbstractPort::clearDeviceNeighbors() void AbstractPort::resolveDeviceNeighbors() { + // For a user triggered 'Resolve Neighbors', the behaviour we want is + // IP not in cache - send ARP/NDP request + // IP present in cache, but unresolved - re-send ARP/NDP request + // IP present in cache and resolved - don't sent ARP/NDP + // + // Device does not resend ARP/NDP requests if the IP address is + // already present in the cache, irrespective of whether it is + // resolved or not (this is done to avoid sending duplicate requests). + // + // So, to get the behaviour we want, let's clear all unresolved neighbors + // before calling resolve + deviceManager_->clearDeviceNeighbors(Device::kUnresolvedNeighbors); + // Resolve gateway for each device first ... deviceManager_->resolveDeviceGateways(); diff --git a/server/device.cpp b/server/device.cpp index 34ea989..6655af3 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -265,10 +265,33 @@ void Device::resolveGateway() sendNeighborSolicit(ip6Gateway_); } -void Device::clearNeighbors() +void Device::clearNeighbors(Device::NeighborSet set) { - arpTable_.clear(); - ndpTable_.clear(); + QMutableHashIterator arpIter(arpTable_); + QMutableHashIterator ndpIter(ndpTable_); + + switch (set) { + case kAllNeighbors: + arpTable_.clear(); + ndpTable_.clear(); + break; + + case kUnresolvedNeighbors: + while (arpIter.hasNext()) { + arpIter.next(); + if (arpIter.value() == 0) + arpIter.remove(); + } + + while (ndpIter.hasNext()) { + ndpIter.next(); + if (ndpIter.value() == 0) + ndpIter.remove(); + } + break; + default: + Q_ASSERT(false); // Unreachable! + } } // Resolve the Neighbor IP address for this to-be-transmitted pktBuf @@ -564,8 +587,12 @@ void Device::sendArpRequest(quint32 tgtIp) if (!tgtIp) return; - // Do we already have a ARP entry (resolved or unresolved)? - // XXX: No NDP state machine for now + // This function will be called once per unique stream - which + // may all have the same dst IP; even if dst IP are different the + // gateway for the different dst IP may all be same. However, + // we don't want to send duplicate ARP requests, so we check + // if the tgtIP is already in the cache (resolved or unresolved) + // and if so, we don't resend it if (arpTable_.contains(tgtIp)) return; @@ -945,7 +972,7 @@ void Device::sendNeighborSolicit(UInt128 tgtIp) return; // Do we already have a NDP entry (resolved or unresolved)? - // XXX: No ARP state machine for now + // If so, don't resend (see note in sendArpRequest()) if (ndpTable_.contains(tgtIp)) return; diff --git a/server/device.h b/server/device.h index fda1a2e..f77615b 100644 --- a/server/device.h +++ b/server/device.h @@ -39,6 +39,11 @@ class Device public: static const quint16 kVlanTpid = 0x8100; + enum NeighborSet { + kAllNeighbors, + kUnresolvedNeighbors + }; + public: Device(DeviceManager *deviceManager); @@ -61,7 +66,7 @@ public: void resolveGateway(); - void clearNeighbors(); + void clearNeighbors(Device::NeighborSet set); void resolveNeighbor(PacketBuffer *pktBuf); void getNeighbors(OstEmul::DeviceNeighborList *neighbors); diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 013fe7e..9db224c 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -258,10 +258,10 @@ void DeviceManager::resolveDeviceGateways() } } -void DeviceManager::clearDeviceNeighbors() +void DeviceManager::clearDeviceNeighbors(Device::NeighborSet set) { foreach(Device *device, deviceList_) - device->clearNeighbors(); + device->clearNeighbors(set); } void DeviceManager::getDeviceNeighbors( diff --git a/server/devicemanager.h b/server/devicemanager.h index 9d472e6..92d4cd0 100644 --- a/server/devicemanager.h +++ b/server/devicemanager.h @@ -55,7 +55,7 @@ public: void resolveDeviceGateways(); - void clearDeviceNeighbors(); + void clearDeviceNeighbors(Device::NeighborSet set = Device::kAllNeighbors); void resolveDeviceNeighbor(PacketBuffer *pktBuf); void getDeviceNeighbors(OstProto::PortNeighborList *neighborList); From c3fe9b03340b30a9f8a3ccd361f372f0ef59f88a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 18 Mar 2016 19:23:26 +0530 Subject: [PATCH 086/121] Device Emulation (contd.): Inform user that src/dst mac resolve requires a corresponding device --- common/mac.ui | 16 +++++++++++++--- common/macconfig.cpp | 12 ++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/common/mac.ui b/common/mac.ui index 5ed2fed..f3604bb 100644 --- a/common/mac.ui +++ b/common/mac.ui @@ -5,8 +5,8 @@ 0 0 - 391 - 116 + 400 + 200 @@ -178,7 +178,17 @@ - + + + + Please ensure that a corresponding device is configured on the port to enable source/destination mac address resolution. A corresponding device is one which has VLANs and source/gateway IP corresponding to this stream. + + + true + + + + Qt::Vertical diff --git a/common/macconfig.cpp b/common/macconfig.cpp index 3728b3d..1e50854 100644 --- a/common/macconfig.cpp +++ b/common/macconfig.cpp @@ -28,6 +28,12 @@ MacConfigForm::MacConfigForm(QWidget *parent) QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); setupUi(this); + resolveInfo->hide(); +#if 0 + // not working for some reason + resolveInfo->setPixmap(resolveInfo->style()->standardIcon( + QStyle::SP_MessageBoxInformation).pixmap(128)); +#endif leDstMac->setValidator(new QRegExpValidator(reMac, this)); leSrcMac->setValidator(new QRegExpValidator(reMac, this)); leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); @@ -63,6 +69,9 @@ void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index) leDstMacStep->setEnabled(true); break; } + resolveInfo->setVisible( + cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve + || cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve); } void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) @@ -84,6 +93,9 @@ void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) leSrcMacStep->setEnabled(true); break; } + resolveInfo->setVisible( + cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve + || cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve); } void MacConfigForm::loadWidget(AbstractProtocol *proto) From f33bd38e7b12095d25e5401327dbfc0cd2ae9351 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 19 Mar 2016 17:31:08 +0530 Subject: [PATCH 087/121] Device Emulation (contd.) - Added icons for the DeviceGroup actions --- client/deviceswidget.ui | 13 ++++++++++++- client/icons/devicegroup_add.png | Bin 0 -> 729 bytes client/icons/devicegroup_delete.png | Bin 0 -> 745 bytes client/icons/devicegroup_edit.png | Bin 0 -> 775 bytes client/ostinato.qrc | 3 +++ 5 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 client/icons/devicegroup_add.png create mode 100644 client/icons/devicegroup_delete.png create mode 100644 client/icons/devicegroup_edit.png diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 6ac8adf..898dec0 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -107,21 +107,32 @@ + + :/icons/devicegroup_add.png + New Device Group + + :/icons/devicegroup_delete.png + Delete Device Group + + :/icons/devicegroup_edit.png + Edit Device Group - + + + diff --git a/client/icons/devicegroup_add.png b/client/icons/devicegroup_add.png new file mode 100644 index 0000000000000000000000000000000000000000..fac186bfb79b7237fcc1ebdbd8e052b994d52f9f GIT binary patch literal 729 zcmV;~0w(>5P)$DtCMgZgg9MW%rA`w{qe=RC&!sIosTV%x&Y5$*@4M&R5da8Mgu~%CZnwK` zGMSDDw5Qlvxg&vldQFc=`4&GHz1 zqa4aTal{E2d+%?xN~Hpa!-4hnbq>4T&SMfsJx<_sI*km-<#Jq+5R1j&@p!Pjyv#v+ zijkXszmH5N1FBhET(k&GslU0oiDWW~R4T<4NnN^593>J7ghC+%0s-iBy1u~diQR4& zJ3Bkr-rh#3R02C6wzjrT9nn4#iJ(v@z~}REq|<3=wOUcI*=(F(e}5l)dwZx>tK4op z9zX2?sZtZEFc=JSkXwyLBT@r7fhdaD-Q7j2)k3{q=c;tHXf(7dbQ@Zxg0jAF6KM@h+4sC6jtTy+RLI zIeishH}wCE^>ao^1fxHPUa!B*=4)mnI-uq<2Oz|6c+rSn>=*hio4|XDI#$RoWYMPX?k900000 LNkvXXu0mjfahOHG literal 0 HcmV?d00001 diff --git a/client/icons/devicegroup_delete.png b/client/icons/devicegroup_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8c373482e071726fcf2e8967eb3c762b224cbe GIT binary patch literal 745 zcmVk(*8wLyDN6qc8lhZhzWexdK7n$>DOH&Eb@W1hKn8Q^d@kW41g?RL@Wbg;d>&1>|H z_R!v=L|lNWxBqtA-QC5~(h_2^7>CVfq3AG<**(V(o?;pYD#p|9T1vg`zE3EEYGZ7mG!H z3=*MUPy!O*&Ws$JzJ?#pH|Ql-(faThp{Xl)KQ;bEU}HMXF2|(VY|c=3_xAQstJQcj zNr2+J?w!YXM#j5wVCD=kJBr$?>u}u~${$}zlgTt}G#X#B*&b|eZgLS4qXw?sl%p51 z|1;Ku7bEOAL%kFKf?lt`%;sxhBkIxJ@OV786E00000NkvXXu0mjf_hDCw literal 0 HcmV?d00001 diff --git a/client/icons/devicegroup_edit.png b/client/icons/devicegroup_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..eb06df3bb3e38de2faf51d80a05e8254d13fb18e GIT binary patch literal 775 zcmV+i1Ni)jP)^9OfoSNCh6N{UH*G>^7ov@7G7j2))Sp-vPxR_~|#}oBaGXXEK?XK|Y^H zHk(B1f~_(T2#W< zvaL<2kX^Wk+SW9-V%M=b+k-`2ClPnyl}cXV$lX>%QAD9oKqix6;&?ocXf%qjM~iIW z4r(PA#H9|DR@zYr^SQ^mz+r?4uyTP%4!Uj$G3|@^f^JjEqDb4hL3OR}pcKA@uPUsqI85-ih+cW#mYc zi0T}yV>}fm2znF>#s1x2Qlrr{snu#3KhP}(-}k}y;R(|2Hi+?dY|h`tqN){Y<$y?p zaw0sq=UXrho&gF8gkM~M(ASI^Qdy9lgzj1YCn7%n-^X;n>!?(5DN%7hg_ zql4Wy8ok7Z25lz}lpb~*ktPRPq%ZQ*4qNZB!+VJ-egn1}Am_E)icons/control_stop.png icons/deco_exclusive.png icons/delete.png + icons/devicegroup_add.png + icons/devicegroup_delete.png + icons/devicegroup_edit.png icons/exit.png icons/gaps.png icons/logo.png From 803242db385e3de39953e62d65c3f59b468272ca Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 19 Mar 2016 18:17:50 +0530 Subject: [PATCH 088/121] Device Emulation (contd.): Use portId as part of the default IP address assigned to a deviceGroup --- client/devicegroupdialog.cpp | 26 +++++++++++++++++--------- common/emulproto.proto | 7 ++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index bbd2a2c..7c66acf 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -221,6 +221,10 @@ void DeviceGroupDialog::loadDeviceGroup() { const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_); int tagCount = 0; + // use 1-indexed id so that it matches the port id used in the + // RFC 4814 compliant mac addresses assigned by default to deviceGroups + // XXX: use deviceGroupId also as part of the id? + quint32 id = (port_->id()+1) & 0xff; Q_ASSERT(devGrp); @@ -255,22 +259,26 @@ void DeviceGroupDialog::loadDeviceGroup() macStep->setValue(mac.step()); OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4); - ip4Address->setValue(ip4.address()); + // If address is not set, assign one from RFC 2544 space - 192.18.0.0/15 + // Use port Id as the 3rd octet of the address + ip4Address->setValue(ip4.has_address() ? + ip4.address() : 0xc6120002 | (id << 8)); ip4PrefixLength->setValue(ip4.prefix_length()); - ip4Step->setValue(ip4.step()); - ip4Gateway->setValue(ip4.default_gateway()); + ip4Step->setValue(ip4.has_step()? ip4.step() : 1); + ip4Gateway->setValue(ip4.has_default_gateway() ? + ip4.default_gateway() : 0xc6120001 | (id << 8)); OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6); - // ip6 fields don't have default values defined in the .proto - // because protobuf doesn't allow different default values for - // embedded message fields, so assign them here - // Use address 2001:0200::/64 from the RFC 5180 range + // If address is not set, assign one from RFC 5180 space 2001:0200::/64 + // Use port Id as the 3rd hextet of the address ip6Address->setValue(ip6.has_address() ? - UINT128(ip6.address()) : UInt128(0x20010200ULL << 32, 2)); + UINT128(ip6.address()) : + UInt128((0x200102000000ULL | id) << 16, 2)); ip6PrefixLength->setValue(ip6.prefix_length()); ip6Step->setValue(ip6.has_step() ? UINT128(ip6.step()) : UInt128(0, 1)); ip6Gateway->setValue(ip6.has_default_gateway() ? - UINT128(ip6.default_gateway()) : UInt128(0x20010200ULL << 32, 1)); + UINT128(ip6.default_gateway()) : + UInt128((0x200102000000ULL | id) << 16, 1)); int stk = kIpNone; if (devGrp->HasExtension(OstEmul::ip4)) diff --git a/common/emulproto.proto b/common/emulproto.proto index 5751f35..864e07e 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -51,10 +51,9 @@ message MacEmulation { } message Ip4Emulation { - // Use RFC 2544 reserved address for default - 198.18.0.2/24 - optional uint32 address = 1 [default = 0xc6120002]; + optional uint32 address = 1; optional uint32 prefix_length = 2 [default = 24]; - optional uint32 default_gateway = 3 [default = 0xc6120001]; + optional uint32 default_gateway = 3; optional uint32 step = 10 [default = 1]; // FIXME: step for gateway? @@ -66,8 +65,6 @@ message Ip6Address { } message Ip6Emulation { - // no defaults since we can't set different default values - // for an embedded message field optional Ip6Address address = 1; optional uint32 prefix_length = 2 [default = 64]; optional Ip6Address default_gateway = 3; From 0e32a1942d12b857ea9ed427d347ec1066f9cb0e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 21 Mar 2016 18:19:15 +0530 Subject: [PATCH 089/121] Device Emulation (contd.): Verify ARP/NDP cache is cleared after clearNeighbors(); also rename dev_cfg var to devcfg to avoid clash with the test param dev_cfg --- test/emultest.py | 140 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 27 deletions(-) diff --git a/test/emultest.py b/test/emultest.py index 2760edd..5768221 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -557,15 +557,58 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # FIXME: find alternative to sleep time.sleep(5) - # resolve ARP on tx/rx ports + # clear ARP/NDP cache + log.info('clearing ARP/NDP cache on tx/rx port') + drone.clearDeviceNeighbors(emul_ports) + + log.info('retrieving ARP/NDP entries on tx port to verify clear') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP/NDP Table on tx port') + for devcfg, device in zip(device_config, devices): + if has_ip4: + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(devcfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert(len(device.arp) == 0) + if has_ip6: + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(devcfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + assert(len(device.ndp) == 0) + + log.info('retrieving ARP/NDP entries on rx port to verify clear') + device_list = drone.getDeviceList(emul_ports.port_id[1]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP/NDP Table on rx port') + for devcfg, device in zip(device_config, devices): + if has_ip4: + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(devcfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert(len(device.arp) == 0) + if has_ip6: + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(devcfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + assert(len(device.ndp) == 0) + + # resolve ARP/NDP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') drone.startCapture(emul_ports) - drone.clearDeviceNeighbors(emul_ports) drone.resolveDeviceNeighbors(emul_ports) time.sleep(3) drone.stopCapture(emul_ports) - # verify ARP Requests sent out from tx port + # verify ARP/NDP Requests sent out from tx port buff = drone.getCaptureBuffer(emul_ports.port_id[0]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Tx capture buffer (all)') @@ -594,7 +637,7 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, print(cap_pkts) assert cap_pkts.count('\n') == 1 - # verify ARP Requests sent out from rx port + # verify ARP/NDP Requests sent out from rx port buff = drone.getCaptureBuffer(emul_ports.port_id[1]) drone.saveCaptureBuffer(buff, 'capture.pcap') log.info('dumping Rx capture buffer (all)') @@ -630,23 +673,23 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on tx port') - for dev_cfg, device in zip(device_config, devices): + for devcfg, device in zip(device_config, devices): if has_ip4: resolved = False for arp in device.arp: print('%s: %s %012x' % - (str(ipaddress.ip_address(dev_cfg.ip4)), + (str(ipaddress.ip_address(devcfg.ip4)), str(ipaddress.ip_address(arp.ip4)), arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac): resolved = True assert resolved if has_ip6: resolved = False for ndp in device.ndp: print('%s: %s %012x' % - (str(ip6_address(dev_cfg.ip6)), + (str(ip6_address(devcfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac): resolved = True assert resolved @@ -656,23 +699,23 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on rx port') - for dev_cfg, device in zip(device_config, devices): + for devcfg, device in zip(device_config, devices): if has_ip4: resolved = False for arp in device.arp: print('%s: %s %012x' % - (str(ipaddress.ip_address(dev_cfg.ip4)), + (str(ipaddress.ip_address(devcfg.ip4)), str(ipaddress.ip_address(arp.ip4)), arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac): resolved = True assert resolved if has_ip6: resolved = False for ndp in device.ndp: print('%s: %s %012x' % - (str(ip6_address(dev_cfg.ip6)), + (str(ip6_address(devcfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac): resolved = True assert resolved @@ -959,10 +1002,53 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, if has_ip6: time.sleep(5) + # clear ARP/NDP cache + log.info('clearing ARP/NDP cache on tx/rx port') + drone.clearDeviceNeighbors(emul_ports) + + log.info('retrieving ARP/NDP entries on tx port to verify clear') + device_list = drone.getDeviceList(emul_ports.port_id[0]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP/NDP Table on tx port') + for devcfg, device in zip(device_config, devices): + if has_ip4: + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(devcfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert(len(device.arp) == 0) + if has_ip6: + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(devcfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + assert(len(device.ndp) == 0) + + log.info('retrieving ARP/NDP entries on rx port to verify clear') + device_list = drone.getDeviceList(emul_ports.port_id[1]) + device_config = device_list.Extensions[emul.port_device] + neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) + devices = neigh_list.Extensions[emul.devices] + log.info('ARP/NDP Table on rx port') + for devcfg, device in zip(device_config, devices): + if has_ip4: + for arp in device.arp: + print('%s: %s %012x' % + (str(ipaddress.ip_address(devcfg.ip4)), + str(ipaddress.ip_address(arp.ip4)), arp.mac)) + assert(len(device.arp) == 0) + if has_ip6: + for ndp in device.ndp: + print('%s: %s %012x' % + (str(ip6_address(devcfg.ip6)), + str(ip6_address(ndp.ip6)), ndp.mac)) + assert(len(device.ndp) == 0) + # resolve ARP/NDP on tx/rx ports log.info('resolving Neighbors on tx/rx ports ...') drone.startCapture(emul_ports) - drone.clearDeviceNeighbors(emul_ports) drone.resolveDeviceNeighbors(emul_ports) time.sleep(3) drone.stopCapture(emul_ports) @@ -1042,26 +1128,26 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on tx port') - for dev_cfg, device in zip(device_config, devices): + for devcfg, device in zip(device_config, devices): vlans = '' - for v in dev_cfg.vlan: + for v in devcfg.vlan: vlans += str(v & 0xffff) + ' ' if has_ip4: resolved = False for arp in device.arp: print('%s%s: %s %012x' % - (vlans, str(ipaddress.ip_address(dev_cfg.ip4)), + (vlans, str(ipaddress.ip_address(devcfg.ip4)), str(ipaddress.ip_address(arp.ip4)), arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac): resolved = True assert resolved if has_ip6: resolved = False for ndp in device.ndp: print('%s%s: %s %012x' % - (vlans, str(ip6_address(dev_cfg.ip6)), + (vlans, str(ip6_address(devcfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac): resolved = True assert resolved @@ -1071,26 +1157,26 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) devices = neigh_list.Extensions[emul.devices] log.info('ARP/NDP Table on rx port') - for dev_cfg, device in zip(device_config, devices): + for devcfg, device in zip(device_config, devices): vlans = '' - for v in dev_cfg.vlan: + for v in devcfg.vlan: vlans += str(v & 0xffff) + ' ' if has_ip4: resolved = False for arp in device.arp: print('%s%s: %s %012x' % - (vlans, str(ipaddress.ip_address(dev_cfg.ip4)), + (vlans, str(ipaddress.ip_address(devcfg.ip4)), str(ipaddress.ip_address(arp.ip4)), arp.mac)) - if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac): + if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac): resolved = True assert resolved if has_ip6: resolved = False for ndp in device.ndp: print('%s%s: %s %012x' % - (vlans, str(ip6_address(dev_cfg.ip6)), + (vlans, str(ip6_address(devcfg.ip6)), str(ip6_address(ndp.ip6)), ndp.mac)) - if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac): + if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac): resolved = True assert resolved From ce7aee54e58c4ef9c619da36cc7c36ea16fa8be2 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 22 Mar 2016 18:27:36 +0530 Subject: [PATCH 090/121] Device Emulation (contd.): Changed the default app window size and ports-list/port-detail split to minimize scroll bars; a larger height would be better but capped it to 1024x600 (netbook resolution) --- client/mainwindow.ui | 12 ++---------- client/portswindow.ui | 12 ++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/mainwindow.ui b/client/mainwindow.ui index 82dfc93..a3fcb78 100644 --- a/client/mainwindow.ui +++ b/client/mainwindow.ui @@ -5,8 +5,8 @@ 0 0 - 700 - 550 + 1024 + 600 @@ -17,14 +17,6 @@ - - - 0 - 0 - 700 - 21 - - &File diff --git a/client/portswindow.ui b/client/portswindow.ui index 7835329..cbab508 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -22,6 +22,12 @@ false + + + 1 + 0 + + Qt::ActionsContextMenu @@ -30,6 +36,12 @@ + + + 2 + 0 + + 0 From fbb56720ba6c767df3e13a88bf6f258e2899d194 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 22 Mar 2016 20:18:53 +0530 Subject: [PATCH 091/121] Device Emulation (contd.): Renamed Device List and Device Neighbor List fields so that the names are better reflective of what they contain --- client/portgroup.cpp | 8 ++++---- common/emulproto.proto | 5 ++--- server/devicemanager.cpp | 4 ++-- test/emultest.py | 34 ++++++++++++++++++---------------- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 8d0c481..bc6d46c 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -594,9 +594,9 @@ void PortGroup::processDeviceList(int portIndex, PbRpcController *controller) } mPorts[portIndex]->clearDeviceList(); - for(int i = 0; i < deviceList->ExtensionSize(OstEmul::port_device); i++) { + for(int i = 0; i < deviceList->ExtensionSize(OstEmul::device); i++) { mPorts[portIndex]->insertDevice( - deviceList->GetExtension(OstEmul::port_device, i)); + deviceList->GetExtension(OstEmul::device, i)); } _exit: @@ -628,9 +628,9 @@ void PortGroup::processDeviceNeighbors( } mPorts[portIndex]->clearDeviceNeighbors(); - for(int i = 0; i < neighList->ExtensionSize(OstEmul::devices); i++) { + for(int i=0; i < neighList->ExtensionSize(OstEmul::device_neighbor); i++) { mPorts[portIndex]->insertDeviceNeighbors( - neighList->GetExtension(OstEmul::devices, i)); // FIXME: change extn id + neighList->GetExtension(OstEmul::device_neighbor, i)); } mPorts[portIndex]->deviceInfoRefreshed(); diff --git a/common/emulproto.proto b/common/emulproto.proto index 864e07e..8820697 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -95,7 +95,7 @@ message Device { } extend OstProto.PortDeviceList { - repeated Device port_device = 100; + repeated Device device = 100; } message ArpEntry { @@ -115,6 +115,5 @@ message DeviceNeighborList { } extend OstProto.PortNeighborList { - // FIXME: rename to device (singular) to be consistent with naming convention for other repeated fields - repeated DeviceNeighborList devices = 100; + repeated DeviceNeighborList device_neighbor = 100; } diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index 9db224c..f0f9b1e 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -163,7 +163,7 @@ void DeviceManager::getDeviceList( { foreach(Device *device, sortedDeviceList_) { OstEmul::Device *dev = - deviceList->AddExtension(OstEmul::port_device); + deviceList->AddExtension(OstEmul::device); device->getConfig(dev); } } @@ -271,7 +271,7 @@ void DeviceManager::getDeviceNeighbors( foreach(Device *device, sortedDeviceList_) { OstEmul::DeviceNeighborList *neighList = - neighborList->AddExtension(OstEmul::devices); + neighborList->AddExtension(OstEmul::device_neighbor); neighList->set_device_index(count++); device->getNeighbors(neighList); } diff --git a/test/emultest.py b/test/emultest.py index 5768221..1124354 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -563,9 +563,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, log.info('retrieving ARP/NDP entries on tx port to verify clear') device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on tx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -583,9 +583,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, log.info('retrieving ARP/NDP entries on rx port to verify clear') device_list = drone.getDeviceList(emul_ports.port_id[1]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on rx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -669,9 +669,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # retrieve and verify ARP/NDP Table on tx/rx ports log.info('retrieving ARP/NDP entries on tx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on tx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -695,9 +695,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, log.info('retrieving ARP/NDP entries on rx port') device_list = drone.getDeviceList(emul_ports.port_id[1]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on rx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -1008,9 +1008,9 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, log.info('retrieving ARP/NDP entries on tx port to verify clear') device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on tx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -1028,9 +1028,9 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, log.info('retrieving ARP/NDP entries on rx port to verify clear') device_list = drone.getDeviceList(emul_ports.port_id[1]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on rx port') for devcfg, device in zip(device_config, devices): if has_ip4: @@ -1124,9 +1124,9 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, # retrieve and verify ARP/NDP Table on tx/rx ports log.info('retrieving ARP/NDP entries on tx port') device_list = drone.getDeviceList(emul_ports.port_id[0]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on tx port') for devcfg, device in zip(device_config, devices): vlans = '' @@ -1153,9 +1153,9 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, log.info('retrieving ARP entries on rx port') device_list = drone.getDeviceList(emul_ports.port_id[1]) - device_config = device_list.Extensions[emul.port_device] + device_config = device_list.Extensions[emul.device] neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1]) - devices = neigh_list.Extensions[emul.devices] + devices = neigh_list.Extensions[emul.device_neighbor] log.info('ARP/NDP Table on rx port') for devcfg, device in zip(device_config, devices): vlans = '' @@ -1257,6 +1257,8 @@ def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports, # * Verify that device_index in OstEmul.DeviceNeighborList matches the # correct device in OstEmul.PortDeviceList # * Verify ARP/NDP resolution in a bridging topology +# * Verify non-default IP prefix-length +# * Verify bi-directional streams # import pytest pytest.main(__file__) From 8b80b4c3ef5b64d866b1069e94cae38c620b3677 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 24 Mar 2016 18:47:43 +0530 Subject: [PATCH 092/121] Device Emulation (contd.): Checked and decided on a couple of FIXMEs/TODOs --- binding/core.py | 2 +- client/devicegroupmodel.cpp | 3 ++- common/emulproto.proto | 3 +++ common/protocol.proto | 5 ----- server/devicemanager.cpp | 2 -- server/packetbuffer.cpp | 1 - server/pcapport.cpp | 20 +++++++++++--------- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/binding/core.py b/binding/core.py index 93c542c..c89020b 100644 --- a/binding/core.py +++ b/binding/core.py @@ -18,7 +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? +import protocols.emulproto_pb2 as emul from __init__ import __version__ class DroneProxy(object): diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index 2c12113..8749d74 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -200,7 +200,8 @@ bool DeviceGroupModel::setData( if (!port_) return false; - // FIXME + // TODO; when implementing also implement flags() to + // return ItemIsEditable return false; } diff --git a/common/emulproto.proto b/common/emulproto.proto index 8820697..8c34d9b 100644 --- a/common/emulproto.proto +++ b/common/emulproto.proto @@ -50,6 +50,9 @@ message MacEmulation { optional uint64 step = 10 [default = 1]; } +// No default values for IP addresses - user needs to explicitly set that +// 'coz we derive if a device has a single/dual or no IP stack on the basis +// of whether OstProto.DeviceGroup.ip[46] is set message Ip4Emulation { optional uint32 address = 1; optional uint32 prefix_length = 2 [default = 24]; diff --git a/common/protocol.proto b/common/protocol.proto index b9980ab..96d25bb 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -277,11 +277,6 @@ message Notification { /* * 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 addDeviceGroup()? - * FIXME: decide default values for device and protoEmulations - * FIXME: rename 'DeviceGroup'? * FIXME: merge getDeviceList() and get DeviceNeighbors() into a single * getDeviceInfo() RPC? */ diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index f0f9b1e..95a2420 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -454,8 +454,6 @@ void DeviceManager::enumerateDevices( ip6.prefix_length(), UINT128(ip6.default_gateway())); - // TODO: fill in other pbDevice data - switch (oper) { case kAdd: if (deviceList_.contains(dk.key())) { diff --git a/server/packetbuffer.cpp b/server/packetbuffer.cpp index 6c4c0d2..0480de7 100644 --- a/server/packetbuffer.cpp +++ b/server/packetbuffer.cpp @@ -76,7 +76,6 @@ uchar* PacketBuffer::end() const void PacketBuffer::reserve(int len) { - // FIXME: add validation data_ += len; tail_ += len; } diff --git a/server/pcapport.cpp b/server/pcapport.cpp index ae0e0c7..9fb13dc 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -947,8 +947,14 @@ void PcapPort::EmulationTransceiver::run() char errbuf[PCAP_ERRBUF_SIZE] = ""; struct bpf_program bpf; #if 0 + const char *capture_filter = + "arp or icmp or icmp6 or " + "(vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and vlan and (arp or icmp or icmp6)) or " + "(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))"; /* - Ideally we should use the below filter, but the 'vlan' capture filter + Ideally we should use the above filter, but the 'vlan' capture filter in libpcap is implemented as a kludge. From the pcap-filter man page - vlan [vlan_id] @@ -965,12 +971,6 @@ void PcapPort::EmulationTransceiver::run() So we use the modified filter expression that works as we intend. If ever libpcap changes their implementation, this will need to change as well. */ - const char *capture_filter = - "arp or icmp or icmp6 or " - "(vlan and (arp or icmp or icmp6)) or " - "(vlan and vlan and (arp or icmp or icmp6)) or " - "(vlan and vlan and vlan and (arp or icmp or icmp6)) or " - "(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))"; #else const char *capture_filter = "arp or icmp or icmp6 or " @@ -989,7 +989,6 @@ void PcapPort::EmulationTransceiver::run() #endif _retry: - // FIXME: use 0 timeout value? #ifdef Q_OS_WIN32 // NOCAPTURE_LOCAL needs windows only pcap_open() handle_ = pcap_open(qPrintable(device_), 65535, @@ -1024,7 +1023,10 @@ _retry: } } - // FIXME: hardcoded filter + // TODO: for now the filter is hardcoded to accept tagged/untagged + // ARP/NDP or ICMPv4/v6; when more protocols are added, we may need + // to derive this filter based on which protocols are configured + // on the devices if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0) { qWarning("%s: error compiling filter: %s", qPrintable(device_), From 321fc3e1ee2c2ee47075da96371c83e5459cba5e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 24 Mar 2016 18:52:48 +0530 Subject: [PATCH 093/121] Device Emulation (contd.): DeviceGroupDialog uses same defaults for VLAN config as the .proto --- client/devicegroupdialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp index 7c66acf..f5b7f2b 100644 --- a/client/devicegroupdialog.cpp +++ b/client/devicegroupdialog.cpp @@ -70,10 +70,11 @@ DeviceGroupDialog::DeviceGroupDialog( vlans->setColumnCount(kVlanColumns); vlans->setHorizontalHeaderLabels(vlanTableColumnHeaders); for (int i = 0; i < kMaxVlanTags; i++) { + // Use same default values as defined in .proto vlans->setItem(i, kVlanId, new QTableWidgetItem(QString::number(100*(i+1)))); vlans->setItem(i, kVlanCount, - new QTableWidgetItem(QString::number(10))); + new QTableWidgetItem(QString::number(1))); vlans->setItem(i, kVlanStep, new QTableWidgetItem(QString::number(1))); vlans->setItem(i, kVlanCfi, From 72a6381c883046d876fe583f22b15ca84ad57c31 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 24 Mar 2016 18:58:28 +0530 Subject: [PATCH 094/121] Device Emulation (contd.): Retrieve Device Information on connect to drone --- client/portgroup.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index bc6d46c..bf65843 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -972,6 +972,9 @@ void PortGroup::processDeviceGroupConfigList(int portIndex, devGrpCfgList->mutable_device_group(i)); } + if (devGrpCfgList->device_group_size()) + getDeviceInfo(portIndex); + #if 0 // FIXME: incorrect check - will never be true if last port does not have any deviceGroups configured // Are we done for all ports? From 5b46bdd8fc8d3f04c20c203875b0133709abf64a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 27 Mar 2016 11:23:56 +0530 Subject: [PATCH 095/121] Save/Open Session - Defined the file format for a session file --- common/fileformat.proto | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/common/fileformat.proto b/common/fileformat.proto index ce2a688..18e7d97 100644 --- a/common/fileformat.proto +++ b/common/fileformat.proto @@ -24,6 +24,7 @@ package OstProto; enum FileType { kReservedFileType = 0; kStreamsFileType = 1; + kSessionFileType = 10; } message FileMetaData { @@ -36,8 +37,28 @@ message FileMetaData { required string generator_revision = 7; } +message PortContent { + optional Port port_config = 1; + repeated Stream streams = 2; + repeated DeviceGroup device_groups = 3; +} + +message PortGroupContent { + optional string server_name = 1; + repeated PortContent ports = 2; +} + +message SessionContent { + repeated PortGroupContent port_groups = 1; +} + message FileContentMatter { optional StreamConfigList streams = 1; + // TODO: optional DeviceGroupConfigList device_groups = 2; + // TODO: optional PortContent port = 3; + // FIXME: (single) portgroup? is there a usecase for this? + + optional SessionContent session = 10; } /* From c98104f07855c5a04ee8852500ed07449869f7fb Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 28 Mar 2016 21:23:10 +0530 Subject: [PATCH 096/121] Refactored native streams file format to share code with native session file format. Added skeletal code for session/ossn file format --- common/fileformat.cpp | 405 +----------------------------- common/fileformat.h | 23 +- common/nativefileformat.cpp | 470 +++++++++++++++++++++++++++++++++++ common/nativefileformat.h | 75 ++++++ common/ossnfileformat.cpp | 46 ++++ common/ossnfileformat.h | 40 +++ common/ostprotogui.pro | 6 + common/sessionfileformat.cpp | 111 +++++++++ common/sessionfileformat.h | 90 +++++++ 9 files changed, 848 insertions(+), 418 deletions(-) create mode 100644 common/nativefileformat.cpp create mode 100644 common/nativefileformat.h create mode 100644 common/ossnfileformat.cpp create mode 100644 common/ossnfileformat.h create mode 100644 common/sessionfileformat.cpp create mode 100644 common/sessionfileformat.h diff --git a/common/fileformat.cpp b/common/fileformat.cpp index 4edd980..2ccf270 100644 --- a/common/fileformat.cpp +++ b/common/fileformat.cpp @@ -19,160 +19,23 @@ along with this program. If not, see #include "fileformat.h" -#include "crc32c.h" - -#include -#include -#include - -#include - -const std::string FileFormat::kFileMagicValue = "\xa7\xb7OSTINATO"; - FileFormat fileFormat; -const int kBaseHex = 16; - FileFormat::FileFormat() + : AbstractFileFormat(), NativeFileFormat() { - /* - * We don't have any "real" work to do here in the constructor. - * What we do is run some "assert" tests so that these get caught - * at init itself instead of while saving/restoring when a user - * might lose some data! - */ - OstProto::FileMagic magic; - OstProto::FileChecksum cksum; - - magic.set_value(kFileMagicValue); - cksum.set_value(quint32(0)); - - // TODO: convert Q_ASSERT to something that will run in RELEASE mode also - Q_ASSERT(magic.IsInitialized()); - Q_ASSERT(cksum.IsInitialized()); - Q_ASSERT(magic.ByteSize() == kFileMagicSize); - Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); -} - -FileFormat::~FileFormat() -{ + // Do Nothing! } bool FileFormat::openStreams(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { - QFile file(fileName); - QByteArray buf; - int size, contentOffset, contentSize; - quint32 calcCksum; - OstProto::FileMagic magic; OstProto::FileMeta meta; OstProto::FileContent content; - OstProto::FileChecksum cksum, zeroCksum; - - if (!file.open(QIODevice::ReadOnly)) - goto _open_fail; - - if (file.size() < kFileMagicSize) - goto _magic_missing; - - if (file.size() < kFileMinSize) - goto _checksum_missing; - - buf.resize(file.size()); - size = file.read(buf.data(), buf.size()); - if (size < 0) - goto _read_fail; - - Q_ASSERT(file.atEnd()); - file.close(); - - qDebug("%s: file.size() = %lld", __FUNCTION__, file.size()); - qDebug("%s: size = %d", __FUNCTION__, size); - - //qDebug("Read %d bytes", buf.size()); - //qDebug("%s", QString(buf.toHex()).toAscii().constData()); - - // Parse and verify magic - if (!magic.ParseFromArray( - (void*)(buf.constData() + kFileMagicOffset), - kFileMagicSize)) - { - goto _magic_parse_fail; - } - if (magic.value() != kFileMagicValue) - goto _magic_match_fail; - - // Parse and verify checksum - if (!cksum.ParseFromArray( - (void*)(buf.constData() + size - kFileChecksumSize), - kFileChecksumSize)) - { - goto _cksum_parse_fail; - } - - zeroCksum.set_value(0); - if (!zeroCksum.SerializeToArray( - (void*) (buf.data() + size - kFileChecksumSize), - kFileChecksumSize)) - { - goto _zero_cksum_serialize_fail; - } + bool ret = NativeFileFormat::open(fileName, meta, content, error); - calcCksum = checksumCrc32C((quint8*) buf.constData(), size); - - qDebug("checksum \nExpected:%x Actual:%x", - calcCksum, cksum.value()); - - if (cksum.value() != calcCksum) - goto _cksum_verify_fail; - - // Parse the metadata first before we parse the full contents - if (!meta.ParseFromArray( - (void*)(buf.constData() + kFileMetaDataOffset), - size - kFileMetaDataOffset)) - { - goto _metadata_parse_fail; - } - - qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__, - QString().fromStdString(meta.DebugString()).toAscii().constData()); - - // MetaData Validation(s) - if (meta.data().file_type() != OstProto::kStreamsFileType) - goto _unexpected_file_type; - - if (meta.data().format_version_major() != kFileFormatVersionMajor) - goto _incompatible_file_version; - - if (meta.data().format_version_minor() > kFileFormatVersionMinor) - goto _incompatible_file_version; - - if (meta.data().format_version_minor() < kFileFormatVersionMinor) - { - // TODO: need to modify 'buf' such that we can parse successfully - // assuming the native minor version - } - - if (meta.data().format_version_revision() > kFileFormatVersionRevision) - { - error = QString(tr("%1 was created using a newer version of Ostinato." - " New features/protocols will not be available.")).arg(fileName); - } - - Q_ASSERT(meta.data().format_version_major() == kFileFormatVersionMajor); - - // ByteSize() does not include the Tag/Key, so we add 2 for that - contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2; - contentSize = size - contentOffset - kFileChecksumSize; - - // Parse full contents - if (!content.ParseFromArray( - (void*)(buf.constData() + contentOffset), - contentSize)) - { - goto _content_parse_fail; - } + if (!ret) + goto _fail; if (!content.matter().has_streams()) goto _missing_streams; @@ -186,75 +49,6 @@ bool FileFormat::openStreams(const QString fileName, _missing_streams: error = QString(tr("%1 does not contain any streams")).arg(fileName); goto _fail; -_content_parse_fail: - error = QString(tr("Failed parsing %1 contents")).arg(fileName); - qDebug("Error: %s", QString().fromStdString( - content.matter().InitializationErrorString()) - .toAscii().constData()); - qDebug("Debug: %s", QString().fromStdString( - content.matter().DebugString()).toAscii().constData()); - goto _fail; -_incompatible_file_version: - error = QString(tr("%1 is in an incompatible format version - %2.%3.%4" - " (Native version is %5.%6.%7)")) - .arg(fileName) - .arg(meta.data().format_version_major()) - .arg(meta.data().format_version_minor()) - .arg(meta.data().format_version_revision()) - .arg(kFileFormatVersionMajor) - .arg(kFileFormatVersionMinor) - .arg(kFileFormatVersionRevision); - goto _fail; -_unexpected_file_type: - error = QString(tr("%1 is not a streams file")).arg(fileName); - goto _fail; -_metadata_parse_fail: - error = QString(tr("Failed parsing %1 meta data")).arg(fileName); - qDebug("Error: %s", QString().fromStdString( - meta.data().InitializationErrorString()) - .toAscii().constData()); - goto _fail; -_cksum_verify_fail: - error = QString(tr("%1 checksum validation failed!\nExpected:%2 Actual:%3")) - .arg(fileName) - .arg(calcCksum, 0, kBaseHex) - .arg(cksum.value(), 0, kBaseHex); - goto _fail; -_zero_cksum_serialize_fail: - error = QString(tr("Internal Error: Zero Checksum Serialize failed!\n" - "Error: %1\nDebug: %2")) - .arg(QString().fromStdString( - cksum.InitializationErrorString())) - .arg(QString().fromStdString(cksum.DebugString())); - goto _fail; -_cksum_parse_fail: - error = QString(tr("Failed parsing %1 checksum")).arg(fileName); - qDebug("Error: %s", QString().fromStdString( - cksum.InitializationErrorString()) - .toAscii().constData()); - goto _fail; -_magic_match_fail: - error = QString(tr("%1 is not an Ostinato file")).arg(fileName); - goto _fail; -_magic_parse_fail: - error = QString(tr("%1 does not look like an Ostinato file")).arg(fileName); - qDebug("Error: %s", QString().fromStdString( - magic.InitializationErrorString()) - .toAscii().constData()); - goto _fail; -_read_fail: - error = QString(tr("Error reading from %1")).arg(fileName); - goto _fail; -_checksum_missing: - error = QString(tr("%1 is too small (missing checksum)")).arg(fileName); - goto _fail; -_magic_missing: - error = QString(tr("%1 is too small (missing magic value)")) - .arg(fileName); - goto _fail; -_open_fail: - error = QString(tr("Error opening %1")).arg(fileName); - goto _fail; _fail: qDebug("%s", error.toAscii().constData()); return false; @@ -263,25 +57,7 @@ _fail: bool FileFormat::saveStreams(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { - OstProto::FileMagic magic; - OstProto::FileMeta meta; OstProto::FileContent content; - OstProto::FileChecksum cksum; - QFile file(fileName); - int metaSize, contentSize; - int contentOffset, cksumOffset; - QByteArray buf; - quint32 calcCksum; - - magic.set_value(kFileMagicValue); - Q_ASSERT(magic.IsInitialized()); - - cksum.set_value(0); - Q_ASSERT(cksum.IsInitialized()); - - initFileMetaData(*(meta.mutable_data())); - meta.mutable_data()->set_file_type(OstProto::kStreamsFileType); - Q_ASSERT(meta.IsInitialized()); if (!streams.IsInitialized()) goto _stream_not_init; @@ -289,104 +65,9 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams, content.mutable_matter()->mutable_streams()->CopyFrom(streams); Q_ASSERT(content.IsInitialized()); - metaSize = meta.ByteSize(); - contentSize = content.ByteSize(); - contentOffset = kFileMetaDataOffset + metaSize; - cksumOffset = contentOffset + contentSize; + return NativeFileFormat::save(OstProto::kStreamsFileType, content, + fileName, error); - Q_ASSERT(magic.ByteSize() == kFileMagicSize); - Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); - buf.resize(kFileMagicSize + metaSize + contentSize + kFileChecksumSize); - - // Serialize everything - if (!magic.SerializeToArray((void*) (buf.data() + kFileMagicOffset), - kFileMagicSize)) - { - goto _magic_serialize_fail; - } - - if (!meta.SerializeToArray((void*) (buf.data() + kFileMetaDataOffset), - metaSize)) - { - goto _meta_serialize_fail; - } - - if (!content.SerializeToArray((void*) (buf.data() + contentOffset), - contentSize)) - { - goto _content_serialize_fail; - } - - if (!cksum.SerializeToArray((void*) (buf.data() + cksumOffset), - kFileChecksumSize)) - { - goto _zero_cksum_serialize_fail; - } - - emit status("Calculating checksum..."); - - // Calculate and write checksum - calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size()); - cksum.set_value(calcCksum); - if (!cksum.SerializeToArray( - (void*) (buf.data() + cksumOffset), - kFileChecksumSize)) - { - goto _cksum_serialize_fail; - } - - qDebug("Writing %d bytes", buf.size()); - //qDebug("%s", QString(buf.toHex()).toAscii().constData()); - - emit status("Writing to disk..."); - if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) - goto _open_fail; - - if (file.write(buf) < 0) - goto _write_fail; - - file.close(); - - return true; - -_write_fail: - error = QString(tr("Error writing to %1")).arg(fileName); - goto _fail; -_open_fail: - error = QString(tr("Error opening %1 (Error Code = %2)")) - .arg(fileName) - .arg(file.error()); - goto _fail; -_cksum_serialize_fail: - error = QString(tr("Internal Error: Checksum Serialize failed\n%1\n%2")) - .arg(QString().fromStdString( - cksum.InitializationErrorString())) - .arg(QString().fromStdString(cksum.DebugString())); - goto _fail; -_zero_cksum_serialize_fail: - error = QString(tr("Internal Eror: Zero Checksum Serialize failed\n%1\n%2")) - .arg(QString().fromStdString( - cksum.InitializationErrorString())) - .arg(QString().fromStdString(cksum.DebugString())); - goto _fail; -_content_serialize_fail: - error = QString(tr("Internal Error: Content Serialize failed\n%1\n%2")) - .arg(QString().fromStdString( - content.InitializationErrorString())) - .arg(QString().fromStdString(content.DebugString())); - goto _fail; -_meta_serialize_fail: - error = QString(tr("Internal Error: Meta Data Serialize failed\n%1\n%2")) - .arg(QString().fromStdString( - meta.InitializationErrorString())) - .arg(QString().fromStdString(meta.DebugString())); - goto _fail; -_magic_serialize_fail: - error = QString(tr("Internal Error: Magic Serialize failed\n%1\n%2")) - .arg(QString().fromStdString( - magic.InitializationErrorString())) - .arg(QString().fromStdString(magic.DebugString())); - goto _fail; _stream_not_init: error = QString(tr("Internal Error: Streams not initialized\n%1\n%2")) .arg(QString().fromStdString( @@ -400,26 +81,7 @@ _fail: bool FileFormat::isMyFileFormat(const QString fileName) { - bool ret = false; - QFile file(fileName); - QByteArray buf; - OstProto::FileMagic magic; - - if (!file.open(QIODevice::ReadOnly)) - goto _exit; - - buf = file.peek(kFileMagicOffset + kFileMagicSize); - if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset), - kFileMagicSize)) - goto _close_exit; - - if (magic.value() == kFileMagicValue) - ret = true; - -_close_exit: - file.close(); -_exit: - return ret; + return isNativeFileFormat(fileName, OstProto::kStreamsFileType); } bool FileFormat::isMyFileType(const QString fileType) @@ -430,54 +92,3 @@ bool FileFormat::isMyFileType(const QString fileType) return false; } -void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData) -{ - // Fill in the "native" file format version - metaData.set_format_version_major(kFileFormatVersionMajor); - metaData.set_format_version_minor(kFileFormatVersionMinor); - metaData.set_format_version_revision(kFileFormatVersionRevision); - - metaData.set_generator_name( - qApp->applicationName().toUtf8().constData()); - metaData.set_generator_version( - qApp->property("version").toString().toUtf8().constData()); - metaData.set_generator_revision( - qApp->property("revision").toString().toUtf8().constData()); -} - -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -/*! Fixup content to what is expected in the native version */ -void FileFormat::postParseFixup(OstProto::FileMetaData metaData, - OstProto::FileContent &content) -{ - Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor); - - // Do fixups from oldest to newest versions - switch (metaData.format_version_minor()) - { - case 1: - { - int n = content.matter().streams().stream_size(); - for (int i = 0; i < n; i++) - { - OstProto::StreamControl *sctl = - content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control(); - sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec()); - sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec()); - } - - // fall-through to next higher version until native version - } - case kFileFormatVersionMinor: // native version - break; - - case 0: - default: - qWarning("%s: minor version %u unhandled", __FUNCTION__, - metaData.format_version_minor()); - Q_ASSERT_X(false, "postParseFixup", "unhandled minor version"); - } - -} -#pragma GCC diagnostic warning "-Wdeprecated-declarations" - diff --git a/common/fileformat.h b/common/fileformat.h index f2c5b32..95eb310 100644 --- a/common/fileformat.h +++ b/common/fileformat.h @@ -20,14 +20,14 @@ along with this program. If not, see #define _FILE_FORMAT_H #include "abstractfileformat.h" +#include "nativefileformat.h" #include "fileformat.pb.h" -class FileFormat : public AbstractFileFormat +class FileFormat : public AbstractFileFormat, public NativeFileFormat { public: FileFormat(); - ~FileFormat(); virtual bool openStreams(const QString fileName, OstProto::StreamConfigList &streams, QString &error); @@ -36,25 +36,6 @@ public: bool isMyFileFormat(const QString fileName); bool isMyFileType(const QString fileType); - -private: - void initFileMetaData(OstProto::FileMetaData &metaData); - void postParseFixup(OstProto::FileMetaData metaData, - OstProto::FileContent &content); - - static const int kFileMagicSize = 12; - static const int kFileChecksumSize = 5; - static const int kFileMinSize = kFileMagicSize + kFileChecksumSize; - - static const int kFileMagicOffset = 0; - static const int kFileMetaDataOffset = kFileMagicSize; - - static const std::string kFileMagicValue; - - // Native file format version - static const uint kFileFormatVersionMajor = 0; - static const uint kFileFormatVersionMinor = 2; - static const uint kFileFormatVersionRevision = 4; }; extern FileFormat fileFormat; diff --git a/common/nativefileformat.cpp b/common/nativefileformat.cpp new file mode 100644 index 0000000..83e13ba --- /dev/null +++ b/common/nativefileformat.cpp @@ -0,0 +1,470 @@ +/* +Copyright (C) 2010, 2016 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 "nativefileformat.h" + +#include "crc32c.h" + +#include +#include +#include + +#define tr(str) QObject::tr(str) + +const std::string NativeFileFormat::kFileMagicValue = "\xa7\xb7OSTINATO"; + +static const int kBaseHex = 16; + +NativeFileFormat::NativeFileFormat() +{ + /* + * We don't have any "real" work to do here in the constructor. + * What we do is run some "assert" tests so that these get caught + * at init itself instead of while saving/restoring when a user + * might lose some data! + */ + OstProto::FileMagic magic; + OstProto::FileChecksum cksum; + + magic.set_value(kFileMagicValue); + cksum.set_value(quint32(0)); + + // TODO: convert Q_ASSERT to something that will run in RELEASE mode also + Q_ASSERT(magic.IsInitialized()); + Q_ASSERT(cksum.IsInitialized()); + Q_ASSERT(magic.ByteSize() == kFileMagicSize); + Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); +} + +bool NativeFileFormat::open( + const QString fileName, + OstProto::FileMeta &meta, + OstProto::FileContent &content, + QString &error) +{ + QFile file(fileName); + QByteArray buf; + int size, contentOffset, contentSize; + quint32 calcCksum; + OstProto::FileMagic magic; + OstProto::FileChecksum cksum, zeroCksum; + + if (!file.open(QIODevice::ReadOnly)) + goto _open_fail; + + if (file.size() < kFileMagicSize) + goto _magic_missing; + + if (file.size() < kFileMinSize) + goto _checksum_missing; + + buf.resize(file.size()); + size = file.read(buf.data(), buf.size()); + if (size < 0) + goto _read_fail; + + Q_ASSERT(file.atEnd()); + file.close(); + + qDebug("%s: file.size() = %lld", __FUNCTION__, file.size()); + qDebug("%s: size = %d", __FUNCTION__, size); + + //qDebug("Read %d bytes", buf.size()); + //qDebug("%s", QString(buf.toHex()).toAscii().constData()); + + // Parse and verify magic + if (!magic.ParseFromArray( + (void*)(buf.constData() + kFileMagicOffset), + kFileMagicSize)) + { + goto _magic_parse_fail; + } + if (magic.value() != kFileMagicValue) + goto _magic_match_fail; + + // Parse and verify checksum + if (!cksum.ParseFromArray( + (void*)(buf.constData() + size - kFileChecksumSize), + kFileChecksumSize)) + { + goto _cksum_parse_fail; + } + + zeroCksum.set_value(0); + if (!zeroCksum.SerializeToArray( + (void*) (buf.data() + size - kFileChecksumSize), + kFileChecksumSize)) + { + goto _zero_cksum_serialize_fail; + } + + calcCksum = checksumCrc32C((quint8*) buf.constData(), size); + + qDebug("checksum \nExpected:%x Actual:%x", + calcCksum, cksum.value()); + + if (cksum.value() != calcCksum) + goto _cksum_verify_fail; + + // Parse the metadata first before we parse the full contents + // FIXME: metadata size is not known beforehand, so we end up + // parsing till EOF + if (!meta.ParseFromArray( + (void*)(buf.constData() + kFileMetaDataOffset), + size - kFileMetaDataOffset)) + { + goto _metadata_parse_fail; + } + + qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__, + QString().fromStdString(meta.DebugString()).toAscii().constData()); + qDebug("%s: END MetaData", __FUNCTION__); + + // MetaData Validation(s) + if (meta.data().file_type() != OstProto::kStreamsFileType) + goto _unexpected_file_type; + + if (meta.data().format_version_major() != kFileFormatVersionMajor) + goto _incompatible_file_version; + + if (meta.data().format_version_minor() > kFileFormatVersionMinor) + goto _incompatible_file_version; + + if (meta.data().format_version_minor() < kFileFormatVersionMinor) + { + // TODO: need to modify 'buf' such that we can parse successfully + // assuming the native minor version + } + + if (meta.data().format_version_revision() > kFileFormatVersionRevision) + { + error = QString(tr("%1 was created using a newer version of Ostinato." + " New features/protocols will not be available.")).arg(fileName); + } + + Q_ASSERT(meta.data().format_version_major() == kFileFormatVersionMajor); + + // ByteSize() does not include the Tag/Key, so we add 2 for that + contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2; + contentSize = size - contentOffset - kFileChecksumSize; + qDebug("%s: content offset/size = %d/%d", __FUNCTION__, + contentOffset, contentSize); + + // Parse full contents + if (!content.ParseFromArray( + (void*)(buf.constData() + contentOffset), + contentSize)) + { + goto _content_parse_fail; + } + + return true; + +_content_parse_fail: + error = QString(tr("Failed parsing %1 contents")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + content.InitializationErrorString()) + .toAscii().constData()); + qDebug("Debug: %s", QString().fromStdString( + content.DebugString()).toAscii().constData()); + goto _fail; +_incompatible_file_version: + error = QString(tr("%1 is in an incompatible format version - %2.%3.%4" + " (Native version is %5.%6.%7)")) + .arg(fileName) + .arg(meta.data().format_version_major()) + .arg(meta.data().format_version_minor()) + .arg(meta.data().format_version_revision()) + .arg(kFileFormatVersionMajor) + .arg(kFileFormatVersionMinor) + .arg(kFileFormatVersionRevision); + goto _fail; +_unexpected_file_type: + error = QString(tr("%1 is not a streams file")).arg(fileName); + goto _fail; +_metadata_parse_fail: + error = QString(tr("Failed parsing %1 meta data")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + meta.data().InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_cksum_verify_fail: + error = QString(tr("%1 checksum validation failed!\nExpected:%2 Actual:%3")) + .arg(fileName) + .arg(calcCksum, 0, kBaseHex) + .arg(cksum.value(), 0, kBaseHex); + goto _fail; +_zero_cksum_serialize_fail: + error = QString(tr("Internal Error: Zero Checksum Serialize failed!\n" + "Error: %1\nDebug: %2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_cksum_parse_fail: + error = QString(tr("Failed parsing %1 checksum")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + cksum.InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_magic_match_fail: + error = QString(tr("%1 is not an Ostinato file")).arg(fileName); + goto _fail; +_magic_parse_fail: + error = QString(tr("%1 does not look like an Ostinato file")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + magic.InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_read_fail: + error = QString(tr("Error reading from %1")).arg(fileName); + goto _fail; +_checksum_missing: + error = QString(tr("%1 is too small (missing checksum)")).arg(fileName); + goto _fail; +_magic_missing: + error = QString(tr("%1 is too small (missing magic value)")) + .arg(fileName); + goto _fail; +_open_fail: + error = QString(tr("Error opening %1")).arg(fileName); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); + return false; +} + +bool NativeFileFormat::save( + OstProto::FileType fileType, + const OstProto::FileContent &content, + const QString fileName, + QString &error) +{ + OstProto::FileMagic magic; + OstProto::FileMeta meta; + OstProto::FileChecksum cksum; + QFile file(fileName); + int metaSize, contentSize; + int contentOffset, cksumOffset; + QByteArray buf; + quint32 calcCksum; + + magic.set_value(kFileMagicValue); + Q_ASSERT(magic.IsInitialized()); + + cksum.set_value(0); + Q_ASSERT(cksum.IsInitialized()); + + initFileMetaData(*(meta.mutable_data())); + meta.mutable_data()->set_file_type(fileType); + Q_ASSERT(meta.IsInitialized()); + + if (!content.IsInitialized()) + goto _content_not_init; + + Q_ASSERT(content.IsInitialized()); + + metaSize = meta.ByteSize(); + contentSize = content.ByteSize(); + contentOffset = kFileMetaDataOffset + metaSize; + cksumOffset = contentOffset + contentSize; + + Q_ASSERT(magic.ByteSize() == kFileMagicSize); + Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); + buf.resize(kFileMagicSize + metaSize + contentSize + kFileChecksumSize); + + // Serialize everything + if (!magic.SerializeToArray((void*) (buf.data() + kFileMagicOffset), + kFileMagicSize)) + { + goto _magic_serialize_fail; + } + + if (!meta.SerializeToArray((void*) (buf.data() + kFileMetaDataOffset), + metaSize)) + { + goto _meta_serialize_fail; + } + + if (!content.SerializeToArray((void*) (buf.data() + contentOffset), + contentSize)) + { + goto _content_serialize_fail; + } + + if (!cksum.SerializeToArray((void*) (buf.data() + cksumOffset), + kFileChecksumSize)) + { + goto _zero_cksum_serialize_fail; + } + + // TODO: emit status("Calculating checksum..."); + + // Calculate and write checksum + calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size()); + cksum.set_value(calcCksum); + if (!cksum.SerializeToArray( + (void*) (buf.data() + cksumOffset), + kFileChecksumSize)) + { + goto _cksum_serialize_fail; + } + + qDebug("Writing %d bytes", buf.size()); + //qDebug("%s", QString(buf.toHex()).toAscii().constData()); + + // TODO: emit status("Writing to disk..."); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + goto _open_fail; + + if (file.write(buf) < 0) + goto _write_fail; + + file.close(); + + return true; + +_write_fail: + error = QString(tr("Error writing to %1")).arg(fileName); + goto _fail; +_open_fail: + error = QString(tr("Error opening %1 (Error Code = %2)")) + .arg(fileName) + .arg(file.error()); + goto _fail; +_cksum_serialize_fail: + error = QString(tr("Internal Error: Checksum Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_zero_cksum_serialize_fail: + error = QString(tr("Internal Eror: Zero Checksum Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_content_serialize_fail: + error = QString(tr("Internal Error: Content Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + content.InitializationErrorString())) + .arg(QString().fromStdString(content.DebugString())); + goto _fail; +_meta_serialize_fail: + error = QString(tr("Internal Error: Meta Data Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + meta.InitializationErrorString())) + .arg(QString().fromStdString(meta.DebugString())); + goto _fail; +_magic_serialize_fail: + error = QString(tr("Internal Error: Magic Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + magic.InitializationErrorString())) + .arg(QString().fromStdString(magic.DebugString())); + goto _fail; +_content_not_init: + error = QString(tr("Internal Error: Content not initialized\n%1\n%2")) + .arg(QString().fromStdString( + content.InitializationErrorString())) + .arg(QString().fromStdString(content.DebugString())); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); + return false; +} + +bool NativeFileFormat::isNativeFileFormat( + const QString fileName, + OstProto::FileType fileType) +{ + bool ret = false; + QFile file(fileName); + QByteArray buf; + OstProto::FileMagic magic; + + if (!file.open(QIODevice::ReadOnly)) + goto _exit; + + buf = file.peek(kFileMagicOffset + kFileMagicSize); + if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset), + kFileMagicSize)) + goto _close_exit; + + if (magic.value() == kFileMagicValue) + ret = true; + + // TODO: check fileType + +_close_exit: + file.close(); +_exit: + return ret; +} + +void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData) +{ + // Fill in the "native" file format version + metaData.set_format_version_major(kFileFormatVersionMajor); + metaData.set_format_version_minor(kFileFormatVersionMinor); + metaData.set_format_version_revision(kFileFormatVersionRevision); + + metaData.set_generator_name( + qApp->applicationName().toUtf8().constData()); + metaData.set_generator_version( + qApp->property("version").toString().toUtf8().constData()); + metaData.set_generator_revision( + qApp->property("revision").toString().toUtf8().constData()); +} + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +/*! Fixup content to what is expected in the native version */ +void NativeFileFormat::postParseFixup(OstProto::FileMetaData metaData, + OstProto::FileContent &content) +{ + Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor); + + // Do fixups from oldest to newest versions + switch (metaData.format_version_minor()) + { + case 1: + { + int n = content.matter().streams().stream_size(); + for (int i = 0; i < n; i++) + { + OstProto::StreamControl *sctl = + content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control(); + sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec()); + sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec()); + } + + // fall-through to next higher version until native version + } + case kFileFormatVersionMinor: // native version + break; + + case 0: + default: + qWarning("%s: minor version %u unhandled", __FUNCTION__, + metaData.format_version_minor()); + Q_ASSERT_X(false, "postParseFixup", "unhandled minor version"); + } + +} +#pragma GCC diagnostic warning "-Wdeprecated-declarations" + diff --git a/common/nativefileformat.h b/common/nativefileformat.h new file mode 100644 index 0000000..fb49fc8 --- /dev/null +++ b/common/nativefileformat.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010, 2016 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 +*/ +#ifndef _NATIVE_FILE_FORMAT_H +#define _NATIVE_FILE_FORMAT_H + +/* + * This file contains helper functions for the native file format + * defined in fileformat.proto + * + * The actual file format classes - (Ostm)FileFormat and OssnFileFormat + * use multiple inheritance from the abstract interface class and this + * helper class + * + * The primary reason for the existence of this class is to have a common + * code for dealing with native file formats + */ + +#include "fileformat.pb.h" + +#include + +class NativeFileFormat +{ +public: + NativeFileFormat(); + + bool open(const QString fileName, + OstProto::FileMeta &meta, + OstProto::FileContent &content, + QString &error); + bool save(OstProto::FileType fileType, + const OstProto::FileContent &content, + const QString fileName, + QString &error); + + bool isNativeFileFormat(const QString fileName, + OstProto::FileType fileType); + void postParseFixup(OstProto::FileMetaData metaData, + OstProto::FileContent &content); + +private: + void initFileMetaData(OstProto::FileMetaData &metaData); + + static const int kFileMagicSize = 12; + static const int kFileChecksumSize = 5; + static const int kFileMinSize = kFileMagicSize + kFileChecksumSize; + + static const int kFileMagicOffset = 0; + static const int kFileMetaDataOffset = kFileMagicSize; + + static const std::string kFileMagicValue; + + // Native file format version + static const uint kFileFormatVersionMajor = 0; + static const uint kFileFormatVersionMinor = 2; + static const uint kFileFormatVersionRevision = 4; +}; + +#endif diff --git a/common/ossnfileformat.cpp b/common/ossnfileformat.cpp new file mode 100644 index 0000000..ed6ebc2 --- /dev/null +++ b/common/ossnfileformat.cpp @@ -0,0 +1,46 @@ +/* +Copyright (C) 2016 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 "ossnfileformat.h" + +bool OssnFileFormat::open(const QString fileName, + OstProto::SessionContent &session, QString &error) +{ + // TODO + return false; +} + +bool OssnFileFormat::save(const OstProto::SessionContent &session, + const QString fileName, QString &error) +{ + // TODO + return false; +} + +bool OssnFileFormat::isMyFileFormat(const QString fileName) +{ + // TODO + return true; +} + +bool OssnFileFormat::isMyFileType(const QString fileType) +{ + // TODO + return true; +} diff --git a/common/ossnfileformat.h b/common/ossnfileformat.h new file mode 100644 index 0000000..73eb5a8 --- /dev/null +++ b/common/ossnfileformat.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _OSSN_FILE_FORMAT_H +#define _OSSN_FILE_FORMAT_H + +#include "sessionfileformat.h" + +class OssnFileFormat : public SessionFileFormat +{ +public: + virtual bool open(const QString fileName, + OstProto::SessionContent &session, QString &error); + virtual bool save(const OstProto::SessionContent &session, + const QString fileName, QString &error); + + virtual bool isMyFileFormat(const QString fileName); + virtual bool isMyFileType(const QString fileType); +}; + +extern OssnFileFormat ossnFileFormat; + +#endif + diff --git a/common/ostprotogui.pro b/common/ostprotogui.pro index b39526e..44f1061 100644 --- a/common/ostprotogui.pro +++ b/common/ostprotogui.pro @@ -39,12 +39,15 @@ HEADERS = \ fileformat.h \ ipv4addressdelegate.h \ ipv6addressdelegate.h \ + nativefileformat.h \ + ossnfileformat.h \ pcapfileformat.h \ pdmlfileformat.h \ pythonfileformat.h \ pdmlprotocol.h \ pdmlprotocols.h \ pdmlreader.h \ + sessionfileformat.h \ spinboxdelegate.h HEADERS += \ @@ -82,12 +85,15 @@ SOURCES += \ ostprotolib.cpp \ abstractfileformat.cpp \ fileformat.cpp \ + nativefileformat.cpp \ + ossnfileformat.cpp \ pcapfileformat.cpp \ pdmlfileformat.cpp \ pythonfileformat.cpp \ pdmlprotocol.cpp \ pdmlprotocols.cpp \ pdmlreader.cpp \ + sessionfileformat.cpp \ spinboxdelegate.cpp SOURCES += \ diff --git a/common/sessionfileformat.cpp b/common/sessionfileformat.cpp new file mode 100644 index 0000000..644b956 --- /dev/null +++ b/common/sessionfileformat.cpp @@ -0,0 +1,111 @@ +/* +Copyright (C) 2016 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 "sessionfileformat.h" + +#include "ossnfileformat.h" + +#include + +SessionFileFormat::SessionFileFormat() +{ + stop_ = false; +} + +SessionFileFormat::~SessionFileFormat() +{ +} + +QDialog* SessionFileFormat::openOptionsDialog() +{ + return NULL; +} + +QDialog* SessionFileFormat::saveOptionsDialog() +{ + return NULL; +} + +QStringList SessionFileFormat::supportedFileTypes() +{ + return QStringList() + << "Ostinato Session (*.ossn)"; +} + +void SessionFileFormat::openOffline(const QString fileName, + OstProto::SessionContent &session, QString &error) +{ + fileName_ = fileName; + openSession_ = &session; + error_ = &error; + op_ = kOpen; + stop_ = false; + + start(); +} + +void SessionFileFormat::saveOffline( + const OstProto::SessionContent &session, + const QString fileName, QString &error) +{ + saveSession_ = &session; + fileName_ = fileName; + error_ = &error; + op_ = kSave; + stop_ = false; + + start(); +} + +bool SessionFileFormat::result() +{ + return result_; +} + +SessionFileFormat* SessionFileFormat::fileFormatFromFile( + const QString fileName) +{ + if (ossnFileFormat.isMyFileFormat(fileName)) + return &ossnFileFormat; + + return NULL; +} + +SessionFileFormat* SessionFileFormat::fileFormatFromType( + const QString fileType) +{ + + if (ossnFileFormat.isMyFileType(fileType)) + return &ossnFileFormat; + + return NULL; +} + +void SessionFileFormat::cancel() +{ + stop_ = true; +} + +void SessionFileFormat::run() +{ + if (op_ == kOpen) + result_ = open(fileName_, *openSession_, *error_); + else if (op_ == kSave) + result_ = save(*saveSession_, fileName_, *error_); +} diff --git a/common/sessionfileformat.h b/common/sessionfileformat.h new file mode 100644 index 0000000..0146324 --- /dev/null +++ b/common/sessionfileformat.h @@ -0,0 +1,90 @@ +/* +Copyright (C) 2016 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 +*/ + +#ifndef _SESSION_FILE_FORMAT_H +#define _SESSION_FILE_FORMAT_H + +#include "fileformat.pb.h" +#include "protocol.pb.h" + +#include +#include + +class QDialog; + +class SessionFileFormat : public QThread +{ + Q_OBJECT +public: + SessionFileFormat(); + virtual ~SessionFileFormat(); + + virtual bool open(const QString fileName, + OstProto::SessionContent &session, QString &error) = 0; + virtual bool save(const OstProto::SessionContent &session, + const QString fileName, QString &error) = 0; + + virtual QDialog* openOptionsDialog(); + virtual QDialog* saveOptionsDialog(); + + void openOffline(const QString fileName, + OstProto::SessionContent &session, QString &error); + void saveOffline(const OstProto::SessionContent &session, + const QString fileName, QString &error); + + bool result(); + + static QStringList supportedFileTypes(); + + static SessionFileFormat* fileFormatFromFile(const QString fileName); + static SessionFileFormat* fileFormatFromType(const QString fileType); + + virtual bool isMyFileFormat(const QString fileName) = 0; + virtual bool isMyFileType(const QString fileType) = 0; + +signals: + void status(QString text); + void target(int value); + void progress(int value); + +public slots: + void cancel(); + +protected: + void run(); + + bool stop_; + +private: + enum kOp { + kOpen, + kSave + }; + + QString fileName_; + OstProto::SessionContent *openSession_; + const OstProto::SessionContent *saveSession_; + QString *error_; + kOp op_; + bool result_; + +}; + +#endif + From 2a77f73e9c4fe2bf940b896ebaf73d8a0f3b7c79 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 29 Mar 2016 18:55:22 +0530 Subject: [PATCH 097/121] Save/Open Session - Implemented OssnFileFormat; code to build SessionContent still pending --- common/ossnfileformat.cpp | 57 ++++++++++++++++++++++++++++++++---- common/ossnfileformat.h | 5 +++- common/sessionfileformat.cpp | 4 +-- common/sessionfileformat.h | 4 +-- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/common/ossnfileformat.cpp b/common/ossnfileformat.cpp index ed6ebc2..ab8b926 100644 --- a/common/ossnfileformat.cpp +++ b/common/ossnfileformat.cpp @@ -19,28 +19,73 @@ along with this program. If not, see #include "ossnfileformat.h" +OssnFileFormat ossnFileFormat; + +OssnFileFormat::OssnFileFormat() + : SessionFileFormat(), NativeFileFormat() +{ + // Do Nothing +} + bool OssnFileFormat::open(const QString fileName, OstProto::SessionContent &session, QString &error) { - // TODO + OstProto::FileMeta meta; + OstProto::FileContent content; + bool ret = NativeFileFormat::open(fileName, meta, content, error); + + if (!ret) + goto _exit; + + if (!content.matter().has_session()) + goto _missing_session; + + postParseFixup(meta.data(), content); + + session.CopyFrom(content.matter().streams()); + + return true; + +_missing_session: + error = QString(tr("%1 does not contain a session")).arg(fileName); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); +_exit: return false; } bool OssnFileFormat::save(const OstProto::SessionContent &session, const QString fileName, QString &error) { - // TODO + OstProto::FileContent content; + + if (!session.IsInitialized()) + goto _session_not_init; + + content.mutable_matter()->mutable_session()->CopyFrom(session); + Q_ASSERT(content.IsInitialized()); + + return NativeFileFormat::save(OstProto::kSessionFileType, content, + fileName, error); + +_session_not_init: + error = QString(tr("Internal Error: Session not initialized\n%1\n%2")) + .arg(QString().fromStdString( + session.InitializationErrorString())) + .arg(QString().fromStdString(session.DebugString())); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); return false; } bool OssnFileFormat::isMyFileFormat(const QString fileName) { - // TODO - return true; + return isNativeFileFormat(fileName, OstProto::kSessionFileType); } bool OssnFileFormat::isMyFileType(const QString fileType) { - // TODO - return true; + return fileType.contains("(*.ossn)") ? true : false; } diff --git a/common/ossnfileformat.h b/common/ossnfileformat.h index 73eb5a8..e6412b4 100644 --- a/common/ossnfileformat.h +++ b/common/ossnfileformat.h @@ -20,11 +20,14 @@ along with this program. If not, see #ifndef _OSSN_FILE_FORMAT_H #define _OSSN_FILE_FORMAT_H +#include "nativefileformat.h" #include "sessionfileformat.h" -class OssnFileFormat : public SessionFileFormat +class OssnFileFormat : public SessionFileFormat, public NativeFileFormat { public: + OssnFileFormat(); + virtual bool open(const QString fileName, OstProto::SessionContent &session, QString &error); virtual bool save(const OstProto::SessionContent &session, diff --git a/common/sessionfileformat.cpp b/common/sessionfileformat.cpp index 644b956..44ec3da 100644 --- a/common/sessionfileformat.cpp +++ b/common/sessionfileformat.cpp @@ -48,7 +48,7 @@ QStringList SessionFileFormat::supportedFileTypes() << "Ostinato Session (*.ossn)"; } -void SessionFileFormat::openOffline(const QString fileName, +void SessionFileFormat::openAsync(const QString fileName, OstProto::SessionContent &session, QString &error) { fileName_ = fileName; @@ -60,7 +60,7 @@ void SessionFileFormat::openOffline(const QString fileName, start(); } -void SessionFileFormat::saveOffline( +void SessionFileFormat::saveAsync( const OstProto::SessionContent &session, const QString fileName, QString &error) { diff --git a/common/sessionfileformat.h b/common/sessionfileformat.h index 0146324..4f4d68f 100644 --- a/common/sessionfileformat.h +++ b/common/sessionfileformat.h @@ -43,9 +43,9 @@ public: virtual QDialog* openOptionsDialog(); virtual QDialog* saveOptionsDialog(); - void openOffline(const QString fileName, + void openAsync(const QString fileName, OstProto::SessionContent &session, QString &error); - void saveOffline(const OstProto::SessionContent &session, + void saveAsync(const OstProto::SessionContent &session, const QString fileName, QString &error); bool result(); From bcb5376f9dfb85180fad0209b96d59bfc35cd3d8 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 14 Apr 2016 07:44:07 +0530 Subject: [PATCH 098/121] Save/Open Session - added UI and related infra to invoke OssnFileFormat; code to build sessionContent incomplete --- client/mainwindow.cpp | 200 +++++++++++++++++++++++++++++++++++- client/mainwindow.h | 5 + client/mainwindow.ui | 12 +++ client/portgroup.cpp | 24 +++++ client/portgroup.h | 8 ++ client/portgrouplist.cpp | 14 +++ client/portgrouplist.h | 1 + client/portmodel.h | 1 + client/portswindow.cpp | 74 +++++++++++++ client/portswindow.h | 13 +++ common/fileformat.cpp | 4 +- common/fileformat.proto | 4 +- common/nativefileformat.cpp | 23 ++++- common/nativefileformat.h | 1 + common/ossnfileformat.cpp | 6 +- test/TODO.md | 15 +++ 16 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 test/TODO.md diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 2f459aa..2182e4f 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -27,12 +27,18 @@ along with this program. If not, see #include "portstatswindow.h" #include "portswindow.h" #include "preferences.h" +#include "sessionfileformat.h" #include "settings.h" #include "ui_about.h" #include "updater.h" +#include "fileformat.pb.h" + #include +#include +#include #include +#include extern const char* version; extern const char* revision; @@ -75,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent) setupUi(this); - menuFile->insertActions(menuFile->actions().at(0), portsWindow->actions()); + menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions()); statsDock->setWidget(statsWindow); addDockWidget(Qt::BottomDockWidgetArea, statsDock); @@ -133,6 +139,83 @@ MainWindow::~MainWindow() delete localServer_; } +void MainWindow::on_actionOpenSession_triggered() +{ + qDebug("Open Session Action"); + + static QString dirName; + QString fileName; + QString errorStr; + bool ret; + + fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), dirName); + if (fileName.isEmpty()) + goto _exit; + + if (portsWindow->portGroupCount()) { + if (QMessageBox::question(this, + tr("Open Session"), + tr("Existing session will be lost. Proceed?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == QMessageBox::No) + goto _exit; + } + + ret = openSession(fileName, errorStr); + if (!ret || !errorStr.isEmpty()) { + QMessageBox msgBox(this); + QStringList str = errorStr.split("\n\n\n\n"); + + msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical); + msgBox.setWindowTitle(qApp->applicationName()); + msgBox.setText(str.at(0)); + if (str.size() > 1) + msgBox.setDetailedText(str.at(1)); + msgBox.setStandardButtons(QMessageBox::Ok); + + msgBox.exec(); + } + dirName = QFileInfo(fileName).absolutePath(); + +_exit: + return; +} + +void MainWindow::on_actionSaveSession_triggered() +{ + qDebug("Save Session Action"); + + static QString fileName; + QStringList fileTypes = SessionFileFormat::supportedFileTypes(); + QString fileType; + QString errorStr; + QFileDialog::Options options; + + // On Mac OS with Native Dialog, getSaveFileName() ignores fileType. + // Although currently there's only one supported file type, we may + // have more in the future +#if defined(Q_OS_MAC) + options |= QFileDialog::DontUseNativeDialog; +#endif + + if (fileTypes.size()) + fileType = fileTypes.at(0); + + fileName = QFileDialog::getSaveFileName(this, tr("Save Session"), + fileName, fileTypes.join(";;"), &fileType, options); + if (fileName.isEmpty()) + goto _exit; + + if (!saveSession(fileName, fileType, errorStr)) + QMessageBox::critical(this, qApp->applicationName(), errorStr); + else if (!errorStr.isEmpty()) + QMessageBox::warning(this, qApp->applicationName(), errorStr); + + fileName = QFileInfo(fileName).absolutePath(); +_exit: + return; +} + void MainWindow::on_actionPreferences_triggered() { Preferences *preferences = new Preferences(); @@ -172,3 +255,118 @@ void MainWindow::onNewVersion(QString newVersion) statusBar()->showMessage(QString("New Ostinato version %1 available. " "Visit http://ostinato.org to download").arg(newVersion)); } + +//! Returns true on success (or user cancel) and false on failure +bool MainWindow::openSession(QString fileName, QString &error) +{ + bool ret = false; + QDialog *optDialog; + QProgressDialog progress("Opening Session", "Cancel", 0, 0, this); + OstProto::SessionContent session; + SessionFileFormat *fmt = SessionFileFormat::fileFormatFromFile(fileName); + + if (fmt == NULL) + goto _fail; + + if ((optDialog = fmt->openOptionsDialog())) + { + int ret; + optDialog->setParent(this, Qt::Dialog); + ret = optDialog->exec(); + optDialog->setParent(0, Qt::Dialog); + if (ret == QDialog::Rejected) + goto _user_opt_cancel; + } + + progress.setAutoReset(false); + progress.setAutoClose(false); + progress.setMinimumDuration(0); + progress.show(); + + setDisabled(true); + progress.setEnabled(true); // to override the mainWindow disable + + connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString))); + connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int))); + connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); + connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); + + fmt->openAsync(fileName, session, error); + qDebug("after open async"); + + while (!fmt->isFinished()) + qApp->processEvents(); + qDebug("wait over for async operation"); + + if (!fmt->result()) + goto _fail; + + // process any remaining events posted from the thread + for (int i = 0; i < 10; i++) + qApp->processEvents(); + + // XXX: user can't cancel operation from here on! + progress.close(); + + portsWindow->openSession(&session, error); + +_user_opt_cancel: + ret = true; + +_fail: + progress.close(); + setEnabled(true); + return ret; +} + +bool MainWindow::saveSession(QString fileName, QString fileType, QString &error) +{ + bool ret = false; + QProgressDialog progress("Saving Session", "Cancel", 0, 0, this); + SessionFileFormat *fmt = SessionFileFormat::fileFormatFromType(fileType); + OstProto::SessionContent session; + + if (fmt == NULL) + goto _fail; + + progress.setAutoReset(false); + progress.setAutoClose(false); + progress.setMinimumDuration(0); + progress.show(); + + setDisabled(true); + progress.setEnabled(true); // to override the mainWindow disable + + // Fill in session + ret = portsWindow->saveSession(&session, error, &progress); + if (!ret) + goto _user_cancel; + + connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString))); + connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int))); + connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); + connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); + + fmt->saveAsync(session, fileName, error); + qDebug("after save async"); + + while (!fmt->isFinished()) + qApp->processEvents(); + qDebug("wait over for async operation"); + + ret = fmt->result(); + goto _exit; + +_user_cancel: + goto _exit; + +_fail: + error = QString("Unsupported File Type - %1").arg(fileType); + goto _exit; + +_exit: + progress.close(); + setEnabled(true); + return ret; +} + diff --git a/client/mainwindow.h b/client/mainwindow.h index e05ab54..fa575f3 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -34,6 +34,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow Q_OBJECT private: + bool openSession(QString fileName, QString &error); + bool saveSession(QString fileName, QString fileType, QString &error); + QProcess *localServer_; PortsWindow *portsWindow; PortStatsWindow *statsWindow; @@ -48,6 +51,8 @@ public: ~MainWindow(); public slots: + void on_actionOpenSession_triggered(); + void on_actionSaveSession_triggered(); void on_actionPreferences_triggered(); void on_actionViewRestoreDefaults_triggered(); void on_actionHelpAbout_triggered(); diff --git a/client/mainwindow.ui b/client/mainwindow.ui index a3fcb78..6e79e3d 100644 --- a/client/mainwindow.ui +++ b/client/mainwindow.ui @@ -21,6 +21,8 @@ &File + + @@ -89,6 +91,16 @@ Restore &Defaults + + + Open Session ... + + + + + Save Session ... + + diff --git a/client/portgroup.cpp b/client/portgroup.cpp index bf65843..a97a8f6 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -22,6 +22,7 @@ along with this program. If not, see #include "settings.h" #include "emulproto.pb.h" +#include "fileformat.pb.h" #include #include @@ -50,6 +51,8 @@ PortGroup::PortGroup(QString serverName, quint16 port) statsController = new PbRpcController(portIdList_, portStatsList_); isGetStatsPending_ = false; + atConnectConfig_ = NULL; + compat = kUnknown; reconnect = false; @@ -91,8 +94,21 @@ PortGroup::~PortGroup() delete serviceStub; delete rpcChannel; delete statsController; + delete atConnectConfig_; } +void PortGroup::setConfigAtConnect(const OstProto::PortGroupContent *config) +{ + if (!config) { + delete atConnectConfig_; + atConnectConfig_ = NULL; + return; + } + + if (!atConnectConfig_) + atConnectConfig_ = config->New(); + atConnectConfig_->CopyFrom(*config); +} // ------------------------------------------------ // Slots @@ -168,6 +184,14 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller) compat = kCompatible; + if (atConnectConfig_) + { + // TODO: apply config + + delete atConnectConfig_; + atConnectConfig_ = NULL; + } + else { OstProto::Void *void_ = new OstProto::Void; OstProto::PortIdList *portIdList = new OstProto::PortIdList; diff --git a/client/portgroup.h b/client/portgroup.h index eb0e72a..c5c99eb 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -36,6 +36,10 @@ LOW #define DEFAULT_SERVER_PORT 7878 +namespace OstProto { + class PortGroupContent; +} + class QFile; class QTimer; @@ -62,6 +66,8 @@ private: OstProto::PortIdList *portIdList_; OstProto::PortStatsList *portStatsList_; + OstProto::PortGroupContent *atConnectConfig_; + public: // FIXME(HIGH): member access QList mPorts; @@ -82,6 +88,8 @@ public: } void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); } + void setConfigAtConnect(const OstProto::PortGroupContent *config); + int numPorts() const { return mPorts.size(); } quint32 id() const { return mPortGroupId; } diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index c2ea811..1475856 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -126,6 +126,20 @@ void PortGroupList::removePortGroup(PortGroup &portGroup) mPortStatsModel.when_portListChanged(); } +void PortGroupList::removeAllPortGroups() +{ + while (!mPortGroups.isEmpty()) { + PortGroup *pg = mPortGroups.at(0); + mPortGroupListModel.portGroupAboutToBeRemoved(pg); + mPortGroups.removeFirst(); + delete pg; + } + mPortGroupListModel.portGroupRemoved(); + + mPortGroupListModel.when_portListChanged(); + mPortStatsModel.when_portListChanged(); +} + //.................... // Private Methods //.................... diff --git a/client/portgrouplist.h b/client/portgrouplist.h index 4a2305d..22f8c84 100644 --- a/client/portgrouplist.h +++ b/client/portgrouplist.h @@ -72,6 +72,7 @@ public: void addPortGroup(PortGroup &portGroup); void removePortGroup(PortGroup &portGroup); + void removeAllPortGroups(); private: int indexOfPortGroup(quint32 portGroupId); diff --git a/client/portmodel.h b/client/portmodel.h index 2027f0b..4464fdf 100644 --- a/client/portmodel.h +++ b/client/portmodel.h @@ -57,6 +57,7 @@ private: QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount]; private slots: + // FIXME: these are invoked from outside - how come they are "private"? void when_portGroupDataChanged(int portGroupId, int portId); void portGroupAboutToBeAppended(); diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 5d62225..7af6b78 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -26,12 +26,17 @@ along with this program. If not, see #include "streamconfigdialog.h" #include "streamlistdelegate.h" +#include "fileformat.pb.h" + #include #include #include +#include #include #include +extern QMainWindow *mainWindow; + PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) : QWidget(parent), proxyPortModel(NULL) { @@ -168,6 +173,75 @@ PortsWindow::~PortsWindow() delete proxyPortModel; } +int PortsWindow::portGroupCount() +{ + return plm->numPortGroups(); +} + +//! Always return true +bool PortsWindow::openSession( + const OstProto::SessionContent *session, + QString &error) +{ + QProgressDialog progress("Opening Session", NULL, + 0, session->port_groups_size(), mainWindow); + progress.show(); + progress.setEnabled(true); // since parent (mainWindow) is disabled + + plm->removeAllPortGroups(); + + for (int i = 0; i < session->port_groups_size(); i++) { + const OstProto::PortGroupContent &pgc = session->port_groups(i); + PortGroup *pg = new PortGroup(QString::fromStdString( + pgc.server_name()), + quint16(pgc.server_port())); + pg->setConfigAtConnect(&pgc); + plm->addPortGroup(*pg); + progress.setValue(i+1); + } + + return true; +} + +/*! + * Prepare content to be saved for a session + * + * Returns false, if user cancels op; true, otherwise + */ +bool PortsWindow::saveSession( + OstProto::SessionContent *session, // OUT param + QString &error, + QProgressDialog *progress) +{ + int n = portGroupCount(); + + if (progress) { + progress->setLabelText("Preparing Ports and PortGroups ..."); + progress->setRange(0, n); + } + + for (int i = 0; i < n; i++) + { + OstProto::PortGroupContent *pgc = session->add_port_groups(); + PortGroup &pg = plm->portGroupByIndex(i); + + pgc->set_server_name(pg.serverName().toStdString()); + pgc->set_server_port(pg.serverPort()); + + // TODO: ports + + if (progress) { + if (progress->wasCanceled()) + return false; + progress->setValue(i); + } + if (i % 2 == 0) + qApp->processEvents(); + } + + return true; +} + void PortsWindow::showMyReservedPortsOnly(bool enabled) { if (!proxyPortModel) diff --git a/client/portswindow.h b/client/portswindow.h index 9d8e6ef..68dc3dc 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -26,8 +26,13 @@ along with this program. If not, see #include "portgrouplist.h" class QAbstractItemDelegate; +class QProgressDialog; class QSortFilterProxyModel; +namespace OstProto { + class SessionContent; +} + class PortsWindow : public QWidget, private Ui::PortsWindow { Q_OBJECT @@ -39,6 +44,14 @@ public: PortsWindow(PortGroupList *pgl, QWidget *parent = 0); ~PortsWindow(); + int portGroupCount(); + + bool openSession(const OstProto::SessionContent *session, + QString &error); + bool saveSession(OstProto::SessionContent *session, + QString &error, + QProgressDialog *progress = NULL); + signals: void currentPortChanged(const QModelIndex ¤t, const QModelIndex &previous); diff --git a/common/fileformat.cpp b/common/fileformat.cpp index 2ccf270..36c3eac 100644 --- a/common/fileformat.cpp +++ b/common/fileformat.cpp @@ -32,8 +32,8 @@ bool FileFormat::openStreams(const QString fileName, { OstProto::FileMeta meta; OstProto::FileContent content; - bool ret = NativeFileFormat::open(fileName, meta, content, error); - + bool ret = NativeFileFormat::open(fileName, OstProto::kStreamsFileType, + meta, content, error); if (!ret) goto _fail; diff --git a/common/fileformat.proto b/common/fileformat.proto index 18e7d97..10339d7 100644 --- a/common/fileformat.proto +++ b/common/fileformat.proto @@ -45,7 +45,9 @@ message PortContent { message PortGroupContent { optional string server_name = 1; - repeated PortContent ports = 2; + optional uint32 server_port = 2; + + repeated PortContent ports = 15; } message SessionContent { diff --git a/common/nativefileformat.cpp b/common/nativefileformat.cpp index 83e13ba..4e4f380 100644 --- a/common/nativefileformat.cpp +++ b/common/nativefileformat.cpp @@ -31,6 +31,22 @@ const std::string NativeFileFormat::kFileMagicValue = "\xa7\xb7OSTINATO"; static const int kBaseHex = 16; +static QString fileTypeStr(OstProto::FileType fileType) +{ + switch (fileType) { + case OstProto::kReservedFileType: + return QString("Reserved"); + case OstProto::kStreamsFileType: + return QString("Streams"); + case OstProto::kSessionFileType: + return QString("Streams"); + default: + Q_ASSERT(false); + } + + return QString("Unknown"); +} + NativeFileFormat::NativeFileFormat() { /* @@ -54,6 +70,7 @@ NativeFileFormat::NativeFileFormat() bool NativeFileFormat::open( const QString fileName, + OstProto::FileType fileType, OstProto::FileMeta &meta, OstProto::FileContent &content, QString &error) @@ -137,7 +154,7 @@ bool NativeFileFormat::open( qDebug("%s: END MetaData", __FUNCTION__); // MetaData Validation(s) - if (meta.data().file_type() != OstProto::kStreamsFileType) + if (meta.data().file_type() != fileType) goto _unexpected_file_type; if (meta.data().format_version_major() != kFileFormatVersionMajor) @@ -196,7 +213,9 @@ _incompatible_file_version: .arg(kFileFormatVersionRevision); goto _fail; _unexpected_file_type: - error = QString(tr("%1 is not a streams file")).arg(fileName); + error = QString(tr("%1 is not a %2 file")) + .arg(fileName) + .arg(fileTypeStr(fileType)); goto _fail; _metadata_parse_fail: error = QString(tr("Failed parsing %1 meta data")).arg(fileName); diff --git a/common/nativefileformat.h b/common/nativefileformat.h index fb49fc8..59054dd 100644 --- a/common/nativefileformat.h +++ b/common/nativefileformat.h @@ -41,6 +41,7 @@ public: NativeFileFormat(); bool open(const QString fileName, + OstProto::FileType fileType, OstProto::FileMeta &meta, OstProto::FileContent &content, QString &error); diff --git a/common/ossnfileformat.cpp b/common/ossnfileformat.cpp index ab8b926..adb943c 100644 --- a/common/ossnfileformat.cpp +++ b/common/ossnfileformat.cpp @@ -32,8 +32,8 @@ bool OssnFileFormat::open(const QString fileName, { OstProto::FileMeta meta; OstProto::FileContent content; - bool ret = NativeFileFormat::open(fileName, meta, content, error); - + bool ret = NativeFileFormat::open(fileName, OstProto::kSessionFileType, + meta, content, error); if (!ret) goto _exit; @@ -42,7 +42,7 @@ bool OssnFileFormat::open(const QString fileName, postParseFixup(meta.data(), content); - session.CopyFrom(content.matter().streams()); + session.CopyFrom(content.matter().session()); return true; diff --git a/test/TODO.md b/test/TODO.md new file mode 100644 index 0000000..6c20142 --- /dev/null +++ b/test/TODO.md @@ -0,0 +1,15 @@ +# TODO - Test Cases + +## Session Save/Open + * Verify each save session triggers the file dialog at the last path used + * OSSN Session file format tests (TODO: expand) + * Verify no file is saved if user clicks 'Cancel' on the progress dialog while saving session file + + * On open session, verify user is prompted if there are existing portgroups and not if there are no port groups + * Verify each open session triggers the file dialog at the last path used + * OSSN Session file format tests (TODO: expand) + * Verify no change in existing port groups if user clicks 'Cancel' on the options dialog + * Verify no change in existing port groups if user clicks 'Cancel' on the progress dialog while opening session file + * Verify all old portgroups are removed before new portgroups from the session file are added + * Verify config of portGroups loaded from the session file are correct + * Verify config of a portGroup is NOT restored to the config saved in session file after a open session - disconnect - connect From 9bd6b536a3f2cc38ba223adff3e14f97133b7016 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 15 Apr 2016 19:03:52 +0530 Subject: [PATCH 099/121] Save/Open Session: saved streams and deviceGroups in file; open code pending --- client/port.cpp | 5 +++++ client/port.h | 2 ++ client/portgrouplist.cpp | 7 +++++-- client/portswindow.cpp | 25 +++++++++++++++++++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 812153c..87c2dd0 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -66,6 +66,11 @@ Port::~Port() delete mStreams.takeFirst(); } +void Port::protoDataCopyInto(OstProto::Port *data) +{ + data->CopyFrom(d); +} + void Port::updatePortConfig(OstProto::Port *port) { bool recalc = false; diff --git a/client/port.h b/client/port.h index f155edc..af86255 100644 --- a/client/port.h +++ b/client/port.h @@ -126,6 +126,8 @@ public: return capFile_; } + void protoDataCopyInto(OstProto::Port *data); + // FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal void updatePortConfig(OstProto::Port *port); diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index 1475856..6e380a0 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -128,12 +128,15 @@ void PortGroupList::removePortGroup(PortGroup &portGroup) void PortGroupList::removeAllPortGroups() { - while (!mPortGroups.isEmpty()) { + if (mPortGroups.isEmpty()) + return; + + do { PortGroup *pg = mPortGroups.at(0); mPortGroupListModel.portGroupAboutToBeRemoved(pg); mPortGroups.removeFirst(); delete pg; - } + } while (!mPortGroups.isEmpty()); mPortGroupListModel.portGroupRemoved(); mPortGroupListModel.when_portListChanged(); diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 7af6b78..fb93a04 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -222,13 +222,34 @@ bool PortsWindow::saveSession( for (int i = 0; i < n; i++) { - OstProto::PortGroupContent *pgc = session->add_port_groups(); PortGroup &pg = plm->portGroupByIndex(i); + OstProto::PortGroupContent *pgc = session->add_port_groups(); pgc->set_server_name(pg.serverName().toStdString()); pgc->set_server_port(pg.serverPort()); - // TODO: ports + for (int j = 0; j < pg.numPorts(); j++) + { + OstProto::PortContent *pc = pgc->add_ports(); + OstProto::Port *p = pc->mutable_port_config(); + + // XXX: We save the entire OstProto::Port even though some + // fields may be ephemeral; while opening we use only relevant + // fields + pg.mPorts.at(j)->protoDataCopyInto(p); + + for (int k = 0; k < pg.mPorts.at(j)->numStreams(); k++) + { + OstProto::Stream *s = pc->add_streams(); + pg.mPorts.at(j)->streamByIndex(k)->protoDataCopyInto(*s); + } + + for (int k = 0; k < pg.mPorts.at(j)->numDeviceGroups(); k++) + { + OstProto::DeviceGroup *dg = pc->add_device_groups(); + dg->CopyFrom(*(pg.mPorts.at(j)->deviceGroupByIndex(k))); + } + } if (progress) { if (progress->wasCanceled()) From e75ed87dd718028edb2f94920346331e6a328255 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 26 Apr 2016 18:33:20 +0530 Subject: [PATCH 100/121] RPC channel now prints method names in addition to id for better debugging --- rpc/pbrpcchannel.cpp | 36 +++++++++++++++++++++++++----------- rpc/pbrpcchannel.h | 1 + 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/rpc/pbrpcchannel.cpp b/rpc/pbrpcchannel.cpp index 6719351..dff54b5 100644 --- a/rpc/pbrpcchannel.cpp +++ b/rpc/pbrpcchannel.cpp @@ -32,6 +32,7 @@ PbRpcChannel::PbRpcChannel(QString serverName, quint16 port, isPending = false; pendingMethodId = -1; // don't care as long as isPending is false + method = NULL; controller = NULL; done = NULL; response = NULL; @@ -107,9 +108,10 @@ void PbRpcChannel::CallMethod( { RpcCall call; qDebug("RpcChannel: queueing rpc since method %d is pending;<----\n " - "queued method = %d\n" + "queued method = %d:%s\n" "queued message = \n%s\n---->", - pendingMethodId, method->index(), req->DebugString().c_str()); + pendingMethodId, method->index(), method->name().c_str(), + req->DebugString().c_str()); call.method = method; call.controller = controller; @@ -128,7 +130,8 @@ void PbRpcChannel::CallMethod( if (!req->IsInitialized()) { qWarning("RpcChannel: missing required fields in request <----"); - qDebug("req = \n%s", req->DebugString().c_str()); + qDebug("req = %s\n%s", method->input_type()->name().c_str(), + req->DebugString().c_str()); qDebug("error = \n%s\n--->", req->InitializationErrorString().c_str()); controller->SetFailed("Required fields missing"); @@ -137,6 +140,7 @@ void PbRpcChannel::CallMethod( } pendingMethodId = method->index(); + this->method=method; this->controller=controller; this->done=done; this->response=response; @@ -153,8 +157,10 @@ void PbRpcChannel::CallMethod( qDebug("client(%s) sending %d bytes <----", __FUNCTION__, PB_HDR_SIZE + len); BUFDUMP(msg, PB_HDR_SIZE); - qDebug("method = %d\n req = \n%s\n---->", - method->index(), req->DebugString().c_str()); + qDebug("method = %d:%s\n req = %s\n%s\n---->", + method->index(), method->name().c_str(), + method->input_type()->name().c_str(), + req->DebugString().c_str()); } mpSocket->write(msg, PB_HDR_SIZE); @@ -308,14 +314,18 @@ _top: if (method != 13) { qDebug("client(%s): Received Msg <---- ", __FUNCTION__); - qDebug("method = %d\nresp = \n%s\n---->", - method, response->DebugString().c_str()); + qDebug("method = %d:%s\nresp = %s\n%s\n---->", + method, this->method->name().c_str(), + this->method->output_type()->name().c_str(), + response->DebugString().c_str()); } if (!response->IsInitialized()) { qWarning("RpcChannel: missing required fields in response <----"); - qDebug("resp = \n%s", response->DebugString().c_str()); + qDebug("resp = %s\n%s", + this->method->output_type()->name().c_str(), + response->DebugString().c_str()); qDebug("error = \n%s\n--->", response->InitializationErrorString().c_str()); @@ -419,6 +429,7 @@ _top: done->Run(); pendingMethodId = -1; + this->method = NULL; controller = NULL; response = NULL; isPending = false; @@ -428,9 +439,11 @@ _top: { RpcCall call = pendingCallList.takeFirst(); qDebug("RpcChannel: executing queued method <----\n" - "method = %d\n" - "req = \n%s\n---->", - call.method->index(), call.request->DebugString().c_str()); + "method = %d:%s\n" + "req = %s\n%s\n---->", + call.method->index(), call.method->name().c_str(), + call.method->input_type()->name().c_str(), + call.request->DebugString().c_str()); CallMethod(call.method, call.controller, call.request, call.response, call.done); } @@ -475,6 +488,7 @@ void PbRpcChannel::on_mpSocket_disconnected() qDebug("In %s", __FUNCTION__); pendingMethodId = -1; + method = NULL; controller = NULL; response = NULL; isPending = false; diff --git a/rpc/pbrpcchannel.h b/rpc/pbrpcchannel.h index 5c598f4..8c77412 100644 --- a/rpc/pbrpcchannel.h +++ b/rpc/pbrpcchannel.h @@ -49,6 +49,7 @@ class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel /*! \todo (MED) : change controller, done and response to references instead of pointers? */ + const ::google::protobuf::MethodDescriptor *method; ::google::protobuf::RpcController *controller; ::google::protobuf::Closure *done; ::google::protobuf::Message *response; From 24266320558bf7dd870a31445d28224b3005f83d Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 26 Apr 2016 20:23:50 +0530 Subject: [PATCH 101/121] Don't wait for all responses of getStreamIdList() before invoking getStreamConfig(); similarly for devices too - this change should simplify session file open --- client/portgroup.cpp | 95 +++++++++++++++++++------------------------- client/portgroup.h | 4 +- 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index a97a8f6..b0f1087 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -776,43 +776,35 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) mPorts[portIndex]->insertStream(streamId); } - // Are we done for all ports? - if (numPorts() && portIndex >= (numPorts()-1)) - { - // FIXME(HI): some way to reset streammodel - getStreamConfigList(); - } + getStreamConfigList(portIndex); _exit: delete controller; } -void PortGroup::getStreamConfigList() +void PortGroup::getStreamConfigList(int portIndex) { - qDebug("requesting stream config list ..."); + if (mPorts[portIndex]->numStreams() == 0) + return; - for (int portIndex = 0; portIndex < numPorts(); portIndex++) + qDebug("requesting stream config list (port %d)...", portIndex); + + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; + OstProto::StreamConfigList *streamConfigList + = new OstProto::StreamConfigList; + PbRpcController *controller = new PbRpcController( + streamIdList, streamConfigList); + + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + for (int j = 0; j < mPorts[portIndex]->numStreams(); j++) { - if (mPorts[portIndex]->numStreams() == 0) - continue; - - OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; - OstProto::StreamConfigList *streamConfigList - = new OstProto::StreamConfigList; - PbRpcController *controller = new PbRpcController( - streamIdList, streamConfigList); - - streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); - for (int j = 0; j < mPorts[portIndex]->numStreams(); j++) - { - OstProto::StreamId *s = streamIdList->add_stream_id(); - s->set_id(mPorts[portIndex]->streamByIndex(j)->id()); - } - - serviceStub->getStreamConfig(controller, streamIdList, streamConfigList, - NewCallback(this, &PortGroup::processStreamConfigList, - portIndex, controller)); + OstProto::StreamId *s = streamIdList->add_stream_id(); + s->set_id(mPorts[portIndex]->streamByIndex(j)->id()); } + + serviceStub->getStreamConfig(controller, streamIdList, streamConfigList, + NewCallback(this, &PortGroup::processStreamConfigList, + portIndex, controller)); } void PortGroup::processStreamConfigList(int portIndex, @@ -919,45 +911,40 @@ void PortGroup::processDeviceGroupIdList( mPorts[portIndex]->when_syncComplete(); - // Are we done for all ports? - if (numPorts() && portIndex >= (numPorts()-1)) - getDeviceGroupConfigList(); + getDeviceGroupConfigList(portIndex); _exit: delete controller; } -void PortGroup::getDeviceGroupConfigList() +void PortGroup::getDeviceGroupConfigList(int portIndex) { using OstProto::DeviceGroupId; using OstProto::DeviceGroupIdList; using OstProto::DeviceGroupConfigList; - qDebug("requesting device group config list ..."); + if (mPorts[portIndex]->numDeviceGroups() == 0) + return; - for (int portIndex = 0; portIndex < numPorts(); portIndex++) + qDebug("requesting device group config list (port %d) ...", portIndex); + + DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList; + DeviceGroupConfigList *devGrpCfgList = new DeviceGroupConfigList; + PbRpcController *controller = new PbRpcController( + devGrpIdList, devGrpCfgList); + + devGrpIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + for (int j = 0; j < mPorts[portIndex]->numDeviceGroups(); j++) { - if (mPorts[portIndex]->numDeviceGroups() == 0) - continue; - - DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList; - DeviceGroupConfigList *devGrpCfgList = new DeviceGroupConfigList; - PbRpcController *controller = new PbRpcController( - devGrpIdList, devGrpCfgList); - - devGrpIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); - for (int j = 0; j < mPorts[portIndex]->numDeviceGroups(); j++) - { - DeviceGroupId *dgid = devGrpIdList->add_device_group_id(); - dgid->set_id(mPorts[portIndex]->deviceGroupByIndex(j) - ->device_group_id().id()); - } - - serviceStub->getDeviceGroupConfig(controller, - devGrpIdList, devGrpCfgList, - NewCallback(this, &PortGroup::processDeviceGroupConfigList, - portIndex, controller)); + DeviceGroupId *dgid = devGrpIdList->add_device_group_id(); + dgid->set_id(mPorts[portIndex]->deviceGroupByIndex(j) + ->device_group_id().id()); } + + serviceStub->getDeviceGroupConfig(controller, + devGrpIdList, devGrpCfgList, + NewCallback(this, &PortGroup::processDeviceGroupConfigList, + portIndex, controller)); } void PortGroup::processDeviceGroupConfigList(int portIndex, diff --git a/client/portgroup.h b/client/portgroup.h index c5c99eb..8dac62d 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -127,14 +127,14 @@ public: void getStreamIdList(); void processStreamIdList(int portIndex, PbRpcController *controller); - void getStreamConfigList(); + void getStreamConfigList(int portIndex); void processStreamConfigList(int portIndex, PbRpcController *controller); void processModifyStreamAck(OstProto::Ack *ack); void getDeviceGroupIdList(); void processDeviceGroupIdList(int portIndex, PbRpcController *controller); - void getDeviceGroupConfigList(); + void getDeviceGroupConfigList(int portIndex); void processDeviceGroupConfigList( int portIndex, PbRpcController *controller); From b8db66a2d55f11d36bef1120834c1f3fc2ee518a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 5 May 2016 19:13:00 +0530 Subject: [PATCH 102/121] Save/Open Session - added open session code (incomplete) --- client/portgroup.cpp | 139 +++++++++++++++++++++++++++++++++++++++---- client/portgroup.h | 2 + 2 files changed, 128 insertions(+), 13 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index b0f1087..af96531 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -184,14 +184,6 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller) compat = kCompatible; - if (atConnectConfig_) - { - // TODO: apply config - - delete atConnectConfig_; - atConnectConfig_ = NULL; - } - else { OstProto::Void *void_ = new OstProto::Void; OstProto::PortIdList *portIdList = new OstProto::PortIdList; @@ -213,6 +205,7 @@ void PortGroup::on_rpcChannel_disconnected() while (!mPorts.isEmpty()) delete mPorts.takeFirst(); + atConnectPortConfig_.clear(); emit portListChanged(mPortGroupId); emit portGroupDataChanged(mPortGroupId); @@ -330,6 +323,7 @@ void PortGroup::processPortIdList(PbRpcController *controller) this, SIGNAL(portGroupDataChanged(int, int))); qDebug("before port append\n"); mPorts.append(p); + atConnectPortConfig_.append(NULL); // will be filled later } emit portListChanged(mPortGroupId); @@ -396,6 +390,27 @@ void PortGroup::processPortConfigList(PbRpcController *controller) getStreamIdList(); } + // Now that we have the port details, let's identify which ports + // need to be re-configured based on atConnectConfig_ + if (atConnectConfig_ && numPorts() > 0) + { + for (int i = 0; i < atConnectConfig_->ports_size(); i++) + { + const OstProto::PortContent *pc = &atConnectConfig_->ports(i); + for (int j = 0; j < mPorts.size(); j++) + { + // FIXME: How to handle the generated ifX Win32 port names + if (mPorts[j]->name() == pc->port_config().name().c_str()) + { + atConnectPortConfig_[j] = pc; + qDebug("port %d will be reconfigured", j); + break; + } + + } + } + } + _error_exit: delete controller; } @@ -749,6 +764,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) { OstProto::StreamIdList *streamIdList = static_cast(controller->response()); + const OstProto::PortContent *newPortContent + = atConnectPortConfig_.at(portIndex); qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); @@ -768,15 +785,111 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) goto _exit; } - for(int i = 0; i < streamIdList->stream_id_size(); i++) + // FIXME: what if port already has a reservation? + if (newPortContent) { - uint streamId; + // This port needs to configured with new content - to do this + // we'll insert the following RPC sequence at this point and once + // this sequence is over, return to the regular RPC sequence by + // re-requesting getStreamId() + // * delete (existing) streams + // * modify port + // * add (new) stream ids + // * modify (new) streams + // FIXME: delete/add/modify deviceGroups - streamId = streamIdList->stream_id(i).id(); - mPorts[portIndex]->insertStream(streamId); + // XXX: same name as input param, but shouldn't cause any problem + PbRpcController *controller; + quint32 portId = mPorts[portIndex]->id(); + + // delete all existing streams + if (streamIdList->stream_id_size()) + { + OstProto::StreamIdList *streamIdList2 = new OstProto::StreamIdList; + streamIdList2->CopyFrom(*streamIdList); + + OstProto::Ack *ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList2, ack); + + serviceStub->deleteStream(controller, streamIdList2, ack, + NewCallback(this, &PortGroup::processDeleteStreamAck, + controller)); + } + + // modify port FIXME: check if there's actually any change + if (newPortContent->has_port_config()) + { + OstProto::PortConfigList *portConfigList = + new OstProto::PortConfigList; + OstProto::Port *port = portConfigList->add_port(); + port->CopyFrom(newPortContent->port_config()); + port->mutable_port_id()->set_id(portId); // overwrite + + OstProto::Ack *ack = new OstProto::Ack; + controller = new PbRpcController(portConfigList, ack); + + serviceStub->modifyPort(controller, portConfigList, ack, + NewCallback(this, &PortGroup::processModifyPortAck, controller)); + // FIXME: change callback function to avoid mainWindow ops + } + + // add/modify streams + if (newPortContent->streams_size()) + { + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; + OstProto::StreamConfigList *streamConfigList = + new OstProto::StreamConfigList; + streamIdList->mutable_port_id()->set_id(portId); + streamConfigList->mutable_port_id()->set_id(portId); + for (int i = 0; i < newPortContent->streams_size(); i++) + { + const OstProto::Stream &s = newPortContent->streams(i); + streamIdList->add_stream_id()->set_id(s.stream_id().id()); + streamConfigList->add_stream()->CopyFrom(s); + } + + OstProto::Ack *ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList, ack); + + serviceStub->addStream(controller, streamIdList, ack, + NewCallback(this, &PortGroup::processAddStreamAck, + controller)); + + ack = new OstProto::Ack; + controller = new PbRpcController(streamConfigList, ack); + + serviceStub->modifyStream(controller, streamConfigList, ack, + NewCallback(this, &PortGroup::processModifyStreamAck, + portIndex, controller)); + } + + // delete newPortConfig + delete atConnectPortConfig_.at(portIndex); + atConnectPortConfig_[portIndex] = NULL; + + // return to normal sequence re-starting from getStreamIdList() + OstProto::PortId *portId2 = new OstProto::PortId; + portId2->set_id(portId); + + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; + controller = new PbRpcController(portId2, streamIdList); + + serviceStub->getStreamIdList(controller, portId2, streamIdList, + NewCallback(this, &PortGroup::processStreamIdList, + portIndex, controller)); } + else + { + for(int i = 0; i < streamIdList->stream_id_size(); i++) + { + uint streamId; - getStreamConfigList(portIndex); + streamId = streamIdList->stream_id(i).id(); + mPorts[portIndex]->insertStream(streamId); + } + + getStreamConfigList(portIndex); + } _exit: delete controller; diff --git a/client/portgroup.h b/client/portgroup.h index 8dac62d..5c3ef81 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -37,6 +37,7 @@ LOW #define DEFAULT_SERVER_PORT 7878 namespace OstProto { + class PortContent; class PortGroupContent; } @@ -67,6 +68,7 @@ private: OstProto::PortStatsList *portStatsList_; OstProto::PortGroupContent *atConnectConfig_; + QList atConnectPortConfig_; public: // FIXME(HIGH): member access QList mPorts; From 294d1c80a1506a5accbe15973e0c94a4f3402dc0 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 6 May 2016 21:24:36 +0530 Subject: [PATCH 103/121] Save/Open Session: Save only owned ports if some ports are reserved - corresponding open session code not to overwrite a owned port is pending --- client/mainwindow.cpp | 11 +++++++++++ client/portgroup.cpp | 10 ++++++++++ client/portgroup.h | 1 + client/portswindow.cpp | 20 ++++++++++++++++++++ client/portswindow.h | 1 + 5 files changed, 43 insertions(+) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 2182e4f..5dcf030 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -206,6 +206,17 @@ void MainWindow::on_actionSaveSession_triggered() if (fileName.isEmpty()) goto _exit; + if (portsWindow->reservedPortCount()) { + QString myself = appSettings->value(kUserKey, kUserDefaultValue) + .toString(); + if (QMessageBox::question(this, + tr("Save Session"), + QString("Some ports are reserved!\n\nOnly ports reserved by %1 will be saved. Proceed?").arg(myself), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == QMessageBox::No) + goto _exit; + } + if (!saveSession(fileName, fileType, errorStr)) QMessageBox::critical(this, qApp->applicationName(), errorStr); else if (!errorStr.isEmpty()) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index af96531..2ab76da 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -110,6 +110,16 @@ void PortGroup::setConfigAtConnect(const OstProto::PortGroupContent *config) atConnectConfig_->CopyFrom(*config); } +int PortGroup::numReservedPorts() const +{ + int count = 0; + for (int i = 0; i < mPorts.size(); i++) + { + if (!mPorts[i]->userName().isEmpty()) + count++; + } +} + // ------------------------------------------------ // Slots // ------------------------------------------------ diff --git a/client/portgroup.h b/client/portgroup.h index 5c3ef81..4a07a60 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -93,6 +93,7 @@ public: void setConfigAtConnect(const OstProto::PortGroupContent *config); int numPorts() const { return mPorts.size(); } + int numReservedPorts() const; quint32 id() const { return mPortGroupId; } const QString& userAlias() const { return mUserAlias; } diff --git a/client/portswindow.cpp b/client/portswindow.cpp index fb93a04..49c4e82 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -178,6 +178,17 @@ int PortsWindow::portGroupCount() return plm->numPortGroups(); } +int PortsWindow::reservedPortCount() +{ + int count = 0; + int n = portGroupCount(); + + for (int i = 0; i < n; i++) + count += plm->portGroupByIndex(i).numReservedPorts(); + + return count; +} + //! Always return true bool PortsWindow::openSession( const OstProto::SessionContent *session, @@ -206,6 +217,8 @@ bool PortsWindow::openSession( /*! * Prepare content to be saved for a session * + * If port reservation is in use, saves only 'my' reserved ports + * * Returns false, if user cancels op; true, otherwise */ bool PortsWindow::saveSession( @@ -214,12 +227,16 @@ bool PortsWindow::saveSession( QProgressDialog *progress) { int n = portGroupCount(); + QString myself; if (progress) { progress->setLabelText("Preparing Ports and PortGroups ..."); progress->setRange(0, n); } + if (reservedPortCount()) + myself = appSettings->value(kUserKey, kUserDefaultValue).toString(); + for (int i = 0; i < n; i++) { PortGroup &pg = plm->portGroupByIndex(i); @@ -230,6 +247,9 @@ bool PortsWindow::saveSession( for (int j = 0; j < pg.numPorts(); j++) { + if (myself != pg.mPorts.at(j)->userName()) + continue; + OstProto::PortContent *pc = pgc->add_ports(); OstProto::Port *p = pc->mutable_port_config(); diff --git a/client/portswindow.h b/client/portswindow.h index 68dc3dc..b407270 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -45,6 +45,7 @@ public: ~PortsWindow(); int portGroupCount(); + int reservedPortCount(); bool openSession(const OstProto::SessionContent *session, QString &error); From 0b24bd6dde07fab4febdfc177a3175f1113de4fd Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 9 May 2016 18:37:42 +0530 Subject: [PATCH 104/121] Save/Open Session: Fix crash while deleting atConnectConfig_ --- client/portgroup.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 2ab76da..79904c0 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -106,7 +106,7 @@ void PortGroup::setConfigAtConnect(const OstProto::PortGroupContent *config) } if (!atConnectConfig_) - atConnectConfig_ = config->New(); + atConnectConfig_ = new OstProto::PortGroupContent; atConnectConfig_->CopyFrom(*config); } @@ -874,7 +874,6 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) } // delete newPortConfig - delete atConnectPortConfig_.at(portIndex); atConnectPortConfig_[portIndex] = NULL; // return to normal sequence re-starting from getStreamIdList() From 5406d3dad8e86b576485e1d8241e1d173b640d38 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 10 May 2016 20:26:48 +0530 Subject: [PATCH 105/121] Open Session: Don't reconfigure ports reserved by someone else; also for the ports which are reconfigured, used self name rather than the name in the session file --- client/portgroup.cpp | 32 +++++++++++++++++++++++++++++++- client/portgroup.h | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 79904c0..39b2457 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -118,6 +118,14 @@ int PortGroup::numReservedPorts() const if (!mPorts[i]->userName().isEmpty()) count++; } + + return count; +} + +const QString PortGroup::serverFullName() const +{ + return serverPort() == DEFAULT_SERVER_PORT ? + serverName() : QString("%1:%2").arg(serverName()).arg(serverPort()); } // ------------------------------------------------ @@ -404,14 +412,32 @@ void PortGroup::processPortConfigList(PbRpcController *controller) // need to be re-configured based on atConnectConfig_ if (atConnectConfig_ && numPorts() > 0) { + QString myself = appSettings->value(kUserKey, kUserDefaultValue) + .toString(); for (int i = 0; i < atConnectConfig_->ports_size(); i++) { const OstProto::PortContent *pc = &atConnectConfig_->ports(i); for (int j = 0; j < mPorts.size(); j++) { + Port *port = mPorts[j]; + // FIXME: How to handle the generated ifX Win32 port names - if (mPorts[j]->name() == pc->port_config().name().c_str()) + if (port->name() == pc->port_config().name().c_str()) { + if (!port->userName().isEmpty() // rsvd? + && port->userName() != myself) // by someone else? + { + QString warning = + QString("%1 - %2: %3 is reserved by %4.\n\n" + "Port will not be reconfigured.") + .arg(serverFullName()) + .arg(j) + .arg(port->name()) + .arg(port->userName()); + QMessageBox::warning(NULL, tr("Open Session"), warning); + qWarning(qPrintable(warning)); + continue; + } atConnectPortConfig_[j] = pc; qDebug("port %d will be reconfigured", j); break; @@ -811,6 +837,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) // XXX: same name as input param, but shouldn't cause any problem PbRpcController *controller; quint32 portId = mPorts[portIndex]->id(); + QString myself = appSettings->value(kUserKey, kUserDefaultValue) + .toString(); // delete all existing streams if (streamIdList->stream_id_size()) @@ -834,6 +862,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) OstProto::Port *port = portConfigList->add_port(); port->CopyFrom(newPortContent->port_config()); port->mutable_port_id()->set_id(portId); // overwrite + if (newPortContent->port_config().has_user_name()) + port->set_user_name(qPrintable(myself)); // overwrite OstProto::Ack *ack = new OstProto::Ack; controller = new PbRpcController(portConfigList, ack); diff --git a/client/portgroup.h b/client/portgroup.h index 4a07a60..1acadcd 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -103,6 +103,7 @@ public: { return rpcChannel->serverName(); } quint16 serverPort() const { return rpcChannel->serverPort(); } + const QString serverFullName() const; QAbstractSocket::SocketState state() const { if (compat == kIncompatible) return QAbstractSocket::SocketState(-1); From 672ceeeb2c6389464f34f06ede9582ef5262fbf0 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 12 May 2016 18:47:16 +0530 Subject: [PATCH 106/121] Open Session - implemented DeviceGroups related code --- client/portgroup.cpp | 96 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 39b2457..71a9d85 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -404,6 +404,10 @@ void PortGroup::processPortConfigList(PbRpcController *controller) emit portListChanged(mPortGroupId); if (numPorts() > 0) { + // XXX: The open session code (atConnectConfig_ related) assumes + // the following two RPCs are invoked in the below order + // Any change here without coressponding change in that code + // will break stuff getDeviceGroupIdList(); getStreamIdList(); } @@ -821,18 +825,22 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) goto _exit; } - // FIXME: what if port already has a reservation? if (newPortContent) { // This port needs to configured with new content - to do this // we'll insert the following RPC sequence at this point and once // this sequence is over, return to the regular RPC sequence by // re-requesting getStreamId() + // * delete (existing) deviceGroups + // (already done by processDeviceIdList) // * delete (existing) streams // * modify port + // * add (new) deviceGroup ids + // * modify (new) deviceGroups // * add (new) stream ids // * modify (new) streams - // FIXME: delete/add/modify deviceGroups + // XXX: This assumes getDeviceGroupIdList() was invoked before + // getStreamIdList() - if the order changes this code will break! // XXX: same name as input param, but shouldn't cause any problem PbRpcController *controller; @@ -873,6 +881,40 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) // FIXME: change callback function to avoid mainWindow ops } + // add/modify deviceGroups + if (newPortContent->device_groups_size()) + { + OstProto::DeviceGroupIdList *deviceGroupIdList + = new OstProto::DeviceGroupIdList; + OstProto::DeviceGroupConfigList *deviceGroupConfigList + = new OstProto::DeviceGroupConfigList; + deviceGroupIdList->mutable_port_id()->set_id(portId); + deviceGroupConfigList->mutable_port_id()->set_id(portId); + for (int i = 0; i < newPortContent->device_groups_size(); i++) + { + const OstProto::DeviceGroup &dg + = newPortContent->device_groups(i); + deviceGroupIdList->add_device_group_id()->set_id( + dg.device_group_id().id()); + deviceGroupConfigList->add_device_group()->CopyFrom(dg); + } + + OstProto::Ack *ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupIdList, ack); + + serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack, + NewCallback(this, &PortGroup::processAddDeviceGroupAck, + controller)); + + ack = new OstProto::Ack; + controller = new PbRpcController(deviceGroupConfigList, ack); + + serviceStub->modifyDeviceGroup(controller, + deviceGroupConfigList, ack, + NewCallback(this, &PortGroup::processModifyDeviceGroupAck, + portIndex, controller)); + } + // add/modify streams if (newPortContent->streams_size()) { @@ -906,10 +948,21 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) // delete newPortConfig atConnectPortConfig_[portIndex] = NULL; - // return to normal sequence re-starting from getStreamIdList() + // return to normal sequence re-starting from + // getDeviceGroupIdList() and getStreamIdList() OstProto::PortId *portId2 = new OstProto::PortId; portId2->set_id(portId); + OstProto::DeviceGroupIdList *devGrpIdList + = new OstProto::DeviceGroupIdList; + controller = new PbRpcController(portId2, devGrpIdList); + + serviceStub->getDeviceGroupIdList(controller, portId2, devGrpIdList, + NewCallback(this, &PortGroup::processDeviceGroupIdList, + portIndex, controller)); + + portId2 = new OstProto::PortId; + portId2->set_id(portId); OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; controller = new PbRpcController(portId2, streamIdList); @@ -1034,6 +1087,8 @@ void PortGroup::processDeviceGroupIdList( DeviceGroupIdList *devGrpIdList = static_cast( controller->response()); + const OstProto::PortContent *newPortContent = atConnectPortConfig_.at( + portIndex); qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); @@ -1053,17 +1108,40 @@ void PortGroup::processDeviceGroupIdList( goto _exit; } - for(int i = 0; i < devGrpIdList->device_group_id_size(); i++) + if (newPortContent) { - uint devGrpId; + // We delete all existing deviceGroups + // Remaining stuff is done in processStreamIdList() - see notes there + if (devGrpIdList->device_group_id_size()) + { + OstProto::DeviceGroupIdList *devGrpIdList2 + = new OstProto::DeviceGroupIdList; + devGrpIdList2->CopyFrom(*devGrpIdList); - devGrpId = devGrpIdList->device_group_id(i).id(); - mPorts[portIndex]->insertDeviceGroup(devGrpId); + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller + = new PbRpcController(devGrpIdList2, ack); + + serviceStub->deleteDeviceGroup(controller, devGrpIdList2, ack, + NewCallback(this, &PortGroup::processDeleteDeviceGroupAck, + controller)); + } } + else + { + for(int i = 0; i < devGrpIdList->device_group_id_size(); i++) + { + uint devGrpId; - mPorts[portIndex]->when_syncComplete(); + devGrpId = devGrpIdList->device_group_id(i).id(); + mPorts[portIndex]->insertDeviceGroup(devGrpId); + } - getDeviceGroupConfigList(portIndex); + // FIXME: incorrect? recheck!!! + mPorts[portIndex]->when_syncComplete(); + + getDeviceGroupConfigList(portIndex); + } _exit: delete controller; From 3090b5eebd369a4088bf1e4324ea6df2ba4bfb28 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 13 May 2016 20:10:53 +0530 Subject: [PATCH 107/121] Mark SyncComplete after stream RPCs since those are invoked after device RPCs --- client/portgroup.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 71a9d85..d5c7f4b 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -980,6 +980,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) mPorts[portIndex]->insertStream(streamId); } + mPorts[portIndex]->when_syncComplete(); + getStreamConfigList(portIndex); } @@ -1137,9 +1139,6 @@ void PortGroup::processDeviceGroupIdList( mPorts[portIndex]->insertDeviceGroup(devGrpId); } - // FIXME: incorrect? recheck!!! - mPorts[portIndex]->when_syncComplete(); - getDeviceGroupConfigList(portIndex); } From f626c179aa4511505b06af6b965eb3fbabb89f3a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 14 May 2016 19:49:07 +0530 Subject: [PATCH 108/121] Open Session - Invoke modifyPort RPC only if required --- client/port.cpp | 34 ++++++++++++++++++++++++++++++++++ client/port.h | 2 ++ client/portgroup.cpp | 9 ++++----- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 87c2dd0..bcecd8e 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -454,6 +454,40 @@ void Port::getModifiedDeviceGroupsSinceLastSync( ->CopyFrom(*deviceGroupById(id)); } +/*! + * Finds the user modifiable fields in 'config' that are different from + * the current configuration on the port and modifes 'config' such that + * only those fields are set and returns true. If 'config' is same as + * current port config, 'config' is unmodified and false is returned + */ +bool Port::modifiablePortConfig(OstProto::Port &config) const +{ + bool change = false; + OstProto::Port modCfg; + + if (config.is_exclusive_control() != d.is_exclusive_control()) { + modCfg.set_is_exclusive_control(config.is_exclusive_control()); + change = true; + } + if (config.transmit_mode() != d.transmit_mode()) { + modCfg.set_transmit_mode(config.transmit_mode()); + change = true; + } + if (config.user_name() != d.user_name()) { + modCfg.set_user_name(config.user_name()); + change = true; + } + + if (change) { + modCfg.mutable_port_id()->set_id(id()); + config.CopyFrom(modCfg); + + return true; + } + + return false; +} + void Port::when_syncComplete() { //reorderStreamsByOrdinals(); diff --git a/client/port.h b/client/port.h index af86255..85ce89a 100644 --- a/client/port.h +++ b/client/port.h @@ -155,6 +155,8 @@ public: void getModifiedDeviceGroupsSinceLastSync( OstProto::DeviceGroupConfigList &streamConfigList); + bool modifiablePortConfig(OstProto::Port &config) const; + void when_syncComplete(); void setAveragePacketRate(double packetsPerSec); diff --git a/client/portgroup.cpp b/client/portgroup.cpp index d5c7f4b..2f60348 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -862,15 +862,14 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) controller)); } - // modify port FIXME: check if there's actually any change - if (newPortContent->has_port_config()) + OstProto::Port portCfg = newPortContent->port_config(); + if (mPorts[portIndex]->modifiablePortConfig(portCfg)) { OstProto::PortConfigList *portConfigList = new OstProto::PortConfigList; OstProto::Port *port = portConfigList->add_port(); - port->CopyFrom(newPortContent->port_config()); - port->mutable_port_id()->set_id(portId); // overwrite - if (newPortContent->port_config().has_user_name()) + port->CopyFrom(portCfg); + if (port->has_user_name()) port->set_user_name(qPrintable(myself)); // overwrite OstProto::Ack *ack = new OstProto::Ack; From 97ad49748090a6c36b9b5f40c0af0943c53d6e03 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 14 May 2016 20:13:43 +0530 Subject: [PATCH 109/121] Open Session - no UI enabling/restore required after modifyPort --- client/portgroup.cpp | 14 ++++++++------ client/portgroup.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 2f60348..99faff4 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -736,10 +736,10 @@ void PortGroup::modifyPort(int portIndex, OstProto::Port portConfig) PbRpcController *controller = new PbRpcController(portConfigList, ack); serviceStub->modifyPort(controller, portConfigList, ack, - NewCallback(this, &PortGroup::processModifyPortAck, controller)); + NewCallback(this, &PortGroup::processModifyPortAck, true, controller)); } -void PortGroup::processModifyPortAck(PbRpcController *controller) +void PortGroup::processModifyPortAck(bool restoreUi,PbRpcController *controller) { qDebug("In %s", __FUNCTION__); @@ -749,8 +749,10 @@ void PortGroup::processModifyPortAck(PbRpcController *controller) qPrintable(controller->ErrorString())); } - mainWindow->setEnabled(true); - QApplication::restoreOverrideCursor(); + if (restoreUi) { + mainWindow->setEnabled(true); + QApplication::restoreOverrideCursor(); + } delete controller; } @@ -876,8 +878,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) controller = new PbRpcController(portConfigList, ack); serviceStub->modifyPort(controller, portConfigList, ack, - NewCallback(this, &PortGroup::processModifyPortAck, controller)); - // FIXME: change callback function to avoid mainWindow ops + NewCallback(this, &PortGroup::processModifyPortAck, + false, controller)); } // add/modify deviceGroups diff --git a/client/portgroup.h b/client/portgroup.h index 1acadcd..ef60f09 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -126,7 +126,7 @@ public: void processDeviceNeighbors(int portIndex, PbRpcController *controller); void modifyPort(int portId, OstProto::Port portConfig); - void processModifyPortAck(PbRpcController *controller); + void processModifyPortAck(bool restoreUi, PbRpcController *controller); void processUpdatedPortConfig(PbRpcController *controller); void getStreamIdList(); From ba754c10432c02dcfe148ed717f141c162f93a5a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 17 May 2016 20:47:10 +0530 Subject: [PATCH 110/121] Open Session - check fileType for native file formats while identifying the file format from filename; also find size of metadata and use it when parsing just the metadata instead of the whole file --- client/mainwindow.cpp | 4 ++- client/port.cpp | 4 ++- common/nativefileformat.cpp | 64 ++++++++++++++++++++++++++++++++----- common/nativefileformat.h | 1 + 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 5dcf030..e460cb5 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -276,8 +276,10 @@ bool MainWindow::openSession(QString fileName, QString &error) OstProto::SessionContent session; SessionFileFormat *fmt = SessionFileFormat::fileFormatFromFile(fileName); - if (fmt == NULL) + if (fmt == NULL) { + error = tr("Unknown session file format"); goto _fail; + } if ((optDialog = fmt->openOptionsDialog())) { diff --git a/client/port.cpp b/client/port.cpp index bcecd8e..8771c0c 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -551,8 +551,10 @@ bool Port::openStreams(QString fileName, bool append, QString &error) OstProto::StreamConfigList streams; AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromFile(fileName); - if (fmt == NULL) + if (fmt == NULL) { + error = tr("Unknown streams file format"); goto _fail; + } if ((optDialog = fmt->openOptionsDialog())) { diff --git a/common/nativefileformat.cpp b/common/nativefileformat.cpp index 4e4f380..5376852 100644 --- a/common/nativefileformat.cpp +++ b/common/nativefileformat.cpp @@ -140,11 +140,9 @@ bool NativeFileFormat::open( goto _cksum_verify_fail; // Parse the metadata first before we parse the full contents - // FIXME: metadata size is not known beforehand, so we end up - // parsing till EOF if (!meta.ParseFromArray( (void*)(buf.constData() + kFileMetaDataOffset), - size - kFileMetaDataOffset)) + fileMetaSize((quint8*)buf.constData(), size))) { goto _metadata_parse_fail; } @@ -420,15 +418,21 @@ bool NativeFileFormat::isNativeFileFormat( if (!file.open(QIODevice::ReadOnly)) goto _exit; - buf = file.peek(kFileMagicOffset + kFileMagicSize); + // Assume tag/length for MetaData will fit in 8 bytes + buf = file.peek(kFileMagicOffset + kFileMagicSize + 8); if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset), kFileMagicSize)) goto _close_exit; - if (magic.value() == kFileMagicValue) - ret = true; - - // TODO: check fileType + if (magic.value() == kFileMagicValue) { + OstProto::FileMetaData meta; + if (!meta.ParseFromArray( + (void*)(buf.constData() + kFileMetaDataOffset), + fileMetaSize((quint8*)buf.constData(), buf.size()))) + goto _close_exit; + if (meta.file_type() == fileType) + ret = true; + } _close_exit: file.close(); @@ -451,6 +455,50 @@ void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData) qApp->property("revision").toString().toUtf8().constData()); } +int NativeFileFormat::fileMetaSize(const quint8* file, int size) +{ + int i = kFileMetaDataOffset; + uint result, shift; + const int kWireTypeLengthDelimited = 2; + + // An embedded Message field is encoded as + // + // See Protobuf Encoding for more details + + // Decode 'Key' varint + result = 0; + shift = 0; + while (i < size) { + quint8 byte = file[i++]; + result |= (byte & 0x7f) << shift; + if (!(byte & 0x80)) // MSB == 0? + break; + shift += 7; + } + + if (i >= size) + return 0; + + Q_ASSERT(result == ((OstProto::File::kMetaDataFieldNumber << 3) + | kWireTypeLengthDelimited)); + + // Decode 'Length' varint + result = 0; + shift = 0; + while (i < size) { + quint8 byte = file[i++]; + result |= (byte & 0x7f) << shift; + if (!(byte & 0x80)) // MSB == 0? + break; + shift += 7; + } + + if (i >= size) + return 0; + + return int(result+(i-kFileMetaDataOffset)); +} + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /*! Fixup content to what is expected in the native version */ void NativeFileFormat::postParseFixup(OstProto::FileMetaData metaData, diff --git a/common/nativefileformat.h b/common/nativefileformat.h index 59054dd..83759ae 100644 --- a/common/nativefileformat.h +++ b/common/nativefileformat.h @@ -57,6 +57,7 @@ public: private: void initFileMetaData(OstProto::FileMetaData &metaData); + int fileMetaSize(const quint8* file, int size); static const int kFileMagicSize = 12; static const int kFileChecksumSize = 5; From 8bea5636abb819f28fc9112809b932bd7fa0c65c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 17 May 2016 22:16:52 +0530 Subject: [PATCH 111/121] Open Session: Fixing problem with previous commit where valid format file was also declared invalid/unkonwn --- common/nativefileformat.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common/nativefileformat.cpp b/common/nativefileformat.cpp index 5376852..e2fc3eb 100644 --- a/common/nativefileformat.cpp +++ b/common/nativefileformat.cpp @@ -425,12 +425,16 @@ bool NativeFileFormat::isNativeFileFormat( goto _close_exit; if (magic.value() == kFileMagicValue) { - OstProto::FileMetaData meta; + OstProto::FileMeta meta; + int metaSize = fileMetaSize((quint8*)buf.constData(), buf.size()); + buf = file.peek(kFileMagicOffset + kFileMagicSize + metaSize); if (!meta.ParseFromArray( - (void*)(buf.constData() + kFileMetaDataOffset), - fileMetaSize((quint8*)buf.constData(), buf.size()))) + (void*)(buf.constData() + kFileMetaDataOffset), metaSize)) { + qDebug("%s: File MetaData\n%s", __FUNCTION__, + QString().fromStdString(meta.DebugString()).toAscii().constData()); goto _close_exit; - if (meta.file_type() == fileType) + } + if (meta.data().file_type() == fileType) ret = true; } From b6a6b776e1c5f95ab6430d77f9c99e40e307a0ae Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 18 May 2016 18:37:48 +0530 Subject: [PATCH 112/121] Open Session - Drone on Windows now sends actual /Device/NPF_XXXX as port names - client sets up the ifX style alias and uses that all places; this was needed for comparison of port names while opening sessions for Drones running on Windows --- client/port.cpp | 4 ++++ client/port.h | 5 +++-- client/portgroup.cpp | 3 +-- client/portmodel.cpp | 2 +- server/pcapport.cpp | 4 ---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 8771c0c..7f3b275 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -81,6 +81,10 @@ void Port::updatePortConfig(OstProto::Port *port) d.MergeFrom(*port); + // Setup a user-friendly alias for Win32 ports + if (name().startsWith("\\Device\\NPF_")) + setAlias(QString("if%1").arg(id())); + if (recalc) recalculateAverageRates(); } diff --git a/client/port.h b/client/port.h index 85ce89a..8496843 100644 --- a/client/port.h +++ b/client/port.h @@ -79,7 +79,8 @@ public: ~Port(); quint32 portGroupId() const { return mPortGroupId; } - const QString& userAlias() const { return mUserAlias; } + const QString userAlias() const + { return mUserAlias.isEmpty() ? name() : mUserAlias; } quint32 id() const { return d.port_id().id(); } @@ -103,7 +104,7 @@ public: { return avgBitsPerSec_; } //void setAdminEnable(AdminStatus status) { mAdminStatus = status; } - void setAlias(QString &alias) { mUserAlias = alias; } + void setAlias(QString alias) { mUserAlias = alias; } //void setExclusive(bool flag); int numStreams() { return mStreams.size(); } diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 99faff4..d81767c 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -425,7 +425,6 @@ void PortGroup::processPortConfigList(PbRpcController *controller) { Port *port = mPorts[j]; - // FIXME: How to handle the generated ifX Win32 port names if (port->name() == pc->port_config().name().c_str()) { if (!port->userName().isEmpty() // rsvd? @@ -436,7 +435,7 @@ void PortGroup::processPortConfigList(PbRpcController *controller) "Port will not be reconfigured.") .arg(serverFullName()) .arg(j) - .arg(port->name()) + .arg(port->userAlias()) .arg(port->userName()); QMessageBox::warning(NULL, tr("Open Session"), warning); qWarning(qPrintable(warning)); diff --git a/client/portmodel.cpp b/client/portmodel.cpp index 479e2bc..cd1c9d7 100644 --- a/client/portmodel.cpp +++ b/client/portmodel.cpp @@ -179,7 +179,7 @@ QVariant PortModel::data(const QModelIndex &index, int role) const return QString("Port %1: %2 %3(%4)") .arg(port->id()) - .arg(port->name()) + .arg(port->userAlias()) .arg(rsvdBy) .arg(port->description()); } diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 9fb13dc..45024a1 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -101,12 +101,8 @@ PcapPort::PcapPort(int id, const char *device) { if (strcmp(device, dev->name) == 0) { -#ifdef Q_OS_WIN32 - data_.set_name(QString("if%1").arg(id).toStdString()); -#else if (dev->name) data_.set_name(dev->name); -#endif if (dev->description) data_.set_description(dev->description); From f3f970cb648040cdbca327e3a33154ff8a31087b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 18 May 2016 21:09:11 +0530 Subject: [PATCH 113/121] Open Session - set *.ossn as default open file filter --- client/mainwindow.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index e460cb5..755ffd9 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -145,10 +145,17 @@ void MainWindow::on_actionOpenSession_triggered() static QString dirName; QString fileName; + QStringList fileTypes = SessionFileFormat::supportedFileTypes(); + QString fileType; QString errorStr; bool ret; - fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), dirName); + fileTypes.append("All files (*)"); + if (fileTypes.size()) + fileType = fileTypes.at(0); + + fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), + dirName, fileTypes.join(";;"), &fileType); if (fileName.isEmpty()) goto _exit; From 29f840c91c568879a0ad88928de65ac38bb18638 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 18 May 2016 21:33:26 +0530 Subject: [PATCH 114/121] Use .ostm as default extension for Saved Stream(s) file and use it as default filter for open stream file --- client/portswindow.cpp | 11 ++++++++++- common/abstractfileformat.cpp | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 49c4e82..488d5c3 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -812,6 +812,8 @@ void PortsWindow::on_actionOpen_Streams_triggered() { qDebug("Open Streams Action"); + QStringList fileTypes = AbstractFileFormat::supportedFileTypes(); + QString fileType; QModelIndex current = tvPortList->selectionModel()->currentIndex(); static QString dirName; QString fileName; @@ -824,7 +826,14 @@ void PortsWindow::on_actionOpen_Streams_triggered() Q_ASSERT(plm->isPort(current)); - fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), dirName); + // cannot open Python Scripts + fileTypes.removeAt(fileTypes.indexOf("PythonScript (*.py)")); + fileTypes.append("All files (*)"); + if (fileTypes.size()) + fileType = fileTypes.at(0); + + fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), + dirName, fileTypes.join(";;"), &fileType); if (fileName.isEmpty()) goto _exit; diff --git a/common/abstractfileformat.cpp b/common/abstractfileformat.cpp index 234795a..b418354 100644 --- a/common/abstractfileformat.cpp +++ b/common/abstractfileformat.cpp @@ -48,7 +48,7 @@ QDialog* AbstractFileFormat::saveOptionsDialog() QStringList AbstractFileFormat::supportedFileTypes() { return QStringList() - << "Ostinato (*)" + << "Ostinato (*.ostm)" << "PCAP (*)" << "PDML (*.pdml)" << "PythonScript (*.py)"; From 8f08fc92c99cf9006a81176d69ccac6cf6f4e9be Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 19 May 2016 20:02:32 +0530 Subject: [PATCH 115/121] Update TODO Test cases for Session Save/Open --- test/TODO.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/TODO.md b/test/TODO.md index 6c20142..c6002b5 100644 --- a/test/TODO.md +++ b/test/TODO.md @@ -1,15 +1,46 @@ # TODO - Test Cases ## Session Save/Open + * Verify save session prompts before opening Save Dialog about only reserved ports being saved if there are some reserved ports and not prompted at all if no ports are reserved * Verify each save session triggers the file dialog at the last path used + * Verify saved session file is correct + * All portgroups are saved + * All suitable (wrt reservation) ports are saved + * All port configuration is saved + * For each port - + * All streams are saved with correct contents + * All deviceGroups are saved with correct contents * OSSN Session file format tests (TODO: expand) * Verify no file is saved if user clicks 'Cancel' on the progress dialog while saving session file - * On open session, verify user is prompted if there are existing portgroups and not if there are no port groups + * On open session, verify user is prompted before opening the file dialog if there are existing portgroups and not prompted at all if there are no port groups * Verify each open session triggers the file dialog at the last path used + * Verify open file dialog file filter has `(*.ssn *.*)` + * Verify opening a unsupported format file triggers an error and existing session is not changed + * Verify all existing portgroups are removed before new ones from the file are created and configured + * Verify that only ports in the opened session file are overwritten and other ports are not changed + * Verify that if port in the opened session file was reserved at save time, and the same port is now reserved by someone else, it is not changed and user is informed; if current port reservation is by self, port is overwritten with contents from the session file; all reservations made by open session are with self username, not the username who had reserved the port during save time (in other words, allow session files to be exchanged between users) + * Verify no unnecessary RPCs during open session + * if port has no existing streams, deleteStreams() is not invoked + * if port has no existing deviceGroups, deleteDeviceGroups() is not invoked + * if port config has no change, modifyPort() is not invoked + * if opened port has no streams, addStreams()/modifyStreams() is not invoked + * if opened port has no deviceGroups, addDeviceGroups()/modifyDeviceGroups() is not invoked + * Verify open session is successful + * All streams are restored with correct contents + * All deviceGroups are restored with correct contents + * Port config (TxMode, ExclusiveMode, UserName) is changed, if required * OSSN Session file format tests (TODO: expand) * Verify no change in existing port groups if user clicks 'Cancel' on the options dialog * Verify no change in existing port groups if user clicks 'Cancel' on the progress dialog while opening session file * Verify all old portgroups are removed before new portgroups from the session file are added * Verify config of portGroups loaded from the session file are correct * Verify config of a portGroup is NOT restored to the config saved in session file after a open session - disconnect - connect + +## Streams Save/Open + * Verify save file dialog file filter has all supported types but no `*.*` + * Stream file format tests (TODO: expand) + + * Verify open file dialog file filter has all supported types except `*.py` and `*.*` + * Verify opening a unsupported format file triggers an error and existing streams are not changed + * Stream file format tests (TODO: expand) From 93dbe8e1183b1694ea56d3ff49da7e35ef0ad4c9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 19 May 2016 20:28:04 +0530 Subject: [PATCH 116/121] Save/Open Session - Prompt user before Open/Save Dialog --- client/mainwindow.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 755ffd9..acfa5c3 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -150,15 +150,6 @@ void MainWindow::on_actionOpenSession_triggered() QString errorStr; bool ret; - fileTypes.append("All files (*)"); - if (fileTypes.size()) - fileType = fileTypes.at(0); - - fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), - dirName, fileTypes.join(";;"), &fileType); - if (fileName.isEmpty()) - goto _exit; - if (portsWindow->portGroupCount()) { if (QMessageBox::question(this, tr("Open Session"), @@ -168,6 +159,15 @@ void MainWindow::on_actionOpenSession_triggered() goto _exit; } + fileTypes.append("All files (*)"); + if (fileTypes.size()) + fileType = fileTypes.at(0); + + fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), + dirName, fileTypes.join(";;"), &fileType); + if (fileName.isEmpty()) + goto _exit; + ret = openSession(fileName, errorStr); if (!ret || !errorStr.isEmpty()) { QMessageBox msgBox(this); @@ -198,6 +198,17 @@ void MainWindow::on_actionSaveSession_triggered() QString errorStr; QFileDialog::Options options; + if (portsWindow->reservedPortCount()) { + QString myself = appSettings->value(kUserKey, kUserDefaultValue) + .toString(); + if (QMessageBox::question(this, + tr("Save Session"), + QString("Some ports are reserved!\n\nOnly ports reserved by %1 will be saved. Proceed?").arg(myself), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == QMessageBox::No) + goto _exit; + } + // On Mac OS with Native Dialog, getSaveFileName() ignores fileType. // Although currently there's only one supported file type, we may // have more in the future @@ -213,17 +224,6 @@ void MainWindow::on_actionSaveSession_triggered() if (fileName.isEmpty()) goto _exit; - if (portsWindow->reservedPortCount()) { - QString myself = appSettings->value(kUserKey, kUserDefaultValue) - .toString(); - if (QMessageBox::question(this, - tr("Save Session"), - QString("Some ports are reserved!\n\nOnly ports reserved by %1 will be saved. Proceed?").arg(myself), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) == QMessageBox::No) - goto _exit; - } - if (!saveSession(fileName, fileType, errorStr)) QMessageBox::critical(this, qApp->applicationName(), errorStr); else if (!errorStr.isEmpty()) From 822ee2a4b4e51d7319c1d1b1ffaac73a5935e397 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 19 May 2016 20:50:33 +0530 Subject: [PATCH 117/121] Save/Open Session - refactor supportedFileTypes() to accept operation - Open/Save as input parameter and return file types accordingly --- client/mainwindow.cpp | 7 ++++--- common/sessionfileformat.cpp | 20 +++++++++++++------- common/sessionfileformat.h | 11 ++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index acfa5c3..b7032d2 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -145,7 +145,8 @@ void MainWindow::on_actionOpenSession_triggered() static QString dirName; QString fileName; - QStringList fileTypes = SessionFileFormat::supportedFileTypes(); + QStringList fileTypes = SessionFileFormat::supportedFileTypes( + SessionFileFormat::kOpenFile); QString fileType; QString errorStr; bool ret; @@ -159,7 +160,6 @@ void MainWindow::on_actionOpenSession_triggered() goto _exit; } - fileTypes.append("All files (*)"); if (fileTypes.size()) fileType = fileTypes.at(0); @@ -193,7 +193,8 @@ void MainWindow::on_actionSaveSession_triggered() qDebug("Save Session Action"); static QString fileName; - QStringList fileTypes = SessionFileFormat::supportedFileTypes(); + QStringList fileTypes = SessionFileFormat::supportedFileTypes( + SessionFileFormat::kSaveFile); QString fileType; QString errorStr; QFileDialog::Options options; diff --git a/common/sessionfileformat.cpp b/common/sessionfileformat.cpp index 44ec3da..9e14530 100644 --- a/common/sessionfileformat.cpp +++ b/common/sessionfileformat.cpp @@ -42,10 +42,16 @@ QDialog* SessionFileFormat::saveOptionsDialog() return NULL; } -QStringList SessionFileFormat::supportedFileTypes() +QStringList SessionFileFormat::supportedFileTypes(Operation op) { - return QStringList() - << "Ostinato Session (*.ossn)"; + QStringList fileTypes; + + fileTypes << "Ostinato Session (*.ossn)"; + + if (op == kOpenFile) + fileTypes << "All files (*)"; + + return fileTypes; } void SessionFileFormat::openAsync(const QString fileName, @@ -54,7 +60,7 @@ void SessionFileFormat::openAsync(const QString fileName, fileName_ = fileName; openSession_ = &session; error_ = &error; - op_ = kOpen; + op_ = kOpenFile; stop_ = false; start(); @@ -67,7 +73,7 @@ void SessionFileFormat::saveAsync( saveSession_ = &session; fileName_ = fileName; error_ = &error; - op_ = kSave; + op_ = kSaveFile; stop_ = false; start(); @@ -104,8 +110,8 @@ void SessionFileFormat::cancel() void SessionFileFormat::run() { - if (op_ == kOpen) + if (op_ == kOpenFile) result_ = open(fileName_, *openSession_, *error_); - else if (op_ == kSave) + else if (op_ == kSaveFile) result_ = save(*saveSession_, fileName_, *error_); } diff --git a/common/sessionfileformat.h b/common/sessionfileformat.h index 4f4d68f..31fe16c 100644 --- a/common/sessionfileformat.h +++ b/common/sessionfileformat.h @@ -32,6 +32,8 @@ class SessionFileFormat : public QThread { Q_OBJECT public: + enum Operation { kOpenFile, kSaveFile }; + SessionFileFormat(); virtual ~SessionFileFormat(); @@ -50,7 +52,7 @@ public: bool result(); - static QStringList supportedFileTypes(); + static QStringList supportedFileTypes(Operation op); static SessionFileFormat* fileFormatFromFile(const QString fileName); static SessionFileFormat* fileFormatFromType(const QString fileType); @@ -72,16 +74,11 @@ protected: bool stop_; private: - enum kOp { - kOpen, - kSave - }; - QString fileName_; OstProto::SessionContent *openSession_; const OstProto::SessionContent *saveSession_; QString *error_; - kOp op_; + Operation op_; bool result_; }; From c8a31f3068f7db546753c696a048aa7bf917a8de Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 19 May 2016 21:05:32 +0530 Subject: [PATCH 118/121] Refactor stream file supportedFileTypes() to accept operation - Open/Save and return fileTypes accordingly --- client/portswindow.cpp | 9 ++++----- common/abstractfileformat.cpp | 24 ++++++++++++++++-------- common/abstractfileformat.h | 11 ++++------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 488d5c3..87793a7 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -812,7 +812,8 @@ void PortsWindow::on_actionOpen_Streams_triggered() { qDebug("Open Streams Action"); - QStringList fileTypes = AbstractFileFormat::supportedFileTypes(); + QStringList fileTypes = AbstractFileFormat::supportedFileTypes( + AbstractFileFormat::kOpenFile); QString fileType; QModelIndex current = tvPortList->selectionModel()->currentIndex(); static QString dirName; @@ -826,9 +827,6 @@ void PortsWindow::on_actionOpen_Streams_triggered() Q_ASSERT(plm->isPort(current)); - // cannot open Python Scripts - fileTypes.removeAt(fileTypes.indexOf("PythonScript (*.py)")); - fileTypes.append("All files (*)"); if (fileTypes.size()) fileType = fileTypes.at(0); @@ -887,7 +885,8 @@ void PortsWindow::on_actionSave_Streams_triggered() QModelIndex current = tvPortList->selectionModel()->currentIndex(); static QString fileName; - QStringList fileTypes = AbstractFileFormat::supportedFileTypes(); + QStringList fileTypes = AbstractFileFormat::supportedFileTypes( + AbstractFileFormat::kSaveFile); QString fileType; QString errorStr; QFileDialog::Options options; diff --git a/common/abstractfileformat.cpp b/common/abstractfileformat.cpp index b418354..ebaba93 100644 --- a/common/abstractfileformat.cpp +++ b/common/abstractfileformat.cpp @@ -45,13 +45,21 @@ QDialog* AbstractFileFormat::saveOptionsDialog() return NULL; } -QStringList AbstractFileFormat::supportedFileTypes() +QStringList AbstractFileFormat::supportedFileTypes(Operation op) { - return QStringList() + QStringList fileTypes; + + fileTypes << "Ostinato (*.ostm)" << "PCAP (*)" - << "PDML (*.pdml)" - << "PythonScript (*.py)"; + << "PDML (*.pdml)"; + + if (op == kSaveFile) + fileTypes << "PythonScript (*.py)"; + else if (op == kOpenFile) + fileTypes << "All files (*)"; + + return fileTypes; } void AbstractFileFormat::openStreamsOffline(const QString fileName, @@ -60,7 +68,7 @@ void AbstractFileFormat::openStreamsOffline(const QString fileName, fileName_ = fileName; openStreams_ = &streams; error_ = &error; - op_ = kOpen; + op_ = kOpenFile; stop_ = false; start(); @@ -73,7 +81,7 @@ void AbstractFileFormat::saveStreamsOffline( saveStreams_ = streams; fileName_ = fileName; error_ = &error; - op_ = kSave; + op_ = kSaveFile; stop_ = false; start(); @@ -125,8 +133,8 @@ void AbstractFileFormat::cancel() void AbstractFileFormat::run() { - if (op_ == kOpen) + if (op_ == kOpenFile) result_ = openStreams(fileName_, *openStreams_, *error_); - else if (op_ == kSave) + else if (op_ == kSaveFile) result_ = saveStreams(saveStreams_, fileName_, *error_); } diff --git a/common/abstractfileformat.h b/common/abstractfileformat.h index 1f8447d..0cbb886 100644 --- a/common/abstractfileformat.h +++ b/common/abstractfileformat.h @@ -31,6 +31,8 @@ class AbstractFileFormat : public QThread { Q_OBJECT public: + enum Operation { kOpenFile, kSaveFile }; + AbstractFileFormat(); virtual ~AbstractFileFormat(); @@ -49,7 +51,7 @@ public: bool result(); - static QStringList supportedFileTypes(); + static QStringList supportedFileTypes(Operation op); static AbstractFileFormat* fileFormatFromFile(const QString fileName); static AbstractFileFormat* fileFormatFromType(const QString fileType); @@ -73,16 +75,11 @@ protected: bool stop_; private: - enum kOp - { - kOpen, - kSave - }; QString fileName_; OstProto::StreamConfigList *openStreams_; OstProto::StreamConfigList saveStreams_; QString *error_; - kOp op_; + Operation op_; bool result_; }; From ff066ea65792556b81370be10d62bd9510c50492 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 19 May 2016 21:54:28 +0530 Subject: [PATCH 119/121] Renamed AbstractFileFormat to StreamFileFormat and FileFormat to OstmFielFormat to be consistent with the new convention introduced with SessionFileFormat/OssnFileFormat --- client/port.cpp | 6 ++-- client/portswindow.cpp | 10 +++---- common/{fileformat.cpp => ostmfileformat.cpp} | 16 +++++------ common/{fileformat.h => ostmfileformat.h} | 12 ++++---- common/ostprotogui.pro | 8 +++--- common/pcapfileformat.h | 4 +-- common/pdmlfileformat.cpp | 2 +- common/pdmlfileformat.h | 4 +-- common/pythonfileformat.h | 4 +-- ...actfileformat.cpp => streamfileformat.cpp} | 28 +++++++++---------- ...bstractfileformat.h => streamfileformat.h} | 14 +++++----- 11 files changed, 54 insertions(+), 54 deletions(-) rename common/{fileformat.cpp => ostmfileformat.cpp} (85%) rename common/{fileformat.h => ostmfileformat.h} (83%) rename common/{abstractfileformat.cpp => streamfileformat.cpp} (79%) rename common/{abstractfileformat.h => streamfileformat.h} (86%) diff --git a/client/port.cpp b/client/port.cpp index 7f3b275..1bbe616 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -19,8 +19,8 @@ along with this program. If not, see #include "port.h" -#include "abstractfileformat.h" #include "emulation.h" +#include "streamfileformat.h" #include #include @@ -553,7 +553,7 @@ bool Port::openStreams(QString fileName, bool append, QString &error) QDialog *optDialog; QProgressDialog progress("Opening Streams", "Cancel", 0, 0, mainWindow); OstProto::StreamConfigList streams; - AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromFile(fileName); + StreamFileFormat *fmt = StreamFileFormat::fileFormatFromFile(fileName); if (fmt == NULL) { error = tr("Unknown streams file format"); @@ -642,7 +642,7 @@ bool Port::saveStreams(QString fileName, QString fileType, QString &error) { bool ret = false; QProgressDialog progress("Saving Streams", "Cancel", 0, 0, mainWindow); - AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType(fileType); + StreamFileFormat *fmt = StreamFileFormat::fileFormatFromType(fileType); OstProto::StreamConfigList streams; if (fmt == NULL) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 87793a7..4c85150 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -19,11 +19,11 @@ along with this program. If not, see #include "portswindow.h" -#include "abstractfileformat.h" #include "deviceswidget.h" #include "portconfigdialog.h" #include "settings.h" #include "streamconfigdialog.h" +#include "streamfileformat.h" #include "streamlistdelegate.h" #include "fileformat.pb.h" @@ -812,8 +812,8 @@ void PortsWindow::on_actionOpen_Streams_triggered() { qDebug("Open Streams Action"); - QStringList fileTypes = AbstractFileFormat::supportedFileTypes( - AbstractFileFormat::kOpenFile); + QStringList fileTypes = StreamFileFormat::supportedFileTypes( + StreamFileFormat::kOpenFile); QString fileType; QModelIndex current = tvPortList->selectionModel()->currentIndex(); static QString dirName; @@ -885,8 +885,8 @@ void PortsWindow::on_actionSave_Streams_triggered() QModelIndex current = tvPortList->selectionModel()->currentIndex(); static QString fileName; - QStringList fileTypes = AbstractFileFormat::supportedFileTypes( - AbstractFileFormat::kSaveFile); + QStringList fileTypes = StreamFileFormat::supportedFileTypes( + StreamFileFormat::kSaveFile); QString fileType; QString errorStr; QFileDialog::Options options; diff --git a/common/fileformat.cpp b/common/ostmfileformat.cpp similarity index 85% rename from common/fileformat.cpp rename to common/ostmfileformat.cpp index 36c3eac..7da2685 100644 --- a/common/fileformat.cpp +++ b/common/ostmfileformat.cpp @@ -17,17 +17,17 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#include "fileformat.h" +#include "ostmfileformat.h" -FileFormat fileFormat; +OstmFileFormat fileFormat; -FileFormat::FileFormat() - : AbstractFileFormat(), NativeFileFormat() +OstmFileFormat::OstmFileFormat() + : StreamFileFormat(), NativeFileFormat() { // Do Nothing! } -bool FileFormat::openStreams(const QString fileName, +bool OstmFileFormat::openStreams(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { OstProto::FileMeta meta; @@ -54,7 +54,7 @@ _fail: return false; } -bool FileFormat::saveStreams(const OstProto::StreamConfigList streams, +bool OstmFileFormat::saveStreams(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { OstProto::FileContent content; @@ -79,12 +79,12 @@ _fail: return false; } -bool FileFormat::isMyFileFormat(const QString fileName) +bool OstmFileFormat::isMyFileFormat(const QString fileName) { return isNativeFileFormat(fileName, OstProto::kStreamsFileType); } -bool FileFormat::isMyFileType(const QString fileType) +bool OstmFileFormat::isMyFileType(const QString fileType) { if (fileType.startsWith("Ostinato")) return true; diff --git a/common/fileformat.h b/common/ostmfileformat.h similarity index 83% rename from common/fileformat.h rename to common/ostmfileformat.h index 95eb310..33332a8 100644 --- a/common/fileformat.h +++ b/common/ostmfileformat.h @@ -16,18 +16,18 @@ 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 */ -#ifndef _FILE_FORMAT_H -#define _FILE_FORMAT_H +#ifndef _OSTM_FILE_FORMAT_H +#define _OSTM_FILE_FORMAT_H -#include "abstractfileformat.h" #include "nativefileformat.h" +#include "streamfileformat.h" #include "fileformat.pb.h" -class FileFormat : public AbstractFileFormat, public NativeFileFormat +class OstmFileFormat : public StreamFileFormat, public NativeFileFormat { public: - FileFormat(); + OstmFileFormat(); virtual bool openStreams(const QString fileName, OstProto::StreamConfigList &streams, QString &error); @@ -38,6 +38,6 @@ public: bool isMyFileType(const QString fileType); }; -extern FileFormat fileFormat; +extern OstmFileFormat fileFormat; #endif diff --git a/common/ostprotogui.pro b/common/ostprotogui.pro index 44f1061..7785def 100644 --- a/common/ostprotogui.pro +++ b/common/ostprotogui.pro @@ -35,12 +35,11 @@ PROTOS = \ # TODO: Move fileformat related stuff into a different library - why? HEADERS = \ ostprotolib.h \ - abstractfileformat.h \ - fileformat.h \ ipv4addressdelegate.h \ ipv6addressdelegate.h \ nativefileformat.h \ ossnfileformat.h \ + ostmfileformat.h \ pcapfileformat.h \ pdmlfileformat.h \ pythonfileformat.h \ @@ -48,6 +47,7 @@ HEADERS = \ pdmlprotocols.h \ pdmlreader.h \ sessionfileformat.h \ + streamfileformat.h \ spinboxdelegate.h HEADERS += \ @@ -83,10 +83,9 @@ HEADERS += \ SOURCES += \ ostprotolib.cpp \ - abstractfileformat.cpp \ - fileformat.cpp \ nativefileformat.cpp \ ossnfileformat.cpp \ + ostmfileformat.cpp \ pcapfileformat.cpp \ pdmlfileformat.cpp \ pythonfileformat.cpp \ @@ -94,6 +93,7 @@ SOURCES += \ pdmlprotocols.cpp \ pdmlreader.cpp \ sessionfileformat.cpp \ + streamfileformat.cpp \ spinboxdelegate.cpp SOURCES += \ diff --git a/common/pcapfileformat.h b/common/pcapfileformat.h index 064aaf1..64b03ac 100644 --- a/common/pcapfileformat.h +++ b/common/pcapfileformat.h @@ -19,7 +19,7 @@ along with this program. If not, see #ifndef _PCAP_FILE_FORMAT_H #define _PCAP_FILE_FORMAT_H -#include "abstractfileformat.h" +#include "streamfileformat.h" #include "ui_pcapfileimport.h" #include @@ -39,7 +39,7 @@ private: }; class PdmlReader; -class PcapFileFormat : public AbstractFileFormat +class PcapFileFormat : public StreamFileFormat { friend class PdmlReader; diff --git a/common/pdmlfileformat.cpp b/common/pdmlfileformat.cpp index e567d99..7f0871b 100644 --- a/common/pdmlfileformat.cpp +++ b/common/pdmlfileformat.cpp @@ -80,7 +80,7 @@ bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams, { bool isOk = false; QTemporaryFile pcapFile; - AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType("PCAP"); + StreamFileFormat *fmt = StreamFileFormat::fileFormatFromType("PCAP"); QProcess tshark; Q_ASSERT(fmt); diff --git a/common/pdmlfileformat.h b/common/pdmlfileformat.h index e05026a..6621222 100644 --- a/common/pdmlfileformat.h +++ b/common/pdmlfileformat.h @@ -19,9 +19,9 @@ along with this program. If not, see #ifndef _PDML_FILE_FORMAT_H #define _PDML_FILE_FORMAT_H -#include "abstractfileformat.h" +#include "streamfileformat.h" -class PdmlFileFormat : public AbstractFileFormat +class PdmlFileFormat : public StreamFileFormat { public: PdmlFileFormat(); diff --git a/common/pythonfileformat.h b/common/pythonfileformat.h index 55a6452..f45f1d5 100644 --- a/common/pythonfileformat.h +++ b/common/pythonfileformat.h @@ -20,11 +20,11 @@ along with this program. If not, see #ifndef _PYTHON_FILE_FORMAT_H #define _PYTHON_FILE_FORMAT_H -#include "abstractfileformat.h" +#include "streamfileformat.h" #include -class PythonFileFormat : public AbstractFileFormat +class PythonFileFormat : public StreamFileFormat { public: PythonFileFormat(); diff --git a/common/abstractfileformat.cpp b/common/streamfileformat.cpp similarity index 79% rename from common/abstractfileformat.cpp rename to common/streamfileformat.cpp index ebaba93..a074aab 100644 --- a/common/abstractfileformat.cpp +++ b/common/streamfileformat.cpp @@ -17,35 +17,35 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#include "abstractfileformat.h" +#include "streamfileformat.h" -#include "fileformat.h" +#include "ostmfileformat.h" #include "pcapfileformat.h" #include "pdmlfileformat.h" #include "pythonfileformat.h" #include -AbstractFileFormat::AbstractFileFormat() +StreamFileFormat::StreamFileFormat() { stop_ = false; } -AbstractFileFormat::~AbstractFileFormat() +StreamFileFormat::~StreamFileFormat() { } -QDialog* AbstractFileFormat::openOptionsDialog() +QDialog* StreamFileFormat::openOptionsDialog() { return NULL; } -QDialog* AbstractFileFormat::saveOptionsDialog() +QDialog* StreamFileFormat::saveOptionsDialog() { return NULL; } -QStringList AbstractFileFormat::supportedFileTypes(Operation op) +QStringList StreamFileFormat::supportedFileTypes(Operation op) { QStringList fileTypes; @@ -62,7 +62,7 @@ QStringList AbstractFileFormat::supportedFileTypes(Operation op) return fileTypes; } -void AbstractFileFormat::openStreamsOffline(const QString fileName, +void StreamFileFormat::openStreamsOffline(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { fileName_ = fileName; @@ -74,7 +74,7 @@ void AbstractFileFormat::openStreamsOffline(const QString fileName, start(); } -void AbstractFileFormat::saveStreamsOffline( +void StreamFileFormat::saveStreamsOffline( const OstProto::StreamConfigList streams, const QString fileName, QString &error) { @@ -87,12 +87,12 @@ void AbstractFileFormat::saveStreamsOffline( start(); } -bool AbstractFileFormat::result() +bool StreamFileFormat::result() { return result_; } -AbstractFileFormat* AbstractFileFormat::fileFormatFromFile( +StreamFileFormat* StreamFileFormat::fileFormatFromFile( const QString fileName) { if (fileFormat.isMyFileFormat(fileName)) @@ -107,7 +107,7 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromFile( return NULL; } -AbstractFileFormat* AbstractFileFormat::fileFormatFromType( +StreamFileFormat* StreamFileFormat::fileFormatFromType( const QString fileType) { @@ -126,12 +126,12 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromType( return NULL; } -void AbstractFileFormat::cancel() +void StreamFileFormat::cancel() { stop_ = true; } -void AbstractFileFormat::run() +void StreamFileFormat::run() { if (op_ == kOpenFile) result_ = openStreams(fileName_, *openStreams_, *error_); diff --git a/common/abstractfileformat.h b/common/streamfileformat.h similarity index 86% rename from common/abstractfileformat.h rename to common/streamfileformat.h index 0cbb886..d8662d5 100644 --- a/common/abstractfileformat.h +++ b/common/streamfileformat.h @@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#ifndef _ABSTRACT_FILE_FORMAT_H -#define _ABSTRACT_FILE_FORMAT_H +#ifndef _STREAM_FILE_FORMAT_H +#define _STREAM_FILE_FORMAT_H #include "protocol.pb.h" @@ -27,14 +27,14 @@ along with this program. If not, see class QDialog; -class AbstractFileFormat : public QThread +class StreamFileFormat : public QThread { Q_OBJECT public: enum Operation { kOpenFile, kSaveFile }; - AbstractFileFormat(); - virtual ~AbstractFileFormat(); + StreamFileFormat(); + virtual ~StreamFileFormat(); virtual bool openStreams(const QString fileName, OstProto::StreamConfigList &streams, QString &error) = 0; @@ -53,8 +53,8 @@ public: static QStringList supportedFileTypes(Operation op); - static AbstractFileFormat* fileFormatFromFile(const QString fileName); - static AbstractFileFormat* fileFormatFromType(const QString fileType); + static StreamFileFormat* fileFormatFromFile(const QString fileName); + static StreamFileFormat* fileFormatFromType(const QString fileType); #if 0 bool isMyFileFormat(const QString fileName) = 0; From 6cc7231e7f70ca96b746fd4a048e06b81540b3dd Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 20 May 2016 20:30:57 +0530 Subject: [PATCH 120/121] Refactored StreamFileFormat and subclasses' methods to be consistent with SessionFileFormat --- client/port.cpp | 12 ++++++------ common/ostmfileformat.cpp | 4 ++-- common/ostmfileformat.h | 4 ++-- common/pcapfileformat.cpp | 6 +++--- common/pcapfileformat.h | 4 ++-- common/pdmlfileformat.cpp | 6 +++--- common/pdmlfileformat.h | 4 ++-- common/pythonfileformat.cpp | 4 ++-- common/pythonfileformat.h | 4 ++-- common/streamfileformat.cpp | 8 ++++---- common/streamfileformat.h | 8 ++++---- 11 files changed, 32 insertions(+), 32 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 1bbe616..50f1716 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -583,12 +583,12 @@ bool Port::openStreams(QString fileName, bool append, QString &error) connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); - fmt->openStreamsOffline(fileName, streams, error); - qDebug("after open offline"); + fmt->openAsync(fileName, streams, error); + qDebug("after open async"); while (!fmt->isFinished()) qApp->processEvents(); - qDebug("wait over for offline operation"); + qDebug("wait over for async operation"); if (!fmt->result()) goto _fail; @@ -676,12 +676,12 @@ bool Port::saveStreams(QString fileName, QString fileType, QString &error) qApp->processEvents(); } - fmt->saveStreamsOffline(streams, fileName, error); - qDebug("after save offline"); + fmt->saveAsync(streams, fileName, error); + qDebug("after save async"); while (!fmt->isFinished()) qApp->processEvents(); - qDebug("wait over for offline operation"); + qDebug("wait over for async operation"); ret = fmt->result(); goto _exit; diff --git a/common/ostmfileformat.cpp b/common/ostmfileformat.cpp index 7da2685..3cbd75c 100644 --- a/common/ostmfileformat.cpp +++ b/common/ostmfileformat.cpp @@ -27,7 +27,7 @@ OstmFileFormat::OstmFileFormat() // Do Nothing! } -bool OstmFileFormat::openStreams(const QString fileName, +bool OstmFileFormat::open(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { OstProto::FileMeta meta; @@ -54,7 +54,7 @@ _fail: return false; } -bool OstmFileFormat::saveStreams(const OstProto::StreamConfigList streams, +bool OstmFileFormat::save(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { OstProto::FileContent content; diff --git a/common/ostmfileformat.h b/common/ostmfileformat.h index 33332a8..f44ea74 100644 --- a/common/ostmfileformat.h +++ b/common/ostmfileformat.h @@ -29,9 +29,9 @@ class OstmFileFormat : public StreamFileFormat, public NativeFileFormat public: OstmFileFormat(); - virtual bool openStreams(const QString fileName, + virtual bool open(const QString fileName, OstProto::StreamConfigList &streams, QString &error); - virtual bool saveStreams(const OstProto::StreamConfigList streams, + virtual bool save(const OstProto::StreamConfigList streams, const QString fileName, QString &error); bool isMyFileFormat(const QString fileName); diff --git a/common/pcapfileformat.cpp b/common/pcapfileformat.cpp index e96b684..9f352dd 100644 --- a/common/pcapfileformat.cpp +++ b/common/pcapfileformat.cpp @@ -91,7 +91,7 @@ PcapFileFormat::~PcapFileFormat() delete importDialog_; } -bool PcapFileFormat::openStreams(const QString fileName, +bool PcapFileFormat::open(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { bool isOk = false; @@ -325,7 +325,7 @@ bool PcapFileFormat::openStreams(const QString fileName, goto _diff_fail; } - if (!saveStreams(streams, importedPcapFile.fileName(), error)) + if (!save(streams, importedPcapFile.fileName(), error)) { error.append("Error saving imported streams as PCAP for diff"); goto _diff_fail; @@ -553,7 +553,7 @@ bool PcapFileFormat::readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf) return true; } -bool PcapFileFormat::saveStreams(const OstProto::StreamConfigList streams, +bool PcapFileFormat::save(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { bool isOk = false; diff --git a/common/pcapfileformat.h b/common/pcapfileformat.h index 64b03ac..cb0c461 100644 --- a/common/pcapfileformat.h +++ b/common/pcapfileformat.h @@ -47,9 +47,9 @@ public: PcapFileFormat(); ~PcapFileFormat(); - bool openStreams(const QString fileName, + bool open(const QString fileName, OstProto::StreamConfigList &streams, QString &error); - bool saveStreams(const OstProto::StreamConfigList streams, + bool save(const OstProto::StreamConfigList streams, const QString fileName, QString &error); virtual QDialog* openOptionsDialog(); diff --git a/common/pdmlfileformat.cpp b/common/pdmlfileformat.cpp index 7f0871b..f522a8c 100644 --- a/common/pdmlfileformat.cpp +++ b/common/pdmlfileformat.cpp @@ -35,7 +35,7 @@ PdmlFileFormat::~PdmlFileFormat() { } -bool PdmlFileFormat::openStreams(const QString fileName, +bool PdmlFileFormat::open(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { bool isOk = false; @@ -75,7 +75,7 @@ _exit: return isOk; } -bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams, +bool PdmlFileFormat::save(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { bool isOk = false; @@ -97,7 +97,7 @@ bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams, connect(fmt, SIGNAL(progress(int)), this, SIGNAL(progress(int))); emit status("Writing intermediate PCAP file..."); - isOk = fmt->saveStreams(streams, pcapFile.fileName(), error); + isOk = fmt->save(streams, pcapFile.fileName(), error); qDebug("generating PDML %s", fileName.toAscii().constData()); emit status("Converting PCAP to PDML..."); diff --git a/common/pdmlfileformat.h b/common/pdmlfileformat.h index 6621222..bc1d15b 100644 --- a/common/pdmlfileformat.h +++ b/common/pdmlfileformat.h @@ -27,9 +27,9 @@ public: PdmlFileFormat(); ~PdmlFileFormat(); - virtual bool openStreams(const QString fileName, + virtual bool open(const QString fileName, OstProto::StreamConfigList &streams, QString &error); - virtual bool saveStreams(const OstProto::StreamConfigList streams, + virtual bool save(const OstProto::StreamConfigList streams, const QString fileName, QString &error); bool isMyFileFormat(const QString fileName); diff --git a/common/pythonfileformat.cpp b/common/pythonfileformat.cpp index cec283b..d9e65ba 100644 --- a/common/pythonfileformat.cpp +++ b/common/pythonfileformat.cpp @@ -46,14 +46,14 @@ PythonFileFormat::~PythonFileFormat() // Nothing to do } -bool PythonFileFormat::openStreams(const QString /*fileName*/, +bool PythonFileFormat::open(const QString /*fileName*/, OstProto::StreamConfigList &/*streams*/, QString &/*error*/) { // NOT SUPPORTED! return false; } -bool PythonFileFormat::saveStreams(const OstProto::StreamConfigList streams, +bool PythonFileFormat::save(const OstProto::StreamConfigList streams, const QString fileName, QString &error) { QFile file(fileName); diff --git a/common/pythonfileformat.h b/common/pythonfileformat.h index f45f1d5..ee8e4bf 100644 --- a/common/pythonfileformat.h +++ b/common/pythonfileformat.h @@ -30,9 +30,9 @@ public: PythonFileFormat(); ~PythonFileFormat(); - virtual bool openStreams(const QString fileName, + virtual bool open(const QString fileName, OstProto::StreamConfigList &streams, QString &error); - virtual bool saveStreams(const OstProto::StreamConfigList streams, + virtual bool save(const OstProto::StreamConfigList streams, const QString fileName, QString &error); bool isMyFileFormat(const QString fileName); diff --git a/common/streamfileformat.cpp b/common/streamfileformat.cpp index a074aab..d00dda6 100644 --- a/common/streamfileformat.cpp +++ b/common/streamfileformat.cpp @@ -62,7 +62,7 @@ QStringList StreamFileFormat::supportedFileTypes(Operation op) return fileTypes; } -void StreamFileFormat::openStreamsOffline(const QString fileName, +void StreamFileFormat::openAsync(const QString fileName, OstProto::StreamConfigList &streams, QString &error) { fileName_ = fileName; @@ -74,7 +74,7 @@ void StreamFileFormat::openStreamsOffline(const QString fileName, start(); } -void StreamFileFormat::saveStreamsOffline( +void StreamFileFormat::saveAsync( const OstProto::StreamConfigList streams, const QString fileName, QString &error) { @@ -134,7 +134,7 @@ void StreamFileFormat::cancel() void StreamFileFormat::run() { if (op_ == kOpenFile) - result_ = openStreams(fileName_, *openStreams_, *error_); + result_ = open(fileName_, *openStreams_, *error_); else if (op_ == kSaveFile) - result_ = saveStreams(saveStreams_, fileName_, *error_); + result_ = save(saveStreams_, fileName_, *error_); } diff --git a/common/streamfileformat.h b/common/streamfileformat.h index d8662d5..474ab09 100644 --- a/common/streamfileformat.h +++ b/common/streamfileformat.h @@ -36,17 +36,17 @@ public: StreamFileFormat(); virtual ~StreamFileFormat(); - virtual bool openStreams(const QString fileName, + virtual bool open(const QString fileName, OstProto::StreamConfigList &streams, QString &error) = 0; - virtual bool saveStreams(const OstProto::StreamConfigList streams, + virtual bool save(const OstProto::StreamConfigList streams, const QString fileName, QString &error) = 0; virtual QDialog* openOptionsDialog(); virtual QDialog* saveOptionsDialog(); - void openStreamsOffline(const QString fileName, + void openAsync(const QString fileName, OstProto::StreamConfigList &streams, QString &error); - void saveStreamsOffline(const OstProto::StreamConfigList streams, + void saveAsync(const OstProto::StreamConfigList streams, const QString fileName, QString &error); bool result(); From a0485cca013976e6784b8f7fca53a704469b8783 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 25 May 2016 18:31:56 +0530 Subject: [PATCH 121/121] Device Emulation (contd.): took decision on DeviceList+DeviceNeigh vs DeviceInfo API --- common/protocol.proto | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/protocol.proto b/common/protocol.proto index 96d25bb..439d28f 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -277,8 +277,6 @@ message Notification { /* * Protocol Emulation - * FIXME: merge getDeviceList() and get DeviceNeighbors() into a single - * getDeviceInfo() RPC? */ message DeviceGroupId { required uint32 id = 1;