/* Author: Maciek Muszkowski * * This is a simple ping implementation for Linux. * It will work ONLY on kernels 3.x+ and you need * to set allowed groups in /proc/sys/net/ipv4/ping_group_range */ #include "ICMPPing.hpp" #include #ifdef Q_OS_UNIX #include #include //macos need that #include #include #include #include #include "uvw.hpp" #ifdef Q_OS_MAC #define SOL_IP 0 #endif namespace Qv2ray::components::latency::icmping { /// 1s complementary checksum uint16_t ping_checksum(const char *buf, size_t size) { size_t i; uint64_t sum = 0; for (i = 0; i < size; i += 2) { sum += *(uint16_t *) buf; buf += 2; } if (size - i > 0) { sum += *(uint8_t *) buf; } while ((sum >> 16) != 0) { sum = (sum & 0xffff) + (sum >> 16); } return (uint16_t) ~sum; } void ICMPPing::deinit() { if (socketId >= 0) { close(socketId); socketId = -1; } } ICMPPing::ICMPPing(int ttl) { auto timeout_s = 5; // create socket if (((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0)) { initErrorMessage = "EPING_SOCK: " + QObject::tr("Socket creation failed"); return; } // set TTL if (setsockopt(socketId, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) { deinit(); initErrorMessage = "EPING_TTL: " + QObject::tr("Failed to setup TTL value"); 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; } void ICMPPing::start(std::shared_ptr loop, LatencyTestRequest &req, LatencyTestHost *testHost) { if (!initialized) { data.errorMessage = initErrorMessage; data.avg = LATENCY_TEST_VALUE_ERROR; testHost->OnLatencyTestCompleted(req.id, data); return; } struct sockaddr_storage storage; data.totalCount = req.totalCount; data.failedCount = 0; data.worst = 0; data.avg = 0; if (getSockAddress(loop, req.host.toStdString().data(), req.port, &storage, 0) != 0) { data.errorMessage = QObject::tr("DNS not resolved"); data.avg = LATENCY_TEST_VALUE_ERROR; testHost->OnLatencyTestCompleted(req.id, data); return; } uvw::OSSocketHandle osSocketHandle{ socketId }; auto pollHandle = loop->resource(osSocketHandle); pollHandle->init(); auto pollEvent = uvw::Flags::from(); pollHandle->on([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(&_icmp_request), sizeof(_icmp_request)); int n; timeval start; gettimeofday(&start, nullptr); startTimevals.push_back(start); do { n = ::sendto(socketId, &_icmp_request, sizeof(icmp), 0, (struct sockaddr *) &storage, sizeof(struct sockaddr)); } while (n < 0 && errno == EINTR); } } bool ICMPPing::notifyTestHost(LatencyTestHost *testHost, const ConnectionId &id) { if (data.failedCount + successCount == data.totalCount) { if (data.failedCount == data.totalCount) data.avg = LATENCY_TEST_VALUE_ERROR; else data.errorMessage.clear(), data.avg = data.avg / successCount / 1000; testHost->OnLatencyTestCompleted(id, data); return true; } return false; } } // namespace Qv2ray::components::latency::icmping #endif