mirror of
https://github.com/Qv2ray/Qv2ray.git
synced 2025-05-20 19:00:22 +08:00
Introduce async ICMP ping and TCP ping for UNIX and Windows (#749)
Thanks DuckVador
This commit is contained in:
parent
c4f0db170c
commit
274ab69db5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
# Some files
|
||||
compile_commands.json
|
||||
.DS_Store
|
||||
*.pro.user
|
||||
*.user
|
||||
|
10
.gitmodules
vendored
10
.gitmodules
vendored
@ -19,3 +19,13 @@
|
||||
[submodule "3rdparty/uistyles"]
|
||||
path = 3rdparty/uistyles
|
||||
url = https://github.com/Qv2ray/QvUIStyles
|
||||
[submodule "3rdparty/backward-cpp"]
|
||||
path = 3rdparty/backward-cpp
|
||||
url = https://github.com/bombela/backward-cpp
|
||||
[submodule "libuv"]
|
||||
path = 3rdparty/libuv
|
||||
url = https://github.com/libuv/libuv.git
|
||||
[submodule "3rdparty/uvw"]
|
||||
path = 3rdparty/uvw
|
||||
url = https://github.com/skypjack/uvw.git
|
||||
|
||||
|
1
3rdparty/libuv
vendored
Submodule
1
3rdparty/libuv
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1ab9ea3790378f9f25c4e78e9e2b511c75f9c9ed
|
1
3rdparty/uvw
vendored
Submodule
1
3rdparty/uvw
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 52785475b9cb727f6912f8cc00ba65ff3bd44e67
|
@ -80,7 +80,7 @@ message(" ")
|
||||
|
||||
if(WIN32)
|
||||
add_compile_options("/std:c++17")
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
add_definitions(-DUNICODE -D_UNICODE -DNOMINMAX)
|
||||
add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
|
||||
set(GUI_TYPE WIN32)
|
||||
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
|
||||
@ -139,6 +139,7 @@ endif()
|
||||
# ==================================================================================
|
||||
# 3rdparty Sources
|
||||
# ==================================================================================
|
||||
include(cmake/libuv.cmake)
|
||||
include(cmake/translations.cmake)
|
||||
include(cmake/qnodeeditor.cmake)
|
||||
if (ANDROID)
|
||||
@ -234,6 +235,7 @@ target_link_libraries(qv2ray-baselib
|
||||
${ZXING_LIBRARY}
|
||||
Threads::Threads
|
||||
${QV2RAY_QT_LIBS}
|
||||
${LibUV_LIBRARIES}
|
||||
)
|
||||
|
||||
target_link_libraries(qv2ray
|
||||
@ -257,6 +259,7 @@ target_include_directories(qv2ray-baselib PUBLIC
|
||||
${CMAKE_BINARY_DIR}
|
||||
${ZXING_INCLUDE_PATH}
|
||||
${Protobuf_INCLUDE_DIRS}
|
||||
${LibUV_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
|
@ -37,6 +37,7 @@ set(QV2RAY_LIB_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.hpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPing.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPingWork.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPing.hpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.hpp
|
||||
|
35
cmake/libuv.cmake
Normal file
35
cmake/libuv.cmake
Normal file
@ -0,0 +1,35 @@
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libuv)
|
||||
add_library(uv::uv-static ALIAS uv_a)
|
||||
set_target_properties(uv_a PROPERTIES POSITION_INDEPENDENT_CODE 1)
|
||||
set(UVW_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/async.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/check.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/dns.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/emitter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/fs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/fs_event.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/fs_poll.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/idle.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/lib.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/loop.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/pipe.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/poll.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/prepare.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/process.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/signal.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/stream.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/tcp.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/thread.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/timer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/tty.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/util.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/work.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src/uvw/udp.cpp
|
||||
)
|
||||
add_library(UVW_LIB ${UVW_SOURCES})
|
||||
target_compile_definitions(UVW_LIB PUBLIC UVW_AS_LIB)
|
||||
target_link_libraries(UVW_LIB uv::uv-static)
|
||||
set(LibUV_LIBRARIES uv::uv-static UVW_LIB)
|
||||
set(LibUV_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libuv/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src
|
||||
)
|
@ -1 +1 @@
|
||||
5748
|
||||
5748
|
@ -1,50 +1,97 @@
|
||||
#include "uvw.hpp"
|
||||
#include "LatencyTest.hpp"
|
||||
|
||||
#include "LatencyTestThread.hpp"
|
||||
#include "core/handler/ConfigHandler.hpp"
|
||||
|
||||
constexpr auto LATENCY_PROPERTY_KEY = "__QvLatencyTest__";
|
||||
|
||||
namespace Qv2ray::components::latency
|
||||
{
|
||||
int getSockAddress(std::shared_ptr<uvw::Loop> &loop, const char *host, int port, struct sockaddr_storage *storage, int ipv6first)
|
||||
{
|
||||
if (uv_ip4_addr(host, port, reinterpret_cast<sockaddr_in *>(storage)) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (uv_ip6_addr(host, port, reinterpret_cast<sockaddr_in6 *>(storage)) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// not an ip
|
||||
auto getAddrInfoReq = loop->resource<uvw::GetAddrInfoReq>();
|
||||
char digitBuffer[20] = { 0 };
|
||||
sprintf(digitBuffer, "%d", port);
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
auto dns_res = getAddrInfoReq->addrInfoSync(host, digitBuffer, &hints);
|
||||
int prefer_af = ipv6first ? AF_INET6 : AF_INET;
|
||||
if (dns_res.first)
|
||||
{
|
||||
struct addrinfo *rp = nullptr;
|
||||
for (rp = dns_res.second.get(); rp != nullptr; rp = rp->ai_next)
|
||||
if (rp->ai_family == prefer_af)
|
||||
{
|
||||
if (rp->ai_family == AF_INET)
|
||||
memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in));
|
||||
else if (rp->ai_family == AF_INET6)
|
||||
memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in6));
|
||||
break;
|
||||
}
|
||||
if (rp == nullptr)
|
||||
{
|
||||
// fallback: if we can't find prefered AF, then we choose alternative.
|
||||
for (rp = dns_res.second.get(); rp != nullptr; rp = rp->ai_next)
|
||||
{
|
||||
if (rp->ai_family == AF_INET)
|
||||
memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in));
|
||||
else if (rp->ai_family == AF_INET6)
|
||||
memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in6));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rp == nullptr)
|
||||
{
|
||||
return -1; // dns not resolved
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1; // dns not resolved
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
LatencyTestHost::LatencyTestHost(const int defaultCount, QObject *parent) : QObject(parent)
|
||||
{
|
||||
qRegisterMetaType<ConnectionId>();
|
||||
qRegisterMetaType<LatencyTestResult>();
|
||||
totalTestCount = defaultCount;
|
||||
latencyThread = new LatencyTestThread(this);
|
||||
latencyThread->start();
|
||||
}
|
||||
|
||||
LatencyTestHost::~LatencyTestHost()
|
||||
{
|
||||
latencyThread->stopLatencyTest();
|
||||
latencyThread->wait();
|
||||
}
|
||||
|
||||
void LatencyTestHost::StopAllLatencyTest()
|
||||
{
|
||||
for (const auto &thread : latencyThreads)
|
||||
{
|
||||
thread->terminate();
|
||||
}
|
||||
latencyThreads.clear();
|
||||
latencyThread->stopLatencyTest();
|
||||
latencyThread->wait();
|
||||
latencyThread->start();
|
||||
}
|
||||
|
||||
void LatencyTestHost::TestLatency(const ConnectionId &id, Qv2rayLatencyTestingMethod method)
|
||||
{
|
||||
const auto &[protocol, host, port] = GetConnectionInfo(id);
|
||||
auto thread = new LatencyTestThread(host, port, method, totalTestCount, this);
|
||||
connect(thread, &QThread::finished, this, &LatencyTestHost::OnLatencyThreadProcessCompleted);
|
||||
thread->setProperty(LATENCY_PROPERTY_KEY, QVariant::fromValue(id));
|
||||
latencyThreads.push_back(thread);
|
||||
thread->start();
|
||||
latencyThread->pushRequest(id, totalTestCount, method);
|
||||
}
|
||||
|
||||
void LatencyTestHost::OnLatencyThreadProcessCompleted()
|
||||
void LatencyTestHost::TestLatency(const QList<ConnectionId> &ids, Qv2rayLatencyTestingMethod method)
|
||||
{
|
||||
const auto senderThread = qobject_cast<LatencyTestThread *>(sender());
|
||||
latencyThreads.removeOne(senderThread);
|
||||
auto result = senderThread->GetResult();
|
||||
|
||||
if (!result.errorMessage.isEmpty())
|
||||
{
|
||||
LOG(MODULE_NETWORK, "Ping --> " + result.errorMessage)
|
||||
result.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
result.best = LATENCY_TEST_VALUE_ERROR;
|
||||
result.worst = LATENCY_TEST_VALUE_ERROR;
|
||||
}
|
||||
|
||||
emit OnLatencyTestCompleted(senderThread->property(LATENCY_PROPERTY_KEY).value<ConnectionId>(), result);
|
||||
latencyThread->pushRequest(ids, totalTestCount, method);
|
||||
}
|
||||
|
||||
} // namespace Qv2ray::components::latency
|
||||
|
@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
#include "base/Qv2rayBase.hpp"
|
||||
|
||||
namespace uvw
|
||||
{
|
||||
class Loop;
|
||||
}
|
||||
struct sockaddr_storage;
|
||||
namespace Qv2ray::components::latency
|
||||
{
|
||||
class LatencyTestThread;
|
||||
@ -14,6 +18,16 @@ namespace Qv2ray::components::latency
|
||||
long avg = LATENCY_TEST_VALUE_ERROR;
|
||||
Qv2rayLatencyTestingMethod method;
|
||||
};
|
||||
struct LatencyTestRequest
|
||||
{
|
||||
ConnectionId id;
|
||||
QString host;
|
||||
int port;
|
||||
int totalCount;
|
||||
Qv2rayLatencyTestingMethod method;
|
||||
};
|
||||
|
||||
int getSockAddress(std::shared_ptr<uvw::Loop> &loop, const char *host, int port, struct sockaddr_storage *storage, int ipv6first);
|
||||
|
||||
class LatencyTestHost : public QObject
|
||||
{
|
||||
@ -21,21 +35,22 @@ namespace Qv2ray::components::latency
|
||||
public:
|
||||
explicit LatencyTestHost(const int defaultCount = 3, QObject *parent = nullptr);
|
||||
void TestLatency(const ConnectionId &connectionId, Qv2rayLatencyTestingMethod);
|
||||
void TestLatency(const QList<ConnectionId> &connectionIds, Qv2rayLatencyTestingMethod);
|
||||
void StopAllLatencyTest();
|
||||
~LatencyTestHost()
|
||||
{
|
||||
StopAllLatencyTest();
|
||||
}
|
||||
signals:
|
||||
void OnLatencyTestCompleted(const ConnectionId &id, const LatencyTestResult &data);
|
||||
|
||||
private slots:
|
||||
void OnLatencyThreadProcessCompleted();
|
||||
~LatencyTestHost() override;
|
||||
|
||||
signals:
|
||||
void OnLatencyTestCompleted(ConnectionId id, LatencyTestResult data);
|
||||
|
||||
private:
|
||||
int totalTestCount;
|
||||
QList<LatencyTestThread *> latencyThreads;
|
||||
// we're not introduce multi latency test thread for now,
|
||||
// cause it's easy to use a scheduler like round-robin scheme
|
||||
// and libuv event loop is fast.
|
||||
LatencyTestThread *latencyThread;
|
||||
};
|
||||
} // namespace Qv2ray::components::latency
|
||||
|
||||
using namespace Qv2ray::components::latency;
|
||||
Q_DECLARE_METATYPE(LatencyTestResult)
|
||||
|
@ -1,84 +1,83 @@
|
||||
#include "LatencyTestThread.hpp"
|
||||
|
||||
#include "TCPing.hpp"
|
||||
#include "core/CoreUtils.hpp"
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include "unix/ICMPPing.hpp"
|
||||
#else
|
||||
#include "win/ICMPPing.hpp"
|
||||
#endif
|
||||
#include "uvw.hpp"
|
||||
|
||||
namespace Qv2ray::components::latency
|
||||
{
|
||||
|
||||
LatencyTestThread::LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod method, int count, QObject *parent)
|
||||
: QThread(parent)
|
||||
LatencyTestThread::LatencyTestThread(QObject *parent) : QThread(parent)
|
||||
{
|
||||
this->count = count;
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
this->method = method;
|
||||
this->resultData = {};
|
||||
this->resultData.method = method;
|
||||
}
|
||||
|
||||
void LatencyTestThread::pushRequest(const ConnectionId &id, int totalTestCount, Qv2rayLatencyTestingMethod method)
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard{ m };
|
||||
const auto &[protocol, host, port] = GetConnectionInfo(id);
|
||||
requests.emplace_back(LatencyTestRequest{ id, host, port, totalTestCount, method });
|
||||
}
|
||||
|
||||
void LatencyTestThread::run()
|
||||
{
|
||||
resultData.avg = 0;
|
||||
resultData.best = 0;
|
||||
resultData.worst = 0;
|
||||
switch (method)
|
||||
loop = uvw::Loop::create();
|
||||
stopTimer = loop->resource<uvw::TimerHandle>();
|
||||
stopTimer->on<uvw::TimerEvent>([this](auto &, auto &handle) {
|
||||
if (isStop)
|
||||
{
|
||||
handle.stop();
|
||||
handle.close();
|
||||
requests.clear();
|
||||
loop->clear();
|
||||
loop->close();
|
||||
loop->stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard{ m, std::defer_lock };
|
||||
if (!lockGuard.try_lock())
|
||||
return;
|
||||
if (requests.empty())
|
||||
return;
|
||||
auto parent = qobject_cast<LatencyTestHost *>(this->parent());
|
||||
for (auto &req : requests)
|
||||
{
|
||||
switch (req.method)
|
||||
{
|
||||
case ICMPING:
|
||||
{
|
||||
auto ptr = std::make_shared<icmping::ICMPPing>(30);
|
||||
ptr->start(loop, req, parent);
|
||||
}
|
||||
break;
|
||||
case TCPING:
|
||||
default:
|
||||
{
|
||||
auto ptr = std::make_shared<tcping::TCPing>();
|
||||
ptr->start(loop, req, parent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
requests.clear();
|
||||
}
|
||||
});
|
||||
stopTimer->start(uvw::TimerHandle::Time{ 500 }, uvw::TimerHandle::Time{ 500 });
|
||||
loop->run();
|
||||
}
|
||||
void LatencyTestThread::pushRequest(const QList<ConnectionId> &ids, int totalTestCount, Qv2rayLatencyTestingMethod method)
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard{ m };
|
||||
for (const auto &id : ids)
|
||||
{
|
||||
case ICMPING:
|
||||
{
|
||||
icmping::ICMPPing pingHelper(30);
|
||||
for (auto i = 0; i < count; i++)
|
||||
{
|
||||
resultData.totalCount++;
|
||||
const auto value = pingHelper.ping(host);
|
||||
const auto _latency = value.first;
|
||||
const auto errMessage = value.second;
|
||||
if (!errMessage.isEmpty())
|
||||
{
|
||||
resultData.errorMessage.append(NEWLINE + errMessage);
|
||||
resultData.failedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Is it Windows?
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
resultData.avg += _latency;
|
||||
#define _qvmin_(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define _qvmax_(x, y) ((x) > (y) ? (x) : (y))
|
||||
resultData.best = _qvmin_(resultData.best, _latency);
|
||||
resultData.worst = _qvmax_(resultData.worst, _latency);
|
||||
#undef _qvmax_
|
||||
#undef _qvmin_
|
||||
}
|
||||
}
|
||||
if (resultData.totalCount > 0 && resultData.failedCount != resultData.totalCount)
|
||||
{
|
||||
resultData.errorMessage.clear();
|
||||
// ms to s
|
||||
resultData.avg = resultData.avg / (resultData.totalCount - resultData.failedCount) / 1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultData.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
LOG(MODULE_NETWORK, resultData.errorMessage)
|
||||
}
|
||||
//
|
||||
//
|
||||
break;
|
||||
}
|
||||
case TCPING:
|
||||
default:
|
||||
{
|
||||
this->resultData = tcping::TestTCPLatency(host, port, count);
|
||||
break;
|
||||
}
|
||||
const auto &[protocol, host, port] = GetConnectionInfo(id);
|
||||
requests.emplace_back(LatencyTestRequest{ id, host, port, totalTestCount, method });
|
||||
}
|
||||
}
|
||||
} // namespace Qv2ray::components::latency
|
||||
|
@ -2,27 +2,36 @@
|
||||
#include "LatencyTest.hpp"
|
||||
|
||||
#include <QThread>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
namespace uvw
|
||||
{
|
||||
class Loop;
|
||||
class TimerHandle;
|
||||
}
|
||||
namespace Qv2ray::components::latency
|
||||
{
|
||||
class LatencyTestThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod, int count, QObject *parent = nullptr);
|
||||
LatencyTestResult GetResult() const
|
||||
explicit LatencyTestThread(QObject *parent = nullptr);
|
||||
void stopLatencyTest()
|
||||
{
|
||||
return resultData;
|
||||
isStop = true;
|
||||
}
|
||||
void pushRequest(const QList<ConnectionId> &ids, int totalTestCount, Qv2rayLatencyTestingMethod method);
|
||||
void pushRequest(const ConnectionId &id, int totalTestCount, Qv2rayLatencyTestingMethod method);
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
LatencyTestResult resultData;
|
||||
QString host;
|
||||
int port;
|
||||
int count;
|
||||
Qv2rayLatencyTestingMethod method;
|
||||
std::shared_ptr<uvw::Loop> loop;
|
||||
bool isStop = false;
|
||||
std::shared_ptr<uvw::TimerHandle> stopTimer;
|
||||
std::vector<LatencyTestRequest> requests;
|
||||
std::mutex m;
|
||||
|
||||
// static LatencyTestResult TestLatency_p(const ConnectionId &id, const int count);
|
||||
};
|
||||
|
@ -1,364 +1,60 @@
|
||||
#include "TCPing.hpp"
|
||||
#include "uvw.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <WS2tcpip.h>
|
||||
#include <WinSock2.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Qv2ray::components::latency::tcping
|
||||
{
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
using qv_socket_t = SOCKET;
|
||||
struct TranslateWSAError{
|
||||
~TranslateWSAError(){
|
||||
errno = translate_sys_error(WSAGetLastError());
|
||||
}
|
||||
int translate_sys_error(int sys_errno) {
|
||||
LOG(MODULE_NETWORK, "translate_sys_error:WSAGetLastError()==" + QSTRN(sys_errno))
|
||||
switch (sys_errno) {
|
||||
case ERROR_NOACCESS: return EACCES;
|
||||
case WSAEACCES: return EACCES;
|
||||
case ERROR_ELEVATION_REQUIRED: return EACCES;
|
||||
case ERROR_CANT_ACCESS_FILE: return EACCES;
|
||||
case ERROR_ADDRESS_ALREADY_ASSOCIATED: return EADDRINUSE;
|
||||
case WSAEADDRINUSE: return EADDRINUSE;
|
||||
case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
|
||||
case WSAEAFNOSUPPORT: return EAFNOSUPPORT;
|
||||
case WSAEWOULDBLOCK: return EAGAIN;
|
||||
case WSAEALREADY: return EALREADY;
|
||||
case ERROR_INVALID_FLAGS: return EBADF;
|
||||
case ERROR_INVALID_HANDLE: return EBADF;
|
||||
case ERROR_LOCK_VIOLATION: return EBUSY;
|
||||
case ERROR_PIPE_BUSY: return EBUSY;
|
||||
case ERROR_SHARING_VIOLATION: return EBUSY;
|
||||
case ERROR_OPERATION_ABORTED: return ECANCELED;
|
||||
case WSAEINTR: return ECANCELED;
|
||||
/*case ERROR_NO_UNICODE_TRANSLATION: return ECHARSET;*/
|
||||
case ERROR_CONNECTION_ABORTED: return ECONNABORTED;
|
||||
case WSAECONNABORTED: return ECONNABORTED;
|
||||
case ERROR_CONNECTION_REFUSED: return ECONNREFUSED;
|
||||
case WSAECONNREFUSED: return ECONNREFUSED;
|
||||
case ERROR_NETNAME_DELETED: return ECONNRESET;
|
||||
case WSAECONNRESET: return ECONNRESET;
|
||||
case ERROR_ALREADY_EXISTS: return EEXIST;
|
||||
case ERROR_FILE_EXISTS: return EEXIST;
|
||||
case ERROR_BUFFER_OVERFLOW: return EFAULT;
|
||||
case WSAEFAULT: return EFAULT;
|
||||
case ERROR_HOST_UNREACHABLE: return EHOSTUNREACH;
|
||||
case WSAEHOSTUNREACH: return EHOSTUNREACH;
|
||||
case ERROR_INSUFFICIENT_BUFFER: return EINVAL;
|
||||
case ERROR_INVALID_DATA: return EINVAL;
|
||||
case ERROR_INVALID_PARAMETER: return EINVAL;
|
||||
case ERROR_SYMLINK_NOT_SUPPORTED: return EINVAL;
|
||||
case WSAEINVAL: return EINVAL;
|
||||
case WSAEPFNOSUPPORT: return EINVAL;
|
||||
case WSAESOCKTNOSUPPORT: return EINVAL;
|
||||
case ERROR_BEGINNING_OF_MEDIA: return EIO;
|
||||
case ERROR_BUS_RESET: return EIO;
|
||||
case ERROR_CRC: return EIO;
|
||||
case ERROR_DEVICE_DOOR_OPEN: return EIO;
|
||||
case ERROR_DEVICE_REQUIRES_CLEANING: return EIO;
|
||||
case ERROR_DISK_CORRUPT: return EIO;
|
||||
case ERROR_EOM_OVERFLOW: return EIO;
|
||||
case ERROR_FILEMARK_DETECTED: return EIO;
|
||||
case ERROR_GEN_FAILURE: return EIO;
|
||||
case ERROR_INVALID_BLOCK_LENGTH: return EIO;
|
||||
case ERROR_IO_DEVICE: return EIO;
|
||||
case ERROR_NO_DATA_DETECTED: return EIO;
|
||||
case ERROR_NO_SIGNAL_SENT: return EIO;
|
||||
case ERROR_OPEN_FAILED: return EIO;
|
||||
case ERROR_SETMARK_DETECTED: return EIO;
|
||||
case ERROR_SIGNAL_REFUSED: return EIO;
|
||||
case WSAEISCONN: return EISCONN;
|
||||
case ERROR_CANT_RESOLVE_FILENAME: return ELOOP;
|
||||
case ERROR_TOO_MANY_OPEN_FILES: return EMFILE;
|
||||
case WSAEMFILE: return EMFILE;
|
||||
case WSAEMSGSIZE: return EMSGSIZE;
|
||||
case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG;
|
||||
case ERROR_NETWORK_UNREACHABLE: return ENETUNREACH;
|
||||
case WSAENETUNREACH: return ENETUNREACH;
|
||||
case WSAENOBUFS: return ENOBUFS;
|
||||
case ERROR_BAD_PATHNAME: return ENOENT;
|
||||
case ERROR_DIRECTORY: return ENOENT;
|
||||
case ERROR_ENVVAR_NOT_FOUND: return ENOENT;
|
||||
case ERROR_FILE_NOT_FOUND: return ENOENT;
|
||||
case ERROR_INVALID_NAME: return ENOENT;
|
||||
case ERROR_INVALID_DRIVE: return ENOENT;
|
||||
case ERROR_INVALID_REPARSE_DATA: return ENOENT;
|
||||
case ERROR_MOD_NOT_FOUND: return ENOENT;
|
||||
case ERROR_PATH_NOT_FOUND: return ENOENT;
|
||||
case WSAHOST_NOT_FOUND: return ENOENT;
|
||||
case WSANO_DATA: return ENOENT;
|
||||
case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM;
|
||||
case ERROR_OUTOFMEMORY: return ENOMEM;
|
||||
case ERROR_CANNOT_MAKE: return ENOSPC;
|
||||
case ERROR_DISK_FULL: return ENOSPC;
|
||||
case ERROR_EA_TABLE_FULL: return ENOSPC;
|
||||
case ERROR_END_OF_MEDIA: return ENOSPC;
|
||||
case ERROR_HANDLE_DISK_FULL: return ENOSPC;
|
||||
case ERROR_NOT_CONNECTED: return ENOTCONN;
|
||||
case WSAENOTCONN: return ENOTCONN;
|
||||
case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY;
|
||||
case WSAENOTSOCK: return ENOTSOCK;
|
||||
case ERROR_NOT_SUPPORTED: return ENOTSUP;
|
||||
case ERROR_BROKEN_PIPE: return EOF;
|
||||
case ERROR_ACCESS_DENIED: return EPERM;
|
||||
case ERROR_PRIVILEGE_NOT_HELD: return EPERM;
|
||||
case ERROR_BAD_PIPE: return EPIPE;
|
||||
case ERROR_NO_DATA: return EPIPE;
|
||||
case ERROR_PIPE_NOT_CONNECTED: return EPIPE;
|
||||
case WSAESHUTDOWN: return EPIPE;
|
||||
case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT;
|
||||
case ERROR_WRITE_PROTECT: return EROFS;
|
||||
case ERROR_SEM_TIMEOUT: return ETIMEDOUT;
|
||||
case WSAETIMEDOUT: return ETIMEDOUT;
|
||||
case ERROR_NOT_SAME_DEVICE: return EXDEV;
|
||||
case ERROR_INVALID_FUNCTION: return EISDIR;
|
||||
case ERROR_META_EXPANSION_TOO_LONG: return E2BIG;
|
||||
default: return EIO;/*unknown*/
|
||||
}
|
||||
}
|
||||
};
|
||||
#else
|
||||
using qv_socket_t = int;
|
||||
#endif
|
||||
|
||||
inline int setnonblocking(qv_socket_t sockno, int &opt)
|
||||
void TCPing::start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ULONG block = 1;
|
||||
if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if ((opt = fcntl(sockno, F_GETFL, NULL)) < 0)
|
||||
{
|
||||
// get socket flags
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(sockno, F_SETFL, opt | O_NONBLOCK) < 0)
|
||||
{
|
||||
// set socket non-blocking
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int setblocking(qv_socket_t sockno, int &opt)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ULONG block = 0;
|
||||
if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (fcntl(sockno, F_SETFL, opt) < 0)
|
||||
{
|
||||
// reset socket flags
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int connect_wait(qv_socket_t sockno, struct sockaddr *addr, size_t addrlen, int timeout_sec = 5)
|
||||
{
|
||||
int res;
|
||||
int opt;
|
||||
timeval tv = { 0, 0 };
|
||||
#ifdef _WIN32
|
||||
TranslateWSAError _translateWSAError_;
|
||||
#endif
|
||||
tv.tv_sec = timeout_sec;
|
||||
tv.tv_usec = 0;
|
||||
if ((res = setnonblocking(sockno, opt)) != 0)
|
||||
return -1;
|
||||
if ((res = ::connect(sockno, addr, addrlen)) < 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
{
|
||||
#else
|
||||
if (errno == EINPROGRESS)
|
||||
{
|
||||
#endif
|
||||
// connecting
|
||||
fd_set wait_set;
|
||||
FD_ZERO(&wait_set);
|
||||
FD_SET(sockno, &wait_set);
|
||||
res = select(sockno + 1, NULL, &wait_set, NULL, &tv);
|
||||
}
|
||||
}
|
||||
else
|
||||
// connect immediately
|
||||
res = 1;
|
||||
|
||||
if (setblocking(sockno, opt) != 0)
|
||||
return -1;
|
||||
|
||||
if (res < 0)
|
||||
// an error occured
|
||||
return -1;
|
||||
else if (res == 0)
|
||||
{
|
||||
// timeout
|
||||
errno = ETIMEDOUT;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
socklen_t len = sizeof(opt);
|
||||
if (getsockopt(sockno, SOL_SOCKET, SO_ERROR, (char *) (&opt), &len) < 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int resolveHost(const QString &host, int port, addrinfo **res)
|
||||
{
|
||||
addrinfo hints;
|
||||
#ifdef Q_OS_WIN
|
||||
WSADATA wsadata;
|
||||
WSAStartup(0x0202, &wsadata);
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
#else
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
#endif
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
return getaddrinfo(host.toStdString().c_str(), std::to_string(port).c_str(), &hints, res);
|
||||
}
|
||||
|
||||
int testLatency(struct addrinfo *addr, system_clock::time_point *start, system_clock::time_point *end)
|
||||
{
|
||||
qv_socket_t fd;
|
||||
|
||||
const int on = 1;
|
||||
|
||||
/* try to connect for each of the entries: */
|
||||
while (addr != nullptr)
|
||||
{
|
||||
if (isExiting)
|
||||
return 0;
|
||||
|
||||
// create socket
|
||||
if (!(fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)))
|
||||
{
|
||||
goto next_addr0;
|
||||
}
|
||||
|
||||
// Windows needs special conversion.
|
||||
#ifdef _WIN32
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0)
|
||||
#else
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
|
||||
#endif
|
||||
goto next_addr1;
|
||||
*start = system_clock::now();
|
||||
if (connect_wait(fd, addr->ai_addr, addr->ai_addrlen) == 0)
|
||||
{
|
||||
*end = system_clock::now();
|
||||
#ifdef Q_OS_WIN
|
||||
closesocket(fd);
|
||||
#else
|
||||
close(fd);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
next_addr1:
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
close(fd);
|
||||
#endif
|
||||
next_addr0:
|
||||
addr = addr->ai_next;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount)
|
||||
{
|
||||
LatencyTestResult data;
|
||||
int successCount = 0;
|
||||
addrinfo *resolved;
|
||||
int errcode;
|
||||
|
||||
if ((errcode = resolveHost(host, port, &resolved)) != 0)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
data.errorMessage = QString::fromStdWString(gai_strerror(errcode));
|
||||
#else
|
||||
data.errorMessage = gai_strerror(errcode);
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
int currentCount = 0;
|
||||
|
||||
data.avg = 0;
|
||||
struct sockaddr_storage storage;
|
||||
data.totalCount = req.totalCount;
|
||||
data.failedCount = 0;
|
||||
data.worst = 0;
|
||||
data.best = 0;
|
||||
|
||||
while (currentCount < testCount)
|
||||
data.avg = 0;
|
||||
if (getSockAddress(loop, req.host.toStdString().data(), req.port, &storage, 0) != 0)
|
||||
{
|
||||
system_clock::time_point start;
|
||||
system_clock::time_point end;
|
||||
|
||||
if ((errcode = testLatency(resolved, &start, &end)) != 0)
|
||||
{
|
||||
LOG(MODULE_NETWORK, "error connecting to host: " + host + ":" + QSTRN(port) + " " + strerror(errno))
|
||||
}
|
||||
else
|
||||
{
|
||||
successCount++;
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < req.totalCount; ++i)
|
||||
{
|
||||
auto tcpClient = loop->resource<uvw::TCPHandle>();
|
||||
system_clock::time_point start = system_clock::now();
|
||||
tcpClient->connect(reinterpret_cast<const sockaddr &>(storage));
|
||||
tcpClient->once<uvw::ErrorEvent>([ptr = shared_from_this(), testHost, this, host = req.host, port = req.port,
|
||||
id = req.id](const uvw::ErrorEvent &e, uvw::TCPHandle &h) {
|
||||
LOG(MODULE_NETWORK, "error connecting to host: " + host + ":" + QSTRN(port) + " " + e.what())
|
||||
data.failedCount += 1;
|
||||
data.errorMessage = e.what();
|
||||
notifyTestHost(testHost, id);
|
||||
h.clear();
|
||||
});
|
||||
tcpClient->once<uvw::ConnectEvent>([ptr = shared_from_this(), start, testHost, id = req.id, this](auto &, auto &h) {
|
||||
++successCount;
|
||||
system_clock::time_point end = system_clock::now();
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
long ms = milliseconds.count();
|
||||
data.avg += ms;
|
||||
#ifdef Q_OS_WIN
|
||||
// Is it Windows?
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
data.worst = std::min(data.worst, ms);
|
||||
data.best = std::max(data.best, ms);
|
||||
|
||||
if (ms > 1000)
|
||||
{
|
||||
LOG(MODULE_NETWORK, "Stop the test on the first long connect()")
|
||||
break; /* Stop the test on the first long connect() */
|
||||
}
|
||||
}
|
||||
|
||||
currentCount++;
|
||||
QThread::msleep(200);
|
||||
data.worst = std::max(data.worst, ms);
|
||||
data.best = std::min(data.best, ms);
|
||||
notifyTestHost(testHost, id);
|
||||
h.clear();
|
||||
h.close();
|
||||
});
|
||||
}
|
||||
freeaddrinfo(resolved);
|
||||
if (successCount > 0)
|
||||
}
|
||||
void TCPing::notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id)
|
||||
{
|
||||
if (data.failedCount + successCount == data.totalCount)
|
||||
{
|
||||
data.avg = data.avg / successCount;
|
||||
if (data.failedCount == data.totalCount)
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
else
|
||||
data.errorMessage.clear(), data.avg = data.avg / successCount;
|
||||
testHost->OnLatencyTestCompleted(id, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
data.worst = LATENCY_TEST_VALUE_ERROR;
|
||||
data.best = LATENCY_TEST_VALUE_ERROR;
|
||||
data.errorMessage = QObject::tr("Timeout");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
} // namespace Qv2ray::components::latency::tcping
|
||||
|
@ -1,7 +1,18 @@
|
||||
#pragma once
|
||||
#include "LatencyTest.hpp"
|
||||
#include "base/Qv2rayBase.hpp"
|
||||
namespace uvw
|
||||
{
|
||||
class Loop;
|
||||
}
|
||||
namespace Qv2ray::components::latency::tcping
|
||||
{
|
||||
LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount);
|
||||
struct TCPing : public std::enable_shared_from_this<TCPing>
|
||||
{
|
||||
TCPing() = default;
|
||||
void start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost);
|
||||
void notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id);
|
||||
int successCount = 0;
|
||||
LatencyTestResult data;
|
||||
};
|
||||
} // namespace Qv2ray::components::latency::tcping
|
||||
|
@ -7,16 +7,14 @@
|
||||
|
||||
#include <QObject>
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
//
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip.h> //macos need that
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <resolv.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include "uvw.hpp"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#define SOL_IP 0
|
||||
#endif
|
||||
@ -59,8 +57,7 @@ namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
auto timeout_s = 5;
|
||||
// create socket
|
||||
if ( //((sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) &&
|
||||
((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0))
|
||||
if (((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0))
|
||||
{
|
||||
initErrorMessage = "EPING_SOCK: " + QObject::tr("Socket creation failed");
|
||||
return;
|
||||
@ -86,71 +83,127 @@ namespace Qv2ray::components::latency::icmping
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/// @return value < 0 on error, response time in ms on success
|
||||
QPair<int64_t, QString> ICMPPing::ping(const QString &address)
|
||||
void ICMPPing::start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
return { 0, initErrorMessage };
|
||||
data.errorMessage = initErrorMessage;
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
return;
|
||||
}
|
||||
timeval start, end;
|
||||
socklen_t slen;
|
||||
// not initialized
|
||||
if (socketId < 0)
|
||||
return { 0, "EPING_SOCK:" + QObject::tr("Socket creation failed") };
|
||||
|
||||
// resolve hostname
|
||||
hostent *resolvedAddress = gethostbyname(address.toStdString().c_str());
|
||||
if (!resolvedAddress)
|
||||
return { 0, "EPING_HOST: " + QObject::tr("Unresolvable hostname") };
|
||||
|
||||
// set IP address to ping
|
||||
sockaddr_in targetAddress;
|
||||
memset(&targetAddress, 0, sizeof(targetAddress));
|
||||
targetAddress.sin_family = resolvedAddress->h_addrtype;
|
||||
targetAddress.sin_port = 0;
|
||||
memcpy(&targetAddress.sin_addr, resolvedAddress->h_addr, resolvedAddress->h_length);
|
||||
|
||||
// prepare echo request packet
|
||||
icmp _icmp_request;
|
||||
memset(&_icmp_request, 0, sizeof(_icmp_request));
|
||||
_icmp_request.icmp_type = ICMP_ECHO;
|
||||
_icmp_request.icmp_hun.ih_idseq.icd_id = 0; // SOCK_DGRAM & 0 => id will be set by kernel
|
||||
unsigned short sent_seq;
|
||||
_icmp_request.icmp_hun.ih_idseq.icd_seq = sent_seq = seq++;
|
||||
_icmp_request.icmp_cksum = ping_checksum(reinterpret_cast<char *>(&_icmp_request), sizeof(_icmp_request));
|
||||
|
||||
// send echo request
|
||||
gettimeofday(&start, NULL);
|
||||
if (sendto(socketId, &_icmp_request, sizeof(icmp), 0, (struct sockaddr *) &targetAddress, sizeof(targetAddress)) <= 0)
|
||||
return { 0, "EPING_SEND: " + QObject::tr("Sending echo request failed") };
|
||||
|
||||
// receive response (if any)
|
||||
sockaddr_in remove_addr;
|
||||
slen = sizeof(remove_addr);
|
||||
int rlen;
|
||||
icmp resp;
|
||||
while ((rlen = recvfrom(socketId, &resp, sizeof(icmp), 0, (struct sockaddr *) &remove_addr, &slen)) > 0)
|
||||
struct sockaddr_storage storage;
|
||||
data.totalCount = req.totalCount;
|
||||
data.failedCount = 0;
|
||||
data.worst = 0;
|
||||
data.avg = 0;
|
||||
if (getSockAddress(loop, req.host.toStdString().data(), req.port, &storage, 0) != 0)
|
||||
{
|
||||
gettimeofday(&end, NULL);
|
||||
|
||||
// skip malformed
|
||||
if (rlen != sizeof(icmp))
|
||||
continue;
|
||||
|
||||
// skip the ones we didn't send
|
||||
if (resp.icmp_hun.ih_idseq.icd_seq != sent_seq)
|
||||
continue;
|
||||
|
||||
switch (resp.icmp_type)
|
||||
{
|
||||
case ICMP_ECHOREPLY: return { 1000000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec), {} };
|
||||
case ICMP_UNREACH: return { 0, "EPING_DST: " + QObject::tr("Destination unreachable") };
|
||||
case ICMP_TIMXCEED: return { 0, "EPING_TIME: " + QObject::tr("Timeout") };
|
||||
default: return { 0, "EPING_UNK: " + QObject::tr("Unknown error") };
|
||||
}
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
return;
|
||||
}
|
||||
return { 0, "EPING_TIME: " + QObject::tr("Timeout") };
|
||||
uvw::OSSocketHandle osSocketHandle{ socketId };
|
||||
auto pollHandle = loop->resource<uvw::PollHandle>(osSocketHandle);
|
||||
pollHandle->init();
|
||||
auto pollEvent = uvw::Flags<uvw::PollHandle::Event>::from<uvw::PollHandle::Event::READABLE>();
|
||||
pollHandle->on<uvw::PollEvent>([this, testHost, id = req.id, ptr = shared_from_this()](uvw::PollEvent &, uvw::PollHandle &h) {
|
||||
timeval end;
|
||||
sockaddr_in remove_addr;
|
||||
socklen_t slen = sizeof(remove_addr);
|
||||
int rlen = 0;
|
||||
icmp resp;
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
rlen = recvfrom(socketId, &resp, sizeof(icmp), 0, (struct sockaddr *) &remove_addr, &slen);
|
||||
} while (rlen == -1 && errno == EINTR);
|
||||
gettimeofday(&end, NULL);
|
||||
|
||||
// skip malformed
|
||||
if (rlen != sizeof(icmp))
|
||||
continue;
|
||||
|
||||
// skip the ones we didn't send
|
||||
auto cur_seq = resp.icmp_hun.ih_idseq.icd_seq;
|
||||
if (cur_seq >= seq)
|
||||
continue;
|
||||
|
||||
switch (resp.icmp_type)
|
||||
{
|
||||
case ICMP_ECHOREPLY:
|
||||
data.avg =
|
||||
1000000 * (end.tv_sec - startTimevals[cur_seq - 1].tv_sec) + (end.tv_usec - startTimevals[cur_seq - 1].tv_usec);
|
||||
successCount++;
|
||||
notifyTestHost(testHost, id);
|
||||
continue;
|
||||
case ICMP_UNREACH:
|
||||
data.errorMessage = "EPING_DST: " + QObject::tr("Destination unreachable");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost(testHost, id))
|
||||
{
|
||||
h.clear();
|
||||
h.close();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
case ICMP_TIMXCEED:
|
||||
data.errorMessage = "EPING_TIME: " + QObject::tr("Timeout");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost(testHost, id))
|
||||
{
|
||||
h.clear();
|
||||
h.close();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
data.errorMessage = "EPING_UNK: " + QObject::tr("Unknown error");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost(testHost, id))
|
||||
{
|
||||
h.clear();
|
||||
h.close();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} while (rlen > 0);
|
||||
});
|
||||
pollHandle->start(pollEvent);
|
||||
for (int i = 0; i < req.totalCount; ++i)
|
||||
{
|
||||
// prepare echo request packet
|
||||
icmp _icmp_request;
|
||||
memset(&_icmp_request, 0, sizeof(_icmp_request));
|
||||
_icmp_request.icmp_type = ICMP_ECHO;
|
||||
_icmp_request.icmp_hun.ih_idseq.icd_id = 0; // SOCK_DGRAM & 0 => id will be set by kernel
|
||||
_icmp_request.icmp_hun.ih_idseq.icd_seq = seq++;
|
||||
_icmp_request.icmp_cksum = ping_checksum(reinterpret_cast<char *>(&_icmp_request), sizeof(_icmp_request));
|
||||
int n;
|
||||
timeval start;
|
||||
gettimeofday(&start, nullptr);
|
||||
startTimevals.push_back(start);
|
||||
do
|
||||
{
|
||||
n = ::sendto(socketId, &_icmp_request, sizeof(icmp), 0, (struct sockaddr *) &storage, sizeof(struct sockaddr));
|
||||
} while (n < 0 && errno == EINTR);
|
||||
}
|
||||
}
|
||||
bool ICMPPing::notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id)
|
||||
{
|
||||
if (data.failedCount + successCount == data.totalCount)
|
||||
{
|
||||
if (data.failedCount == data.totalCount)
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
else
|
||||
data.errorMessage.clear(), data.avg = data.avg / successCount / 1000;
|
||||
testHost->OnLatencyTestCompleted(id, data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace Qv2ray::components::latency::icmping
|
||||
#endif
|
||||
|
@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
#include <QtGlobal>
|
||||
#ifdef Q_OS_UNIX
|
||||
#include "components/latency/LatencyTest.hpp"
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
namespace uvw
|
||||
{
|
||||
class Loop;
|
||||
}
|
||||
namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
class ICMPPing
|
||||
class ICMPPing : public std::enable_shared_from_this<ICMPPing>
|
||||
{
|
||||
public:
|
||||
explicit ICMPPing(int ttl);
|
||||
@ -13,7 +19,8 @@ namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
QPair<int64_t, QString> ping(const QString &address);
|
||||
void start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost);
|
||||
bool notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id);
|
||||
|
||||
private:
|
||||
void deinit();
|
||||
@ -22,6 +29,9 @@ namespace Qv2ray::components::latency::icmping
|
||||
// socket
|
||||
int socketId = -1;
|
||||
bool initialized = false;
|
||||
int successCount = 0;
|
||||
LatencyTestResult data;
|
||||
std::vector<timeval> startTimevals;
|
||||
QString initErrorMessage;
|
||||
};
|
||||
} // namespace Qv2ray::components::latency::icmping
|
||||
|
@ -1,16 +1,12 @@
|
||||
#include "ICMPPing.hpp"
|
||||
#ifdef Q_OS_WIN
|
||||
//
|
||||
#include <WS2tcpip.h>
|
||||
//
|
||||
#include <Windows.h>
|
||||
//
|
||||
#include <iphlpapi.h>
|
||||
//
|
||||
#include <IcmpAPI.h>
|
||||
//
|
||||
#include <QEventLoop>
|
||||
#include <QHostInfo>
|
||||
|
||||
namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
ICMPPing::ICMPPing(uint64_t timeout)
|
||||
@ -83,5 +79,18 @@ namespace Qv2ray::components::latency::icmping
|
||||
const ICMP_ECHO_REPLY *r = (const ICMP_ECHO_REPLY *) reply_buf;
|
||||
return QPair<long, QString>(r->RoundTripTime * 1000, QString{});
|
||||
}
|
||||
bool ICMPPing::notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id)
|
||||
{
|
||||
if (data.failedCount + successCount == data.totalCount)
|
||||
{
|
||||
if (data.failedCount == data.totalCount)
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
else
|
||||
data.errorMessage.clear(), data.avg = data.avg / successCount / 1000;
|
||||
testHost->OnLatencyTestCompleted(id, data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace Qv2ray::components::latency::icmping
|
||||
#endif
|
||||
|
@ -8,15 +8,20 @@
|
||||
#include <QtGlobal>
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
#include "components/latency/LatencyTest.hpp"
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace uvw
|
||||
{
|
||||
class Loop;
|
||||
}
|
||||
namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
class ICMPPing
|
||||
class ICMPPing : public std::enable_shared_from_this<ICMPPing>
|
||||
{
|
||||
public:
|
||||
ICMPPing(uint64_t timeout = DEFAULT_TIMEOUT);
|
||||
@ -24,12 +29,16 @@ namespace Qv2ray::components::latency::icmping
|
||||
|
||||
public:
|
||||
static const uint64_t DEFAULT_TIMEOUT = 10000U;
|
||||
void start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost);
|
||||
bool notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id);
|
||||
|
||||
public:
|
||||
QPair<long, QString> ping(const QString &ipAddr);
|
||||
|
||||
private:
|
||||
uint64_t timeout = DEFAULT_TIMEOUT;
|
||||
int successCount = 0;
|
||||
LatencyTestResult data;
|
||||
};
|
||||
} // namespace Qv2ray::components::latency::icmping
|
||||
#endif
|
||||
|
35
src/components/latency/win/ICMPPingWork.cpp
Normal file
35
src/components/latency/win/ICMPPingWork.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "uvw.hpp"
|
||||
#include "ICMPPing.hpp"
|
||||
#ifdef Q_OS_WIN
|
||||
namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
void ICMPPing::start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost)
|
||||
{
|
||||
data.totalCount = req.totalCount;
|
||||
data.failedCount = 0;
|
||||
data.worst = 0;
|
||||
data.avg = 0;
|
||||
for (int i = 0; i < req.totalCount; ++i)
|
||||
{
|
||||
auto work = loop->resource<uvw::WorkReq>([ptr = shared_from_this(), this, addr = req.host, id = req.id, testHost]() mutable {
|
||||
auto pingres = ping(addr);
|
||||
if (!pingres.second.isEmpty())
|
||||
{
|
||||
data.errorMessage = pingres.second;
|
||||
data.failedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.avg += pingres.first;
|
||||
data.best = std::min(pingres.first, data.best);
|
||||
data.worst = std::max(pingres.first, data.worst);
|
||||
successCount++;
|
||||
}
|
||||
notifyTestHost(testHost, id);
|
||||
ptr.reset();
|
||||
});
|
||||
work->queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -124,16 +124,18 @@ namespace Qv2ray::core::handler
|
||||
{
|
||||
for (const auto &connection : connections.keys())
|
||||
{
|
||||
StartLatencyTest(connection);
|
||||
emit OnLatencyTestStarted(connection);
|
||||
}
|
||||
tcpingHelper->TestLatency(connections.keys(), GlobalConfig.networkConfig.latencyTestingMethod);
|
||||
}
|
||||
|
||||
void QvConfigHandler::StartLatencyTest(const GroupId &id)
|
||||
{
|
||||
for (const auto &connection : groups[id].connections)
|
||||
{
|
||||
StartLatencyTest(connection);
|
||||
emit OnLatencyTestStarted(connection);
|
||||
}
|
||||
tcpingHelper->TestLatency(groups[id].connections, GlobalConfig.networkConfig.latencyTestingMethod);
|
||||
}
|
||||
|
||||
void QvConfigHandler::StartLatencyTest(const ConnectionId &id)
|
||||
@ -356,7 +358,6 @@ namespace Qv2ray::core::handler
|
||||
QvConfigHandler::~QvConfigHandler()
|
||||
{
|
||||
LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor")
|
||||
tcpingHelper->StopAllLatencyTest();
|
||||
delete kernelHandler;
|
||||
SaveConnectionConfig();
|
||||
}
|
||||
@ -367,7 +368,7 @@ namespace Qv2ray::core::handler
|
||||
return connectionRootCache.value(id);
|
||||
}
|
||||
|
||||
void QvConfigHandler::OnLatencyDataArrived_p(const ConnectionId &id, const LatencyTestResult &result)
|
||||
void QvConfigHandler::OnLatencyDataArrived_p(ConnectionId id, LatencyTestResult result)
|
||||
{
|
||||
CheckValidId(id, nothing);
|
||||
connections[id].latency = result.avg;
|
||||
|
@ -163,7 +163,7 @@ namespace Qv2ray::core::handler
|
||||
//
|
||||
private slots:
|
||||
void OnKernelCrashed_p(const ConnectionGroupPair &id, const QString &errMessage);
|
||||
void OnLatencyDataArrived_p(const ConnectionId &id, const LatencyTestResult &data);
|
||||
void OnLatencyDataArrived_p(ConnectionId id, LatencyTestResult data);
|
||||
void OnStatsDataArrived_p(const ConnectionGroupPair &id, const QMap<StatisticsType, QvStatsSpeed> &data);
|
||||
|
||||
protected:
|
||||
|
Loading…
Reference in New Issue
Block a user