amnezia-client/client/mozilla/pinghelper.cpp
pokamest 35ecb8499d
WireGuard for MacOS (#248)
* WireGuard for MacOS
* Fix openvpn block-outside-dns
2023-07-15 22:19:48 +01:00

189 lines
5 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "pinghelper.h"
#include <QDateTime>
#include <cmath>
#include "dnspingsender.h"
#include "leakdetector.h"
#include "logger.h"
#include "pingsender.h"
#include "pingsenderfactory.h"
// Any X seconds, a new ping.
constexpr uint32_t PING_TIMEOUT_SEC = 1;
// Maximum window size for ping statistics.
constexpr int PING_STATS_WINDOW = 32;
namespace {
Logger logger("PingHelper");
}
PingHelper::PingHelper() {
MZ_COUNT_CTOR(PingHelper);
m_sequence = 0;
m_pingData.resize(PING_STATS_WINDOW);
connect(&m_pingTimer, &QTimer::timeout, this, &PingHelper::nextPing);
}
PingHelper::~PingHelper() { MZ_COUNT_DTOR(PingHelper); }
void PingHelper::start(const QString& serverIpv4Gateway,
const QString& deviceIpv4Address) {
logger.debug() << "PingHelper activated for server:"
<< logger.sensitive(serverIpv4Gateway);
m_gateway = QHostAddress(serverIpv4Gateway);
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
m_pingSender = PingSenderFactory::create(m_source, this);
// Some platforms require root access to send and receive ICMP pings. If
// we happen to be on one of these unlucky devices, create a DnsPingSender
// instead.
if (!m_pingSender->isValid()) {
delete m_pingSender;
m_pingSender = new DnsPingSender(m_source, this);
}
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
Qt::QueuedConnection);
connect(m_pingSender, &PingSender::criticalPingError, this,
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
// Reset the ping statistics
m_sequence = 0;
for (int i = 0; i < PING_STATS_WINDOW; i++) {
m_pingData[i].timestamp = -1;
m_pingData[i].latency = -1;
m_pingData[i].sequence = 0;
}
m_pingTimer.start(PING_TIMEOUT_SEC * 1000);
}
void PingHelper::stop() {
logger.debug() << "PingHelper deactivated";
if (m_pingSender) {
delete m_pingSender;
m_pingSender = nullptr;
}
m_pingTimer.stop();
}
void PingHelper::nextPing() {
#ifdef MZ_DEBUG
logger.debug() << "Sending ping seq:" << m_sequence;
#endif
// The ICMP sequence number is used to match replies with their originating
// request, and serves as an index into the circular buffer. Overflows of
// the sequence number acceptable.
int index = m_sequence % PING_STATS_WINDOW;
m_pingData[index].timestamp = QDateTime::currentMSecsSinceEpoch();
m_pingData[index].latency = -1;
m_pingData[index].sequence = m_sequence;
m_pingSender->sendPing(m_gateway, m_sequence);
m_sequence++;
}
void PingHelper::pingReceived(quint16 sequence) {
int index = sequence % PING_STATS_WINDOW;
if (m_pingData[index].sequence == sequence) {
qint64 sendTime = m_pingData[index].timestamp;
m_pingData[index].latency = QDateTime::currentMSecsSinceEpoch() - sendTime;
emit pingSentAndReceived(m_pingData[index].latency);
#ifdef MZ_DEBUG
logger.debug() << "Ping answer received seq:" << sequence
<< "avg:" << latency()
<< "loss:" << QString("%1%").arg(loss() * 100.0)
<< "stddev:" << stddev();
#endif
}
}
uint PingHelper::latency() const {
int recvCount = 0;
qint64 totalMsec = 0;
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
recvCount++;
totalMsec += data.latency;
}
if (recvCount <= 0) {
return 0.0;
}
// Add half the denominator to produce nearest-integer rounding.
totalMsec += recvCount / 2;
return static_cast<uint>(totalMsec / recvCount);
}
uint PingHelper::stddev() const {
int recvCount = 0;
qint64 totalVariance = 0;
uint average = PingHelper::latency();
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
recvCount++;
totalVariance += (average - data.latency) * (average - data.latency);
}
if (recvCount <= 0) {
return 0.0;
}
return std::sqrt((double)totalVariance / recvCount);
}
uint PingHelper::maximum() const {
uint maxRtt = 0;
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
if (data.latency > maxRtt &&
data.latency < std::numeric_limits<uint>::max()) {
maxRtt = static_cast<uint>(data.latency);
}
}
return maxRtt;
}
double PingHelper::loss() const {
int sendCount = 0;
int recvCount = 0;
// Don't count pings that are possibly still in flight as losses.
qint64 sendBefore =
QDateTime::currentMSecsSinceEpoch() - (PING_TIMEOUT_SEC * 1000);
for (const PingSendData& data : m_pingData) {
if (data.latency >= 0) {
recvCount++;
sendCount++;
} else if ((data.timestamp > 0) && (data.timestamp < sendBefore)) {
sendCount++;
}
}
if (sendCount <= 0) {
return 0.0;
}
return (double)(sendCount - recvCount) / PING_STATS_WINDOW;
}