Jak działa Traceroute: implementacja w Rust od zera
Traceroute to narzędzie diagnostyki sieciowej, które ujawnia ścieżkę pakietów przez internet. Jego działanie opiera się na dwóch kluczowych mechanizmach: TTL (Time to Live) w nagłówkach IP oraz komunikatach ICMP o błędach. Zwiększając TTL na każdym kroku, routery zwracają powiadomienia o przekroczeniu czasu życia pakietu, tworząc mapę trasy. Implementacja własnej wersji w Rust w 80 liniach kodu pokazuje, jak niskopoziomowe operacje sieciowe stają się zrozumiałe przy dogłębnym opanowaniu protokołów.
Architektura sondy UDP
Kluczowym elementem działania traceroute jest celowe wysyłanie pakietów z ograniczonym TTL. W tym celu stosuje się protokół UDP zamiast TCP z trzech powodów:
- Brak handshake'u zmniejsza nakłady
- Brak gwarancji dostarczenia — pakiety są przeznaczone do utraty
- Wysokie numery portów (począwszy od 33434) minimalizują konflikty z działającymi usługami
Kod inicjalizuje dwa sockety:
let send_sock = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
send_sock.set_ttl_v4(ttl)?;
let recv_sock = Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::ICMPV4),
)?;
Pierwszy socket wysyła „śmiertelne” pakiety z zadanym TTL, drugi przechwytuje odpowiedzi ICMP. Ważne: raw-socket wymaga praw root, ponieważ działa na poziomie stosu sieciowego.
Parsowanie odpowiedzi ICMP
Gdy router odrzuca pakiet przy TTL=0, wysyła komunikat ICMP typu 11 (Time Exceeded). Struktura danych zawiera:
- Pierwsze 20 bajtów — nagłówek IP odpowiedzi
- Bajty 12-15 — adres IP routera
- Bajt 20 — typ komunikatu ICMP
Początkowa implementacja analizowała tylko adres IP:
if buf.len() >= 20 {
let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]);
Ok(Some(ip))
}
Prowadziło to do błędów w określaniu punktu końcowego. Poprawna obsługa wymaga sprawdzenia typu ICMP:
match buf[20] {
11 => Ok(ProbeResult::Hop(ip)),
3 if ip == target => Ok(ProbeResult::Reached(ip)),
3 => Ok(ProbeResult::Hop(ip)),
_ => Ok(ProbeResult::Timeout),
}
Typ 3 (Destination Unreachable) wskazuje na osiągnięcie celu tylko przy zgodności adresu IP.
Optymalizacja trasowania
Oryginalny traceroute wykorzystuje dwie techniki brakujące w podstawowej implementacji:
- Inkrementacja numeru portu — każdy kolejny pakiet jest wysyłany na port +1. Umożliwia to jednoznaczne kojarzenie odpowiedzi ICMP z zapytaniami za pomocą pola Identification w nagłówku UDP.
- Obsługa trybu TCP — w przypadku blokady UDP przez firewalle stosuje się pakiet SYN z niskim TTL. Mechanizm wykrywania hopów pozostaje identyczny.
Krytyczny błąd w wczesnych wersjach kodu — pomijanie sprawdzenia adresu IP przy typie 3. Bez warunku if ip == target trasowanie zatrzymywało się na pierwszym routerze zwracającym Destination Unreachable.
Co ważne
- TTL jako narzędzie kontroli — stopniowe zwiększanie wartości pozwala kolejno odkrywać każdy hop
- Komunikaty ICMP — podstawa diagnostyki — Time Exceeded (typ 11) i Destination Unreachable (typ 3) tworzą mapę trasy
- Raw sockety wymagają przywilejów — operacje sieciowe niskiego poziomu niemożliwe bez praw root
- UDP kontra TCP — wybór protokołu wpływa na omijanie firewalli, ale nie na algorytm bazowy
- Parsowanie danych binarnych — bezpośredni dostęp do bajtów pakietu wymaga znajomości struktury nagłówków IP/ICMP
Implementacja warunku zatrzymania
Kluczowa poprawa — poprawne wykrywanie osiągnięcia celu. Wyliczenie ProbeResult zastępuje prosty Option<Ipv4Addr>:
enum ProbeResult {
Hop(Ipv4Addr),
Reached(Ipv4Addr),
Timeout,
}
W głównej pętli obsługa wyniku wygląda następująco:
match hop {
ProbeResult::Hop(ip) => println!("{:>2} {}", ttl, ip),
ProbeResult::Reached(ip) => {
println!("{:>2} {}", ttl, ip);
break;
}
ProbeResult::Timeout => println!("{:>2} *", ttl),
}
Gwarantuje to zatrzymanie trasowania po osiągnięciu hosta docelowego, a nie na podstawie timeoutu. W warunkach rzeczywistych należy dodać:
- Trzy próby na każdy TTL w celu statystyk opóźnień
- Obsługę spakietowanych pakietów
- Wsparcie dla IPv6
- Timeauty zależne od odległości do celu
Podsumowanie i ograniczenia
Zbudowana implementacja pokazuje sedno traceroute, ale ma ograniczenia:
- Nie uwzględnia tras asymetrycznych
- Nie obsługuje ograniczeń ICMP Rate Limiting
- Ignoruje etykiety MPLS w nowoczesnych sieciach
- Wymaga praw administratora
W rozwiązaniach produkcyjnych zaleca się biblioteki takie jak pnet_packet, unikające ręcznego parsowania bajtów. Niemniej napisanie wersji „surowej” to najlepszy sposób zrozumienia podstaw sieciowych. Każdy inżynier sieciowy powinien choć raz samodzielnie zaimplementować traceroute, aby pojąć, jak dane podróżują przez internet.
— Editorial Team
Brak komentarzy.