Powrót do strony głównej

Traceroute w Rust: jak działa śledzenie trasy

Artykuł rozkłada implementację traceroute w Rust. Pokazano, jak TTL i komunikaty ICMP budują mapę trasy. Podano kod z wyjaśnieniem działania raw-socketów i przetwarzania pakietów sieciowych.

Jak traceroute buduje trasę: techniczny rozkład w Rust
Advertisement 728x90

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:

Google AdInline article slot
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:

Google AdInline article slot
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:

Google AdInline article slot
  • 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

Advertisement 728x90

Czytaj dalej