/* 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 #include #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(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::max()) { maxRtt = static_cast(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; }