Fix infinite loop when stopping capture etc.

On some platforms and/or some libpcap verisons, libpcap doesn't support a
timeout which makes interactive stop not possible. So we now use a UNIX
signal to break out. Obviously this works only on *nix platforms - which
includes MacOS. For now the problem is not seen on Windows with WinPCAP,
so we should be fine. May need to revisit when we add Npcap support.

Fixes #215, #234
This commit is contained in:
Srivats P 2019-08-10 13:16:18 +05:30
parent ea69335d29
commit 64d1525f50
8 changed files with 204 additions and 13 deletions

View File

@ -50,6 +50,7 @@ SOURCES += \
portmanager.cpp \
abstractport.cpp \
pcapport.cpp \
pcapsession.cpp \
pcaptransmitter.cpp \
pcaprxstats.cpp \
pcaptxstats.cpp \

View File

@ -73,6 +73,8 @@ LinuxPort::LinuxPort(int id, const char *device)
populateInterfaceInfo();
// We don't need per port Rx/Tx monitors for Linux
// No need to stop them because we start them only in
// PcapPort::init which has not yet been called
delete monitorRx_;
delete monitorTx_;
monitorRx_ = monitorTx_ = NULL;

View File

@ -404,6 +404,7 @@ _retry:
}
dumpHandle_ = pcap_dump_open(handle_, qPrintable(capFile_.fileName()));
PcapSession::preRun();
state_ = kRunning;
while (1)
{
@ -425,16 +426,21 @@ _retry:
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
break;
case -2:
qDebug("Loop/signal break or some other error");
break;
default:
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret);
qWarning("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret);
stop_ = true;
}
if (stop_)
{
qDebug("user requested capture stop\n");
qDebug("user requested capture stop");
break;
}
}
PcapSession::postRun();
pcap_dump_close(dumpHandle_);
pcap_close(handle_);
dumpHandle_ = NULL;
@ -464,6 +470,7 @@ void PcapPort::PortCapturer::stop()
{
if (state_ == kRunning) {
stop_ = true;
PcapSession::stop(handle_);
while (state_ == kRunning)
QThread::msleep(10);
}
@ -606,6 +613,7 @@ _retry:
}
_skip_filter:
PcapSession::preRun();
state_ = kRunning;
while (1)
{
@ -643,17 +651,22 @@ _skip_filter:
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
break;
case -2:
qDebug("Loop/signal break or some other error");
break;
default:
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
qWarning("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
ret);
stop_ = true;
}
if (stop_)
{
qDebug("user requested receiver stop\n");
qDebug("user requested receiver stop");
break;
}
}
PcapSession::postRun();
pcap_close(handle_);
handle_ = NULL;
stop_ = false;
@ -680,6 +693,7 @@ void PcapPort::EmulationTransceiver::stop()
{
if (state_ == kRunning) {
stop_ = true;
PcapSession::stop(handle_);
while (state_ == kRunning)
QThread::msleep(10);
}

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 "pcapsession.h"
#include "pcaptransmitter.h"
class PcapPort : public AbstractPort
@ -84,7 +85,7 @@ protected:
kDirectionTx
};
class PortMonitor: public QThread
class PortMonitor: public QThread // TODO: inherit from PcapSession (only if required)
{
public:
PortMonitor(const char *device, Direction direction,
@ -106,7 +107,7 @@ protected:
bool isPromisc_;
};
class PortCapturer: public QThread
class PortCapturer: public PcapSession
{
public:
PortCapturer(const char *device);
@ -133,7 +134,7 @@ protected:
volatile State state_;
};
class EmulationTransceiver: public QThread
class EmulationTransceiver: public PcapSession
{
public:
EmulationTransceiver(const char *device, DeviceManager *deviceManager);

View File

@ -104,6 +104,7 @@ void PcapRxStats::run()
}
_skip_filter:
PcapSession::preRun();
state_ = kRunning;
while (1) {
int ret;
@ -128,16 +129,21 @@ _skip_filter:
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
break;
case -2:
qDebug("Loop/signal break or some other error");
break;
default:
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
qWarning("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
ret);
stop_ = true;
}
if (stop_) {
qDebug("user requested receiver stop\n");
qDebug("user requested rxstats stop");
break;
}
}
PcapSession::postRun();
pcap_close(handle_);
handle_ = NULL;
stop_ = false;
@ -154,7 +160,7 @@ bool PcapRxStats::start()
}
state_ = kNotStarted;
QThread::start();
PcapSession::start();
while (state_ == kNotStarted)
QThread::msleep(10);
@ -166,6 +172,7 @@ bool PcapRxStats::stop()
{
if (state_ == kRunning) {
stop_ = true;
PcapSession::stop(handle_);
while (state_ == kRunning)
QThread::msleep(10);
}

View File

@ -22,10 +22,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "streamstats.h"
#include <QThread>
#include <pcap.h>
#include "pcapsession.h"
class PcapRxStats: public QThread
class PcapRxStats: public PcapSession
{
public:
PcapRxStats(const char *device, StreamStats &portStreamStats);

85
server/pcapsession.cpp Normal file
View File

@ -0,0 +1,85 @@
/*
Copyright (C) 2019 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 "pcapsession.h"
#ifdef Q_OS_UNIX
#include <signal.h>
#define MY_BREAK_SIGNAL SIGUSR1
QHash<ThreadId, bool> PcapSession::signalSeen_;
void PcapSession::preRun()
{
// Should be called in the thread's context
thread_ = pthread_self();
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = PcapSession::signalBreakHandler;
if (!sigaction(MY_BREAK_SIGNAL, &sa, NULL)) {
signalSeen_[thread_] = false;
qDebug("Break signal handler installed");
}
else
qWarning("Failed to install MY_BREAK_SIGNAL handler");
}
void PcapSession::postRun()
{
// Should be called in the thread's context
ThreadId id = pthread_self();
qDebug("In %s::%s", typeid(*this).name(), __FUNCTION__);
if (!signalSeen_.contains(id)) {
qWarning("Thread not found in signalSeen");
return;
}
bool &seen = signalSeen_[id];
// XXX: don't exit the thread until we see the signal; if we don't
// some platforms will crash
if (!seen) {
qDebug("Wait for signal");
while (!seen)
QThread::msleep(10);
}
signalSeen_.remove(id);
qDebug("Signal seen and handled");
}
void PcapSession::stop(pcap_t *handle)
{
// Should be called OUTSIDE the thread's context
// XXX: As per the man page for pcap_breakloop, we need both
// pcap_breakloop and a mechanism to interrupt system calls;
// we use a signal for the latter
// TODO: If the signal mechanism doesn't work, we could try
// pthread_cancel(thread_);
pcap_breakloop(handle);
pthread_kill(thread_.nativeId(), MY_BREAK_SIGNAL);
}
void PcapSession::signalBreakHandler(int /*signum*/)
{
qDebug("In %s", __FUNCTION__);
signalSeen_[pthread_self()] = true;
}
#endif

82
server/pcapsession.h Normal file
View File

@ -0,0 +1,82 @@
/*
Copyright (C) 2019 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_SESSION_H
#define _PCAP_SESSION_H
#include <QThread>
#include <pcap.h>
#ifdef Q_OS_UNIX
#include <QHash>
#include <pthread.h>
class ThreadId {
public:
ThreadId() {
id_ = pthread_self();
}
ThreadId(pthread_t id) {
id_ = id;
}
pthread_t nativeId() {
return id_;
}
uint hash() const {
QByteArray t((const char*)(&id_), sizeof(id_));
return qHash(t);
}
bool operator==(const ThreadId &other) const {
return (pthread_equal(id_, other.id_) != 0);
}
private:
pthread_t id_;
};
inline uint qHash(const ThreadId &key)
{
return key.hash();
}
class PcapSession: public QThread
{
protected:
void preRun();
void postRun();
void stop(pcap_t *handle);
private:
static void signalBreakHandler(int /*signum*/);
ThreadId thread_;
static QHash<ThreadId, bool> signalSeen_;
};
#else
class PcapSession: public QThread
{
protected:
void preRun() {};
void postRun() {};
void stop(pcap_t *handle) {
qDebug("calling breakloop with handle %p", handle);
pcap_breakloop(handle);
}
};
#endif
#endif