File: pcaprxstats.cpp

package info (click to toggle)
ostinato 1.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 4,840 kB
  • sloc: cpp: 46,226; makefile: 8
file content (256 lines) | stat: -rw-r--r-- 7,576 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
Copyright (C) 2016 Srivats P.

This file is part of "Ostinato"

This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

#include "pcaprxstats.h"

#include "pcapextra.h"
#include "../common/debugdefs.h"
#include "../common/sign.h"
#include "settings.h"
#include "streamtiming.h"

#define Xnotify qWarning // FIXME

PcapRxStats::PcapRxStats(const char *device, int id)
{
    setObjectName(QString("Rx$:%1").arg(device));
    device_ = QString::fromLatin1(device);
    stop_ = false;
    state_ = kNotStarted;
    isDirectional_ = true;

    handle_ = NULL;

    portId_ = id;

    timing_ = StreamTiming::instance();
}

pcap_t* PcapRxStats::handle()
{
    return handle_;
}

void PcapRxStats::run()
{
    int flags = PCAP_OPENFLAG_PROMISCUOUS;
    char errbuf[PCAP_ERRBUF_SIZE] = "";
    struct bpf_program bpf;
    const int optimize = 1;
    QString capture_filter = QString("(ether[len - 4:4] == 0x%1)").arg(
            SignProtocol::magic(), 0, BASE_HEX);
    // XXX: Exclude ICMP packets which contain an embedded signed packet
    //      For now we check upto 4 vlan tags
    // 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 "
            "(vlan and icmp) or "
            "(vlan and icmp) or "
            "(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);
    if (handle_ == NULL) {
        if (flags && QString(errbuf).contains("promiscuous")) {
            Xnotify("Unable to set promiscuous mode on <%s> - "
                    "stream stats rx 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_IN) < 0) {
        qDebug("RxStats: Error setting IN direction %s: %s\n",
                qPrintable(device_), pcap_geterr(handle_));
        isDirectional_ = false;
    }
#endif

    if (pcap_compile(handle_, &bpf, qPrintable(capture_filter),
                     optimize, 0) < 0) {
        qWarning("%s: error compiling filter: %s", qPrintable(device_),
                pcap_geterr(handle_));
        goto _skip_filter;
    }

    if (pcap_setfilter(handle_, &bpf) < 0) {
        qWarning("%s: error setting filter: %s", qPrintable(device_),
                pcap_geterr(handle_));
        goto _skip_filter;
    }

_skip_filter:
    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, 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;
                }
                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("Loop/signal break or some other error");
                break;
            default:
                qWarning("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
                        ret);
                stop_ = true;
        }

        if (stop_) {
            qDebug("user requested rxstats stop");
            break;
        }
    }
    PcapSession::postRun();
    pcap_close(handle_);
    handle_ = NULL;
    stop_ = false;

_exit:
    state_ = kFinished;
}

bool PcapRxStats::start()
{
    if (state_ == kRunning) {
        qWarning("RxStats start requested but is already running!");
        goto _exit;
    }

    state_ = kNotStarted;
    PcapSession::start();

    while (state_ == kNotStarted)
        QThread::msleep(10);
_exit:
    return true;
}

bool PcapRxStats::stop()
{
    if (state_ == kRunning) {
        stop_ = true;
        PcapSession::stop();
        while (state_ == kRunning)
            QThread::msleep(10);
    }
    else
        qWarning("RxStats stop requested but is not running!");

    return true;
}

bool PcapRxStats::isRunning()
{
    return (state_ == kRunning);
}

bool PcapRxStats::isDirectional()
{
    return isDirectional_;
}

// XXX: Stats are reset on read
void PcapRxStats::updateRxStreamStats(StreamStats &streamStats)
{
    QMutexLocker lock(&streamStatsLock_);
    StreamStatsIterator i(streamStats_);

    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;
    }
    streamStats_.clear();
}