Compare commits

...

88 Commits

Author SHA1 Message Date
Srivats P
318fe124bf Suggest disabling intelligent import if pcap diff 2024-02-02 17:02:04 +05:30
Srivats P
b3ac5f8d5d Add support for RARP 2024-01-19 15:53:00 +05:30
Srivats P
f185ceb206 Stop thread if still running in destructor
Threads addressed in this commit:
 - Transmitter (txThread, txStats)
 - Capturer
 - EmulXcvr
2023-11-24 13:12:35 +05:30
Srivats P
cf84060277 Start/stop the txStats thread along with txThread
This will avoid having an always running thread unnecessarily
2023-11-23 17:02:58 +05:30
Srivats P
469e0b054e Simplify .pro file by removing some duplication 2023-11-16 11:53:52 +05:30
Srivats P
23ee0e6f00 Extract libostfilegui out of libostfile
libostfile now has no GUI dependencies
2023-11-16 11:53:52 +05:30
Srivats P
f3dccb9484 Move spinBoxDelegate back to ostprotogui
This was mistakenly moved to ostfile when it was extracted from
ostprotogui
2023-11-16 11:53:52 +05:30
Srivats P
8f89c577ef Extract file format UI code into separate files 2023-11-16 11:53:52 +05:30
Srivats P
71435869cf Rename pcapfileformatoptions to pcapoptionsdialog 2023-11-16 11:53:52 +05:30
Srivats P
f779b0138b Extract pcapfileformatoptions into separate files 2023-11-16 11:53:52 +05:30
Srivats P
8c58d8610f Prep to move PcapOptions UI into separate files
This commit only creates a copy of pcapfileformat.* files for better
history tracking. The actual code move will happen in next commit.
2023-11-16 11:53:52 +05:30
Srivats P
c285e91d4a Extract libostfile from libprotogui
All .pro file changes only

libostfile still has GUI dependency due to PCAP import options UI.
Need to see if we can remove that - maybe extract into a separate
libostfilegui.
2023-11-16 11:53:52 +05:30
Srivats P
f5350663cd Prepare to extract libostfile from libostprotogui
This commit only copies ostprotogui as ostfile to track changes. Actual
changes will be made in subsequent commits
2023-11-16 11:53:52 +05:30
Srivats P
483f7fb4c5 Fix empty port stats when viewing rsvd ports only
This was a regression introduced in 1.3.0 as part of the port stats UX
improvement. The 'user' row was changed from the first (0) to the last,
but the proxy model continued to use a hard-coded value of 0 to check
for user to determine whether to show a port column or not.
2023-11-08 12:55:39 +05:30
Srivats P
d067019959 Bump to next dev version 1.4.0-dev 2023-11-08 10:44:51 +05:30
Srivats P
eb9d5eaf1a Bump version to 1.3.0 2023-10-19 10:50:25 +05:30
Srivats P
e677dc7d16 Fix div by 0 crash in stream timing stats (jitter) 2023-10-14 12:05:21 +05:30
Srivats P
ebe6aef62d Fix icon-only delegate
The delegate was still rendering display text. But we didn't realize
because the only place where this delegate was used was returning a
QVariant() for DisplayText role earlier. Once we started returning a
valid QString (for copy-paste purposes), this bug showed up.

This didn't work because we further delegate the actual painting to
the base QStyledItemDelegate which re-initializes option.features
from the item index values, so removing HasDisplay does not work.

The fix is to override the displayText() to return a null string.
2023-10-13 12:16:03 +05:30
Srivats P
1e8486991d Include state in port stats text copy to clipboard
The view uses an icon-only delegate for state to suppress the text
display
2023-10-11 13:07:02 +05:30
Srivats P
fff61d7773 Add assert version checks for upcoming 1.3.0 2023-10-11 12:57:25 +05:30
Srivats P
eb3331f74d Update copyright years in About 2023-10-11 12:57:05 +05:30
Srivats P
f3f553d149 Override RxStats filter if specified in drone.ini
This was added to work around any libpcap wrong BPF generation like #354 or
to workaround issues like #355 (stream stats with ICMPv6)
2023-10-03 11:41:57 +05:30
Srivats P
49cb7c40e0 Check and warn about stream stats for ICMP streams
Stream stats are not supportedd for ICMP packets
2023-10-03 11:39:46 +05:30
Srivats P
5581118e2f Work around faulty linux rx stats capture filter
libpcap has a bug on Linux which leads to incorrect BPF generation for our
capture filter. This commit changes the filter expression on Linux such that
correct BPF is generated.

Fixes #354
2023-10-03 10:39:03 +05:30
Srivats P
3bcd31a1ea Use IPv4AddrValidator for IPv4 protocol config UI 2023-07-17 16:10:07 +05:30
Srivats P
268fad0690 Include headers also when copying stream stats
This is for plain text copy format.

This also replaces any newlines in the header text with a space to maintain
formatting.
2023-06-26 18:08:49 +05:30
Srivats P
28b308ce6c Try and fit all stream stats column within window
* Resize columns to content (instead of using default width)
 * Use Kpps/Mpps for Pkt Rate with 3 decimal
 * Use 3 decimal places for bit-rates (is more logical because units change
   every 1000 anyway)
 * Use 2 decimal places for time interval

This just improves the chances of all columns fitting - but is not guaranteed
2023-06-26 17:03:47 +05:30
Srivats P
bfda96a888 Add jitter to Stream Stats in the GUI
These are the GUI side changes to the jitter server side changes committed
earlier.
2023-06-26 14:38:28 +05:30
Srivats P
a2734647b6 Fix stream timing clear for all GUIDs for a port
AbstractPort uses a value of UINT_MAX to signify 'all guids', but
StreamTiming uses 'invalid guid' value which are NOT the same value. But
UINT_MAX is guaranteed to be greater than invalid GUID - so instead of
equality we check for greater than comparison.
2023-06-26 13:03:25 +05:30
Srivats P
5eb2ad1979 Calculate per stream jitter using latency values
We calculate "average jitter".

The changes include only the server side, not the client/GUI side which will
be done in a future commit.
2023-06-26 13:01:11 +05:30
Srivats P
1fa84ec644 Use pkt-set delay instead of list's in intlvd mode
Since we now create an explicit packet set even for interleaved mode, we can
calculate and use the packet set delay instead of using the list loop delay.

With this change both sequential and interleaved mode do not use list loop
delay at all, but we still retain the code for that - in case we need to use
it later for some reason.
2023-06-22 11:45:22 +05:30
Srivats P
c044880f1a Fix inaccurate low tx rates (~1000pps) in seq mode
Creation of explicit packet sets had both a packet set delay and a list loop
delay set leading to lower than configured rate at tx. Earlier implicit
packet set always had set delay as 0 and only list loop delay was used.

Fix is to always set list loop delay to 0 in sequential mode as packet set
will have the correct delay set due to explicit packet sets.
2023-06-22 10:47:13 +05:30
Srivats P
2941c7ec22 Fix crash for top speed tx with ttag in seq mode
Crash was due to loopDelay being 0 in case of topSpeed. For ttags, we
recalculatue loopDelay based on max line rate and avg pkt size.

The problem doesn't exist in interleaved mode because minGap and duration
are non-zero in the top speed tx case.
2023-06-20 11:59:56 +05:30
Srivats P
ee45aaf0eb Change StreamTiming::TxRxKey format
The format now matches the ttag list format input in recordTxTime() making
that API a little more optimized.
2023-06-17 12:14:18 +05:30
Srivats P
5ec7010c51 Fix contention for port streamStats across threads
Tx/Rx stream stats related threads no longer have a direct reference to the
port's stream stats - instead they have their own copy that they keep and
return (in a reset-on-read fashion) when asked for. Each copy also has it's
own lock to prevent contention for read/update/clear.

PcapPort now fetches Tx(Transmitter) and Rx(Poller) stats on demand and
updates the port's stream stats - under protection of a lock.
2023-06-17 11:31:48 +05:30
Srivats P
bef0f1d162 Make stream timing recordTx/Rx APIs inline 2023-06-15 12:56:53 +05:30
Srivats P
598e6bf243 Merge changes made in base code for turbo/latency 2023-06-15 12:32:20 +05:30
Srivats P
63ed64a9d2 Fix extra whitespace in latency feature added code 2023-05-26 11:31:52 +05:30
Srivats P
8ef9da062d Comment out L4Cksum code from pcap tx thread
We are not rewriting L4Cksum for ttag packets at the moment. See
comment in packetsequence.h

Commenting out this code doesn't seem to observably improve tx
performance though.

The latency code seems to reduce stream stats max tx performance
by around 3 to 5%. Recovering this may be done separately as part
of overall performance optimzation of Tx code.
2023-05-26 11:31:52 +05:30
Srivats P
7fee90313e Don't use avgDelay in interleaved mode
The interleaved mode's single packet set MUST always have 0 delay
for accurate rate.

Before latency code, the interleaved packet set was added implicitly
and had 0 delay. Latency code added explicit packet set and used
avgDelay for the set. The avgDelay was needed for the original algo
used to determine when to send ttag packets. That algo is no longer
used (ttag markers are now explicitly configured by AbstractPort).

Turbo still needs avgDelay for other use, but changes will be made in
Turbo code for that.
2023-05-26 11:31:52 +05:30
Srivats P
bafdd948f8 Make win32 specific changes for per-stream latency
There are 2 changes -
1. Encode txPort in ttag packets and use it at TxTtagStats and
RxPcapStats to identify Tx and Rx packets respectively
2. Don't use pcap_sendqueue_transmit() if stream timing is in use -
since we can't modify TTAG packets inside that API
2023-05-26 11:31:52 +05:30
Srivats P
e138fa0d3d Fix windows build issues 2023-05-26 11:31:52 +05:30
Srivats P
8583299a1c Compile out timing debug prints
They can be compiled in if required
2023-05-26 11:31:52 +05:30
Srivats P
4ba98cc520 Fix latency timers not getting started/stopped
In a previous commit, we start/stop these timers based on number of
ports tracking stream stats triggered by RPCs. However, timers cannot
be triggered across threads (RPC thread and main thread in this case).

This fix uses a queued connection to post it to the other queue.
2023-05-26 11:31:52 +05:30
Srivats P
649fa03575 Fix tx ttag pcap filter capture filter syntax
The capture filter was not getting compiled earlier
2023-05-26 11:31:52 +05:30
Srivats P
650c098370 Use TtagTimeInterval to determine ttag markers
This is for interlaved mode; sequential mode was already using it
2023-05-26 11:31:52 +05:30
Srivats P
d375736a39 Don't use udiffTimeStamp() with struct timeval
It will fail to build for non-Linux platforms where TimeStamp is NOT timeval
2023-05-26 11:31:52 +05:30
Srivats P
976fc72de8 Retain seq as param for sendQueueTransmit for now
If and when we remove PacketSequence::ttagL4ChecksumOffset we will take a call
if we should revert back to passing seq->sendQueue instead of seq at that time
2023-05-26 11:31:52 +05:30
Srivats P
4426a51d0f Run stream latency timers only when required
Required => At least one port is tracking stream stats

Also changed some optimization FIXMEs as TODOs
2023-05-26 11:31:52 +05:30
Srivats P
3c6632b6a2 Remove comment about trying read-write lock
The stream timingHash is read by getStreamStats() while it is read/write
for processRecords(), the latter is a more frequent operation so there's
no real benefit of using a read-write lock instead of simple mutex.
2023-05-26 11:31:52 +05:30
Srivats P
d3be505a0c Make app QObject parent of StreamTiming singleton 2023-05-26 11:31:52 +05:30
Srivats P
70f1b6c6e6 Add thread name for PcapRxStats
PcapTxTtagStats thread name has also been shortened since names beyond 16
chars are truncated.
2023-05-26 11:31:52 +05:30
Srivats P
d65fea00d5 Change stream timing GC timer to CLOCK_REALTIME 2023-05-26 11:31:52 +05:30
Srivats P
0afc150264 Don't fix incorrect checksum for Ttag packets
See comments in code
2023-05-26 11:31:52 +05:30
Srivats P
6ba942d00f Remove PcapTxTtagStats::handle_ member
PcaptxTtagStats inherits from PcapSession which already includes a protected
handle_ member.

This removal was likely left off when PcapTxTtagStats started inheriting from
PcapSession.
2023-05-26 11:31:52 +05:30
Srivats P
4ee91c1bc2 Rework sequential mode build for new ttag algo
The previous commit changed the algo to determine which packets were Ttag'd,
but changes were done only for interleaved mode.

This commit adds the changes required for sequential mode.
2023-05-26 11:31:52 +05:30
Srivats P
aebb609e37 Change algo to infer next Ttag pkt (interleaved mode)
The algo works for the following cases of interleaved streams -
 * pktListDuration < ttagTimeInterval
 * pktListDuration > ttagTimeInterval
 * some streams have Ttag, some don't
    - first stream has Ttag
    - first stream does NOT have Ttag
 * no streams have Ttag

Changes for sequential mode are pending
2023-05-26 11:31:52 +05:30
Srivats P
7160f724cb Fix infinite loop in building interleaved streams
Incorrect timestamp comparison was leading to infinite loop
2023-05-26 11:31:52 +05:30
Srivats P
a48a11fe02 Add explicit packet set for interleaved streams
Interleaved mode used an implicitly added packet set in both base and Turbo
code. This has been chaned to use an explicit mode to keep things consistent.

Turbo code still has the implicit packet set related code - that needs to be
removed, once the explicit packet set code is validated and tested.
2023-05-26 11:31:52 +05:30
Srivats P
91a6efdeb7 Use qFrom/ToBigEndian instead of ntohs/htons
For consistency with rest of the code
2023-05-26 11:31:52 +05:30
Srivats P
bfdcee2baa Recompute L4 checksum on-the-fly for TTag packets 2023-05-26 11:31:52 +05:30
Srivats P
c2967b663d Calculate L4 checksum offset for TTag packets 2023-05-26 11:31:52 +05:30
Srivats P
70b5e60440 Rename delay as latency in Protobuf/RPC
The GUI uses the term 'latency', so it's better if the API alsos use the same
term instead of 'delay'
2023-05-26 11:31:52 +05:30
Srivats P
5540253e61 Fix StreamTiming TxRxKey
makeKey was incorrect by mistake
2023-05-26 11:31:52 +05:30
Srivats P
8ecbe78ddd GUI changes to display avg latency
At this time we only show per-guid latency aggregated across all ports
2023-05-26 11:31:52 +05:30
Srivats P
3e3b5144aa Process pending before fetching streamTiming delay 2023-05-26 11:31:52 +05:30
Srivats P
596df69519 Fix MacOS build break by removing unused member
PcapTxTtagStats::lastPcapStats_ was unused because debugStats() was moved to
PcapSession, but removing this member var was left out
2023-05-26 11:31:52 +05:30
Srivats P
680f6eb89f Fix streamTiming garbage collection infinite loop 2023-05-26 11:31:52 +05:30
Srivats P
1b18149aaa Delete PcapTxTtagSession::debugStats
debugStats() was moved to base class PcapSession earlier, but removing it
from here was missed out
2023-05-26 11:31:52 +05:30
Srivats P
bf749847e0 Rename PcapRxStats::id_ as PcapRxStats::portId_
Better code clarity
2023-05-26 11:31:52 +05:30
Srivats P
ab713ce043 Integrate StreamTiming with the code
Bugs found during integration were fixed and minor code improvements were made
such as using consts, const params, renaming members etc.
2023-05-26 11:31:52 +05:30
Srivats P
fc2d8408fa Change StreamTiming::timing_ from QList to QHash
Using QList meant we need to know the port count in the constructor - which is
difficult to know because StreamTiming is designed as a singleton
2023-05-26 11:31:52 +05:30
Srivats P
39c8d6f5f3 Add initial cut of StreamTiming class
This singleton class will keep track of Ttag timing across all ports and GUIDs.

A bunch of FIXMEs/TODOs are pending for this class implementation; also this
class has not been hooked up to the rest of the code yet.
2023-05-26 11:31:52 +05:30
Srivats P
219ad576ad Fix another MacOS build break
Break was due to following warnings (being promoted to errors) -
 * id_ private member was unused
 * tv_usec is int on MacOS (but long on Linux)
2023-05-26 11:31:52 +05:30
Srivats P
3060701386 Move debugStats() from PcapRx to base PcapSession
With this change, other classes that use PcapSession as base can also
use debugStats(), if required
2023-05-26 11:31:52 +05:30
Srivats P
47325c38b0 Ignore failures when stopping stream stats tracking
Stop everything irrespective of any failures
2023-05-26 11:31:52 +05:30
Srivats P
21ce331c43 Create a Tx Ttag tracker thread
For now we are just debug printing timestamp with T-TagId and GUID. We
need to store this tuple and compare when we Rx the same - this will be
in a upcoming commit.
2023-05-26 11:31:52 +05:30
Srivats P
fd2fae711b Fix MacOS build break
For some reason udiffTimeStamp is not defined for MacOS. To be investigated
later.
2023-05-26 11:31:52 +05:30
Srivats P
2d998b3708 Add an incrementing tag id to T-Tag packets 2023-05-26 11:31:52 +05:30
Srivats P
f65aed7bb0 Add infra to update L4 checksum for T-Tag packets
Pending
 * Calculate L4 checksum offset instead of hardcoded value
 * Recalculate packet L4 checksum instead of using 0 value
2023-05-26 11:31:52 +05:30
Srivats P
fdf8c77350 Change T-Tag on the fly 2023-05-26 11:31:52 +05:30
Srivats P
429eff123d Don't create implicit packet sets for Tx
As part of Turbo changes, we made changes to create explicit packet
sets, but for the base code we continued creating implicit packet
sets for some cases. With this change we don't create any implicit
packet set.

This change needs to be tested thoroughly for multiple cases.
2023-05-26 11:31:52 +05:30
Srivats P
3c98900092 Reduce vertical whitespace in sendQueueTransmit() 2023-05-26 11:31:52 +05:30
Srivats P
159cd7c0da Add T-Tag placeholder in sign protocol 2023-05-26 11:31:52 +05:30
Srivats P
46b148b62b Reformat TxThread/run {} to use less vertical space 2023-05-26 11:31:52 +05:30
Srivats P
f29d31d38a Update comments about implict packetset 2023-05-26 11:31:52 +05:30
Srivats P
1056b8d170 Fix werror warning about 0 being signed by default 2023-05-26 11:31:52 +05:30
Srivats P
4a4de23d8a Replace Donate button with Github Sponsors button 2023-05-09 18:27:15 +05:30
70 changed files with 2204 additions and 458 deletions

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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:

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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_;

View File

@ -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();
}

View File

@ -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)

View File

@ -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);
}
};

View File

@ -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");
}

View File

@ -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;
}

View File

@ -96,6 +96,8 @@ public:
virtual int protocolFrameVariableCount() const;
private:
bool isRarp() const;
OstProto::Arp data;
};

View File

@ -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
View 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

View 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;
}

View 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

View File

@ -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>

View File

@ -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)));

View File

@ -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
View 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
View 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

View File

@ -113,6 +113,7 @@ SOURCES += \
udp.cpp \
textproto.cpp \
hexdump.cpp \
packet.cpp \
payload.cpp \
sample.cpp \
sign.cpp \

View File

@ -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
View 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
View 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

View File

@ -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>&#x1F4A1; 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*/)

View File

@ -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);

View 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();
}

View 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

View File

@ -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;

View File

@ -32,12 +32,7 @@ SessionFileFormat::~SessionFileFormat()
{
}
QDialog* SessionFileFormat::openOptionsDialog()
{
return NULL;
}
QDialog* SessionFileFormat::saveOptionsDialog()
QVariantMap* SessionFileFormat::options()
{
return NULL;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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 - "

View File

@ -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

View File

@ -35,12 +35,7 @@ StreamFileFormat::~StreamFileFormat()
{
}
QDialog* StreamFileFormat::openOptionsDialog()
{
return NULL;
}
QDialog* StreamFileFormat::saveOptionsDialog()
QVariantMap* StreamFileFormat::options()
{
return NULL;
}

View File

@ -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);

View File

@ -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
View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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_;
};

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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();
}

View File

@ -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_;

View File

@ -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, &currentPacketSequence_->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
}

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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
View 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
View 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 &timestamp);
bool recordRxTime(uint portId, uint guid, uint ttagId,
const struct timespec &timestamp);
bool recordTxTime(uint portId, uint guid, uint ttagId,
const struct timeval &timestamp);
bool recordRxTime(uint portId, uint guid, uint ttagId,
const struct timeval &timestamp);
bool recordTxTime(uint portId, uint *ttagList, int count,
const struct timespec &timestamp);
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 &timestamp)
{
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 &timestamp)
{
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 &timestamp)
{
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 &timestamp)
{
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 &timestamp)
{
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

View File

@ -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
View 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

View File

@ -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

View File

@ -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>@