init: icmp traceroute

This commit is contained in:
ImSavsis 2026-07-19 20:25:30 +05:00
commit 5f815647c1
5 changed files with 179 additions and 0 deletions

14
.github/workflows/ci.yml vendored Normal file
View file

@ -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

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
build/
.claude/
*.obj
*.exe
*.pdb
*.ilk

11
CMakeLists.txt Normal file
View file

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.16)
project(ttlprobe CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(ttlprobe src/main.cpp)
if(WIN32)
target_link_libraries(ttlprobe ws2_32)
endif()

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# ttl-probe
свой traceroute на C++ через сырые ICMP-сокеты. шлёт echo-запросы с растущим TTL и смотрит кто отвечает "time exceeded" на каждом хопе.
```mermaid
sequenceDiagram
ttl-probe->>Hop1: ICMP echo, TTL=1
Hop1-->>ttl-probe: time exceeded
ttl-probe->>Hop2: ICMP echo, TTL=2
Hop2-->>ttl-probe: time exceeded
ttl-probe->>Target: ICMP echo, TTL=N
Target-->>ttl-probe: echo reply (дошли)
```
изначально планировал сюда честный fake-packet TTL-трюк (как в zapret), но на винде это требует сырых TCP-пакетов, а винда с висты их резать не даёт без отдельного драйвера (WinDivert/Npcap) — это уже не "просто". traceroute зато честно показывает сколько хопов до сервера, полезно прежде чем городить любой TTL-трюк вручную.
## сборка
```
cmake -B build
cmake --build build --config Release
```
## юзать
нужны права администратора (сырые сокеты):
```
build\Release\ttlprobe.exe example.com 30
```

118
src/main.cpp Normal file
View file

@ -0,0 +1,118 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <chrono>
#pragma comment(lib, "ws2_32.lib")
#pragma pack(push, 1)
struct IcmpHeader {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id;
uint16_t seq;
};
#pragma pack(pop)
static uint16_t checksum16(const uint8_t* data, size_t len) {
uint32_t sum = 0;
for (size_t i = 0; i + 1 < len; i += 2) {
sum += (static_cast<uint16_t>(data[i]) << 8) | data[i + 1];
}
if (len % 2 == 1) sum += static_cast<uint16_t>(data[len - 1]) << 8;
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return static_cast<uint16_t>(~sum);
}
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "usage: ttlprobe <host> [max-hops]" << std::endl;
return 1;
}
std::string target = argv[1];
int max_hops = argc > 2 ? std::stoi(argv[2]) : 30;
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
std::cerr << "WSAStartup failed" << std::endl;
return 1;
}
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_RAW;
addrinfo* res = nullptr;
if (getaddrinfo(target.c_str(), nullptr, &hints, &res) != 0 || !res) {
std::cerr << "cant resolve " << target << std::endl;
return 1;
}
sockaddr_in dest = *reinterpret_cast<sockaddr_in*>(res->ai_addr);
freeaddrinfo(res);
char dest_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &dest.sin_addr, dest_ip, sizeof(dest_ip));
std::cout << "probing " << target << " (" << dest_ip << "), " << max_hops << " hops max" << std::endl;
SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock == INVALID_SOCKET) {
std::cerr << "raw socket failed, run as administrator" << std::endl;
WSACleanup();
return 1;
}
DWORD timeout = 1500;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout));
for (int ttl = 1; ttl <= max_hops; ++ttl) {
setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast<const char*>(&ttl), sizeof(ttl));
uint8_t packet[64] = {};
IcmpHeader* icmp = reinterpret_cast<IcmpHeader*>(packet);
icmp->type = 8;
icmp->code = 0;
icmp->id = static_cast<uint16_t>(GetCurrentProcessId());
icmp->seq = static_cast<uint16_t>(ttl);
icmp->checksum = 0;
icmp->checksum = htons(checksum16(packet, sizeof(packet)));
auto start = std::chrono::steady_clock::now();
sendto(sock, reinterpret_cast<char*>(packet), sizeof(packet), 0,
reinterpret_cast<sockaddr*>(&dest), sizeof(dest));
uint8_t reply[512];
sockaddr_in from{};
int from_len = sizeof(from);
int n = recvfrom(sock, reinterpret_cast<char*>(reply), sizeof(reply), 0,
reinterpret_cast<sockaddr*>(&from), &from_len);
auto end = std::chrono::steady_clock::now();
double ms = std::chrono::duration<double, std::milli>(end - start).count();
std::cout << ttl << "\t";
if (n <= 0) {
std::cout << "*" << std::endl;
continue;
}
char from_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &from.sin_addr, from_ip, sizeof(from_ip));
std::cout << from_ip << "\t" << ms << " ms" << std::endl;
int ip_header_len = (reply[0] & 0x0F) * 4;
if (n > ip_header_len) {
uint8_t icmp_type = reply[ip_header_len];
if (icmp_type == 0 || from.sin_addr.S_un.S_addr == dest.sin_addr.S_un.S_addr) {
std::cout << "reached destination" << std::endl;
break;
}
}
}
closesocket(sock);
WSACleanup();
return 0;
}