Retour à l'accueil

Traceroute sur Rust : fonctionnement du traçage de route

L'article décompose l'implémentation traceroute sur Rust. Il montre comment TTL et les messages ICMP construisent une carte de route. Le code est fourni avec des explications sur le fonctionnement des sockets raw et le traitement des paquets réseau.

Comment traceroute construit une route : analyse technique sur Rust
Advertisement 728x90

Comment fonctionne Traceroute : Implémentation en Rust à partir de zéro

Traceroute est un outil de diagnostic réseau qui révèle le chemin que prennent les paquets à travers internet. Il repose sur deux mécanismes clés : TTL (Time to Live) dans les en-têtes IP et les messages d'erreur ICMP. En incrémentant le TTL à chaque étape, les routeurs envoient des notifications lorsque la durée de vie du paquet expire, construisant une carte de routage. Implémenter notre propre version en Rust en seulement 80 lignes de code montre à quel point les opérations réseau de bas niveau deviennent limpides avec une plongée profonde dans les protocoles.

Architecture de la sonde UDP

L'élément critique de traceroute est l'envoi de paquets ciblés avec un TTL limité. UDP est utilisé au lieu de TCP pour trois raisons :

  • Pas de poignée de main, ce qui réduit la surcharge
  • Pas de garantie de livraison — les paquets sont destinés à être abandonnés
  • Des numéros de ports élevés (à partir de 33434) minimisent les conflits avec les services en cours

Le code initialise deux sockets :

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),
)?;

Le premier socket envoie des paquets condamnés avec le TTL spécifié, le second intercepte les réponses ICMP. Important : les sockets raw nécessitent des privilèges root, car ils opèrent au niveau de la pile réseau.

Analyse des réponses ICMP

Lorsqu'un routeur abandonne un paquet à TTL=0, il envoie un message ICMP de type 11 (Time Exceeded). La structure de données contient :

  • Les 20 premiers octets — en-tête IP de la réponse
  • Octets 12-15 — adresse IP du routeur
  • Octet 20 — type de message ICMP

L'implémentation initiale n'analysait que l'adresse IP :

Google AdInline article slot
if buf.len() >= 20 {
    let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]);
    Ok(Some(ip))
}

Cela entraînait des erreurs dans la détection de l'extrémité. Une gestion correcte nécessite de vérifier le type 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),
}

Le type 3 (Destination Unreachable) indique l'arrivée à la cible uniquement si l'IP correspond.

Optimisations de Traceroute

Le traceroute original utilise deux techniques absentes de l'implémentation basique :

Google AdInline article slot
  • Incrémentation du numéro de port — chaque paquet suivant est envoyé vers le port +1. Cela permet un appariement sans ambiguïté des réponses ICMP aux requêtes via le champ Identification dans l'en-tête UDP.
  • Support du mode TCP — lorsque UDP est bloqué par les pare-feu, un paquet SYN avec un TTL faible est utilisé. Le mécanisme de détection de saut reste le même.

Une erreur critique dans les premières versions du code — ignorer la vérification d'adresse IP lors de la réception du type 3. Sans la condition if ip == target, le traçage s'arrêtait au premier routeur renvoyant Destination Unreachable.

Points importants

  • TTL comme outil de contrôle — en augmentant progressivement la valeur, on découvre séquentiellement chaque saut
  • Messages ICMP — base du diagnostic — Time Exceeded (type 11) et Destination Unreachable (type 3) forment la carte de routage
  • Sockets raw nécessitent des privilèges — les opérations réseau de bas niveau sont impossibles sans droits root
  • UDP vs TCP — le choix du protocole affecte l'évasion des pare-feu, mais pas l'algorithme de base
  • Analyse de données binaires — l'accès direct aux octets des paquets nécessite la connaissance des structures d'en-têtes IP/ICMP

Implémentation de la condition d'arrêt

Une amélioration clé — détecter correctement quand la cible est atteinte. L'enum ProbeResult remplace le simple Option<Ipv4Addr> :

enum ProbeResult {
    Hop(Ipv4Addr),
    Reached(Ipv4Addr),
    Timeout,
}

Dans la boucle principale, la gestion des résultats ressemble à ceci :

match hop {
    ProbeResult::Hop(ip) => println!("{:>2}  {}", ttl, ip),
    ProbeResult::Reached(ip) => {
        println!("{:>2}  {}", ttl, ip);
        break;
    }
    ProbeResult::Timeout => println!("{:>2}  *", ttl),
}

Cela garantit que le traçage s'arrête à l'arrivée sur l'hôte cible, et non au timeout. Dans des scénarios réels, ajouter également :

  • Trois sondes par TTL pour les statistiques de latence
  • Gestion des paquets fragmentés
  • Support IPv6
  • Timeouts basés sur la distance

Résumé et limitations

L'implémentation construite démontre le cœur de traceroute, mais présente des limitations :

  • Ne tient pas compte des routes asymétriques
  • Ne gère pas la limitation de taux ICMP
  • Ignore les étiquettes MPLS dans les réseaux modernes
  • Nécessite des privilèges superutilisateur

Pour des solutions de production, utilisez des bibliothèques comme pnet_packet pour éviter l'analyse manuelle des octets. Cependant, écrire une version raw est la meilleure façon de comprendre les fondamentaux réseau. Tout ingénieur réseau devrait implémenter traceroute au moins une fois pour saisir comment les données voyagent à travers internet.

— Editorial Team

Advertisement 728x90

Lire ensuite