Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
318fe124bf | ||
|
b3ac5f8d5d | ||
|
f185ceb206 | ||
|
cf84060277 | ||
|
469e0b054e | ||
|
23ee0e6f00 | ||
|
f3dccb9484 | ||
|
8f89c577ef | ||
|
71435869cf | ||
|
f779b0138b | ||
|
8c58d8610f | ||
|
c285e91d4a | ||
|
f5350663cd | ||
|
483f7fb4c5 | ||
|
d067019959 | ||
|
eb9d5eaf1a | ||
|
e677dc7d16 | ||
|
ebe6aef62d | ||
|
1e8486991d | ||
|
fff61d7773 | ||
|
eb3331f74d | ||
|
f3f553d149 | ||
|
49cb7c40e0 | ||
|
5581118e2f | ||
|
3bcd31a1ea | ||
|
268fad0690 | ||
|
28b308ce6c | ||
|
bfda96a888 | ||
|
a2734647b6 | ||
|
5eb2ad1979 | ||
|
1fa84ec644 | ||
|
c044880f1a | ||
|
2941c7ec22 | ||
|
ee45aaf0eb | ||
|
5ec7010c51 | ||
|
bef0f1d162 | ||
|
598e6bf243 | ||
|
63ed64a9d2 | ||
|
8ef9da062d | ||
|
7fee90313e | ||
|
bafdd948f8 | ||
|
e138fa0d3d | ||
|
8583299a1c | ||
|
4ba98cc520 | ||
|
649fa03575 | ||
|
650c098370 | ||
|
d375736a39 | ||
|
976fc72de8 | ||
|
4426a51d0f | ||
|
3c6632b6a2 | ||
|
d3be505a0c | ||
|
70f1b6c6e6 | ||
|
d65fea00d5 | ||
|
0afc150264 | ||
|
6ba942d00f | ||
|
4ee91c1bc2 | ||
|
aebb609e37 | ||
|
7160f724cb | ||
|
a48a11fe02 | ||
|
91a6efdeb7 | ||
|
bfdcee2baa | ||
|
c2967b663d | ||
|
70b5e60440 | ||
|
5540253e61 | ||
|
8ecbe78ddd | ||
|
3e3b5144aa | ||
|
596df69519 | ||
|
680f6eb89f | ||
|
1b18149aaa | ||
|
bf749847e0 | ||
|
ab713ce043 | ||
|
fc2d8408fa | ||
|
39c8d6f5f3 | ||
|
219ad576ad | ||
|
3060701386 | ||
|
47325c38b0 | ||
|
21ce331c43 | ||
|
fd2fae711b | ||
|
2d998b3708 | ||
|
f65aed7bb0 | ||
|
fdf8c77350 | ||
|
429eff123d | ||
|
3c98900092 | ||
|
159cd7c0da | ||
|
46b148b62b | ||
|
f29d31d38a | ||
|
1056b8d170 | ||
|
4a4de23d8a |
@ -13,11 +13,11 @@ I have been developing and maintaining Ostinato [single-handedly](https://github
|
||||
|
||||
I sell binary licenses on [ostinato.org](https://ostinato.org/downloads) to try and cover the costs of development. Please consider buying those - they are priced low enough that you can afford it or you could just as easily expense them to your organisation.
|
||||
|
||||
If you build Ostinato from source and find it useful, please donate to keep the lights on and sustain the project.
|
||||
If you build Ostinato from source and find it useful, please sponsor to keep the lights on and sustain the project.
|
||||
|
||||
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86&style=for-the-badge)](https://github.com/sponsors/pstavirs)
|
||||
|
||||
Read the Ostinato [origin story](https://ostinato.org/about).
|
||||
|
||||
[![Donate](https://ostinato.org/images/donate.png)](https://gum.co/ostdonate)
|
||||
|
||||
Srivats P<br/>
|
||||
Author, Ostinato
|
||||
|
@ -94,7 +94,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="CopyrightLabel" >
|
||||
<property name="text" >
|
||||
<string>Copyright (c) 2007-2020 Srivats P.</string>
|
||||
<string>Copyright (c) 2007-2023 Srivats P.</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
@ -209,7 +209,7 @@ void DeviceGroupDialog::updateTotalDeviceCount()
|
||||
|
||||
void DeviceGroupDialog::updateIp4Gateway()
|
||||
{
|
||||
quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value()));
|
||||
quint32 net = ip4Address->value() & (~0UL << (32 - ip4PrefixLength->value()));
|
||||
ip4Gateway->setValue(net | 0x01);
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,13 @@ class IconOnlyDelegate : public QStyledItemDelegate
|
||||
{
|
||||
QStyleOptionViewItem opt = option;
|
||||
opt.decorationPosition = QStyleOptionViewItem::Top;
|
||||
opt.features &= ~QStyleOptionViewItem::HasDisplay;
|
||||
QStyledItemDelegate::paint(painter, opt, index);
|
||||
}
|
||||
|
||||
QString displayText(const QVariant&, const QLocale&) const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#endif
|
||||
|
||||
#include "clipboardhelper.h"
|
||||
#include "fileformatoptions.h"
|
||||
#include "jumpurl.h"
|
||||
#include "logsmodel.h"
|
||||
#include "logswindow.h"
|
||||
@ -547,7 +548,7 @@ bool MainWindow::openSession(QString fileName, QString &error)
|
||||
goto _fail;
|
||||
}
|
||||
|
||||
if ((optDialog = fmt->openOptionsDialog()))
|
||||
if ((optDialog = FileFormatOptions::openOptionsDialog(fmt)))
|
||||
{
|
||||
int ret;
|
||||
optDialog->setParent(this, Qt::Dialog);
|
||||
|
@ -5,31 +5,26 @@ win32:RC_FILE = ostinato.rc
|
||||
macx:ICON = icons/logo.icns
|
||||
QT += widgets network script xml svg
|
||||
INCLUDEPATH += "../rpc/" "../common/"
|
||||
|
||||
OBJDIR = .
|
||||
win32 {
|
||||
QMAKE_LFLAGS += -static
|
||||
CONFIG(debug, debug|release) {
|
||||
LIBS += -L"../common/debug" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc/debug" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/debug/libostprotogui.a" \
|
||||
"../common/debug/libostproto.a" \
|
||||
"../rpc/debug/libpbrpc.a"
|
||||
OBJDIR = debug
|
||||
} else {
|
||||
LIBS += -L"../common/release" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc/release" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/release/libostprotogui.a" \
|
||||
"../common/release/libostproto.a" \
|
||||
"../rpc/release/libpbrpc.a"
|
||||
OBJDIR = release
|
||||
}
|
||||
} else {
|
||||
LIBS += -L"../common" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/libostprotogui.a" \
|
||||
"../common/libostproto.a" \
|
||||
"../rpc/libpbrpc.a"
|
||||
}
|
||||
LIBS += -L"../common/$$OBJDIR" -lostfile -lostfilegui
|
||||
LIBS += -L"../common/$$OBJDIR" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc/$$OBJDIR" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/$$OBJDIR/libostfilegui.a" \
|
||||
"../common/$$OBJDIR/libostfile.a" \
|
||||
"../common/$$OBJDIR/libostprotogui.a" \
|
||||
"../common/$$OBJDIR/libostproto.a" \
|
||||
"../rpc/$$OBJDIR/libpbrpc.a"
|
||||
|
||||
LIBS += -lprotobuf
|
||||
LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2
|
||||
RESOURCES += ostinato.qrc
|
||||
|
@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "port.h"
|
||||
|
||||
#include "emulation.h"
|
||||
#include "fileformatoptions.h"
|
||||
#include "streamfileformat.h"
|
||||
|
||||
#include <QApplication>
|
||||
@ -604,7 +605,7 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
|
||||
goto _fail;
|
||||
}
|
||||
|
||||
if ((optDialog = fmt->openOptionsDialog()))
|
||||
if ((optDialog = FileFormatOptions::openOptionsDialog(fmt)))
|
||||
{
|
||||
int ret;
|
||||
optDialog->setParent(mainWindow, Qt::Dialog);
|
||||
|
@ -137,7 +137,10 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
// States
|
||||
case e_COMBO_STATE:
|
||||
return QVariant();
|
||||
return QString("Link %1%2%3")
|
||||
.arg(LinkStateName.at(stats.state().link_state()))
|
||||
.arg(stats.state().is_transmit_on() ? ";Tx On" : "")
|
||||
.arg(stats.state().is_capture_on() ? ";Cap On" : "");
|
||||
|
||||
// Statistics
|
||||
case e_STAT_FRAMES_RCVD:
|
||||
|
@ -22,15 +22,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include <QSet>
|
||||
|
||||
class PortStatsProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PortStatsProxyModel(QSet<int> hiddenRows = QSet<int>(),
|
||||
QObject *parent = 0)
|
||||
: QSortFilterProxyModel(parent), hiddenRows_(hiddenRows)
|
||||
PortStatsProxyModel(int userRow, QObject *parent = 0)
|
||||
: QSortFilterProxyModel(parent), userRow_(userRow)
|
||||
{
|
||||
setFilterRegExp(QRegExp(".*"));
|
||||
}
|
||||
@ -39,7 +36,7 @@ protected:
|
||||
bool filterAcceptsColumn(int sourceColumn,
|
||||
const QModelIndex &sourceParent) const
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(0, sourceColumn, sourceParent);
|
||||
QModelIndex index = sourceModel()->index(userRow_, sourceColumn,sourceParent);
|
||||
QString user = sourceModel()->data(index).toString();
|
||||
|
||||
return filterRegExp().exactMatch(user) ? true : false;
|
||||
@ -47,10 +44,10 @@ protected:
|
||||
bool filterAcceptsRow(int sourceRow,
|
||||
const QModelIndex &/*sourceParent*/) const
|
||||
{
|
||||
return hiddenRows_.contains(sourceRow) ? false : true;
|
||||
return sourceRow == userRow_ ? false : true;
|
||||
}
|
||||
private:
|
||||
QSet<int> hiddenRows_;
|
||||
int userRow_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -44,8 +44,7 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
|
||||
model = pgl->getPortStatsModel();
|
||||
|
||||
// Hide 'user' row
|
||||
proxyStatsModel = new PortStatsProxyModel(
|
||||
QSet<int>({e_INFO_USER}), this);
|
||||
proxyStatsModel = new PortStatsProxyModel(e_INFO_USER, this);
|
||||
if (proxyStatsModel) {
|
||||
proxyStatsModel->setSourceModel(model);
|
||||
tvPortStats->setModel(proxyStatsModel);
|
||||
|
@ -51,6 +51,8 @@ enum {
|
||||
kAvgRxFrameRate,
|
||||
kAvgTxBitRate,
|
||||
kAvgRxBitRate,
|
||||
kAvgLatency,
|
||||
kAvgJitter,
|
||||
kMaxAggrStreamStats
|
||||
};
|
||||
static QStringList aggrStatTitles = QStringList()
|
||||
@ -61,7 +63,9 @@ static QStringList aggrStatTitles = QStringList()
|
||||
<< "Avg\nTx PktRate"
|
||||
<< "Avg\nRx PktRate"
|
||||
<< "Avg\nTx BitRate"
|
||||
<< "Avg\nRx BitRate";
|
||||
<< "Avg\nRx BitRate"
|
||||
<< "Avg\nLatency"
|
||||
<< "Avg\nJitter";
|
||||
|
||||
static const uint kAggrGuid = 0xffffffff;
|
||||
|
||||
@ -164,12 +168,12 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
|
||||
return QString("%L1").arg(aggrGuidStats_.value(guid).txDuration);
|
||||
case kAvgTxFrameRate:
|
||||
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
|
||||
QString("%L1").arg(
|
||||
XLocale().toPktRateString(
|
||||
aggrGuidStats_.value(guid).txPkts
|
||||
/ aggrGuidStats_.value(guid).txDuration);
|
||||
case kAvgRxFrameRate:
|
||||
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
|
||||
QString("%L1").arg(
|
||||
XLocale().toPktRateString(
|
||||
aggrGuidStats_.value(guid).rxPkts
|
||||
/ aggrGuidStats_.value(guid).txDuration);
|
||||
case kAvgTxBitRate:
|
||||
@ -184,6 +188,18 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
|
||||
(aggrGuidStats_.value(guid).rxBytes
|
||||
+ 24 * aggrGuidStats_.value(guid).rxPkts) * 8
|
||||
/ aggrGuidStats_.value(guid).txDuration);
|
||||
case kAvgLatency:
|
||||
return aggrGuidStats_.value(guid).latencyCount <= 0
|
||||
|| aggrGuidStats_.value(guid).latencySum <= 0 ? QString("-") :
|
||||
XLocale().toTimeIntervalString(
|
||||
aggrGuidStats_.value(guid).latencySum
|
||||
/ aggrGuidStats_.value(guid).latencyCount);
|
||||
case kAvgJitter:
|
||||
return aggrGuidStats_.value(guid).latencyCount <= 0
|
||||
|| aggrGuidStats_.value(guid).latencySum <= 0 ? QString("-") :
|
||||
XLocale().toTimeIntervalString(
|
||||
aggrGuidStats_.value(guid).jitterSum
|
||||
/ aggrGuidStats_.value(guid).latencyCount);
|
||||
default:
|
||||
break;
|
||||
};
|
||||
@ -258,6 +274,8 @@ void StreamStatsModel::appendStreamStatsList(
|
||||
ss.txPkts = s.tx_pkts();
|
||||
ss.rxBytes = s.rx_bytes();
|
||||
ss.txBytes = s.tx_bytes();
|
||||
ss.rxLatency = s.latency();
|
||||
ss.rxJitter = s.jitter();
|
||||
|
||||
aggrPort.rxPkts += ss.rxPkts;
|
||||
aggrPort.txPkts += ss.txPkts;
|
||||
@ -271,6 +289,11 @@ void StreamStatsModel::appendStreamStatsList(
|
||||
aggrGuid.txBytes += ss.txBytes;
|
||||
if (s.tx_duration() > aggrGuid.txDuration)
|
||||
aggrGuid.txDuration = s.tx_duration(); // XXX: use largest or avg?
|
||||
if (ss.rxLatency) {
|
||||
aggrGuid.latencySum += ss.rxLatency;
|
||||
aggrGuid.jitterSum += ss.rxJitter;
|
||||
aggrGuid.latencyCount++;
|
||||
}
|
||||
|
||||
aggrAggr.rxPkts += ss.rxPkts;
|
||||
aggrAggr.txPkts += ss.txPkts;
|
||||
@ -279,6 +302,11 @@ void StreamStatsModel::appendStreamStatsList(
|
||||
aggrAggr.txBytes += ss.txBytes;
|
||||
if (aggrGuid.txDuration > aggrAggr.txDuration)
|
||||
aggrAggr.txDuration = aggrGuid.txDuration;
|
||||
if (ss.rxLatency) {
|
||||
aggrAggr.latencySum += ss.rxLatency;
|
||||
aggrAggr.jitterSum += ss.rxJitter;
|
||||
aggrAggr.latencyCount++;
|
||||
}
|
||||
|
||||
if (!portList_.contains(pgp))
|
||||
portList_.append(pgp);
|
||||
|
@ -57,6 +57,8 @@ private:
|
||||
quint64 txPkts;
|
||||
quint64 rxBytes;
|
||||
quint64 txBytes;
|
||||
quint64 rxLatency;
|
||||
quint64 rxJitter;
|
||||
};
|
||||
struct AggrGuidStats {
|
||||
quint64 rxPkts;
|
||||
@ -65,6 +67,9 @@ private:
|
||||
quint64 txBytes;
|
||||
qint64 pktLoss;
|
||||
double txDuration;
|
||||
quint64 latencySum;
|
||||
quint64 jitterSum;
|
||||
uint latencyCount;
|
||||
};
|
||||
QList<Guid> guidList_;
|
||||
QList<PortGroupPort> portList_;
|
||||
|
@ -46,6 +46,10 @@ StreamStatsWindow::StreamStatsWindow(QAbstractItemModel *model, QWidget *parent)
|
||||
streamStats->verticalHeader()->setHighlightSections(false);
|
||||
streamStats->verticalHeader()->setDefaultSectionSize(
|
||||
streamStats->verticalHeader()->minimumSectionSize());
|
||||
|
||||
// Fit all columns in window whenever data changes
|
||||
connect(model, &QAbstractItemModel::modelReset,
|
||||
[=]() { streamStats->resizeColumnsToContents(); });
|
||||
}
|
||||
|
||||
StreamStatsWindow::~StreamStatsWindow()
|
||||
@ -62,4 +66,6 @@ void StreamStatsWindow::on_actionShowDetails_triggered(bool checked)
|
||||
filterModel_->setFilterRegExp(QRegExp(".*"));
|
||||
else
|
||||
filterModel_->setFilterRegExp(QRegExp(kDefaultFilter_));
|
||||
|
||||
streamStats->resizeColumnsToContents();
|
||||
}
|
||||
|
@ -19,6 +19,9 @@
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ActionsContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Oops! We don't seem to have any stream statistics for the requested port(s)
|
||||
|
||||
|
@ -85,20 +85,49 @@ public:
|
||||
return toDouble(text, ok) * multiplier;
|
||||
}
|
||||
|
||||
QString toPktRateString(double pps) const
|
||||
{
|
||||
QString text;
|
||||
|
||||
if (pps >= 1e6)
|
||||
return QObject::tr("%L1 Mpps").arg(pps/1e6, 0, 'f', 3);
|
||||
|
||||
if (pps >= 1e3)
|
||||
return QObject::tr("%L1 Kpps").arg(pps/1e3, 0, 'f', 3);
|
||||
|
||||
return QObject::tr("%L1").arg(pps, 0, 'f', 3);
|
||||
}
|
||||
|
||||
QString toBitRateString(double bps) const
|
||||
{
|
||||
QString text;
|
||||
|
||||
if (bps >= 1e9)
|
||||
return QObject::tr("%L1 Gbps").arg(bps/1e9, 0, 'f', 4);
|
||||
return QObject::tr("%L1 Gbps").arg(bps/1e9, 0, 'f', 3);
|
||||
|
||||
if (bps >= 1e6)
|
||||
return QObject::tr("%L1 Mbps").arg(bps/1e6, 0, 'f', 4);
|
||||
return QObject::tr("%L1 Mbps").arg(bps/1e6, 0, 'f', 3);
|
||||
|
||||
if (bps >= 1e3)
|
||||
return QObject::tr("%L1 Kbps").arg(bps/1e3, 0, 'f', 4);
|
||||
return QObject::tr("%L1 Kbps").arg(bps/1e3, 0, 'f', 3);
|
||||
|
||||
return QObject::tr("%L1 bps").arg(bps, 0, 'f', 4);
|
||||
return QObject::tr("%L1 bps").arg(bps, 0, 'f', 3);
|
||||
}
|
||||
|
||||
QString toTimeIntervalString(qint64 nanosecs) const
|
||||
{
|
||||
QString text;
|
||||
|
||||
if (nanosecs >= 1e9)
|
||||
return QObject::tr("%L1 s").arg(nanosecs/1e9, 0, 'f', 2);
|
||||
|
||||
if (nanosecs >= 1e6)
|
||||
return QObject::tr("%L1 ms").arg(nanosecs/1e6, 0, 'f', 2);
|
||||
|
||||
if (nanosecs >= 1e3)
|
||||
return QObject::tr("%L1 us").arg(nanosecs/1e3, 0, 'f', 2);
|
||||
|
||||
return QObject::tr("%L1 ns").arg(nanosecs);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -169,7 +169,8 @@ private:
|
||||
for (int i = start; i < end; i++)
|
||||
if (indexes.contains(model()->index(indexes.first().row(), i)))
|
||||
text.append(model()->headerData(i, Qt::Horizontal)
|
||||
.toString()+"\t");;
|
||||
.toString().replace('\n', ' ')
|
||||
+"\t");;
|
||||
text.append("\n");
|
||||
}
|
||||
|
||||
|
@ -62,12 +62,14 @@ void ArpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
|
||||
|
||||
QString ArpProtocol::name() const
|
||||
{
|
||||
return QString("Address Resolution Protocol");
|
||||
return isRarp() ?
|
||||
QString("Reverse Address Resolution Protocol") :
|
||||
QString("Address Resolution Protocol");
|
||||
}
|
||||
|
||||
QString ArpProtocol::shortName() const
|
||||
{
|
||||
return QString("ARP");
|
||||
return isRarp() ? QString("RARP") : QString("ARP");
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -96,7 +98,7 @@ quint32 ArpProtocol::protocolId(ProtocolIdType type) const
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ProtocolIdEth: return 0x0806;
|
||||
case ProtocolIdEth: return isRarp() ? 0x8035 : 0x0806;
|
||||
default:break;
|
||||
}
|
||||
|
||||
@ -808,3 +810,11 @@ int ArpProtocol::protocolFrameVariableCount() const
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool ArpProtocol::isRarp() const
|
||||
{
|
||||
if ((data.op_code() == 3)
|
||||
|| (data.op_code() ==4))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
@ -96,6 +96,8 @@ public:
|
||||
virtual int protocolFrameVariableCount() const;
|
||||
|
||||
private:
|
||||
bool isRarp() const;
|
||||
|
||||
OstProto::Arp data;
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,8 @@ ArpConfigForm::ArpConfigForm(QWidget *parent)
|
||||
opCodeCombo->setValidator(new QIntValidator(0, 0xFFFF, this));
|
||||
opCodeCombo->addItem(1, "ARP Request");
|
||||
opCodeCombo->addItem(2, "ARP Reply");
|
||||
opCodeCombo->addItem(3, "Reverse ARP Request");
|
||||
opCodeCombo->addItem(4, "Reverse ARP Reply");
|
||||
|
||||
connect(senderHwAddrMode, SIGNAL(currentIndexChanged(int)),
|
||||
SLOT(on_senderHwAddrMode_currentIndexChanged(int)));
|
||||
|
29
common/debugdefs.h
Normal file
29
common/debugdefs.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright (C) 2023 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 _DEBUG_DEFS_H
|
||||
#define _DEBUG_DEFS_H
|
||||
|
||||
#if 0
|
||||
#define timingDebug(fmt, ...) qDebug("TIMING:" fmt, __VA_ARGS__)
|
||||
#else
|
||||
#define timingDebug(...)
|
||||
#endif
|
||||
|
||||
#endif
|
47
common/fileformatoptions.cpp
Normal file
47
common/fileformatoptions.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright (C) 2022 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 "fileformatoptions.h"
|
||||
|
||||
#include "pcapfileformat.h"
|
||||
#include "pcapoptionsdialog.h"
|
||||
#include "streamfileformat.h"
|
||||
|
||||
QDialog* FileFormatOptions::openOptionsDialog(StreamFileFormat *fileFormat)
|
||||
{
|
||||
if (dynamic_cast<PcapFileFormat*>(fileFormat))
|
||||
return new PcapImportOptionsDialog(fileFormat->options());
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QDialog* FileFormatOptions::saveOptionsDialog(StreamFileFormat* /*fileFormat*/)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QDialog* FileFormatOptions::openOptionsDialog(SessionFileFormat* /*fileFormat*/)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QDialog* FileFormatOptions::saveOptionsDialog(SessionFileFormat* /*fileFormat*/)
|
||||
{
|
||||
return NULL;
|
||||
}
|
41
common/fileformatoptions.h
Normal file
41
common/fileformatoptions.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright (C) 2022 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 _FILE_FORMAT_OPTIONS_H
|
||||
#define _FILE_FORMAT_OPTIONS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class SessionFileFormat;
|
||||
class StreamFileFormat;
|
||||
class QDialog;
|
||||
|
||||
class FileFormatOptions : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static QDialog* openOptionsDialog(StreamFileFormat *fileFormat);
|
||||
static QDialog* saveOptionsDialog(StreamFileFormat *fileFormat);
|
||||
|
||||
static QDialog* openOptionsDialog(SessionFileFormat *fileFormat);
|
||||
static QDialog* saveOptionsDialog(SessionFileFormat *fileFormat);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -214,12 +214,6 @@ Length (x4)</string>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QLineEdit" name="leIpSrcAddr" >
|
||||
<property name="inputMask" >
|
||||
<string>009.009.009.009; </string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
@ -264,12 +258,6 @@ Length (x4)</string>
|
||||
<property name="enabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="inputMask" >
|
||||
<string>009.009.009.009; </string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
@ -281,12 +269,6 @@ Length (x4)</string>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QLineEdit" name="leIpDstAddr" >
|
||||
<property name="inputMask" >
|
||||
<string>000.000.000.000; </string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
@ -331,12 +313,6 @@ Length (x4)</string>
|
||||
<property name="enabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="inputMask" >
|
||||
<string>009.009.009.009; </string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "ip4config.h"
|
||||
#include "ip4.h"
|
||||
#include "ipv4addressvalidator.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
|
||||
@ -30,6 +31,10 @@ Ip4ConfigForm::Ip4ConfigForm(QWidget *parent)
|
||||
leIpVersion->setValidator(new QIntValidator(0, 15, this));
|
||||
leIpOptions->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]*"),
|
||||
this));
|
||||
leIpSrcAddr->setValidator(new IPv4AddressValidator(this));
|
||||
leIpSrcAddrMask->setValidator(new IPv4AddressValidator(this));
|
||||
leIpDstAddr->setValidator(new IPv4AddressValidator(this));
|
||||
leIpDstAddrMask->setValidator(new IPv4AddressValidator(this));
|
||||
|
||||
connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int)));
|
||||
|
@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "crc32c.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QVariant>
|
||||
|
||||
@ -437,17 +437,19 @@ _exit:
|
||||
|
||||
void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
|
||||
{
|
||||
QCoreApplication *app = QCoreApplication::instance();
|
||||
|
||||
// Fill in the "native" file format version
|
||||
metaData.set_format_version_major(kFileFormatVersionMajor);
|
||||
metaData.set_format_version_minor(kFileFormatVersionMinor);
|
||||
metaData.set_format_version_revision(kFileFormatVersionRevision);
|
||||
|
||||
metaData.set_generator_name(
|
||||
qApp->applicationName().toUtf8().constData());
|
||||
app->applicationName().toUtf8().constData());
|
||||
metaData.set_generator_version(
|
||||
qApp->property("version").toString().toUtf8().constData());
|
||||
app->property("version").toString().toUtf8().constData());
|
||||
metaData.set_generator_revision(
|
||||
qApp->property("revision").toString().toUtf8().constData());
|
||||
app->property("revision").toString().toUtf8().constData());
|
||||
}
|
||||
|
||||
int NativeFileFormat::fileMetaSize(const quint8* file, int size)
|
||||
|
60
common/ostfile.pro
Normal file
60
common/ostfile.pro
Normal file
@ -0,0 +1,60 @@
|
||||
TEMPLATE = lib
|
||||
CONFIG += qt staticlib
|
||||
QT += network xml script
|
||||
LIBS += \
|
||||
-lprotobuf
|
||||
|
||||
PROTOS = \
|
||||
fileformat.proto
|
||||
|
||||
HEADERS = \
|
||||
ostprotolib.h \
|
||||
nativefileformat.h \
|
||||
ossnfileformat.h \
|
||||
ostmfileformat.h \
|
||||
pcapfileformat.h \
|
||||
pdmlfileformat.h \
|
||||
pythonfileformat.h \
|
||||
pdmlprotocol.h \
|
||||
pdmlprotocols.h \
|
||||
pdmlreader.h \
|
||||
sessionfileformat.h \
|
||||
streamfileformat.h
|
||||
|
||||
SOURCES += \
|
||||
ostprotolib.cpp \
|
||||
nativefileformat.cpp \
|
||||
ossnfileformat.cpp \
|
||||
ostmfileformat.cpp \
|
||||
pcapfileformat.cpp \
|
||||
pdmlfileformat.cpp \
|
||||
pythonfileformat.cpp \
|
||||
pdmlprotocol.cpp \
|
||||
pdmlprotocols.cpp \
|
||||
pdmlreader.cpp \
|
||||
sessionfileformat.cpp \
|
||||
streamfileformat.cpp \
|
||||
|
||||
SOURCES += \
|
||||
vlanpdml.cpp \
|
||||
svlanpdml.cpp \
|
||||
stppdml.cpp \
|
||||
eth2pdml.cpp \
|
||||
llcpdml.cpp \
|
||||
arppdml.cpp \
|
||||
ip4pdml.cpp \
|
||||
ip6pdml.cpp \
|
||||
grepdml.cpp \
|
||||
icmppdml.cpp \
|
||||
icmp6pdml.cpp \
|
||||
igmppdml.cpp \
|
||||
mldpdml.cpp \
|
||||
tcppdml.cpp \
|
||||
udppdml.cpp \
|
||||
textprotopdml.cpp \
|
||||
samplepdml.cpp
|
||||
|
||||
QMAKE_DISTCLEAN += object_script.*
|
||||
|
||||
include(../protobuf.pri)
|
||||
include(../options.pri)
|
15
common/ostfilegui.pro
Normal file
15
common/ostfilegui.pro
Normal file
@ -0,0 +1,15 @@
|
||||
TEMPLATE = lib
|
||||
CONFIG += qt staticlib
|
||||
QT += widgets
|
||||
|
||||
FORMS = \
|
||||
pcapfileimport.ui
|
||||
|
||||
HEADERS = \
|
||||
fileformatoptions.h \
|
||||
pcapoptionsdialog.h
|
||||
|
||||
SOURCES = \
|
||||
fileformatoptions.cpp \
|
||||
pcapoptionsdialog.cpp
|
||||
|
@ -113,6 +113,7 @@ SOURCES += \
|
||||
udp.cpp \
|
||||
textproto.cpp \
|
||||
hexdump.cpp \
|
||||
packet.cpp \
|
||||
payload.cpp \
|
||||
sample.cpp \
|
||||
sign.cpp \
|
||||
|
@ -2,11 +2,6 @@ TEMPLATE = lib
|
||||
CONFIG += qt staticlib
|
||||
QT += widgets network xml script
|
||||
INCLUDEPATH += "../extra/qhexedit2/src"
|
||||
LIBS += \
|
||||
-lprotobuf
|
||||
|
||||
FORMS = \
|
||||
pcapfileimport.ui \
|
||||
|
||||
FORMS += \
|
||||
mac.ui \
|
||||
@ -32,28 +27,10 @@ FORMS += \
|
||||
sign.ui \
|
||||
userscript.ui
|
||||
|
||||
PROTOS = \
|
||||
fileformat.proto
|
||||
|
||||
# TODO: Move fileformat related stuff into a different library - why?
|
||||
HEADERS = \
|
||||
ostprotolib.h \
|
||||
ipv4addressdelegate.h \
|
||||
ipv6addressdelegate.h \
|
||||
nativefileformat.h \
|
||||
ossnfileformat.h \
|
||||
ostmfileformat.h \
|
||||
pcapfileformat.h \
|
||||
pdmlfileformat.h \
|
||||
pythonfileformat.h \
|
||||
pdmlprotocol.h \
|
||||
pdmlprotocols.h \
|
||||
pdmlreader.h \
|
||||
sessionfileformat.h \
|
||||
streamfileformat.h \
|
||||
spinboxdelegate.h
|
||||
|
||||
HEADERS += \
|
||||
spinboxdelegate.h \
|
||||
tosdscp.h
|
||||
|
||||
HEADERS += \
|
||||
@ -90,21 +67,7 @@ HEADERS += \
|
||||
userscriptconfig.h
|
||||
|
||||
SOURCES += \
|
||||
ostprotolib.cpp \
|
||||
nativefileformat.cpp \
|
||||
ossnfileformat.cpp \
|
||||
ostmfileformat.cpp \
|
||||
pcapfileformat.cpp \
|
||||
pdmlfileformat.cpp \
|
||||
pythonfileformat.cpp \
|
||||
pdmlprotocol.cpp \
|
||||
pdmlprotocols.cpp \
|
||||
pdmlreader.cpp \
|
||||
sessionfileformat.cpp \
|
||||
streamfileformat.cpp \
|
||||
spinboxdelegate.cpp
|
||||
|
||||
SOURCES += \
|
||||
spinboxdelegate.cpp \
|
||||
tosdscp.cpp
|
||||
|
||||
SOURCES += \
|
||||
@ -133,26 +96,6 @@ SOURCES += \
|
||||
signconfig.cpp \
|
||||
userscriptconfig.cpp
|
||||
|
||||
SOURCES += \
|
||||
vlanpdml.cpp \
|
||||
svlanpdml.cpp \
|
||||
stppdml.cpp \
|
||||
eth2pdml.cpp \
|
||||
llcpdml.cpp \
|
||||
arppdml.cpp \
|
||||
ip4pdml.cpp \
|
||||
ip6pdml.cpp \
|
||||
grepdml.cpp \
|
||||
icmppdml.cpp \
|
||||
icmp6pdml.cpp \
|
||||
igmppdml.cpp \
|
||||
mldpdml.cpp \
|
||||
tcppdml.cpp \
|
||||
udppdml.cpp \
|
||||
textprotopdml.cpp \
|
||||
samplepdml.cpp
|
||||
|
||||
QMAKE_DISTCLEAN += object_script.*
|
||||
|
||||
include(../protobuf.pri)
|
||||
include(../options.pri)
|
||||
|
117
common/packet.cpp
Normal file
117
common/packet.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright (C) 2023 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 "packet.h"
|
||||
|
||||
using namespace Packet;
|
||||
|
||||
quint16 Packet::l4ChecksumOffset(const uchar *pktData, int pktLen)
|
||||
{
|
||||
Parser parser(pktData, pktLen);
|
||||
quint16 offset = kEthTypeOffset;
|
||||
|
||||
// Skip VLANs, if any
|
||||
quint16 ethType = parser.field16(offset);
|
||||
if (!parser.ok()) return 0;
|
||||
|
||||
// TODO: support 802.3 frames
|
||||
if (ethType <= 1500)
|
||||
return 0;
|
||||
|
||||
while (kVlanEthTypes.contains(ethType)) {
|
||||
offset += kVlanTagSize;
|
||||
ethType = parser.field16(offset);
|
||||
if (!parser.ok()) return 0;
|
||||
}
|
||||
offset += kEthTypeSize;
|
||||
|
||||
// XXX: offset now points to Eth payload
|
||||
|
||||
// Skip MPLS tags, if any
|
||||
if (ethType == kMplsEthType) {
|
||||
while (1) {
|
||||
quint32 mplsTag = parser.field32(offset);
|
||||
if (!parser.ok()) return 0;
|
||||
offset += kMplsTagSize;
|
||||
if (mplsTag & 0x100) { // BOS bit
|
||||
quint32 nextWord = parser.field32(offset);
|
||||
if (!parser.ok()) return 0;
|
||||
if (nextWord == 0) { // PW Control Word
|
||||
offset += kMplsTagSize;
|
||||
ethType = 0;
|
||||
break;
|
||||
}
|
||||
quint8 firstPayloadNibble = nextWord >> 28;
|
||||
if (firstPayloadNibble == 0x4)
|
||||
ethType = kIp4EthType;
|
||||
else if (firstPayloadNibble == 0x6)
|
||||
ethType = kIp6EthType;
|
||||
else
|
||||
ethType = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quint8 ipProto = 0;
|
||||
if (ethType == kIp4EthType) {
|
||||
ipProto = parser.field8(offset + kIp4ProtocolOffset);
|
||||
if (!parser.ok()) return 0;
|
||||
|
||||
quint8 ipHdrLen = parser.field8(offset) & 0x0F;
|
||||
if (!parser.ok()) return 0;
|
||||
offset += 4*ipHdrLen;
|
||||
} else if (ethType == kIp6EthType) {
|
||||
ipProto = parser.field8(offset + kIp6NextHeaderOffset);
|
||||
if (!parser.ok()) return 0;
|
||||
offset += kIp6HeaderSize;
|
||||
|
||||
// XXX: offset now points to IPv6 payload
|
||||
|
||||
// Skip IPv6 extension headers, if any
|
||||
while (kIp6ExtensionHeaders.contains(ipProto)) {
|
||||
ipProto = parser.field8(offset + kIp6ExtNextHeaderOffset);
|
||||
if (!parser.ok()) return 0;
|
||||
|
||||
quint16 extHdrLen = parser.field8(offset + kIp6ExtLengthOffset);
|
||||
offset += 8 + 8*extHdrLen;
|
||||
}
|
||||
} else {
|
||||
// Non-IP
|
||||
// TODO: support MPLS PW with Eth payload
|
||||
return 0;
|
||||
}
|
||||
|
||||
// XXX: offset now points to IP payload
|
||||
|
||||
if (ipProto == kIpProtoTcp) {
|
||||
parser.field16(offset + kTcpChecksumOffset);
|
||||
if (!parser.ok()) return 0;
|
||||
|
||||
return offset + kTcpChecksumOffset;
|
||||
} else if (ipProto == kIpProtoUdp) {
|
||||
parser.field16(offset + kUdpChecksumOffset);
|
||||
if (!parser.ok()) return 0;
|
||||
|
||||
return offset + kUdpChecksumOffset;
|
||||
}
|
||||
|
||||
// No L4
|
||||
return 0;
|
||||
}
|
113
common/packet.h
Normal file
113
common/packet.h
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (C) 2023 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_H
|
||||
#define _PACKET_H
|
||||
|
||||
#include <QSet>
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace Packet {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
Parser(const uchar *data, int length)
|
||||
: pktData_(data), pktLen_(length) {}
|
||||
quint8 field8(int offset) {
|
||||
if (offset >= pktLen_) {
|
||||
ok_ = false;
|
||||
return 0;
|
||||
}
|
||||
ok_ = true;
|
||||
return pktData_[offset];
|
||||
}
|
||||
quint16 field16(int offset) {
|
||||
if (offset + 1 >= pktLen_) {
|
||||
ok_ = false;
|
||||
return 0;
|
||||
}
|
||||
ok_ = true;
|
||||
return pktData_[offset] << 8
|
||||
| pktData_[offset+1];
|
||||
}
|
||||
quint32 field32(int offset) {
|
||||
if (offset + 3 >= pktLen_) {
|
||||
ok_ = false;
|
||||
return 0;
|
||||
}
|
||||
ok_ = true;
|
||||
return pktData_[offset] << 24
|
||||
| pktData_[offset+1] << 16
|
||||
| pktData_[offset+2] << 8
|
||||
| pktData_[offset+3];
|
||||
}
|
||||
bool ok() {
|
||||
return ok_;
|
||||
}
|
||||
private:
|
||||
const uchar *pktData_;
|
||||
int pktLen_;
|
||||
bool ok_{false};
|
||||
};
|
||||
|
||||
quint16 l4ChecksumOffset(const uchar *pktData, int pktLen);
|
||||
|
||||
//
|
||||
// Constants
|
||||
//
|
||||
// Ethernet
|
||||
const quint16 kEthTypeOffset = 12;
|
||||
const quint16 kEthTypeSize = 2;
|
||||
const quint16 kIp4EthType = 0x0800;
|
||||
const quint16 kIp6EthType = 0x86dd;
|
||||
const quint16 kMplsEthType = 0x8847;
|
||||
const QSet<quint16> kVlanEthTypes = {0x8100, 0x9100, 0x88a8};
|
||||
const uint kEthOverhead = 20;
|
||||
|
||||
// VLAN
|
||||
const quint16 kVlanTagSize = 4;
|
||||
|
||||
// MPLS
|
||||
const quint16 kMplsTagSize = 4;
|
||||
|
||||
// IPv4
|
||||
const quint16 kIp4ProtocolOffset = 9;
|
||||
|
||||
// IPv6
|
||||
const quint16 kIp6HeaderSize = 40;
|
||||
const quint16 kIp6NextHeaderOffset = 6;
|
||||
|
||||
// IPv6 Extension Header
|
||||
const quint16 kIp6ExtNextHeaderOffset = 0;
|
||||
const quint16 kIp6ExtLengthOffset = 1;
|
||||
|
||||
// IPv4/IPv6 Proto/NextHeader values
|
||||
const quint8 kIpProtoTcp = 6;
|
||||
const quint8 kIpProtoUdp = 17;
|
||||
|
||||
const QSet<quint8> kIp6ExtensionHeaders = {0, 60, 43, 44, 51, 50, 60, 135}; // FIXME: use names
|
||||
|
||||
// TCP
|
||||
const quint16 kTcpChecksumOffset = 16;
|
||||
|
||||
// UDP
|
||||
const quint16 kUdpChecksumOffset = 6;
|
||||
};
|
||||
|
||||
#endif
|
@ -42,38 +42,6 @@ const quint32 kDltEthernet = 1;
|
||||
|
||||
PcapFileFormat pcapFileFormat;
|
||||
|
||||
PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
|
||||
: QDialog(NULL)
|
||||
{
|
||||
setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
options_ = options;
|
||||
|
||||
viaPdml->setChecked(options_->value("ViaPdml").toBool());
|
||||
// XXX: By default this key is absent - so that pcap import tests
|
||||
// evaluate to false and hence show minimal diffs.
|
||||
// However, for the GUI user, this should be enabled by default.
|
||||
recalculateCksums->setChecked(
|
||||
options_->value("RecalculateCksums", QVariant(true))
|
||||
.toBool());
|
||||
doDiff->setChecked(options_->value("DoDiff").toBool());
|
||||
|
||||
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
}
|
||||
|
||||
PcapImportOptionsDialog::~PcapImportOptionsDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void PcapImportOptionsDialog::accept()
|
||||
{
|
||||
options_->insert("ViaPdml", viaPdml->isChecked());
|
||||
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
|
||||
options_->insert("DoDiff", doDiff->isChecked());
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
PcapFileFormat::PcapFileFormat()
|
||||
{
|
||||
importOptions_.insert("ViaPdml", true);
|
||||
@ -463,7 +431,7 @@ _retry:
|
||||
diffFile.close();
|
||||
if (diffFile.size())
|
||||
{
|
||||
error.append(tr("<p>There is a diff between the original and imported streams. See details to review the diff.</p><p>Why a diff? See <a href='%1'>possible reasons</a>.</p>\n\n\n\n").arg("https://jump.ostinato.org/pcapdiff"));
|
||||
error.append(tr("<p>There is a diff between the original and imported streams. See details to review the diff.</p><p>💡 If you don't need to edit packets, you can retry the import and uncheck the Intelligent Import option.</p><p>Why a diff? See <a href='%1'>possible reasons</a>.</p>\n\n\n\n").arg("https://jump.ostinato.org/pcapdiff"));
|
||||
diffFile.open();
|
||||
diffFile.seek(0);
|
||||
error.append(QString(diffFile.readAll()));
|
||||
@ -731,9 +699,9 @@ _exit:
|
||||
return isOk;
|
||||
}
|
||||
|
||||
QDialog* PcapFileFormat::openOptionsDialog()
|
||||
QVariantMap* PcapFileFormat::options()
|
||||
{
|
||||
return new PcapImportOptionsDialog(&importOptions_);
|
||||
return &importOptions_;
|
||||
}
|
||||
|
||||
bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/)
|
||||
|
@ -20,24 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#define _PCAP_FILE_FORMAT_H
|
||||
|
||||
#include "streamfileformat.h"
|
||||
#include "ui_pcapfileimport.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QVariantMap>
|
||||
|
||||
class PcapImportOptionsDialog: public QDialog, public Ui::PcapFileImport
|
||||
{
|
||||
public:
|
||||
PcapImportOptionsDialog(QVariantMap *options);
|
||||
~PcapImportOptionsDialog();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
|
||||
private:
|
||||
QVariantMap *options_;
|
||||
};
|
||||
|
||||
class PdmlReader;
|
||||
class PcapFileFormat : public StreamFileFormat
|
||||
{
|
||||
@ -52,7 +38,7 @@ public:
|
||||
bool save(const OstProto::StreamConfigList streams,
|
||||
const QString fileName, QString &error);
|
||||
|
||||
virtual QDialog* openOptionsDialog();
|
||||
virtual QVariantMap* options();
|
||||
|
||||
bool isMyFileFormat(const QString fileName);
|
||||
bool isMyFileType(const QString fileType);
|
||||
|
55
common/pcapoptionsdialog.cpp
Normal file
55
common/pcapoptionsdialog.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (C) 2011 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 "pcapoptionsdialog.h"
|
||||
|
||||
PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
|
||||
: QDialog(NULL)
|
||||
{
|
||||
Q_ASSERT(options != NULL);
|
||||
|
||||
setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
options_ = options;
|
||||
|
||||
viaPdml->setChecked(options_->value("ViaPdml").toBool());
|
||||
// XXX: By default this key is absent - so that pcap import tests
|
||||
// evaluate to false and hence show minimal diffs.
|
||||
// However, for the GUI user, this should be enabled by default.
|
||||
recalculateCksums->setChecked(
|
||||
options_->value("RecalculateCksums", QVariant(true))
|
||||
.toBool());
|
||||
doDiff->setChecked(options_->value("DoDiff").toBool());
|
||||
|
||||
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
}
|
||||
|
||||
PcapImportOptionsDialog::~PcapImportOptionsDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void PcapImportOptionsDialog::accept()
|
||||
{
|
||||
options_->insert("ViaPdml", viaPdml->isChecked());
|
||||
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
|
||||
options_->insert("DoDiff", doDiff->isChecked());
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
39
common/pcapoptionsdialog.h
Normal file
39
common/pcapoptionsdialog.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright (C) 2011 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_OPTIONS_DIALOG_H
|
||||
#define _PCAP_OPTIONS_DIALOG_H
|
||||
|
||||
#include "ui_pcapfileimport.h"
|
||||
|
||||
#include <QVariantMap>
|
||||
|
||||
class PcapImportOptionsDialog: public QDialog, public Ui::PcapFileImport
|
||||
{
|
||||
public:
|
||||
PcapImportOptionsDialog(QVariantMap *options);
|
||||
~PcapImportOptionsDialog();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
|
||||
private:
|
||||
QVariantMap *options_;
|
||||
};
|
||||
|
||||
#endif
|
@ -292,6 +292,8 @@ message StreamStats {
|
||||
required StreamGuid stream_guid = 2;
|
||||
|
||||
optional double tx_duration = 3; // in seconds
|
||||
optional uint64 latency = 4; // in nanoseconds
|
||||
optional uint64 jitter = 5; // in nanoseconds
|
||||
|
||||
optional uint64 rx_pkts = 11;
|
||||
optional uint64 rx_bytes = 12;
|
||||
|
@ -32,12 +32,7 @@ SessionFileFormat::~SessionFileFormat()
|
||||
{
|
||||
}
|
||||
|
||||
QDialog* SessionFileFormat::openOptionsDialog()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QDialog* SessionFileFormat::saveOptionsDialog()
|
||||
QVariantMap* SessionFileFormat::options()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
@ -23,10 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "fileformat.pb.h"
|
||||
#include "protocol.pb.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QString>
|
||||
|
||||
class QDialog;
|
||||
#include <QThread>
|
||||
#include <QVariantMap>
|
||||
|
||||
class SessionFileFormat : public QThread
|
||||
{
|
||||
@ -42,8 +41,7 @@ public:
|
||||
virtual bool save(const OstProto::SessionContent &session,
|
||||
const QString fileName, QString &error) = 0;
|
||||
|
||||
virtual QDialog* openOptionsDialog();
|
||||
virtual QDialog* saveOptionsDialog();
|
||||
virtual QVariantMap* options();
|
||||
|
||||
void openAsync(const QString fileName,
|
||||
OstProto::SessionContent &session, QString &error);
|
||||
|
@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "sign.h"
|
||||
|
||||
#include "../common/streambase.h"
|
||||
|
||||
SignProtocol::SignProtocol(StreamBase *stream, AbstractProtocol *parent)
|
||||
: AbstractProtocol(stream, parent)
|
||||
{
|
||||
@ -76,7 +78,9 @@ AbstractProtocol::FieldFlags SignProtocol::fieldFlags(int index) const
|
||||
switch (index)
|
||||
{
|
||||
case sign_magic:
|
||||
case sign_tlv_tx_port:
|
||||
case sign_tlv_guid:
|
||||
case sign_tlv_ttag:
|
||||
case sign_tlv_end:
|
||||
break;
|
||||
|
||||
@ -116,6 +120,52 @@ QVariant SignProtocol::fieldData(int index, FieldAttrib attrib,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sign_tlv_ttag:
|
||||
{
|
||||
switch(attrib)
|
||||
{
|
||||
case FieldName:
|
||||
return QString("T-Tag");
|
||||
case FieldValue:
|
||||
return 0;
|
||||
case FieldTextValue:
|
||||
return QString("%1").arg(0);
|
||||
case FieldFrameValue:
|
||||
{
|
||||
QByteArray fv;
|
||||
fv.resize(2);
|
||||
fv[0] = 0;
|
||||
fv[1] = kTypeLenTtagPlaceholder;
|
||||
return fv;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sign_tlv_tx_port:
|
||||
{
|
||||
switch(attrib)
|
||||
{
|
||||
case FieldName:
|
||||
return QString("TxPort");
|
||||
case FieldValue:
|
||||
return mpStream->portId();
|
||||
case FieldTextValue:
|
||||
return QString("%1").arg(mpStream->portId());
|
||||
case FieldFrameValue:
|
||||
{
|
||||
QByteArray fv;
|
||||
fv.resize(2);
|
||||
fv[0] = mpStream->portId() & 0xFF;
|
||||
fv[1] = kTypeLenTxPort;
|
||||
return fv;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sign_tlv_guid:
|
||||
{
|
||||
quint32 guid = data.stream_guid() & 0xFFFFFF;
|
||||
@ -217,3 +267,29 @@ bool SignProtocol::packetGuid(const uchar *pkt, int pktLen, uint *guid)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SignProtocol::packetTtagId(const uchar *pkt, int pktLen, uint *ttagId, uint *guid)
|
||||
{
|
||||
bool ret = false;
|
||||
const uchar *p = pkt + pktLen - sizeof(kSignMagic);
|
||||
quint32 magic = qFromBigEndian<quint32>(p);
|
||||
if (magic != kSignMagic)
|
||||
return ret;
|
||||
|
||||
*guid = kInvalidGuid;
|
||||
p--;
|
||||
while (*p != kTypeLenEnd) {
|
||||
if (*p == kTypeLenTtag) {
|
||||
*ttagId = *(p - 1);
|
||||
ret = true;
|
||||
} else if (*p == kTypeLenGuid) {
|
||||
*guid = qFromBigEndian<quint32>(p - 3) >> 8;
|
||||
} else if (*p == kTypeLenTxPort) {
|
||||
#ifdef Q_OS_WIN32
|
||||
*ttagId |= uint(*(p - 1)) << 8;
|
||||
#endif
|
||||
}
|
||||
p -= 1 + (*p >> 5); // move to next TLV
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "abstractprotocol.h"
|
||||
#include "sign.pb.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
/*
|
||||
Sign Protocol is expected at the end of the frame (just before the Eth FCS)
|
||||
---+--------+-------+
|
||||
@ -39,9 +41,19 @@ TLVs are encoded as
|
||||
Len does NOT include the one byte of TypeLen
|
||||
Size of the value field varies between 0 to 7 bytes
|
||||
|
||||
Defined TLVs
|
||||
Defined TLVs
|
||||
Type = 0, Len = 0 (0x00): End of TLVs
|
||||
Type = 1, Len = 3 (0x61): Stream GUID
|
||||
Type = 2, Len = 1 (0x22): T-Tag Placeholder (0 value)
|
||||
Type = 3, Len = 1 (0x23): T-Tag with actual value
|
||||
Type = 4, Len = 1 (0x24): Tx Port Id
|
||||
|
||||
Order of TLVs from end of packet towards beginning [Offset, Size]
|
||||
[ -4, 4 bytes] Magic
|
||||
[ -6, 2 bytes] TTag (Placeholder or actual)
|
||||
[-10, 4 bytes] Stream Guid
|
||||
[-12, 2 bytes] Tx Port Id
|
||||
[-13, 1 byte ] End
|
||||
*/
|
||||
|
||||
class SignProtocol : public AbstractProtocol
|
||||
@ -51,7 +63,9 @@ public:
|
||||
{
|
||||
// Frame Fields
|
||||
sign_tlv_end = 0,
|
||||
sign_tlv_tx_port,
|
||||
sign_tlv_guid,
|
||||
sign_tlv_ttag,
|
||||
sign_magic,
|
||||
|
||||
// Meta Fields
|
||||
@ -83,11 +97,19 @@ public:
|
||||
|
||||
static quint32 magic();
|
||||
static bool packetGuid(const uchar *pkt, int pktLen, uint *guid);
|
||||
static bool packetTtagId(const uchar *pkt, int pktLen, uint *ttagId, uint *guid);
|
||||
|
||||
// XXX: Any change in kTypeLenXXX or magic value should also be done in
|
||||
// TxThread/Ttag code as well where hardcoded values are used
|
||||
static const quint32 kMaxGuid = 0x00ffffff;
|
||||
static const quint32 kInvalidGuid = UINT_MAX;
|
||||
static const quint8 kTypeLenTtagPlaceholder = 0x22;
|
||||
static const quint8 kTypeLenTtag = 0x23;
|
||||
private:
|
||||
static const quint32 kSignMagic = 0x1d10c0da; // coda! (unicode - 0x1d10c)
|
||||
static const quint8 kTypeLenEnd = 0x00;
|
||||
static const quint8 kTypeLenGuid = 0x61;
|
||||
static const quint8 kTypeLenTxPort = 0x24;
|
||||
OstProto::Sign data;
|
||||
};
|
||||
|
||||
|
@ -684,6 +684,7 @@ bool StreamBase::preflightCheck(QStringList &result) const
|
||||
bool chkShort = true;
|
||||
bool chkTrunc = true;
|
||||
bool chkJumbo = true;
|
||||
bool chkSignIcmp = true;
|
||||
int count = isFrameSizeVariable() ? frameSizeVariableCount() : 1;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
@ -701,6 +702,17 @@ bool StreamBase::preflightCheck(QStringList &result) const
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (chkSignIcmp && hasProtocol(OstProto::Protocol::kSignFieldNumber)
|
||||
&& hasProtocol(OstProto::Protocol::kIcmpFieldNumber))
|
||||
{
|
||||
result << QObject::tr("Stream statistics are not supported "
|
||||
"for ICMP packets - please use a non-ICMP protocol or "
|
||||
"remove special signature from ICMP streams");
|
||||
chkSignIcmp = false;
|
||||
pass = false;
|
||||
}
|
||||
|
||||
|
||||
if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize)))
|
||||
{
|
||||
result << QObject::tr("One or more frames may be truncated - "
|
||||
|
@ -75,9 +75,8 @@ public:
|
||||
quint32 id() const;
|
||||
bool setId(quint32 id);
|
||||
|
||||
quint32 portId() { return portId_;}
|
||||
#if 0 // FIXME(HI): needed?
|
||||
quint32 portId()
|
||||
{ return mCore->port_id();}
|
||||
bool setPortId(quint32 id)
|
||||
{ mCore->set_port_id(id); return true;}
|
||||
#endif
|
||||
|
@ -35,12 +35,7 @@ StreamFileFormat::~StreamFileFormat()
|
||||
{
|
||||
}
|
||||
|
||||
QDialog* StreamFileFormat::openOptionsDialog()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QDialog* StreamFileFormat::saveOptionsDialog()
|
||||
QVariantMap* StreamFileFormat::options()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
@ -41,8 +41,7 @@ public:
|
||||
virtual bool save(const OstProto::StreamConfigList streams,
|
||||
const QString fileName, QString &error) = 0;
|
||||
|
||||
virtual QDialog* openOptionsDialog();
|
||||
virtual QDialog* saveOptionsDialog();
|
||||
virtual QVariantMap* options();
|
||||
|
||||
void openAsync(const QString fileName,
|
||||
OstProto::StreamConfigList &streams, QString &error);
|
||||
|
@ -32,6 +32,8 @@ Updater::Updater()
|
||||
|
||||
#if 1
|
||||
// Tests!
|
||||
Q_ASSERT(isVersionNewer("1.3.0", "1.2.0") == true);
|
||||
Q_ASSERT(isVersionNewer("1.3.0", "1.1") == true);
|
||||
Q_ASSERT(isVersionNewer("1.2.0", "1.1") == true);
|
||||
Q_ASSERT(isVersionNewer("1.1", "1") == true);
|
||||
Q_ASSERT(isVersionNewer("10.1", "2") == true);
|
||||
|
10
ost.pro
10
ost.pro
@ -1,14 +1,20 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = client server ostproto ostprotogui rpc extra
|
||||
SUBDIRS = client server ostfile ostfilegui ostproto ostprotogui rpc extra
|
||||
|
||||
client.target = client
|
||||
client.file = client/ostinato.pro
|
||||
client.depends = ostproto ostprotogui rpc extra
|
||||
client.depends = ostfile ostfilegui ostproto ostprotogui rpc extra
|
||||
|
||||
server.target = server
|
||||
server.file = server/drone.pro
|
||||
server.depends = ostproto rpc
|
||||
|
||||
ostfile.file = common/ostfile.pro
|
||||
ostfile.depends = ostproto
|
||||
|
||||
ostfilegui.file = common/ostfilegui.pro
|
||||
ostfilegui.depends = ostfile
|
||||
|
||||
ostproto.file = common/ostproto.pro
|
||||
|
||||
ostprotogui.file = common/ostprotogui.pro
|
||||
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "../common/abstractprotocol.h"
|
||||
#include "../common/framevalueattrib.h"
|
||||
#include "../common/packet.h"
|
||||
#include "../common/streambase.h"
|
||||
#include "devicemanager.h"
|
||||
#include "interfaceinfo.h"
|
||||
@ -54,6 +55,8 @@ AbstractPort::AbstractPort(int id, const char *device)
|
||||
maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats
|
||||
memset((void*) &stats_, 0, sizeof(stats_));
|
||||
resetStats();
|
||||
|
||||
streamTiming_ = StreamTiming::instance();
|
||||
}
|
||||
|
||||
AbstractPort::~AbstractPort()
|
||||
@ -197,6 +200,13 @@ void AbstractPort::addNote(QString note)
|
||||
|
||||
bool AbstractPort::setTrackStreamStats(bool enable)
|
||||
{
|
||||
// XXX: This function is called by modify() in context of the RPC
|
||||
// thread (1 thread per connected client), but the StreamTiming
|
||||
// singleton resides in the main thread and its' start/stop methods
|
||||
// start/stop timers which cannot be done across Qt Threads. Hence
|
||||
// this slightly hacky way of invoking those methods
|
||||
QMetaObject::invokeMethod(streamTiming_, enable ? "start" : "stop",
|
||||
Qt::QueuedConnection, Q_ARG(uint, id()));
|
||||
data_.set_is_tracking_stream_stats(enable);
|
||||
|
||||
return true;
|
||||
@ -232,6 +242,10 @@ int AbstractPort::updatePacketList()
|
||||
|
||||
int AbstractPort::updatePacketListSequential()
|
||||
{
|
||||
quint64 duration = 0; // in nanosec
|
||||
quint64 totalPkts = 0;
|
||||
QList<uint> ttagMarkers;
|
||||
uint ttagRepeatInterval;
|
||||
FrameValueAttrib packetListAttrib;
|
||||
long sec = 0;
|
||||
long nsec = 0;
|
||||
@ -259,6 +273,8 @@ int AbstractPort::updatePacketListSequential()
|
||||
quint64 npy1 = 0, npy2 = 0;
|
||||
quint64 loopDelay;
|
||||
ulong frameVariableCount = streamList_[i]->frameVariableCount();
|
||||
bool hasTtag = streamList_[i]->hasProtocol(
|
||||
OstProto::Protocol::kSignFieldNumber);
|
||||
|
||||
// We derive n, x, y such that
|
||||
// n * x + y = total number of packets to be sent
|
||||
@ -326,11 +342,13 @@ int AbstractPort::updatePacketListSequential()
|
||||
|
||||
if (n >= 1) {
|
||||
loopNextPacketSet(x, n, 0, loopDelay);
|
||||
qDebug("PacketSet: n = %lu, x = %lu", n, x);
|
||||
qDebug("PacketSet: n = %lu, x = %lu, delay = %llu ns",
|
||||
n, x, loopDelay);
|
||||
}
|
||||
else if (n == 0)
|
||||
x = 0;
|
||||
|
||||
quint64 pktCount = n*x + y;
|
||||
for (uint j = 0; j < (x+y); j++)
|
||||
{
|
||||
|
||||
@ -347,7 +365,8 @@ int AbstractPort::updatePacketListSequential()
|
||||
// Create a packet set for 'y' with repeat = 1
|
||||
if (j == x) {
|
||||
loopNextPacketSet(y, 1, 0, loopDelay);
|
||||
qDebug("PacketSet: n = 1, y = %lu", y);
|
||||
qDebug("PacketSet: n = 1, y = %lu, delay = %llu",
|
||||
y, loopDelay);
|
||||
}
|
||||
|
||||
qDebug("q(%d, %d) sec = %lu nsec = %lu",
|
||||
@ -383,6 +402,27 @@ int AbstractPort::updatePacketListSequential()
|
||||
}
|
||||
}
|
||||
|
||||
// loopDelay == 0 implies 0 pps i.e. top speed
|
||||
// For ttag calc/config below we need loopDelay to be non-zero,
|
||||
// so we re-calc based on max line rate (speed). If we don't
|
||||
// have the actual port speed, we assume 1000 Mbps
|
||||
if (loopDelay == 0) {
|
||||
double maxSpeed = data_.speed() ? data_.speed(): 1000;
|
||||
double maxPktRate = (maxSpeed*1e6)
|
||||
/(8*(streamList_[i]->frameLenAvg()
|
||||
+ Packet::kEthOverhead));
|
||||
loopDelay = 1e9/maxPktRate; // in nanosec
|
||||
}
|
||||
|
||||
// Add a Ttag marker after every kTtagTimeInterval_ worth of pkts
|
||||
if (hasTtag) {
|
||||
uint ttagPktInterval = kTtagTimeInterval_*1e9/loopDelay;
|
||||
for (uint k = 0; k < pktCount; k += ttagPktInterval)
|
||||
ttagMarkers.append(totalPkts + k);
|
||||
}
|
||||
totalPkts += pktCount;
|
||||
duration += pktCount*loopDelay; // in nanosecs
|
||||
|
||||
switch(streamList_[i]->nextWhat())
|
||||
{
|
||||
case StreamBase::e_nw_stop:
|
||||
@ -401,9 +441,10 @@ int AbstractPort::updatePacketListSequential()
|
||||
returnToQIdx = 0;
|
||||
*/
|
||||
|
||||
setPacketListLoopMode(true, 0,
|
||||
streamList_[i]->sendUnit() ==
|
||||
StreamBase::e_su_bursts ? ibg1 : ipg1);
|
||||
// XXX: no list loop delay required since we don't create
|
||||
// any implicit packet sets now
|
||||
setPacketListLoopMode(true, 0, 0);
|
||||
qDebug("Seq mode list loop true with 0 delay");
|
||||
goto _stop_no_more_pkts;
|
||||
|
||||
case StreamBase::e_nw_goto_next:
|
||||
@ -418,8 +459,17 @@ int AbstractPort::updatePacketListSequential()
|
||||
} // if (stream is enabled)
|
||||
} // for (numStreams)
|
||||
|
||||
_out_of_memory:
|
||||
_stop_no_more_pkts:
|
||||
// See comments in updatePacketListInterleaved() for calc explanation
|
||||
ttagRepeatInterval = ttagMarkers.isEmpty() ? 0 :
|
||||
qMax(uint(kTtagTimeInterval_*1e9/(duration)), 1U)
|
||||
* totalPkts;
|
||||
if (!setPacketListTtagMarkers(ttagMarkers, ttagRepeatInterval)) {
|
||||
clearPacketList(); // don't leave it half baked/inconsitent
|
||||
packetListAttrib.errorFlags |= FrameValueAttrib::OutOfMemoryError;
|
||||
}
|
||||
|
||||
_out_of_memory:
|
||||
isSendQueueDirty_ = false;
|
||||
|
||||
qDebug("PacketListAttrib = %x",
|
||||
@ -432,7 +482,9 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
FrameValueAttrib packetListAttrib;
|
||||
int numStreams = 0;
|
||||
quint64 minGap = ULLONG_MAX;
|
||||
quint64 duration = quint64(1e3);
|
||||
quint64 duration = quint64(1e3); // 1000ns (1us)
|
||||
|
||||
// TODO: convert the below to a QVector of struct aggregating all list vars
|
||||
QList<int> streamId;
|
||||
QList<quint64> ibg1, ibg2;
|
||||
QList<quint64> nb1, nb2;
|
||||
@ -442,6 +494,7 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
QList<ulong> pktCount, burstCount;
|
||||
QList<ulong> burstSize;
|
||||
QList<bool> isVariable;
|
||||
QList<bool> hasTtag;
|
||||
QList<QByteArray> pktBuf;
|
||||
QList<ulong> pktLen;
|
||||
int activeStreamCount = 0;
|
||||
@ -465,6 +518,9 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
// First sort the streams by ordinalValue
|
||||
std::sort(streamList_.begin(), streamList_.end(), StreamBase::StreamLessThan);
|
||||
|
||||
// FIXME: we are calculating n[bp][12], i[bp]g[12] for a duration of 1sec;
|
||||
// this was fine when the actual packet list duration was also 1sec. But
|
||||
// in the current code (post Turbo changes), the latter can be different!
|
||||
for (int i = 0; i < streamList_.size(); i++)
|
||||
{
|
||||
if (!streamList_[i]->isEnabled())
|
||||
@ -588,6 +644,8 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
packetListAttrib += attrib;
|
||||
}
|
||||
|
||||
hasTtag.append(streamList_[i]->hasProtocol(
|
||||
OstProto::Protocol::kSignFieldNumber));
|
||||
numStreams++;
|
||||
} // for i
|
||||
|
||||
@ -595,6 +653,7 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
// i.e. send all streams "simultaneously" as fast as possible
|
||||
// as a result all streams will be at the same rate e.g. for 2 streams,
|
||||
// it would 50% each; for 3 streams - all at 33.3% and so on
|
||||
// FIXME: Should we calc minGap based on max line rate and avg pkt size?
|
||||
if (minGap == ULLONG_MAX) {
|
||||
minGap = 1;
|
||||
duration = 1;
|
||||
@ -610,12 +669,99 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
|
||||
uchar* buf;
|
||||
int len;
|
||||
quint64 durSec = duration/ulong(1e9);
|
||||
quint64 durNsec = duration % ulong(1e9);
|
||||
const quint64 durSec = duration/ulong(1e9);
|
||||
const quint64 durNsec = duration % ulong(1e9);
|
||||
quint64 sec = 0;
|
||||
quint64 nsec = 0;
|
||||
quint64 lastPktTxSec = 0;
|
||||
quint64 lastPktTxNsec = 0;
|
||||
|
||||
// Count total packets we are going to add, so that we can create
|
||||
// an explicit packet set first
|
||||
// TODO: Find less expensive way to do this counting
|
||||
quint64 totalPkts = 0;
|
||||
QVector<ulong> ttagSchedSec(numStreams, 0);
|
||||
QVector<ulong> ttagSchedNsec(numStreams, 0);
|
||||
QList<uint> ttagMarkers;
|
||||
uint ttagRepeatInterval;
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < numStreams; i++)
|
||||
{
|
||||
// If a packet is not scheduled yet, look at the next stream
|
||||
if ((schedSec.at(i) > sec) || (schedNsec.at(i) > nsec))
|
||||
continue;
|
||||
|
||||
// Ttag marker every TtagTimeInterval for each stream
|
||||
if (hasTtag.at(i)
|
||||
&& ((schedSec.at(i) > ttagSchedSec.at(i))
|
||||
|| ((schedSec.at(i) == ttagSchedSec.at(i))
|
||||
&& (schedNsec.at(i) >= ttagSchedNsec.at(i))))) {
|
||||
ttagMarkers.append(totalPkts);
|
||||
ttagSchedSec[i] = schedSec.at(i) + kTtagTimeInterval_;
|
||||
ttagSchedNsec[i] = schedNsec.at(i);
|
||||
}
|
||||
|
||||
for (uint j = 0; j < burstSize[i]; j++)
|
||||
{
|
||||
pktCount[i]++;
|
||||
schedNsec[i] += (pktCount.at(i) < np1.at(i)) ?
|
||||
ipg1.at(i) : ipg2.at(i);
|
||||
while (schedNsec.at(i) >= 1e9)
|
||||
{
|
||||
schedSec[i]++;
|
||||
schedNsec[i] -= long(1e9);
|
||||
}
|
||||
lastPktTxSec = sec;
|
||||
lastPktTxNsec = nsec;
|
||||
totalPkts++;
|
||||
}
|
||||
|
||||
burstCount[i]++;
|
||||
schedNsec[i] += (burstCount.at(i) < nb1.at(i)) ?
|
||||
ibg1.at(i) : ibg2.at(i);
|
||||
while (schedNsec.at(i) >= 1e9)
|
||||
{
|
||||
schedSec[i]++;
|
||||
schedNsec[i] -= long(1e9);
|
||||
}
|
||||
}
|
||||
|
||||
nsec += minGap;
|
||||
while (nsec >= 1e9)
|
||||
{
|
||||
sec++;
|
||||
nsec -= long(1e9);
|
||||
}
|
||||
} while ((sec < durSec) || ((sec == durSec) && (nsec < durNsec)));
|
||||
|
||||
qint64 delaySec = durSec - lastPktTxSec;
|
||||
qint64 delayNsec = durNsec - lastPktTxNsec;
|
||||
while (delayNsec < 0)
|
||||
{
|
||||
delayNsec += long(1e9);
|
||||
delaySec--;
|
||||
}
|
||||
|
||||
// XXX: For interleaved mode, we ALWAYS have a single packet set with
|
||||
// one repeat
|
||||
loopNextPacketSet(totalPkts, 1, delaySec, delayNsec);
|
||||
qDebug("Interleaved single PacketSet of size %lld, duration %llu.%09llu "
|
||||
"repeat 1 and delay %lld.%09lld",
|
||||
totalPkts, durSec, durNsec, delaySec, delayNsec);
|
||||
|
||||
// Reset working sched/counts before building the packet list
|
||||
sec = nsec = 0;
|
||||
for (int i = 0; i < numStreams; i++)
|
||||
{
|
||||
schedSec[i] = 0;
|
||||
schedNsec[i] = 0;
|
||||
pktCount[i] = 0;
|
||||
burstCount[i] = 0;
|
||||
}
|
||||
|
||||
// Now build the packet list
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < numStreams; i++)
|
||||
@ -643,14 +789,12 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
if (len <= 0)
|
||||
continue;
|
||||
|
||||
qDebug("q(%d) sec = %llu nsec = %llu", i, sec, nsec);
|
||||
qDebug("q(%d) TS = %llu.%09llu", i, sec, nsec);
|
||||
if (!appendToPacketList(sec, nsec, buf, len)) {
|
||||
clearPacketList(); // don't leave it half baked/inconsitent
|
||||
packetListAttrib.errorFlags |= FrameValueAttrib::OutOfMemoryError;
|
||||
goto _out_of_memory;
|
||||
}
|
||||
lastPktTxSec = sec;
|
||||
lastPktTxNsec = nsec;
|
||||
|
||||
pktCount[i]++;
|
||||
schedNsec[i] += (pktCount.at(i) < np1.at(i)) ?
|
||||
@ -678,18 +822,25 @@ int AbstractPort::updatePacketListInterleaved()
|
||||
sec++;
|
||||
nsec -= long(1e9);
|
||||
}
|
||||
} while ((sec < durSec) || (nsec < durNsec));
|
||||
} while ((sec < durSec) || ((sec == durSec) && (nsec < durNsec)));
|
||||
|
||||
{
|
||||
qint64 delaySec = durSec - lastPktTxSec;
|
||||
qint64 delayNsec = durNsec - lastPktTxNsec;
|
||||
while (delayNsec < 0)
|
||||
{
|
||||
delayNsec += long(1e9);
|
||||
delaySec--;
|
||||
}
|
||||
qDebug("loop Delay = %lld/%lld", delaySec, delayNsec);
|
||||
setPacketListLoopMode(true, delaySec, delayNsec);
|
||||
// XXX: The single packet has the delay, so no list loop delay required
|
||||
// XXX: Both seq/interleaved mode no longer use list loop delay!
|
||||
setPacketListLoopMode(true, 0, 0);
|
||||
|
||||
// XXX: TTag repeat interval calculation:
|
||||
// CASE 1. pktListDuration < kTtagTimeInterval:
|
||||
// e.g. if pktListDuration is 1sec and TtagTimerInterval is 5s, we
|
||||
// skip 5 times total packets before we repeat the markers
|
||||
// CASE 2. pktListDuration > kTtagTimeInterval:
|
||||
// e.g. if pktListDuration is 7sec and TtagTimerInterval is 5s, we
|
||||
// skip repeat markers every pktList iteration
|
||||
ttagRepeatInterval = ttagMarkers.isEmpty() ? 0 :
|
||||
qMax(uint(kTtagTimeInterval_*1e9/(durSec*1e9+durNsec)), 1U)
|
||||
* totalPkts;
|
||||
if (!setPacketListTtagMarkers(ttagMarkers, ttagRepeatInterval)) {
|
||||
clearPacketList(); // don't leave it half baked/inconsitent
|
||||
packetListAttrib.errorFlags |= FrameValueAttrib::OutOfMemoryError;
|
||||
}
|
||||
|
||||
_out_of_memory:
|
||||
@ -734,20 +885,37 @@ void AbstractPort::stats(PortStats *stats)
|
||||
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
|
||||
}
|
||||
|
||||
StreamTiming::Stats AbstractPort::streamTimingStats(uint guid)
|
||||
{
|
||||
return streamTiming_->stats(id(), guid);
|
||||
}
|
||||
|
||||
void AbstractPort::clearStreamTiming(uint guid)
|
||||
{
|
||||
streamTiming_->clear(id(), guid);
|
||||
}
|
||||
|
||||
void AbstractPort::streamStats(uint guid, OstProto::StreamStatsList *stats)
|
||||
{
|
||||
// In case stats are being maintained elsewhere
|
||||
updateStreamStats();
|
||||
|
||||
// Lock for read here as updateStreamStats() above will take write lock
|
||||
// and the lock is NOT recursive
|
||||
QReadLocker lock(&streamStatsLock_);
|
||||
|
||||
if (streamStats_.contains(guid))
|
||||
{
|
||||
StreamStatsTuple sst = streamStats_.value(guid);
|
||||
OstProto::StreamStats *s = stats->add_stream_stats();
|
||||
StreamTiming::Stats t = streamTimingStats(guid);
|
||||
|
||||
s->mutable_stream_guid()->set_id(guid);
|
||||
s->mutable_port_id()->set_id(id());
|
||||
|
||||
s->set_tx_duration(lastTransmitDuration());
|
||||
s->set_latency(t.latency);
|
||||
s->set_jitter(t.jitter);
|
||||
|
||||
s->set_tx_pkts(sst.tx_pkts);
|
||||
s->set_tx_bytes(sst.tx_bytes);
|
||||
@ -761,6 +929,10 @@ void AbstractPort::streamStatsAll(OstProto::StreamStatsList *stats)
|
||||
// In case stats are being maintained elsewhere
|
||||
updateStreamStats();
|
||||
|
||||
// Lock for read here as updateStreamStats() above will take write lock
|
||||
// and the lock is NOT recursive
|
||||
QReadLocker lock(&streamStatsLock_);
|
||||
|
||||
// FIXME: change input param to a non-OstProto type and/or have
|
||||
// a getFirst/Next like API?
|
||||
double txDur = lastTransmitDuration();
|
||||
@ -770,11 +942,14 @@ void AbstractPort::streamStatsAll(OstProto::StreamStatsList *stats)
|
||||
i.next();
|
||||
StreamStatsTuple sst = i.value();
|
||||
OstProto::StreamStats *s = stats->add_stream_stats();
|
||||
StreamTiming::Stats t = streamTimingStats(i.key());
|
||||
|
||||
s->mutable_stream_guid()->set_id(i.key());
|
||||
s->mutable_port_id()->set_id(id());
|
||||
|
||||
s->set_tx_duration(txDur);
|
||||
s->set_latency(t.latency);
|
||||
s->set_jitter(t.jitter);
|
||||
|
||||
s->set_tx_pkts(sst.tx_pkts);
|
||||
s->set_tx_bytes(sst.tx_bytes);
|
||||
@ -785,12 +960,16 @@ void AbstractPort::streamStatsAll(OstProto::StreamStatsList *stats)
|
||||
|
||||
void AbstractPort::resetStreamStats(uint guid)
|
||||
{
|
||||
QWriteLocker lock(&streamStatsLock_);
|
||||
streamStats_.remove(guid);
|
||||
clearStreamTiming(guid);
|
||||
}
|
||||
|
||||
void AbstractPort::resetStreamStatsAll()
|
||||
{
|
||||
QWriteLocker lock(&streamStatsLock_);
|
||||
streamStats_.clear();
|
||||
clearStreamTiming();
|
||||
}
|
||||
|
||||
void AbstractPort::clearDeviceNeighbors()
|
||||
|
@ -20,18 +20,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#ifndef _SERVER_ABSTRACT_PORT_H
|
||||
#define _SERVER_ABSTRACT_PORT_H
|
||||
|
||||
#include "../common/protocol.pb.h"
|
||||
#include "streamstats.h"
|
||||
#include "streamtiming.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QReadWriteLock>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "../common/protocol.pb.h"
|
||||
#include <limits.h>
|
||||
|
||||
class DeviceManager;
|
||||
struct InterfaceInfo;
|
||||
class StreamBase;
|
||||
class PacketBuffer;
|
||||
class QIODevice;
|
||||
class StreamBase;
|
||||
|
||||
// TODO: send notification back to client(s)
|
||||
#define Xnotify qWarning
|
||||
@ -105,6 +108,8 @@ public:
|
||||
int length) = 0;
|
||||
virtual void setPacketListLoopMode(bool loop,
|
||||
quint64 secDelay, quint64 nsecDelay) = 0;
|
||||
virtual bool setPacketListTtagMarkers(QList<uint> markers,
|
||||
uint repeatInterval) = 0;
|
||||
int updatePacketList();
|
||||
|
||||
virtual void startTransmit() = 0;
|
||||
@ -120,6 +125,9 @@ public:
|
||||
void stats(PortStats *stats);
|
||||
void resetStats() { epochStats_ = stats_; }
|
||||
|
||||
StreamTiming::Stats streamTimingStats(uint guid);
|
||||
void clearStreamTiming(uint guid = UINT_MAX);
|
||||
|
||||
// FIXME: combine single and All calls?
|
||||
void streamStats(uint guid, OstProto::StreamStatsList *stats);
|
||||
void streamStatsAll(OstProto::StreamStatsList *stats);
|
||||
@ -156,8 +164,11 @@ protected:
|
||||
quint64 maxStatsValue_;
|
||||
struct PortStats stats_;
|
||||
StreamStats streamStats_;
|
||||
QReadWriteLock streamStatsLock_;
|
||||
//! \todo Need lock for stats access/update
|
||||
|
||||
const uint kTtagTimeInterval_{5}; // in seconds
|
||||
|
||||
struct InterfaceInfo *interfaceInfo_;
|
||||
DeviceManager *deviceManager_;
|
||||
|
||||
@ -177,6 +188,8 @@ private:
|
||||
QList<StreamBase*> streamList_;
|
||||
|
||||
struct PortStats epochStats_;
|
||||
|
||||
StreamTiming *streamTiming_{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -7,6 +7,8 @@ linux*:system(grep -q IFLA_STATS64 /usr/include/linux/if_link.h): \
|
||||
DEFINES += HAVE_IFLA_STATS64
|
||||
INCLUDEPATH += "../common"
|
||||
INCLUDEPATH += "../rpc"
|
||||
|
||||
OBJDIR = .
|
||||
win32 {
|
||||
# Support Windows Vista and above only
|
||||
DEFINES += WIN32_LEAN_AND_MEAN NTDDI_VERSION=0x06000000 _WIN32_WINNT=0x0600
|
||||
@ -15,24 +17,19 @@ win32 {
|
||||
QMAKE_LFLAGS += -static
|
||||
LIBS += -lwpcap -lpacket -liphlpapi
|
||||
CONFIG(debug, debug|release) {
|
||||
LIBS += -L"../common/debug" -lostproto
|
||||
LIBS += -L"../rpc/debug" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/debug/libostproto.a" \
|
||||
"../rpc/debug/libpbrpc.a"
|
||||
OBJDIR = debug
|
||||
} else {
|
||||
LIBS += -L"../common/release" -lostproto
|
||||
LIBS += -L"../rpc/release" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/release/libostproto.a" \
|
||||
"../rpc/release/libpbrpc.a"
|
||||
OBJDIR = release
|
||||
}
|
||||
} else {
|
||||
LIBS += -lpcap
|
||||
LIBS += -L"../common" -lostproto
|
||||
LIBS += -L"../rpc" -lpbrpc
|
||||
POST_TARGETDEPS += "../common/libostproto.a" "../rpc/libpbrpc.a"
|
||||
}
|
||||
LIBS += -L"../common/$$OBJDIR" -lostproto
|
||||
LIBS += -L"../rpc/$$OBJDIR" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/$$OBJDIR//libostproto.a" \
|
||||
"../rpc/$$OBJDIR/libpbrpc.a"
|
||||
|
||||
linux {
|
||||
INCLUDEPATH += "/usr/include/libnl3"
|
||||
LIBS += -lnl-3 -lnl-route-3
|
||||
@ -41,7 +38,8 @@ LIBS += -lm
|
||||
LIBS += -lprotobuf
|
||||
HEADERS += drone.h \
|
||||
pcaptransmitter.h \
|
||||
myservice.h
|
||||
myservice.h \
|
||||
streamtiming.h
|
||||
SOURCES += \
|
||||
devicemanager.cpp \
|
||||
device.cpp \
|
||||
@ -56,12 +54,14 @@ SOURCES += \
|
||||
pcaprxstats.cpp \
|
||||
pcaptxstats.cpp \
|
||||
pcaptxthread.cpp \
|
||||
pcaptxttagstats.cpp \
|
||||
bsdhostdevice.cpp \
|
||||
bsdport.cpp \
|
||||
linuxhostdevice.cpp \
|
||||
linuxport.cpp \
|
||||
linuxutils.cpp \
|
||||
params.cpp \
|
||||
streamtiming.cpp \
|
||||
turbo.cpp \
|
||||
winhostdevice.cpp \
|
||||
winpcapport.cpp
|
||||
|
@ -20,8 +20,9 @@ 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/packet.h"
|
||||
#include "../common/sign.h"
|
||||
#include "pcapextra.h"
|
||||
#include "streamstats.h"
|
||||
|
||||
class PacketSequence
|
||||
@ -37,6 +38,7 @@ public:
|
||||
repeatCount_ = 1;
|
||||
repeatSize_ = 1;
|
||||
usecDelay_ = 0;
|
||||
ttagL4CksumOffset_ = 0;
|
||||
}
|
||||
~PacketSequence() {
|
||||
pcap_sendqueue_destroy(sendQueue_);
|
||||
@ -69,6 +71,16 @@ public:
|
||||
streamStatsMeta_[guid].tx_bytes += pktHeader->caplen;
|
||||
}
|
||||
}
|
||||
// TODO: A PacketSequence belongs to a unique stream only in case of
|
||||
// sequential streams; for interleaved streams, we have only a single
|
||||
// packet set (with one or more sequences) containing packets from
|
||||
// multiple streams. To support this, we need to make l4cksum a packet
|
||||
// property not a sequence property
|
||||
// Till the above is fixed, Ttag packets will have wrong checksum
|
||||
#if 0
|
||||
if (trackGuidStats_ && (packets_ == 1)) // first packet of seq
|
||||
ttagL4CksumOffset_ = Packet::l4ChecksumOffset(pktData, pktHeader->caplen);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
pcap_send_queue *sendQueue_;
|
||||
@ -79,6 +91,7 @@ public:
|
||||
int repeatCount_;
|
||||
int repeatSize_;
|
||||
long usecDelay_;
|
||||
quint16 ttagL4CksumOffset_; // For ttag packets
|
||||
StreamStats streamStatsMeta_;
|
||||
|
||||
private:
|
||||
|
@ -31,10 +31,11 @@ PcapPort::PcapPort(int id, const char *device)
|
||||
{
|
||||
monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_);
|
||||
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
|
||||
transmitter_ = new PcapTransmitter(device, streamStats_);
|
||||
transmitter_ = new PcapTransmitter(device);
|
||||
capturer_ = new PortCapturer(device);
|
||||
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
|
||||
rxStatsPoller_ = new PcapRxStats(device, streamStats_, id);
|
||||
txTtagStatsPoller_ = new PcapTxTtagStats(device, id);
|
||||
rxStatsPoller_ = new PcapRxStats(device, id);
|
||||
|
||||
if (!monitorRx_->handle() || !monitorTx_->handle())
|
||||
isUsable_ = false;
|
||||
@ -85,6 +86,9 @@ PcapPort::~PcapPort()
|
||||
if (monitorTx_)
|
||||
monitorTx_->stop();
|
||||
|
||||
txTtagStatsPoller_->stop();
|
||||
delete txTtagStatsPoller_;
|
||||
|
||||
rxStatsPoller_->stop();
|
||||
delete rxStatsPoller_;
|
||||
|
||||
@ -144,9 +148,16 @@ bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
|
||||
|
||||
void PcapPort::updateStreamStats()
|
||||
{
|
||||
// XXX: PcapTxThread already does this at the end of transmit; we
|
||||
// just dump rx stats poller debug stats here
|
||||
QWriteLocker lock(&streamStatsLock_);
|
||||
|
||||
// XXX: Transmitter may also 'adjust' rx stats in some cases (pcap
|
||||
// direction not supported platforms)
|
||||
transmitter_->updateTxRxStreamStats(streamStats_);
|
||||
rxStatsPoller_->updateRxStreamStats(streamStats_);
|
||||
|
||||
// Dump tx/rx stats poller debug stats
|
||||
qDebug("port %d txTtagStatsPoller: %s",
|
||||
id(), qUtf8Printable(txTtagStatsPoller_->debugStats()));
|
||||
qDebug("port %d rxStatsPoller: %s",
|
||||
id(), qUtf8Printable(rxStatsPoller_->debugStats()));
|
||||
}
|
||||
@ -170,6 +181,8 @@ bool PcapPort::startStreamStatsTracking()
|
||||
{
|
||||
if (!transmitter_->setStreamStatsTracking(true))
|
||||
goto _tx_fail;
|
||||
if (!txTtagStatsPoller_->start())
|
||||
goto _tx_ttag_fail;
|
||||
if (!rxStatsPoller_->start())
|
||||
goto _rx_fail;
|
||||
/*
|
||||
@ -183,6 +196,8 @@ bool PcapPort::startStreamStatsTracking()
|
||||
return true;
|
||||
|
||||
_rx_fail:
|
||||
txTtagStatsPoller_->stop();
|
||||
_tx_ttag_fail:
|
||||
transmitter_->setStreamStatsTracking(false);
|
||||
_tx_fail:
|
||||
qWarning("failed to start stream stats tracking");
|
||||
@ -191,17 +206,22 @@ _tx_fail:
|
||||
|
||||
bool PcapPort::stopStreamStatsTracking()
|
||||
{
|
||||
if (!transmitter_->setStreamStatsTracking(false))
|
||||
goto _tx_fail;
|
||||
if (!rxStatsPoller_->stop())
|
||||
goto _rx_fail;
|
||||
return true;
|
||||
bool ret = true;
|
||||
|
||||
_rx_fail:
|
||||
transmitter_->setStreamStatsTracking(true);
|
||||
_tx_fail:
|
||||
qWarning("failed to stop stream stats tracking");
|
||||
return false;
|
||||
if (!transmitter_->setStreamStatsTracking(false)) {
|
||||
qWarning("failed to stop Transmitter stream stats tracking");
|
||||
ret = false;
|
||||
}
|
||||
if (!txTtagStatsPoller_->stop()) {
|
||||
qWarning("failed to stop TxTtag stream stats thread");
|
||||
ret = false;
|
||||
}
|
||||
if (!rxStatsPoller_->stop()) {
|
||||
qWarning("failed to stop Rx stream stats thread");
|
||||
ret = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -381,6 +401,8 @@ PcapPort::PortCapturer::PortCapturer(const char *device)
|
||||
|
||||
PcapPort::PortCapturer::~PortCapturer()
|
||||
{
|
||||
if (isRunning())
|
||||
stop();
|
||||
capFile_.close();
|
||||
}
|
||||
|
||||
@ -489,7 +511,7 @@ void PcapPort::PortCapturer::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
PcapSession::stop(handle_);
|
||||
PcapSession::stop();
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
@ -529,7 +551,8 @@ PcapPort::EmulationTransceiver::EmulationTransceiver(const char *device,
|
||||
|
||||
PcapPort::EmulationTransceiver::~EmulationTransceiver()
|
||||
{
|
||||
stop();
|
||||
if (isRunning())
|
||||
stop();
|
||||
}
|
||||
|
||||
void PcapPort::EmulationTransceiver::run()
|
||||
@ -713,12 +736,12 @@ void PcapPort::EmulationTransceiver::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
PcapSession::stop(handle_);
|
||||
PcapSession::stop();
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
else {
|
||||
qWarning("Receive stop requested but is not running!");
|
||||
qWarning("Emulation Xcvr stop requested but is not running!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "abstractport.h"
|
||||
#include "pcapextra.h"
|
||||
#include "pcaprxstats.h"
|
||||
#include "pcaptxttagstats.h"
|
||||
#include "pcapsession.h"
|
||||
#include "pcaptransmitter.h"
|
||||
|
||||
@ -47,6 +48,7 @@ public:
|
||||
virtual void clearPacketList() {
|
||||
transmitter_->clearPacketList();
|
||||
setPacketListLoopMode(false, 0, 0);
|
||||
setPacketListTtagMarkers(QList<uint>(), 0);
|
||||
}
|
||||
virtual void loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec) {
|
||||
@ -61,6 +63,11 @@ public:
|
||||
{
|
||||
transmitter_->setPacketListLoopMode(loop, secDelay, nsecDelay);
|
||||
}
|
||||
virtual bool setPacketListTtagMarkers(QList<uint> markers,
|
||||
uint repeatInterval)
|
||||
{
|
||||
return transmitter_->setPacketListTtagMarkers(markers, repeatInterval);
|
||||
}
|
||||
|
||||
virtual void startTransmit() {
|
||||
Q_ASSERT(!isDirty());
|
||||
@ -134,7 +141,6 @@ protected:
|
||||
QString device_;
|
||||
volatile bool stop_;
|
||||
QTemporaryFile capFile_;
|
||||
pcap_t *handle_;
|
||||
pcap_dumper_t *dumpHandle_;
|
||||
volatile State state_;
|
||||
};
|
||||
@ -161,13 +167,14 @@ protected:
|
||||
QString device_;
|
||||
DeviceManager *deviceManager_;
|
||||
volatile bool stop_;
|
||||
pcap_t *handle_;
|
||||
volatile State state_;
|
||||
};
|
||||
|
||||
PortMonitor *monitorRx_;
|
||||
PortMonitor *monitorTx_;
|
||||
|
||||
PcapRxStats *rxStatsPoller_;
|
||||
|
||||
void updateNotes();
|
||||
|
||||
private:
|
||||
@ -177,7 +184,7 @@ private:
|
||||
PcapTransmitter *transmitter_;
|
||||
PortCapturer *capturer_;
|
||||
EmulationTransceiver *emulXcvr_;
|
||||
PcapRxStats *rxStatsPoller_;
|
||||
PcapTxTtagStats *txTtagStatsPoller_;
|
||||
|
||||
static pcap_if_t *deviceList_;
|
||||
};
|
||||
|
@ -20,13 +20,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "pcaprxstats.h"
|
||||
|
||||
#include "pcapextra.h"
|
||||
#include "../common/debugdefs.h"
|
||||
#include "../common/sign.h"
|
||||
#include "settings.h"
|
||||
#include "streamtiming.h"
|
||||
|
||||
#define Xnotify qWarning // FIXME
|
||||
|
||||
PcapRxStats::PcapRxStats(const char *device, StreamStats &portStreamStats, int id)
|
||||
: streamStats_(portStreamStats)
|
||||
PcapRxStats::PcapRxStats(const char *device, int id)
|
||||
{
|
||||
setObjectName(QString("Rx$:%1").arg(device));
|
||||
device_ = QString::fromLatin1(device);
|
||||
stop_ = false;
|
||||
state_ = kNotStarted;
|
||||
@ -34,7 +37,9 @@ PcapRxStats::PcapRxStats(const char *device, StreamStats &portStreamStats, int i
|
||||
|
||||
handle_ = NULL;
|
||||
|
||||
id_ = id;
|
||||
portId_ = id;
|
||||
|
||||
timing_ = StreamTiming::instance();
|
||||
}
|
||||
|
||||
pcap_t* PcapRxStats::handle()
|
||||
@ -52,6 +57,20 @@ void PcapRxStats::run()
|
||||
SignProtocol::magic(), 0, BASE_HEX);
|
||||
// XXX: Exclude ICMP packets which contain an embedded signed packet
|
||||
// For now we check upto 4 vlan tags
|
||||
// XXX: libpcap for Linux has a special bpf vlan check which generates
|
||||
// incorrect BPF instructions for our capture filter expression,
|
||||
// so we modify it to work correctly
|
||||
// See https://srivatsp.com/ostinato/ostinato-rx-stream-stats-zero/
|
||||
#ifdef Q_OS_LINUX
|
||||
capture_filter.prepend(
|
||||
"not ("
|
||||
"icmp or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) "
|
||||
") and ");
|
||||
#else
|
||||
capture_filter.append(
|
||||
"and not ("
|
||||
"icmp or "
|
||||
@ -60,8 +79,15 @@ void PcapRxStats::run()
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) "
|
||||
")");
|
||||
#endif
|
||||
|
||||
// Override filter expression if one is specified in .ini
|
||||
if (appSettings->contains(kInternalRxStatsFilterKey))
|
||||
capture_filter = appSettings->value(kInternalRxStatsFilterKey)
|
||||
.toString();
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
qDebug("RxStats Filter: %s", qPrintable(capture_filter));
|
||||
|
||||
handle_ = pcap_open_live(qPrintable(device_), 65535,
|
||||
flags, 100 /* ms */, errbuf);
|
||||
@ -106,7 +132,7 @@ void PcapRxStats::run()
|
||||
}
|
||||
|
||||
_skip_filter:
|
||||
memset(&lastPcapStats_, 0, sizeof(lastPcapStats_));
|
||||
clearDebugStats();
|
||||
PcapSession::preRun();
|
||||
state_ = kRunning;
|
||||
while (1) {
|
||||
@ -117,8 +143,25 @@ _skip_filter:
|
||||
ret = pcap_next_ex(handle_, &hdr, &data);
|
||||
switch (ret) {
|
||||
case 1: {
|
||||
uint guid;
|
||||
if (SignProtocol::packetGuid(data, hdr->caplen, &guid)) {
|
||||
uint ttagId, guid;
|
||||
#ifdef Q_OS_WIN32
|
||||
// Npcap (Windows) doesn't support direction, so packets
|
||||
// Tx by PcapTxThread are received back by us here - use
|
||||
// TxPort to filter out. TxPort is returned as byte 1 of
|
||||
// ttagId (byte 0 is ttagId).
|
||||
// If TxPort is us ==> Tx Packet, so skip
|
||||
// FIXME: remove once npcap supports pcap direction
|
||||
if (SignProtocol::packetTtagId(data, hdr->caplen, &ttagId, &guid)
|
||||
&& (ttagId >> 8 != uint(portId_))) {
|
||||
ttagId &= 0xFF;
|
||||
timing_->recordRxTime(portId_, guid, ttagId, hdr->ts);
|
||||
}
|
||||
#else
|
||||
if (SignProtocol::packetTtagId(data, hdr->caplen, &ttagId, &guid)) {
|
||||
timing_->recordRxTime(portId_, guid, ttagId, hdr->ts);
|
||||
}
|
||||
#endif
|
||||
if (guid != SignProtocol::kInvalidGuid) {
|
||||
streamStats_[guid].rx_pkts++;
|
||||
streamStats_[guid].rx_bytes += hdr->caplen;
|
||||
}
|
||||
@ -174,7 +217,7 @@ bool PcapRxStats::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
PcapSession::stop(handle_);
|
||||
PcapSession::stop();
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
@ -194,56 +237,20 @@ bool PcapRxStats::isDirectional()
|
||||
return isDirectional_;
|
||||
}
|
||||
|
||||
// XXX: Implemented as reset on read
|
||||
QString PcapRxStats::debugStats()
|
||||
// XXX: Stats are reset on read
|
||||
void PcapRxStats::updateRxStreamStats(StreamStats &streamStats)
|
||||
{
|
||||
QString dbgStats;
|
||||
QMutexLocker lock(&streamStatsLock_);
|
||||
StreamStatsIterator i(streamStats_);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
static_assert(sizeof(struct pcap_stat) == 6*sizeof(uint),
|
||||
"pcap_stat has less or more than 6 values");
|
||||
int size;
|
||||
struct pcap_stat incPcapStats;
|
||||
struct pcap_stat *pcapStats = pcap_stats_ex(handle_, &size);
|
||||
if (pcapStats && (uint(size) >= 6*sizeof(uint))) {
|
||||
incPcapStats.ps_recv = pcapStats->ps_recv - lastPcapStats_.ps_recv;
|
||||
incPcapStats.ps_drop = pcapStats->ps_drop - lastPcapStats_.ps_drop;
|
||||
incPcapStats.ps_ifdrop = pcapStats->ps_ifdrop - lastPcapStats_.ps_ifdrop;
|
||||
incPcapStats.ps_capt = pcapStats->ps_capt - lastPcapStats_.ps_capt;
|
||||
incPcapStats.ps_sent = pcapStats->ps_sent - lastPcapStats_.ps_sent;
|
||||
incPcapStats.ps_netdrop = pcapStats->ps_netdrop - lastPcapStats_.ps_netdrop;
|
||||
dbgStats = QString("recv: %1 drop: %2 ifdrop: %3 "
|
||||
"capt: %4 sent: %5 netdrop: %6")
|
||||
.arg(incPcapStats.ps_recv)
|
||||
.arg(incPcapStats.ps_drop)
|
||||
.arg(incPcapStats.ps_ifdrop)
|
||||
.arg(incPcapStats.ps_capt)
|
||||
.arg(incPcapStats.ps_sent)
|
||||
.arg(incPcapStats.ps_netdrop);
|
||||
lastPcapStats_ = *pcapStats;
|
||||
} else {
|
||||
dbgStats = QString("error reading pcap stats: %1")
|
||||
.arg(pcap_geterr(handle_));
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
uint guid = i.key();
|
||||
StreamStatsTuple sst = i.value();
|
||||
|
||||
streamStats[guid].rx_pkts += sst.rx_pkts;
|
||||
streamStats[guid].rx_bytes += sst.rx_bytes;
|
||||
}
|
||||
#else
|
||||
struct pcap_stat pcapStats;
|
||||
struct pcap_stat incPcapStats;
|
||||
|
||||
int ret = pcap_stats(handle_, &pcapStats);
|
||||
if (ret == 0) {
|
||||
incPcapStats.ps_recv = pcapStats.ps_recv - lastPcapStats_.ps_recv;
|
||||
incPcapStats.ps_drop = pcapStats.ps_drop - lastPcapStats_.ps_drop;
|
||||
incPcapStats.ps_ifdrop = pcapStats.ps_ifdrop - lastPcapStats_.ps_ifdrop;
|
||||
dbgStats = QString("recv: %1 drop: %2 ifdrop: %3")
|
||||
.arg(incPcapStats.ps_recv)
|
||||
.arg(incPcapStats.ps_drop)
|
||||
.arg(incPcapStats.ps_ifdrop);
|
||||
lastPcapStats_ = pcapStats;
|
||||
} else {
|
||||
dbgStats = QString("error reading pcap stats: %1")
|
||||
.arg(pcap_geterr(handle_));
|
||||
}
|
||||
#endif
|
||||
|
||||
return dbgStats;
|
||||
streamStats_.clear();
|
||||
}
|
||||
|
@ -24,10 +24,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "pcapsession.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
class StreamTiming;
|
||||
|
||||
class PcapRxStats: public PcapSession
|
||||
{
|
||||
public:
|
||||
PcapRxStats(const char *device, StreamStats &portStreamStats, int id);
|
||||
PcapRxStats(const char *device, int id);
|
||||
pcap_t* handle();
|
||||
void run();
|
||||
bool start();
|
||||
@ -35,8 +39,7 @@ public:
|
||||
bool isRunning();
|
||||
bool isDirectional();
|
||||
|
||||
QString debugStats();
|
||||
|
||||
void updateRxStreamStats(StreamStats &streamStats); // Reset on read
|
||||
private:
|
||||
enum State {
|
||||
kNotStarted,
|
||||
@ -45,14 +48,15 @@ private:
|
||||
};
|
||||
|
||||
QString device_;
|
||||
StreamStats &streamStats_;
|
||||
StreamStats streamStats_;
|
||||
QMutex streamStatsLock_;
|
||||
volatile bool stop_;
|
||||
pcap_t *handle_;
|
||||
volatile State state_;
|
||||
bool isDirectional_;
|
||||
|
||||
int id_;
|
||||
struct pcap_stat lastPcapStats_;
|
||||
int portId_;
|
||||
|
||||
StreamTiming *timing_{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -19,6 +19,69 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "pcapsession.h"
|
||||
|
||||
// XXX: Implemented as reset on read
|
||||
QString PcapSession::debugStats()
|
||||
{
|
||||
QString dbgStats;
|
||||
|
||||
if (!handle_)
|
||||
return QString();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
static_assert(sizeof(struct pcap_stat) == 6*sizeof(uint),
|
||||
"pcap_stat has less or more than 6 values");
|
||||
int size;
|
||||
struct pcap_stat incPcapStats;
|
||||
struct pcap_stat *pcapStats = pcap_stats_ex(handle_, &size);
|
||||
if (pcapStats && (uint(size) >= 6*sizeof(uint))) {
|
||||
incPcapStats.ps_recv = pcapStats->ps_recv - lastPcapStats_.ps_recv;
|
||||
incPcapStats.ps_drop = pcapStats->ps_drop - lastPcapStats_.ps_drop;
|
||||
incPcapStats.ps_ifdrop = pcapStats->ps_ifdrop - lastPcapStats_.ps_ifdrop;
|
||||
incPcapStats.ps_capt = pcapStats->ps_capt - lastPcapStats_.ps_capt;
|
||||
incPcapStats.ps_sent = pcapStats->ps_sent - lastPcapStats_.ps_sent;
|
||||
incPcapStats.ps_netdrop = pcapStats->ps_netdrop - lastPcapStats_.ps_netdrop;
|
||||
dbgStats = QString("recv: %1 drop: %2 ifdrop: %3 "
|
||||
"capt: %4 sent: %5 netdrop: %6")
|
||||
.arg(incPcapStats.ps_recv)
|
||||
.arg(incPcapStats.ps_drop)
|
||||
.arg(incPcapStats.ps_ifdrop)
|
||||
.arg(incPcapStats.ps_capt)
|
||||
.arg(incPcapStats.ps_sent)
|
||||
.arg(incPcapStats.ps_netdrop);
|
||||
lastPcapStats_ = *pcapStats;
|
||||
} else {
|
||||
dbgStats = QString("error reading pcap stats: %1")
|
||||
.arg(pcap_geterr(handle_));
|
||||
}
|
||||
#else
|
||||
struct pcap_stat pcapStats;
|
||||
struct pcap_stat incPcapStats;
|
||||
|
||||
int ret = pcap_stats(handle_, &pcapStats);
|
||||
if (ret == 0) {
|
||||
incPcapStats.ps_recv = pcapStats.ps_recv - lastPcapStats_.ps_recv;
|
||||
incPcapStats.ps_drop = pcapStats.ps_drop - lastPcapStats_.ps_drop;
|
||||
incPcapStats.ps_ifdrop = pcapStats.ps_ifdrop - lastPcapStats_.ps_ifdrop;
|
||||
dbgStats = QString("recv: %1 drop: %2 ifdrop: %3")
|
||||
.arg(incPcapStats.ps_recv)
|
||||
.arg(incPcapStats.ps_drop)
|
||||
.arg(incPcapStats.ps_ifdrop);
|
||||
lastPcapStats_ = pcapStats;
|
||||
} else {
|
||||
dbgStats = QString("error reading pcap stats: %1")
|
||||
.arg(pcap_geterr(handle_));
|
||||
}
|
||||
#endif
|
||||
|
||||
return dbgStats;
|
||||
}
|
||||
|
||||
bool PcapSession::clearDebugStats()
|
||||
{
|
||||
memset(&lastPcapStats_, 0, sizeof(lastPcapStats_));
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <signal.h>
|
||||
#include <typeinfo>
|
||||
@ -66,7 +129,7 @@ void PcapSession::postRun()
|
||||
qDebug("Signal seen and handled");
|
||||
}
|
||||
|
||||
void PcapSession::stop(pcap_t *handle)
|
||||
void PcapSession::stop()
|
||||
{
|
||||
// Should be called OUTSIDE the thread's context
|
||||
// XXX: As per the man page for pcap_breakloop, we need both
|
||||
@ -74,7 +137,7 @@ void PcapSession::stop(pcap_t *handle)
|
||||
// we use a signal for the latter
|
||||
// TODO: If the signal mechanism doesn't work, we could try
|
||||
// pthread_cancel(thread_);
|
||||
pcap_breakloop(handle);
|
||||
pcap_breakloop(handle_);
|
||||
pthread_kill(thread_.nativeId(), MY_BREAK_SIGNAL);
|
||||
}
|
||||
|
||||
|
@ -55,27 +55,46 @@ inline uint qHash(const ThreadId &key)
|
||||
|
||||
class PcapSession: public QThread
|
||||
{
|
||||
public:
|
||||
QString debugStats();
|
||||
|
||||
protected:
|
||||
bool clearDebugStats();
|
||||
|
||||
void preRun();
|
||||
void postRun();
|
||||
void stop(pcap_t *handle);
|
||||
void stop();
|
||||
|
||||
pcap_t *handle_{nullptr};
|
||||
|
||||
private:
|
||||
static void signalBreakHandler(int /*signum*/);
|
||||
|
||||
ThreadId thread_;
|
||||
static QHash<ThreadId, bool> signalSeen_;
|
||||
|
||||
struct pcap_stat lastPcapStats_;
|
||||
};
|
||||
#else
|
||||
class PcapSession: public QThread
|
||||
{
|
||||
public:
|
||||
QString debugStats();
|
||||
|
||||
protected:
|
||||
bool clearDebugStats();
|
||||
|
||||
void preRun() {};
|
||||
void postRun() {};
|
||||
void stop(pcap_t *handle) {
|
||||
qDebug("calling breakloop with handle %p", handle);
|
||||
pcap_breakloop(handle);
|
||||
void stop() {
|
||||
qDebug("calling breakloop with handle %p", handle_);
|
||||
pcap_breakloop(handle_);
|
||||
}
|
||||
|
||||
pcap_t *handle_{nullptr};
|
||||
|
||||
private:
|
||||
struct pcap_stat lastPcapStats_;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -20,15 +20,13 @@ 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)
|
||||
const char *device)
|
||||
: txThread_(device)
|
||||
{
|
||||
adjustRxStreamStats_ = false;
|
||||
txStats_.setObjectName(QString("TxStats:%1").arg(device));
|
||||
memset(&stats_, 0, sizeof(stats_));
|
||||
txStats_.setTxThreadStats(&stats_);
|
||||
txStats_.start(); // TODO: alongwith user transmit start
|
||||
|
||||
txThread_.setStats(&stats_);
|
||||
connect(&txThread_, SIGNAL(finished()), SLOT(updateTxThreadStreamStats()));
|
||||
@ -36,7 +34,10 @@ PcapTransmitter::PcapTransmitter(
|
||||
|
||||
PcapTransmitter::~PcapTransmitter()
|
||||
{
|
||||
txStats_.stop(); // TODO: alongwith user transmit stop
|
||||
if (txThread_.isRunning())
|
||||
txThread_.stop();
|
||||
if (txStats_.isRunning())
|
||||
txStats_.stop();
|
||||
}
|
||||
|
||||
bool PcapTransmitter::setRateAccuracy(
|
||||
@ -55,6 +56,31 @@ bool PcapTransmitter::setStreamStatsTracking(bool enable)
|
||||
return txThread_.setStreamStatsTracking(enable);
|
||||
}
|
||||
|
||||
// XXX: Stats are reset on read
|
||||
void PcapTransmitter::updateTxRxStreamStats(StreamStats &streamStats)
|
||||
{
|
||||
QMutexLocker lock(&streamStatsLock_);
|
||||
StreamStatsIterator i(streamStats_);
|
||||
|
||||
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_) {
|
||||
// XXX: rx_pkts counting may lag behind tx_pkts, so stream stats
|
||||
// may become negative after adjustment transiently. But this
|
||||
// should fix itself once all the rx pkts come in
|
||||
streamStats[guid].rx_pkts -= sst.tx_pkts;
|
||||
streamStats[guid].rx_bytes -= sst.tx_bytes;
|
||||
}
|
||||
}
|
||||
streamStats_.clear();
|
||||
}
|
||||
|
||||
void PcapTransmitter::clearPacketList()
|
||||
{
|
||||
txThread_.clearPacketList();
|
||||
@ -88,6 +114,13 @@ void PcapTransmitter::setPacketListLoopMode(
|
||||
txThread_.setPacketListLoopMode(loop, secDelay, nsecDelay);
|
||||
}
|
||||
|
||||
bool PcapTransmitter::setPacketListTtagMarkers(
|
||||
QList<uint> markers,
|
||||
uint repeatInterval)
|
||||
{
|
||||
return txThread_.setPacketListTtagMarkers(markers, repeatInterval);
|
||||
}
|
||||
|
||||
void PcapTransmitter::useExternalStats(AbstractPort::PortStats *stats)
|
||||
{
|
||||
txStats_.useExternalStats(stats);
|
||||
@ -95,18 +128,27 @@ void PcapTransmitter::useExternalStats(AbstractPort::PortStats *stats)
|
||||
|
||||
void PcapTransmitter::start()
|
||||
{
|
||||
// XXX: Start the stats thread before the tx thread, so no tx stats
|
||||
// is missed
|
||||
txStats_.start();
|
||||
Q_ASSERT(txStats_.isRunning());
|
||||
txThread_.start();
|
||||
}
|
||||
|
||||
void PcapTransmitter::stop()
|
||||
{
|
||||
// XXX: Stop the tx thread before the stats thread, so no tx stats
|
||||
// is missed
|
||||
txThread_.stop();
|
||||
Q_ASSERT(!txThread_.isRunning());
|
||||
txStats_.stop();
|
||||
}
|
||||
|
||||
bool PcapTransmitter::isRunning()
|
||||
{
|
||||
return txThread_.isRunning();
|
||||
}
|
||||
|
||||
double PcapTransmitter::lastTxDuration()
|
||||
{
|
||||
return txThread_.lastTxDuration();
|
||||
@ -114,8 +156,9 @@ double PcapTransmitter::lastTxDuration()
|
||||
|
||||
void PcapTransmitter::updateTxThreadStreamStats()
|
||||
{
|
||||
QMutexLocker lock(&streamStatsLock_);
|
||||
PcapTxThread *txThread = dynamic_cast<PcapTxThread*>(sender());
|
||||
const StreamStats& threadStreamStats = txThread->streamStats();
|
||||
StreamStats threadStreamStats = txThread->streamStats();
|
||||
StreamStatsIterator i(threadStreamStats);
|
||||
|
||||
while (i.hasNext())
|
||||
@ -126,13 +169,5 @@ void PcapTransmitter::updateTxThreadStreamStats()
|
||||
|
||||
streamStats_[guid].tx_pkts += sst.tx_pkts;
|
||||
streamStats_[guid].tx_bytes += sst.tx_bytes;
|
||||
if (adjustRxStreamStats_) {
|
||||
// XXX: rx_pkts counting may lag behind tx_pkts, so stream stats
|
||||
// may become negative after adjustment transiently. But this
|
||||
// should fix itself once all the rx pkts come in
|
||||
streamStats_[guid].rx_pkts -= sst.tx_pkts;
|
||||
streamStats_[guid].rx_bytes -= sst.tx_bytes;
|
||||
}
|
||||
}
|
||||
txThread->clearStreamStats();
|
||||
}
|
||||
|
@ -29,12 +29,13 @@ class PcapTransmitter : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PcapTransmitter(const char *device, StreamStats &portStreamStats);
|
||||
PcapTransmitter(const char *device);
|
||||
~PcapTransmitter();
|
||||
|
||||
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||
bool setStreamStatsTracking(bool enable);
|
||||
void adjustRxStreamStats(bool enable);
|
||||
void updateTxRxStreamStats(StreamStats &streamStats); // Reset on read
|
||||
|
||||
void clearPacketList();
|
||||
void loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
@ -42,6 +43,7 @@ public:
|
||||
bool appendToPacketList(long sec, long usec, const uchar *packet,
|
||||
int length);
|
||||
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
|
||||
bool setPacketListTtagMarkers(QList<uint> markers, uint repeatInterval);
|
||||
|
||||
void setHandle(pcap_t *handle);
|
||||
void useExternalStats(AbstractPort::PortStats *stats);
|
||||
@ -53,7 +55,8 @@ public:
|
||||
private slots:
|
||||
void updateTxThreadStreamStats();
|
||||
private:
|
||||
StreamStats &streamStats_;
|
||||
StreamStats streamStats_;
|
||||
QMutex streamStatsLock_;
|
||||
PcapTxThread txThread_;
|
||||
PcapTxStats txStats_;
|
||||
StatsTuple stats_;
|
||||
|
@ -19,9 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "pcaptxthread.h"
|
||||
|
||||
#include "sign.h"
|
||||
#include "statstuple.h"
|
||||
#include "timestamp.h"
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
PcapTxThread::PcapTxThread(const char *device)
|
||||
{
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
@ -109,11 +112,6 @@ void PcapTxThread::clearPacketList()
|
||||
void PcapTxThread::loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec)
|
||||
{
|
||||
// Since we create implicit packetset for this case, skip
|
||||
// This case => Packet set for y when x = 0 or n==1 in n*x+y
|
||||
if (repeats == 1)
|
||||
return;
|
||||
|
||||
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
|
||||
currentPacketSequence_->repeatCount_ = repeats;
|
||||
currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6)
|
||||
@ -136,24 +134,20 @@ bool PcapTxThread::appendToPacketList(long sec, long nsec,
|
||||
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;
|
||||
// loopNextPacketSet should have created a seq
|
||||
Q_ASSERT(currentPacketSequence_ != NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
// If not enough space, update usecDelay and alloc a new seq
|
||||
if (!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
|
||||
{
|
||||
struct timeval diff;
|
||||
timersub(&pktHdr.ts, ¤tPacketSequence_->lastPacket_->ts, &diff);
|
||||
currentPacketSequence_->usecDelay_ = diff.tv_usec;
|
||||
if (diff.tv_sec)
|
||||
currentPacketSequence_->usecDelay_ += diff.tv_sec*1e6;
|
||||
|
||||
//! \todo (LOW): calculate sendqueue size
|
||||
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
|
||||
|
||||
packetSequenceList_.append(currentPacketSequence_);
|
||||
|
||||
// Validate that the pkt will fit inside the new currentSendQueue_
|
||||
@ -169,6 +163,8 @@ bool PcapTxThread::appendToPacketList(long sec, long nsec,
|
||||
packetCount_++;
|
||||
packetListSize_ += repeatSize_ ?
|
||||
currentPacketSequence_->repeatCount_ : 1;
|
||||
|
||||
// Last packet of packet-set?
|
||||
if (repeatSize_ > 0 && packetCount_ == repeatSize_)
|
||||
{
|
||||
qDebug("repeatSequenceStart_=%d, repeatSize_ = %llu",
|
||||
@ -190,7 +186,7 @@ bool PcapTxThread::appendToPacketList(long sec, long nsec,
|
||||
|
||||
repeatSize_ = 0;
|
||||
|
||||
// End current pktSeq and trigger a new pktSeq allocation for next pkt
|
||||
// End current pktSeq
|
||||
currentPacketSequence_ = NULL;
|
||||
}
|
||||
|
||||
@ -206,6 +202,27 @@ void PcapTxThread::setPacketListLoopMode(
|
||||
loopDelay_ = secDelay*long(1e6) + nsecDelay/1000;
|
||||
}
|
||||
|
||||
bool PcapTxThread::setPacketListTtagMarkers(
|
||||
QList<uint> markers,
|
||||
uint repeatInterval)
|
||||
{
|
||||
// XXX: Empty markers => no streams have Ttag
|
||||
firstTtagPkt_ = markers.isEmpty() ? -1 : int(markers.first());
|
||||
|
||||
// Calculate delta markers
|
||||
ttagDeltaMarkers_.clear();
|
||||
for (int i = 1; i < markers.size(); i++)
|
||||
ttagDeltaMarkers_.append(markers.at(i) - markers.at(i-1));
|
||||
if (!markers.isEmpty()) {
|
||||
ttagDeltaMarkers_.append(repeatInterval - markers.last()
|
||||
+ markers.first());
|
||||
qDebug() << "TtagRepeatInterval:" << repeatInterval;
|
||||
qDebug() << "FirstTtagPkt:" << firstTtagPkt_;
|
||||
qDebug() << "TtagMarkers:" << ttagDeltaMarkers_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcapTxThread::setHandle(pcap_t *handle)
|
||||
{
|
||||
if (usingInternalHandle_)
|
||||
@ -219,14 +236,20 @@ void PcapTxThread::setStats(StatsTuple *stats)
|
||||
stats_ = stats;
|
||||
}
|
||||
|
||||
const StreamStats& PcapTxThread::streamStats()
|
||||
StreamStats PcapTxThread::streamStats()
|
||||
{
|
||||
return streamStats_;
|
||||
}
|
||||
// This function is typically called in client-specific-RPC-thread
|
||||
// context; hence different client RPC threads may call this function,
|
||||
// so use a lock. Although RPCs are protected by the portLock just
|
||||
// for this purpose, the streamStats RPC takes a Read lock, so it can
|
||||
// still happen that multiple RPC threads land up here - that's why
|
||||
// this lock is required
|
||||
QMutexLocker lock(&streamStatsLock_);
|
||||
|
||||
void PcapTxThread::clearStreamStats()
|
||||
{
|
||||
streamStats_.clear();
|
||||
StreamStats ss(streamStats_); // Make a copy
|
||||
streamStats_.clear(); // Reset on read semantics
|
||||
|
||||
return ss; // Return copy
|
||||
}
|
||||
|
||||
void PcapTxThread::run()
|
||||
@ -261,39 +284,47 @@ void PcapTxThread::run()
|
||||
packetSequenceList_.at(i)->repeatCount_,
|
||||
packetSequenceList_.at(i)->repeatSize_,
|
||||
packetSequenceList_.at(i)->usecDelay_);
|
||||
qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld", i,
|
||||
qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld, ttagL4CksumOfs = %hu", i,
|
||||
packetSequenceList_.at(i)->packets_,
|
||||
packetSequenceList_.at(i)->usecDuration_);
|
||||
packetSequenceList_.at(i)->usecDuration_,
|
||||
packetSequenceList_.at(i)->ttagL4CksumOffset_);
|
||||
}
|
||||
|
||||
qDebug() << "Loop:" << (returnToQIdx_ >= 0)
|
||||
<< "LoopDelay:" << loopDelay_;
|
||||
qDebug() << "First Ttag: " << firstTtagPkt_
|
||||
<< "Ttag Markers:" << ttagDeltaMarkers_;
|
||||
|
||||
lastStats_ = *stats_; // used for stream stats
|
||||
|
||||
// Init Ttag related vars. If no packets need ttag, firstTtagPkt_ is -1,
|
||||
// so nextTagPkt_ is set to practically unreachable value (due to
|
||||
// 64 bit counter wraparound time!)
|
||||
ttagMarkerIndex_ = 0;
|
||||
nextTtagPkt_ = stats_->pkts + firstTtagPkt_;
|
||||
|
||||
getTimeStamp(&startTime);
|
||||
state_ = kRunning;
|
||||
i = 0;
|
||||
while (i < packetSequenceList_.size())
|
||||
{
|
||||
|
||||
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++)
|
||||
{
|
||||
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
|
||||
{
|
||||
// Use Windows-only pcap_sendqueue_transmit() if duration < 1s
|
||||
// and no stream timing is configured
|
||||
if (seq->usecDuration_ <= long(1e6) && firstTtagPkt_ < 0) {
|
||||
getTimeStamp(&ovrStart);
|
||||
ret = pcap_sendqueue_transmit(handle_,
|
||||
seq->sendQueue_, kSyncTransmit);
|
||||
if (ret >= 0)
|
||||
{
|
||||
if (ret >= 0) {
|
||||
stats_->pkts += seq->packets_;
|
||||
stats_->bytes += seq->bytes_;
|
||||
|
||||
@ -304,52 +335,42 @@ _restart:
|
||||
}
|
||||
if (stop_)
|
||||
ret = -2;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = sendQueueTransmit(handle_, seq->sendQueue_,
|
||||
} else {
|
||||
ret = sendQueueTransmit(handle_, seq,
|
||||
overHead, kSyncTransmit);
|
||||
}
|
||||
#else
|
||||
ret = sendQueueTransmit(handle_, seq->sendQueue_,
|
||||
ret = sendQueueTransmit(handle_, seq,
|
||||
overHead, kSyncTransmit);
|
||||
#endif
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
if (ret >= 0) {
|
||||
long usecs = seq->usecDelay_ + overHead;
|
||||
if (usecs > 0)
|
||||
{
|
||||
if (usecs > 0) {
|
||||
(*udelayFn_)(usecs);
|
||||
overHead = 0;
|
||||
}
|
||||
else
|
||||
} else
|
||||
overHead = usecs;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug("error %d in sendQueueTransmit()", ret);
|
||||
qDebug("overHead = %ld", overHead);
|
||||
stop_ = false;
|
||||
goto _exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // rptSz
|
||||
} // rptCnt
|
||||
|
||||
// Move to the next Packet Set
|
||||
i += rptSz;
|
||||
}
|
||||
|
||||
if (returnToQIdx_ >= 0)
|
||||
{
|
||||
if (returnToQIdx_ >= 0) {
|
||||
long usecs = loopDelay_ + overHead;
|
||||
|
||||
if (usecs > 0)
|
||||
{
|
||||
if (usecs > 0) {
|
||||
(*udelayFn_)(usecs);
|
||||
overHead = 0;
|
||||
}
|
||||
else
|
||||
} else
|
||||
overHead = usecs;
|
||||
|
||||
i = returnToQIdx_;
|
||||
@ -409,38 +430,77 @@ double PcapTxThread::lastTxDuration()
|
||||
return lastTxDuration_;
|
||||
}
|
||||
|
||||
int PcapTxThread::sendQueueTransmit(pcap_t *p,
|
||||
pcap_send_queue *queue, long &overHead, int sync)
|
||||
int PcapTxThread::sendQueueTransmit(pcap_t *p, PacketSequence *seq,
|
||||
long &overHead, int sync)
|
||||
{
|
||||
TimeStamp ovrStart, ovrEnd;
|
||||
struct timeval ts;
|
||||
pcap_send_queue *queue = seq->sendQueue_;
|
||||
struct pcap_pkthdr *hdr = (struct pcap_pkthdr*) queue->buffer;
|
||||
char *end = queue->buffer + queue->len;
|
||||
|
||||
ts = hdr->ts;
|
||||
|
||||
getTimeStamp(&ovrStart);
|
||||
while((char*) hdr < end)
|
||||
{
|
||||
while((char*) hdr < end) {
|
||||
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
|
||||
int pktLen = hdr->caplen;
|
||||
bool ttagPkt = false;
|
||||
#if 0
|
||||
quint16 origCksum = 0;
|
||||
#endif
|
||||
|
||||
if (sync)
|
||||
{
|
||||
// Time for a T-Tag packet?
|
||||
if (stats_->pkts == nextTtagPkt_) {
|
||||
ttagPkt = true;
|
||||
// XXX: write 2xBytes instead of 1xHalf-word to avoid
|
||||
// potential alignment problem
|
||||
*(pkt+pktLen-5) = SignProtocol::kTypeLenTtag;
|
||||
*(pkt+pktLen-6) = ttagId_;
|
||||
|
||||
#if 0
|
||||
// Recalc L4 checksum; use incremental checksum as per RFC 1624
|
||||
// HC' = ~(~HC + ~m + m')
|
||||
if (seq->ttagL4CksumOffset_) {
|
||||
quint16 *cksum = reinterpret_cast<quint16*>(
|
||||
pkt + seq->ttagL4CksumOffset_);
|
||||
origCksum = qFromBigEndian<quint16>(*cksum);
|
||||
// XXX: SignProtocol trailer
|
||||
// ... | <guid> | 0x61 | 0x00 | 0x22 | 0x1d10c0da
|
||||
// ... | <guid> | 0x61 | <TtagId> | 0x23 | 0x1d10c0da
|
||||
// For odd pkt Length, Ttag spans across 2 half-words
|
||||
// XXX: Hardcoded values instead of sign protocol constants
|
||||
// used below for readability
|
||||
quint32 newCksum = pktLen & 1 ?
|
||||
quint16(~origCksum) + quint16(~0x221d) + 0x231d
|
||||
+ quint16(~0x6100) + (0x6100 | ttagId_) :
|
||||
quint16(~origCksum) + quint16(~0x0022) + (ttagId_ << 8 | 0x23);
|
||||
while (newCksum > 0xffff)
|
||||
newCksum = (newCksum & 0xffff) + (newCksum >> 16);
|
||||
// XXX: For IPv4/UDP, if ~newcksum is 0x0000 we are supposed to
|
||||
// set the checksum as 0xffff since 0x0000 indicates no cksum
|
||||
// is present - we choose not to do this to avoid extra cost
|
||||
*cksum = qToBigEndian(quint16(~newCksum));
|
||||
}
|
||||
#endif
|
||||
ttagId_++;
|
||||
nextTtagPkt_ += ttagDeltaMarkers_.at(ttagMarkerIndex_);
|
||||
ttagMarkerIndex_++;
|
||||
if (ttagMarkerIndex_ >= ttagDeltaMarkers_.size())
|
||||
ttagMarkerIndex_ = 0;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (usec > 0) {
|
||||
(*udelayFn_)(usec);
|
||||
overHead = 0;
|
||||
}
|
||||
else
|
||||
} else
|
||||
overHead = usec;
|
||||
|
||||
ts = hdr->ts;
|
||||
@ -453,21 +513,34 @@ int PcapTxThread::sendQueueTransmit(pcap_t *p,
|
||||
stats_->pkts++;
|
||||
stats_->bytes += pktLen;
|
||||
|
||||
// Revert T-Tag packet changes
|
||||
if (ttagPkt) {
|
||||
*(pkt+pktLen-5) = SignProtocol::kTypeLenTtagPlaceholder;
|
||||
*(pkt+pktLen-6) = 0;
|
||||
#if 0
|
||||
if (seq->ttagL4CksumOffset_) {
|
||||
quint16 *cksum = reinterpret_cast<quint16*>(
|
||||
pkt + seq->ttagL4CksumOffset_);
|
||||
*cksum = qToBigEndian(origCksum);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Step to the next packet in the buffer
|
||||
hdr = (struct pcap_pkthdr*) (pkt + pktLen);
|
||||
pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr)); // FIXME: superfluous?
|
||||
|
||||
if (stop_)
|
||||
{
|
||||
if (stop_) {
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PcapTxThread::updateTxStreamStats()
|
||||
{
|
||||
QMutexLocker lock(&streamStatsLock_);
|
||||
|
||||
// If no packets in list, nothing to be done
|
||||
if (!packetListSize_)
|
||||
return;
|
||||
@ -613,4 +686,3 @@ void PcapTxThread::udelay(unsigned long usec)
|
||||
QThread::usleep(usec);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "packetsequence.h"
|
||||
#include "statstuple.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <pcap.h>
|
||||
|
||||
@ -42,13 +43,13 @@ public:
|
||||
bool appendToPacketList(long sec, long usec, const uchar *packet,
|
||||
int length);
|
||||
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
|
||||
bool setPacketListTtagMarkers(QList<uint> markers, uint repeatInterval);
|
||||
|
||||
void setHandle(pcap_t *handle);
|
||||
|
||||
void setStats(StatsTuple *stats);
|
||||
|
||||
const StreamStats& streamStats();
|
||||
void clearStreamStats();
|
||||
StreamStats streamStats(); // reset on read
|
||||
|
||||
void run();
|
||||
|
||||
@ -66,8 +67,8 @@ private:
|
||||
};
|
||||
|
||||
static void udelay(unsigned long usec);
|
||||
int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead,
|
||||
int sync);
|
||||
int sendQueueTransmit(pcap_t *p, PacketSequence *seq,
|
||||
long &overHead, int sync);
|
||||
void updateTxStreamStats();
|
||||
|
||||
// Intermediate state variables used while building the packet list
|
||||
@ -80,7 +81,7 @@ private:
|
||||
quint64 packetListSize_; // count of pkts in packet List including repeats
|
||||
|
||||
int returnToQIdx_;
|
||||
quint64 loopDelay_;
|
||||
quint64 loopDelay_; // in nanosecs
|
||||
|
||||
void (*udelayFn_)(unsigned long);
|
||||
|
||||
@ -93,8 +94,18 @@ private:
|
||||
StatsTuple *stats_;
|
||||
StatsTuple lastStats_;
|
||||
StreamStats streamStats_;
|
||||
QMutex streamStatsLock_;
|
||||
quint8 ttagId_{0};
|
||||
|
||||
double lastTxDuration_{0.0}; // in secs
|
||||
|
||||
// XXX: Ttag Marker config derived; not updated during Tx
|
||||
int firstTtagPkt_;
|
||||
QList<uint> ttagDeltaMarkers_;
|
||||
|
||||
// XXX: Ttag related; updated during Tx
|
||||
int ttagMarkerIndex_;
|
||||
quint64 nextTtagPkt_{0};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
190
server/pcaptxttagstats.cpp
Normal file
190
server/pcaptxttagstats.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
Copyright (C) 2023 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 "pcaptxttagstats.h"
|
||||
|
||||
#include "pcapextra.h"
|
||||
#include "../common/debugdefs.h"
|
||||
#include "../common/sign.h"
|
||||
#include "streamtiming.h"
|
||||
|
||||
#define Xnotify qWarning // FIXME
|
||||
|
||||
PcapTxTtagStats::PcapTxTtagStats(const char *device, int id)
|
||||
: portId_(id)
|
||||
{
|
||||
setObjectName(QString("TxT$:%1").arg(device));
|
||||
device_ = QString::fromLatin1(device);
|
||||
|
||||
timing_ = StreamTiming::instance();
|
||||
}
|
||||
|
||||
void PcapTxTtagStats::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) and (ether[len - 5:1] == 0x%2)")
|
||||
.arg(SignProtocol::magic(), 0, BASE_HEX)
|
||||
.arg(SignProtocol::kTypeLenTtag, 0, BASE_HEX);
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
qDebug("pcap-filter: %s", qPrintable(capture_filter));
|
||||
|
||||
handle_ = pcap_open_live(qPrintable(device_), 65535,
|
||||
flags, 100 /* ms */, errbuf);
|
||||
if (!handle_) {
|
||||
if (flags && QString(errbuf).contains("promiscuous")) {
|
||||
Xnotify("Unable to set promiscuous mode on <%s> - "
|
||||
"stream stats time tracking will not work", qPrintable(device_));
|
||||
goto _exit;
|
||||
}
|
||||
else {
|
||||
Xnotify("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_OUT) < 0) {
|
||||
qDebug("TxTtagStats: Error setting OUT 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:
|
||||
clearDebugStats();
|
||||
PcapSession::preRun();
|
||||
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 ttagId;
|
||||
uint guid;
|
||||
if (SignProtocol::packetTtagId(data, hdr->caplen,
|
||||
&ttagId, &guid)) {
|
||||
#ifdef Q_OS_WIN32
|
||||
// TxPort is NOT us ==> Rx Packet, so skip
|
||||
// See similar check in PcapRxStats for details
|
||||
if (ttagId >> 8 != uint(portId_))
|
||||
break;
|
||||
ttagId &= 0xFF;
|
||||
#endif
|
||||
timing_->recordTxTime(portId_, guid, ttagId, hdr->ts);
|
||||
}
|
||||
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:
|
||||
qDebug("%s: Loop/signal break or some other error",
|
||||
__PRETTY_FUNCTION__);
|
||||
break;
|
||||
default:
|
||||
qWarning("%s: Unexpected return value %d",
|
||||
__PRETTY_FUNCTION__, ret);
|
||||
stop_ = true;
|
||||
}
|
||||
|
||||
if (stop_) {
|
||||
qDebug("user requested txTtagStats stop");
|
||||
break;
|
||||
}
|
||||
}
|
||||
PcapSession::postRun();
|
||||
pcap_close(handle_);
|
||||
handle_ = NULL;
|
||||
stop_ = false;
|
||||
|
||||
_exit:
|
||||
state_ = kFinished;
|
||||
}
|
||||
|
||||
bool PcapTxTtagStats::start()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
qWarning("TxTtagStats start requested but is already running!");
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
state_ = kNotStarted;
|
||||
PcapSession::start();
|
||||
|
||||
while (state_ == kNotStarted)
|
||||
QThread::msleep(10);
|
||||
_exit:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PcapTxTtagStats::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
PcapSession::stop();
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
else
|
||||
qWarning("TxTtagStats stop requested but is not running!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PcapTxTtagStats::isRunning()
|
||||
{
|
||||
return (state_ == kRunning);
|
||||
}
|
||||
|
||||
bool PcapTxTtagStats::isDirectional()
|
||||
{
|
||||
return isDirectional_;
|
||||
}
|
55
server/pcaptxttagstats.h
Normal file
55
server/pcaptxttagstats.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (C) 2023 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_TTAG_H
|
||||
#define _PCAP_TX_TTAG_H
|
||||
|
||||
#include "pcapsession.h"
|
||||
|
||||
class StreamTiming;
|
||||
|
||||
class PcapTxTtagStats: public PcapSession
|
||||
{
|
||||
public:
|
||||
PcapTxTtagStats(const char *device, int id);
|
||||
|
||||
void run();
|
||||
bool start();
|
||||
bool stop();
|
||||
bool isRunning();
|
||||
bool isDirectional();
|
||||
|
||||
private:
|
||||
enum State {
|
||||
kNotStarted,
|
||||
kRunning,
|
||||
kFinished
|
||||
};
|
||||
|
||||
QString device_;
|
||||
bool isDirectional_{true};
|
||||
volatile State state_{kNotStarted};
|
||||
volatile bool stop_{false};
|
||||
|
||||
int portId_;
|
||||
|
||||
StreamTiming *timing_{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
@ -42,4 +42,8 @@ const QString kRpcServerAddress("RpcServer/Address");
|
||||
const QString kPortListIncludeKey("PortList/Include");
|
||||
const QString kPortListExcludeKey("PortList/Exclude");
|
||||
|
||||
//
|
||||
// Internal Section Keys
|
||||
//
|
||||
const QString kInternalRxStatsFilterKey("Internal/RxStatsFilter");
|
||||
#endif
|
||||
|
@ -30,6 +30,7 @@ struct StreamStatsTuple
|
||||
quint64 tx_bytes;
|
||||
};
|
||||
|
||||
// Key(uint) is GUID
|
||||
typedef QHash<uint, StreamStatsTuple> StreamStats;
|
||||
typedef QHashIterator<uint, StreamStatsTuple> StreamStatsIterator;
|
||||
|
||||
|
213
server/streamtiming.cpp
Normal file
213
server/streamtiming.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright (C) 2023 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 "streamtiming.h"
|
||||
|
||||
#include "timestamp.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
StreamTiming::StreamTiming(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
// This class MUST be part of the main thread so that timers can work
|
||||
Q_ASSERT(this->thread() == QCoreApplication::instance()->thread());
|
||||
|
||||
timer_ = new QTimer(this);
|
||||
connect(timer_, &QTimer::timeout, this, &StreamTiming::processRecords);
|
||||
timer_->setInterval(3000);
|
||||
|
||||
gcTimer_ = new QTimer(this);
|
||||
connect(gcTimer_, &QTimer::timeout, this, &StreamTiming::deleteStaleRecords);
|
||||
gcTimer_->setInterval(30000);
|
||||
}
|
||||
|
||||
void StreamTiming::start(uint portId)
|
||||
{
|
||||
if (activePortSet_.isEmpty()) { // First port?
|
||||
timer_->start();
|
||||
gcTimer_->start();
|
||||
qDebug("Stream Latency tracking started");
|
||||
}
|
||||
activePortSet_.insert(portId);
|
||||
qDebug("Stream Latency tracking started for port %u", portId);
|
||||
}
|
||||
|
||||
void StreamTiming::stop(uint portId)
|
||||
{
|
||||
activePortSet_.remove(portId);
|
||||
qDebug("Stream Latency tracking stopped for port %u", portId);
|
||||
if (activePortSet_.isEmpty()) { // Last port?
|
||||
processRecords();
|
||||
deleteStaleRecords();
|
||||
timer_->stop();
|
||||
gcTimer_->stop();
|
||||
qDebug("Stream Latency tracking stopped");
|
||||
}
|
||||
}
|
||||
|
||||
StreamTiming::Stats StreamTiming::stats(uint portId, uint guid)
|
||||
{
|
||||
Stats stats = {0, 0};
|
||||
|
||||
Q_ASSERT(guid <= SignProtocol::kMaxGuid);
|
||||
|
||||
// Process anything pending first
|
||||
processRecords();
|
||||
|
||||
QMutexLocker locker(&timingLock_);
|
||||
|
||||
if (!timing_.contains(portId))
|
||||
return stats;
|
||||
|
||||
Timing t = timing_.value(portId)->value(guid);
|
||||
if (t.countDelays == 0)
|
||||
return stats;
|
||||
|
||||
stats.latency = timespecToNsecs(t.sumDelays)/t.countDelays;
|
||||
if (t.countDelays > 1)
|
||||
stats.jitter = t.sumJitter/(t.countDelays-1);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void StreamTiming::clear(uint portId, uint guid)
|
||||
{
|
||||
// XXX: We need to clear only the final timing hash; rx/tx hashes
|
||||
// are cleared by StreamTiming itself as part of processRecords and
|
||||
// deleteStaleRecords respectively
|
||||
QMutexLocker locker(&timingLock_);
|
||||
|
||||
if (!timing_.contains(portId))
|
||||
return;
|
||||
|
||||
PortTiming *portTiming = timing_.value(portId);
|
||||
if (!portTiming)
|
||||
return;
|
||||
|
||||
if (guid >= SignProtocol::kInvalidGuid)
|
||||
portTiming->clear(); // remove ALL guids
|
||||
else
|
||||
portTiming->remove(guid);
|
||||
}
|
||||
|
||||
int StreamTiming::processRecords()
|
||||
{
|
||||
// TODO: yield after a certain count of records or time when called in
|
||||
// timer context; when called from delay(), process ALL
|
||||
|
||||
int count = 0;
|
||||
QMutexLocker txLocker(&txHashLock_);
|
||||
QMutexLocker rxLocker(&rxHashLock_);
|
||||
QMutexLocker timingLocker(&timingLock_);
|
||||
|
||||
auto i = rxHash_.begin();
|
||||
while (i != rxHash_.end()) {
|
||||
if (txHash_.contains(i.key())) {
|
||||
struct timespec txTime = txHash_.take(i.key()).timeStamp;
|
||||
struct timespec rxTime = i.value().timeStamp;
|
||||
struct timespec diff;
|
||||
timespecsub(&rxTime, &txTime, &diff);
|
||||
|
||||
uint guid = guidFromKey(i.key());
|
||||
uint portId = i.value().portId;
|
||||
|
||||
if (!timing_.contains(portId))
|
||||
timing_.insert(portId, new PortTiming);
|
||||
PortTiming *portTiming = timing_.value(portId);
|
||||
Timing &guidTiming = (*portTiming)[guid];
|
||||
timespecadd(&guidTiming.sumDelays, &diff, &guidTiming.sumDelays);
|
||||
if (guidTiming.countDelays)
|
||||
guidTiming.sumJitter += abs(
|
||||
diff.tv_sec*long(1e9) + diff.tv_nsec
|
||||
- guidTiming.lastDelay.tv_sec*long(1e9)
|
||||
- guidTiming.lastDelay.tv_nsec);
|
||||
guidTiming.lastDelay = diff;
|
||||
guidTiming.countDelays++;
|
||||
|
||||
count++;
|
||||
|
||||
timingDebug("[%u/%u/%u] diff %ld.%09ld (%ld.%09ld - %ld.%09ld)",
|
||||
i.value().portId, guid, ttagIdFromKey(i.key()),
|
||||
diff.tv_sec, diff.tv_nsec,
|
||||
rxTime.tv_sec, rxTime.tv_nsec,
|
||||
txTime.tv_sec, txTime.tv_nsec);
|
||||
timingDebug("[%u/%u](%d) total %ld.%09ld count %u jittersum %09llu",
|
||||
i.value().portId, guid, count,
|
||||
guidTiming.sumDelays.tv_sec, guidTiming.sumDelays.tv_nsec,
|
||||
guidTiming.countDelays, guidTiming.sumJitter);
|
||||
}
|
||||
i = rxHash_.erase(i);
|
||||
}
|
||||
|
||||
Q_ASSERT(rxHash_.isEmpty());
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int StreamTiming::deleteStaleRecords()
|
||||
{
|
||||
// TODO: yield after a certain count of records or time unless we are
|
||||
// idle when we process all; how do we determine we are "idle"?
|
||||
|
||||
// XXX: We assume the Tx packet timestamps are based on CLOCK_REALTIME
|
||||
// (or a similar and comparable source). Since garbage collection timer
|
||||
// is not a short interval, it need not be the exact same source as long
|
||||
// as the values are comparable
|
||||
int count = 0;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
|
||||
// XXX: processRecords() iterates and deletes all rx records irrespective
|
||||
// of whether it found a matching tx record. So for garbage collection we
|
||||
// only need to look at (and delete) tx records
|
||||
QMutexLocker locker(&txHashLock_);
|
||||
|
||||
auto i = txHash_.begin();
|
||||
while (i != txHash_.end()) {
|
||||
struct timespec txTime = i.value().timeStamp;
|
||||
struct timespec diff;
|
||||
timespecsub(&now, &txTime, &diff);
|
||||
timingDebug("gc diff %ld", diff.tv_sec);
|
||||
if (diff.tv_sec > 30) {
|
||||
i = txHash_.erase(i);
|
||||
count++;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count)
|
||||
qDebug("Latency garbage collected %d stale tx timing records", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
StreamTiming* StreamTiming::instance()
|
||||
{
|
||||
static StreamTiming *instance{nullptr};
|
||||
|
||||
// XXX: As of this writing, AbstractPort constructor is the first one
|
||||
// to call this - hence this singleton is created when the first port
|
||||
// is created
|
||||
if (!instance)
|
||||
instance = new StreamTiming(QCoreApplication::instance());
|
||||
|
||||
return instance;
|
||||
}
|
196
server/streamtiming.h
Normal file
196
server/streamtiming.h
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
Copyright (C) 2023 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_TIMING
|
||||
#define _STREAM_TIMING
|
||||
|
||||
#include "../common/debugdefs.h"
|
||||
#include "../common/sign.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
class StreamTiming : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Stats
|
||||
{
|
||||
quint64 latency;
|
||||
quint64 jitter;
|
||||
};
|
||||
|
||||
bool recordTxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timespec ×tamp);
|
||||
bool recordRxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timespec ×tamp);
|
||||
|
||||
bool recordTxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timeval ×tamp);
|
||||
bool recordRxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timeval ×tamp);
|
||||
|
||||
bool recordTxTime(uint portId, uint *ttagList, int count,
|
||||
const struct timespec ×tamp);
|
||||
|
||||
Stats stats(uint portId, uint guid);
|
||||
void clear(uint portId, uint guid = SignProtocol::kInvalidGuid);
|
||||
|
||||
static StreamTiming* instance();
|
||||
|
||||
public slots:
|
||||
void start(uint portId);
|
||||
void stop(uint portId);
|
||||
|
||||
private:
|
||||
StreamTiming(QObject *parent=nullptr);
|
||||
|
||||
int processRecords();
|
||||
int deleteStaleRecords();
|
||||
|
||||
// XXX: use only time intervals, not absolute time
|
||||
quint64 timespecToNsecs(const struct timespec &interval) {
|
||||
return interval.tv_nsec + interval.tv_sec*1e9;
|
||||
}
|
||||
|
||||
struct TtagData {
|
||||
struct timespec timeStamp; // nanosec resolution
|
||||
uint portId;
|
||||
};
|
||||
|
||||
// XXX: used only as a Qt Container value, so members will get init to 0
|
||||
// when this struct is retrieved from the container due to Qt's default-
|
||||
// cosntructed value semantics
|
||||
struct Timing {
|
||||
struct timespec sumDelays; // nanosec resolution
|
||||
struct timespec lastDelay;
|
||||
quint64 sumJitter; // nanosec resolution
|
||||
uint countDelays;
|
||||
};
|
||||
|
||||
QSet<uint> activePortSet_;
|
||||
|
||||
// XXX: TxRxKey = ttagid (8 bit MSB) + guid (24 bit LSB)
|
||||
// TODO: encode tx port in packet and use as part of key
|
||||
typedef quint32 TxRxKey;
|
||||
TxRxKey makeKey(uint guid, uint ttagId) {
|
||||
return (ttagId << 24 ) | (guid & 0x00FFFFFF);
|
||||
}
|
||||
uint guidFromKey(TxRxKey key) {
|
||||
return uint(key) & 0x00FFFFFF;
|
||||
}
|
||||
uint ttagIdFromKey(TxRxKey key) {
|
||||
return uint(key) >> 24;
|
||||
}
|
||||
|
||||
QHash<TxRxKey, TtagData> txHash_;
|
||||
QHash<TxRxKey, TtagData> rxHash_;
|
||||
QMutex txHashLock_;
|
||||
QMutex rxHashLock_;
|
||||
|
||||
typedef uint PortIdKey;
|
||||
typedef uint GuidKey; // guid only, no ttagid
|
||||
typedef QHash<GuidKey, Timing> PortTiming;
|
||||
QHash<PortIdKey, PortTiming*> timing_;
|
||||
QMutex timingLock_;
|
||||
|
||||
QTimer *timer_; // Periodic timer to process tx/rx records
|
||||
QTimer *gcTimer_; // Garbage collection for stale tx records
|
||||
};
|
||||
|
||||
inline
|
||||
bool StreamTiming::recordTxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timespec ×tamp)
|
||||
{
|
||||
TxRxKey key = makeKey(guid, ttagId);
|
||||
TtagData value = { .timeStamp = timestamp, .portId = portId};
|
||||
|
||||
timingDebug("[%d TX] %ld:%ld ttag %u guid %u", portId,
|
||||
timestamp.tv_sec, long(timestamp.tv_nsec), ttagId, guid);
|
||||
|
||||
QMutexLocker locker(&txHashLock_);
|
||||
txHash_.insert(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline
|
||||
bool StreamTiming::recordRxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timespec ×tamp)
|
||||
{
|
||||
TxRxKey key = makeKey(guid, ttagId);
|
||||
TtagData value = { .timeStamp = timestamp, .portId = portId};
|
||||
|
||||
timingDebug("[%d RX] %ld:%ld ttag %u guid %u", portId,
|
||||
timestamp.tv_sec, long(timestamp.tv_nsec), ttagId, guid);
|
||||
|
||||
QMutexLocker locker(&rxHashLock_);
|
||||
rxHash_.insert(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline
|
||||
bool StreamTiming::recordTxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timeval ×tamp)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = timestamp.tv_sec;
|
||||
ts.tv_nsec = timestamp.tv_usec*1000;
|
||||
|
||||
return recordTxTime(portId, guid, ttagId, ts);
|
||||
}
|
||||
|
||||
inline
|
||||
bool StreamTiming::recordRxTime(uint portId, uint guid, uint ttagId,
|
||||
const struct timeval ×tamp)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = timestamp.tv_sec;
|
||||
ts.tv_nsec = timestamp.tv_usec*1000;
|
||||
|
||||
return recordRxTime(portId, guid, ttagId, ts);
|
||||
}
|
||||
|
||||
// TTagList contains 32-bit ttags formatted as ttagId (8msb) + guid (24lsb)
|
||||
inline
|
||||
bool StreamTiming::recordTxTime(uint portId, uint *ttagList, int count,
|
||||
const struct timespec ×tamp)
|
||||
{
|
||||
TtagData value = { .timeStamp = timestamp, .portId = portId};
|
||||
QMutexLocker locker(&txHashLock_);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
TxRxKey key = TxRxKey(ttagList[i]);
|
||||
|
||||
timingDebug("[%d TX] %ld:%ld ttag %u guid %u", portId,
|
||||
timestamp.tv_sec, long(timestamp.tv_nsec),
|
||||
ttagIdFromKey(key), guidFromKey(key));
|
||||
|
||||
txHash_.insert(key, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#define _TIMESTAMP_H
|
||||
|
||||
#include "timespecops.h"
|
||||
#include "timevalops.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
@ -69,6 +70,7 @@ static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
|
||||
#endif
|
||||
|
||||
#elif defined(Q_OS_WIN32)
|
||||
#include <windows.h>
|
||||
static quint64 gTicksFreq;
|
||||
typedef LARGE_INTEGER TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp* stamp)
|
||||
|
54
server/timevalops.h
Normal file
54
server/timevalops.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
This file is part of "Ostinato"
|
||||
|
||||
These macros are copied from BSD sys/time.h
|
||||
*/
|
||||
|
||||
#ifndef _TIME_VAL_OPS
|
||||
#define _TIME_VAL_OPS
|
||||
|
||||
/* Operations on timeval - for platforms where some are not already defined*/
|
||||
#if defined(Q_OS_WIN32)
|
||||
|
||||
#ifndef timerclear
|
||||
#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0)
|
||||
#endif
|
||||
|
||||
#ifndef timerisset
|
||||
#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
|
||||
#endif
|
||||
|
||||
#ifndef timercmp
|
||||
#define timercmp(tvp, uvp, cmp) \
|
||||
(((tvp)->tv_sec == (uvp)->tv_sec) ? \
|
||||
g((tvp)->tv_usec cmp (uvp)->tv_usec) : \
|
||||
g((tvp)->tv_sec cmp (uvp)->tv_sec))
|
||||
#endif
|
||||
|
||||
#ifndef timeradd
|
||||
#define timeradd(tvp, uvp, vvp) \
|
||||
do { \
|
||||
(vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
|
||||
(vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
|
||||
if ((vvp)->tv_usec >= 1000000) { \
|
||||
(vvp)->tv_sec++; \
|
||||
(vvp)->tv_usec -= 1000000; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef timersub
|
||||
#define timersub(tvp, uvp, vvp) \
|
||||
do { \
|
||||
(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
|
||||
(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
|
||||
if ((vvp)->tv_usec < 0) { \
|
||||
(vvp)->tv_sec--; \
|
||||
(vvp)->tv_usec += 1000000; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif /* Q_OS_WIN32 */
|
||||
|
||||
#endif
|
@ -1,33 +1,26 @@
|
||||
TEMPLATE = app
|
||||
CONFIG += qt console
|
||||
QT += xml network script widgets
|
||||
QT += xml network script
|
||||
INCLUDEPATH += "../rpc/" "../common/" "../client"
|
||||
|
||||
OBJDIR = .
|
||||
win32 {
|
||||
LIBS += -lwpcap -lpacket
|
||||
CONFIG(debug, debug|release) {
|
||||
LIBS += -L"../common/debug" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc/debug" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/debug/libostprotogui.a" \
|
||||
"../common/debug/libostproto.a" \
|
||||
"../rpc/debug/libpbrpc.a"
|
||||
OBJDIR = debug
|
||||
} else {
|
||||
LIBS += -L"../common/release" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc/release" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/release/libostprotogui.a" \
|
||||
"../common/release/libostproto.a" \
|
||||
"../rpc/release/libpbrpc.a"
|
||||
OBJDIR = release
|
||||
}
|
||||
} else {
|
||||
LIBS += -lpcap
|
||||
LIBS += -L"../common" -lostprotogui -lostproto
|
||||
LIBS += -L"../rpc" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/libostprotogui.a" \
|
||||
"../common/libostproto.a" \
|
||||
"../rpc/libpbrpc.a"
|
||||
}
|
||||
LIBS += -L"../common/$$OBJDIR" -lostfile -lostproto
|
||||
LIBS += -L"../rpc/$$OBJDIR" -lpbrpc
|
||||
POST_TARGETDEPS += \
|
||||
"../common/$$OBJDIR/libostfile.a" \
|
||||
"../common/$$OBJDIR/libostproto.a" \
|
||||
"../rpc/$$OBJDIR/libpbrpc.a"
|
||||
|
||||
LIBS += -lprotobuf
|
||||
LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
APP_VERSION = 1.3.0-dev
|
||||
APP_VERSION = 1.4.0-dev
|
||||
APP_REVISION = $(shell git rev-parse --short=12 --verify HEAD)
|
||||
#uncomment the below line in a source package and fill-in the correct revision
|
||||
#APP_REVISION = <rev-hash>@
|
||||
|
Loading…
Reference in New Issue
Block a user