mirror of
https://github.com/Qv2ray/Qv2ray.git
synced 2025-05-21 19:30:26 +08:00
Merge pull request #775 from Qv2ray/dev-ping-refactor
Dev ping refactor
This commit is contained in:
commit
8464be37ab
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
# Some files
|
||||
compile_commands.json
|
||||
.DS_Store
|
||||
*.pro.user
|
||||
*.user
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -16,3 +16,9 @@
|
||||
[submodule "libs/QJsonStruct"]
|
||||
path = libs/QJsonStruct
|
||||
url = https://github.com/Qv2ray/QJsonStruct
|
||||
[submodule "3rdparty/libuv"]
|
||||
path = 3rdparty/libuv
|
||||
url = https://github.com/libuv/libuv.git
|
||||
[submodule "3rdparty/uvw"]
|
||||
path = 3rdparty/uvw
|
||||
url = https://github.com/DuckVador/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 4c414f19a234003727766cf41d1f10ea7ed8679e
|
@ -19,6 +19,11 @@ list(GET VERSION_LIST 0 CMAKE_PROJECT_VERSION_MAJOR)
|
||||
list(GET VERSION_LIST 1 CMAKE_PROJECT_VERSION_MINOR)
|
||||
list(GET VERSION_LIST 2 CPACK_PACKAGE_VERSION_PATCH)
|
||||
|
||||
# Tweaks and other defaults
|
||||
# Setting CMAKE to use loose block and search for find modules in source directory
|
||||
set ( CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true )
|
||||
set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH} )
|
||||
|
||||
# For Windows RC file.
|
||||
add_definitions(-DQV2RAY_VERSION_MAJOR=${CMAKE_PROJECT_VERSION_MAJOR})
|
||||
add_definitions(-DQV2RAY_VERSION_MINOR=${CMAKE_PROJECT_VERSION_MINOR})
|
||||
@ -80,7 +85,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)
|
||||
@ -140,6 +145,7 @@ endif()
|
||||
# ==================================================================================
|
||||
# 3rdparty Sources
|
||||
# ==================================================================================
|
||||
include(cmake/libuv.cmake)
|
||||
include(cmake/translations.cmake)
|
||||
include(cmake/qnodeeditor.cmake)
|
||||
if (ANDROID)
|
||||
@ -238,6 +244,7 @@ target_link_libraries(qv2ray-baselib
|
||||
${ZXING_LIBRARY}
|
||||
Threads::Threads
|
||||
${QV2RAY_QT_LIBS}
|
||||
${LibUV_LIBRARIES}
|
||||
)
|
||||
|
||||
target_link_libraries(qv2ray
|
||||
@ -261,6 +268,7 @@ target_include_directories(qv2ray-baselib PUBLIC
|
||||
${CMAKE_BINARY_DIR}
|
||||
${ZXING_INCLUDE_PATH}
|
||||
${Protobuf_INCLUDE_DIRS}
|
||||
${LibUV_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
|
139
cmake/FindLibUV.cmake
Normal file
139
cmake/FindLibUV.cmake
Normal file
@ -0,0 +1,139 @@
|
||||
#[=======================================================================[.rst:
|
||||
FindLibUV
|
||||
---------
|
||||
|
||||
Find libuv includes and library.
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
An :ref:`imported target <Imported targets>` named
|
||||
``LibUV::LibUV`` is provided if libuv has been found.
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines the following variables:
|
||||
|
||||
``LibUV_FOUND``
|
||||
True if libuv was found, false otherwise.
|
||||
``LibUV_INCLUDE_DIRS``
|
||||
Include directories needed to include libuv headers.
|
||||
``LibUV_LIBRARIES``
|
||||
Libraries needed to link to libuv.
|
||||
``LibUV_VERSION``
|
||||
The version of libuv found.
|
||||
``LibUV_VERSION_MAJOR``
|
||||
The major version of libuv.
|
||||
``LibUV_VERSION_MINOR``
|
||||
The minor version of libuv.
|
||||
``LibUV_VERSION_PATCH``
|
||||
The patch version of libuv.
|
||||
|
||||
Cache Variables
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
This module uses the following cache variables:
|
||||
|
||||
``LibUV_LIBRARY``
|
||||
The location of the libuv library file.
|
||||
``LibUV_INCLUDE_DIR``
|
||||
The location of the libuv include directory containing ``uv.h``.
|
||||
|
||||
The cache variables should not be used by project code.
|
||||
They may be set by end users to point at libuv components.
|
||||
#]=======================================================================]
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2014-2016 Kitware, Inc.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
#=============================================================================
|
||||
# (To distribute this file outside of CMake, substitute the full
|
||||
# License text for the above reference.)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
if(STATIC_LINK_LIBUV)
|
||||
find_library(LibUV_LIBRARY
|
||||
NAMES libuv.a
|
||||
)
|
||||
else()
|
||||
find_library(LibUV_LIBRARY
|
||||
NAMES uv
|
||||
)
|
||||
endif()
|
||||
mark_as_advanced(LibUV_LIBRARY)
|
||||
|
||||
find_path(LibUV_INCLUDE_DIR
|
||||
NAMES uv.h
|
||||
)
|
||||
mark_as_advanced(LibUV_INCLUDE_DIR)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Extract version number if possible.
|
||||
set(_LibUV_H_REGEX "#[ \t]*define[ \t]+UV_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+")
|
||||
if(LibUV_INCLUDE_DIR AND EXISTS "${LibUV_INCLUDE_DIR}/uv-version.h")
|
||||
file(STRINGS "${LibUV_INCLUDE_DIR}/uv-version.h" _LibUV_H REGEX "${_LibUV_H_REGEX}")
|
||||
elseif(LibUV_INCLUDE_DIR AND EXISTS "${LibUV_INCLUDE_DIR}/uv.h")
|
||||
file(STRINGS "${LibUV_INCLUDE_DIR}/uv.h" _LibUV_H REGEX "${_LibUV_H_REGEX}")
|
||||
else()
|
||||
set(_LibUV_H "")
|
||||
endif()
|
||||
foreach(c MAJOR MINOR PATCH)
|
||||
if(_LibUV_H MATCHES "#[ \t]*define[ \t]+UV_VERSION_${c}[ \t]+([0-9]+)")
|
||||
set(_LibUV_VERSION_${c} "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
unset(_LibUV_VERSION_${c})
|
||||
endif()
|
||||
endforeach()
|
||||
if(DEFINED _LibUV_VERSION_MAJOR AND DEFINED _LibUV_VERSION_MINOR)
|
||||
set(LibUV_VERSION_MAJOR "${_LibUV_VERSION_MAJOR}")
|
||||
set(LibUV_VERSION_MINOR "${_LibUV_VERSION_MINOR}")
|
||||
set(LibUV_VERSION "${LibUV_VERSION_MAJOR}.${LibUV_VERSION_MINOR}")
|
||||
if(DEFINED _LibUV_VERSION_PATCH)
|
||||
set(LibUV_VERSION_PATCH "${_LibUV_VERSION_PATCH}")
|
||||
set(LibUV_VERSION "${LibUV_VERSION}.${LibUV_VERSION_PATCH}")
|
||||
else()
|
||||
unset(LibUV_VERSION_PATCH)
|
||||
endif()
|
||||
else()
|
||||
set(LibUV_VERSION_MAJOR "")
|
||||
set(LibUV_VERSION_MINOR "")
|
||||
set(LibUV_VERSION_PATCH "")
|
||||
set(LibUV_VERSION "")
|
||||
endif()
|
||||
unset(_LibUV_VERSION_MAJOR)
|
||||
unset(_LibUV_VERSION_MINOR)
|
||||
unset(_LibUV_VERSION_PATCH)
|
||||
unset(_LibUV_H_REGEX)
|
||||
unset(_LibUV_H)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUV
|
||||
FOUND_VAR LibUV_FOUND
|
||||
REQUIRED_VARS LibUV_LIBRARY LibUV_INCLUDE_DIR
|
||||
VERSION_VAR LibUV_VERSION
|
||||
)
|
||||
set(LIBUV_FOUND ${LibUV_FOUND})
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Provide documented result variables and targets.
|
||||
if(LibUV_FOUND)
|
||||
set(LibUV_INCLUDE_DIRS ${LibUV_INCLUDE_DIR})
|
||||
set(LibUV_LIBRARIES ${LibUV_LIBRARY})
|
||||
message("libuv library:" ${LibUV_LIBRARY})
|
||||
if(NOT TARGET LibUV::LibUV)
|
||||
add_library(LibUV::LibUV UNKNOWN IMPORTED)
|
||||
set_target_properties(LibUV::LibUV PROPERTIES
|
||||
IMPORTED_LOCATION "${LibUV_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LibUV_INCLUDE_DIRS}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -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
|
||||
|
53
cmake/libuv.cmake
Normal file
53
cmake/libuv.cmake
Normal file
@ -0,0 +1,53 @@
|
||||
option(USE_SYSTEM_LIBUV "use system libuv" OFF)
|
||||
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
|
||||
)
|
||||
set(UVW_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/uvw/src
|
||||
)
|
||||
if(NOT USE_SYSTEM_LIBUV)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libuv EXCLUDE_FROM_ALL)
|
||||
set_target_properties(uv PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
set_target_properties(uv_a PROPERTIES POSITION_INDEPENDENT_CODE 1)
|
||||
add_library(uv::uv-static ALIAS uv_a)
|
||||
set(LibUV_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libuv/include
|
||||
)
|
||||
add_library(uv::uv-static ALIAS uv_a)
|
||||
set_target_properties(uv_a PROPERTIES POSITION_INDEPENDENT_CODE 1)
|
||||
set(LibUV_LIBRARIES uv::uv-static)
|
||||
else()
|
||||
if(NOT WIN32)
|
||||
find_package(LibUV REQUIRED)
|
||||
else()
|
||||
find_package(unofficial-libuv CONFIG REQUIRED)
|
||||
set(${LibUV_LIBRARIES} unofficial::libuv::libuv)
|
||||
endif()
|
||||
endif()
|
||||
add_library(UVW_LIB ${UVW_SOURCES})
|
||||
target_compile_definitions(UVW_LIB PUBLIC UVW_AS_LIB)
|
||||
target_include_directories(UVW_LIB PUBLIC ${LibUV_INCLUDE_DIR})
|
||||
target_link_libraries(UVW_LIB ${LibUV_LIBRARIES})
|
||||
set(LibUV_INCLUDE_DIR ${UVW_INCLUDE_DIR} ${LibUV_INCLUDE_DIR})
|
||||
set(LibUV_LIBRARIES UVW_LIB ${LibUV_LIBRARIES})
|
@ -1 +1 @@
|
||||
5790
|
||||
5791
|
@ -1,50 +1,52 @@
|
||||
#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 isAddr(const char *host, int port, struct sockaddr_storage *storage, int ipv6first)
|
||||
{
|
||||
if (uv_ip4_addr(host, port, reinterpret_cast<sockaddr_in *>(storage)) == 0)
|
||||
{
|
||||
return AF_INET;
|
||||
}
|
||||
if (uv_ip6_addr(host, port, reinterpret_cast<sockaddr_in6 *>(storage)) == 0)
|
||||
{
|
||||
return AF_INET6;
|
||||
}
|
||||
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 isAddr(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,101 @@
|
||||
#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)
|
||||
{
|
||||
if(isStop)
|
||||
return;
|
||||
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)
|
||||
{
|
||||
case ICMPING:
|
||||
loop = uvw::Loop::create();
|
||||
stopTimer = loop->resource<uvw::TimerHandle>();
|
||||
stopTimer->on<uvw::TimerEvent>([this](auto &, auto &handle) {
|
||||
if (isStop)
|
||||
{
|
||||
icmping::ICMPPing pingHelper(30);
|
||||
for (auto i = 0; i < count; i++)
|
||||
if(!requests.empty())
|
||||
requests.clear();
|
||||
int timer_count=0;
|
||||
//LOG(MODULE_NETWORK,"fuck")
|
||||
loop->walk([&timer_count,this](uvw::BaseHandle&h)
|
||||
{
|
||||
if(!h.closing())
|
||||
timer_count++;
|
||||
});
|
||||
if(timer_count==1)//only current timer
|
||||
{
|
||||
resultData.totalCount++;
|
||||
const auto value = pingHelper.ping(host);
|
||||
const auto _latency = value.first;
|
||||
const auto errMessage = value.second;
|
||||
if (!errMessage.isEmpty())
|
||||
handle.stop();
|
||||
handle.close();
|
||||
loop->clear();
|
||||
loop->close();
|
||||
loop->stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requests.empty())
|
||||
return;
|
||||
std::unique_lock<std::mutex> lockGuard{ m };
|
||||
auto parent = qobject_cast<LatencyTestHost *>(this->parent());
|
||||
for (auto &req : requests)
|
||||
{
|
||||
switch (req.method)
|
||||
{
|
||||
resultData.errorMessage.append(NEWLINE + errMessage);
|
||||
resultData.failedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Is it Windows?
|
||||
#undef min
|
||||
#undef max
|
||||
case ICMPING:
|
||||
{
|
||||
#ifdef Q_OS_UNIX
|
||||
auto ptr = std::make_shared<icmping::ICMPPing>(30,loop,req,parent);
|
||||
ptr->start();
|
||||
#else
|
||||
auto ptr = std::make_shared<icmping::ICMPPing>(30);
|
||||
ptr->start(loop,req,parent);
|
||||
#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_
|
||||
}
|
||||
break;
|
||||
case TCPING:
|
||||
default:
|
||||
{
|
||||
auto ptr = std::make_shared<tcping::TCPing>(loop, req, parent);
|
||||
ptr->start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
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)
|
||||
{
|
||||
if(isStop)
|
||||
return;
|
||||
std::unique_lock<std::mutex> lockGuard{ m };
|
||||
for (const auto &id : ids)
|
||||
{
|
||||
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 uvw
|
||||
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,135 @@
|
||||
#include "TCPing.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
|
||||
#include "uvw.hpp"
|
||||
|
||||
namespace Qv2ray::components::latency::tcping
|
||||
{
|
||||
constexpr int conn_timeout_sec=5;
|
||||
|
||||
#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;
|
||||
int getSocket(int af, int socktype, int proto) {
|
||||
uv_os_sock_t fd;
|
||||
#ifndef INVALID_SOCKET
|
||||
# define INVALID_SOCKET -1
|
||||
#endif
|
||||
|
||||
inline int setnonblocking(qv_socket_t sockno, int &opt)
|
||||
{
|
||||
#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;
|
||||
if ((fd = socket(af, socktype, proto)) == INVALID_SOCKET) {
|
||||
return 0;
|
||||
}
|
||||
// Set TCP connection timeout per-socket level.
|
||||
// See [https://github.com/libuv/help/issues/54] for details.
|
||||
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_MAXRT, (char*)&conn_timeout_sec, sizeof(conn_timeout_sec));
|
||||
#elif defined(__APPLE__)
|
||||
// (billhoo) MacOS uses TCP_CONNECTIONTIMEOUT to do so.
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, (char*)&conn_timeout_sec, sizeof(conn_timeout_sec));
|
||||
#else // Linux like systems
|
||||
uint32_t conn_timeout_ms = conn_timeout_sec * 1000;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, (char*)&conn_timeout_ms, sizeof(conn_timeout_ms));
|
||||
#endif
|
||||
return 0;
|
||||
return (int)fd;
|
||||
}
|
||||
|
||||
inline int setblocking(qv_socket_t sockno, int &opt)
|
||||
TCPing::TCPing(std::shared_ptr<uvw::Loop> loopin, LatencyTestRequest &reqin, LatencyTestHost *testHost)
|
||||
: loop(std::move(loopin)), req(std::move(reqin)), testHost(testHost)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ULONG block = 0;
|
||||
if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR)
|
||||
data.totalCount = 1;
|
||||
data.failedCount = 0;
|
||||
data.worst = 0;
|
||||
data.avg = 0;
|
||||
af=isAddr(req.host.toStdString().data(), req.port, &storage, 0);
|
||||
if (af == -1)
|
||||
{
|
||||
return -1;
|
||||
getAddrHandle = loop->resource<uvw::GetAddrInfoReq>();
|
||||
sprintf(digitBuffer, "%d", req.port);
|
||||
}
|
||||
#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)
|
||||
TCPing::~TCPing()
|
||||
{
|
||||
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)
|
||||
if (getAddrHandle)
|
||||
getAddrHandle->clear();
|
||||
}
|
||||
int TCPing::getAddrInfoRes(uvw::AddrInfoEvent &e)
|
||||
{
|
||||
struct addrinfo *rp = nullptr;
|
||||
for (rp = e.data.get(); rp != nullptr; rp = rp->ai_next)
|
||||
if (rp->ai_family == AF_INET)
|
||||
{
|
||||
if (rp->ai_family == AF_INET)
|
||||
{
|
||||
af=AF_INET;
|
||||
memcpy(&storage, rp->ai_addr, sizeof(struct sockaddr_in));
|
||||
}
|
||||
else if (rp->ai_family == AF_INET6)
|
||||
{
|
||||
af=AF_INET6;
|
||||
memcpy(&storage, rp->ai_addr, sizeof(struct sockaddr_in6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (rp == nullptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
// fallback: if we can't find prefered AF, then we choose alternative.
|
||||
for (rp = e.data.get(); rp != nullptr; rp = rp->ai_next)
|
||||
{
|
||||
#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);
|
||||
if (rp->ai_family == AF_INET)
|
||||
{
|
||||
af=AF_INET;
|
||||
memcpy(&storage, rp->ai_addr, sizeof(struct sockaddr_in));
|
||||
}
|
||||
else if (rp->ai_family == AF_INET6)
|
||||
{
|
||||
af=AF_INET6;
|
||||
memcpy(&storage, rp->ai_addr, sizeof(struct sockaddr_in6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (rp)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount)
|
||||
void TCPing::start()
|
||||
{
|
||||
LatencyTestResult data;
|
||||
int successCount = 0;
|
||||
addrinfo *resolved;
|
||||
int errcode;
|
||||
|
||||
if ((errcode = resolveHost(host, port, &resolved)) != 0)
|
||||
async_DNS_lookup(0, 0);
|
||||
}
|
||||
void TCPing::notifyTestHost()
|
||||
{
|
||||
if (data.failedCount + successCount == req.totalCount)
|
||||
{
|
||||
#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.best = 0;
|
||||
|
||||
while (currentCount < testCount)
|
||||
{
|
||||
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))
|
||||
}
|
||||
if (data.failedCount == req.totalCount)
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
else
|
||||
{
|
||||
successCount++;
|
||||
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.errorMessage.clear(), data.avg = data.avg / successCount;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
}
|
||||
freeaddrinfo(resolved);
|
||||
if (successCount > 0)
|
||||
}
|
||||
void TCPing::tcp_ping()
|
||||
{
|
||||
for (; data.totalCount <= req.totalCount; ++data.totalCount)
|
||||
{
|
||||
data.avg = data.avg / successCount;
|
||||
auto tcpClient = loop->resource<uvw::TCPHandle>();
|
||||
tcpClient->open(getSocket(af,SOCK_STREAM,IPPROTO_TCP));
|
||||
tcpClient->once<uvw::ErrorEvent>([ptr = shared_from_this(), this](const uvw::ErrorEvent &e, uvw::TCPHandle &h) {
|
||||
LOG(MODULE_NETWORK, "error connecting to host: " + req.host + ":" + QSTRN(req.port) + " " + e.what())
|
||||
data.failedCount += 1;
|
||||
data.errorMessage = e.what();
|
||||
notifyTestHost();
|
||||
h.clear();
|
||||
h.close();
|
||||
});
|
||||
tcpClient->once<uvw::ConnectEvent>([ptr = shared_from_this(), start = system_clock::now(), 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;
|
||||
data.worst = std::max(data.worst, ms);
|
||||
data.best = std::min(data.best, ms);
|
||||
notifyTestHost();
|
||||
h.clear();
|
||||
h.close();
|
||||
});
|
||||
tcpClient->connect(reinterpret_cast<const sockaddr &>(storage));
|
||||
}
|
||||
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,70 @@
|
||||
#pragma once
|
||||
#include "LatencyTest.hpp"
|
||||
#include "base/Qv2rayBase.hpp"
|
||||
#include "coroutine.hpp"
|
||||
#include "uvw.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
namespace Qv2ray::components::latency::tcping
|
||||
{
|
||||
LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount);
|
||||
class TCPing
|
||||
: public std::enable_shared_from_this<TCPing>
|
||||
, public coroutine
|
||||
{
|
||||
public:
|
||||
TCPing(std::shared_ptr<uvw::Loop> loop, LatencyTestRequest &req, LatencyTestHost *testHost);
|
||||
void start();
|
||||
~TCPing();
|
||||
|
||||
private:
|
||||
int getAddrInfoRes(uvw::AddrInfoEvent &e);
|
||||
void tcp_ping();
|
||||
template<typename E, typename H>
|
||||
void async_DNS_lookup(E &&e, H &&h)
|
||||
{
|
||||
co_enter(*this)
|
||||
{
|
||||
if (getAddrHandle)
|
||||
{
|
||||
getAddrHandle->once<uvw::ErrorEvent>(coro(async_DNS_lookup));
|
||||
getAddrHandle->once<uvw::AddrInfoEvent>(coro(async_DNS_lookup));
|
||||
co_yield return getAddrHandle->addrInfo(req.host.toStdString(), digitBuffer);
|
||||
co_yield if constexpr (std::is_same_v<uvw::AddrInfoEvent, std::remove_reference_t<E>>)
|
||||
{
|
||||
if (getAddrInfoRes(e) != 0)
|
||||
{
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
h.clear();
|
||||
return;
|
||||
}
|
||||
h.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_same_v<uvw::ErrorEvent, std::remove_reference_t<E>>)
|
||||
{
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
h.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tcp_ping();
|
||||
}
|
||||
void notifyTestHost();
|
||||
int successCount = 0;
|
||||
LatencyTestRequest req;
|
||||
LatencyTestResult data;
|
||||
LatencyTestHost *testHost;
|
||||
struct sockaddr_storage storage;
|
||||
char digitBuffer[20] = { 0 };
|
||||
int af=AF_INET;
|
||||
std::shared_ptr<uvw::Loop> loop;
|
||||
std::shared_ptr<uvw::GetAddrInfoReq> getAddrHandle;
|
||||
};
|
||||
} // namespace Qv2ray::components::latency::tcping
|
||||
|
93
src/components/latency/coroutine.hpp
Normal file
93
src/components/latency/coroutine.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct coroutine_ref;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
struct coroutine
|
||||
{
|
||||
coroutine() : line_(0)
|
||||
{
|
||||
}
|
||||
bool await_ready() const
|
||||
{
|
||||
return line_ == -1;
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct detail::coroutine_ref;
|
||||
int line_;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct coroutine_ref
|
||||
{
|
||||
coroutine_ref(coroutine &c) : line_(c.line_), modified_(false)
|
||||
{
|
||||
}
|
||||
~coroutine_ref()
|
||||
{
|
||||
if (!modified_)
|
||||
line_ = -1;
|
||||
}
|
||||
operator int() const
|
||||
{
|
||||
return line_;
|
||||
}
|
||||
int &operator=(int v)
|
||||
{
|
||||
modified_ = true;
|
||||
return line_ = v;
|
||||
}
|
||||
|
||||
private:
|
||||
void operator=(coroutine_ref const &);
|
||||
|
||||
int &line_;
|
||||
bool modified_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#define co_enter(c) \
|
||||
switch (::detail::coroutine_ref _coro_value = c) \
|
||||
case -1: \
|
||||
if (_coro_value) \
|
||||
{ \
|
||||
goto terminate_coroutine; \
|
||||
terminate_coroutine: \
|
||||
_coro_value = -1; \
|
||||
goto bail_out_of_coroutine; \
|
||||
bail_out_of_coroutine: \
|
||||
break; \
|
||||
} \
|
||||
else \
|
||||
case 0:
|
||||
|
||||
#define __co_yield_impl(n) \
|
||||
for (_coro_value = (n);;) \
|
||||
if (_coro_value == 0) \
|
||||
{ \
|
||||
case (n):; break; \
|
||||
} \
|
||||
else \
|
||||
switch (_coro_value ? 0 : 1) \
|
||||
for (;;) case -1: \
|
||||
if (_coro_value) \
|
||||
goto terminate_coroutine; \
|
||||
else \
|
||||
for (;;) case 1: \
|
||||
if (_coro_value) \
|
||||
goto bail_out_of_coroutine; \
|
||||
else \
|
||||
case 0:
|
||||
|
||||
#define co_yield __co_yield_impl(__LINE__)
|
||||
#define coro(f) \
|
||||
[this, ptr = (shared_from_this())](auto &&e, auto &&h) { \
|
||||
f(std::forward<decltype(e)>(e), std::forward<decltype(h)>(h)); \
|
||||
}
|
@ -7,16 +7,13 @@
|
||||
|
||||
#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>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#define SOL_IP 0
|
||||
#endif
|
||||
@ -55,12 +52,12 @@ namespace Qv2ray::components::latency::icmping
|
||||
}
|
||||
}
|
||||
|
||||
ICMPPing::ICMPPing(int ttl)
|
||||
ICMPPing::ICMPPing(int ttl,std::shared_ptr<uvw::Loop> loop_in,LatencyTestRequest& req_in,LatencyTestHost* testHost):
|
||||
loop(std::move(loop_in)),
|
||||
req(std::move(req_in)),
|
||||
testHost(testHost)
|
||||
{
|
||||
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;
|
||||
@ -73,91 +70,207 @@ namespace Qv2ray::components::latency::icmping
|
||||
return;
|
||||
}
|
||||
|
||||
// set timeout in secs (do not use secs - BUGGY)
|
||||
timeval timeout;
|
||||
timeout.tv_sec = timeout_s;
|
||||
timeout.tv_usec = 0;
|
||||
if (setsockopt(socketId, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) != 0)
|
||||
{
|
||||
deinit();
|
||||
initErrorMessage = "EPING_SETTO: " + QObject::tr("Setting timeout failed");
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
data.totalCount = req.totalCount;
|
||||
data.failedCount = 0;
|
||||
data.worst = 0;
|
||||
data.avg = 0;
|
||||
if (isAddr(req.host.toStdString().data(), req.port, &storage, 0) == -1)
|
||||
{
|
||||
getAddrHandle = loop->resource<uvw::GetAddrInfoReq>();
|
||||
sprintf(digitBuffer, "%d", req.port);
|
||||
}
|
||||
}
|
||||
|
||||
/// @return value < 0 on error, response time in ms on success
|
||||
QPair<int64_t, QString> ICMPPing::ping(const QString &address)
|
||||
int ICMPPing::getAddrInfoRes(uvw::AddrInfoEvent &e)
|
||||
{
|
||||
struct addrinfo *rp = nullptr;
|
||||
for (rp = e.data.get(); rp != nullptr; rp = rp->ai_next)
|
||||
if (rp->ai_family == AF_INET)
|
||||
{
|
||||
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 = e.data.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)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
void ICMPPing::start()
|
||||
{
|
||||
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;
|
||||
|
||||
auto src = resolvedAddress->h_addr;
|
||||
if (src == nullptr)
|
||||
{
|
||||
// Buggy GCC detected
|
||||
return { 0, "GCC: COMPILER BUG. Cannot even dereference a char**" };
|
||||
data.errorMessage="EPING_SOCK:" + QObject::tr("Socket creation failed");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
return;
|
||||
}
|
||||
async_DNS_lookup(0,0);
|
||||
}
|
||||
|
||||
memcpy(&targetAddress.sin_addr, src, 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)
|
||||
bool ICMPPing::notifyTestHost()
|
||||
{
|
||||
if (data.failedCount + successCount == data.totalCount)
|
||||
{
|
||||
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)
|
||||
if (data.failedCount == data.totalCount)
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
else
|
||||
data.errorMessage.clear(), data.avg = data.avg / successCount;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
if(timoutTimer)
|
||||
{
|
||||
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") };
|
||||
timoutTimer->stop();
|
||||
timoutTimer->clear();
|
||||
timoutTimer->close();
|
||||
}
|
||||
if(pollHandle)
|
||||
{
|
||||
if(!pollHandle->closing())
|
||||
pollHandle->stop();
|
||||
pollHandle->clear();
|
||||
pollHandle->close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ICMPPing::ping()
|
||||
{
|
||||
timoutTimer = loop->resource<uvw::TimerHandle>();
|
||||
uvw::OSSocketHandle osSocketHandle{ socketId };
|
||||
pollHandle = loop->resource<uvw::PollHandle>(osSocketHandle);
|
||||
timoutTimer->once<uvw::TimerEvent>([this,ptr=std::weak_ptr<ICMPPing>{shared_from_this()}](auto&,uvw::TimerHandle&h)
|
||||
{
|
||||
if(ptr.expired())
|
||||
return;
|
||||
else
|
||||
{
|
||||
auto p=ptr.lock();
|
||||
pollHandle->clear();
|
||||
if(!pollHandle->closing())
|
||||
pollHandle->stop();
|
||||
pollHandle->close();
|
||||
successCount = 0;
|
||||
data.failedCount = data.totalCount = req.totalCount;
|
||||
notifyTestHost();
|
||||
}
|
||||
});
|
||||
timoutTimer->start(uvw::TimerHandle::Time{ 10000 }, uvw::TimerHandle::Time{ 0 });
|
||||
auto pollEvent = uvw::Flags<uvw::PollHandle::Event>::from<uvw::PollHandle::Event::READABLE>();
|
||||
pollHandle->on<uvw::PollEvent>([this, ptr = shared_from_this()](uvw::PollEvent &, uvw::PollHandle &h) {
|
||||
timeval end;
|
||||
sockaddr_in addr;
|
||||
socklen_t slen = sizeof(sockaddr_in);
|
||||
int rlen = 0;
|
||||
char buf[1024];
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
rlen = recvfrom(socketId,buf , 1024, 0, (struct sockaddr *) &addr, &slen);
|
||||
} while (rlen == -1 && errno == EINTR);
|
||||
|
||||
// skip malformed
|
||||
#ifdef Q_OS_MAC
|
||||
if(rlen<sizeof(icmp)+20)
|
||||
#else
|
||||
if (rlen < sizeof(icmp))
|
||||
#endif
|
||||
continue;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
auto& resp=*reinterpret_cast<icmp*>(buf+20);
|
||||
#else
|
||||
auto& resp=*reinterpret_cast<icmp*>(buf);
|
||||
#endif
|
||||
// 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:
|
||||
gettimeofday(&end, nullptr);
|
||||
data.avg +=
|
||||
1000 * (end.tv_sec - startTimevals[cur_seq - 1].tv_sec) + (end.tv_usec - startTimevals[cur_seq - 1].tv_usec)/1000;
|
||||
successCount++;
|
||||
notifyTestHost();
|
||||
continue;
|
||||
case ICMP_UNREACH:
|
||||
data.errorMessage = "EPING_DST: " + QObject::tr("Destination unreachable");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost())
|
||||
{
|
||||
h.clear();
|
||||
h.close();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
case ICMP_TIMXCEED:
|
||||
data.errorMessage = "EPING_TIME: " + QObject::tr("Timeout");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost())
|
||||
{
|
||||
h.clear();
|
||||
h.close();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
data.errorMessage = "EPING_UNK: " + QObject::tr("Unknown error");
|
||||
data.failedCount++;
|
||||
if (notifyTestHost())
|
||||
{
|
||||
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);
|
||||
}
|
||||
return { 0, "EPING_TIME: " + QObject::tr("Timeout") };
|
||||
}
|
||||
} // namespace Qv2ray::components::latency::icmping
|
||||
#endif
|
||||
|
@ -1,19 +1,64 @@
|
||||
#pragma once
|
||||
#include <QtGlobal>
|
||||
#ifdef Q_OS_UNIX
|
||||
#include "components/latency/LatencyTest.hpp"
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include "components/latency/coroutine.hpp"
|
||||
#include "uvw.hpp"
|
||||
namespace Qv2ray::components::latency::icmping
|
||||
{
|
||||
class ICMPPing
|
||||
class ICMPPing : public std::enable_shared_from_this<ICMPPing>,public coroutine
|
||||
{
|
||||
public:
|
||||
explicit ICMPPing(int ttl);
|
||||
explicit ICMPPing(int ttl,std::shared_ptr<uvw::Loop> loop,LatencyTestRequest& req,LatencyTestHost* testHost);
|
||||
~ICMPPing()
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
QPair<int64_t, QString> ping(const QString &address);
|
||||
void start();
|
||||
private:
|
||||
int getAddrInfoRes(uvw::AddrInfoEvent &e);
|
||||
void ping();
|
||||
bool notifyTestHost();
|
||||
template<typename E, typename H>
|
||||
void async_DNS_lookup(E &&e, H &&h)
|
||||
{
|
||||
co_enter(*this)
|
||||
{
|
||||
if (getAddrHandle)
|
||||
{
|
||||
getAddrHandle->once<uvw::ErrorEvent>(coro(async_DNS_lookup));
|
||||
getAddrHandle->once<uvw::AddrInfoEvent>(coro(async_DNS_lookup));
|
||||
co_yield return getAddrHandle->addrInfo(req.host.toStdString(), digitBuffer);
|
||||
co_yield if constexpr (std::is_same_v<uvw::AddrInfoEvent, std::remove_reference_t<E>>)
|
||||
{
|
||||
if (getAddrInfoRes(e) != 0)
|
||||
{
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
h.clear();
|
||||
return;
|
||||
}
|
||||
h.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_same_v<uvw::ErrorEvent, std::remove_reference_t<E>>)
|
||||
{
|
||||
data.errorMessage = QObject::tr("DNS not resolved");
|
||||
data.avg = LATENCY_TEST_VALUE_ERROR;
|
||||
testHost->OnLatencyTestCompleted(req.id, data);
|
||||
h.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ping();
|
||||
}
|
||||
|
||||
private:
|
||||
void deinit();
|
||||
@ -22,6 +67,17 @@ namespace Qv2ray::components::latency::icmping
|
||||
// socket
|
||||
int socketId = -1;
|
||||
bool initialized = false;
|
||||
int successCount = 0;
|
||||
LatencyTestRequest req;
|
||||
LatencyTestHost* testHost;
|
||||
LatencyTestResult data;
|
||||
struct sockaddr_storage storage;
|
||||
char digitBuffer[20] = { 0 };
|
||||
std::shared_ptr<uvw::Loop> loop;
|
||||
std::shared_ptr<uvw::GetAddrInfoReq> getAddrHandle;
|
||||
std::shared_ptr<uvw::TimerHandle> timoutTimer;
|
||||
std::shared_ptr<uvw::PollHandle> pollHandle;
|
||||
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:
|
||||
|
@ -1183,16 +1183,9 @@ void PreferencesWindow::on_latencyTCPingRB_clicked()
|
||||
void PreferencesWindow::on_latencyICMPingRB_clicked()
|
||||
{
|
||||
LOADINGCHECK
|
||||
#ifdef Q_OS_MAC
|
||||
#warning No ICMPing support on macOS
|
||||
CurrentConfig.networkConfig.latencyTestingMethod = TCPING;
|
||||
latencyICMPingRB->setChecked(false);
|
||||
latencyTCPingRB->setChecked(true);
|
||||
#else
|
||||
CurrentConfig.networkConfig.latencyTestingMethod = ICMPING;
|
||||
latencyICMPingRB->setChecked(true);
|
||||
latencyTCPingRB->setChecked(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PreferencesWindow::on_qvNetworkUATxt_editTextChanged(const QString &arg1)
|
||||
|
Loading…
Reference in New Issue
Block a user