Introduce async ICMP ping and TCP ping for UNIX and Windows (#749)

Thanks DuckVador
This commit is contained in:
DuckVador 2020-07-09 21:28:45 +08:00 committed by GitHub
parent c4f0db170c
commit 274ab69db5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 485 additions and 539 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
# Some files # Some files
compile_commands.json
.DS_Store .DS_Store
*.pro.user *.pro.user
*.user *.user

10
.gitmodules vendored
View File

@ -19,3 +19,13 @@
[submodule "3rdparty/uistyles"] [submodule "3rdparty/uistyles"]
path = 3rdparty/uistyles path = 3rdparty/uistyles
url = https://github.com/Qv2ray/QvUIStyles 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

@ -0,0 +1 @@
Subproject commit 1ab9ea3790378f9f25c4e78e9e2b511c75f9c9ed

1
3rdparty/uvw vendored Submodule

@ -0,0 +1 @@
Subproject commit 52785475b9cb727f6912f8cc00ba65ff3bd44e67

View File

@ -80,7 +80,7 @@ message(" ")
if(WIN32) if(WIN32)
add_compile_options("/std:c++17") 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) add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
set(GUI_TYPE WIN32) set(GUI_TYPE WIN32)
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
@ -139,6 +139,7 @@ endif()
# ================================================================================== # ==================================================================================
# 3rdparty Sources # 3rdparty Sources
# ================================================================================== # ==================================================================================
include(cmake/libuv.cmake)
include(cmake/translations.cmake) include(cmake/translations.cmake)
include(cmake/qnodeeditor.cmake) include(cmake/qnodeeditor.cmake)
if (ANDROID) if (ANDROID)
@ -234,6 +235,7 @@ target_link_libraries(qv2ray-baselib
${ZXING_LIBRARY} ${ZXING_LIBRARY}
Threads::Threads Threads::Threads
${QV2RAY_QT_LIBS} ${QV2RAY_QT_LIBS}
${LibUV_LIBRARIES}
) )
target_link_libraries(qv2ray target_link_libraries(qv2ray
@ -257,6 +259,7 @@ target_include_directories(qv2ray-baselib PUBLIC
${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}
${ZXING_INCLUDE_PATH} ${ZXING_INCLUDE_PATH}
${Protobuf_INCLUDE_DIRS} ${Protobuf_INCLUDE_DIRS}
${LibUV_INCLUDE_DIR}
) )
if (BUILD_TESTING) if (BUILD_TESTING)

View File

@ -37,6 +37,7 @@ set(QV2RAY_LIB_SOURCES
${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.cpp ${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.cpp
${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.hpp ${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.hpp
${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPing.cpp ${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/win/ICMPPing.hpp
${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.cpp ${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.cpp
${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.hpp ${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.hpp

35
cmake/libuv.cmake Normal file
View 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
)

View File

@ -1 +1 @@
5748 5748

View File

@ -1,50 +1,97 @@
#include "uvw.hpp"
#include "LatencyTest.hpp" #include "LatencyTest.hpp"
#include "LatencyTestThread.hpp" #include "LatencyTestThread.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
constexpr auto LATENCY_PROPERTY_KEY = "__QvLatencyTest__";
namespace Qv2ray::components::latency 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) LatencyTestHost::LatencyTestHost(const int defaultCount, QObject *parent) : QObject(parent)
{ {
qRegisterMetaType<ConnectionId>();
qRegisterMetaType<LatencyTestResult>();
totalTestCount = defaultCount; totalTestCount = defaultCount;
latencyThread = new LatencyTestThread(this);
latencyThread->start();
}
LatencyTestHost::~LatencyTestHost()
{
latencyThread->stopLatencyTest();
latencyThread->wait();
} }
void LatencyTestHost::StopAllLatencyTest() void LatencyTestHost::StopAllLatencyTest()
{ {
for (const auto &thread : latencyThreads) latencyThread->stopLatencyTest();
{ latencyThread->wait();
thread->terminate(); latencyThread->start();
}
latencyThreads.clear();
} }
void LatencyTestHost::TestLatency(const ConnectionId &id, Qv2rayLatencyTestingMethod method) void LatencyTestHost::TestLatency(const ConnectionId &id, Qv2rayLatencyTestingMethod method)
{ {
const auto &[protocol, host, port] = GetConnectionInfo(id); latencyThread->pushRequest(id, totalTestCount, method);
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();
} }
void LatencyTestHost::TestLatency(const QList<ConnectionId> &ids, Qv2rayLatencyTestingMethod method)
void LatencyTestHost::OnLatencyThreadProcessCompleted()
{ {
const auto senderThread = qobject_cast<LatencyTestThread *>(sender()); latencyThread->pushRequest(ids, totalTestCount, method);
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);
} }
} // namespace Qv2ray::components::latency } // namespace Qv2ray::components::latency

View File

@ -1,6 +1,10 @@
#pragma once #pragma once
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
namespace uvw
{
class Loop;
}
struct sockaddr_storage;
namespace Qv2ray::components::latency namespace Qv2ray::components::latency
{ {
class LatencyTestThread; class LatencyTestThread;
@ -14,6 +18,16 @@ namespace Qv2ray::components::latency
long avg = LATENCY_TEST_VALUE_ERROR; long avg = LATENCY_TEST_VALUE_ERROR;
Qv2rayLatencyTestingMethod method; 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 class LatencyTestHost : public QObject
{ {
@ -21,21 +35,22 @@ namespace Qv2ray::components::latency
public: public:
explicit LatencyTestHost(const int defaultCount = 3, QObject *parent = nullptr); explicit LatencyTestHost(const int defaultCount = 3, QObject *parent = nullptr);
void TestLatency(const ConnectionId &connectionId, Qv2rayLatencyTestingMethod); void TestLatency(const ConnectionId &connectionId, Qv2rayLatencyTestingMethod);
void TestLatency(const QList<ConnectionId> &connectionIds, Qv2rayLatencyTestingMethod);
void StopAllLatencyTest(); void StopAllLatencyTest();
~LatencyTestHost()
{
StopAllLatencyTest();
}
signals:
void OnLatencyTestCompleted(const ConnectionId &id, const LatencyTestResult &data);
private slots: ~LatencyTestHost() override;
void OnLatencyThreadProcessCompleted();
signals:
void OnLatencyTestCompleted(ConnectionId id, LatencyTestResult data);
private: private:
int totalTestCount; 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 } // namespace Qv2ray::components::latency
using namespace Qv2ray::components::latency; using namespace Qv2ray::components::latency;
Q_DECLARE_METATYPE(LatencyTestResult)

View File

@ -1,84 +1,83 @@
#include "LatencyTestThread.hpp" #include "LatencyTestThread.hpp"
#include "TCPing.hpp" #include "TCPing.hpp"
#include "core/CoreUtils.hpp"
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include "unix/ICMPPing.hpp" #include "unix/ICMPPing.hpp"
#else #else
#include "win/ICMPPing.hpp" #include "win/ICMPPing.hpp"
#endif #endif
#include "uvw.hpp"
namespace Qv2ray::components::latency namespace Qv2ray::components::latency
{ {
LatencyTestThread::LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod method, int count, QObject *parent) LatencyTestThread::LatencyTestThread(QObject *parent) : QThread(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() void LatencyTestThread::run()
{ {
resultData.avg = 0; loop = uvw::Loop::create();
resultData.best = 0; stopTimer = loop->resource<uvw::TimerHandle>();
resultData.worst = 0; stopTimer->on<uvw::TimerEvent>([this](auto &, auto &handle) {
switch (method) 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: const auto &[protocol, host, port] = GetConnectionInfo(id);
{ requests.emplace_back(LatencyTestRequest{ id, host, port, totalTestCount, method });
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;
}
} }
} }
} // namespace Qv2ray::components::latency } // namespace Qv2ray::components::latency

View File

@ -2,27 +2,36 @@
#include "LatencyTest.hpp" #include "LatencyTest.hpp"
#include <QThread> #include <QThread>
#include <mutex>
#include <unordered_set>
namespace uvw
{
class Loop;
class TimerHandle;
}
namespace Qv2ray::components::latency namespace Qv2ray::components::latency
{ {
class LatencyTestThread : public QThread class LatencyTestThread : public QThread
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod, int count, QObject *parent = nullptr); explicit LatencyTestThread(QObject *parent = nullptr);
LatencyTestResult GetResult() const 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: protected:
void run() override; void run() override;
private: private:
LatencyTestResult resultData; std::shared_ptr<uvw::Loop> loop;
QString host; bool isStop = false;
int port; std::shared_ptr<uvw::TimerHandle> stopTimer;
int count; std::vector<LatencyTestRequest> requests;
Qv2rayLatencyTestingMethod method; std::mutex m;
// static LatencyTestResult TestLatency_p(const ConnectionId &id, const int count); // static LatencyTestResult TestLatency_p(const ConnectionId &id, const int count);
}; };

View File

@ -1,364 +1,60 @@
#include "TCPing.hpp" #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 namespace Qv2ray::components::latency::tcping
{ {
#ifdef Q_OS_WIN void TCPing::start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost)
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)
{ {
#ifdef _WIN32 struct sockaddr_storage storage;
ULONG block = 1; data.totalCount = req.totalCount;
if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR) data.failedCount = 0;
{
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;
data.worst = 0; data.worst = 0;
data.best = 0; data.avg = 0;
if (getSockAddress(loop, req.host.toStdString().data(), req.port, &storage, 0) != 0)
while (currentCount < testCount)
{ {
system_clock::time_point start; data.errorMessage = QObject::tr("DNS not resolved");
system_clock::time_point end; data.avg = LATENCY_TEST_VALUE_ERROR;
testHost->OnLatencyTestCompleted(req.id, data);
if ((errcode = testLatency(resolved, &start, &end)) != 0) return;
{ }
LOG(MODULE_NETWORK, "error connecting to host: " + host + ":" + QSTRN(port) + " " + strerror(errno)) for (int i = 0; i < req.totalCount; ++i)
} {
else auto tcpClient = loop->resource<uvw::TCPHandle>();
{ system_clock::time_point start = system_clock::now();
successCount++; 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); auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
long ms = milliseconds.count(); long ms = milliseconds.count();
data.avg += ms; data.avg += ms;
#ifdef Q_OS_WIN data.worst = std::max(data.worst, ms);
// Is it Windows? data.best = std::min(data.best, ms);
#undef min notifyTestHost(testHost, id);
#undef max h.clear();
#endif h.close();
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);
} }
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 } // namespace Qv2ray::components::latency::tcping

View File

@ -1,7 +1,18 @@
#pragma once #pragma once
#include "LatencyTest.hpp" #include "LatencyTest.hpp"
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
namespace uvw
{
class Loop;
}
namespace Qv2ray::components::latency::tcping 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 } // namespace Qv2ray::components::latency::tcping

View File

@ -7,16 +7,14 @@
#include <QObject> #include <QObject>
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include <unistd.h>
//
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/ip.h> #include <netinet/ip.h> //macos need that
#include <netinet/ip_icmp.h> #include <netinet/ip_icmp.h>
#include <resolv.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h>
#include "uvw.hpp"
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
#define SOL_IP 0 #define SOL_IP 0
#endif #endif
@ -59,8 +57,7 @@ namespace Qv2ray::components::latency::icmping
{ {
auto timeout_s = 5; auto timeout_s = 5;
// create socket // create socket
if ( //((sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) && if (((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0))
((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0))
{ {
initErrorMessage = "EPING_SOCK: " + QObject::tr("Socket creation failed"); initErrorMessage = "EPING_SOCK: " + QObject::tr("Socket creation failed");
return; return;
@ -86,71 +83,127 @@ namespace Qv2ray::components::latency::icmping
initialized = true; initialized = true;
} }
/// @return value < 0 on error, response time in ms on success void ICMPPing::start(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost)
QPair<int64_t, QString> ICMPPing::ping(const QString &address)
{ {
if (!initialized) if (!initialized)
{ {
return { 0, initErrorMessage }; data.errorMessage = initErrorMessage;
data.avg = LATENCY_TEST_VALUE_ERROR;
testHost->OnLatencyTestCompleted(req.id, data);
return;
} }
timeval start, end; struct sockaddr_storage storage;
socklen_t slen; data.totalCount = req.totalCount;
// not initialized data.failedCount = 0;
if (socketId < 0) data.worst = 0;
return { 0, "EPING_SOCK:" + QObject::tr("Socket creation failed") }; data.avg = 0;
if (getSockAddress(loop, req.host.toStdString().data(), req.port, &storage, 0) != 0)
// 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)
{ {
gettimeofday(&end, NULL); data.errorMessage = QObject::tr("DNS not resolved");
data.avg = LATENCY_TEST_VALUE_ERROR;
// skip malformed testHost->OnLatencyTestCompleted(req.id, data);
if (rlen != sizeof(icmp)) return;
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") };
}
} }
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 } // namespace Qv2ray::components::latency::icmping
#endif #endif

View File

@ -1,11 +1,17 @@
#pragma once #pragma once
#include <QtGlobal> #include <QtGlobal>
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include "components/latency/LatencyTest.hpp"
#include <QPair> #include <QPair>
#include <QString> #include <QString>
namespace uvw
{
class Loop;
}
namespace Qv2ray::components::latency::icmping namespace Qv2ray::components::latency::icmping
{ {
class ICMPPing class ICMPPing : public std::enable_shared_from_this<ICMPPing>
{ {
public: public:
explicit ICMPPing(int ttl); explicit ICMPPing(int ttl);
@ -13,7 +19,8 @@ namespace Qv2ray::components::latency::icmping
{ {
deinit(); 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: private:
void deinit(); void deinit();
@ -22,6 +29,9 @@ namespace Qv2ray::components::latency::icmping
// socket // socket
int socketId = -1; int socketId = -1;
bool initialized = false; bool initialized = false;
int successCount = 0;
LatencyTestResult data;
std::vector<timeval> startTimevals;
QString initErrorMessage; QString initErrorMessage;
}; };
} // namespace Qv2ray::components::latency::icmping } // namespace Qv2ray::components::latency::icmping

View File

@ -1,16 +1,12 @@
#include "ICMPPing.hpp" #include "ICMPPing.hpp"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
//
#include <WS2tcpip.h> #include <WS2tcpip.h>
//
#include <Windows.h> #include <Windows.h>
//
#include <iphlpapi.h> #include <iphlpapi.h>
//
#include <IcmpAPI.h> #include <IcmpAPI.h>
//
#include <QEventLoop> #include <QEventLoop>
#include <QHostInfo> #include <QHostInfo>
namespace Qv2ray::components::latency::icmping namespace Qv2ray::components::latency::icmping
{ {
ICMPPing::ICMPPing(uint64_t timeout) 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; const ICMP_ECHO_REPLY *r = (const ICMP_ECHO_REPLY *) reply_buf;
return QPair<long, QString>(r->RoundTripTime * 1000, QString{}); 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 } // namespace Qv2ray::components::latency::icmping
#endif #endif

View File

@ -8,15 +8,20 @@
#include <QtGlobal> #include <QtGlobal>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "components/latency/LatencyTest.hpp"
#include <QPair> #include <QPair>
#include <QString> #include <QString>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <utility> #include <utility>
namespace uvw
{
class Loop;
}
namespace Qv2ray::components::latency::icmping namespace Qv2ray::components::latency::icmping
{ {
class ICMPPing class ICMPPing : public std::enable_shared_from_this<ICMPPing>
{ {
public: public:
ICMPPing(uint64_t timeout = DEFAULT_TIMEOUT); ICMPPing(uint64_t timeout = DEFAULT_TIMEOUT);
@ -24,12 +29,16 @@ namespace Qv2ray::components::latency::icmping
public: public:
static const uint64_t DEFAULT_TIMEOUT = 10000U; 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: public:
QPair<long, QString> ping(const QString &ipAddr); QPair<long, QString> ping(const QString &ipAddr);
private: private:
uint64_t timeout = DEFAULT_TIMEOUT; uint64_t timeout = DEFAULT_TIMEOUT;
int successCount = 0;
LatencyTestResult data;
}; };
} // namespace Qv2ray::components::latency::icmping } // namespace Qv2ray::components::latency::icmping
#endif #endif

View 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

View File

@ -124,16 +124,18 @@ namespace Qv2ray::core::handler
{ {
for (const auto &connection : connections.keys()) for (const auto &connection : connections.keys())
{ {
StartLatencyTest(connection); emit OnLatencyTestStarted(connection);
} }
tcpingHelper->TestLatency(connections.keys(), GlobalConfig.networkConfig.latencyTestingMethod);
} }
void QvConfigHandler::StartLatencyTest(const GroupId &id) void QvConfigHandler::StartLatencyTest(const GroupId &id)
{ {
for (const auto &connection : groups[id].connections) 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) void QvConfigHandler::StartLatencyTest(const ConnectionId &id)
@ -356,7 +358,6 @@ namespace Qv2ray::core::handler
QvConfigHandler::~QvConfigHandler() QvConfigHandler::~QvConfigHandler()
{ {
LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor") LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor")
tcpingHelper->StopAllLatencyTest();
delete kernelHandler; delete kernelHandler;
SaveConnectionConfig(); SaveConnectionConfig();
} }
@ -367,7 +368,7 @@ namespace Qv2ray::core::handler
return connectionRootCache.value(id); 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); CheckValidId(id, nothing);
connections[id].latency = result.avg; connections[id].latency = result.avg;

View File

@ -163,7 +163,7 @@ namespace Qv2ray::core::handler
// //
private slots: private slots:
void OnKernelCrashed_p(const ConnectionGroupPair &id, const QString &errMessage); 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); void OnStatsDataArrived_p(const ConnectionGroupPair &id, const QMap<StatisticsType, QvStatsSpeed> &data);
protected: protected: