init: icmp traceroute
This commit is contained in:
commit
5f815647c1
5 changed files with 179 additions and 0 deletions
14
.github/workflows/ci.yml
vendored
Normal file
14
.github/workflows/ci.yml
vendored
Normal 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
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
build/
|
||||||
|
.claude/
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal 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
30
README.md
Normal 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
118
src/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue