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