Merge branch 'sign'
This commit is contained in:
commit
5191b72f2b
111
binding/core.py
111
binding/core.py
@ -98,3 +98,114 @@ class DroneProxy(object):
|
|||||||
os.fsync(f.fileno())
|
os.fsync(f.fileno())
|
||||||
f.close()
|
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
|
||||||
|
BIN
client/icons/stream_stats.png
Normal file
BIN
client/icons/stream_stats.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 714 B |
@ -96,7 +96,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
portsDock->setObjectName("portsDock");
|
portsDock->setObjectName("portsDock");
|
||||||
portsDock->setFeatures(
|
portsDock->setFeatures(
|
||||||
portsDock->features() & ~QDockWidget::DockWidgetClosable);
|
portsDock->features() & ~QDockWidget::DockWidgetClosable);
|
||||||
statsDock = new QDockWidget(tr("Statistics"), this);
|
statsDock = new QDockWidget(tr("Port Statistics"), this);
|
||||||
statsDock->setObjectName("statsDock");
|
statsDock->setObjectName("statsDock");
|
||||||
statsDock->setFeatures(
|
statsDock->setFeatures(
|
||||||
statsDock->features() & ~QDockWidget::DockWidgetClosable);
|
statsDock->features() & ~QDockWidget::DockWidgetClosable);
|
||||||
@ -166,6 +166,13 @@ MainWindow::~MainWindow()
|
|||||||
|
|
||||||
delete pgl;
|
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);
|
QByteArray layout = saveState(0);
|
||||||
appSettings->setValue(kApplicationWindowLayout, layout);
|
appSettings->setValue(kApplicationWindowLayout, layout);
|
||||||
appSettings->setValue(kApplicationWindowGeometryKey, geometry());
|
appSettings->setValue(kApplicationWindowGeometryKey, geometry());
|
||||||
@ -309,6 +316,16 @@ void MainWindow::on_actionViewRestoreDefaults_triggered()
|
|||||||
setGeometry(defaultGeometry_);
|
setGeometry(defaultGeometry_);
|
||||||
restoreState(defaultLayout_, 0);
|
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);
|
actionViewShowMyReservedPortsOnly->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,9 @@ HEADERS += \
|
|||||||
streamconfigdialog.h \
|
streamconfigdialog.h \
|
||||||
streamlistdelegate.h \
|
streamlistdelegate.h \
|
||||||
streammodel.h \
|
streammodel.h \
|
||||||
|
streamstatsfiltermodel.h \
|
||||||
|
streamstatsmodel.h \
|
||||||
|
streamstatswindow.h \
|
||||||
variablefieldswidget.h
|
variablefieldswidget.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
@ -71,6 +74,7 @@ FORMS += \
|
|||||||
portswindow.ui \
|
portswindow.ui \
|
||||||
preferences.ui \
|
preferences.ui \
|
||||||
streamconfigdialog.ui \
|
streamconfigdialog.ui \
|
||||||
|
streamstatswindow.ui \
|
||||||
variablefieldswidget.ui
|
variablefieldswidget.ui
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@ -100,6 +104,8 @@ SOURCES += \
|
|||||||
streamconfigdialog.cpp \
|
streamconfigdialog.cpp \
|
||||||
streamlistdelegate.cpp \
|
streamlistdelegate.cpp \
|
||||||
streammodel.cpp \
|
streammodel.cpp \
|
||||||
|
streamstatsmodel.cpp \
|
||||||
|
streamstatswindow.cpp \
|
||||||
variablefieldswidget.cpp
|
variablefieldswidget.cpp
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,5 +42,6 @@
|
|||||||
<file>icons/stream_delete.png</file>
|
<file>icons/stream_delete.png</file>
|
||||||
<file>icons/stream_duplicate.png</file>
|
<file>icons/stream_duplicate.png</file>
|
||||||
<file>icons/stream_edit.png</file>
|
<file>icons/stream_edit.png</file>
|
||||||
|
<file>icons/stream_stats.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -100,6 +100,8 @@ public:
|
|||||||
{ return d.is_exclusive_control(); }
|
{ return d.is_exclusive_control(); }
|
||||||
OstProto::TransmitMode transmitMode() const
|
OstProto::TransmitMode transmitMode() const
|
||||||
{ return d.transmit_mode(); }
|
{ return d.transmit_mode(); }
|
||||||
|
bool trackStreamStats() const
|
||||||
|
{ return d.is_tracking_stream_stats(); }
|
||||||
double averagePacketRate() const
|
double averagePacketRate() const
|
||||||
{ return avgPacketsPerSec_; }
|
{ return avgPacketsPerSec_; }
|
||||||
double averageBitRate() const
|
double averageBitRate() const
|
||||||
|
@ -20,7 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "portconfigdialog.h"
|
#include "portconfigdialog.h"
|
||||||
#include "settings.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)
|
: QDialog(parent), portConfig_(portConfig)
|
||||||
{
|
{
|
||||||
QString currentUser(portConfig_.user_name().c_str());
|
QString currentUser(portConfig_.user_name().c_str());
|
||||||
@ -64,6 +67,13 @@ PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent)
|
|||||||
qDebug("reservedBy_ = %d", reservedBy_);
|
qDebug("reservedBy_ = %d", reservedBy_);
|
||||||
|
|
||||||
exclusiveControlButton->setChecked(portConfig_.is_exclusive_control());
|
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()
|
void PortConfigDialog::accept()
|
||||||
@ -95,6 +105,7 @@ void PortConfigDialog::accept()
|
|||||||
}
|
}
|
||||||
|
|
||||||
pc.set_is_exclusive_control(exclusiveControlButton->isChecked());
|
pc.set_is_exclusive_control(exclusiveControlButton->isChecked());
|
||||||
|
pc.set_is_tracking_stream_stats(streamStatsButton->isChecked());
|
||||||
|
|
||||||
// Update fields that have changed, clear the rest
|
// Update fields that have changed, clear the rest
|
||||||
if (pc.transmit_mode() != portConfig_.transmit_mode())
|
if (pc.transmit_mode() != portConfig_.transmit_mode())
|
||||||
@ -112,5 +123,10 @@ void PortConfigDialog::accept()
|
|||||||
else
|
else
|
||||||
portConfig_.clear_is_exclusive_control();
|
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();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
class PortConfigDialog : public QDialog, public Ui::PortConfigDialog
|
class PortConfigDialog : public QDialog, public Ui::PortConfigDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PortConfigDialog(OstProto::Port &portConfig, QWidget *parent);
|
PortConfigDialog(OstProto::Port &portConfig,
|
||||||
|
const OstProto::PortState& portState,
|
||||||
|
QWidget *parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void accept();
|
virtual void accept();
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>244</width>
|
<width>244</width>
|
||||||
<height>233</height>
|
<height>257</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle" >
|
<property name="windowTitle" >
|
||||||
@ -69,6 +69,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="streamStatsButton" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Stream Statistics</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer>
|
<spacer>
|
||||||
<property name="orientation" >
|
<property name="orientation" >
|
||||||
|
@ -1593,13 +1593,14 @@ void PortGroup::clearPortStats(QList<uint> *portList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
serviceStub->clearStats(controller, portIdList, ack,
|
serviceStub->clearStats(controller, portIdList, ack,
|
||||||
NewCallback(this, &PortGroup::processClearStatsAck, controller));
|
NewCallback(this, &PortGroup::processClearPortStatsAck,
|
||||||
|
controller));
|
||||||
}
|
}
|
||||||
_exit:
|
_exit:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortGroup::processClearStatsAck(PbRpcController *controller)
|
void PortGroup::processClearPortStatsAck(PbRpcController *controller)
|
||||||
{
|
{
|
||||||
qDebug("In %s", __FUNCTION__);
|
qDebug("In %s", __FUNCTION__);
|
||||||
|
|
||||||
@ -1609,3 +1610,78 @@ void PortGroup::processClearStatsAck(PbRpcController *controller)
|
|||||||
delete 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ LOW
|
|||||||
namespace OstProto {
|
namespace OstProto {
|
||||||
class PortContent;
|
class PortContent;
|
||||||
class PortGroupContent;
|
class PortGroupContent;
|
||||||
|
class StreamStatsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
class QFile;
|
class QFile;
|
||||||
@ -163,13 +164,19 @@ public:
|
|||||||
void getPortStats();
|
void getPortStats();
|
||||||
void processPortStatsList();
|
void processPortStatsList();
|
||||||
void clearPortStats(QList<uint> *portList = NULL);
|
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:
|
signals:
|
||||||
void portGroupDataChanged(int portGroupId, int portId = 0xFFFF);
|
void portGroupDataChanged(int portGroupId, int portId = 0xFFFF);
|
||||||
void portListAboutToBeChanged(quint32 portGroupId);
|
void portListAboutToBeChanged(quint32 portGroupId);
|
||||||
void portListChanged(quint32 portGroupId);
|
void portListChanged(quint32 portGroupId);
|
||||||
void statsChanged(quint32 portGroupId);
|
void statsChanged(quint32 portGroupId);
|
||||||
|
void streamStatsReceived(quint32 portGroupId,
|
||||||
|
const OstProto::StreamStatsList *stats);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_reconnectTimer_timeout();
|
void on_reconnectTimer_timeout();
|
||||||
|
@ -170,6 +170,7 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
|
|||||||
case e_STAT_BYTES_SENT_NIC:
|
case e_STAT_BYTES_SENT_NIC:
|
||||||
return stats.tx_bytes_nic();
|
return stats.tx_bytes_nic();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case e_STAT_RX_DROPS : return quint64(stats.rx_drops());
|
case e_STAT_RX_DROPS : return quint64(stats.rx_drops());
|
||||||
case e_STAT_RX_ERRORS: return quint64(stats.rx_errors());
|
case e_STAT_RX_ERRORS: return quint64(stats.rx_errors());
|
||||||
case e_STAT_RX_FIFO_ERRORS: return quint64(stats.rx_fifo_errors());
|
case e_STAT_RX_FIFO_ERRORS: return quint64(stats.rx_fifo_errors());
|
||||||
|
@ -66,6 +66,7 @@ typedef enum {
|
|||||||
e_STAT_RX_FIFO_ERRORS,
|
e_STAT_RX_FIFO_ERRORS,
|
||||||
e_STAT_RX_FRAME_ERRORS,
|
e_STAT_RX_FRAME_ERRORS,
|
||||||
|
|
||||||
|
|
||||||
e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS,
|
e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS,
|
||||||
|
|
||||||
e_STAT_MAX
|
e_STAT_MAX
|
||||||
@ -92,6 +93,7 @@ static QStringList PortStatName = (QStringList()
|
|||||||
<< "Bytes Received (NIC)"
|
<< "Bytes Received (NIC)"
|
||||||
<< "Bytes Sent (NIC)"
|
<< "Bytes Sent (NIC)"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
<< "Receive Drops"
|
<< "Receive Drops"
|
||||||
<< "Receive Errors"
|
<< "Receive Errors"
|
||||||
<< "Receive Fifo Errors"
|
<< "Receive Fifo Errors"
|
||||||
|
@ -23,9 +23,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "portstatsfilterdialog.h"
|
#include "portstatsfilterdialog.h"
|
||||||
#include "portstatsmodel.h"
|
#include "portstatsmodel.h"
|
||||||
#include "portstatsproxymodel.h"
|
#include "portstatsproxymodel.h"
|
||||||
|
#include "streamstatsmodel.h"
|
||||||
|
#include "streamstatswindow.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
extern QMainWindow *mainWindow;
|
||||||
|
|
||||||
PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
|
PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
|
||||||
: QWidget(parent), proxyStatsModel(NULL)
|
: QWidget(parent), proxyStatsModel(NULL)
|
||||||
@ -227,6 +233,8 @@ void PortStatsWindow::on_tbClear_clicked()
|
|||||||
{
|
{
|
||||||
pgl->portGroupByIndex(portList.at(i).portGroupId).
|
pgl->portGroupByIndex(portList.at(i).portGroupId).
|
||||||
clearPortStats(&portList[i].portList);
|
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)
|
pgl->portGroupByIndex(portList.at(i).portGroupId)
|
||||||
.clearPortStats(&portList[i].portList);
|
.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*)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ private slots:
|
|||||||
|
|
||||||
void on_tbClear_clicked();
|
void on_tbClear_clicked();
|
||||||
void on_tbClearAll_clicked();
|
void on_tbClearAll_clicked();
|
||||||
|
void on_tbGetStreamStats_clicked();
|
||||||
|
|
||||||
void on_tbResolveNeighbors_clicked();
|
void on_tbResolveNeighbors_clicked();
|
||||||
void on_tbClearNeighbors_clicked();
|
void on_tbClearNeighbors_clicked();
|
||||||
|
@ -112,6 +112,22 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<widget class="Line" name="line_3">
|
<widget class="Line" name="line_3">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -755,12 +755,19 @@ void PortsWindow::on_actionPort_Configuration_triggered()
|
|||||||
if (!plm->isPort(current))
|
if (!plm->isPort(current))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Port &port = plm->port(current);
|
||||||
OstProto::Port config;
|
OstProto::Port config;
|
||||||
config.set_transmit_mode(plm->port(current).transmitMode());
|
// XXX: we don't call Port::protoDataCopyInto() to get config b'coz
|
||||||
config.set_is_exclusive_control(plm->port(current).hasExclusiveControl());
|
// we want only the modifiable fields populated to send to Drone
|
||||||
config.set_user_name(plm->port(current).userName().toStdString());
|
// 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)
|
if (dialog.exec() == QDialog::Accepted)
|
||||||
plm->portGroup(current.parent()).modifyPort(current.row(), config);
|
plm->portGroup(current.parent()).modifyPort(current.row(), config);
|
||||||
|
@ -85,13 +85,14 @@ StreamConfigDialog::StreamConfigDialog(
|
|||||||
|
|
||||||
// Time to play match the signals and slots!
|
// 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(rbL1None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||||
connect(rbFtNone, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
connect(rbFtNone, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||||
connect(rbL3None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
connect(rbL3None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||||
connect(rbL4None, 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
|
// disable the subsequent protocol group as well
|
||||||
connect(rbL1Other, SIGNAL(toggled(bool)), rbFtOther, SLOT(setChecked(bool)));
|
connect(rbL1Other, SIGNAL(toggled(bool)), rbFtOther, SLOT(setChecked(bool)));
|
||||||
connect(rbL1Other, SIGNAL(toggled(bool)), gbFrameType, SLOT(setDisabled(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(rbFtOther, SIGNAL(toggled(bool)), gbL3Proto, SLOT(setDisabled(bool)));
|
||||||
connect(rbL3Other, SIGNAL(toggled(bool)), rbL4Other, SLOT(setChecked(bool)));
|
connect(rbL3Other, SIGNAL(toggled(bool)), rbL4Other, SLOT(setChecked(bool)));
|
||||||
connect(rbL3Other, SIGNAL(toggled(bool)), gbL4Proto, SLOT(setDisabled(bool)));
|
connect(rbL3Other, SIGNAL(toggled(bool)), gbL4Proto, SLOT(setDisabled(bool)));
|
||||||
connect(rbL4Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool)));
|
connect(rbL4Other, SIGNAL(toggled(bool)), rbL5Other, SLOT(setChecked(bool)));
|
||||||
connect(rbL4Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(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
|
// Setup valid subsequent protocols for L2 to L4 protocols
|
||||||
for (int i = ProtoL2; i <= ProtoL4; i++)
|
for (int i = ProtoL2; i <= ProtoL4; i++)
|
||||||
@ -294,6 +297,17 @@ void StreamConfigDialog::setupUiExtra()
|
|||||||
bgProto[ProtoPayload]->addButton(rbPayloadHexDump, OstProto::Protocol::kHexDumpFieldNumber);
|
bgProto[ProtoPayload]->addButton(rbPayloadHexDump, OstProto::Protocol::kHexDumpFieldNumber);
|
||||||
bgProto[ProtoPayload]->addButton(rbPayloadOther, ButtonIdOther);
|
bgProto[ProtoPayload]->addButton(rbPayloadOther, ButtonIdOther);
|
||||||
#endif
|
#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
|
** 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)
|
void StreamConfigDialog::updateProtocol(int newId)
|
||||||
{
|
{
|
||||||
int level;
|
int level;
|
||||||
@ -802,6 +818,8 @@ void StreamConfigDialog::updateProtocol(int newId)
|
|||||||
__updateProtocol(level, 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)
|
void StreamConfigDialog::__updateProtocol(int level, int newId)
|
||||||
{
|
{
|
||||||
int oldId;
|
int oldId;
|
||||||
@ -849,7 +867,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
|
|||||||
// Free both protocol and associated widget
|
// Free both protocol and associated widget
|
||||||
delete _protocolWidgets.take(p);
|
delete _protocolWidgets.take(p);
|
||||||
delete p;
|
delete p;
|
||||||
if (level == ProtoPayload)
|
if (level == ProtoTrailer)
|
||||||
{
|
{
|
||||||
while (_iter->hasNext())
|
while (_iter->hasNext())
|
||||||
{
|
{
|
||||||
@ -868,6 +886,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Traverse the ProtocolList and update the SelectProtocols (Simple) widget
|
||||||
void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@ -893,7 +912,7 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
|||||||
id = _iter->next()->protocolNumber();
|
id = _iter->next()->protocolNumber();
|
||||||
btn = bgProto[i]->button(id);
|
btn = bgProto[i]->button(id);
|
||||||
|
|
||||||
if (btn)
|
if (btn) // we have a button for this protocol
|
||||||
{
|
{
|
||||||
if (btn->isEnabled())
|
if (btn->isEnabled())
|
||||||
btn->click();
|
btn->click();
|
||||||
@ -903,23 +922,32 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
|||||||
__updateProtocol(i, id);
|
__updateProtocol(i, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else // we don't have a button for this protocol
|
||||||
{
|
{
|
||||||
switch (i)
|
switch (i)
|
||||||
{
|
{
|
||||||
case ProtoVlan:
|
case ProtoVlan:
|
||||||
|
// No vlan - proto may belong to next layer
|
||||||
|
_iter->previous();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ProtoSign:
|
||||||
|
// No sign - but we have a trailer
|
||||||
_iter->previous();
|
_iter->previous();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ProtoPayload:
|
case ProtoPayload:
|
||||||
goto _other;
|
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);
|
btn = bgProto[ProtoPayload]->button(id);
|
||||||
if (btn && btn->isEnabled())
|
if (btn && btn->isEnabled())
|
||||||
{
|
{
|
||||||
btn->click();
|
btn->click();
|
||||||
break;
|
i = ProtoPayload;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
goto _other;
|
goto _other;
|
||||||
@ -927,20 +955,22 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If more protocol(s) beyond payload ...
|
// If more protocol(s) beyond trailer ...
|
||||||
if (_iter->hasNext())
|
if (_iter->hasNext())
|
||||||
{
|
{
|
||||||
i = ProtoPayload;
|
i = ProtoTrailer;
|
||||||
goto _other;
|
goto _other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(!_iter->hasNext()); // At end of the ProtocolList
|
||||||
goto _done;
|
goto _done;
|
||||||
|
|
||||||
_other:
|
_other:
|
||||||
|
// Set remaining protocols as 'Other'
|
||||||
for (int j = i; j < ProtoMax; j++)
|
for (int j = i; j < ProtoMax; j++)
|
||||||
{
|
{
|
||||||
// VLAN doesn't have a "Other" button
|
// VLAN/Sign doesn't have a "Other" button
|
||||||
if (j == ProtoVlan)
|
if ((j == ProtoVlan) || (j == ProtoSign))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bgProto[j]->button(ButtonIdOther)->setChecked(true);
|
bgProto[j]->button(ButtonIdOther)->setChecked(true);
|
||||||
@ -1212,6 +1242,13 @@ bool StreamConfigDialog::isCurrentStreamValid()
|
|||||||
"varying fields at transmit time may not be same as configured");
|
"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);
|
mpStream->preflightCheck(log);
|
||||||
|
|
||||||
if (log.size())
|
if (log.size())
|
||||||
|
@ -67,6 +67,8 @@ private:
|
|||||||
ProtoL4 = 4,
|
ProtoL4 = 4,
|
||||||
ProtoL5 = 5,
|
ProtoL5 = 5,
|
||||||
ProtoPayload = 6,
|
ProtoPayload = 6,
|
||||||
|
ProtoSign = 7,
|
||||||
|
ProtoTrailer = 8,
|
||||||
ProtoMax
|
ProtoMax
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>634</width>
|
<width>647</width>
|
||||||
<height>507</height>
|
<height>549</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -164,8 +164,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>592</width>
|
<width>605</width>
|
||||||
<height>269</height>
|
<height>311</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -211,7 +211,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1" rowspan="2">
|
<item rowspan="3" row="0" column="1" >
|
||||||
<widget class="QGroupBox" name="gbFrameType" >
|
<widget class="QGroupBox" name="gbFrameType" >
|
||||||
<property name="enabled" >
|
<property name="enabled" >
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -394,49 +394,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="3">
|
<item rowspan="2" row="1" column="0" >
|
||||||
<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" >
|
<widget class="QGroupBox" name="gbVlan" >
|
||||||
<property name="enabled" >
|
<property name="enabled" >
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -560,7 +518,49 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="3">
|
<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" >
|
<widget class="QGroupBox" name="gbPayloadProto" >
|
||||||
<property name="enabled" >
|
<property name="enabled" >
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -609,6 +609,67 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page_2">
|
<widget class="QWidget" name="page_2">
|
||||||
|
50
client/streamstatsfiltermodel.h
Normal file
50
client/streamstatsfiltermodel.h
Normal 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
234
client/streamstatsmodel.cpp
Normal 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
70
client/streamstatsmodel.h
Normal 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
|
||||||
|
|
65
client/streamstatswindow.cpp
Normal file
65
client/streamstatswindow.cpp
Normal 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.*"));
|
||||||
|
}
|
43
client/streamstatswindow.h
Normal file
43
client/streamstatswindow.h
Normal 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
|
||||||
|
|
35
client/streamstatswindow.ui
Normal file
35
client/streamstatswindow.ui
Normal 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>
|
@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "protocollistiterator.h"
|
#include "protocollistiterator.h"
|
||||||
#include "streambase.h"
|
#include "streambase.h"
|
||||||
|
|
||||||
|
#include "bswap.h"
|
||||||
|
|
||||||
#include <qendian.h>
|
#include <qendian.h>
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -972,18 +974,29 @@ out:
|
|||||||
quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
|
quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
|
||||||
CksumType cksumType, CksumScope cksumScope) const
|
CksumType cksumType, CksumScope cksumScope) const
|
||||||
{
|
{
|
||||||
quint32 sum = 0;
|
quint32 sum;
|
||||||
quint16 cksum;
|
quint16 cksum;
|
||||||
AbstractProtocol *p = next;
|
AbstractProtocol *p = next;
|
||||||
|
|
||||||
Q_ASSERT(cksumType == CksumIp);
|
Q_ASSERT(cksumType == CksumIp);
|
||||||
|
|
||||||
while (p)
|
if (!p)
|
||||||
{
|
return 0xFFFF;
|
||||||
|
|
||||||
cksum = p->protocolFrameCksum(streamIndex, cksumType);
|
cksum = p->protocolFrameCksum(streamIndex, cksumType);
|
||||||
sum += (quint16) ~cksum;
|
sum = (quint16) ~cksum;
|
||||||
if (cksumScope == CksumScopeAdjacentProtocol)
|
if (cksumScope == CksumScopeAdjacentProtocol)
|
||||||
goto out;
|
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;
|
||||||
p = p->next;
|
p = p->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -998,7 +1011,9 @@ out:
|
|||||||
while(sum>>16)
|
while(sum>>16)
|
||||||
sum = (sum & 0xFFFF) + (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
|
// Stein's binary GCD algo - from wikipedia
|
||||||
|
39
common/bswap.h
Normal file
39
common/bswap.h
Normal 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
|
@ -200,7 +200,10 @@ int HexDumpProtocol::protocolFrameSize(int streamIndex) const
|
|||||||
if (data.pad_until_end())
|
if (data.pad_until_end())
|
||||||
{
|
{
|
||||||
int pad = mpStream->frameLen(streamIndex)
|
int pad = mpStream->frameLen(streamIndex)
|
||||||
- (protocolFrameOffset(streamIndex) + len + kFcsSize);
|
- (protocolFrameOffset(streamIndex)
|
||||||
|
+ len
|
||||||
|
+ protocolFramePayloadSize(streamIndex)
|
||||||
|
+ kFcsSize);
|
||||||
if (pad < 0)
|
if (pad < 0)
|
||||||
pad = 0;
|
pad = 0;
|
||||||
len += pad;
|
len += pad;
|
||||||
|
@ -38,7 +38,8 @@ PROTOS += \
|
|||||||
textproto.proto \
|
textproto.proto \
|
||||||
userscript.proto \
|
userscript.proto \
|
||||||
hexdump.proto \
|
hexdump.proto \
|
||||||
sample.proto
|
sample.proto \
|
||||||
|
sign.proto
|
||||||
|
|
||||||
HEADERS = \
|
HEADERS = \
|
||||||
abstractprotocol.h \
|
abstractprotocol.h \
|
||||||
@ -77,6 +78,7 @@ HEADERS += \
|
|||||||
hexdump.h \
|
hexdump.h \
|
||||||
payload.h \
|
payload.h \
|
||||||
sample.h \
|
sample.h \
|
||||||
|
sign.h \
|
||||||
userscript.h
|
userscript.h
|
||||||
|
|
||||||
SOURCES = \
|
SOURCES = \
|
||||||
@ -110,6 +112,7 @@ SOURCES += \
|
|||||||
hexdump.cpp \
|
hexdump.cpp \
|
||||||
payload.cpp \
|
payload.cpp \
|
||||||
sample.cpp \
|
sample.cpp \
|
||||||
|
sign.cpp \
|
||||||
userscript.cpp
|
userscript.cpp
|
||||||
|
|
||||||
QMAKE_DISTCLEAN += object_script.*
|
QMAKE_DISTCLEAN += object_script.*
|
||||||
|
@ -27,6 +27,7 @@ FORMS += \
|
|||||||
hexdump.ui \
|
hexdump.ui \
|
||||||
payload.ui \
|
payload.ui \
|
||||||
sample.ui \
|
sample.ui \
|
||||||
|
sign.ui \
|
||||||
userscript.ui
|
userscript.ui
|
||||||
|
|
||||||
PROTOS = \
|
PROTOS = \
|
||||||
@ -79,6 +80,7 @@ HEADERS += \
|
|||||||
hexdumpconfig.h \
|
hexdumpconfig.h \
|
||||||
payloadconfig.h \
|
payloadconfig.h \
|
||||||
sampleconfig.h \
|
sampleconfig.h \
|
||||||
|
signconfig.h \
|
||||||
userscriptconfig.h
|
userscriptconfig.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@ -118,6 +120,7 @@ SOURCES += \
|
|||||||
hexdumpconfig.cpp \
|
hexdumpconfig.cpp \
|
||||||
payloadconfig.cpp \
|
payloadconfig.cpp \
|
||||||
sampleconfig.cpp \
|
sampleconfig.cpp \
|
||||||
|
signconfig.cpp \
|
||||||
userscriptconfig.cpp
|
userscriptconfig.cpp
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -68,7 +68,7 @@ int PayloadProtocol::protocolFrameSize(int streamIndex) const
|
|||||||
int len;
|
int len;
|
||||||
|
|
||||||
len = mpStream->frameLen(streamIndex) - protocolFrameOffset(streamIndex)
|
len = mpStream->frameLen(streamIndex) - protocolFrameOffset(streamIndex)
|
||||||
- kFcsSize;
|
- protocolFramePayloadSize(streamIndex) - kFcsSize;
|
||||||
|
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
len = 0;
|
len = 0;
|
||||||
|
@ -31,20 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QtGlobal>
|
#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 kPcapFileMagic = 0xa1b2c3d4;
|
||||||
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
|
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
|
||||||
const quint16 kPcapFileVersionMajor = 2;
|
const quint16 kPcapFileVersionMajor = 2;
|
||||||
|
@ -128,6 +128,7 @@ message Protocol {
|
|||||||
kSampleFieldNumber = 102;
|
kSampleFieldNumber = 102;
|
||||||
kUserScriptFieldNumber = 103;
|
kUserScriptFieldNumber = 103;
|
||||||
kHexDumpFieldNumber = 104;
|
kHexDumpFieldNumber = 104;
|
||||||
|
kSignFieldNumber = 105;
|
||||||
|
|
||||||
kEth2FieldNumber = 200;
|
kEth2FieldNumber = 200;
|
||||||
kDot3FieldNumber = 201;
|
kDot3FieldNumber = 201;
|
||||||
@ -204,6 +205,7 @@ message Port {
|
|||||||
optional bool is_exclusive_control = 6;
|
optional bool is_exclusive_control = 6;
|
||||||
optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit];
|
optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit];
|
||||||
optional string user_name = 8;
|
optional string user_name = 8;
|
||||||
|
optional bool is_tracking_stream_stats = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PortConfigList {
|
message PortConfigList {
|
||||||
@ -265,6 +267,29 @@ message PortStatsList {
|
|||||||
repeated PortStats port_stats = 1;
|
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 {
|
enum NotifType {
|
||||||
portConfigChanged = 1;
|
portConfigChanged = 1;
|
||||||
}
|
}
|
||||||
@ -358,5 +383,11 @@ service OstService {
|
|||||||
rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
|
rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
|
||||||
rpc clearDeviceNeighbors(PortIdList) returns (Ack);
|
rpc clearDeviceNeighbors(PortIdList) returns (Ack);
|
||||||
rpc getDeviceNeighbors(PortId) returns (PortNeighborList);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "hexdump.h"
|
#include "hexdump.h"
|
||||||
#include "payload.h"
|
#include "payload.h"
|
||||||
#include "sample.h"
|
#include "sample.h"
|
||||||
|
#include "sign.h"
|
||||||
#include "userscript.h"
|
#include "userscript.h"
|
||||||
|
|
||||||
ProtocolManager *OstProtocolManager;
|
ProtocolManager *OstProtocolManager;
|
||||||
@ -133,6 +134,8 @@ ProtocolManager::ProtocolManager()
|
|||||||
(void*) PayloadProtocol::createInstance);
|
(void*) PayloadProtocol::createInstance);
|
||||||
registerProtocol(OstProto::Protocol::kSampleFieldNumber,
|
registerProtocol(OstProto::Protocol::kSampleFieldNumber,
|
||||||
(void*) SampleProtocol::createInstance);
|
(void*) SampleProtocol::createInstance);
|
||||||
|
registerProtocol(OstProto::Protocol::kSignFieldNumber,
|
||||||
|
(void*) SignProtocol::createInstance);
|
||||||
registerProtocol(OstProto::Protocol::kUserScriptFieldNumber,
|
registerProtocol(OstProto::Protocol::kUserScriptFieldNumber,
|
||||||
(void*) UserScriptProtocol::createInstance);
|
(void*) UserScriptProtocol::createInstance);
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "hexdumpconfig.h"
|
#include "hexdumpconfig.h"
|
||||||
#include "payloadconfig.h"
|
#include "payloadconfig.h"
|
||||||
#include "sampleconfig.h"
|
#include "sampleconfig.h"
|
||||||
|
#include "signconfig.h"
|
||||||
#include "userscriptconfig.h"
|
#include "userscriptconfig.h"
|
||||||
|
|
||||||
ProtocolWidgetFactory *OstProtocolWidgetFactory;
|
ProtocolWidgetFactory *OstProtocolWidgetFactory;
|
||||||
@ -154,6 +155,9 @@ ProtocolWidgetFactory::ProtocolWidgetFactory()
|
|||||||
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||||
OstProto::Protocol::kSampleFieldNumber,
|
OstProto::Protocol::kSampleFieldNumber,
|
||||||
(void*) SampleConfigForm::createInstance);
|
(void*) SampleConfigForm::createInstance);
|
||||||
|
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||||
|
OstProto::Protocol::kSignFieldNumber,
|
||||||
|
(void*) SignConfigForm::createInstance);
|
||||||
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||||
OstProto::Protocol::kUserScriptFieldNumber,
|
OstProto::Protocol::kUserScriptFieldNumber,
|
||||||
(void*) UserScriptConfigForm::createInstance);
|
(void*) UserScriptConfigForm::createInstance);
|
||||||
|
219
common/sign.cpp
Normal file
219
common/sign.cpp
Normal 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
93
common/sign.h
Normal 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
31
common/sign.proto
Normal 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
59
common/sign.ui
Normal 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
53
common/signconfig.cpp
Normal 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
43
common/signconfig.h
Normal 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
|
@ -140,6 +140,15 @@ void StreamBase::setFrameProtocol(ProtocolList protocolList)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool StreamBase::hasProtocol(quint32 protocolNumber)
|
||||||
|
{
|
||||||
|
foreach(const AbstractProtocol *proto, *currentFrameProtocols)
|
||||||
|
if (proto->protocolNumber() == protocolNumber)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ProtocolListIterator* StreamBase::createProtocolListIterator() const
|
ProtocolListIterator* StreamBase::createProtocolListIterator() const
|
||||||
{
|
{
|
||||||
return new ProtocolListIterator(*currentFrameProtocols);
|
return new ProtocolListIterator(*currentFrameProtocols);
|
||||||
@ -581,32 +590,45 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const
|
|||||||
return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex);
|
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 StreamBase::preflightCheck(QStringList &result) const
|
||||||
{
|
{
|
||||||
bool pass = true;
|
bool pass = true;
|
||||||
|
bool chkTrunc = true;
|
||||||
|
bool chkJumbo = true;
|
||||||
int count = isFrameSizeVariable() ? frameCount() : 1;
|
int count = isFrameSizeVariable() ? frameCount() : 1;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
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 - "
|
result << QObject::tr("One or more frames may be truncated - "
|
||||||
"frame length should be at least %1")
|
"frame length should be at least %1")
|
||||||
.arg(frameProtocolLength(i) + kFcsSize);
|
.arg(frameProtocolLength(i) + kFcsSize);
|
||||||
|
chkTrunc = false;
|
||||||
pass = false;
|
pass = false;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
if (chkJumbo && (pktLen > 1522))
|
||||||
{
|
|
||||||
if (frameLen(i) > 1522)
|
|
||||||
{
|
{
|
||||||
result << QObject::tr("Jumbo frames may be truncated or dropped "
|
result << QObject::tr("Jumbo frames may be truncated or dropped "
|
||||||
"if not supported by the hardware");
|
"if not supported by the hardware");
|
||||||
pass = false;
|
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)
|
if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id)
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
void protoDataCopyFrom(const OstProto::Stream &stream);
|
void protoDataCopyFrom(const OstProto::Stream &stream);
|
||||||
void protoDataCopyInto(OstProto::Stream &stream) const;
|
void protoDataCopyInto(OstProto::Stream &stream) const;
|
||||||
|
|
||||||
|
bool hasProtocol(quint32 protocolNumber);
|
||||||
ProtocolListIterator* createProtocolListIterator() const;
|
ProtocolListIterator* createProtocolListIterator() const;
|
||||||
|
|
||||||
//! \todo (LOW) should we have a copy constructor??
|
//! \todo (LOW) should we have a copy constructor??
|
||||||
|
@ -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 AbstractPort::modify(const OstProto::Port &port)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
@ -82,6 +105,9 @@ bool AbstractPort::modify(const OstProto::Port &port)
|
|||||||
if (port.has_transmit_mode())
|
if (port.has_transmit_mode())
|
||||||
data_.set_transmit_mode(port.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()) {
|
if (port.has_user_name()) {
|
||||||
data_.set_user_name(port.user_name());
|
data_.set_user_name(port.user_name());
|
||||||
}
|
}
|
||||||
@ -155,6 +181,13 @@ void AbstractPort::addNote(QString note)
|
|||||||
data_.set_notes(notes.toStdString());
|
data_.set_notes(notes.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AbstractPort::setTrackStreamStats(bool enable)
|
||||||
|
{
|
||||||
|
data_.set_is_tracking_stream_stats(enable);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
AbstractPort::Accuracy AbstractPort::rateAccuracy()
|
AbstractPort::Accuracy AbstractPort::rateAccuracy()
|
||||||
{
|
{
|
||||||
return rateAccuracy_;
|
return rateAccuracy_;
|
||||||
@ -622,6 +655,54 @@ void AbstractPort::stats(PortStats *stats)
|
|||||||
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
|
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()
|
void AbstractPort::clearDeviceNeighbors()
|
||||||
{
|
{
|
||||||
deviceManager_->clearDeviceNeighbors();
|
deviceManager_->clearDeviceNeighbors();
|
||||||
|
@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#ifndef _SERVER_ABSTRACT_PORT_H
|
#ifndef _SERVER_ABSTRACT_PORT_H
|
||||||
#define _SERVER_ABSTRACT_PORT_H
|
#define _SERVER_ABSTRACT_PORT_H
|
||||||
|
|
||||||
|
#include "streamstats.h"
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ public:
|
|||||||
const char* name() { return data_.name().c_str(); }
|
const char* name() { return data_.name().c_str(); }
|
||||||
void protoDataCopyInto(OstProto::Port *port) { port->CopyFrom(data_); }
|
void protoDataCopyInto(OstProto::Port *port) { port->CopyFrom(data_); }
|
||||||
|
|
||||||
|
bool canModify(const OstProto::Port &port, bool *dirty);
|
||||||
bool modify(const OstProto::Port &port);
|
bool modify(const OstProto::Port &port);
|
||||||
|
|
||||||
virtual OstProto::LinkState linkState() { return linkState_; }
|
virtual OstProto::LinkState linkState() { return linkState_; }
|
||||||
@ -87,6 +90,8 @@ public:
|
|||||||
bool isDirty() { return isSendQueueDirty_; }
|
bool isDirty() { return isSendQueueDirty_; }
|
||||||
void setDirty() { isSendQueueDirty_ = true; }
|
void setDirty() { isSendQueueDirty_ = true; }
|
||||||
|
|
||||||
|
virtual bool setTrackStreamStats(bool enable);
|
||||||
|
|
||||||
Accuracy rateAccuracy();
|
Accuracy rateAccuracy();
|
||||||
virtual bool setRateAccuracy(Accuracy accuracy);
|
virtual bool setRateAccuracy(Accuracy accuracy);
|
||||||
|
|
||||||
@ -111,6 +116,12 @@ public:
|
|||||||
void stats(PortStats *stats);
|
void stats(PortStats *stats);
|
||||||
void resetStats() { epochStats_ = 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();
|
DeviceManager* deviceManager();
|
||||||
virtual void startDeviceEmulation() = 0;
|
virtual void startDeviceEmulation() = 0;
|
||||||
virtual void stopDeviceEmulation() = 0;
|
virtual void stopDeviceEmulation() = 0;
|
||||||
@ -123,6 +134,7 @@ public:
|
|||||||
quint64 neighborMacAddress(int streamId, int frameIndex);
|
quint64 neighborMacAddress(int streamId, int frameIndex);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void addNote(QString note);
|
void addNote(QString note);
|
||||||
|
|
||||||
void updatePacketListSequential();
|
void updatePacketListSequential();
|
||||||
@ -136,6 +148,7 @@ protected:
|
|||||||
|
|
||||||
quint64 maxStatsValue_;
|
quint64 maxStatsValue_;
|
||||||
struct PortStats stats_;
|
struct PortStats stats_;
|
||||||
|
StreamStats streamStats_;
|
||||||
//! \todo Need lock for stats access/update
|
//! \todo Need lock for stats access/update
|
||||||
|
|
||||||
DeviceManager *deviceManager_;
|
DeviceManager *deviceManager_;
|
||||||
|
@ -31,6 +31,7 @@ win32 {
|
|||||||
LIBS += -lm
|
LIBS += -lm
|
||||||
LIBS += -lprotobuf
|
LIBS += -lprotobuf
|
||||||
HEADERS += drone.h \
|
HEADERS += drone.h \
|
||||||
|
pcaptransmitter.h \
|
||||||
myservice.h
|
myservice.h
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
devicemanager.cpp \
|
devicemanager.cpp \
|
||||||
@ -40,6 +41,10 @@ SOURCES += \
|
|||||||
portmanager.cpp \
|
portmanager.cpp \
|
||||||
abstractport.cpp \
|
abstractport.cpp \
|
||||||
pcapport.cpp \
|
pcapport.cpp \
|
||||||
|
pcaptransmitter.cpp \
|
||||||
|
pcaprxstats.cpp \
|
||||||
|
pcaptxstats.cpp \
|
||||||
|
pcaptxthread.cpp \
|
||||||
bsdport.cpp \
|
bsdport.cpp \
|
||||||
linuxport.cpp \
|
linuxport.cpp \
|
||||||
winpcapport.cpp
|
winpcapport.cpp
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (C) 2010-2015 Srivats P.
|
Copyright (C) 2010-2016 Srivats P.
|
||||||
|
|
||||||
This file is part of "Ostinato"
|
This file is part of "Ostinato"
|
||||||
|
|
||||||
@ -133,10 +133,20 @@ void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/,
|
|||||||
id = port.port_id().id();
|
id = port.port_id().id();
|
||||||
if (id < portInfo.size())
|
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();
|
portLock[id]->lockForWrite();
|
||||||
portInfo[id]->modify(port);
|
portInfo[id]->modify(port);
|
||||||
|
if (dirty)
|
||||||
|
portInfo[id]->updatePacketList();
|
||||||
portLock[id]->unlock();
|
portLock[id]->unlock();
|
||||||
|
|
||||||
|
|
||||||
notif->mutable_port_id_list()->add_port_id()->set_id(id);
|
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);
|
notif->set_notif_type(OstProto::portConfigChanged);
|
||||||
emit notification(notif->notif_type(), SharedProtobufMessage(notif));
|
emit notification(notif->notif_type(), SharedProtobufMessage(notif));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
delete notif;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyService::getStreamIdList(::google::protobuf::RpcController* controller,
|
void MyService::getStreamIdList(::google::protobuf::RpcController* controller,
|
||||||
@ -558,6 +570,66 @@ void MyService::clearStats(::google::protobuf::RpcController* /*controller*/,
|
|||||||
done->Run();
|
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,
|
void MyService::checkVersion(::google::protobuf::RpcController* controller,
|
||||||
const ::OstProto::VersionInfo* request,
|
const ::OstProto::VersionInfo* request,
|
||||||
::OstProto::VersionCompatibility* response,
|
::OstProto::VersionCompatibility* response,
|
||||||
|
@ -100,6 +100,16 @@ public:
|
|||||||
const ::OstProto::PortIdList* request,
|
const ::OstProto::PortIdList* request,
|
||||||
::OstProto::Ack* response,
|
::OstProto::Ack* response,
|
||||||
::google::protobuf::Closure* done);
|
::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,
|
virtual void checkVersion(::google::protobuf::RpcController* controller,
|
||||||
const ::OstProto::VersionInfo* request,
|
const ::OstProto::VersionInfo* request,
|
||||||
::OstProto::VersionCompatibility* response,
|
::OstProto::VersionCompatibility* response,
|
||||||
|
88
server/packetsequence.h
Normal file
88
server/packetsequence.h
Normal 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
|
@ -24,67 +24,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pcap_if_t *PcapPort::deviceList_ = NULL;
|
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)
|
PcapPort::PcapPort(int id, const char *device)
|
||||||
: AbstractPort(id, device)
|
: AbstractPort(id, device)
|
||||||
{
|
{
|
||||||
monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_);
|
monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_);
|
||||||
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
|
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
|
||||||
transmitter_ = new PortTransmitter(device);
|
transmitter_ = new PcapTransmitter(device, streamStats_);
|
||||||
capturer_ = new PortCapturer(device);
|
capturer_ = new PortCapturer(device);
|
||||||
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
|
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
|
||||||
|
rxStatsPoller_ = new PcapRxStats(device, streamStats_);
|
||||||
|
|
||||||
if (!monitorRx_->handle() || !monitorTx_->handle())
|
if (!monitorRx_->handle() || !monitorTx_->handle())
|
||||||
isUsable_ = false;
|
isUsable_ = false;
|
||||||
@ -133,6 +83,9 @@ PcapPort::~PcapPort()
|
|||||||
if (monitorTx_)
|
if (monitorTx_)
|
||||||
monitorTx_->stop();
|
monitorTx_->stop();
|
||||||
|
|
||||||
|
rxStatsPoller_->stop();
|
||||||
|
delete rxStatsPoller_;
|
||||||
|
|
||||||
delete emulXcvr_;
|
delete emulXcvr_;
|
||||||
delete capturer_;
|
delete capturer_;
|
||||||
delete transmitter_;
|
delete transmitter_;
|
||||||
@ -168,6 +121,16 @@ void PcapPort::updateNotes()
|
|||||||
arg(notes).toStdString());
|
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)
|
bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
|
||||||
{
|
{
|
||||||
if (transmitter_->setRateAccuracy(accuracy)) {
|
if (transmitter_->setRateAccuracy(accuracy)) {
|
||||||
@ -192,6 +155,44 @@ int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
|
|||||||
return emulXcvr_->transmitPacket(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
|
* Port Monitor
|
||||||
@ -342,440 +343,6 @@ void PcapPort::PortMonitor::stop()
|
|||||||
pcap_breakloop(handle());
|
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
|
* Port Capturer
|
||||||
|
@ -26,6 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include "abstractport.h"
|
#include "abstractport.h"
|
||||||
#include "pcapextra.h"
|
#include "pcapextra.h"
|
||||||
|
#include "pcaprxstats.h"
|
||||||
|
#include "pcaptransmitter.h"
|
||||||
|
|
||||||
class PcapPort : public AbstractPort
|
class PcapPort : public AbstractPort
|
||||||
{
|
{
|
||||||
@ -38,6 +40,7 @@ public:
|
|||||||
virtual bool hasExclusiveControl() { return false; }
|
virtual bool hasExclusiveControl() { return false; }
|
||||||
virtual bool setExclusiveControl(bool /*exclusive*/) { return false; }
|
virtual bool setExclusiveControl(bool /*exclusive*/) { return false; }
|
||||||
|
|
||||||
|
virtual bool setTrackStreamStats(bool enable);
|
||||||
virtual bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
virtual bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||||
|
|
||||||
virtual void clearPacketList() {
|
virtual void clearPacketList() {
|
||||||
@ -103,107 +106,6 @@ protected:
|
|||||||
bool isPromisc_;
|
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
|
class PortCapturer: public QThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -263,9 +165,13 @@ protected:
|
|||||||
void updateNotes();
|
void updateNotes();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PortTransmitter *transmitter_;
|
bool startStreamStatsTracking();
|
||||||
|
bool stopStreamStatsTracking();
|
||||||
|
|
||||||
|
PcapTransmitter *transmitter_;
|
||||||
PortCapturer *capturer_;
|
PortCapturer *capturer_;
|
||||||
EmulationTransceiver *emulXcvr_;
|
EmulationTransceiver *emulXcvr_;
|
||||||
|
PcapRxStats *rxStatsPoller_;
|
||||||
|
|
||||||
static pcap_if_t *deviceList_;
|
static pcap_if_t *deviceList_;
|
||||||
};
|
};
|
||||||
|
186
server/pcaprxstats.cpp
Normal file
186
server/pcaprxstats.cpp
Normal 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
54
server/pcaprxstats.h
Normal 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
130
server/pcaptransmitter.cpp
Normal 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
63
server/pcaptransmitter.h
Normal 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
86
server/pcaptxstats.cpp
Normal 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
53
server/pcaptxstats.h
Normal 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
596
server/pcaptxthread.cpp
Normal 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
98
server/pcaptxthread.h
Normal 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
31
server/statstuple.h
Normal 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
36
server/streamstats.h
Normal 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
72
server/timestamp.h
Normal 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
601
test/streamstatstest.py
Normal 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
|
||||||
|
#
|
Loading…
Reference in New Issue
Block a user