La DHT BitTorrent, ou Mainline DHT décrite par le BEP 5, est une table de hachage distribuée : chaque client torrent y joue le rôle de node DHT, garde une part de l'annuaire et renvoie, pour le hash d'un torrent, la liste des peer qui le partagent, sans tracker.
La DHT, une table de hachage répartie entre des millions de clients
La DHT (pour distributed hash table) répond à la question de tout client BitTorrent : quel peer partage en ce moment le fichier torrent identifié par tel infohash ? Dans le bittorrent protocol d'origine, un tracker détient cette liste. Sur le réseau DHT, chaque node en stocke un fragment, celui des clés proches de son identifiant : la peer discovery passe alors par la DHT, un client torrent interrogeant d'autres nœuds.
Le principe d'une table de hachage reste le même qu'en programmation : une clé (key) renvoie une valeur (value). Ici, la clé est l'infohash du torrent, une empreinte SHA-1 de 160 bits calculée sur la partie « info » du fichier torrent, et la valeur est une liste de contacts de pairs (adresse IP et port). La différence tient à la répartition : la table n'est pas en mémoire sur une machine, elle se découpe entre tous les participants, qui se relaient les requêtes jusqu'à atteindre ceux qui détiennent la bonne entrée.
Le client BitTorrent officiel, surnommé Mainline, a introduit cette DHT en 2005 avec sa version 4.2.0, d'où le nom de Mainline DHT. Azureus (devenu Vuze) disposait déjà de sa propre DHT, incompatible. La version Mainline s'est imposée : qBittorrent, Transmission, Deluge, rTorrent (avec libtorrent) ou µTorrent la parlent tous. Une mesure publiée en 2013 par Liang Wang et Jussi Kangasharju (université d'Helsinki) estimait sa taille entre 15 et 27 millions de nœuds actifs par jour, ce qui en fait l'un des plus grands systèmes distribués ouverts.
La spécification tient dans le document BEP 5 (BitTorrent Enhancement Proposal), publié sur bittorrent.org. Elle repose sur l'algorithme Kademlia, décrit en 2002 par Petar Maymounkov et David Mazières.
Comment fonctionne la DHT dans BitTorrent ?
La DHT de BitTorrent fonctionne en trois temps : chaque nœud reçoit un identifiant, il range ses contacts dans une routing table selon leur distance, puis il interroge de proche en proche les nœuds les plus proches d'un infohash jusqu'à obtenir des pairs.
Identifiant de nœud et distance XOR
Au démarrage, le client tire au hasard un node ID de 160 bits, la même taille qu'un infohash. Identifiants de nœuds et identifiants de torrents vivent donc dans le même espace, ce qui permet de mesurer une distance entre eux. Kademlia définit cette distance par un OU exclusif (XOR) entre deux identifiants, lu comme un entier non signé : plus le résultat est petit, plus les deux identifiants sont proches. Un nœud se charge des torrents dont l'infohash est proche de son propre ID.
La routing table et ses buckets
Chaque nœud tient une routing table découpée en buckets. Un bucket couvre une tranche de l'espace des identifiants et contient au plus huit contacts. La table connaît beaucoup de nœuds proches de son propre ID et peu de nœuds lointains : il suffit de quelques sauts pour atteindre n'importe quelle zone du réseau. Le BEP 5 distingue les nœuds « good » (une réponse reçue dans les quinze dernières minutes), « questionable » et « bad ». Un bucket plein n'accepte un nouveau contact qu'en évinçant un nœud qui ne répond plus ; un bucket resté quinze minutes sans activité se rafraîchit par une recherche au hasard dans sa tranche.
Le lookup itératif
Pour trouver des pairs, le client choisit dans sa table les nœuds les plus proches de l'infohash et leur envoie une requête get_peers. Chaque node répond soit par des contacts de pairs, soit par des nœuds encore plus proches. Le client recommence avec ces nouveaux contacts, et chaque tour divise à peu près par deux la distance restante. Sur un réseau de plusieurs millions de nœuds, une vingtaine de sauts au plus suffit en théorie ; en pratique, plusieurs requêtes partent en parallèle pour gagner du temps. Le code de libtorrent appelle cette boucle un « iterative lookup » : le lookup s'arrête quand plus aucune réponse ne renvoie de node plus proche (closest to the infohash) que ceux déjà interrogés.
Les messages KRPC : ping, find_node, get_peers et announce_peer
Les nœuds de la DHT échangent des messages KRPC : un dictionnaire bencodé (le format des fichiers .torrent) envoyé dans un seul paquet UDP, sans connexion. Chaque message porte une clé « t » (identifiant de transaction) et une clé « y » dont la value vaut « q » pour une requête (query), « r » pour une réponse ou « e » pour une erreur. Le BEP 5 définit quatre requêtes.
| Requête | Ce que le client demande | Ce que le nœud interrogé renvoie |
|---|---|---|
ping | Vérifier qu'un contact répond encore | Son node ID |
find_node | Les contacts proches d'un identifiant cible | Les huit nœuds les plus proches de sa table (compact node info) |
get_peers | Les pairs d'un infohash | Des pairs (clé « values ») ou des nœuds plus proches (clé « nodes »), plus un token |
announce_peer | S'inscrire comme pair d'un torrent, avec le token reçu | Une confirmation |
Une requête get_peers ressemble à ceci, une fois décodée : {"t": "aa", "y": "q", "q": "get_peers", "a": {"id": "<node ID>", "info_hash": "<infohash>"}}. La réponse contient un « token », une courte chaîne que le nœud calcule à partir de l'adresse IP du demandeur et d'un secret renouvelé toutes les cinq minutes. Le client doit renvoyer ce token dans son announce_peer : le nœud refuse un token de plus de dix minutes environ. Ce mécanisme empêche une machine d'inscrire l'adresse d'une autre dans la DHT.
Les formats compacts économisent la place : la « peer contact information » tient en 6 octets (4 pour l'IPv4, 2 pour le port, en network byte order), la « compact node info » en 26 octets (20 pour l'ID, 6 pour l'adresse). Les erreurs portent un code : 201 (generic error), 202 (server error), 203 (protocol error, paquet malformé ou token invalide) et 204 (method unknown). Une erreur KRPC prend la forme {"t": "aa", "y": "e", "e": [201, "A Generic Error Ocurred"]} : la key « e » contient une liste, un entier puis une string qui décrit l'error.
Une réponse positive à get_peers suit le même moule : {"t": "aa", "y": "r", "r": {"id": "<node ID>", "token": "<token>", "values": ["<peer 1>", "<peer 2>"]}}. La key « values » contient une liste de strings de 6 octets, une par peer ; si le nœud ne connaît aucun pair, il return à la place une key « nodes » avec plus de contacts proches. Le client ne reçoit jamais de metadata du torrent par cette voie : la DHT ne stocke que des adresses, pas de content.
Le premier contact : bootstrap et port UDP
Un client BitTorrent qui démarre sans aucun contact ne peut pas entrer seul dans la DHT : il lui faut une première adresse. Les logiciels intègrent des nœuds d'amorçage connus, comme router.bittorrent.com ou dht.transmissionbt.com sur le port 6881. Le client leur envoie un find_node sur son propre ID, récupère les nœuds proches de lui et remplit sa routing table de proche en proche. Il sauvegarde ensuite ses contacts sur le disque pour ne plus dépendre de ces serveurs au lancement suivant.
Deux autres voies d'entrée existent. Un fichier torrent peut contenir une clé « nodes » listant quelques nœuds, ce que le BEP 5 prévoit pour les trackerless torrents. Et deux pairs qui se connectent en TCP s'annoncent mutuellement leur port DHT par un message « port » du peer protocol, ce qui ajoute des contacts sans requête de plus.
Côté réseau local, la DHT utilise UDP, souvent sur le même numéro de port que les connexions entrantes TCP du client. Un port fermé par la box ou un pare-feu n'empêche pas d'envoyer des requêtes, mais le nœud reçoit mal les sollicitations des autres : il reste dans les tables comme un contact peu fiable et rend moins de services au réseau. Ouvrir ce port UDP, ou le rediriger par UPnP, améliore la découverte de pairs.
Télécharger un torrent sans tracker grâce aux liens magnet
Un torrent sans tracker se télécharge dès que le client connaît son infohash : la DHT fournit les pairs, puis le client récupère auprès d'eux les métadonnées (la partie « info » du fichier .torrent) grâce à l'extension décrite dans le BEP 9. C'est ce couple qui fait fonctionner un lien magnet réduit à son hash : la chaîne magnet:?xt=urn:btih: suivie de l'infohash suffit, sans fichier ni annonce.
La DHT ne travaille pas seule. Le Peer Exchange (PEX) laisse deux pairs connectés s'échanger leurs listes de contacts, et la Local Peer Discovery cherche des pairs sur le réseau local. Le tableau compare ces sources de pairs.
| Source | Point central | Fonctionne au premier contact | Torrent privé |
|---|---|---|---|
| Tracker | Oui, un serveur | Oui | Seule source permise |
| DHT | Non, réseau de nœuds | Oui, après bootstrap | Désactivée |
| PEX | Non, entre pairs connectés | Non, il faut déjà un pair | Désactivé |
| Local Peer Discovery | Non, multicast local | Oui, sur le même réseau | Désactivée |
La colonne « torrent privé » vient du BEP 27 : un fichier qui porte le drapeau private=1 interdit au client d'utiliser la DHT et le PEX pour ce torrent. Le tracker du site privé garde ainsi la maîtrise de la liste des pairs et du ratio de chaque membre, notion détaillée avec le rôle du seeder et du leecher.
Les extensions de la DHT : BEP 32, 33, 42, 44 et 51
La DHT Mainline a gagné plusieurs extensions depuis 2005, chacune décrite dans un BEP distinct :
- BEP 32 : la DHT en IPv6, avec une table séparée et des requêtes « nodes6 ».
- BEP 33 : le scrape par DHT, qui estime le nombre de seeders et de leechers d'un torrent grâce à des filtres de Bloom.
- BEP 42 : l'extension de sécurité, qui lie le node ID à l'adresse IP du nœud pour compliquer le choix d'un identifiant sur mesure.
- BEP 44 : le stockage de données arbitraires (requêtes get et put). Une entrée immuable a pour clé le hash de sa value ; une entrée mutable se signe avec une public key ed25519 et porte un numéro de séquence, ce qui permet de publier un contenu modifiable.
- BEP 51 : la requête sample_infohashes, qui renvoie un échantillon des infohashes connus d'un nœud, utile aux outils d'indexation et de mesure.
Toutes les implémentations ne couvrent pas toutes ces extensions. libtorrent (la bibliothèque de qBittorrent et Deluge) en gère la plupart. Côté JavaScript, le module open source bittorrent-dht du projet WebTorrent implémente le BEP 5 et le BEP 44 pour Node.js. Dans un navigateur, en revanche, le code JavaScript ne peut pas ouvrir de socket UDP : WebTorrent y passe par WebRTC et ne rejoint pas la DHT directement.
Quelles sont les vulnérabilités du DHT ?
La DHT BitTorrent est ouverte à tous, sans authentification : sa première faiblesse tient à cette ouverture. Une attaque Sybil consiste à faire tourner un grand nombre de nœuds fictifs ; placés près d'un infohash, ils peuvent cacher les vrais pairs (attaque par éclipse) ou renvoyer de fausses adresses. Le BEP 42 limite ce risque en contraignant l'identifiant d'un nœud selon son adresse IP, mais tous les clients ne l'appliquent pas strictement.
Le protocole sert aussi d'amplificateur. Une réponse UDP plus grosse que la requête, envoyée vers une adresse usurpée, permet des attaques par déni de service réfléchi. Florian Adamsky et ses coauteurs ont décrit en 2015 (conférence USENIX WOOT) comment plusieurs protocoles BitTorrent, DHT comprise, se prêtaient à ce détournement ; les développeurs de libtorrent ont corrigé une partie des failles signalées.
Reste la confidentialité. La DHT publie par nature l'adresse IP et le port de chaque pair qui annonce un torrent : n'importe quel nœud peut lister qui partage quoi. Des sociétés de surveillance et des chercheurs collectent ces données à grande échelle, en particulier depuis le BEP 51. Le client qui tourne sur un serveur distant expose l'adresse de ce serveur, et non celle de la connexion domestique ; c'est l'un des points qui distinguent l'usage d'une seedbox, un serveur dédié au partage, d'un client lancé sur son propre ordinateur.
Comment configurer DHT sur qBittorrent ?
Dans qBittorrent, la DHT s'active ou se coupe dans Outils, puis Options, onglet BitTorrent : la case « Activer le DHT (réseau décentralisé) pour trouver plus de pairs » la commande pour tous les torrents publics. La même page règle le PEX et la Local Peer Discovery. Le port UDP de la DHT suit celui défini dans l'onglet Connexion ; le menu Avancé, propre à libtorrent, propose en plus des options comme le respect strict du BEP 42.
La barre d'état, en bas de la fenêtre, affiche « DHT : N nœuds ». Un compteur bloqué à zéro après quelques minutes signale en général un port UDP filtré ou des nœuds d'amorçage injoignables. Un torrent privé n'affiche jamais de pairs venus de la DHT, même case cochée : c'est le comportement attendu. Les autres réglages du logiciel figurent dans la présentation de qBittorrent, client libre et sans publicité.
Sur rTorrent, la ligne dht.mode.set = auto du fichier de configuration lance la DHT dès qu'un torrent public la demande ; Transmission et Deluge proposent une case équivalente dans leurs préférences.
Ce que la DHT change pour un magnet, un port fermé ou un torrent privé
La DHT remplace-t-elle le tracker ?
La DHT complète le tracker plus qu'elle ne le remplace : un client interroge les deux et fusionne les pairs obtenus. Pour un torrent sans tracker ou au tracker hors ligne, elle devient la source principale de pairs.
Pourquoi la DHT ne trouve-t-elle aucun pair ?
Une DHT sans pair vient le plus souvent d'un torrent privé (DHT désactivée par le BEP 27), d'un port UDP bloqué ou d'un torrent que plus personne n'annonce. Le compteur de nœuds du client distingue les cas : à zéro, le problème vient du réseau.
Faut-il couper la DHT ?
Couper la DHT se justifie sur un site privé qui l'exige pour tous les torrents ou quand la confidentialité de l'adresse IP prime. Pour les torrents publics, la couper réduit le nombre de pairs et rend les liens magnet souvent inutilisables.
