From 06182a435c4d76f68b9597e2c46ce46e45956116 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 12 Oct 2015 18:11:30 +0530 Subject: [PATCH 1/5] Bugfix: Introduced RateAccuracy setting for Drone to conserve CPU at the cost of accuracy Fixes #151 --- server/abstractport.cpp | 12 ++++++++++++ server/abstractport.h | 11 +++++++++++ server/pcapport.cpp | 37 +++++++++++++++++++++++++++++++++---- server/pcapport.h | 9 ++++++++- server/portmanager.cpp | 21 +++++++++++++++++++++ server/portmanager.h | 1 + server/settings.h | 6 ++++++ 7 files changed, 92 insertions(+), 5 deletions(-) diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 76c6db4..8c7d747 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -43,6 +43,7 @@ AbstractPort::AbstractPort(int id, const char *device) data_.set_is_exclusive_control(false); isSendQueueDirty_ = false; + rateAccuracy_ = kHighAccuracy; linkState_ = OstProto::LinkStateUnknown; minPacketSetSize_ = 1; @@ -144,6 +145,17 @@ void AbstractPort::addNote(QString note) data_.set_notes(notes.toStdString()); } +AbstractPort::Accuracy AbstractPort::rateAccuracy() +{ + return rateAccuracy_; +} + +bool AbstractPort::setRateAccuracy(Accuracy accuracy) +{ + rateAccuracy_ = accuracy; + return true; +} + void AbstractPort::updatePacketList() { switch(data_.transmit_mode()) diff --git a/server/abstractport.h b/server/abstractport.h index 44f0c1d..3e99cb9 100644 --- a/server/abstractport.h +++ b/server/abstractport.h @@ -49,6 +49,13 @@ public: quint64 txBps; }; + enum Accuracy + { + kHighAccuracy, + kMediumAccuracy, + kLowAccuracy, + }; + AbstractPort(int id, const char *device); virtual ~AbstractPort(); @@ -75,6 +82,9 @@ public: bool isDirty() { return isSendQueueDirty_; } void setDirty() { isSendQueueDirty_ = true; } + Accuracy rateAccuracy(); + virtual bool setRateAccuracy(Accuracy accuracy); + virtual void clearPacketList() = 0; virtual void loopNextPacketSet(qint64 size, qint64 repeats, long repeatDelaySec, long repeatDelayNsec) = 0; @@ -106,6 +116,7 @@ protected: OstProto::Port data_; OstProto::LinkState linkState_; ulong minPacketSetSize_; + Accuracy rateAccuracy_; quint64 maxStatsValue_; struct PortStats stats_; diff --git a/server/pcapport.cpp b/server/pcapport.cpp index c46d5ad..12ca276 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -167,6 +167,15 @@ void PcapPort::updateNotes() arg(notes).toStdString()); } +bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy) +{ + if (transmitter_->setRateAccuracy(accuracy)) { + AbstractPort::setRateAccuracy(accuracy); + return true; + } + return false; +} + PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction, AbstractPort::PortStats *stats) { @@ -352,6 +361,26 @@ PcapPort::PortTransmitter::~PortTransmitter() pcap_close(handle_); } +bool PcapPort::PortTransmitter::setRateAccuracy( + AbstractPort::Accuracy accuracy) +{ + switch (accuracy) { + case kHighAccuracy: + udelayFn_ = udelay; + qWarning("%s: rate accuracy set to High - busy wait", __FUNCTION__); + break; + case kLowAccuracy: + udelayFn_ = QThread::usleep; + qWarning("%s: rate accuracy set to Low - usleep", __FUNCTION__); + break; + default: + qWarning("%s: unsupported rate accuracy value %d", __FUNCTION__, + accuracy); + return false; + } + return true; +} + void PcapPort::PortTransmitter::clearPacketList() { Q_ASSERT(!isRunning()); @@ -554,7 +583,7 @@ _restart: long usecs = seq->usecDelay_ + overHead; if (usecs > 0) { - udelay(usecs); + (*udelayFn_)(usecs); overHead = 0; } else @@ -580,7 +609,7 @@ _restart: if (usecs > 0) { - udelay(usecs); + (*udelayFn_)(usecs); overHead = 0; } else @@ -656,7 +685,7 @@ int PcapPort::PortTransmitter::sendQueueTransmit(pcap_t *p, usec += overHead; if (usec > 0) { - udelay(usec); + (*udelayFn_)(usec); overHead = 0; } else @@ -685,7 +714,7 @@ int PcapPort::PortTransmitter::sendQueueTransmit(pcap_t *p, return 0; } -void PcapPort::PortTransmitter::udelay(long usec) +void PcapPort::PortTransmitter::udelay(unsigned long usec) { #if defined(Q_OS_WIN32) LARGE_INTEGER tgtTicks; diff --git a/server/pcapport.h b/server/pcapport.h index b25ab5c..6830d21 100644 --- a/server/pcapport.h +++ b/server/pcapport.h @@ -38,6 +38,8 @@ public: virtual bool hasExclusiveControl() { return false; } virtual bool setExclusiveControl(bool /*exclusive*/) { return false; } + virtual bool setRateAccuracy(AbstractPort::Accuracy accuracy); + virtual void clearPacketList() { transmitter_->clearPacketList(); setPacketListLoopMode(false, 0, 0); @@ -102,6 +104,9 @@ protected: public: PortTransmitter(const char *device); ~PortTransmitter(); + + bool setRateAccuracy(AbstractPort::Accuracy accuracy); + void clearPacketList(); void loopNextPacketSet(qint64 size, qint64 repeats, long repeatDelaySec, long repeatDelayNsec); @@ -172,7 +177,7 @@ protected: long usecDelay_; }; - void udelay(long usec); + static void udelay(unsigned long usec); int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead, int sync); @@ -186,6 +191,8 @@ protected: int returnToQIdx_; quint64 loopDelay_; + void (*udelayFn_)(unsigned long); + bool usingInternalStats_; AbstractPort::PortStats *stats_; bool usingInternalHandle_; diff --git a/server/portmanager.cpp b/server/portmanager.cpp index 897ad4f..5334e9d 100644 --- a/server/portmanager.cpp +++ b/server/portmanager.cpp @@ -36,12 +36,15 @@ PortManager::PortManager() pcap_if_t *deviceList; pcap_if_t *device; char errbuf[PCAP_ERRBUF_SIZE]; + AbstractPort::Accuracy txRateAccuracy; qDebug("Retrieving the device list from the local machine\n"); if (pcap_findalldevs(&deviceList, errbuf) == -1) qDebug("Error in pcap_findalldevs_ex: %s\n", errbuf); + txRateAccuracy = rateAccuracy(); + for(device = deviceList, i = 0; device != NULL; device = device->next, i++) { AbstractPort *port; @@ -81,6 +84,9 @@ PortManager::PortManager() continue; } + if (!port->setRateAccuracy(txRateAccuracy)) + qWarning("failed to set rateAccuracy (%d)", txRateAccuracy); + portList_.append(port); } @@ -106,6 +112,21 @@ PortManager* PortManager::instance() return instance_; } +AbstractPort::Accuracy PortManager::rateAccuracy() +{ + QString rateAccuracy = appSettings->value(kRateAccuracyKey, + kRateAccuracyDefaultValue).toString(); + if (rateAccuracy == "High") + return AbstractPort::kHighAccuracy; + else if (rateAccuracy == "Low") + return AbstractPort::kLowAccuracy; + else + qWarning("Unsupported RateAccuracy setting - %s", + qPrintable(rateAccuracy)); + + return AbstractPort::kHighAccuracy; +} + bool PortManager::filterAcceptsPort(const char *name) { QRegExp pattern; diff --git a/server/portmanager.h b/server/portmanager.h index 801fbf1..fc375fe 100644 --- a/server/portmanager.h +++ b/server/portmanager.h @@ -35,6 +35,7 @@ public: static PortManager* instance(); private: + AbstractPort::Accuracy rateAccuracy(); bool filterAcceptsPort(const char *name); private: diff --git a/server/settings.h b/server/settings.h index 50a3b03..48cea4f 100644 --- a/server/settings.h +++ b/server/settings.h @@ -25,6 +25,12 @@ along with this program. If not, see extern QSettings *appSettings; +// +// General Section Keys +// +const QString kRateAccuracyKey("RateAccuracy"); +const QString kRateAccuracyDefaultValue("High"); + // // PortList Section Keys // From 2840d5b7f24f4725cd5fbb94f27bfcda3460b52b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 4 Nov 2015 19:01:29 +0530 Subject: [PATCH 2/5] Edited README.md to add link to COPYING --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afb3748..998e57c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Ostinato is an open-source, cross-platform network packet crafter/traffic genera Ostinato aims to be "Wireshark in Reverse" and become complementary to Wireshark. -License: GPLv3+ (see COPYING) +License: GPLv3+ (see [COPYING](https://raw.githubusercontent.com/pstavirs/ostinato/master/COPYING)) For more information visit http://ostinato.org. From 01e852449149b641a1d83f674bc5b6ddf37444a1 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 6 Nov 2015 18:53:27 +0530 Subject: [PATCH 3/5] Regression Fix: Fix compilation issue on Windows introduced by the RateAccuracy feature --- server/pcapport.cpp | 4 ++-- server/pcapport.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server/pcapport.cpp b/server/pcapport.cpp index 12ca276..d690553 100644 --- a/server/pcapport.cpp +++ b/server/pcapport.cpp @@ -328,7 +328,7 @@ PcapPort::PortTransmitter::PortTransmitter(const char *device) #ifdef Q_OS_WIN32 LARGE_INTEGER freq; if (QueryPerformanceFrequency(&freq)) - gTicksFreq = ticksFreq_ = freq.QuadPart; + gTicksFreq = freq.QuadPart; else Q_ASSERT_X(false, "PortTransmitter::PortTransmitter", "This Win32 platform does not support performance counter"); @@ -721,7 +721,7 @@ void PcapPort::PortTransmitter::udelay(unsigned long usec) LARGE_INTEGER curTicks; QueryPerformanceCounter(&curTicks); - tgtTicks.QuadPart = curTicks.QuadPart + (usec*ticksFreq_)/1000000; + tgtTicks.QuadPart = curTicks.QuadPart + (usec*gTicksFreq)/1000000; while (curTicks.QuadPart < tgtTicks.QuadPart) QueryPerformanceCounter(&curTicks); diff --git a/server/pcapport.h b/server/pcapport.h index 6830d21..7776a8e 100644 --- a/server/pcapport.h +++ b/server/pcapport.h @@ -181,7 +181,6 @@ protected: int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead, int sync); - quint64 ticksFreq_; QList packetSequenceList_; PacketSequence *currentPacketSequence_; int repeatSequenceStart_; From efdfa7f95d4192fae3f1f3ae1110e3623c0f5522 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 6 Nov 2015 18:57:07 +0530 Subject: [PATCH 4/5] Feature: Ostinato client now accepts hostnames for drones and does DNS resolution for the same; IPv6 addresses are also accepted. Introduced Drone setting to listen to specific or 'any' IPv4 (or IPv6) address Fixes #152 --- client/portgroup.cpp | 6 +++--- client/portgroup.h | 10 +++++----- client/portmodel.cpp | 4 ++-- client/portswindow.cpp | 22 ++++++++++++++++++---- rpc/pbrpcchannel.cpp | 10 +++++----- rpc/pbrpcchannel.h | 12 ++++++++---- rpc/rpcserver.cpp | 9 +++++---- rpc/rpcserver.h | 2 +- server/drone.cpp | 15 +++++++++++++-- server/settings.h | 5 +++++ 10 files changed, 65 insertions(+), 30 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 46d93a2..1742fe3 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -37,7 +37,7 @@ extern char *version; quint32 PortGroup::mPortGroupAllocId = 0; -PortGroup::PortGroup(QHostAddress ip, quint16 port) +PortGroup::PortGroup(QString serverName, quint16 port) { // Allocate an id for self mPortGroupId = PortGroup::mPortGroupAllocId++; @@ -57,7 +57,7 @@ PortGroup::PortGroup(QHostAddress ip, quint16 port) connect(reconnectTimer, SIGNAL(timeout()), this, SLOT(on_reconnectTimer_timeout())); - rpcChannel = new PbRpcChannel(ip, port, + rpcChannel = new PbRpcChannel(serverName, port, OstProto::Notification::default_instance()); serviceStub = new OstProto::OstService::Stub(rpcChannel); @@ -272,7 +272,7 @@ void PortGroup::when_portListChanged(quint32 /*portGroupId*/) "For more information see " "http://code.google.com/p/ostinato/wiki/FAQ#" "Q._Port_group_has_no_interfaces") - .arg(serverAddress().toString()) + .arg(serverName()) .arg(int(serverPort()))); } } diff --git a/client/portgroup.h b/client/portgroup.h index 820be79..7accbcf 100644 --- a/client/portgroup.h +++ b/client/portgroup.h @@ -66,7 +66,7 @@ public: // FIXME(HIGH): member access QList mPorts; public: - PortGroup(QHostAddress ip = QHostAddress::LocalHost, + PortGroup(QString serverName = "127.0.0.1", quint16 port = DEFAULT_SERVER_PORT); ~PortGroup(); @@ -75,10 +75,10 @@ public: compat = kUnknown; rpcChannel->establish(); } - void connectToHost(QHostAddress ip, quint16 port) { + void connectToHost(QString serverName, quint16 port) { reconnect = true; compat = kUnknown; - rpcChannel->establish(ip, port); + rpcChannel->establish(serverName, port); } void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); } @@ -88,8 +88,8 @@ public: const QString& userAlias() const { return mUserAlias; } void setUserAlias(QString alias) { mUserAlias = alias; }; - const QHostAddress& serverAddress() const - { return rpcChannel->serverAddress(); } + const QString serverName() const + { return rpcChannel->serverName(); } quint16 serverPort() const { return rpcChannel->serverPort(); } QAbstractSocket::SocketState state() const { diff --git a/client/portmodel.cpp b/client/portmodel.cpp index 01da997..479e2bc 100644 --- a/client/portmodel.cpp +++ b/client/portmodel.cpp @@ -121,10 +121,10 @@ QVariant PortModel::data(const QModelIndex &index, int role) const if ((role == Qt::DisplayRole)) { DBG0("Exit PortModel data 1\n"); - return QString("Port Group %1: %2 [%3:%4] (%5)"). + return QString("Port Group %1: %2 [%3]:%4 (%5)"). arg(pgl->mPortGroups.at(index.row())->id()). arg(pgl->mPortGroups.at(index.row())->userAlias()). - arg(pgl->mPortGroups.at(index.row())->serverAddress().toString()). + arg(pgl->mPortGroups.at(index.row())->serverName()). arg(pgl->mPortGroups.at(index.row())->serverPort()). arg(pgl->mPortGroups.value(index.row())->numPorts()); } diff --git a/client/portswindow.cpp b/client/portswindow.cpp index b35b10e..21668f2 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -503,15 +503,29 @@ void PortsWindow::on_actionNew_Port_Group_triggered() { bool ok; QString text = QInputDialog::getText(this, - "Add Port Group", "Port Group Address (IP[:Port])", + "Add Port Group", "Port Group Address (HostName[:Port])", QLineEdit::Normal, lastNewPortGroup, &ok); if (ok) { QStringList addr = text.split(":"); - if (addr.size() == 1) // Port unspecified - addr.append(QString().setNum(DEFAULT_SERVER_PORT)); - PortGroup *pg = new PortGroup(QHostAddress(addr[0]),addr[1].toUShort()); + quint16 port = DEFAULT_SERVER_PORT; + + if (addr.size() > 2) { // IPv6 Address + // IPv6 addresses with port number SHOULD be specified as + // [2001:db8::1]:80 (RFC5952 Sec6) to avoid ambiguity due to ':' + addr = text.split("]:"); + if (addr.size() > 1) + port = addr[1].toUShort(); + } + else if (addr.size() == 2) // Hostname/IPv4 + Port specified + port = addr[1].toUShort(); + + // Play nice and remove square brackets irrespective of addr type + addr[0].remove(QChar('[')); + addr[0].remove(QChar(']')); + + PortGroup *pg = new PortGroup(addr[0], port); plm->addPortGroup(*pg); lastNewPortGroup = text; } diff --git a/rpc/pbrpcchannel.cpp b/rpc/pbrpcchannel.cpp index 5be7d72..3c996e8 100644 --- a/rpc/pbrpcchannel.cpp +++ b/rpc/pbrpcchannel.cpp @@ -25,7 +25,7 @@ along with this program. If not, see static uchar msgBuf[4096]; -PbRpcChannel::PbRpcChannel(QHostAddress ip, quint16 port, +PbRpcChannel::PbRpcChannel(QString serverName, quint16 port, const ::google::protobuf::Message ¬ifProto) : notifPrototype(notifProto) { @@ -36,7 +36,7 @@ PbRpcChannel::PbRpcChannel(QHostAddress ip, quint16 port, done = NULL; response = NULL; - mServerAddress = ip; + mServerHost = serverName; mServerPort = port; mpSocket = new QTcpSocket(this); @@ -75,12 +75,12 @@ void PbRpcChannel::establish() { qDebug("In %s", __FUNCTION__); - mpSocket->connectToHost(mServerAddress, mServerPort); + mpSocket->connectToHost(mServerHost, mServerPort); } -void PbRpcChannel::establish(QHostAddress ip, quint16 port) +void PbRpcChannel::establish(QString serverName, quint16 port) { - mServerAddress = ip; + mServerHost = serverName; mServerPort = port; establish(); } diff --git a/rpc/pbrpcchannel.h b/rpc/pbrpcchannel.h index d42df4e..5c598f4 100644 --- a/rpc/pbrpcchannel.h +++ b/rpc/pbrpcchannel.h @@ -20,6 +20,7 @@ along with this program. If not, see #ifndef _PB_RPC_CHANNEL_H #define _PB_RPC_CHANNEL_H +#include #include #include @@ -64,7 +65,7 @@ class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel const ::google::protobuf::Message ¬ifPrototype; ::google::protobuf::Message *notif; - QHostAddress mServerAddress; + QString mServerHost; quint16 mServerPort; QTcpSocket *mpSocket; @@ -72,15 +73,18 @@ class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel ::google::protobuf::io::CopyingOutputStreamAdaptor *outStream; public: - PbRpcChannel(QHostAddress ip, quint16 port, + PbRpcChannel(QString serverName, quint16 port, const ::google::protobuf::Message ¬ifProto); ~PbRpcChannel(); void establish(); - void establish(QHostAddress ip, quint16 port); + void establish(QString serverName, quint16 port); void tearDown(); - const QHostAddress& serverAddress() const { return mServerAddress; } + const QString serverName() const + { + return mpSocket->peerName(); + } quint16 serverPort() const { return mServerPort; } QAbstractSocket::SocketState state() const diff --git a/rpc/rpcserver.cpp b/rpc/rpcserver.cpp index 0457055..5121a67 100644 --- a/rpc/rpcserver.cpp +++ b/rpc/rpcserver.cpp @@ -42,14 +42,15 @@ RpcServer::~RpcServer() { } -bool RpcServer::registerService(::google::protobuf::Service *service, - quint16 tcpPortNum) +bool RpcServer::registerService(::google::protobuf::Service *service, + QHostAddress address, quint16 tcpPortNum) { this->service = service; - if (!listen(QHostAddress::Any, tcpPortNum)) + if (!listen(address, tcpPortNum)) { - qDebug("Unable to start the server: %s", + qDebug("Unable to start the server on <%s>: %s", + qPrintable(address.toString()), errorString().toAscii().constData()); return false; } diff --git a/rpc/rpcserver.h b/rpc/rpcserver.h index 9154674..2550cb3 100644 --- a/rpc/rpcserver.h +++ b/rpc/rpcserver.h @@ -41,7 +41,7 @@ public: virtual ~RpcServer(); bool registerService(::google::protobuf::Service *service, - quint16 tcpPortNum); + QHostAddress address, quint16 tcpPortNum); signals: void notifyClients(int notifType, SharedProtobufMessage notifData); diff --git a/server/drone.cpp b/server/drone.cpp index a6e3b12..8f8b632 100644 --- a/server/drone.cpp +++ b/server/drone.cpp @@ -19,8 +19,9 @@ along with this program. If not, see #include "drone.h" -#include "rpcserver.h" #include "myservice.h" +#include "rpcserver.h" +#include "settings.h" #include @@ -43,11 +44,21 @@ Drone::~Drone() bool Drone::init() { + QString addr = appSettings->value(kRpcServerAddress).toString(); + QHostAddress address = addr.isEmpty() ? + QHostAddress::Any : QHostAddress(addr); + Q_ASSERT(rpcServer); qRegisterMetaType("SharedProtobufMessage"); - if (!rpcServer->registerService(service, myport ? myport : 7878)) + if (address.isNull()) { + qWarning("Invalid RpcServer Address <%s> specified. Using 'Any'", + qPrintable(addr)); + address = QHostAddress::Any; + } + + if (!rpcServer->registerService(service, address, myport ? myport : 7878)) { //qCritical(qPrintable(rpcServer->errorString())); return false; diff --git a/server/settings.h b/server/settings.h index 48cea4f..81ea20b 100644 --- a/server/settings.h +++ b/server/settings.h @@ -31,6 +31,11 @@ extern QSettings *appSettings; const QString kRateAccuracyKey("RateAccuracy"); const QString kRateAccuracyDefaultValue("High"); +// +// RpcServer Section Keys +// +const QString kRpcServerAddress("RpcServer/Address"); + // // PortList Section Keys // From f8d5cbd2523a31b94f48c2dcfcfbeffdbd773acb Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 22 Nov 2015 20:03:24 +0530 Subject: [PATCH 5/5] Bugfix: Fixed frameVariableCount() for the stream when it doesn't contain the payload protocol and is configured to generate increment/decrement/random length packets Fixes #168 --- common/streambase.cpp | 23 ++++- common/streambase.h | 1 + test/pktlentest.py | 214 ++++++++++++++++++++++++++++++++++++++++++ test/pytest.ini | 3 + 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 test/pktlentest.py create mode 100644 test/pytest.ini diff --git a/common/streambase.cpp b/common/streambase.cpp index 0333eb9..7977c8a 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -444,6 +444,27 @@ _exit: return true; } +int StreamBase::frameSizeVariableCount() const +{ + int count = 1; + + switch(lenMode()) + { + case OstProto::StreamCore::e_fl_fixed: + break; + case OstProto::StreamCore::e_fl_inc: + case OstProto::StreamCore::e_fl_dec: + case OstProto::StreamCore::e_fl_random: + count = frameLenMax() - frameLenMin() + 1; + break; + default: + qWarning("%s: Unhandled len mode %d", __FUNCTION__, lenMode()); + break; + } + + return count; +} + int StreamBase::frameVariableCount() const { ProtocolListIterator *iter; @@ -466,7 +487,7 @@ int StreamBase::frameVariableCount() const } delete iter; - return frameCount; + return AbstractProtocol::lcm(frameCount, frameSizeVariableCount()); } // frameProtocolLength() returns the sum of all the individual protocol sizes diff --git a/common/streambase.h b/common/streambase.h index 9ef3ba1..11ac2c2 100644 --- a/common/streambase.h +++ b/common/streambase.h @@ -138,6 +138,7 @@ public: bool isFrameVariable() const; bool isFrameSizeVariable() const; + int frameSizeVariableCount() const; int frameVariableCount() const; int frameProtocolLength(int frameIndex) const; int frameCount() const; diff --git a/test/pktlentest.py b/test/pktlentest.py new file mode 100644 index 0000000..8b4f0ad --- /dev/null +++ b/test/pktlentest.py @@ -0,0 +1,214 @@ +#! /usr/bin/env python + +# standard modules +import logging +import os +import pytest +import subprocess +import sys +import time + +from harness import extract_column + +sys.path.insert(1, '../binding') +from core import ost_pb, DroneProxy +from rpc import RpcError +#from protocols.mac_pb2 import mac +#from protocols.payload_pb2 import payload, Payload + +# initialize defaults +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' + +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 + +# setup protocol number dictionary +proto_number = {} +proto_number['mac'] = ost_pb.Protocol.kMacFieldNumber +proto_number['eth2'] = ost_pb.Protocol.kEth2FieldNumber +proto_number['ip4'] = ost_pb.Protocol.kIp4FieldNumber +proto_number['udp'] = ost_pb.Protocol.kUdpFieldNumber +proto_number['payload'] = ost_pb.Protocol.kPayloadFieldNumber + +# setup logging +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +print('') +print('This test uses the following topology -') +print('') +print(' +-------+ ') +print(' | |Tx--->----+') +print(' | Drone | |') +print(' | |Rx---<----+') +print(' +-------+ ') +print('') +print('A loopback port is used as both the Tx and Rx ports') +print('') + +@pytest.fixture(scope='module') +def drone(request): + """Baseline Configuration for all testcases in this module""" + + dut = DroneProxy(host_name) + + log.info('connecting to drone(%s:%d)' % (dut.hostName(), dut.portNumber())) + dut.connect() + + def fin(): + dut.disconnect() + + request.addfinalizer(fin) + + return dut + +@pytest.fixture(scope='module') +def ports(request, drone): + # 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) + + # iterate port list to find a loopback port to use as the tx/rx port id + 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 loopback port as default tx/rx port + if ('lo' in port.name or 'loopback' in port.description.lower()): + tx_number = port.port_id.id + rx_number = port.port_id.id + + if tx_number < 0 or rx_number < 0: + log.warning('loopback port not found') + sys.exit(1) + + print('Using port %d as tx/rx port(s)' % tx_number) + + ports.tx = ost_pb.PortIdList() + ports.tx.port_id.add().id = tx_number; + + ports.rx = ost_pb.PortIdList() + ports.rx.port_id.add().id = rx_number; + + # delete existing streams, if any, on tx port + sid_list = drone.getStreamIdList(ports.tx.port_id[0]) + drone.deleteStream(sid_list) + + return ports + +protolist=['mac eth2 ip4 udp payload', 'mac eth2 ip4 udp'] +@pytest.fixture(scope='module', params=protolist) +def stream(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 as mac:eth2:ip:udp:payload + s.ClearField("protocol") + protos = request.param.split() + for p in protos: + s.protocol.add().protocol_id.id = proto_number[p] + + 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 + +@pytest.mark.parametrize("mode", [ + ost_pb.StreamCore.e_fl_inc, + ost_pb.StreamCore.e_fl_dec, + ost_pb.StreamCore.e_fl_random +]) +def test_packet_length(drone, ports, stream, mode): + """ Test random length packets """ + + min_pkt_len = 100 + max_pkt_len = 1000 + 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 + + 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', + '-R', 'udp']) + cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap', + '-R', 'udp', '-o', fmt]) + print(cap_pkts) + result = extract_column(cap_pkts, fmt_col) + diffSum = 0 + for i in range(len(result)): + l = int(result[i]) + 4 # add FCS to length + assert (l >= min_pkt_len) and (l <= max_pkt_len) + + # check current packet length to last + if (i > 0): + ll = int(result[i-1]) + 4 + if mode == ost_pb.StreamCore.e_fl_inc: + assert l == (ll+1) + elif mode == ost_pb.StreamCore.e_fl_dec: + assert l == (ll-1) + elif mode == ost_pb.StreamCore.e_fl_random: + diffSum += (l-ll) + + # TODO: find a better way to check for randomness + if mode == ost_pb.StreamCore.e_fl_random: + assert (diffSum % (len(result) - 1)) != 0 + + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(ports.tx) + diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..015c471 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +; change file glob pattern after converting old test files to pytest +python_files=pktlentest.py