Merge branch 'sign'

This commit is contained in:
Srivats P 2017-12-03 19:38:50 +05:30
commit 5191b72f2b
64 changed files with 3969 additions and 698 deletions

View File

@ -98,3 +98,114 @@ class DroneProxy(object):
os.fsync(f.fileno())
f.close()
def getStreamStatsDict(self, stream_guid_list):
"""
Convenience method for fetching stream stats. Returns an object
containing port/sguid dictionaries for easier access e.g.
stream_stats = drone.getStreamStatsDict(guid_list)
stream_stats.sguid[101].port[1].tx_pkts
stream_stats.port[1].sguid[101].rx_bytes
In addition, you can also retrieve totals across ports, e.g.
stream_stats.port[1].total.rx_pkts
stream_stats.port[1].total.rx_bytes
stream_stats.port[1].total.tx_pkts
stream_stats.port[1].total.tx_bytes
and totals across sguids -
stream_stats.sguid[101].total.tx_pkts
stream_stats.sguid[101].total.rx_pkts
stream_stats.sguid[101].total.pkt_loss
This method is a wrapper around the getStreamStats() RPC
"""
class StreamStatsPortTotal:
def __init__(self):
self.tx_pkts = 0
self.rx_pkts = 0
self.tx_bytes = 0
self.rx_bytes = 0
def __repr__(self):
s = ' total: { \n'
s += ' rx_pkts: ' + str(self.rx_pkts) + ' \n'
s += ' rx_bytes: ' + str(self.rx_bytes) + ' \n'
s += ' tx_pkts: ' + str(self.tx_pkts) + ' \n'
s += ' tx_bytes: ' + str(self.tx_bytes) + ' \n'
s += ' }\n'
return s
class StreamStatsDictPort:
def __init__(self):
self.sguid = dict()
self.total = StreamStatsPortTotal()
def __repr__(self):
s = ' sguid: { \n'
for k, v in self.sguid.items():
s += ' ' + str(k) + ': {\n ' \
+ str(v).replace('\n', '\n ') + '}\n'
s += ' }\n'
s += str(self.total)
return s
class StreamStatsGuidTotal:
def __init__(self):
self.tx_pkts = 0
self.rx_pkts = 0
self.pkt_loss = 0
def __repr__(self):
s = ' total: { \n'
s += ' tx_pkts: ' + str(self.tx_pkts) + ' \n'
s += ' rx_pkts: ' + str(self.rx_pkts) + ' \n'
s += ' pkt_loss: ' + str(self.pkt_loss) + ' \n'
s += ' }\n'
return s
class StreamStatsDictGuid:
def __init__(self):
self.port = dict()
self.total = StreamStatsGuidTotal()
def __repr__(self):
s = ' port: { \n'
for k, v in self.port.items():
s += ' ' + str(k) + ': {\n ' \
+ str(v).replace('\n', '\n ') + '}\n'
s += ' }\n'
s += str(self.total)
return s
class StreamStatsDict:
def __init__(self):
self.port = dict()
self.sguid = dict()
def __repr__(self):
s = 'port: {\n'
for k, v in self.port.items():
s += str(k) + ': {\n' + str(v) + '}\n'
s += '}\n'
s += 'sguid: {\n'
for k, v in self.sguid.items():
s += str(k) + ': {\n' + str(v) + '}\n'
s += '}\n'
return s
ssl = self.getStreamStats(stream_guid_list)
ssd = StreamStatsDict()
for ss in ssl.stream_stats:
if ss.port_id.id not in ssd.port:
ssd.port[ss.port_id.id] = StreamStatsDictPort()
assert ss.stream_guid.id not in ssd.port[ss.port_id.id].sguid
ssd.port[ss.port_id.id].sguid[ss.stream_guid.id] = ss
ssd.port[ss.port_id.id].total.tx_pkts += ss.tx_pkts
ssd.port[ss.port_id.id].total.rx_pkts += ss.rx_pkts
ssd.port[ss.port_id.id].total.tx_bytes += ss.tx_bytes
ssd.port[ss.port_id.id].total.rx_bytes += ss.rx_bytes
if ss.stream_guid.id not in ssd.sguid:
ssd.sguid[ss.stream_guid.id] = StreamStatsDictGuid()
assert ss.port_id.id not in ssd.sguid[ss.stream_guid.id].port
ssd.sguid[ss.stream_guid.id].port[ss.port_id.id] = ss
ssd.sguid[ss.stream_guid.id].total.tx_pkts += ss.tx_pkts
ssd.sguid[ss.stream_guid.id].total.rx_pkts += ss.rx_pkts
ssd.sguid[ss.stream_guid.id].total.pkt_loss += \
ss.tx_pkts - ss.rx_pkts
return ssd

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

View File

@ -96,7 +96,7 @@ MainWindow::MainWindow(QWidget *parent)
portsDock->setObjectName("portsDock");
portsDock->setFeatures(
portsDock->features() & ~QDockWidget::DockWidgetClosable);
statsDock = new QDockWidget(tr("Statistics"), this);
statsDock = new QDockWidget(tr("Port Statistics"), this);
statsDock->setObjectName("statsDock");
statsDock->setFeatures(
statsDock->features() & ~QDockWidget::DockWidgetClosable);
@ -166,6 +166,13 @@ MainWindow::~MainWindow()
delete pgl;
// We don't want to save state for Stream Stats Docks - so delete them
QList<QDockWidget*> streamStatsDocks
= findChildren<QDockWidget*>("streamStatsDock");
foreach(QDockWidget *dock, streamStatsDocks)
delete dock;
Q_ASSERT(findChildren<QDockWidget*>("streamStatsDock").size() == 0);
QByteArray layout = saveState(0);
appSettings->setValue(kApplicationWindowLayout, layout);
appSettings->setValue(kApplicationWindowGeometryKey, geometry());
@ -309,6 +316,16 @@ void MainWindow::on_actionViewRestoreDefaults_triggered()
setGeometry(defaultGeometry_);
restoreState(defaultLayout_, 0);
// Add streamStats as tabs
QList<QDockWidget*> streamStatsDocks
= findChildren<QDockWidget*>("streamStatsDock");
foreach(QDockWidget *dock, streamStatsDocks) {
dock->setFloating(false);
tabifyDockWidget(statsDock, dock);
}
statsDock->show();
statsDock->raise();
actionViewShowMyReservedPortsOnly->setChecked(false);
}

View File

@ -58,6 +58,9 @@ HEADERS += \
streamconfigdialog.h \
streamlistdelegate.h \
streammodel.h \
streamstatsfiltermodel.h \
streamstatsmodel.h \
streamstatswindow.h \
variablefieldswidget.h
FORMS += \
@ -71,6 +74,7 @@ FORMS += \
portswindow.ui \
preferences.ui \
streamconfigdialog.ui \
streamstatswindow.ui \
variablefieldswidget.ui
SOURCES += \
@ -100,6 +104,8 @@ SOURCES += \
streamconfigdialog.cpp \
streamlistdelegate.cpp \
streammodel.cpp \
streamstatsmodel.cpp \
streamstatswindow.cpp \
variablefieldswidget.cpp

View File

@ -42,5 +42,6 @@
<file>icons/stream_delete.png</file>
<file>icons/stream_duplicate.png</file>
<file>icons/stream_edit.png</file>
<file>icons/stream_stats.png</file>
</qresource>
</RCC>

View File

@ -100,6 +100,8 @@ public:
{ return d.is_exclusive_control(); }
OstProto::TransmitMode transmitMode() const
{ return d.transmit_mode(); }
bool trackStreamStats() const
{ return d.is_tracking_stream_stats(); }
double averagePacketRate() const
{ return avgPacketsPerSec_; }
double averageBitRate() const

View File

@ -20,7 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portconfigdialog.h"
#include "settings.h"
PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent)
PortConfigDialog::PortConfigDialog(
OstProto::Port &portConfig,
const OstProto::PortState &portState,
QWidget *parent)
: QDialog(parent), portConfig_(portConfig)
{
QString currentUser(portConfig_.user_name().c_str());
@ -64,6 +67,13 @@ PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent)
qDebug("reservedBy_ = %d", reservedBy_);
exclusiveControlButton->setChecked(portConfig_.is_exclusive_control());
streamStatsButton->setChecked(portConfig_.is_tracking_stream_stats());
// Disable UI elements based on portState
if (portState.is_transmit_on()) {
transmitModeBox->setDisabled(true);
streamStatsButton->setDisabled(true);
}
}
void PortConfigDialog::accept()
@ -95,6 +105,7 @@ void PortConfigDialog::accept()
}
pc.set_is_exclusive_control(exclusiveControlButton->isChecked());
pc.set_is_tracking_stream_stats(streamStatsButton->isChecked());
// Update fields that have changed, clear the rest
if (pc.transmit_mode() != portConfig_.transmit_mode())
@ -112,5 +123,10 @@ void PortConfigDialog::accept()
else
portConfig_.clear_is_exclusive_control();
if (pc.is_tracking_stream_stats() != portConfig_.is_tracking_stream_stats())
portConfig_.set_is_tracking_stream_stats(pc.is_tracking_stream_stats());
else
portConfig_.clear_is_tracking_stream_stats();
QDialog::accept();
}

View File

@ -27,7 +27,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
class PortConfigDialog : public QDialog, public Ui::PortConfigDialog
{
public:
PortConfigDialog(OstProto::Port &portConfig, QWidget *parent);
PortConfigDialog(OstProto::Port &portConfig,
const OstProto::PortState& portState,
QWidget *parent);
private:
virtual void accept();

View File

@ -6,7 +6,7 @@
<x>0</x>
<y>0</y>
<width>244</width>
<height>233</height>
<height>257</height>
</rect>
</property>
<property name="windowTitle" >
@ -69,6 +69,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="streamStatsButton" >
<property name="text" >
<string>Stream Statistics</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >

View File

@ -1593,13 +1593,14 @@ void PortGroup::clearPortStats(QList<uint> *portList)
}
serviceStub->clearStats(controller, portIdList, ack,
NewCallback(this, &PortGroup::processClearStatsAck, controller));
NewCallback(this, &PortGroup::processClearPortStatsAck,
controller));
}
_exit:
return;
}
void PortGroup::processClearStatsAck(PbRpcController *controller)
void PortGroup::processClearPortStatsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
@ -1609,3 +1610,78 @@ void PortGroup::processClearStatsAck(PbRpcController *controller)
delete controller;
}
bool PortGroup::clearStreamStats(QList<uint> *portList)
{
qDebug("In %s", __FUNCTION__);
if (state() != QAbstractSocket::ConnectedState)
return false;
OstProto::StreamGuidList *guidList = new OstProto::StreamGuidList;
OstProto::Ack *ack = new OstProto::Ack;
PbRpcController *controller = new PbRpcController(guidList, ack);
if (portList == NULL)
guidList->mutable_port_id_list()->CopyFrom(*portIdList_);
else
for (int i = 0; i < portList->size(); i++)
guidList->mutable_port_id_list()->add_port_id()
->set_id(portList->at(i));
serviceStub->clearStreamStats(controller, guidList, ack,
NewCallback(this, &PortGroup::processClearStreamStatsAck,
controller));
return true;
}
void PortGroup::processClearStreamStatsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
}
bool PortGroup::getStreamStats(QList<uint> *portList)
{
qDebug("In %s", __FUNCTION__);
if (state() != QAbstractSocket::ConnectedState)
return false;
OstProto::StreamGuidList *guidList = new OstProto::StreamGuidList;
OstProto::StreamStatsList *statsList = new OstProto::StreamStatsList;
PbRpcController *controller = new PbRpcController(guidList, statsList);
if (portList == NULL)
guidList->mutable_port_id_list()->CopyFrom(*portIdList_);
else
for (int i = 0; i < portList->size(); i++)
guidList->mutable_port_id_list()->add_port_id()
->set_id(portList->at(i));
serviceStub->getStreamStats(controller, guidList, statsList,
NewCallback(this, &PortGroup::processStreamStatsList, controller));
return true;
}
void PortGroup::processStreamStatsList(PbRpcController *controller)
{
using OstProto::StreamStatsList;
qDebug("In %s", __FUNCTION__);
StreamStatsList *streamStatsList =
static_cast<StreamStatsList*>(controller->response());
// XXX: It is required to emit the signal even if the returned
// streamStatsList contains no records since the recipient
// StreamStatsModel slot needs to disconnect this signal-slot
// connection to prevent future stream stats for this portgroup
// to be sent to it
emit streamStatsReceived(mPortGroupId, streamStatsList);
delete controller;
}

View File

@ -39,6 +39,7 @@ LOW
namespace OstProto {
class PortContent;
class PortGroupContent;
class StreamStatsList;
}
class QFile;
@ -163,13 +164,19 @@ public:
void getPortStats();
void processPortStatsList();
void clearPortStats(QList<uint> *portList = NULL);
void processClearStatsAck(PbRpcController *controller);
void processClearPortStatsAck(PbRpcController *controller);
bool clearStreamStats(QList<uint> *portList = NULL);
void processClearStreamStatsAck(PbRpcController *controller);
bool getStreamStats(QList<uint> *portList = NULL);
void processStreamStatsList(PbRpcController *controller);
signals:
void portGroupDataChanged(int portGroupId, int portId = 0xFFFF);
void portListAboutToBeChanged(quint32 portGroupId);
void portListChanged(quint32 portGroupId);
void statsChanged(quint32 portGroupId);
void streamStatsReceived(quint32 portGroupId,
const OstProto::StreamStatsList *stats);
private slots:
void on_reconnectTimer_timeout();

View File

@ -170,6 +170,7 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
case e_STAT_BYTES_SENT_NIC:
return stats.tx_bytes_nic();
#endif
case e_STAT_RX_DROPS : return quint64(stats.rx_drops());
case e_STAT_RX_ERRORS: return quint64(stats.rx_errors());
case e_STAT_RX_FIFO_ERRORS: return quint64(stats.rx_fifo_errors());

View File

@ -66,6 +66,7 @@ typedef enum {
e_STAT_RX_FIFO_ERRORS,
e_STAT_RX_FRAME_ERRORS,
e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS,
e_STAT_MAX
@ -92,6 +93,7 @@ static QStringList PortStatName = (QStringList()
<< "Bytes Received (NIC)"
<< "Bytes Sent (NIC)"
#endif
<< "Receive Drops"
<< "Receive Errors"
<< "Receive Fifo Errors"

View File

@ -23,9 +23,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portstatsfilterdialog.h"
#include "portstatsmodel.h"
#include "portstatsproxymodel.h"
#include "streamstatsmodel.h"
#include "streamstatswindow.h"
#include "settings.h"
#include <QDockWidget>
#include <QHeaderView>
#include <QMainWindow>
extern QMainWindow *mainWindow;
PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
: QWidget(parent), proxyStatsModel(NULL)
@ -227,6 +233,8 @@ void PortStatsWindow::on_tbClear_clicked()
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
clearPortStats(&portList[i].portList);
pgl->portGroupByIndex(portList.at(i).portGroupId).
clearStreamStats(&portList[i].portList);
}
}
@ -253,6 +261,44 @@ void PortStatsWindow::on_tbClearAll_clicked()
{
pgl->portGroupByIndex(portList.at(i).portGroupId)
.clearPortStats(&portList[i].portList);
pgl->portGroupByIndex(portList.at(i).portGroupId)
.clearStreamStats(&portList[i].portList);
}
}
void PortStatsWindow::on_tbGetStreamStats_clicked()
{
QList<PortStatsModel::PortGroupAndPortList> portList;
StreamStatsModel *streamStatsModel;
// Get selected ports
model->portListFromIndex(selectedColumns, portList);
if (portList.size()) {
QDockWidget *dock = new QDockWidget(mainWindow);
streamStatsModel = new StreamStatsModel(dock);
dock->setWidget(new StreamStatsWindow(streamStatsModel, dock));
dock->setWindowTitle(dock->widget()->windowTitle());
dock->setObjectName("streamStatsDock");
dock->setAttribute(Qt::WA_DeleteOnClose);
QDockWidget *statsDock = mainWindow->findChild<QDockWidget*>(
"statsDock");
mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dock);
mainWindow->tabifyDockWidget(statsDock, dock);
dock->show();
dock->raise();
}
// Get stream stats for selected ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
{
PortGroup &pg = pgl->portGroupByIndex(portList.at(i).portGroupId);
if (pg.getStreamStats(&portList[i].portList)) {
connect(&pg,SIGNAL(streamStatsReceived(
quint32, const OstProto::StreamStatsList*)),
streamStatsModel, SLOT(appendStreamStatsList(
quint32, const OstProto::StreamStatsList*)));
}
}
}

View File

@ -52,6 +52,7 @@ private slots:
void on_tbClear_clicked();
void on_tbClearAll_clicked();
void on_tbGetStreamStats_clicked();
void on_tbResolveNeighbors_clicked();
void on_tbClearNeighbors_clicked();

View File

@ -112,6 +112,22 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbGetStreamStats" >
<property name="toolTip" >
<string>Fetch Selected Port Stream Stats</string>
</property>
<property name="statusTip" >
<string>Fetches stream statistics from the selected port(s)</string>
</property>
<property name="text" >
<string>Fetch Stream Stats</string>
</property>
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/stream_stats.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
@ -127,8 +143,8 @@
</widget>
</item>
<item>
<widget class="QToolButton" name="tbStartCapture">
<property name="toolTip">
<widget class="QToolButton" name="tbStartCapture" >
<property name="toolTip" >
<string>Start Capture</string>
</property>
<property name="statusTip">

View File

@ -755,12 +755,19 @@ void PortsWindow::on_actionPort_Configuration_triggered()
if (!plm->isPort(current))
return;
Port &port = plm->port(current);
OstProto::Port config;
config.set_transmit_mode(plm->port(current).transmitMode());
config.set_is_exclusive_control(plm->port(current).hasExclusiveControl());
config.set_user_name(plm->port(current).userName().toStdString());
// XXX: we don't call Port::protoDataCopyInto() to get config b'coz
// we want only the modifiable fields populated to send to Drone
// TODO: extend Port::protoDataCopyInto() to accept an optional param
// which says copy only modifiable fields
//plm->port(current).protoDataCopyInto(&config);
config.set_transmit_mode(port.transmitMode());
config.set_is_tracking_stream_stats(port.trackStreamStats());
config.set_is_exclusive_control(port.hasExclusiveControl());
config.set_user_name(port.userName().toStdString());
PortConfigDialog dialog(config, this);
PortConfigDialog dialog(config, port.getStats().state(), this);
if (dialog.exec() == QDialog::Accepted)
plm->portGroup(current.parent()).modifyPort(current.row(), config);

View File

@ -85,13 +85,14 @@ StreamConfigDialog::StreamConfigDialog(
// Time to play match the signals and slots!
// If L1/L2(FT)/L3 = None, force subsequent protocol level(s) also to None
// If L1/L2(FT)/L3/L4 = None,
// force subsequent protocol level(s) also to None
connect(rbL1None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
connect(rbFtNone, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
connect(rbL3None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
connect(rbL4None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
// If L1/L2(FT)/L3/L4 = Other, force subsequent protocol to Other and
// If L1/L2(FT)/L3/L4/L5 = Other, force subsequent protocol to Other and
// disable the subsequent protocol group as well
connect(rbL1Other, SIGNAL(toggled(bool)), rbFtOther, SLOT(setChecked(bool)));
connect(rbL1Other, SIGNAL(toggled(bool)), gbFrameType, SLOT(setDisabled(bool)));
@ -99,8 +100,10 @@ StreamConfigDialog::StreamConfigDialog(
connect(rbFtOther, SIGNAL(toggled(bool)), gbL3Proto, SLOT(setDisabled(bool)));
connect(rbL3Other, SIGNAL(toggled(bool)), rbL4Other, SLOT(setChecked(bool)));
connect(rbL3Other, SIGNAL(toggled(bool)), gbL4Proto, SLOT(setDisabled(bool)));
connect(rbL4Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool)));
connect(rbL4Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(bool)));
connect(rbL4Other, SIGNAL(toggled(bool)), rbL5Other, SLOT(setChecked(bool)));
connect(rbL4Other, SIGNAL(toggled(bool)), gbL5Proto, SLOT(setDisabled(bool)));
connect(rbL5Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool)));
connect(rbL5Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(bool)));
// Setup valid subsequent protocols for L2 to L4 protocols
for (int i = ProtoL2; i <= ProtoL4; i++)
@ -294,6 +297,17 @@ void StreamConfigDialog::setupUiExtra()
bgProto[ProtoPayload]->addButton(rbPayloadHexDump, OstProto::Protocol::kHexDumpFieldNumber);
bgProto[ProtoPayload]->addButton(rbPayloadOther, ButtonIdOther);
#endif
// Special
bgProto[ProtoSign] = new QButtonGroup();
bgProto[ProtoSign]->addButton(rbSpecialNone, ButtonIdNone);
bgProto[ProtoSign]->addButton(rbSignature,
OstProto::Protocol::kSignFieldNumber);
// Trailer
bgProto[ProtoTrailer] = new QButtonGroup();
bgProto[ProtoTrailer]->addButton(rbTrailerNone, ButtonIdNone);
bgProto[ProtoTrailer]->addButton(rbTrailerOther, ButtonIdOther);
/*
** Setup Validators
*/
@ -788,6 +802,8 @@ void StreamConfigDialog::forceProtocolNone(bool checked)
}
}
// Button 'newId' has been clicked
// - update the protocol list correspondingly
void StreamConfigDialog::updateProtocol(int newId)
{
int level;
@ -802,6 +818,8 @@ void StreamConfigDialog::updateProtocol(int newId)
__updateProtocol(level, newId);
}
// Button 'newId' belonging to layer 'level' has been clicked
// - update the protocol list correspondingly
void StreamConfigDialog::__updateProtocol(int level, int newId)
{
int oldId;
@ -849,7 +867,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
// Free both protocol and associated widget
delete _protocolWidgets.take(p);
delete p;
if (level == ProtoPayload)
if (level == ProtoTrailer)
{
while (_iter->hasNext())
{
@ -868,6 +886,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
return;
}
//! Traverse the ProtocolList and update the SelectProtocols (Simple) widget
void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
{
int i;
@ -893,7 +912,7 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
id = _iter->next()->protocolNumber();
btn = bgProto[i]->button(id);
if (btn)
if (btn) // we have a button for this protocol
{
if (btn->isEnabled())
btn->click();
@ -903,23 +922,32 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
__updateProtocol(i, id);
}
}
else
else // we don't have a button for this protocol
{
switch (i)
{
case ProtoVlan:
// No vlan - proto may belong to next layer
_iter->previous();
break;
case ProtoSign:
// No sign - but we have a trailer
_iter->previous();
break;
case ProtoPayload:
goto _other;
default:
default: // viz. L1, L2, L3, L4, L5, Trailer
// Is this a Payload layer protocol?
// (maybe intermediate layers are not present)
btn = bgProto[ProtoPayload]->button(id);
if (btn && btn->isEnabled())
{
btn->click();
break;
i = ProtoPayload;
continue;
}
else
goto _other;
@ -927,20 +955,22 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
}
}
// If more protocol(s) beyond payload ...
// If more protocol(s) beyond trailer ...
if (_iter->hasNext())
{
i = ProtoPayload;
i = ProtoTrailer;
goto _other;
}
Q_ASSERT(!_iter->hasNext()); // At end of the ProtocolList
goto _done;
_other:
// Set remaining protocols as 'Other'
for (int j = i; j < ProtoMax; j++)
{
// VLAN doesn't have a "Other" button
if (j == ProtoVlan)
// VLAN/Sign doesn't have a "Other" button
if ((j == ProtoVlan) || (j == ProtoSign))
continue;
bgProto[j]->button(ButtonIdOther)->setChecked(true);
@ -1212,6 +1242,13 @@ bool StreamConfigDialog::isCurrentStreamValid()
"varying fields at transmit time may not be same as configured");
}
if (!mPort.trackStreamStats()
&& mpStream->hasProtocol(OstProto::Protocol::kSignFieldNumber))
{
log << tr("Stream contains special signature, but per stream statistics "
"will not be available till it is enabled on the port");
}
mpStream->preflightCheck(log);
if (log.size())

View File

@ -67,6 +67,8 @@ private:
ProtoL4 = 4,
ProtoL5 = 5,
ProtoPayload = 6,
ProtoSign = 7,
ProtoTrailer = 8,
ProtoMax
};

View File

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>634</width>
<height>507</height>
<width>647</width>
<height>549</height>
</rect>
</property>
<property name="sizePolicy">
@ -164,8 +164,8 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
<rect>
<x>0</x>
<y>0</y>
<width>592</width>
<height>269</height>
<width>605</width>
<height>311</height>
</rect>
</property>
<attribute name="label">
@ -211,9 +211,9 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QGroupBox" name="gbFrameType">
<property name="enabled">
<item rowspan="3" row="0" column="1" >
<widget class="QGroupBox" name="gbFrameType" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title">
@ -394,51 +394,9 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="gbL5Proto">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>L5</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="rbL5None">
<property name="text">
<string>None</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbL5Text">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Text</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbL5Other">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Other</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="gbVlan">
<property name="enabled">
<item rowspan="2" row="1" column="0" >
<widget class="QGroupBox" name="gbVlan" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title">
@ -560,9 +518,51 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout>
</widget>
</item>
<item row="1" column="3">
<widget class="QGroupBox" name="gbPayloadProto">
<property name="enabled">
<item row="2" column="2" >
<widget class="QGroupBox" name="gbL5Proto" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title" >
<string>L5</string>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="QRadioButton" name="rbL5None" >
<property name="text" >
<string>None</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbL5Text" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="text" >
<string>Text</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbL5Other" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="text" >
<string>Other</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3" >
<widget class="QGroupBox" name="gbPayloadProto" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title">
@ -609,6 +609,67 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout>
</widget>
</item>
<item row="1" column="3" >
<widget class="QGroupBox" name="gbSpecial" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title" >
<string>Special</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QRadioButton" name="rbSpecialNone" >
<property name="text" >
<string>None</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbSignature" >
<property name="text" >
<string>Signature</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="3" >
<widget class="QGroupBox" name="gbTrailer" >
<property name="enabled" >
<bool>true</bool>
</property>
<property name="title" >
<string>Trailer</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QRadioButton" name="rbTrailerNone" >
<property name="text" >
<string>None</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbTrailerOther" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="text" >
<string>Other</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">

View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2017 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _STREAM_STATS_FILTER_MODEL_H
#define _STREAM_STATS_FILTER_MODEL_H
#include <QSortFilterProxyModel>
class StreamStatsFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
StreamStatsFilterModel(QObject *parent = 0)
: QSortFilterProxyModel(parent)
{
}
protected:
bool filterAcceptsColumn(int sourceColumn,
const QModelIndex &/*sourceParent*/) const
{
QString title = sourceModel()->headerData(sourceColumn, Qt::Horizontal)
.toString();
return filterRegExp().exactMatch(title) ? true : false;
}
bool filterAcceptsRow(int /*sourceRow*/,
const QModelIndex &/*sourceParent*/) const
{
return true;
}
};
#endif

234
client/streamstatsmodel.cpp Normal file
View File

@ -0,0 +1,234 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "streamstatsmodel.h"
#include "protocol.pb.h"
#include <QBrush>
// XXX: Keep the enum in sync with it's string
enum {
kTxPkts,
kRxPkts,
kTxBytes,
kRxBytes,
kMaxStreamStats
};
static QStringList statTitles = QStringList()
<< "Tx Pkts"
<< "Rx Pkts"
<< "Tx Bytes"
<< "Rx Bytes";
// XXX: Keep the enum in sync with it's string
enum {
kAggrTxPkts,
kAggrRxPkts,
kAggrPktLoss,
kMaxAggrStreamStats
};
static QStringList aggrStatTitles = QStringList()
<< "Total\nTx Pkts"
<< "Total\nRx Pkts"
<< "Total\nPkt Loss";
static const uint kAggrGuid = 0xffffffff;
StreamStatsModel::StreamStatsModel(QObject *parent)
: QAbstractTableModel(parent)
{
clearStats();
}
int StreamStatsModel::rowCount(const QModelIndex &parent) const
{
return guidList_.size();
}
int StreamStatsModel::columnCount(const QModelIndex &parent) const
{
if (!portList_.size())
return 0;
return kMaxAggrStreamStats + portList_.size() * kMaxStreamStats;
}
QVariant StreamStatsModel::headerData(
int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (orientation) {
case Qt::Horizontal: // Column Header
if (section < kMaxAggrStreamStats)
return aggrStatTitles.at(section % kMaxAggrStreamStats);
section -= kMaxAggrStreamStats;
return QString("Port %1-%2\n%3")
.arg(portList_.at(section/kMaxStreamStats).first)
.arg(portList_.at(section/kMaxStreamStats).second)
.arg(statTitles.at(section % kMaxStreamStats));
case Qt::Vertical: // Row Header
if (section == (guidList_.size() - 1))
return QString("GUID Total");
return QString("Stream GUID %1")
.arg(guidList_.at(section));
default:
break;
}
return QVariant();
}
QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::TextAlignmentRole)
return Qt::AlignRight;
int portColumn = index.column() - kMaxAggrStreamStats;
if (role == Qt::BackgroundRole) {
if (portColumn < 0)
return QBrush(QColor("lavender")); // Aggregate Column
if (index.row() == (guidList_.size() - 1))
return QBrush(QColor("burlywood")); // Aggregate Row
else if ((portColumn/kMaxStreamStats) & 1)
return QBrush(QColor("beige")); // Color alternate Ports
}
Guid guid = guidList_.at(index.row());
if (role == Qt::ForegroundRole) {
if ((index.column() == kAggrPktLoss)
&& aggrGuidStats_.value(guid).pktLoss)
return QBrush(QColor("firebrick"));
}
if (role != Qt::DisplayRole)
return QVariant();
if (index.column() < kMaxAggrStreamStats) {
int stat = index.column() % kMaxAggrStreamStats;
switch (stat) {
case kAggrRxPkts:
return QString("%L1").arg(aggrGuidStats_.value(guid).rxPkts);
case kAggrTxPkts:
return QString("%L1").arg(aggrGuidStats_.value(guid).txPkts);
case kAggrPktLoss:
return QString("%L1").arg(aggrGuidStats_.value(guid).pktLoss);
default:
break;
};
return QVariant();
}
PortGroupPort pgp = portList_.at(portColumn/kMaxStreamStats);
int stat = portColumn % kMaxStreamStats;
switch (stat) {
case kRxPkts:
return QString("%L1").arg(streamStats_.value(guid).value(pgp).rxPkts);
case kTxPkts:
return QString("%L1").arg(streamStats_.value(guid).value(pgp).txPkts);
case kRxBytes:
return QString("%L1").arg(streamStats_.value(guid).value(pgp).rxBytes);
case kTxBytes:
return QString("%L1").arg(streamStats_.value(guid).value(pgp).txBytes);
default:
break;
}
return QVariant();
}
// --------------------------------------------- //
// Slots
// --------------------------------------------- //
void StreamStatsModel::clearStats()
{
#if QT_VERSION >= 0x040600
beginResetModel();
#endif
guidList_.clear();
portList_.clear();
streamStats_.clear();
aggrGuidStats_.clear();
#if QT_VERSION >= 0x040600
endResetModel();
#else
reset();
#endif
}
void StreamStatsModel::appendStreamStatsList(
quint32 portGroupId,
const OstProto::StreamStatsList *stats)
{
int n = stats->stream_stats_size();
#if QT_VERSION >= 0x040600
beginResetModel();
#endif
for (int i = 0; i < n; i++) {
const OstProto::StreamStats &s = stats->stream_stats(i);
PortGroupPort pgp = PortGroupPort(portGroupId, s.port_id().id());
Guid guid = s.stream_guid().id();
StreamStats &ss = streamStats_[guid][pgp];
StreamStats &aggrPort = streamStats_[kAggrGuid][pgp];
AggrGuidStats &aggrGuid = aggrGuidStats_[guid];
AggrGuidStats &aggrAggr = aggrGuidStats_[kAggrGuid];
ss.rxPkts = s.rx_pkts();
ss.txPkts = s.tx_pkts();
ss.rxBytes = s.rx_bytes();
ss.txBytes = s.tx_bytes();
aggrPort.rxPkts += ss.rxPkts;
aggrPort.txPkts += ss.txPkts;
aggrPort.rxBytes += ss.rxBytes;
aggrPort.txBytes += ss.txBytes;
aggrGuid.rxPkts += ss.rxPkts;
aggrGuid.txPkts += ss.txPkts;
aggrGuid.pktLoss += ss.txPkts - ss.rxPkts;
aggrAggr.rxPkts += ss.rxPkts;
aggrAggr.txPkts += ss.txPkts;
aggrAggr.pktLoss += ss.txPkts - ss.rxPkts;
if (!portList_.contains(pgp))
portList_.append(pgp);
if (!guidList_.contains(guid))
guidList_.append(guid);
}
if (guidList_.size())
guidList_.append(kAggrGuid);
#if QT_VERSION >= 0x040600
endResetModel();
#else
reset();
#endif
// Prevent receiving any future updates from this sender
disconnect(sender(), 0, this, 0);
}

70
client/streamstatsmodel.h Normal file
View File

@ -0,0 +1,70 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _STREAM_STATS_MODEL_H
#define _STREAM_STATS_MODEL_H
#include <QAbstractTableModel>
#include <QHash>
#include <QList>
#include <QPair>
#include <QStringList>
namespace OstProto {
class StreamStatsList;
}
class StreamStatsModel: public QAbstractTableModel
{
Q_OBJECT
public:
StreamStatsModel(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 = Qt::DisplayRole) const;
public slots:
void clearStats();
void appendStreamStatsList(quint32 portGroupId,
const OstProto::StreamStatsList *stats);
private:
typedef QPair<uint, uint> PortGroupPort; // Pair = (PortGroupId, PortId)
typedef uint Guid;
struct StreamStats {
quint64 rxPkts;
quint64 txPkts;
quint64 rxBytes;
quint64 txBytes;
};
struct AggrGuidStats {
quint64 rxPkts;
quint64 txPkts;
qint64 pktLoss;
};
QList<Guid> guidList_;
QList<PortGroupPort> portList_;
QHash<Guid, QHash<PortGroupPort, StreamStats> > streamStats_;
QHash<Guid, AggrGuidStats> aggrGuidStats_;
};
#endif

View File

@ -0,0 +1,65 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "streamstatswindow.h"
#include "streamstatsfiltermodel.h"
#include <QAbstractItemModel>
#include <QHeaderView>
static int id;
static int count;
StreamStatsWindow::StreamStatsWindow(QAbstractItemModel *model, QWidget *parent)
: QWidget(parent)
{
setupUi(this);
streamStats->addAction(actionShowByteCounters);
if (id)
setWindowTitle(windowTitle() + QString("(%1)").arg(id));
id++;
count++;
filterModel_ = new StreamStatsFilterModel(this);
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*"));
filterModel_->setSourceModel(model);
streamStats->setModel(filterModel_);
streamStats->verticalHeader()->setHighlightSections(false);
streamStats->verticalHeader()->setDefaultSectionSize(
streamStats->verticalHeader()->minimumSectionSize());
}
StreamStatsWindow::~StreamStatsWindow()
{
delete filterModel_;
count--;
if (count == 0)
id = 0;
}
void StreamStatsWindow::on_actionShowByteCounters_triggered(bool checked)
{
if (checked)
filterModel_->setFilterRegExp(QRegExp(".*"));
else
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*"));
}

View File

@ -0,0 +1,43 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _STREAM_STATS_WINDOW_H
#define _STREAM_STATS_WINDOW_H
#include "ui_streamstatswindow.h"
class QAbstractItemModel;
class QSortFilterProxyModel;
class StreamStatsWindow: public QWidget, private Ui::StreamStatsWindow
{
Q_OBJECT
public:
StreamStatsWindow(QAbstractItemModel *model, QWidget *parent = 0);
~StreamStatsWindow();
private slots:
void on_actionShowByteCounters_triggered(bool checked);
private:
QSortFilterProxyModel *filterModel_;
};
#endif

View File

@ -0,0 +1,35 @@
<ui version="4.0" >
<class>StreamStatsWindow</class>
<widget class="QWidget" name="StreamStatsWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>551</width>
<height>452</height>
</rect>
</property>
<property name="windowTitle" >
<string>Stream Statistics</string>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="QTableView" name="streamStats" >
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
</widget>
</item>
</layout>
<action name="actionShowByteCounters" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Show Byte Counters</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "protocollistiterator.h"
#include "streambase.h"
#include "bswap.h"
#include <qendian.h>
/*!
@ -972,18 +974,29 @@ out:
quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
CksumType cksumType, CksumScope cksumScope) const
{
quint32 sum = 0;
quint32 sum;
quint16 cksum;
AbstractProtocol *p = next;
Q_ASSERT(cksumType == CksumIp);
if (!p)
return 0xFFFF;
cksum = p->protocolFrameCksum(streamIndex, cksumType);
sum = (quint16) ~cksum;
if (cksumScope == CksumScopeAdjacentProtocol)
goto out;
p = p->next;
while (p)
{
// when combining cksums, a non-first protocol starting at odd offset
// needs a byte swap (see RFC 1071 section(s) 2A, 2B)
cksum = p->protocolFrameCksum(streamIndex, cksumType);
if (p->protocolFrameOffset(streamIndex) & 0x1)
cksum = swap16(cksum);
sum += (quint16) ~cksum;
if (cksumScope == CksumScopeAdjacentProtocol)
goto out;
p = p->next;
}
@ -998,7 +1011,9 @@ out:
while(sum>>16)
sum = (sum & 0xFFFF) + (sum >> 16);
return (quint16) ~sum;
cksum = (quint16) ~sum;
qDebug("%s: cksum = %u", __FUNCTION__, cksum);
return cksum;
}
// Stein's binary GCD algo - from wikipedia

39
common/bswap.h Normal file
View File

@ -0,0 +1,39 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _B_SWAP_H
#define _B_SWAP_H
#include <QtGlobal>
static inline quint32 swap32(quint32 val)
{
return (((val >> 24) & 0x000000FF) |
((val >> 16) & 0x0000FF00) |
((val << 16) & 0x00FF0000) |
((val << 24) & 0xFF000000));
}
static inline quint16 swap16(quint16 val)
{
return (((val >> 8) & 0x00FF) |
((val << 8) & 0xFF00));
}
#endif

View File

@ -200,7 +200,10 @@ int HexDumpProtocol::protocolFrameSize(int streamIndex) const
if (data.pad_until_end())
{
int pad = mpStream->frameLen(streamIndex)
- (protocolFrameOffset(streamIndex) + len + kFcsSize);
- (protocolFrameOffset(streamIndex)
+ len
+ protocolFramePayloadSize(streamIndex)
+ kFcsSize);
if (pad < 0)
pad = 0;
len += pad;

View File

@ -38,7 +38,8 @@ PROTOS += \
textproto.proto \
userscript.proto \
hexdump.proto \
sample.proto
sample.proto \
sign.proto
HEADERS = \
abstractprotocol.h \
@ -77,6 +78,7 @@ HEADERS += \
hexdump.h \
payload.h \
sample.h \
sign.h \
userscript.h
SOURCES = \
@ -110,6 +112,7 @@ SOURCES += \
hexdump.cpp \
payload.cpp \
sample.cpp \
sign.cpp \
userscript.cpp
QMAKE_DISTCLEAN += object_script.*

View File

@ -27,6 +27,7 @@ FORMS += \
hexdump.ui \
payload.ui \
sample.ui \
sign.ui \
userscript.ui
PROTOS = \
@ -79,6 +80,7 @@ HEADERS += \
hexdumpconfig.h \
payloadconfig.h \
sampleconfig.h \
signconfig.h \
userscriptconfig.h
SOURCES += \
@ -118,6 +120,7 @@ SOURCES += \
hexdumpconfig.cpp \
payloadconfig.cpp \
sampleconfig.cpp \
signconfig.cpp \
userscriptconfig.cpp
SOURCES += \

View File

@ -68,7 +68,7 @@ int PayloadProtocol::protocolFrameSize(int streamIndex) const
int len;
len = mpStream->frameLen(streamIndex) - protocolFrameOffset(streamIndex)
- kFcsSize;
- protocolFramePayloadSize(streamIndex) - kFcsSize;
if (len < 0)
len = 0;

View File

@ -31,20 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QTemporaryFile>
#include <QtGlobal>
static inline quint32 swap32(quint32 val)
{
return (((val >> 24) && 0x000000FF) |
((val >> 16) && 0x0000FF00) |
((val << 16) && 0x00FF0000) |
((val << 24) && 0xFF000000));
}
static inline quint16 swap16(quint16 val)
{
return (((val >> 8) && 0x00FF) |
((val << 8) && 0xFF00));
}
const quint32 kPcapFileMagic = 0xa1b2c3d4;
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
const quint16 kPcapFileVersionMajor = 2;

View File

@ -128,6 +128,7 @@ message Protocol {
kSampleFieldNumber = 102;
kUserScriptFieldNumber = 103;
kHexDumpFieldNumber = 104;
kSignFieldNumber = 105;
kEth2FieldNumber = 200;
kDot3FieldNumber = 201;
@ -204,6 +205,7 @@ message Port {
optional bool is_exclusive_control = 6;
optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit];
optional string user_name = 8;
optional bool is_tracking_stream_stats = 9;
}
message PortConfigList {
@ -265,6 +267,29 @@ message PortStatsList {
repeated PortStats port_stats = 1;
}
message StreamGuid {
required uint32 id = 1;
}
message StreamGuidList {
required PortIdList port_id_list = 1;
repeated StreamGuid stream_guid = 2;
}
message StreamStats {
required PortId port_id = 1;
required StreamGuid stream_guid = 2;
optional uint64 rx_pkts = 11;
optional uint64 rx_bytes = 12;
optional uint64 tx_pkts = 13;
optional uint64 tx_bytes = 14;
}
message StreamStatsList {
repeated StreamStats stream_stats = 1;
}
enum NotifType {
portConfigChanged = 1;
}
@ -358,5 +383,11 @@ service OstService {
rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
rpc clearDeviceNeighbors(PortIdList) returns (Ack);
rpc getDeviceNeighbors(PortId) returns (PortNeighborList);
// Stream Stats
rpc getStreamStats(StreamGuidList) returns (StreamStatsList);
rpc clearStreamStats(StreamGuidList) returns (Ack);
// XXX: Add new RPCs at the end only to preserve backward compatibility
}

View File

@ -59,6 +59,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "hexdump.h"
#include "payload.h"
#include "sample.h"
#include "sign.h"
#include "userscript.h"
ProtocolManager *OstProtocolManager;
@ -133,6 +134,8 @@ ProtocolManager::ProtocolManager()
(void*) PayloadProtocol::createInstance);
registerProtocol(OstProto::Protocol::kSampleFieldNumber,
(void*) SampleProtocol::createInstance);
registerProtocol(OstProto::Protocol::kSignFieldNumber,
(void*) SignProtocol::createInstance);
registerProtocol(OstProto::Protocol::kUserScriptFieldNumber,
(void*) UserScriptProtocol::createInstance);

View File

@ -51,6 +51,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "hexdumpconfig.h"
#include "payloadconfig.h"
#include "sampleconfig.h"
#include "signconfig.h"
#include "userscriptconfig.h"
ProtocolWidgetFactory *OstProtocolWidgetFactory;
@ -154,6 +155,9 @@ ProtocolWidgetFactory::ProtocolWidgetFactory()
OstProtocolWidgetFactory->registerProtocolConfigWidget(
OstProto::Protocol::kSampleFieldNumber,
(void*) SampleConfigForm::createInstance);
OstProtocolWidgetFactory->registerProtocolConfigWidget(
OstProto::Protocol::kSignFieldNumber,
(void*) SignConfigForm::createInstance);
OstProtocolWidgetFactory->registerProtocolConfigWidget(
OstProto::Protocol::kUserScriptFieldNumber,
(void*) UserScriptConfigForm::createInstance);

219
common/sign.cpp Normal file
View File

@ -0,0 +1,219 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "sign.h"
SignProtocol::SignProtocol(StreamBase *stream, AbstractProtocol *parent)
: AbstractProtocol(stream, parent)
{
}
SignProtocol::~SignProtocol()
{
}
AbstractProtocol* SignProtocol::createInstance(StreamBase *stream,
AbstractProtocol *parent)
{
return new SignProtocol(stream, parent);
}
quint32 SignProtocol::protocolNumber() const
{
return OstProto::Protocol::kSignFieldNumber;
}
void SignProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const
{
protocol.MutableExtension(OstProto::sign)->CopyFrom(data);
protocol.mutable_protocol_id()->set_id(protocolNumber());
}
void SignProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
{
if (protocol.protocol_id().id() == protocolNumber() &&
protocol.HasExtension(OstProto::sign))
data.MergeFrom(protocol.GetExtension(OstProto::sign));
}
QString SignProtocol::name() const
{
return QString("Signature");
}
QString SignProtocol::shortName() const
{
return QString("SIGN");
}
int SignProtocol::fieldCount() const
{
return sign_fieldCount;
}
AbstractProtocol::FieldFlags SignProtocol::fieldFlags(int index) const
{
AbstractProtocol::FieldFlags flags;
flags = AbstractProtocol::fieldFlags(index);
switch (index)
{
case sign_magic:
case sign_tlv_guid:
case sign_tlv_end:
break;
default:
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
index);
break;
}
return flags;
}
QVariant SignProtocol::fieldData(int index, FieldAttrib attrib,
int streamIndex) const
{
switch (index)
{
case sign_magic:
{
switch(attrib)
{
case FieldName:
return QString("Magic");
case FieldValue:
return kSignMagic;
case FieldTextValue:
return QString("%1").arg(kSignMagic);
case FieldFrameValue:
{
QByteArray fv;
fv.resize(4);
qToBigEndian(kSignMagic, (uchar*) fv.data());
return fv;
}
default:
break;
}
break;
}
case sign_tlv_guid:
{
quint32 guid = data.stream_guid() & 0xFFFFFF;
switch(attrib)
{
case FieldName:
return QString("Stream GUID");
case FieldValue:
return guid;
case FieldTextValue:
return QString("%1").arg(guid);
case FieldFrameValue:
{
QByteArray fv;
fv.resize(4);
fv[0] = (guid >> 16) & 0xff;
fv[1] = (guid >> 8) & 0xff;
fv[2] = (guid >> 0) & 0xff;
fv[3] = kTypeLenGuid;
return fv;
}
default:
break;
}
break;
}
case sign_tlv_end:
{
switch(attrib)
{
case FieldName:
return QString("End TLV");
case FieldValue:
return 0;
case FieldTextValue:
return QString("NA");
case FieldFrameValue:
return QByteArray(1, kTypeLenEnd);
default:
break;
}
break;
}
default:
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
index);
break;
}
return AbstractProtocol::fieldData(index, attrib, streamIndex);
}
bool SignProtocol::setFieldData(int index, const QVariant &value,
FieldAttrib attrib)
{
bool isOk = false;
if (attrib != FieldValue)
goto _exit;
switch (index)
{
case sign_tlv_guid:
{
uint guid = value.toUInt(&isOk);
if (isOk)
data.set_stream_guid(guid & 0xFFFFFF);
break;
}
default:
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
index);
break;
}
_exit:
return isOk;
}
quint32 SignProtocol::magic()
{
return kSignMagic;
}
bool SignProtocol::packetGuid(const uchar *pkt, int pktLen, uint *guid)
{
const uchar *p = pkt + pktLen - sizeof(kSignMagic);
quint32 magic = qFromBigEndian<quint32>(p);
if (magic != kSignMagic)
return false;
p--;
while (*p != kTypeLenEnd) {
if (*p == kTypeLenGuid) {
*guid = qFromBigEndian<quint32>(p - 3) >> 8;
return true;
}
p -= 1 + (*p >> 5); // move to next TLV
}
return false;
}

93
common/sign.h Normal file
View File

@ -0,0 +1,93 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _SIGN_H
#define _SIGN_H
#include "abstractprotocol.h"
#include "sign.pb.h"
/*
Sign Protocol is expected at the end of the frame (just before the Eth FCS)
---+--------+-------+
. . .| TLV(s) | Magic |
| (8+) | (32) |
---+--------+-------+
Figures in brackets represent field width in bits
TLVs are encoded as
+-------+-----+------+
| Value | Len | Type |
| (...) | (3) | (5) |
+-------+-----+------+
Len does NOT include the one byte of TypeLen
Size of the value field varies between 0 to 7 bytes
Defined TLVs
Type = 0, Len = 0 (0x00): End of TLVs
Type = 1, Len = 3 (0x61): Stream GUID
*/
class SignProtocol : public AbstractProtocol
{
public:
enum samplefield
{
// Frame Fields
sign_tlv_end = 0,
sign_tlv_guid,
sign_magic,
// Meta Fields
// - None
sign_fieldCount
};
SignProtocol(StreamBase *stream, AbstractProtocol *parent = 0);
virtual ~SignProtocol();
static AbstractProtocol* createInstance(StreamBase *stream,
AbstractProtocol *parent = 0);
virtual quint32 protocolNumber() const;
virtual void protoDataCopyInto(OstProto::Protocol &protocol) const;
virtual void protoDataCopyFrom(const OstProto::Protocol &protocol);
virtual QString name() const;
virtual QString shortName() const;
virtual int fieldCount() const;
virtual AbstractProtocol::FieldFlags fieldFlags(int index) const;
virtual QVariant fieldData(int index, FieldAttrib attrib,
int streamIndex = 0) const;
virtual bool setFieldData(int index, const QVariant &value,
FieldAttrib attrib = FieldValue);
static quint32 magic();
static bool packetGuid(const uchar *pkt, int pktLen, uint *guid);
private:
static const quint32 kSignMagic = 0x1d10c0da; // coda! (unicode - 0x1d10c)
static const quint8 kTypeLenEnd = 0x00;
static const quint8 kTypeLenGuid = 0x61;
OstProto::Sign data;
};
#endif

31
common/sign.proto Normal file
View File

@ -0,0 +1,31 @@
/*
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 <http://www.gnu.org/licenses/>
*/
import "protocol.proto";
package OstProto;
// Sign Protocol
message Sign {
optional uint32 stream_guid = 1;
}
extend Protocol {
optional Sign sign = 105;
}

59
common/sign.ui Normal file
View File

@ -0,0 +1,59 @@
<ui version="4.0" >
<class>Sign</class>
<widget class="QWidget" name="Sign" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>64</height>
</rect>
</property>
<property name="windowTitle" >
<string>Form</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>Stream GUID</string>
</property>
<property name="buddy" >
<cstring>guid</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QLineEdit" name="guid" />
</item>
<item row="0" column="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

53
common/signconfig.cpp Normal file
View File

@ -0,0 +1,53 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "signconfig.h"
#include "sign.h"
SignConfigForm::SignConfigForm(QWidget *parent)
: AbstractProtocolConfigForm(parent)
{
setupUi(this);
}
SignConfigForm::~SignConfigForm()
{
}
SignConfigForm* SignConfigForm::createInstance()
{
return new SignConfigForm;
}
void SignConfigForm::loadWidget(AbstractProtocol *proto)
{
guid->setText(
proto->fieldData(
SignProtocol::sign_tlv_guid,
AbstractProtocol::FieldValue
).toString());
}
void SignConfigForm::storeWidget(AbstractProtocol *proto)
{
proto->setFieldData(
SignProtocol::sign_tlv_guid,
guid->text());
}

43
common/signconfig.h Normal file
View File

@ -0,0 +1,43 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _SIGN_CONFIG_H
#define _SIGN_CONFIG_H
#include "abstractprotocolconfig.h"
#include "ui_sign.h"
class SignConfigForm :
public AbstractProtocolConfigForm,
private Ui::Sign
{
Q_OBJECT
public:
SignConfigForm(QWidget *parent = 0);
virtual ~SignConfigForm();
static SignConfigForm* createInstance();
virtual void loadWidget(AbstractProtocol *proto);
virtual void storeWidget(AbstractProtocol *proto);
private slots:
};
#endif

View File

@ -140,6 +140,15 @@ void StreamBase::setFrameProtocol(ProtocolList protocolList)
}
#endif
bool StreamBase::hasProtocol(quint32 protocolNumber)
{
foreach(const AbstractProtocol *proto, *currentFrameProtocols)
if (proto->protocolNumber() == protocolNumber)
return true;
return false;
}
ProtocolListIterator* StreamBase::createProtocolListIterator() const
{
return new ProtocolListIterator(*currentFrameProtocols);
@ -581,32 +590,45 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const
return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex);
}
/*!
Checks for any potential errors with the packets generated by this
stream. Returns true if no problems are found, false otherwise. Details
of the error(s) are available in the INOUT param result
All errors found are returned. However, each type of error is reported
only once, even if multiple packets may have that error.
*/
bool StreamBase::preflightCheck(QStringList &result) const
{
bool pass = true;
bool chkTrunc = true;
bool chkJumbo = true;
int count = isFrameSizeVariable() ? frameCount() : 1;
for (int i = 0; i < count; i++)
{
if (frameLen(i) < (frameProtocolLength(i) + kFcsSize))
int pktLen = frameLen(i);
if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize)))
{
result << QObject::tr("One or more frames may be truncated - "
"frame length should be at least %1")
.arg(frameProtocolLength(i) + kFcsSize);
chkTrunc = false;
pass = false;
break;
}
}
for (int i = 0; i < count; i++)
{
if (frameLen(i) > 1522)
if (chkJumbo && (pktLen > 1522))
{
result << QObject::tr("Jumbo frames may be truncated or dropped "
"if not supported by the hardware");
pass = false;
break;
}
// Break out of loop if we've seen at least one instance of all
// the above errors
if (!chkTrunc && !chkJumbo)
break;
}
if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id)

View File

@ -40,6 +40,7 @@ public:
void protoDataCopyFrom(const OstProto::Stream &stream);
void protoDataCopyInto(OstProto::Stream &stream) const;
bool hasProtocol(quint32 protocolNumber);
ProtocolListIterator* createProtocolListIterator() const;
//! \todo (LOW) should we have a copy constructor??

View File

@ -65,6 +65,29 @@ void AbstractPort::init()
{
}
/*! Can we modify Port with these params? Should modify cause port dirty? */
bool AbstractPort::canModify(const OstProto::Port &port, bool *dirty)
{
bool allow = true;
*dirty = false;
if (port.has_transmit_mode()
&& (port.transmit_mode() != data_.transmit_mode())) {
*dirty = true;
allow = !isTransmitOn();
}
if (port.has_is_tracking_stream_stats()
&& (port.is_tracking_stream_stats()
!= data_.is_tracking_stream_stats())) {
*dirty = true;
allow = !isTransmitOn();
}
return allow;
}
bool AbstractPort::modify(const OstProto::Port &port)
{
bool ret = true;
@ -82,6 +105,9 @@ bool AbstractPort::modify(const OstProto::Port &port)
if (port.has_transmit_mode())
data_.set_transmit_mode(port.transmit_mode());
if (port.has_is_tracking_stream_stats())
ret |= setTrackStreamStats(port.is_tracking_stream_stats());
if (port.has_user_name()) {
data_.set_user_name(port.user_name());
}
@ -155,6 +181,13 @@ void AbstractPort::addNote(QString note)
data_.set_notes(notes.toStdString());
}
bool AbstractPort::setTrackStreamStats(bool enable)
{
data_.set_is_tracking_stream_stats(enable);
return true;
}
AbstractPort::Accuracy AbstractPort::rateAccuracy()
{
return rateAccuracy_;
@ -622,6 +655,54 @@ void AbstractPort::stats(PortStats *stats)
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
}
void AbstractPort::streamStats(uint guid, OstProto::StreamStatsList *stats)
{
if (streamStats_.contains(guid))
{
StreamStatsTuple sst = streamStats_.value(guid);
OstProto::StreamStats *s = stats->add_stream_stats();
s->mutable_stream_guid()->set_id(guid);
s->mutable_port_id()->set_id(id());
s->set_tx_pkts(sst.tx_pkts);
s->set_tx_bytes(sst.tx_bytes);
s->set_rx_pkts(sst.rx_pkts);
s->set_rx_bytes(sst.rx_bytes);
}
}
void AbstractPort::streamStatsAll(OstProto::StreamStatsList *stats)
{
// FIXME: change input param to a non-OstProto type and/or have
// a getFirst/Next like API?
StreamStatsIterator i(streamStats_);
while (i.hasNext())
{
i.next();
StreamStatsTuple sst = i.value();
OstProto::StreamStats *s = stats->add_stream_stats();
s->mutable_stream_guid()->set_id(i.key());
s->mutable_port_id()->set_id(id());
s->set_tx_pkts(sst.tx_pkts);
s->set_tx_bytes(sst.tx_bytes);
s->set_rx_pkts(sst.rx_pkts);
s->set_rx_bytes(sst.rx_bytes);
}
}
void AbstractPort::resetStreamStats(uint guid)
{
streamStats_.remove(guid);
}
void AbstractPort::resetStreamStatsAll()
{
streamStats_.clear();
}
void AbstractPort::clearDeviceNeighbors()
{
deviceManager_->clearDeviceNeighbors();

View File

@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _SERVER_ABSTRACT_PORT_H
#define _SERVER_ABSTRACT_PORT_H
#include "streamstats.h"
#include <QList>
#include <QtGlobal>
@ -72,6 +74,7 @@ public:
const char* name() { return data_.name().c_str(); }
void protoDataCopyInto(OstProto::Port *port) { port->CopyFrom(data_); }
bool canModify(const OstProto::Port &port, bool *dirty);
bool modify(const OstProto::Port &port);
virtual OstProto::LinkState linkState() { return linkState_; }
@ -87,6 +90,8 @@ public:
bool isDirty() { return isSendQueueDirty_; }
void setDirty() { isSendQueueDirty_ = true; }
virtual bool setTrackStreamStats(bool enable);
Accuracy rateAccuracy();
virtual bool setRateAccuracy(Accuracy accuracy);
@ -111,6 +116,12 @@ public:
void stats(PortStats *stats);
void resetStats() { epochStats_ = stats_; }
// FIXME: combine single and All calls?
void streamStats(uint guid, OstProto::StreamStatsList *stats);
void streamStatsAll(OstProto::StreamStatsList *stats);
void resetStreamStats(uint guid);
void resetStreamStatsAll();
DeviceManager* deviceManager();
virtual void startDeviceEmulation() = 0;
virtual void stopDeviceEmulation() = 0;
@ -123,6 +134,7 @@ public:
quint64 neighborMacAddress(int streamId, int frameIndex);
protected:
void addNote(QString note);
void updatePacketListSequential();
@ -136,6 +148,7 @@ protected:
quint64 maxStatsValue_;
struct PortStats stats_;
StreamStats streamStats_;
//! \todo Need lock for stats access/update
DeviceManager *deviceManager_;

View File

@ -31,6 +31,7 @@ win32 {
LIBS += -lm
LIBS += -lprotobuf
HEADERS += drone.h \
pcaptransmitter.h \
myservice.h
SOURCES += \
devicemanager.cpp \
@ -40,6 +41,10 @@ SOURCES += \
portmanager.cpp \
abstractport.cpp \
pcapport.cpp \
pcaptransmitter.cpp \
pcaprxstats.cpp \
pcaptxstats.cpp \
pcaptxthread.cpp \
bsdport.cpp \
linuxport.cpp \
winpcapport.cpp

View File

@ -1,5 +1,5 @@
/*
Copyright (C) 2010-2015 Srivats P.
Copyright (C) 2010-2016 Srivats P.
This file is part of "Ostinato"
@ -133,10 +133,20 @@ void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/,
id = port.port_id().id();
if (id < portInfo.size())
{
bool dirty;
if (!portInfo[id]->canModify(port, &dirty)) {
qDebug("Port %d cannot be modified - stop tx and retry", id);
continue; //! \todo(LOW): Partial status of RPC
}
portLock[id]->lockForWrite();
portInfo[id]->modify(port);
if (dirty)
portInfo[id]->updatePacketList();
portLock[id]->unlock();
notif->mutable_port_id_list()->add_port_id()->set_id(id);
}
}
@ -148,6 +158,8 @@ void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/,
notif->set_notif_type(OstProto::portConfigChanged);
emit notification(notif->notif_type(), SharedProtobufMessage(notif));
}
else
delete notif;
}
void MyService::getStreamIdList(::google::protobuf::RpcController* controller,
@ -558,6 +570,66 @@ void MyService::clearStats(::google::protobuf::RpcController* /*controller*/,
done->Run();
}
void MyService::getStreamStats(
::google::protobuf::RpcController* /*controller*/,
const ::OstProto::StreamGuidList* request,
::OstProto::StreamStatsList* response,
::google::protobuf::Closure* done)
{
qDebug("In %s", __PRETTY_FUNCTION__);
for (int i = 0; i < request->port_id_list().port_id_size(); i++)
{
int portId;
portId = request->port_id_list().port_id(i).id();
if ((portId < 0) || (portId >= portInfo.size()))
continue; //! \todo(LOW): partial rpc?
portLock[portId]->lockForRead();
if (request->stream_guid_size())
for (int j = 0; j < request->stream_guid_size(); j++)
portInfo[portId]->streamStats(request->stream_guid(j).id(),
response);
else
portInfo[portId]->streamStatsAll(response);
portLock[portId]->unlock();
}
done->Run();
}
void MyService::clearStreamStats(
::google::protobuf::RpcController* /*controller*/,
const ::OstProto::StreamGuidList* request,
::OstProto::Ack* /*response*/,
::google::protobuf::Closure* done)
{
qDebug("In %s", __PRETTY_FUNCTION__);
for (int i = 0; i < request->port_id_list().port_id_size(); i++)
{
int portId;
portId = request->port_id_list().port_id(i).id();
if ((portId < 0) || (portId >= portInfo.size()))
continue; //! \todo (LOW): partial RPC?
portLock[portId]->lockForWrite();
if (request->stream_guid_size())
for (int j = 0; j < request->stream_guid_size(); j++)
portInfo[portId]->resetStreamStats(
request->stream_guid(j).id());
else
portInfo[portId]->resetStreamStatsAll();
portLock[portId]->unlock();
}
//! \todo (LOW): fill-in response "Ack"????
done->Run();
}
void MyService::checkVersion(::google::protobuf::RpcController* controller,
const ::OstProto::VersionInfo* request,
::OstProto::VersionCompatibility* response,

View File

@ -100,6 +100,16 @@ public:
const ::OstProto::PortIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void getStreamStats(::google::protobuf::RpcController* controller,
const ::OstProto::StreamGuidList* request,
::OstProto::StreamStatsList* response,
::google::protobuf::Closure* done);
virtual void clearStreamStats(::google::protobuf::RpcController* controller,
const ::OstProto::StreamGuidList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void checkVersion(::google::protobuf::RpcController* controller,
const ::OstProto::VersionInfo* request,
::OstProto::VersionCompatibility* response,

88
server/packetsequence.h Normal file
View File

@ -0,0 +1,88 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _PACKET_SEQUENCE_H
#define _PACKET_SEQUENCE_H
#include "pcapextra.h"
#include "../common/sign.h"
#include "streamstats.h"
class PacketSequence
{
public:
PacketSequence(bool trackGuidStats) {
trackGuidStats_ = trackGuidStats;
sendQueue_ = pcap_sendqueue_alloc(1*1024*1024);
lastPacket_ = NULL;
packets_ = 0;
bytes_ = 0;
usecDuration_ = 0;
repeatCount_ = 1;
repeatSize_ = 1;
usecDelay_ = 0;
}
~PacketSequence() {
pcap_sendqueue_destroy(sendQueue_);
}
bool hasFreeSpace(int size) {
if ((sendQueue_->len + size) <= sendQueue_->maxlen)
return true;
else
return false;
}
int appendPacket(const struct pcap_pkthdr *pktHeader,
const uchar *pktData) {
int ret;
if (lastPacket_)
{
usecDuration_ += (pktHeader->ts.tv_sec
- lastPacket_->ts.tv_sec) * long(1e6);
usecDuration_ += (pktHeader->ts.tv_usec
- lastPacket_->ts.tv_usec);
}
packets_++;
bytes_ += pktHeader->caplen;
lastPacket_ = (struct pcap_pkthdr *)
(sendQueue_->buffer + sendQueue_->len);
ret = pcap_sendqueue_queue(sendQueue_, pktHeader, pktData);
if (trackGuidStats_ && (ret >= 0)) {
uint guid;
if (SignProtocol::packetGuid(pktData, pktHeader->caplen, &guid)) {
streamStatsMeta_[guid].tx_pkts++;
streamStatsMeta_[guid].tx_bytes += pktHeader->caplen;
}
}
return ret;
}
pcap_send_queue *sendQueue_;
struct pcap_pkthdr *lastPacket_;
long packets_;
long bytes_;
ulong usecDuration_;
int repeatCount_;
int repeatSize_;
long usecDelay_;
StreamStats streamStatsMeta_;
private:
bool trackGuidStats_;
};
#endif

View File

@ -24,67 +24,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QtGlobal>
#ifdef Q_OS_WIN32
#include <windows.h>
#endif
pcap_if_t *PcapPort::deviceList_ = NULL;
#if defined(Q_OS_LINUX)
typedef struct timeval TimeStamp;
static void inline getTimeStamp(TimeStamp *stamp)
{
gettimeofday(stamp, NULL);
}
// Returns time diff in usecs between end and start
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
{
struct timeval diff;
long usecs;
timersub(end, start, &diff);
usecs = diff.tv_usec;
if (diff.tv_sec)
usecs += diff.tv_sec*1e6;
return usecs;
}
#elif defined(Q_OS_WIN32)
static quint64 gTicksFreq;
typedef LARGE_INTEGER TimeStamp;
static void inline getTimeStamp(TimeStamp* stamp)
{
QueryPerformanceCounter(stamp);
}
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
{
if (end->QuadPart >= start->QuadPart)
return (end->QuadPart - start->QuadPart)*long(1e6)/gTicksFreq;
else
{
// FIXME: incorrect! what's the max value for this counter before
// it rolls over?
return (start->QuadPart)*long(1e6)/gTicksFreq;
}
}
#else
typedef int TimeStamp;
static void inline getTimeStamp(TimeStamp*) {}
static long inline udiffTimeStamp(const TimeStamp*, const TimeStamp*) { return 0; }
#endif
PcapPort::PcapPort(int id, const char *device)
: AbstractPort(id, device)
{
monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_);
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
transmitter_ = new PortTransmitter(device);
transmitter_ = new PcapTransmitter(device, streamStats_);
capturer_ = new PortCapturer(device);
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
rxStatsPoller_ = new PcapRxStats(device, streamStats_);
if (!monitorRx_->handle() || !monitorTx_->handle())
isUsable_ = false;
@ -133,6 +83,9 @@ PcapPort::~PcapPort()
if (monitorTx_)
monitorTx_->stop();
rxStatsPoller_->stop();
delete rxStatsPoller_;
delete emulXcvr_;
delete capturer_;
delete transmitter_;
@ -168,6 +121,16 @@ void PcapPort::updateNotes()
arg(notes).toStdString());
}
bool PcapPort::setTrackStreamStats(bool enable)
{
bool val = enable ? startStreamStatsTracking() : stopStreamStatsTracking();
if (val)
AbstractPort::setTrackStreamStats(enable);
return val;
}
bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
{
if (transmitter_->setRateAccuracy(accuracy)) {
@ -192,6 +155,44 @@ int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
return emulXcvr_->transmitPacket(pktBuf);
}
bool PcapPort::startStreamStatsTracking()
{
if (!transmitter_->setStreamStatsTracking(true))
goto _tx_fail;
if (!rxStatsPoller_->start())
goto _rx_fail;
/*
* If RxPoller receives both IN and OUT packets, packets Tx on this
* port will also be received by it and we consider it to be a Rx (IN)
* packet incorrectly - so adjust Rx stats for this case
* XXX - ideally, RxPoller should do this adjustment, but given our
* design, it is easier to implement in transmitter
*/
transmitter_->adjustRxStreamStats(!rxStatsPoller_->isDirectional());
return true;
_rx_fail:
transmitter_->setStreamStatsTracking(false);
_tx_fail:
qWarning("failed to start stream stats tracking");
return false;
}
bool PcapPort::stopStreamStatsTracking()
{
if (!transmitter_->setStreamStatsTracking(false))
goto _tx_fail;
if (!rxStatsPoller_->stop())
goto _rx_fail;
return true;
_rx_fail:
transmitter_->setStreamStatsTracking(true);
_tx_fail:
qWarning("failed to stop stream stats tracking");
return false;
}
/*
* ------------------------------------------------------------------- *
* Port Monitor
@ -342,440 +343,6 @@ void PcapPort::PortMonitor::stop()
pcap_breakloop(handle());
}
/*
* ------------------------------------------------------------------- *
* Port Transmitter
* ------------------------------------------------------------------- *
*/
PcapPort::PortTransmitter::PortTransmitter(const char *device)
{
char errbuf[PCAP_ERRBUF_SIZE] = "";
#ifdef Q_OS_WIN32
LARGE_INTEGER freq;
if (QueryPerformanceFrequency(&freq))
gTicksFreq = freq.QuadPart;
else
Q_ASSERT_X(false, "PortTransmitter::PortTransmitter",
"This Win32 platform does not support performance counter");
#endif
state_ = kNotStarted;
returnToQIdx_ = -1;
loopDelay_ = 0;
stop_ = false;
stats_ = new AbstractPort::PortStats;
usingInternalStats_ = true;
handle_ = pcap_open_live(device, 64 /* FIXME */, 0, 1000 /* ms */, errbuf);
if (handle_ == NULL)
goto _open_error;
usingInternalHandle_ = true;
return;
_open_error:
qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf);
usingInternalHandle_ = false;
}
PcapPort::PortTransmitter::~PortTransmitter()
{
if (usingInternalStats_)
delete stats_;
if (usingInternalHandle_)
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());
// \todo lock for packetSequenceList
while(packetSequenceList_.size())
delete packetSequenceList_.takeFirst();
currentPacketSequence_ = NULL;
repeatSequenceStart_ = -1;
repeatSize_ = 0;
packetCount_ = 0;
returnToQIdx_ = -1;
setPacketListLoopMode(false, 0, 0);
}
void PcapPort::PortTransmitter::loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec)
{
currentPacketSequence_ = new PacketSequence;
currentPacketSequence_->repeatCount_ = repeats;
currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6)
+ repeatDelayNsec/1000;
repeatSequenceStart_ = packetSequenceList_.size();
repeatSize_ = size;
packetCount_ = 0;
packetSequenceList_.append(currentPacketSequence_);
}
bool PcapPort::PortTransmitter::appendToPacketList(long sec, long nsec,
const uchar *packet, int length)
{
bool op = true;
pcap_pkthdr pktHdr;
pktHdr.caplen = pktHdr.len = length;
pktHdr.ts.tv_sec = sec;
pktHdr.ts.tv_usec = nsec/1000;
if (currentPacketSequence_ == NULL ||
!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
{
if (currentPacketSequence_ != NULL)
{
long usecs;
usecs = (pktHdr.ts.tv_sec
- currentPacketSequence_->lastPacket_->ts.tv_sec)
* long(1e6);
usecs += (pktHdr.ts.tv_usec
- currentPacketSequence_->lastPacket_->ts.tv_usec);
currentPacketSequence_->usecDelay_ = usecs;
}
//! \todo (LOW): calculate sendqueue size
currentPacketSequence_ = new PacketSequence;
packetSequenceList_.append(currentPacketSequence_);
// Validate that the pkt will fit inside the new currentSendQueue_
Q_ASSERT(currentPacketSequence_->hasFreeSpace(
sizeof(pcap_pkthdr) + length));
}
if (currentPacketSequence_->appendPacket(&pktHdr, (u_char*) packet) < 0)
{
op = false;
}
packetCount_++;
if (repeatSize_ > 0 && packetCount_ == repeatSize_)
{
qDebug("repeatSequenceStart_=%d, repeatSize_ = %llu",
repeatSequenceStart_, repeatSize_);
// Set the packetSequence repeatSize
Q_ASSERT(repeatSequenceStart_ >= 0);
Q_ASSERT(repeatSequenceStart_ < packetSequenceList_.size());
if (currentPacketSequence_ != packetSequenceList_[repeatSequenceStart_])
{
PacketSequence *start = packetSequenceList_[repeatSequenceStart_];
currentPacketSequence_->usecDelay_ = start->usecDelay_;
start->usecDelay_ = 0;
start->repeatSize_ =
packetSequenceList_.size() - repeatSequenceStart_;
}
repeatSize_ = 0;
// End current pktSeq and trigger a new pktSeq allocation for next pkt
currentPacketSequence_ = NULL;
}
return op;
}
void PcapPort::PortTransmitter::setHandle(pcap_t *handle)
{
if (usingInternalHandle_)
pcap_close(handle_);
handle_ = handle;
usingInternalHandle_ = false;
}
void PcapPort::PortTransmitter::useExternalStats(AbstractPort::PortStats *stats)
{
if (usingInternalStats_)
delete stats_;
stats_ = stats;
usingInternalStats_ = false;
}
void PcapPort::PortTransmitter::run()
{
//! \todo (MED) Stream Mode - continuous: define before implement
// NOTE1: We can't use pcap_sendqueue_transmit() directly even on Win32
// 'coz of 2 reasons - there's no way of stopping it before all packets
// in the sendQueue are sent out and secondly, stats are available only
// when all packets have been sent - no periodic updates
//
// NOTE2: Transmit on the Rx Handle so that we can receive it back
// on the Tx Handle to do stats
//
// NOTE3: Update pcapExtra counters - port TxStats will be updated in the
// 'stats callback' function so that both Rx and Tx stats are updated
// together
const int kSyncTransmit = 1;
int i;
long overHead = 0; // overHead should be negative or zero
qDebug("packetSequenceList_.size = %d", packetSequenceList_.size());
if (packetSequenceList_.size() <= 0)
goto _exit;
for(i = 0; i < packetSequenceList_.size(); i++) {
qDebug("sendQ[%d]: rptCnt = %d, rptSz = %d, usecDelay = %ld", i,
packetSequenceList_.at(i)->repeatCount_,
packetSequenceList_.at(i)->repeatSize_,
packetSequenceList_.at(i)->usecDelay_);
qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld", i,
packetSequenceList_.at(i)->packets_,
packetSequenceList_.at(i)->usecDuration_);
}
state_ = kRunning;
i = 0;
while (i < packetSequenceList_.size())
{
_restart:
int rptSz = packetSequenceList_.at(i)->repeatSize_;
int rptCnt = packetSequenceList_.at(i)->repeatCount_;
for (int j = 0; j < rptCnt; j++)
{
for (int k = 0; k < rptSz; k++)
{
int ret;
PacketSequence *seq = packetSequenceList_.at(i+k);
#ifdef Q_OS_WIN32
TimeStamp ovrStart, ovrEnd;
if (seq->usecDuration_ <= long(1e6)) // 1s
{
getTimeStamp(&ovrStart);
ret = pcap_sendqueue_transmit(handle_,
seq->sendQueue_, kSyncTransmit);
if (ret >= 0)
{
stats_->txPkts += seq->packets_;
stats_->txBytes += seq->bytes_;
getTimeStamp(&ovrEnd);
overHead += seq->usecDuration_
- udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
}
if (stop_)
ret = -2;
}
else
{
ret = sendQueueTransmit(handle_, seq->sendQueue_,
overHead, kSyncTransmit);
}
#else
ret = sendQueueTransmit(handle_, seq->sendQueue_,
overHead, kSyncTransmit);
#endif
if (ret >= 0)
{
long usecs = seq->usecDelay_ + overHead;
if (usecs > 0)
{
(*udelayFn_)(usecs);
overHead = 0;
}
else
overHead = usecs;
}
else
{
qDebug("error %d in sendQueueTransmit()", ret);
qDebug("overHead = %ld", overHead);
stop_ = false;
goto _exit;
}
}
}
// Move to the next Packet Set
i += rptSz;
}
if (returnToQIdx_ >= 0)
{
long usecs = loopDelay_ + overHead;
if (usecs > 0)
{
(*udelayFn_)(usecs);
overHead = 0;
}
else
overHead = usecs;
i = returnToQIdx_;
goto _restart;
}
_exit:
state_ = kFinished;
}
void PcapPort::PortTransmitter::start()
{
// FIXME: return error
if (state_ == kRunning) {
qWarning("Transmit start requested but is already running!");
return;
}
state_ = kNotStarted;
QThread::start();
while (state_ == kNotStarted)
QThread::msleep(10);
}
void PcapPort::PortTransmitter::stop()
{
if (state_ == kRunning) {
stop_ = true;
while (state_ == kRunning)
QThread::msleep(10);
}
else {
// FIXME: return error
qWarning("Transmit stop requested but is not running!");
return;
}
}
bool PcapPort::PortTransmitter::isRunning()
{
return (state_ == kRunning);
}
int PcapPort::PortTransmitter::sendQueueTransmit(pcap_t *p,
pcap_send_queue *queue, long &overHead, int sync)
{
TimeStamp ovrStart, ovrEnd;
struct timeval ts;
struct pcap_pkthdr *hdr = (struct pcap_pkthdr*) queue->buffer;
char *end = queue->buffer + queue->len;
ts = hdr->ts;
getTimeStamp(&ovrStart);
while((char*) hdr < end)
{
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
int pktLen = hdr->caplen;
if (sync)
{
long usec = (hdr->ts.tv_sec - ts.tv_sec) * 1000000 +
(hdr->ts.tv_usec - ts.tv_usec);
getTimeStamp(&ovrEnd);
overHead -= udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
usec += overHead;
if (usec > 0)
{
(*udelayFn_)(usec);
overHead = 0;
}
else
overHead = usec;
ts = hdr->ts;
getTimeStamp(&ovrStart);
}
Q_ASSERT(pktLen > 0);
pcap_sendpacket(p, pkt, pktLen);
stats_->txPkts++;
stats_->txBytes += pktLen;
// Step to the next packet in the buffer
hdr = (struct pcap_pkthdr*) (pkt + pktLen);
pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr));
if (stop_)
{
return -2;
}
}
return 0;
}
void PcapPort::PortTransmitter::udelay(unsigned long usec)
{
#if defined(Q_OS_WIN32)
LARGE_INTEGER tgtTicks;
LARGE_INTEGER curTicks;
QueryPerformanceCounter(&curTicks);
tgtTicks.QuadPart = curTicks.QuadPart + (usec*gTicksFreq)/1000000;
while (curTicks.QuadPart < tgtTicks.QuadPart)
QueryPerformanceCounter(&curTicks);
#elif defined(Q_OS_LINUX)
struct timeval delay, target, now;
//qDebug("usec delay = %ld", usec);
delay.tv_sec = 0;
delay.tv_usec = usec;
while (delay.tv_usec >= 1000000)
{
delay.tv_sec++;
delay.tv_usec -= 1000000;
}
gettimeofday(&now, NULL);
timeradd(&now, &delay, &target);
do {
gettimeofday(&now, NULL);
} while (timercmp(&now, &target, <));
#else
QThread::usleep(usec);
#endif
}
/*
* ------------------------------------------------------------------- *
* Port Capturer

View File

@ -26,6 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "abstractport.h"
#include "pcapextra.h"
#include "pcaprxstats.h"
#include "pcaptransmitter.h"
class PcapPort : public AbstractPort
{
@ -38,6 +40,7 @@ public:
virtual bool hasExclusiveControl() { return false; }
virtual bool setExclusiveControl(bool /*exclusive*/) { return false; }
virtual bool setTrackStreamStats(bool enable);
virtual bool setRateAccuracy(AbstractPort::Accuracy accuracy);
virtual void clearPacketList() {
@ -103,107 +106,6 @@ protected:
bool isPromisc_;
};
class PortTransmitter: public QThread
{
public:
PortTransmitter(const char *device);
~PortTransmitter();
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
void clearPacketList();
void loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec);
bool appendToPacketList(long sec, long usec, const uchar *packet,
int length);
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay) {
returnToQIdx_ = loop ? 0 : -1;
loopDelay_ = secDelay*long(1e6) + nsecDelay/1000;
}
void setHandle(pcap_t *handle);
void useExternalStats(AbstractPort::PortStats *stats);
void run();
void start();
void stop();
bool isRunning();
private:
enum State
{
kNotStarted,
kRunning,
kFinished
};
class PacketSequence
{
public:
PacketSequence() {
sendQueue_ = pcap_sendqueue_alloc(1*1024*1024);
lastPacket_ = NULL;
packets_ = 0;
bytes_ = 0;
usecDuration_ = 0;
repeatCount_ = 1;
repeatSize_ = 1;
usecDelay_ = 0;
}
~PacketSequence() {
pcap_sendqueue_destroy(sendQueue_);
}
bool hasFreeSpace(int size) {
if ((sendQueue_->len + size) <= sendQueue_->maxlen)
return true;
else
return false;
}
int appendPacket(const struct pcap_pkthdr *pktHeader,
const uchar *pktData) {
if (lastPacket_)
{
usecDuration_ += (pktHeader->ts.tv_sec
- lastPacket_->ts.tv_sec) * long(1e6);
usecDuration_ += (pktHeader->ts.tv_usec
- lastPacket_->ts.tv_usec);
}
packets_++;
bytes_ += pktHeader->caplen;
lastPacket_ = (struct pcap_pkthdr *)
(sendQueue_->buffer + sendQueue_->len);
return pcap_sendqueue_queue(sendQueue_, pktHeader, pktData);
}
pcap_send_queue *sendQueue_;
struct pcap_pkthdr *lastPacket_;
long packets_;
long bytes_;
ulong usecDuration_;
int repeatCount_;
int repeatSize_;
long usecDelay_;
};
static void udelay(unsigned long usec);
int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead,
int sync);
QList<PacketSequence*> packetSequenceList_;
PacketSequence *currentPacketSequence_;
int repeatSequenceStart_;
quint64 repeatSize_;
quint64 packetCount_;
int returnToQIdx_;
quint64 loopDelay_;
void (*udelayFn_)(unsigned long);
bool usingInternalStats_;
AbstractPort::PortStats *stats_;
bool usingInternalHandle_;
pcap_t *handle_;
volatile bool stop_;
volatile State state_;
};
class PortCapturer: public QThread
{
public:
@ -263,9 +165,13 @@ protected:
void updateNotes();
private:
PortTransmitter *transmitter_;
bool startStreamStatsTracking();
bool stopStreamStatsTracking();
PcapTransmitter *transmitter_;
PortCapturer *capturer_;
EmulationTransceiver *emulXcvr_;
PcapRxStats *rxStatsPoller_;
static pcap_if_t *deviceList_;
};

186
server/pcaprxstats.cpp Normal file
View File

@ -0,0 +1,186 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "pcaprxstats.h"
#include "pcapextra.h"
#include "../common/sign.h"
#define notify qWarning // FIXME
PcapRxStats::PcapRxStats(const char *device, StreamStats &portStreamStats)
: streamStats_(portStreamStats)
{
device_ = QString::fromAscii(device);
stop_ = false;
state_ = kNotStarted;
isDirectional_ = true;
handle_ = NULL;
}
pcap_t* PcapRxStats::handle()
{
return handle_;
}
void PcapRxStats::run()
{
int flags = PCAP_OPENFLAG_PROMISCUOUS;
char errbuf[PCAP_ERRBUF_SIZE] = "";
struct bpf_program bpf;
const int optimize = 1;
QString capture_filter = QString("(ether[len - 4:4] == 0x%1)").arg(
SignProtocol::magic(), 0, BASE_HEX);
// XXX: Exclude ICMP packets which contain an embedded signed packet
// For now we check upto 4 vlan tags
capture_filter.append(
"and not ("
"icmp or "
"(vlan and icmp) or "
"(vlan and icmp) or "
"(vlan and icmp) or "
"(vlan and icmp) "
")");
qDebug("In %s", __PRETTY_FUNCTION__);
handle_ = pcap_open_live(qPrintable(device_), 65535,
flags, 100 /* ms */, errbuf);
if (handle_ == NULL) {
if (flags && QString(errbuf).contains("promiscuous")) {
notify("Unable to set promiscuous mode on <%s> - "
"stream stats rx will not work", qPrintable(device_));
goto _exit;
}
else {
notify("Unable to open <%s> [%s] - stream stats rx will not work",
qPrintable(device_), errbuf);
goto _exit;
}
}
#ifdef Q_OS_WIN32
// pcap_setdirection() API is not supported in Windows.
// NOTE: WinPcap 4.1.1 and above exports a dummy API that returns -1
// but since we would like to work with previous versions of WinPcap
// also, we assume the API does not exist
isDirectional_ = false;
#else
if (pcap_setdirection(handle_, PCAP_D_IN) < 0) {
qDebug("RxStats: Error setting IN direction %s: %s\n",
qPrintable(device_), pcap_geterr(handle_));
isDirectional_ = false;
}
#endif
if (pcap_compile(handle_, &bpf, qPrintable(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) {
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: {
uint guid;
if (SignProtocol::packetGuid(data, hdr->caplen, &guid)) {
streamStats_[guid].rx_pkts++;
streamStats_[guid].rx_bytes += hdr->caplen;
}
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;
}
bool PcapRxStats::start()
{
if (state_ == kRunning) {
qWarning("RxStats start requested but is already running!");
goto _exit;
}
state_ = kNotStarted;
QThread::start();
while (state_ == kNotStarted)
QThread::msleep(10);
_exit:
return true;
}
bool PcapRxStats::stop()
{
if (state_ == kRunning) {
stop_ = true;
while (state_ == kRunning)
QThread::msleep(10);
}
else
qWarning("RxStats stop requested but is not running!");
return true;
}
bool PcapRxStats::isRunning()
{
return (state_ == kRunning);
}
bool PcapRxStats::isDirectional()
{
return isDirectional_;
}

54
server/pcaprxstats.h Normal file
View File

@ -0,0 +1,54 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _PCAP_RX_STATS_H
#define _PCAP_RX_STATS_H
#include "streamstats.h"
#include <QThread>
#include <pcap.h>
class PcapRxStats: public QThread
{
public:
PcapRxStats(const char *device, StreamStats &portStreamStats);
pcap_t* handle();
void run();
bool start();
bool stop();
bool isRunning();
bool isDirectional();
private:
enum State {
kNotStarted,
kRunning,
kFinished
};
QString device_;
StreamStats &streamStats_;
volatile bool stop_;
pcap_t *handle_;
volatile State state_;
bool isDirectional_;
};
#endif

130
server/pcaptransmitter.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "pcaptransmitter.h"
PcapTransmitter::PcapTransmitter(
const char *device,
StreamStats &portStreamStats)
: streamStats_(portStreamStats), txThread_(device)
{
adjustRxStreamStats_ = false;
memset(&stats_, 0, sizeof(stats_));
txStats_.setTxThreadStats(&stats_);
txStats_.start(); // TODO: alongwith user transmit start
txThread_.setStats(&stats_);
connect(&txThread_, SIGNAL(finished()), SLOT(updateTxThreadStreamStats()));
}
PcapTransmitter::~PcapTransmitter()
{
txStats_.stop(); // TODO: alongwith user transmit stop
}
bool PcapTransmitter::setRateAccuracy(
AbstractPort::Accuracy accuracy)
{
return txThread_.setRateAccuracy(accuracy);
}
void PcapTransmitter::adjustRxStreamStats(bool enable)
{
adjustRxStreamStats_ = enable;
}
bool PcapTransmitter::setStreamStatsTracking(bool enable)
{
return txThread_.setStreamStatsTracking(enable);
}
void PcapTransmitter::clearPacketList()
{
txThread_.clearPacketList();
}
void PcapTransmitter::loopNextPacketSet(
qint64 size,
qint64 repeats,
long repeatDelaySec,
long repeatDelayNsec)
{
txThread_.loopNextPacketSet(size, repeats, repeatDelaySec, repeatDelayNsec);
}
bool PcapTransmitter::appendToPacketList(long sec, long nsec,
const uchar *packet, int length)
{
return txThread_.appendToPacketList(sec, nsec, packet, length);
}
void PcapTransmitter::setHandle(pcap_t *handle)
{
txThread_.setHandle(handle);
}
void PcapTransmitter::setPacketListLoopMode(
bool loop,
quint64 secDelay,
quint64 nsecDelay)
{
txThread_.setPacketListLoopMode(loop, secDelay, nsecDelay);
}
void PcapTransmitter::useExternalStats(AbstractPort::PortStats *stats)
{
txStats_.useExternalStats(stats);
}
void PcapTransmitter::start()
{
txThread_.start();
}
void PcapTransmitter::stop()
{
txThread_.stop();
}
bool PcapTransmitter::isRunning()
{
return txThread_.isRunning();
}
void PcapTransmitter::updateTxThreadStreamStats()
{
PcapTxThread *txThread = dynamic_cast<PcapTxThread*>(sender());
const StreamStats& threadStreamStats = txThread->streamStats();
StreamStatsIterator i(threadStreamStats);
while (i.hasNext())
{
i.next();
uint guid = i.key();
StreamStatsTuple sst = i.value();
streamStats_[guid].tx_pkts += sst.tx_pkts;
streamStats_[guid].tx_bytes += sst.tx_bytes;
if (adjustRxStreamStats_) {
streamStats_[guid].rx_pkts -= sst.tx_pkts;
streamStats_[guid].rx_bytes -= sst.tx_bytes;
}
}
txThread->clearStreamStats();
}

63
server/pcaptransmitter.h Normal file
View File

@ -0,0 +1,63 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _PCAP_TRANSMITTER_H
#define _PCAP_TRANSMITTER_H
#include "abstractport.h"
#include "pcaptxstats.h"
#include "pcaptxthread.h"
#include "statstuple.h"
class PcapTransmitter : QObject
{
Q_OBJECT
public:
PcapTransmitter(const char *device, StreamStats &portStreamStats);
~PcapTransmitter();
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
bool setStreamStatsTracking(bool enable);
void adjustRxStreamStats(bool enable);
void clearPacketList();
void loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec);
bool appendToPacketList(long sec, long usec, const uchar *packet,
int length);
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
void setHandle(pcap_t *handle);
void useExternalStats(AbstractPort::PortStats *stats);
void start();
void stop();
bool isRunning();
private slots:
void updateTxThreadStreamStats();
private:
StreamStats &streamStats_;
PcapTxThread txThread_;
PcapTxStats txStats_;
StatsTuple stats_;
bool adjustRxStreamStats_;
};
#endif

86
server/pcaptxstats.cpp Normal file
View File

@ -0,0 +1,86 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "pcaptxstats.h"
#include "pcaptxstats.h"
#include "statstuple.h"
PcapTxStats::PcapTxStats()
{
txThreadStats_ = NULL;
stats_ = new AbstractPort::PortStats;
usingInternalStats_ = true;
stop_ = false;
}
PcapTxStats::~PcapTxStats()
{
if (usingInternalStats_)
delete stats_;
}
void PcapTxStats::setTxThreadStats(StatsTuple *stats)
{
txThreadStats_ = stats;
}
void PcapTxStats::useExternalStats(AbstractPort::PortStats *stats)
{
if (usingInternalStats_)
delete stats_;
stats_ = stats;
usingInternalStats_ = false;
}
void PcapTxStats::start()
{
QThread::start();
while (!isRunning())
QThread::msleep(10);
}
void PcapTxStats::stop()
{
stop_ = true;
while (isRunning())
QThread::msleep(10);
}
void PcapTxStats::run()
{
Q_ASSERT(txThreadStats_);
qDebug("txStats: collection start");
while (1) {
stats_->txPkts = txThreadStats_->pkts;
stats_->txBytes = txThreadStats_->bytes;
if (stop_)
break;
QThread::msleep(1000);
}
stop_ = false;
qDebug("txStats: collection end");
}

53
server/pcaptxstats.h Normal file
View File

@ -0,0 +1,53 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _PCAP_TX_STATS_H
#define _PCAP_TX_STATS_H
#include "abstractport.h"
#include <QThread>
class StatsTuple;
class PcapTxStats : public QThread
{
public:
PcapTxStats();
~PcapTxStats();
void setTxThreadStats(StatsTuple *stats);
void useExternalStats(AbstractPort::PortStats *stats);
void start();
void stop();
private:
void run();
StatsTuple *txThreadStats_;
bool usingInternalStats_;
AbstractPort::PortStats *stats_;
volatile bool stop_;
};
#endif

596
server/pcaptxthread.cpp Normal file
View File

@ -0,0 +1,596 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "pcaptransmitter.h"
#include "statstuple.h"
#include "timestamp.h"
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
PcapTxThread::PcapTxThread(const char *device)
{
char errbuf[PCAP_ERRBUF_SIZE] = "";
#ifdef Q_OS_WIN32
LARGE_INTEGER freq;
if (QueryPerformanceFrequency(&freq))
gTicksFreq = freq.QuadPart;
else
Q_ASSERT_X(false, "PcapTxThread::PcapTxThread",
"This Win32 platform does not support performance counter");
#endif
state_ = kNotStarted;
stop_ = false;
trackStreamStats_ = false;
clearPacketList();
handle_ = pcap_open_live(device, 64 /* FIXME */, 0, 1000 /* ms */, errbuf);
if (handle_ == NULL)
goto _open_error;
usingInternalHandle_ = true;
stats_ = NULL;
return;
_open_error:
qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf);
usingInternalHandle_ = false;
}
PcapTxThread::~PcapTxThread()
{
if (usingInternalHandle_)
pcap_close(handle_);
}
bool PcapTxThread::setRateAccuracy(
AbstractPort::Accuracy accuracy)
{
switch (accuracy) {
case AbstractPort::kHighAccuracy:
udelayFn_ = udelay;
qWarning("%s: rate accuracy set to High - busy wait", __FUNCTION__);
break;
case AbstractPort::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;
}
bool PcapTxThread::setStreamStatsTracking(bool enable)
{
trackStreamStats_ = enable;
return true;
}
void PcapTxThread::clearPacketList()
{
Q_ASSERT(!isRunning());
// \todo lock for packetSequenceList
while(packetSequenceList_.size())
delete packetSequenceList_.takeFirst();
currentPacketSequence_ = NULL;
repeatSequenceStart_ = -1;
repeatSize_ = 0;
packetCount_ = 0;
packetListSize_ = 0;
returnToQIdx_ = -1;
setPacketListLoopMode(false, 0, 0);
}
void PcapTxThread::loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec)
{
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
currentPacketSequence_->repeatCount_ = repeats;
currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6)
+ repeatDelayNsec/1000;
repeatSequenceStart_ = packetSequenceList_.size();
repeatSize_ = size;
packetCount_ = 0;
packetSequenceList_.append(currentPacketSequence_);
}
bool PcapTxThread::appendToPacketList(long sec, long nsec,
const uchar *packet, int length)
{
bool op = true;
pcap_pkthdr pktHdr;
pktHdr.caplen = pktHdr.len = length;
pktHdr.ts.tv_sec = sec;
pktHdr.ts.tv_usec = nsec/1000;
if (currentPacketSequence_ == NULL ||
!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
{
if (currentPacketSequence_ != NULL)
{
long usecs;
usecs = (pktHdr.ts.tv_sec
- currentPacketSequence_->lastPacket_->ts.tv_sec)
* long(1e6);
usecs += (pktHdr.ts.tv_usec
- currentPacketSequence_->lastPacket_->ts.tv_usec);
currentPacketSequence_->usecDelay_ = usecs;
}
//! \todo (LOW): calculate sendqueue size
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
packetSequenceList_.append(currentPacketSequence_);
// Validate that the pkt will fit inside the new currentSendQueue_
Q_ASSERT(currentPacketSequence_->hasFreeSpace(
sizeof(pcap_pkthdr) + length));
}
if (currentPacketSequence_->appendPacket(&pktHdr, (u_char*) packet) < 0)
{
op = false;
}
packetCount_++;
packetListSize_ += repeatSize_ ?
currentPacketSequence_->repeatCount_ : 1;
if (repeatSize_ > 0 && packetCount_ == repeatSize_)
{
qDebug("repeatSequenceStart_=%d, repeatSize_ = %llu",
repeatSequenceStart_, repeatSize_);
// Set the packetSequence repeatSize
Q_ASSERT(repeatSequenceStart_ >= 0);
Q_ASSERT(repeatSequenceStart_ < packetSequenceList_.size());
if (currentPacketSequence_ != packetSequenceList_[repeatSequenceStart_])
{
PacketSequence *start = packetSequenceList_[repeatSequenceStart_];
currentPacketSequence_->usecDelay_ = start->usecDelay_;
start->usecDelay_ = 0;
start->repeatSize_ =
packetSequenceList_.size() - repeatSequenceStart_;
}
repeatSize_ = 0;
// End current pktSeq and trigger a new pktSeq allocation for next pkt
currentPacketSequence_ = NULL;
}
return op;
}
void PcapTxThread::setPacketListLoopMode(
bool loop,
quint64 secDelay,
quint64 nsecDelay)
{
returnToQIdx_ = loop ? 0 : -1;
loopDelay_ = secDelay*long(1e6) + nsecDelay/1000;
}
void PcapTxThread::setHandle(pcap_t *handle)
{
if (usingInternalHandle_)
pcap_close(handle_);
handle_ = handle;
usingInternalHandle_ = false;
}
void PcapTxThread::setStats(StatsTuple *stats)
{
stats_ = stats;
}
const StreamStats& PcapTxThread::streamStats()
{
return streamStats_;
}
void PcapTxThread::clearStreamStats()
{
streamStats_.clear();
}
void PcapTxThread::run()
{
//! \todo (MED) Stream Mode - continuous: define before implement
// NOTE1: We can't use pcap_sendqueue_transmit() directly even on Win32
// 'coz of 2 reasons - there's no way of stopping it before all packets
// in the sendQueue are sent out and secondly, stats are available only
// when all packets have been sent - no periodic updates
//
// NOTE2: Transmit on the Rx Handle so that we can receive it back
// on the Tx Handle to do stats
//
// NOTE3: Update pcapExtra counters - port TxStats will be updated in the
// 'stats callback' function so that both Rx and Tx stats are updated
// together
const int kSyncTransmit = 1;
int i;
long overHead = 0; // overHead should be negative or zero
qDebug("packetSequenceList_.size = %d", packetSequenceList_.size());
if (packetSequenceList_.size() <= 0)
goto _exit;
for(i = 0; i < packetSequenceList_.size(); i++) {
qDebug("sendQ[%d]: rptCnt = %d, rptSz = %d, usecDelay = %ld", i,
packetSequenceList_.at(i)->repeatCount_,
packetSequenceList_.at(i)->repeatSize_,
packetSequenceList_.at(i)->usecDelay_);
qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld", i,
packetSequenceList_.at(i)->packets_,
packetSequenceList_.at(i)->usecDuration_);
}
lastStats_ = *stats_; // used for stream stats
state_ = kRunning;
i = 0;
while (i < packetSequenceList_.size())
{
_restart:
int rptSz = packetSequenceList_.at(i)->repeatSize_;
int rptCnt = packetSequenceList_.at(i)->repeatCount_;
for (int j = 0; j < rptCnt; j++)
{
for (int k = 0; k < rptSz; k++)
{
int ret;
PacketSequence *seq = packetSequenceList_.at(i+k);
#ifdef Q_OS_WIN32
TimeStamp ovrStart, ovrEnd;
if (seq->usecDuration_ <= long(1e6)) // 1s
{
getTimeStamp(&ovrStart);
ret = pcap_sendqueue_transmit(handle_,
seq->sendQueue_, kSyncTransmit);
if (ret >= 0)
{
stats_->pkts += seq->packets_;
stats_->bytes += seq->bytes_;
getTimeStamp(&ovrEnd);
overHead += seq->usecDuration_
- udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
}
if (stop_)
ret = -2;
}
else
{
ret = sendQueueTransmit(handle_, seq->sendQueue_,
overHead, kSyncTransmit);
}
#else
ret = sendQueueTransmit(handle_, seq->sendQueue_,
overHead, kSyncTransmit);
#endif
if (ret >= 0)
{
long usecs = seq->usecDelay_ + overHead;
if (usecs > 0)
{
(*udelayFn_)(usecs);
overHead = 0;
}
else
overHead = usecs;
}
else
{
qDebug("error %d in sendQueueTransmit()", ret);
qDebug("overHead = %ld", overHead);
stop_ = false;
goto _exit;
}
}
}
// Move to the next Packet Set
i += rptSz;
}
if (returnToQIdx_ >= 0)
{
long usecs = loopDelay_ + overHead;
if (usecs > 0)
{
(*udelayFn_)(usecs);
overHead = 0;
}
else
overHead = usecs;
i = returnToQIdx_;
goto _restart;
}
_exit:
if (trackStreamStats_)
updateStreamStats();
state_ = kFinished;
}
void PcapTxThread::start()
{
// FIXME: return error
if (state_ == kRunning) {
qWarning("Transmit start requested but is already running!");
return;
}
state_ = kNotStarted;
QThread::start();
while (state_ == kNotStarted)
QThread::msleep(10);
}
void PcapTxThread::stop()
{
if (state_ == kRunning) {
stop_ = true;
while (state_ == kRunning)
QThread::msleep(10);
}
else {
// FIXME: return error
qWarning("Transmit stop requested but is not running!");
return;
}
}
bool PcapTxThread::isRunning()
{
return (state_ == kRunning);
}
int PcapTxThread::sendQueueTransmit(pcap_t *p,
pcap_send_queue *queue, long &overHead, int sync)
{
TimeStamp ovrStart, ovrEnd;
struct timeval ts;
struct pcap_pkthdr *hdr = (struct pcap_pkthdr*) queue->buffer;
char *end = queue->buffer + queue->len;
ts = hdr->ts;
getTimeStamp(&ovrStart);
while((char*) hdr < end)
{
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
int pktLen = hdr->caplen;
if (sync)
{
long usec = (hdr->ts.tv_sec - ts.tv_sec) * 1000000 +
(hdr->ts.tv_usec - ts.tv_usec);
getTimeStamp(&ovrEnd);
overHead -= udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
usec += overHead;
if (usec > 0)
{
(*udelayFn_)(usec);
overHead = 0;
}
else
overHead = usec;
ts = hdr->ts;
getTimeStamp(&ovrStart);
}
Q_ASSERT(pktLen > 0);
pcap_sendpacket(p, pkt, pktLen);
stats_->pkts++;
stats_->bytes += pktLen;
// Step to the next packet in the buffer
hdr = (struct pcap_pkthdr*) (pkt + pktLen);
pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr)); // FIXME: superfluous?
if (stop_)
{
return -2;
}
}
return 0;
}
void PcapTxThread::updateStreamStats()
{
// If no packets in list, nothing to be done
if (!packetListSize_)
return;
// Get number of tx packets sent during last transmit
quint64 pkts = stats_->pkts > lastStats_.pkts ?
stats_->pkts - lastStats_.pkts :
stats_->pkts + (ULLONG_MAX - lastStats_.pkts);
// Calculate -
// number of complete repeats of packetList_
// => each PacketSet in the packetList is repeated these many times
// number of pkts sent in last partial repeat of packetList_
// - This encompasses 0 or more potentially partial PacketSets
// XXX: Note for the above, we consider a PacketSet to include its
// own repeats within itself
int c = pkts/packetListSize_;
int d = pkts%packetListSize_;
qDebug("%s:", __FUNCTION__);
qDebug("txPkts = %" PRIu64, pkts);
qDebug("packetListSize_ = %" PRIu64, packetListSize_);
qDebug("c = %d, d = %d\n", c, d);
int i;
if (!c)
goto _last_repeat;
i = 0;
while (i < packetSequenceList_.size()) {
PacketSequence *seq = packetSequenceList_.at(i);
int rptSz = seq->repeatSize_;
int rptCnt = seq->repeatCount_;
for (int k = 0; k < rptSz; k++) {
seq = packetSequenceList_.at(i+k);
StreamStatsIterator iter(seq->streamStatsMeta_);
while (iter.hasNext()) {
iter.next();
uint guid = iter.key();
StreamStatsTuple ssm = iter.value();
streamStats_[guid].tx_pkts += c * rptCnt * ssm.tx_pkts;
streamStats_[guid].tx_bytes += c * rptCnt * ssm.tx_bytes;
}
}
// Move to the next Packet Set
i += rptSz;
}
_last_repeat:
if (!d)
goto _done;
i = 0;
while (i < packetSequenceList_.size()) {
PacketSequence *seq = packetSequenceList_.at(i);
int rptSz = seq->repeatSize_;
int rptCnt = seq->repeatCount_;
for (int j = 0; j < rptCnt; j++) {
for (int k = 0; k < rptSz; k++) {
seq = packetSequenceList_.at(i+k);
Q_ASSERT(seq->packets_);
if (d >= seq->packets_) {
// All packets of this seq were sent
StreamStatsIterator iter(seq->streamStatsMeta_);
while (iter.hasNext()) {
iter.next();
uint guid = iter.key();
StreamStatsTuple ssm = iter.value();
streamStats_[guid].tx_pkts += ssm.tx_pkts;
streamStats_[guid].tx_bytes += ssm.tx_bytes;
}
d -= seq->packets_;
}
else { // (d < seq->packets_)
// not all packets of this seq were sent, so we need to
// traverse this seq upto 'd' pkts, parse guid from the
// packet and update streamStats
struct pcap_pkthdr *hdr =
(struct pcap_pkthdr*) seq->sendQueue_->buffer;
char *end = seq->sendQueue_->buffer + seq->sendQueue_->len;
while(d && ((char*) hdr < end)) {
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
uint guid;
if (SignProtocol::packetGuid(pkt, hdr->caplen, &guid)) {
streamStats_[guid].tx_pkts++;
streamStats_[guid].tx_bytes += hdr->caplen;
}
// Step to the next packet in the buffer
hdr = (struct pcap_pkthdr*) (pkt + hdr->caplen);
d--;
}
Q_ASSERT(d == 0);
goto _done;
}
}
}
// Move to the next Packet Set
i += rptSz;
}
_done:
return;
}
void PcapTxThread::udelay(unsigned long usec)
{
#if defined(Q_OS_WIN32)
LARGE_INTEGER tgtTicks;
LARGE_INTEGER curTicks;
QueryPerformanceCounter(&curTicks);
tgtTicks.QuadPart = curTicks.QuadPart + (usec*gTicksFreq)/1000000;
while (curTicks.QuadPart < tgtTicks.QuadPart)
QueryPerformanceCounter(&curTicks);
#elif defined(Q_OS_LINUX)
struct timeval delay, target, now;
//qDebug("usec delay = %ld", usec);
delay.tv_sec = 0;
delay.tv_usec = usec;
while (delay.tv_usec >= 1000000)
{
delay.tv_sec++;
delay.tv_usec -= 1000000;
}
gettimeofday(&now, NULL);
timeradd(&now, &delay, &target);
do {
gettimeofday(&now, NULL);
} while (timercmp(&now, &target, <));
#else
QThread::usleep(usec);
#endif
}

98
server/pcaptxthread.h Normal file
View File

@ -0,0 +1,98 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _PCAP_TX_THREAD_H
#define _PCAP_TX_THREAD_H
#include "abstractport.h"
#include "packetsequence.h"
#include "statstuple.h"
#include <QThread>
#include <pcap.h>
class PcapTxThread: public QThread
{
public:
PcapTxThread(const char *device);
~PcapTxThread();
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
bool setStreamStatsTracking(bool enable);
void clearPacketList();
void loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec);
bool appendToPacketList(long sec, long usec, const uchar *packet,
int length);
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
void setHandle(pcap_t *handle);
void setStats(StatsTuple *stats);
const StreamStats& streamStats();
void clearStreamStats();
void run();
void start();
void stop();
bool isRunning();
private:
enum State
{
kNotStarted,
kRunning,
kFinished
};
static void udelay(unsigned long usec);
int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead,
int sync);
void updateStreamStats();
// Intermediate state variables used while building the packet list
PacketSequence *currentPacketSequence_;
int repeatSequenceStart_;
quint64 repeatSize_;
quint64 packetCount_;
QList<PacketSequence*> packetSequenceList_;
quint64 packetListSize_; // count of pkts in packet List including repeats
int returnToQIdx_;
quint64 loopDelay_;
void (*udelayFn_)(unsigned long);
bool usingInternalHandle_;
pcap_t *handle_;
volatile bool stop_;
volatile State state_;
bool trackStreamStats_;
StatsTuple *stats_;
StatsTuple lastStats_;
StreamStats streamStats_;
};
#endif

31
server/statstuple.h Normal file
View File

@ -0,0 +1,31 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _STATS_TUPLE_H
#define _STATS_TUPLE_H
#include <QtGlobal>
struct StatsTuple
{
quint64 pkts;
quint64 bytes;
};
#endif

36
server/streamstats.h Normal file
View File

@ -0,0 +1,36 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _STREAM_STATS_H
#define _STREAM_STATS_H
#include <QHash>
struct StreamStatsTuple
{
quint64 rx_pkts;
quint64 rx_bytes;
quint64 tx_pkts;
quint64 tx_bytes;
};
typedef QHash<uint, StreamStatsTuple> StreamStats;
typedef QHashIterator<uint, StreamStatsTuple> StreamStatsIterator;
#endif

72
server/timestamp.h Normal file
View File

@ -0,0 +1,72 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _TIMESTAMP_H
#define _TIMESTAMP_H
#include <QtGlobal>
#if defined(Q_OS_LINUX)
typedef struct timeval TimeStamp;
static void inline getTimeStamp(TimeStamp *stamp)
{
gettimeofday(stamp, NULL);
}
// Returns time diff in usecs between end and start
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
{
struct timeval diff;
long usecs;
timersub(end, start, &diff);
usecs = diff.tv_usec;
if (diff.tv_sec)
usecs += diff.tv_sec*1e6;
return usecs;
}
#elif defined(Q_OS_WIN32)
static quint64 gTicksFreq;
typedef LARGE_INTEGER TimeStamp;
static void inline getTimeStamp(TimeStamp* stamp)
{
QueryPerformanceCounter(stamp);
}
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
{
if (end->QuadPart >= start->QuadPart)
return (end->QuadPart - start->QuadPart)*long(1e6)/gTicksFreq;
else
{
// FIXME: incorrect! what's the max value for this counter before
// it rolls over?
return (start->QuadPart)*long(1e6)/gTicksFreq;
}
}
#else
typedef int TimeStamp;
static void inline getTimeStamp(TimeStamp*) {}
static long inline udiffTimeStamp(const TimeStamp*, const TimeStamp*) { return 0; }
#endif
#endif

601
test/streamstatstest.py Normal file
View File

@ -0,0 +1,601 @@
#! /usr/bin/env python
# standard modules
import ipaddress
import logging
import os
import pytest
import random
import re
import subprocess
import sys
import time
import pytest
from fabric.api import run, env, sudo
from utils import get_tshark
sys.path.insert(1, '../binding')
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.payload_pb2 import payload, Payload
from protocols.sign_pb2 import sign
# Convenience class to interwork with OstEmul::Ip6Address() and
# the python ipaddress module
# FIXME: move to a common module for reuse and remove duplication in other
# scripts
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()
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.gateway.hi = int(gateway) >> 64
self.gateway.lo = int(gateway) & 0xffffffffffffffff
use_defaults = True
# initialize defaults
host_name = '127.0.0.1'
# initialize defaults - DUT
env.use_shell = False
env.user = 'tc'
env.password = 'tc'
env.host_string = 'localhost:50022'
tshark = get_tshark(minversion = '2')
# 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(' | |X--<-->--X|-+ |')
print(' | Drone | | | DUT |')
print(' | |Y--<-->--Y|-+ |')
print(' +-------+ +-------+')
print('')
print('Drone has 2 ports connected to DUT. Packets sent on port X')
print('are expected to be forwarded by the DUT and received back on')
print('port Y and vice versa')
print('')
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
# FIXME: get inputs for dut x/y ports
@pytest.fixture(scope='module')
def drone(request):
"""Baseline Configuration for all testcases in this module"""
drn = DroneProxy(host_name)
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()
port_config_list = drone.getPortConfig(port_id_list)
assert len(port_config_list.port) != 0
# print port list and find default X/Y ports
ports.x_num = -1
ports.y_num = -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 X/Y port
if ('vhost' in port.name or 'oracle' in port.description.lower()):
if ports.x_num < 0:
ports.x_num = port.port_id.id
elif ports.y_num < 0:
ports.y_num = port.port_id.id
if ('eth1' in port.name):
ports.x_num = port.port_id.id
if ('eth2' in port.name):
ports.y_num = port.port_id.id
assert ports.x_num >= 0
assert ports.y_num >= 0
print('Using port %d as port X' % ports.x_num)
print('Using port %d as port Y' % ports.y_num)
ports.x = ost_pb.PortIdList()
ports.x.port_id.add().id = ports.x_num;
ports.y = ost_pb.PortIdList()
ports.y.port_id.add().id = ports.y_num;
# Enable stream stats on ports
portConfig = ost_pb.PortConfigList()
portConfig.port.add().port_id.id = ports.x_num;
portConfig.port[0].is_tracking_stream_stats = True;
portConfig.port.add().port_id.id = ports.y_num;
portConfig.port[1].is_tracking_stream_stats = True;
print('Enabling Stream Stats tracking on ports X and Y');
drone.modifyPort(portConfig);
return ports
@pytest.fixture(scope='module')
def emul_ports(request, drone, ports):
emul_ports = ost_pb.PortIdList()
emul_ports.port_id.add().id = ports.x.port_id[0].id;
emul_ports.port_id.add().id = ports.y.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
# use these same devices
# ----------------------------------------------------------------- #
# delete existing devices, if any, on tx port
dgid_list.x = drone.getDeviceGroupIdList(ports.x.port_id[0])
drone.deleteDeviceGroup(dgid_list.x)
# add a emulated device group on port X
dgid_list.x = ost_pb.DeviceGroupIdList()
dgid_list.x.port_id.CopyFrom(ports.x.port_id[0])
dgid_list.x.device_group_id.add().id = 1
log.info('adding X device_group %d' % dgid_list.x.device_group_id[0].id)
drone.addDeviceGroup(dgid_list.x)
# configure the X device(s)
devgrp_cfg = ost_pb.DeviceGroupConfigList()
devgrp_cfg.port_id.CopyFrom(ports.x.port_id[0])
dg = devgrp_cfg.device_group.add()
dg.device_group_id.id = dgid_list.x.device_group_id[0].id
dg.core.name = "HostX"
dg.device_count = 5
dg.Extensions[emul.mac].address = 0x000102030a01
dg.Extensions[emul.ip4].address = 0x0a0a0165
dg.Extensions[emul.ip4].prefix_length = 24
dg.Extensions[emul.ip4].default_gateway = 0x0a0a0101
ip6addr = ip6_address('1234:1::65/96')
dg.Extensions[emul.ip6].address.CopyFrom(ip6addr.ip6)
dg.Extensions[emul.ip6].prefix_length = ip6addr.prefixlen
dg.Extensions[emul.ip6].default_gateway.CopyFrom(ip6addr.gateway)
drone.modifyDeviceGroup(devgrp_cfg)
# delete existing devices, if any, on Y port
dgid_list.y = drone.getDeviceGroupIdList(ports.y.port_id[0])
drone.deleteDeviceGroup(dgid_list.y)
# add a emulated device group on port Y
dgid_list.y = ost_pb.DeviceGroupIdList()
dgid_list.y.port_id.CopyFrom(ports.y.port_id[0])
dgid_list.y.device_group_id.add().id = 1
log.info('adding Y device_group %d' % dgid_list.y.device_group_id[0].id)
drone.addDeviceGroup(dgid_list.y)
# configure the Y device(s)
devgrp_cfg = ost_pb.DeviceGroupConfigList()
devgrp_cfg.port_id.CopyFrom(ports.y.port_id[0])
dg = devgrp_cfg.device_group.add()
dg.device_group_id.id = dgid_list.y.device_group_id[0].id
dg.core.name = "HostY"
dg.Extensions[emul.mac].address = 0x000102030b01
dg.Extensions[emul.ip4].address = 0x0a0a0265
dg.Extensions[emul.ip4].prefix_length = 24
dg.Extensions[emul.ip4].default_gateway = 0x0a0a0201
ip6addr = ip6_address('1234:2::65/96')
dg.Extensions[emul.ip6].address.CopyFrom(ip6addr.ip6)
dg.Extensions[emul.ip6].prefix_length = ip6addr.prefixlen
dg.Extensions[emul.ip6].default_gateway.CopyFrom(ip6addr.gateway)
drone.modifyDeviceGroup(devgrp_cfg)
def fin():
dgid_list = drone.getDeviceGroupIdList(ports.x.port_id[0])
drone.deleteDeviceGroup(dgid_list)
dgid_list = drone.getDeviceGroupIdList(ports.y.port_id[0])
drone.deleteDeviceGroup(dgid_list)
request.addfinalizer(fin)
return dgid_list
@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')
sudo('sysctl -w net.ipv6.conf.all.forwarding=1')
@pytest.fixture(scope='module')
def dut_ports(request):
dut_ports.x = 'eth1'
dut_ports.y = 'eth2'
# delete all configuration on the DUT interfaces
sudo('ip address flush dev ' + dut_ports.y)
sudo('ip address flush dev ' + dut_ports.x)
return dut_ports
@pytest.fixture
def dut_ip(request, dut_ports):
sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.x)
sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.y)
sudo('ip -6 address add 1234:1::1/96 dev ' + dut_ports.x)
sudo('ip -6 address add 1234:2::1/96 dev ' + dut_ports.y)
def fin():
sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.x)
sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.y)
sudo('ip -6 address delete 1234:1::1/96 dev ' + dut_ports.x)
sudo('ip -6 address delete 1234:2::1/96 dev ' + dut_ports.y)
request.addfinalizer(fin)
@pytest.fixture(scope='module')
def stream_clear(request, drone, ports):
# delete existing streams, if any, on all ports
sid_list = drone.getStreamIdList(ports.x.port_id[0])
drone.deleteStream(sid_list)
sid_list = drone.getStreamIdList(ports.y.port_id[0])
drone.deleteStream(sid_list)
@pytest.fixture
def stream(request, drone, ports, sign_stream_cfg):
sscfg = sign_stream_cfg
num_streams = sscfg['num_streams']
# add stream(s)
stream_id = ost_pb.StreamIdList()
stream_id.port_id.CopyFrom(ports.x.port_id[0])
for i in range(num_streams):
stream_id.stream_id.add().id = 1+i
log.info('adding X stream(s) ' + str(stream_id))
drone.addStream(stream_id)
# configure the stream(s)
stream_cfg = ost_pb.StreamConfigList()
stream_cfg.port_id.CopyFrom(ports.x.port_id[0])
for i in range(num_streams):
s = stream_cfg.stream.add()
s.stream_id.id = stream_id.stream_id[i].id
s.core.is_enabled = True
# On Win32, packets in a seq that take less than 1s to transmit
# are transmitted in one shot using pcap_send_queue; this means that
# stopTransmit() cannot stop them in the middle of the sequence and
# stop will happen only at the pktSeq boundary
# Since we want to test cases where stop is trigerred in the middle of
# a sequence, we set pps < 1000
# FIXME: for some reason values such as 100pps is also leading to
# stop only at seq boundary - need to debug
s.control.packets_per_sec = 523
# setup stream protocols
p = s.protocol.add()
s.control.num_packets = sscfg['num_pkts'][i]
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
s.protocol.add().protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
p = s.protocol.add()
if i & 0x1: # odd numbered stream
p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
ip = p.Extensions[ip4]
ip.src_ip = 0x0a0a0165
ip.dst_ip = 0x0a0a0265
if sscfg['num_var_pkts'][i]:
ip4.src_ip_mode = Ip4.e_im_inc_host
ip4.src_ip_count = sscfg['num_var_pkts'][i]
s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber
else: # even numbered stream
s.core.frame_len = 96 # IPv6 needs a larger frame
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
ip6addr = ip6_address('1234:2::65/96')
ip.dst_addr_hi = ip6addr.ip6.hi
ip.dst_addr_lo = ip6addr.ip6.lo
if sscfg['num_var_pkts'][i]:
ip.dst_addr_mode = Ip6.kIncHost
ip.dst_addr_count = sscfg['num_var_pkts'][i]
ip.dst_addr_prefix = ip6addr.prefixlen
s.protocol.add().protocol_id.id = ost_pb.Protocol.kTcpFieldNumber
s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber
if sscfg['guid'][i] > 0:
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kSignFieldNumber
p.Extensions[sign].stream_guid = sscfg['guid'][i]
if sscfg['loop']:
stream_cfg.stream[-1].control.next = ost_pb.StreamControl.e_nw_goto_id
drone.modifyStream(stream_cfg)
def fin():
# delete streams
log.info('deleting tx_stream ' + str(stream_id))
drone.deleteStream(stream_id)
request.addfinalizer(fin)
return stream_cfg
@pytest.fixture(scope='module')
def stream_guids(request, drone, ports):
stream_guids = ost_pb.StreamGuidList()
stream_guids.port_id_list.port_id.add().id = ports.x.port_id[0].id;
stream_guids.port_id_list.port_id.add().id = ports.y.port_id[0].id;
return stream_guids
# ================================================================= #
# ----------------------------------------------------------------- #
# TEST CASES
# ----------------------------------------------------------------- #
# ================================================================= #
@pytest.mark.parametrize('sign_stream_cfg', [
# Min Seq Size = 256 on Win32 and 16 on other platforms, keep this in
# mind while configuring the below num_pkts
# Verify tx pkts = multiple complete repeats of packetList + no partials
{'num_streams': 3, 'num_pkts': [10, 270, 512], 'num_var_pkts': [0, 0, 0],
'guid': [-1, 101, 102], 'loop': False},
# Verify tx pkts = multiple repeats of packetList + partial repeat
{'num_streams': 3, 'num_pkts': [10, 276, 510], 'num_var_pkts': [0, 0, 0],
'guid': [-1, 101, 102], 'loop': True},
# Verify tx pkts = first seq partial
{'num_streams': 1, 'num_pkts': [12], 'num_var_pkts': [0],
'guid': [101], 'loop': True},
])
def test_unidir(drone, ports, dut, dut_ports, dut_ip, emul_ports, dgid_list,
sign_stream_cfg, stream_clear, stream, stream_guids):
""" TESTCASE: Verify that uni-directional stream stats are correct for a
single signed stream X --> Y
DUT
/.1 \.1
/ \
10.10.1/24 10.10.2/24
1234::1/96 1234::2/96
/ \
/.101-105 \.101-105
HostX HostY
"""
# calculate tx pkts
total_tx_pkts = 0
for pkts in sign_stream_cfg['num_pkts']:
total_tx_pkts += pkts
num_sign_streams = sum(1 for i in sign_stream_cfg['guid'] if i > 0)
# clear port X/Y stats
log.info('clearing stats')
drone.clearStats(ports.x)
drone.clearStats(ports.y)
# clear and verify stream strats are indeed cleared
drone.clearStreamStats(stream_guids)
ssd = drone.getStreamStatsDict(stream_guids)
assert len(ssd.port) == 0
# resolve ARP/NDP on ports X/Y
log.info('resolving Neighbors on (X, Y) ports ...')
# wait for interface to do DAD? Otherwise we don't get replies for NS
# FIXME: find alternative to sleep
time.sleep(5)
drone.resolveDeviceNeighbors(emul_ports)
time.sleep(3)
# clear ARP/NDP cache before ping from DUT to devices
# - this helps set up ARP/NDP on DUT so that it doesn't age out
# while traffic is flowing
sudo('ip neigh flush all')
out = run('ping -c3 10.10.1.101', warn_only=True)
assert '100% packet loss' not in out
out = run('ping -c3 10.10.2.101', warn_only=True)
assert '100% packet loss' not in out
out = run('ping -6 -c3 1234:1::65', warn_only=True)
assert '100% packet loss' not in out
out = run('ping -6 -c3 1234:2::65', warn_only=True)
assert '100% packet loss' not in out
# FIXME: dump ARP/NDP table on devices and DUT
try:
#run('ip -6 neigh show')
drone.startCapture(ports.y)
drone.startCapture(ports.x)
time.sleep(1)
drone.startTransmit(ports.x)
log.info('waiting for transmit to finish ...')
time.sleep(random.randint(3, 10))
drone.stopTransmit(ports.x)
time.sleep(1)
drone.stopCapture(ports.x)
drone.stopCapture(ports.y)
#run('ip -6 neigh show')
# verify port stats
x_stats = drone.getStats(ports.x)
log.info('--> (x_stats)' + x_stats.__str__())
if not sign_stream_cfg['loop']:
assert(x_stats.port_stats[0].tx_pkts >= total_tx_pkts)
y_stats = drone.getStats(ports.y)
log.info('--> (y_stats)' + y_stats.__str__())
if not sign_stream_cfg['loop']:
assert(y_stats.port_stats[0].rx_pkts >= total_tx_pkts)
ssd = drone.getStreamStatsDict(stream_guids)
log.info('--> (stream stats)\n' + str(ssd))
assert len(ssd.port) == 2
assert len(ssd.port[ports.x_num].sguid) == num_sign_streams
assert len(ssd.port[ports.y_num].sguid) == num_sign_streams
assert len(ssd.sguid) == num_sign_streams
# dump X capture buffer
log.info('getting X capture buffer')
buff = drone.getCaptureBuffer(ports.x.port_id[0])
drone.saveCaptureBuffer(buff, 'capX.pcap')
#log.info('dumping X capture buffer')
#cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capX.pcap'])
#print(cap_pkts)
# get and verify each sign stream Tx stats from capture
for i in range(sign_stream_cfg['num_streams']):
guid = sign_stream_cfg['guid'][i]
if guid < 0:
continue
filter='frame[-9:9]==00.' \
+ re.sub('..', '\g<0>.', format(guid, '06x')) \
+ '61.1d.10.c0.da && !icmp && !icmpv6'
print(filter)
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capX.pcap',
'-Y', filter])
#log.info('dumping X capture buffer (filtered)')
#print(cap_pkts)
sign_stream_cnt = cap_pkts.count('\n')
log.info('guid %d tx cap count: %d' % (guid, sign_stream_cnt))
#log.info('--> (stream stats)\n' + str(ssd))
# verify tx stream stats from drone is same as that from capture
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
== sign_stream_cnt
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
== (sign_stream_cnt
* (stream.stream[i].core.frame_len - 4))
# verify tx stream stats from drone is same as configured
if not sign_stream_cfg['loop']:
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
== sign_stream_cfg['num_pkts'][i]
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
== (sign_stream_cfg['num_pkts'][i]
* (stream.stream[i].core.frame_len - 4))
os.remove('capX.pcap')
# dump Y capture buffer
log.info('getting Y capture buffer')
buff = drone.getCaptureBuffer(ports.y.port_id[0])
drone.saveCaptureBuffer(buff, 'capY.pcap')
#log.info('dumping Y capture buffer')
#cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capY.pcap'])
#print(cap_pkts)
# get and verify each sign stream Rx stats from capture
for i in range(sign_stream_cfg['num_streams']):
guid = sign_stream_cfg['guid'][i]
if guid < 0:
continue
filter='frame[-9:9]==00.' \
+ re.sub('..', '\g<0>.', format(guid, '06x')) \
+ '61.1d.10.c0.da && !icmp && !icmpv6'
print(filter)
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capY.pcap',
'-Y', filter])
#log.info('dumping X capture buffer (filtered)')
#print(cap_pkts)
sign_stream_cnt = cap_pkts.count('\n')
log.info('guid %d rx cap count: %d' % (guid, sign_stream_cnt))
#log.info('--> (stream stats)\n' + str(ssd))
# verify rx stream stats from drone is same as that from capture
assert ssd.port[ports.y_num].sguid[guid].rx_pkts \
== sign_stream_cnt
assert ssd.port[ports.y_num].sguid[guid].rx_bytes \
== (sign_stream_cnt
* (stream.stream[i].core.frame_len - 4))
# verify rx stream stats from drone is same as configured
if not sign_stream_cfg['loop']:
assert ssd.port[ports.y_num].sguid[guid].rx_pkts \
== sign_stream_cfg['num_pkts'][i]
assert ssd.port[ports.y_num].sguid[guid].rx_bytes \
== (sign_stream_cfg['num_pkts'][i]
* (stream.stream[i].core.frame_len - 4))
os.remove('capY.pcap')
# verify tx == rx
for i in range(sign_stream_cfg['num_streams']):
guid = sign_stream_cfg['guid'][i]
if guid < 0:
continue
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
== ssd.port[ports.y_num].sguid[guid].rx_pkts
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
== ssd.port[ports.y_num].sguid[guid].rx_bytes
assert ssd.sguid[guid].total.tx_pkts \
== ssd.sguid[guid].total.rx_pkts
assert ssd.sguid[guid].total.pkt_loss == 0
# for unidir verify rx on tx port is 0 and vice versa
assert ssd.port[ports.x_num].sguid[guid].rx_pkts == 0
assert ssd.port[ports.x_num].sguid[guid].rx_bytes == 0
assert ssd.port[ports.y_num].sguid[guid].tx_pkts == 0
assert ssd.port[ports.y_num].sguid[guid].tx_bytes == 0
except RpcError as e:
raise
finally:
drone.stopTransmit(ports.x)
#
# TODO
# * Verify that bi-directional stream stats are correct for multiple streams
# * Verify transmit modes
#