commit 37c8aaab80e35e3e8e457ac2b6d37b1188fbcd12 Author: ImSavsis Date: Sun Jul 19 20:28:32 2026 +0500 init: tcp connect latency and jitter tester diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c4b2583 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: ci + +on: + push: + branches: [master] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: ilammy/msvc-dev-cmd@v1 + - run: cmake -B build + - run: cmake --build build --config Release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d42690b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.claude/ +*.obj +*.exe +*.pdb +*.ilk diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5a99e1c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.16) +project(pingpp CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(pingpp src/main.cpp) + +if(WIN32) + target_link_libraries(pingpp ws2_32) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..df6ecf3 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# pingpp + +меряет задержку и джиттер до хостов через обычный TCP-connect, без ICMP и прав администратора. удобно быстро глянуть какая VPN-нода бодрее. + +```mermaid +sequenceDiagram + loop 5 попыток + pingpp->>Host: TCP connect + Host-->>pingpp: SYN-ACK + Note over pingpp: засекли время + end + pingpp->>pingpp: avg / min / max / jitter +``` + +## сборка + +``` +cmake -B build +cmake --build build --config Release +``` + +## юзать + +``` +build\Release\pingpp.exe 1.1.1.1:443 vpn.example.com:51820 +``` diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ba545e5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,121 @@ +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +struct Target { + std::string host; + uint16_t port; +}; + +static bool parse_target(const std::string& s, Target& t) { + size_t colon = s.rfind(':'); + if (colon == std::string::npos) return false; + t.host = s.substr(0, colon); + try { + t.port = static_cast(std::stoi(s.substr(colon + 1))); + } catch (...) { + return false; + } + return true; +} + +static bool tcp_connect_once(const Target& t, double& ms) { + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + addrinfo* res = nullptr; + std::string portstr = std::to_string(t.port); + if (getaddrinfo(t.host.c_str(), portstr.c_str(), &hints, &res) != 0 || !res) return false; + + SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == INVALID_SOCKET) { freeaddrinfo(res); return false; } + + u_long nonblock = 1; + ioctlsocket(s, FIONBIO, &nonblock); + + auto start = std::chrono::steady_clock::now(); + int rc = connect(s, res->ai_addr, static_cast(res->ai_addrlen)); + bool ok = false; + + if (rc == 0) { + ok = true; + } else if (WSAGetLastError() == WSAEWOULDBLOCK) { + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(s, &writefds); + timeval tv{}; + tv.tv_sec = 2; + tv.tv_usec = 0; + if (select(0, nullptr, &writefds, nullptr, &tv) > 0) { + int err = 0; + int errlen = sizeof(err); + getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &errlen); + ok = (err == 0); + } + } + + auto end = std::chrono::steady_clock::now(); + ms = std::chrono::duration(end - start).count(); + + closesocket(s); + freeaddrinfo(res); + return ok; +} + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "usage: pingpp [host:port ...]" << std::endl; + return 1; + } + + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + std::cerr << "WSAStartup failed" << std::endl; + return 1; + } + + const int attempts = 5; + + for (int i = 1; i < argc; ++i) { + Target t; + if (!parse_target(argv[i], t)) { + std::cout << argv[i] << ": bad format, use host:port" << std::endl; + continue; + } + + std::vector times; + for (int a = 0; a < attempts; ++a) { + double ms; + if (tcp_connect_once(t, ms)) times.push_back(ms); + } + + std::cout << t.host << ":" << t.port << " -> "; + if (times.empty()) { + std::cout << "no response" << std::endl; + continue; + } + + double sum = 0, mn = times[0], mx = times[0]; + for (double v : times) { sum += v; mn = std::min(mn, v); mx = std::max(mx, v); } + double avg = sum / times.size(); + + double var = 0; + for (double v : times) var += (v - avg) * (v - avg); + double jitter = std::sqrt(var / times.size()); + + std::cout << times.size() << "/" << attempts << " ok, avg " << avg << " ms, " + << "min " << mn << " ms, max " << mx << " ms, jitter " << jitter << " ms" << std::endl; + } + + WSACleanup(); + return 0; +}