# Geek++ — Étude technique complète du protocole eD2K/eMule {#top} Cette édition publie la matière technique de l'étude du protocole : constats dans le code, structures, constantes, opcodes, états, formats, comportements historiques, différences d'implémentation et anomalies relevées. Les passages consacrés aux pistes produit et aux modifications futures ont été retirés afin de conserver ici uniquement l'étude d'eMule/eD2K/Kad elle-même. > **Niveau de lecture :** cette page ne cherche pas à simplifier. Les pages « eMule en 5 minutes » et « Le voyage d'un fichier » existent pour la vulgarisation. Geek++ est le dossier technique. # Dossier 01 — Découverte et gestion des sources {#g01} --- ## 0. Résumé exécutif - des éléments strictement protocolaires et donc intouchables sans rupture de compatibilité ; - des conventions communes entre clients ; - une grande quantité de politique locale : temporisations, seuils, ordonnancement, dédoublonnage et choix du canal de découverte. La piste prioritaire n’est **pas** de créer de nouveaux paquets réseau. La piste prioritaire est de rendre la stratégie locale plus intelligente et plus observable tout en conservant les protocoles existants. --- # 1. Périmètre analysé Corpus utilisé : - eDonkey Protocol v0.6 (2002) - Overnet Protocol (2003) - The eMule Protocol Specification (2005) - eMule 0.50a - eMule Community 0.72a - aMule 3.x - MLDonkey et son ancien wiki - Wireshark `packet-edonkey` - documentation officielle eMule - documentation aMule - papier Kademlia Les références de lignes ci-dessous concernent les copies fournies dans le corpus. --- # 2. Les cinq origines de sources d’eMule 0.72a Dans : `srchybrid/ClientStateDefs.h:103-110` eMule définit : ```cpp SF_SERVER SF_KADEMLIA SF_SOURCE_EXCHANGE SF_PASSIVE SF_LINK ``` L’interface reprend cette information dans : `DownloadListCtrl.cpp:468-486` Il s’agit donc bien d’une classification fonctionnelle utilisée par le programme. --- # 3. Canal 1 — serveur eD2K connecté, en TCP ## 3.1 Racine historique La spécification eDonkey 0.6 définit : ```text 0x19 Get sources -> serveur 0x42 Found sources <- serveur ``` La demande contient le hash du fichier. Le mécanisme existe donc dans le socle eDonkey, avant les extensions propres à eMule. ## 3.2 eMule 0.72a Le test de programmation est dans : `PartFile.cpp:2339-2346` Conditions principales : - aucune demande locale déjà en attente ; - première recherche OU dernier appel vieux d’au moins `SERVERREASKTIME` ; - serveur eD2K connecté ; - nombre de sources inférieur au seuil « soft » ; - fichier non stoppé ; - serveur capable de traiter les grandes tailles si le fichier est > 4 Gio. Valeur : `Opcodes.h:66` ```cpp SERVERREASKTIME = 15 minutes ``` Le commentaire historique dit explicitement de ne pas réduire agressivement cette valeur. ## 3.3 Mise en file des requêtes `DownloadQueue.cpp:1318-1401` Les demandes ne sont pas envoyées immédiatement une par une. eMule : - place les fichiers dans une file locale ; - choisit le fichier ayant attendu le plus longtemps, pondéré par la priorité ; - regroupe jusqu’à **15** demandes dans une même trame TCP ; - espace ensuite le prochain groupe. Après un lot de 15, le code fixe : ```cpp m_dwNextTCPSrcReq = curTick + SEC2MS(15 * (16 + 4)); ``` soit **300 secondes / 5 minutes** avant le prochain lot TCP. Cela distingue deux notions : - intervalle de re-demande d’un fichier : ~15 min ; - débit de vidage de la file globale de requêtes : lots espacés. ## 3.4 Obfuscation Si le serveur le supporte et que l’obfuscation est activée : ```text OP_GETSOURCES_OBFU ``` est utilisé à la place du `OP_GETSOURCES` historique. Référence : `DownloadQueue.cpp:1372-1378` ## 3.5 Réception `ServerSocket.cpp:366-379` Les réponses `OP_FOUNDSOURCES` et `OP_FOUNDSOURCES_OBFU` sont dirigées vers : `CPartFile::AddSources(...)` --- # 4. Canal 2 — autres serveurs eD2K, en UDP ## 4.1 Racine historique eDonkey v0.6 documente : ```text 0x9A UDP Get sources 0x9B UDP Found sources ``` Ce mécanisme permet de demander des sources à des serveurs autres que celui auquel le client est connecté. ## 4.2 eMule 0.72a Cadence globale : `Opcodes.h:67` ```cpp UDPSERVERREASKTIME = 30 minutes ``` Déclenchement : `DownloadQueue.cpp:414-427` ## 4.3 Seuil de sources eMule ne fait une recherche UDP que si le fichier reste sous : `GetMaxSourcePerFileUDP()` Calcul : `PartFile.cpp:5316-5319` ```cpp min(75 % de la limite du fichier, 50) ``` Le `50` vient de : `Opcodes.h:98` ```cpp MAX_SOURCES_FILE_UDP = 50 ``` C’est important : ce n’est pas la limite totale de sources du fichier. C’est un seuil à partir duquel eMule estime que les requêtes UDP serveur sont moins nécessaires. ## 4.4 Protection des serveurs `DownloadQueue.cpp:713-734` Quelques limites : - taille de données utile visée : ~510 octets ; - maximum **35 requêtes** par serveur dans l’intervalle ; - support d’un paquet `GETSOURCES2` permettant d’inclure la taille du fichier et les grands fichiers. Une fois 35 demandes atteintes, eMule fait tourner les fichiers et passe au serveur suivant. --- # 5. Canal 3 — Kad ## 5.1 Déclenchement `PartFile.cpp:2320-2337` Kad est utilisé lorsque : - le fichier est sous le seuil UDP de sources ; - Kad est connecté ; - eMule est connecté ; - le fichier n’est pas stoppé ; - la recherche précédente est suffisamment ancienne. ## 5.2 Nombre simultané `Opcodes.h:72` ```cpp KADEMLIATOTALFILE = 5 ``` eMule limite à cinq le nombre de fichiers dont les sources sont recherchées simultanément via Kad. ## 5.3 Temporisation adaptative déjà présente Le code contient déjà une forme rudimentaire de backoff. `PartFile.cpp:2327-2331` À chaque nouvelle recherche Kad, `m_TotalSearchesKad` augmente jusqu’à 7, puis : ```cpp m_LastSearchTimeKad = maintenant + KADEMLIAREASKTIME * m_TotalSearchesKad; ``` avec : ```cpp KADEMLIAREASKTIME = 1 heure ``` La re-recherche passe donc progressivement à environ : **1 h → 2 h → 3 h → … → 7 h** ## 5.4 Types de sources Kad reçues `DownloadQueue.cpp:1521-1618` Le code différencie plusieurs types : - sources non firewalled ; - sources firewalled utilisant un Buddy ; - sources firewalled supportant un callback UDP direct. Si notre propre client est firewalled, certaines sources firewalled sont rejetées car elles ne seraient pas joignables. ## 5.5 Évolution Kad eMule 0.50a : ```cpp KADEMLIA_VERSION = 9 ``` eMule 0.72a : ```cpp KADEMLIA_VERSION = 10 ``` Cette évolution devra faire l’objet d’un dossier Kad séparé. --- # 6. Canal 4 — Source Exchange ## 6.1 SX1 historique La spécification eDonkey de 2002 documente déjà les extensions eMule : ```text 0x81 eMule Sources request 0x82 eMule Sources answer ``` Le format initial de réponse contient : - IP/ID du client ; - port client ; - IP serveur ; - port serveur. ## 6.2 État dans eMule 0.72a `Opcodes.h:254-257` ```cpp OP_REQUESTSOURCES 0x81 // DEPRECATED OP_ANSWERSOURCES 0x82 // DEPRECATED OP_REQUESTSOURCES2 0x83 OP_ANSWERSOURCES2 0x84 ``` Version SX2 : `Opcodes.h:37` ```cpp SOURCEEXCHANGE2_VERSION = 4 ``` SX1 reste compris pour la compatibilité. ## 6.3 Quand eMule demande des sources ? `DownloadClient.cpp:212-255` Conditions principales : - protocole étendu disponible ; - SX2 ou SX1 > 1 ; - fichier sous son seuil « soft » ; - politique différente selon le nombre de sources. Constantes : ```cpp SOURCECLIENTREASKS = 40 min SOURCECLIENTREASKF = 5 min RARE_FILE = 50 MINCOMMONPENALTY = 4 ``` Le comportement est approximativement : ### Très rare Jusqu’à `RARE_FILE / 5`, soit **10 sources** : - demande permise plus facilement ; - temporisation centrée sur le client. ### Rare Jusqu’à environ **50 sources** : - re-demande client ~40 min ; - fichier ~5 min. ### Fichier courant Les délais sont multipliés par `MINCOMMONPENALTY = 4`. On obtient donc des ordres de grandeur pouvant atteindre : - **160 min** côté client ; - **20 min** côté fichier. La stratégie n’est donc pas « toutes les 10 minutes pour tout ». Le code moderne adapte déjà la sollicitation à la rareté. ## 6.4 Anti-spam côté réception `ListenSocket.cpp:1204-1223` Un client distant ne peut pas nous demander notre liste en boucle. eMule ne répond de nouveau que si : - le fichier est rare et la dernière demande suffisamment ancienne ; - ou le délai long commun a expiré ; - ou c’est la première demande. ## 6.5 Ce qu’eMule transmet `PartFile.cpp:3652-3695` eMule : - ignore les sources LowID ; - ignore les sources invalides ; - essaie de ne transmettre que des sources possédant des parties utiles au demandeur ; - limite la réponse à environ 501 entrées ; - compresse le paquet s’il dépasse 354 octets. ## 6.6 Évolution du format Décodage : `PartFile.cpp:3728-3840` ### Version 1 12 octets/source : ```text ID/IP + port + IP serveur + port serveur ``` ### Version 2 Ajout d’un **UserHash de 16 octets**. ### Version 3 Taille identique à v2, mais évolution de la représentation de l’ID. ### Version 4 Ajout d’un octet de capacités de connexion / cryptage. C’est un bel exemple de compatibilité ascendante construite progressivement. --- # 7. Canal 5 — source passive `ListenSocket.cpp:326-334` et `909-913` C’est un mécanisme particulièrement élégant. Lorsqu’un client distant nous demande un fichier que nous partageons et que ce fichier est **également en cours de téléchargement chez nous**, son simple contact révèle qu’il possède le fichier ou au moins une partie. eMule peut donc l’ajouter comme source sans effectuer une recherche. La source est marquée : ```cpp SF_PASSIVE ``` dans : `DownloadQueue.cpp:621-625` Restriction intéressante : le code évite l’ajout passif pour un fichier d’une seule partie. --- # 8. Canal 6 au sens pratique — lien / source URL Même si l’enum en compte cinq, `SF_LINK` couvre une autre voie logique : - source incluse dans un lien ; - hostname à résoudre ; - source URL. Références : - `DownloadQueue.cpp:1451-1499` - `PartFile.cpp:2488-2514` - `ED2KLink.cpp` Une source URL reçoit : ```cpp SF_LINK ``` --- # 9. Après la découverte : la grande étape de validation `DownloadQueue.cpp:472-555` Une source nouvelle est rejetée notamment si : - elle correspond à notre propre UserHash ; - elle se trouve dans une Dead Source List ; - ses exigences d’obfuscation sont incompatibles ; - elle existe déjà pour le fichier ou pour un autre fichier. Quand elle existe déjà pour un autre fichier, eMule ne crée pas un deuxième objet client : il peut l’associer à la nouvelle demande et utiliser la logique **A4AF**. D’autres filtres sont appliqués plus tôt : - IP invalide ; - IPFilter ; - client banni ; - LowID inutilisable dans notre situation ; - limite maximale de sources. --- # 10. Point important : LowID et utilité réelle d’une source `PartFile.cpp:2368-2404` Le code rejette une source LowID lorsque notre propre client est firewalled. C’est cohérent avec le modèle historique : **LowID ↔ LowID ne peut pas ouvrir une connexion TCP directe classique.** Pour Kad, eMule applique une logique similaire mais sait exploiter : - Buddy ; - callbacks ; - callback UDP direct selon la version/capacité. La modernisation de la connectivité (NAT moderne, CGNAT, IPv6) doit devenir un chantier séparé. Elle ne doit pas être mélangée à l’optimisation de la découverte des sources. --- # 11. Comparaison eMule 0.50a → 0.72a La découverte des sources a remarquablement peu changé dans ses constantes fondamentales. Les valeurs suivantes sont identiques dans 0.50a et 0.72a : | Élément | 0.50a | 0.72a | |---|---:|---:| | Serveur TCP reask | 15 min | 15 min | | Serveurs UDP | 30 min | 30 min | | SX client | 40 min | 40 min | | SX fichier | 5 min | 5 min | | recherches Kad simultanées | 5 | 5 | | base du reask Kad | 1 h | 1 h | | seuil rare | 50 | 50 | | SX2 | v4 | v4 | Cela ne prouve pas qu’elles sont mauvaises. Cela prouve qu’elles méritent un audit car une part importante de cette politique a traversé plus de quinze ans pratiquement inchangée. --- # 12. Comparaison avec aMule aMule confirme une partie de la logique mais apporte une information très importante : **les mêmes protocoles peuvent fonctionner avec des politiques locales différentes.** Dans le corpus aMule : ```cpp SERVERREASKTIME = 800000 ms ≈ 13 min 20 s UDPSERVERREASKTIME = 1300000 ms ≈ 21 min 40 s SOURCECLIENTREASKS = 40 min SOURCECLIENTREASKF = 5 min SOURCEEXCHANGE2_VERSION = 4 RARE_FILE = 50 ``` Valeur par défaut de sources/fichier : - eMule 0.50a / 0.72a : **400** - aMule 3.x : **300** ### Correction importante par rapport à notre toute première ébauche Le `750` trouvé dans eMule **n’est pas la valeur par défaut des sources par fichier**. C’est : ```cpp MAX_SOURCES_FILE_SOFT = 750 ``` c’est-à-dire un **plafond du seuil soft**, calculé à 90 % de la limite configurée. La valeur par défaut réelle dans `Preferences.cpp` est **400**. Pour aMule, la valeur par défaut est **300** et son seuil soft est 90 %, plafonné à 1000. Cette correction illustre exactement pourquoi notre méthode doit toujours distinguer « constante trouvée » et « signification réelle ». --- # 13. Ce qui appartient au protocole et ce qui appartient à la politique locale ## Protocole — à préserver - opcodes serveur et client ; - structure de `OP_GETSOURCES` / `OP_FOUNDSOURCES` ; - formats SX1/SX2 ; - interprétation des versions SX ; - hash ED2K ; - représentation des clients HighID/LowID ; - messages Kad ; - compatibilité des grandes tailles. # 17. Conclusion du dossier 01 Le système de découverte des sources d’eMule est déjà multi-canal et plus sophistiqué qu’une simple interrogation périodique d’un serveur. Sa faiblesse potentielle en 2026 n’est pas l’absence de mécanismes. Au contraire : il possède beaucoup de mécanismes, mais ils sont largement pilotés par des seuils et des temporisations historiques indépendants. > **conserver tous les protocoles existants, instrumenter leur rendement réel, puis coordonner intelligemment leur utilisation.** C’est une modernisation compatible, mesurable et techniquement réaliste. --- --- # Dossier 02 — HighID, LowID, callbacks et joignabilité {#g02} --- # 0. Résumé exécutif Le modèle HighID/LowID d'eD2K est historiquement simple : - le serveur attribue un Client ID ; - un client directement joignable reçoit un HighID dérivé de son IPv4 publique ; - un client que le serveur ne parvient pas à joindre reçoit un LowID ; - un HighID peut joindre un LowID en demandant au serveur de provoquer un callback ; - deux clients réellement non joignables en TCP ne peuvent pas établir une connexion directe entre eux par le seul callback serveur. Mais eMule moderne ajoute deux couches importantes : - **Kad Buddy callback** ; - **Direct UDP Callback**. > **Le couple HighID/LowID ne suffit plus comme modèle interne ou comme diagnostic utilisateur.** - eD2K Client ID ; - TCP entrant ; - UDP entrant ; - état Kad ; - disponibilité d'un Buddy ; - possibilité de callback direct ; - état des redirections de ports. Ce chantier présente surtout des possibilités d'amélioration du diagnostic, de l'instrumentation et de la sélection du meilleur chemin de connexion. Les mécanismes réseau historiques doivent rester compatibles. --- # 1. Le Client ID eD2K ## 1.1 Source historique `protocol_guide.pdf`, section 1.3, définit le Client ID comme un identifiant 32 bits attribué par le serveur lors du handshake. Le document précise : - HighID : identifiant calculé à partir de l'adresse IPv4 publique ; - LowID : identifiant inférieur à `16777216` ; - le LowID n'est significatif que dans le contexte du serveur qui l'a attribué ; - le HighID reste stable tant que l'IPv4 publique ne change pas. La spécification eDonkey v0.6 montre déjà : ```text 0x40 <- ID change ``` dans le dialogue client-serveur. ## 1.2 eMule 0.72a `OtherFunctions.h:482-485` ```cpp inline bool IsLowID(uint32 id) { return id < 16777216u; } ``` Soit : ```text 0x01000000 16 777 216 2^24 ``` ### Petite anomalie documentaire trouvée Le commentaire de `OtherFunctions.h` écrit : ```cpp 0x01000000u or 2^25 ``` mais `0x01000000` vaut **2^24**, pas 2^25. Le code est cohérent avec la documentation historique ; c'est seulement le commentaire qui est faux. Cela n'a aucun impact protocolaire. ## 1.3 aMule `NetworkFunctions.h:123-129` ```cpp #define HIGHEST_LOWID_ED2K_KAD 16777216 inline bool IsLowID(uint32 id) { return (id < HIGHEST_LOWID_ED2K_KAD); } ``` Confirmation indépendante du seuil. --- # 2. Comment le Client ID est reçu ## eMule 0.72a `ServerSocket.cpp:307-351` Lors de `OP_IDCHANGE` : - le serveur peut fournir le Client ID ; - les versions récentes du paquet peuvent aussi fournir l'IP publique observée ; - si le Client ID n'est pas LowID, eMule l'utilise comme IP publique ; - si le Client ID est LowID mais que le serveur donne séparément l'adresse publique, eMule conserve cette dernière. `ServerConnect.cpp:493-500` ```cpp void CServerConnect::SetClientID(uint32 newid) { m_clientid = newid; if (!::IsLowID(newid)) theApp.SetPublicIP(newid); } ``` Cela confirme que le HighID sert aussi de représentation historique de l'IPv4 publique. --- # 3. Joignabilité globale dans eMule 0.72a `Emule.cpp:1056-1065` ```cpp bool CemuleApp::IsFirewalled() { if (eD2K connecté && HighID) return false; if (Kad connecté && Kad non firewalled) return false; return true; } ``` C'est une information très importante. L'état global `IsFirewalled()` d'eMule n'est **pas strictement synonyme de LowID eD2K**. Un client peut théoriquement : - être LowID sur son serveur eD2K ; - mais être considéré joignable grâce à l'état Kad. --- # 4. Le callback serveur eD2K ## 4.1 Protocole historique eDonkey v0.6 : ```text 0x1C Client callback request -> serveur 0x35 <- Server callback request 0x36 <- Callback fail ``` eMule 0.72a `Opcodes.h` : ```cpp OP_CALLBACKREQUEST 0x1C // OP_CALLBACKREQUESTED 0x35 // OP_CALLBACK_FAIL 0x36 ``` ## 4.2 Séquence Client A est joignable, B est LowID. ```text A serveur B OP_CALLBACKREQUEST (ID de B) -----------> OP_CALLBACKREQUESTED (IP+port de A) -----------> <----------------------------------------------- B ouvre une connexion TCP vers A ``` Le serveur ne relaie pas les données. Il relaie seulement la demande de connexion. ## 4.3 Condition essentielle Pour que le mécanisme fonctionne, **A doit pouvoir recevoir la connexion TCP que B va ouvrir**. C'est pourquoi le callback classique ne résout pas le cas de deux clients réellement TCP-firewalled. --- # 5. Les sept chemins de `TryToConnect` Le commentaire de `BaseClient.cpp:1241-1264` dans eMule 0.72a est une documentation exceptionnellement utile. Le code distingue explicitement sept situations : 1. déjà connecté / connexion en cours ; 2. échec immédiat ; 3. connexion TCP sortante directe ; 4. callback UDP direct ; 5. attente volontaire ; 6. callback serveur ; 7. callback Kad. --- # 6. Chemin 3 — connexion TCP directe `BaseClient.cpp:1385-1399` Si le client n'a pas de LowID : ```cpp if (!HasLowID() ...) { ... Connect(); } ``` `Connect()` utilise : ```cpp sockaddr_in AF_INET GetUserPort() GetConnectIP() ``` Le chemin protocolaire client-à-client analysé ici reste donc centré sur IPv4. ### Nuance La bibliothèque `AsyncSocketEx` incluse dans la branche 0.72a contient du support `AF_INET6`. Cela ne prouve toutefois pas que le protocole eD2K/Kad de cette branche transporte des adresses IPv6 de bout en bout. Les structures protocole étudiées utilisent encore largement des adresses 32 bits. **Conclusion : IPv6 doit être traité comme un chantier de recherche séparé et non comme un quick win.** --- # 7. Détection du cas LowID -> LowID `BaseClient.cpp:1342-1353` Avant tout callback, eMule appelle : ```cpp theApp.CanDoCallback(this) ``` En cas d'impossibilité : ```cpp Disconnected("LowID->LowID") ``` `DownloadClient.cpp:190-204` est toutefois plus subtil. Si un LowID déjà connu ne peut pas être rappelé, le client n'est pas nécessairement supprimé immédiatement : ```cpp DS_LOWTOLOWIP m_bReaskPending = true ``` Le commentaire explique que le client peut être conservé dans l'espoir que l'un des deux obtienne ensuite un HighID. C'est une politique de résilience intéressante à conserver. --- # 9. Chemin 4 — Direct UDP Callback ## 9.1 Principe Le client distant est TCP firewalled mais son UDP est joignable. Au lieu d'utiliser un serveur ou un Buddy : ```text A ---- UDP "connecte-toi à moi" ----> B A <========== TCP ouvert par B ======== ``` ## 9.2 Conditions d'annonce `OtherFunctions.cpp:4037-4052` Le bit « Direct Callback » est annoncé uniquement lorsque : - la fonction callback est autorisée ; - notre client est firewalled au sens TCP ; - Kad fonctionne ; - l'UDP n'est pas firewalled ; - l'état UDP a été vérifié. Commentaire source : ```cpp direct callback is only possible if connected to kad, TCP firewalled and verified UDP open ``` ## 9.3 Paquet `Opcodes.h` ```cpp OP_DIRECTCALLBACKREQ = 0x95 ``` Format : ```text ``` `BaseClient.cpp:1402-1417` construit et envoie ce paquet directement à l'IP/port Kad du pair. # 10. Chemin 6 — callback serveur `BaseClient.cpp:1429-1437` Condition : la source LowID est enregistrée sur le serveur auquel nous sommes actuellement connectés. eMule envoie : ```cpp OP_CALLBACKREQUEST ``` avec le LowID du client distant. Ce mécanisme dépend donc explicitement : - du serveur ; - de la session du client LowID avec ce serveur ; - du fait que notre propre client puisse recevoir la connexion de retour. --- # 11. Chemin 7 — callback Kad / Buddy ## 11.1 Publication d'une source firewalled `kademlia/kademlia/Search.cpp:638-677` eMule distingue plusieurs types de sources Kad : ```text 1 = source HighID 3 = source Kad firewalled 4 = >4GB HighID 5 = >4GB firewalled 6 = firewalled avec Direct Callback ``` Pour une source firewalled sans Direct Callback, le client doit disposer d'un Buddy. Il publie notamment : - IP du Buddy ; - port UDP du Buddy ; - Buddy ID ; - son propre port TCP. ## 11.2 Callback `BaseClient.cpp:1439-1473` Le demandeur envoie au Buddy : ```cpp KADEMLIA_CALLBACK_REQ ``` Le Buddy fait parvenir la demande au client firewalled. Le client firewalled ouvre ensuite la connexion TCP vers le demandeur. ## 11.3 Ce que le Buddy n'est pas Le Buddy n'est pas un proxy de téléchargement. Il ne transporte normalement pas le fichier. Son rôle est comparable à une boîte aux lettres permettant de réveiller ou joindre le client firewalled. --- # 12. Vérification du firewall UDP `kademlia/kademlia/UDPFirewallTester.cpp` eMule 0.72a utilise plusieurs pairs pour tester si son port UDP est joignable. Constante : ```cpp UDP_FIREWALLTEST_CLIENTSTOASK = 2 ``` Deux tests sont utilisés afin de réduire le risque d'une conclusion erronée. Le code gère notamment : - état UDP ouvert ; - état UDP firewalled ; - timeout ; - différence entre port interne et port externe observé. --- # 13. Confirmation indépendante par aMule aMule utilise le même seuil LowID : `NetworkFunctions.h` ```cpp HIGHEST_LOWID_ED2K_KAD = 16777216 ``` Son `BaseClient.cpp` contient également : - connexion directe ; - détection LowID -> LowID ; - Direct UDP Callback ; - callback serveur ; - Buddy/Kad callback. Les détails d'implémentation diffèrent mais l'architecture générale est la même. Cela confirme qu'il s'agit de comportements d'interopérabilité et pas uniquement d'une particularité graphique d'eMule Windows. --- # 14. Apport du wiki MLDonkey La page historique `LowID.html` explique clairement le modèle eD2K : - HighID = IPv4 du pair dans la représentation du protocole ; - LowID = identifiant opaque attribué par le serveur ; - HighID -> LowID = callback serveur ; - LowID -> LowID = impossible dans le modèle eD2K classique. Elle ajoute aussi une remarque historique intéressante : Overnet utilisait une idée comparable sans serveur central en choisissant un pair non firewalled comme **Buddy** pour faire suivre les demandes de connexion. C'est un excellent chaînon historique vers Kad. --- # 15. HighID et performance : éviter un mauvais diagnostic HighID n'est pas directement un réglage de vitesse. Il améliore la **connectivité**. L'effet sur les performances est donc indirect : ```text meilleure joignabilité ↓ davantage de combinaisons de pairs possibles ↓ davantage de chances de trouver / maintenir une source utile ``` Cela signifie qu'un LowID peut encore télécharger vite sur un fichier très populaire. À l'inverse, sur un fichier très rare, perdre ne serait-ce qu'une source parce que les deux extrémités sont non joignables peut être déterminant. Le futur site doit éviter le raccourci « HighID = téléchargement rapide ». --- # 16. Source Exchange et LowID Rappel du dossier 01 : `PartFile.cpp:3655` eMule 0.72a évite d'envoyer les sources LowID dans la réponse Source Exchange classique. La joignabilité influence donc non seulement la connexion à une source déjà connue, mais aussi sa **propagation dans le réseau de découverte**. C'est une raison supplémentaire pour intégrer les données de joignabilité dans le futur Source Discovery Manager. --- # 17. UPnP dans eMule 0.72a La branche contient deux implémentations UPnP principales : - Windows Service UPnP ; - MiniUPnP. Fichiers : ```text UPnPImpl.cpp UPnPImplWinServ.cpp UPnPImplMiniLib.cpp UPnPImplWrapper.cpp ``` Le code sait : - découvrir un gateway UPnP ; - créer des port mappings TCP/UDP ; - vérifier/rafraîchir les mappings ; - supprimer les mappings existants. Cela représente le mécanisme automatique actuel pour tenter d'obtenir une bonne joignabilité derrière une box compatible. --- # 20. Classification protocole / politique locale ## À ne pas casser - définition du Client ID ; - seuil et représentation LowID/HighID ; - `OP_CALLBACKREQUEST` ; - `OP_CALLBACKREQUESTED` ; - messages Kad callback ; - `OP_DIRECTCALLBACKREQ` ; - formats des options de connexion ; - règles nécessaires à l'interopérabilité. # 23. Une correction documentaire utile dès maintenant `OtherFunctions.h` de la branche 0.72a contient : ```cpp return id < 16777216u; //0x01000000u or 2^25 ``` La valeur et le test sont corrects, mais : ```text 0x01000000 = 2^24 ``` La mention `2^25` est donc une simple erreur de commentaire. - aucun changement fonctionnel ; - documentation du source plus exacte. --- # 24. Conclusion HighID/LowID paraît être un vieux concept simple. En réalité, il ouvre une porte sur toute l'évolution de la connectivité eMule : ```text eDonkey callback serveur ↓ Overnet Buddy sans serveur ↓ Kad/eMule Buddy + état firewall ↓ eMule moderne Direct UDP Callback ``` La piste est de comprendre précisément **pourquoi un client est ou n'est pas joignable**, de mesurer les différents chemins existants et de choisir/intégrer ces informations beaucoup mieux que ne le fait l'interface historique. --- --- # Dossier 03 — Identité, UserHash, Secure Identification et crédits {#g03} --- # 0. Résumé exécutif Ce dossier clarifie quatre identifiants qui sont très souvent confondus : ```text Client ID eD2K : 32 bits, fourni par le serveur UserHash : 128 bits, persistant, propre au client Kad ID : 128 bits, persistant, position dans Kad File Hash : 128 bits, identité du contenu ``` > **le système de crédits n’est pas un système global et le UserHash n’est pas une preuve d’identité.** Le UserHash sert de clé persistante pour retrouver une entrée locale de crédit. Le Secure User Identification ajoute la preuve cryptographique que le pair possède bien la clé privée associée à cette identité. 1. **le défi SecureIdent est toujours généré avec `rand()+1` dans la branche 0.72a**, exactement comme dans 0.50a ; on peut le remplacer localement par un générateur cryptographiquement sûr sans modifier le protocole ; 2. **le système de crédits est une politique locale**, puisque eMule et aMule appliquent des calculs légèrement différents tout en restant interopérables ; 3. l’utilisateur ne dispose aujourd’hui d’aucune vision claire de la santé de son identité locale (`preferences.dat`, `cryptkey.dat`, `clients.met`, `preferencesKad.dat`). Un futur module **Identity Health** est donc une piste réaliste et compatible. --- # 1. Client ID eD2K Le dossier 02 a déjà couvert le Client ID. Rappel : ```text taille : 32 bits source : serveur eD2K usage : session / joignabilité ``` La vieille spécification eDonkey expose déjà le changement d’ID via le serveur. Le *Protocol Guide* rappelle qu’un Client ID n’est valable que pour une session client-serveur, même si un HighID conserve normalement la même valeur tant que l’IPv4 publique reste identique. Conclusion : **le Client ID n’est pas une identité durable d’utilisateur.** --- # 2. UserHash : identité persistante eMule ## 2.1 Taille et stockage `Preferences.cpp` ```cpp uchar CPreferences::userhash[MDX_DIGEST_SIZE]; ``` `MDX_DIGEST_SIZE = 16`. Le UserHash est donc une valeur de : ```text 128 bits 16 octets ``` Il est enregistré dans : ```text preferences.dat ``` L’aide officielle confirme que `preferences.dat` sauvegarde le UserHash utilisé pour identifier le client dans le réseau, notamment pour les crédits et les amis. ## 2.2 Ce n’est pas réellement un « hash » Nom historique trompeur. `Preferences.cpp:1484-1493` ```cpp while (isbadhash(userhash)) { CryptoPP::AutoSeededRandomPool rng; rng.GenerateBlock(userhash, sizeof userhash); } userhash[5] = 14; userhash[14] = 111; ``` Il n’est donc pas obtenu en appliquant MD4 à une donnée personnelle. C’est essentiellement : ```text 128 bits aléatoires + deux octets marqueurs eMule ``` La documentation de 2005 décrit elle aussi l’User ID comme 16 octets aléatoires avec les sixième et quinzième octets fixés respectivement à 14 et 111. ### Conséquence éditoriale Sur le site, nous devons conserver le terme historique **UserHash**, mais expliquer qu’il s’agit plutôt d’un identifiant aléatoire persistant. ## 2.3 Marqueurs historiques `BaseClient.cpp:1768-1776` ```cpp [5] = 13, [14] = 110 -> SO_OLDEMULE [5] = 14, [14] = 111 -> SO_EMULE [5] = 'M', [14] = 'L' -> SO_MLDONKEY ``` Le UserHash transporte donc historiquement un petit marqueur de famille de client. Ce n’est pas une authentification. --- # 3. Quand le UserHash est reçu `BaseClient.cpp:359-375` Lors du hello client-à-client : ```cpp data.ReadHash16(m_achUserHash); m_nUserIDHybrid = data.ReadUInt32(); nUserPort = data.ReadUInt16(); ``` Un même hello fournit donc : ```text UserHash 128 bits Client ID 32 bits Port TCP ``` Les deux identités coexistent directement dans le protocole. ## Association aux crédits `BaseClient.cpp:611` ```cpp CClientCredits *pFoundCredits = theApp.clientcredits->GetCredit(m_achUserHash); ``` Le UserHash est directement utilisé comme clé de recherche de l’entrée de crédit. --- # 4. Kad ID ## 4.1 Génération `kademlia/kademlia/Prefs.cpp:64-77` ```cpp m_uClientID.SetValueRandom(); ... m_uClientHash = CUInt128(thePrefs.GetUserHash()); ``` Puis le code tente de charger le Kad ID depuis : ```text preferencesKad.dat ``` `Prefs.cpp:104-109` ```cpp file.ReadUInt128(m_uClientID); if (m_uClientID == 0) m_uClientID.SetValueRandom(); ``` Ainsi : ```text Kad ID = valeur 128 bits propre à Kad UserHash = valeur 128 bits eMule distincte ``` Le code conserve aussi une copie du UserHash dans `m_uClientHash`, mais cela ne rend pas les deux identifiants équivalents. ## 4.2 aMule confirme Le corpus aMule applique le même modèle : ```cpp m_clientID = GetRandomUint128(); m_clientHash.SetValueBE(thePrefs::GetUserHash().GetHash()); ``` et recharge `m_clientID` depuis `preferencesKad.dat`. Confirmation indépendante de l’architecture. --- # 5. Le fichier hash : quatrième notion Le protocole eDonkey v0.6 définit : ```text ::= HASH HASH ::= 16 bytes MD4 digest ``` Le fichier est identifié par le contenu, tandis que UserHash et Kad ID identifient des entités clientes/nœuds. Il est essentiel que le site ne mélange jamais : ```text UserHash ≠ File Hash ≠ Kad ID ``` même s’ils sont tous représentables sur 16 octets. --- # 6. Architecture réelle du système de crédits ## 6.1 Structure locale `ClientCredits.h` Chaque entrée contient : ```cpp abyKey[16] // UserHash distant nUploaded... nDownloaded... tLastSeen nKeySize abySecureIdent[80] // clé publique distante ``` L’entrée de crédit est donc locale et indexée sur le UserHash du pair. ## 6.2 Sémantique des compteurs Attention aux noms vus du point de vue du programme local : ```text GetDownloadedTotal() = données que NOUS avons téléchargées depuis ce pair = données que CE PAIR nous a uploadées GetUploadedTotal() = données que NOUS avons uploadées vers ce pair = données que CE PAIR a téléchargées depuis nous ``` C’est la raison pour laquelle la formule officielle, décrite du point de vue du pair bénéficiaire, semble parfois inversée par rapport aux noms de fonctions. ## 6.3 Crédits non globaux L’aide officielle dit explicitement : - les crédits sont entre deux clients spécifiques ; - le client qui reçoit les données récompense celui qui lui envoie ; - vos propres crédits sont sauvegardés par le client qui vous les doit ; - ils ne peuvent donc pas être « affichés globalement ». Il n’existe aucun compteur central. --- # 7. clients.met `ClientCredits.cpp` Nom : ```cpp CLIENTS_MET_FILENAME = "clients.met" ``` Le fichier contient les entrées de crédits locales. ## Sauvegarde `ClientCredits.cpp:283-286` ```cpp if (GetTickCount() >= m_nLastSaved + MIN2MS(13)) SaveList(); ``` Sauvegarde périodique d’environ : ```text 13 minutes ``` ## Backup Au chargement, eMule entretient aussi : ```text clients.met.bak ``` en évitant d’écraser un backup plus gros par un fichier potentiellement endommagé. ## Expiration `ClientCredits.cpp:207` ```cpp const time_t tExpired = time(NULL) - DAY2S(150); ``` Une entrée non revue depuis environ : ```text 150 jours ``` est ignorée lors du chargement. Cette valeur était déjà présente dans eMule 0.50a. Donc : > **150 jours est une politique historique du client, pas une contrainte du protocole.** --- # 8. Calcul du multiplicateur de crédit eMule ## 8.1 Conditions d’identité `ClientCredits.cpp:97-107` Si SecureIdent est disponible mais que le pair est : ```text IS_IDFAILED IS_IDBADGUY IS_IDNEEDED ``` alors : ```cpp return 1.0f; ``` Donc aucun bonus de crédit tant que l’identité sécurisée requise n’est pas validée. Les fonctions `AddDownloaded` et `AddUploaded` appliquent une logique similaire et peuvent refuser de mettre à jour les compteurs. ## 8.2 Seuil minimal eMule 0.72a : ```cpp if (GetDownloadedTotal() < 1048576) return 1.0f; ``` Le pair doit donc nous avoir fourni au moins : ```text 1 MiB ``` avant qu’un bonus > 1 soit possible. ## 8.3 Formule 1 ```cpp result = GetDownloadedTotal() * 2 / GetUploadedTotal(); ``` Du point de vue du pair : ```text 2 × données qu’il nous a envoyées / données qu’il a reçues de nous ``` Si nous ne lui avons encore rien envoyé : ```text result = 10 ``` ## 8.4 Formule 2 ```cpp sqrt(GetDownloadedTotal()/1MiB + 2) ``` Elle empêche d’obtenir immédiatement un énorme multiplicateur après un échange minuscule. ## 8.5 Formule 3 — souvent absente de la documentation publique eMule 0.50a ET 0.72a possèdent une troisième limitation : ```cpp if (GetDownloadedTotal() < 9646899) result3 = (GetDownloadedTotal() - 1048576) / 8598323 * 2.34 + 1; else result3 = 10; ``` Le commentaire indique : ```text 1 MB -> ~1.01 9.2MB -> ~3.34 ``` Puis : ```cpp result = min(result, min(result2, result3)); ``` Enfin : ```text minimum = 1 maximum = 10 ``` ### Découverte documentaire importante L’aide officielle eMule et le guide de protocole résument essentiellement **deux** formules. Le code officiel 0.50a contient déjà **trois limites**. La documentation publique est donc simplifiée/incomplète sur ce point. > **pour les détails comportementaux, le source fait autorité sur la vieille documentation.** --- # 9. aMule confirme que le calcul des crédits est une politique locale Dans le corpus aMule : ```cpp if (GetDownloadedTotal() < 1000000) return 1.0f; ``` Puis : ```cpp result = 2 * downloaded / uploaded result2 = sqrt(downloaded / 1048576 + 2) result = min(result, result2) result borné entre 1 et 10 ``` Pas de troisième rampe linéaire `result3` observée dans cette implémentation. Différence supplémentaire : ```text eMule : 1 048 576 octets aMule : 1 000 000 octets ``` Les deux clients restent pourtant interopérables. # 10. Application du crédit à la file d’upload `UploadClient.cpp:176-229` Le score local utilise notamment : ```text temps d’attente × multiplicateur de crédit × priorité du fichier ``` Code : ```cpp if (thePrefs.UseCreditSystem()) fBaseValue *= credits->GetScoreRatio(GetIP()); fBaseValue *= GetFilePrioAsNumber() / 10; ``` Le système de crédits : - ne contrôle pas le débit brut du slot ; - modifie surtout la vitesse de progression dans la file. Il existe ensuite d’autres règles : bannissement, friend slot, ancien client, LowID hors connexion, etc. --- # 11. Secure User Identification : architecture ## 11.1 Motivation Un UserHash est envoyé en clair lors du hello client. Sans protection, un client malveillant pourrait recopier le UserHash d’un utilisateur ayant des crédits. SecureIdent transforme donc : ```text « je connais cet identifiant » ``` en : ```text « je peux prouver que je possède la clé privée associée à cet identifiant » ``` --- # 12. Clé privée `ClientCredits.cpp:348-409` Fichier : ```text cryptkey.dat ``` Si absent ou vide : ```cpp CreateKeyPair(); ``` Génération : ```cpp AutoSeededRandomPool rng; InvertibleRSAFunction privkey; privkey.Initialize(rng, RSAKEYSIZE); ``` avec : ```cpp RSAKEYSIZE = 384 ``` La clé privée est encodée puis enregistrée dans `cryptkey.dat`. --- # 13. Clé publique Au démarrage : ```cpp RSASSA_PKCS1v15_SHA_Verifier pubkey(*m_pSignkey); pubkey.GetMaterial().Save(...) ``` eMule dérive sa clé publique de la clé privée. La clé publique du pair distant est stockée dans l’entrée `clients.met` : ```cpp nKeySize abySecureIdent[MAXPUBKEYSIZE] ``` `MAXPUBKEYSIZE = 80`. --- # 14. Les trois opcodes SecureIdent `Opcodes.h` ```text 0x85 OP_PUBLICKEY 0x86 OP_SIGNATURE 0x87 OP_SECIDENTSTATE ``` Ils existaient déjà dans eMule 0.50a et sont toujours présents dans 0.72a. --- # 15. Challenge-response `BaseClient.cpp:1979-2005` eMule décide si : ```text clé + signature nécessaires ou signature seulement ``` Puis crée un challenge 32 bits et envoie : ```text OP_SECIDENTSTATE ``` Le pair signe ensuite un ensemble qui contient notamment : - notre clé publique ; - le challenge ; - éventuellement une IP et un indicateur de type de challenge pour SecureIdent v2. La signature est vérifiée à l’aide de la clé publique du pair. --- # 16. SecureIdent v1 / v2 `BaseClient.cpp:1860-1877` Le client choisit v1 par défaut sauf lorsque le pair impose v2. v2 ajoute une notion d’IP au contenu signé. `ClientCredits.cpp:470-495` Le vérificateur traite : ```text CRYPT_CIP_LOCALCLIENT CRYPT_CIP_REMOTECLIENT CRYPT_CIP_NONECLIENT ``` Le but est de mieux lier la preuve à la situation de connexion et de réduire la réutilisation d’une signature dans un autre contexte. --- # 18. `GetRandomUInt32()` n’est pas une solution suffisante La branche possède : ```cpp uint32 GetRandomUInt32() ``` mais son implémentation combine plusieurs appels à : ```cpp rand() ``` et reseed ensuite `srand()` avec `GetTickCount()`. Ce n’est donc pas un CSPRNG. Pour SecureIdent, remplacer simplement : ```cpp rand()+1 ``` par : ```cpp GetRandomUInt32() ``` améliorerait éventuellement l’espace de sortie, mais ne constituerait pas un durcissement cryptographique satisfaisant. La bonne solution est d’utiliser le générateur cryptographique déjà présent. --- # 19. RSA 384 et SHA-1 La classe utilisée est : ```cpp RSASSA_PKCS1v15_SHA_Signer RSASSA_PKCS1v15_SHA_Verifier ``` avec : ```text RSA 384 bits ``` Il s’agit de paramètres cryptographiques historiques très faibles au regard des standards modernes. ### Mais attention Ils sont intégrés au protocole d’interopérabilité. Changer unilatéralement : - la taille RSA ; - le format de clé ; - l’algorithme de signature ; - la taille maximale de clé publique ; ### Stratégie raisonnable **Court terme :** - durcir ce qui n’affecte pas le wire format ; - vérifier les entrées ; - conserver les protections anti-flood ; - utiliser un challenge CSPRNG. **Long terme :** étudier éventuellement une extension SecureIdent nouvelle **négociée** entre clients compatibles, tout en maintenant v1/v2 pour le réseau existant. --- # 20. Perte de cryptkey.dat L’aide officielle dit clairement : - `cryptkey.dat` contient la clé privée ; - perdre cette clé empêche de prouver les crédits déjà connus par les pairs ; - conserver l’ancien UserHash avec une nouvelle clé provoque une incohérence ; - en cas de perte définitive, il faut aussi repartir avec un nouveau UserHash. Le code confirme cela. Lorsqu’une nouvelle clé publique est attachée pour la première fois à une entrée jusque-là non sécurisée : `ClientCredits.cpp:305-320` les anciens compteurs sont remis à une valeur minimale : ```cpp downloaded = 1 uploaded = 1 ``` afin de ne pas migrer aveuglément des crédits non sécurisés vers une nouvelle identité cryptographique. --- # 23. Réparation automatique prudente ```text preferences.dat présent ? UserHash valide ? cryptkey.dat présent ? SecureIdent activé ? clé privée décodable ? clé publique dérivable ? preferencesKad.dat valide ? Kad ID nul ? clients.met lisible ? backup clients.met meilleur que le fichier principal ? ``` Puis présenter un diagnostic. Il ne faut surtout pas régénérer silencieusement une identité valide simplement parce qu’un fichier est temporairement inaccessible. La priorité doit être : ```text détecter → expliquer → sauvegarder → proposer une réparation ``` et non : ```text écraser et recréer ``` --- # 24. Politique de crédits : chantier séparé Les différences eMule/aMule montrent qu’on peut techniquement modifier la politique locale. Mais une modification des crédits affecte : - l’équité de la file ; - l’incitation au partage ; - le comportement vis-à-vis de clients anciens ; - l’écosystème social du réseau. ```text ratio moyen des clients en queue distribution des multiplicateurs temps gagné grâce au crédit pourcentage de clients identifiés crédits expirant sans jamais être réutilisés ``` Puis comparer plusieurs politiques en simulation. --- # 26. Protection des données locales Le UserHash et les clés ne sont pas des mots de passe de compte central. Mais ils représentent une identité réseau persistante et des droits accumulés localement chez d’autres pairs. - éviter de les copier dans les logs publics ; - masquer les hashes complets dans les captures diagnostiques par défaut ; - distinguer clairement clé publique et clé privée ; - ne jamais exporter `cryptkey.dat` sans action explicite de l’utilisateur. --- # 27. Classification protocole / politique / stockage ## Protocole / compatibilité ```text UserHash 16 octets dans les hello OP_PUBLICKEY OP_SIGNATURE OP_SECIDENTSTATE format SecureIdent v1/v2 taille/champ des messages ``` ## Identité locale historique ```text preferences.dat cryptkey.dat preferencesKad.dat ``` ## Politique locale ```text formule de crédit expiration à 150 jours fréquence de sauvegarde usage exact du bonus diagnostics ``` # 29. Conclusion > **préserver les identités et les formats historiques ne signifie pas conserver toutes les implémentations internes de 2002.** Le UserHash, les opcodes et les formats SecureIdent sont des éléments d’interopérabilité. Mais : - la génération du challenge ; - le diagnostic ; - la sauvegarde ; - la validation des fichiers ; - le calcul local des crédits ; sont des domaines où le logiciel peut évoluer. La première modification concrète issue de notre étude est donc déjà identifiable : > **remplacer le challenge SecureIdent généré par `rand()+1` par un vrai nombre aléatoire cryptographique de 32 bits, sans changer un seul octet du protocole.** --- --- # Dossier 04 — File d’upload, Queue Rank, slots et ordonnancement {#g04} --- # 0. Résumé exécutif ## Le protocole impose principalement ```text OP_STARTUPLOADREQ OP_ACCEPTUPLOADREQ OP_OUTOFPARTREQS OP_REQUESTPARTS OP_SENDINGPART OP_QUEUERANK / OP_QUEUERANKING ``` ## Le client décide localement ```text comment calculer le score comment classer la file combien de slots ouvrir quand faire tourner un slot quelle priorité donner aux fichiers quelle taille maximale donner à la file comment distribuer la bande passante entre les slots ``` La file d’upload est donc un excellent terrain de modernisation **sans modification du wire protocol**. 1. **Queue Observatory** : rendre mesurable et explicable chaque décision d’ordonnancement ; 2. **corriger le vieux problème “priorité du fichier utilisé pour le score ≠ fichier finalement demandé”**, explicitement marqué TODO depuis au moins eMule 0.50a et toujours présent dans 0.72a ; 3. **auditer l’algorithme d’ouverture et d’alimentation des slots avec des mesures modernes**, plutôt que modifier arbitrairement les constantes. --- # 1. Racine protocolaire `edonkey-protocol.txt` documente déjà les opérations : ```text 0x54 Slot request 0x55 Slot given 0x56 Slot release 0x57 Slot taken 0x47 Request parts 0x46 Sending part 0x49 End of download ``` Dans eMule 0.72a, `Opcodes.h` conserve notamment : ```cpp OP_REQUESTPARTS 0x47 OP_STARTUPLOADREQ 0x54 OP_ACCEPTUPLOADREQ 0x55 OP_OUTOFPARTREQS 0x57 OP_QUEUERANK 0x5C OP_QUEUERANKING 0x60 ``` La file d’attente elle-même et le score ne sont donc pas imposés par ces paquets. --- # 2. Entrée dans la file ## 2.1 Réception de la demande `ListenSocket.cpp:465-480` À la réception de : ```cpp OP_STARTUPLOADREQ ``` eMule : 1. vérifie que le handshake est terminé ; 2. cherche le fichier partagé correspondant au hash ; 3. enregistre ce fichier comme fichier demandé ; 4. appelle : ```cpp theApp.uploadqueue->AddClientToQueue(client); ``` --- # 3. `AddClientToQueue` : beaucoup plus qu’un simple push_back `UploadQueue.cpp:501-654` Cette fonction applique déjà une politique importante. ## 3.1 Limitation liée aux callbacks LowID Le bloc `514-523` refuse certains clients quand : - notre eMule est lui-même firewalled ; - le demandeur n’a pas de port Kad ; - il n’est ni une source actuelle ni un ami ; - il n’est pas sur notre serveur ; - plus de 50 clients sont déjà en attente. Le commentaire précise que le but est de limiter l’abus des callbacks LowID. ## 3.2 Bannis / fréquence de requêtes Le client : ```cpp IncrementAskedCount(); SetLastUpRequest(); AddRequestCount(...) ``` puis les clients bannis sont rejetés. Le mécanisme anti-request abuse est donc déjà lié à l’entrée en queue. ## 3.3 Collision / doublon de UserHash `UploadQueue.cpp:533-580` Si un client existe déjà : - SecureIdent est utilisé pour départager les entrées ; - un client identifié peut être conservé face à un doublon non identifié ; - si aucun des deux n’est identifiable, les entrées ambiguës peuvent être rejetées. Lien direct avec le dossier 03. ## 3.4 Limite par IP `UploadQueue.cpp:581-595` La file évite normalement plus de trois clients depuis une même IP / liste de clients suivis. Il s’agit clairement d’une politique anti-abus locale. --- # 4. File souple et file dure `UploadQueue.cpp:617-633` ```cpp softQueueLimit = thePrefs.GetQueueSize(); hardQueueLimit = softQueueLimit + max(softQueueLimit, 800) / 4; ``` Avec la valeur par défaut historique : ```text soft = 5000 hard = 6250 ``` ## Entre soft et hard Un client standard n’est encore accepté que si : ```cpp GetCombinedFilePrioAndCredit() >= moyenne de la file ``` ou s’il dispose d’un Friend Slot. Cette fonction combine : ```cpp 10 * priorité fichier * crédit ``` Référence : `UploadClient.cpp:137-145` # 5. Cas spéciaux contournant la file ## 5.1 Collection Priority Slot `UploadQueue.cpp:605-613` Une petite collection eMule : ```text extension collection taille < 50 KiB ``` peut contourner la file et obtenir un slot direct si le client est connecté. Constante : `Opcodes.h:129` ```cpp MAXPRIORITYCOLL_SIZE = 50 * 1024 ``` ## 5.2 Friend Slot Dans `GetScore()` : ```cpp if (IsFriend() && GetFriendSlot() && !HasLowID()) return 0x0FFFFFFF; ``` Il reçoit donc un score pratiquement imbattable. --- # 6. Le score d’attente eMule 0.72a `UploadClient.cpp:180-229` Conditions préliminaires : - username présent ; - crédits disponibles ; - fichier demandé encore partagé ; - client non `IS_IDBADGUY` ; - client non banni ; - LowID non connecté exclu lorsque `sysvalue=true`. ## Formule normale ```cpp dwBaseValue = GetTickCount() - GetWaitStartTime(); score_seconds = dwBaseValue / 1000; if (CreditSystem) score *= CreditRatio; score *= FilePriority / 10; ``` Résumé : ```text score = temps d’attente en secondes × crédit × coefficient fichier ``` --- # 9. Un TODO historique très intéressant Dans eMule 0.50a : `UploadClient.cpp:161-164` et encore dans eMule 0.72a : `UploadClient.cpp:156-159` le même commentaire apparaît : > un client peut demander deux fichiers ; il peut être classé grâce à la priorité élevée d’un fichier puis demander finalement quelque chose de complètement différent. C’est donc un problème connu depuis longtemps et resté explicitement non résolu. ## Pourquoi c’est important Le score est calculé à partir de : ```cpp requpfileid ``` au moment de l’évaluation. Mais une fois le slot attribué, les demandes de blocs peuvent amener le client à utiliser un autre fichier. `UploadQueue.cpp:324-343` vérifie même le FileID du premier bloc demandé et, s’il correspond à un autre fichier partagé, fait : ```cpp cur_client->SetUploadFileID(pCurrentUploadFile); ``` Le changement de fichier pendant une session normale est donc prévu. ## Conséquence possible Un client peut, dans certaines séquences valides : ```text attendre/classer avec fichier A très prioritaire ↓ obtenir le slot ↓ demander des blocs du fichier B moins prioritaire ``` Cela ne prouve pas une exploitation volontaire. Mais cela signifie que **la politique de priorité n’est pas strictement attachée à la ressource effectivement servie**. --- # 11. Classement et Queue Rank ## 11.1 Sélection du meilleur client `UploadQueue.cpp:106-153` `FindBestClientInQueue()` parcourt la file et choisit le score maximal. Les LowID non connectés ne peuvent pas recevoir immédiatement le slot. Cependant, si le meilleur score appartient à un LowID non connecté : ```cpp m_bAddNextConnect = true; ``` Il sera privilégié lorsqu’il reviendra. ## 11.2 Calcul du QR `UploadQueue.cpp:830-840` ```cpp rank = 1; for each other client: if other.score > myscore: rank++; ``` Nuance : le QR correspond au nombre de clients ayant un score **strictement supérieur**, +1. Deux clients de score exactement identique peuvent donc théoriquement recevoir le même rank. Ce n’est pas nécessairement l’index physique de la liste. ## 11.3 Envoi `UploadClient.cpp:558-572` ```cpp OP_QUEUERANKING ``` Paquet de 12 octets : - rang sur 16 bits ; - 10 octets à zéro. aMule contient même un commentaire demandant à quoi servent ces dix octets, puis conclut qu’ils sont gardés car eMule procède ainsi. Encore un bon exemple de compatibilité historique. --- # 12. Retour en queue en fin de session `UploadClient.cpp:484-499` eMule envoie : ```cpp OP_OUTOFPARTREQS ``` avant de replacer le client dans la file. Le commentaire explique pourquoi : sans ce message, le downloader pourrait croire que ses anciennes requêtes de blocs sont toujours valables alors que l’uploader les a oubliées, provoquant des timeouts. Donc ce paquet n’est pas un simple « slot terminé ». Il sert aussi à **resynchroniser l’état des deux clients**. --- # 13. Politique de session : Full Chunk Constantes : `Opcodes.h` ```cpp PARTSIZE = 9 728 000 SESSIONMAXTRANS = PARTSIZE + 20*1024 ``` L’option : ```text FullChunkTransfers ``` est chargée par défaut à `true` dans `Preferences.cpp:2164`. ## eMule 0.72a `UploadQueue.cpp:787-793` En mode full chunk : si le payload dépasse `SESSIONMAXTRANS`, le slot peut tourner si la bande passante ne peut pas simplement satisfaire un client supplémentaire. L’idée est donc : ```text ≈ une partie complète par session ``` sans forcer inutilement la rotation si la capacité permet de garder le client et d’en ouvrir un autre. --- # 14. Politique de session sans Full Chunk `Opcodes.h` ```cpp SESSIONMAXTIME = 1 heure ``` `UploadQueue.cpp:794-815` Deux mécanismes : 1. session > 1 h ; 2. score actuel inférieur au meilleur score de la file. Une protection de 6 secondes évite d’éjecter trop de slots successivement uniquement à cause du recalcul des scores. --- # 15. Le score pendant un upload `GetScore(sysvalue=true, isdownloading=true)` Pour éviter les sessions de quelques secondes, le score d’un client en cours d’upload reçoit une logique particulière. Le commentaire dit : ```text les 15 premières minutes d'upload comptent avec un bonus destiné à empêcher des uploads de 20 secondes ``` Puis le score cesse d’augmenter indéfiniment. Cette logique est déjà présente dans eMule 0.50a. # 16. Purge de la file `Opcodes.h` ```cpp MAX_PURGEQUEUETIME = 1 heure ``` `FindBestClientInQueue()` Un client est retiré si : ```text dernière requête + 1h dépassée ou fichier plus partagé ``` La valeur existait déjà en 0.50a. C’est une convention de client, pas une structure du protocole. --- # 17. Ouverture dynamique des slots en 0.72a C’est l’une des parties où l’évolution par rapport à 0.50a est visible. ## Bornes `Opcodes.h` ```cpp MIN_UP_CLIENTS_ALLOWED = 2 MAX_UP_CLIENTS_ALLOWED = 100 ``` Mais `AcceptNewClient()` autorise en pratique directement jusqu’à : ```cpp max(MIN_UP_CLIENTS_ALLOWED, 4) ``` donc quatre slots dans cette première condition. ## Débit cible `UploadQueue.cpp:397-409` ```text <= 3 slots : cible 3 KiB/s >= 4 slots : cible = nombre de slots KiB/s plafonnée à 25 KiB/s ``` Le test « minimum » utilise 75 % de cette cible. ## Décision `ForceNewClient()` tient compte : - du débit courant ; - de la limite d’upload ; - d’UploadSpeedSense ; - de la cible par client ; - du nombre de slots réellement pleinement actifs demandé par le throttler. La file et le throttler fonctionnent donc en boucle de rétroaction. --- # 18. Évolution depuis eMule 0.50a ## 0.50a Constantes : ```cpp UPLOAD_CLIENT_DATARATE = 3072 bytes/s MIN_UP_CLIENTS_ALLOWED = 2 MAX_UP_CLIENTS_ALLOWED = 100 ``` L’ouverture des slots utilise largement cette cible fixe d’environ 3 KiB/s, avec une limite interne dynamique autour de 7,5 KiB/s/client dans une autre partie de `ForceNewClient()`. ## 0.72a La logique `GetTargetClientDataRate()` devient variable et peut viser jusqu’à : ```text 25 KiB/s par client ``` Le changelog inclus indique au 1er mars 2010 : ```text upload code tweaked to significantly increase possible upload speed per slot ``` et le maximum de slots est ramené à 100. ### Conclusion L’algorithme de slots a déjà été modernisé au fil de l’histoire. Ce n’est donc clairement **pas une règle figée du protocole eD2K**. --- # 19. UploadBandwidthThrottler `UploadBandwidthThrottler.cpp` La distribution se fait en plusieurs phases. ## 19.1 Contrôle d’abord Les paquets de contrôle en attente sont servis avant les données fichier. ## 19.2 « Trickle » Les sockets qui n’ont pas reçu de données depuis environ une seconde peuvent recevoir un petit envoi afin de ne pas rester affamées. ## 19.3 Part égale Commentaire explicite : ```cpp // Equal bandwidth for all slots ``` Le throttler donne une part aux slots jusqu’à un nombre calculé à partir du débit permis et de la cible. ## 19.4 Bande passante restante Commentaire : ```cpp // Any remaining bandwidth will be used - from first to last. ``` La capacité restante est ensuite proposée dans l’ordre de la liste. ## 19.5 Feedback vers la file Si le throttler n’arrive pas à consommer tout le budget alors qu’il semble avoir besoin de davantage de sockets, il peut fixer : ```cpp m_highestNumberOfFullyActivatedSlots = GetStandardListSize() + 1; ``` La file peut alors ouvrir un nouveau slot. --- # 24. Comparaison aMule : preuve de liberté locale L’archive aMule fournie suit le même protocole mais possède une politique sensiblement différente. ## Score `UploadClient.cpp:75-145` Même architecture : ```text attente × crédits × priorité ``` mais avec notamment une priorité supplémentaire `PR_POWERSHARE`. ## Queue aMule trie réellement la waiting list et mémorise un rang pour les clients. Il réordonne périodiquement la file. ## Limite Dans cette implémentation, l’entrée est refusée directement une fois : ```cpp m_waitinglist.size() >= GetQueueSize() ``` sans la zone soft/hard particulière de l’eMule 0.72a. ## Slots La politique de calcul du maximum de slots diffère également. ## Sessions Le code observé applique : ```text 1 heure ou 10 MiB environ ``` pour les slots ordinaires. ### Conclusion Même philosophie globale, mais politiques locales clairement différentes. C’est une preuve supplémentaire que l’ordonnanceur peut évoluer sans changer eD2K. --- # 25. Vieilles règles qui méritent un audit, pas une suppression Liste issue du code : ```text MAX_PURGEQUEUETIME 1 h SESSIONMAXTIME 1 h SESSIONMAXTRANS 1 part + 20 KiB queue default ~5000 hard queue default ~6250 MAX_UP_CLIENTS_ALLOWED 100 same IP ~3 entrées old eMule version penalty ×0.5 friend slot score maximal collection <50 KiB bypass ``` Pour chacune : 1. comprendre l’objectif historique ; 2. mesurer sa fréquence d’activation en 2026 ; 3. identifier les effets secondaires ; 4. seulement ensuite décider. --- # 26. Une règle historique probablement devenue presque invisible `UploadClient.cpp:227-228` ```cpp if ((IsEmuleClient() || GetClientSoft() < 10) && m_byEmuleVersion <= 0x19) { fBaseValue *= 0.5f; } ``` Certains très anciens clients eMule reçoivent donc un coefficient `0.5`. Cette règle existe encore dans 0.72a. # 27. Queue Rank : amélioration d’interface possible Aujourd’hui, le QR envoyé au pair est uniquement un nombre. ```text Client X QR local : 37 Attente : 42 min Crédit : ×2.8 Priorité : High ×0.9 Score : 6350 Joignable : oui ``` Pour notre propre download, on ne connaît pas la formule exacte utilisée par le pair distant. Donc l’interface doit bien distinguer : ```text QR distant reçu ``` de : ```text score de notre propre file d’upload ``` Ne jamais prétendre expliquer le score d’un client distant si nous ne possédons que son QR. --- # 33. Conclusion Le protocole eD2K fournit un vocabulaire très simple : ```text je veux un slot tu as un slot donne-moi ces blocs voici les données retourne en attente ``` Tout le reste est une politique locale construite au fil des années : ```text score crédits priorités slots rotation queue soft/hard LowID handling bandwidth scheduling ``` Cette politique a déjà évolué entre eMule 0.50a, 0.72a et aMule. > **instrumenter l’ordonnanceur actuel, comprendre ses décisions sur le réseau réel, puis moderniser les heuristiques sans modifier les messages eD2K.** Le vieux TODO concernant le fichier réellement servi après attribution d’un slot est, à ce stade, la piste fonctionnelle la plus intéressante de ce dossier. --- --- # Dossier 05 — Trust Model, Leecher Mods et données distantes non fiables {#g05} --- # 0. Résumé exécutif Jusqu’ici, nous séparions principalement : ```text PROTOCOLE vs POLITIQUE LOCALE ``` Il faut maintenant ajouter : ```text CONFIANCE DANS LES DONNÉES DISTANTES ``` Une valeur parfaitement conforme au protocole peut être **mensongère**. Exemples : ```text Queue Rank ModString Client Version pseudo bits de capacité état de parties liste Source Exchange ``` Le paquet peut être syntaxiquement valide tout en portant une information choisie volontairement par un client modifié. Le principe directeur proposé est : > **Toute donnée contrôlée par un pair distant est une assertion non fiable tant qu’elle n’est pas vérifiée localement ou cryptographiquement.** Ce dossier montre également une nuance importante par rapport à l’intuition « un leecher envoie un faux rang pour passer premier dans notre upload ». Dans l’eMule officiel : - le **score de notre propre Upload Queue est calculé localement** ; - un faux Remote Queue Rank ne permet donc pas directement au pair de s’attribuer un meilleur score chez nous ; - en revanche, le **Remote Queue Rank reçu est utilisé dans certaines décisions de notre Download Manager**, notamment lors des choix A4AF / Source Exchange autour du seuil QR 50. Il existe donc bien une surface de manipulation, mais elle n’est pas exactement celle que l’interface pourrait laisser penser. --- # 1. Preuve historique : les clients mensongers ont réellement existé Le corpus des Mods contient des fonctions explicitement offensives. ## Applejuice Le changelog annonce : ```text advanced undetectable Fake Rank send Fake Rank to Clients on Queue send Fake Rank instead of Full Queue send Fake Rank to all Clients or only Leechers manual enter send Fake Rank < x ``` Il annonce aussi : ```text Fake Tags Clientversion Thief Community Faker Client Emulations send arbitrary Clientversion select sending Modstring random Modstring Ghost / no Modstring ``` Le même Mod comprend également : ```text Boost Clients depending on Remote Queue Rank ``` C’est une démonstration directe du problème de confiance : ```text A prend une décision locale selon un champ distant B peut falsifier ce champ → B peut tenter d’influencer A ``` --- # 2. Les protections historiques confirment l’existence du problème Le corpus mentionne notamment : ```text QR-Faker / Faked Ranks kicken Anti-Fake-Rank Anti-Nick-Thief Anti-ModID Faker Hash Thief Protection Ghost Mod detection Client Analyzer Argos Anti-Leecher DLP SNAFU ``` Argos est documenté comme utilisant une détection multi-critères portant sur : ```text ModString pseudonyme logiciel UserHash opcode Source Exchange fast ask tags invalides violations de protocole ``` Ce corpus doit être étudié comme une **base historique de menaces**, pas comme un ensemble de règles à recopier. --- # 4. Classification des données par niveau de confiance ## Niveau L0 — Remote Claim Le pair contrôle entièrement la valeur. Exemples : ```text nickname ModString version Remote Queue Rank capabilities déclarées part map sources SX envoyées ``` ```text affichage optimisation faible jamais privilège important seul ``` ## Niveau L1 — Cohérence locale La valeur distante est cohérente avec d’autres observations. Exemple : ```text le pair annonce SX4 et répond ensuite avec des paquets SX4 structurellement valides ``` Cela augmente la confiance dans **cette capacité seulement**. ## Niveau L2 — Observation comportementale Notre client observe directement un événement. Exemples : ```text connexion réussie octets reçus latence requêtes réellement faites source SX réellement joignable bloc reçu ``` ## Niveau L3 — Vérification cryptographique / intégrité Exemples : ```text SecureIdent validé bloc vérifié par hash AICH cohérent FileHash localement confirmé ``` Ces niveaux ne doivent pas devenir un « score universel ». Ils doivent être appliqués au domaine concerné. --- # 5. Remote Queue Rank : cas d’école ## 5.1 Réception Dans `DownloadClient.cpp` eMule 0.72a : ```cpp ProcessEdonkeyQueueRank(...) ProcessEmuleQueueRank(...) ``` lisent directement le rang envoyé par le pair puis appellent : ```cpp SetRemoteQueueRank(rank, ...) ``` Aucune preuve de la file distante n’est possible. C’est logique : nous n’avons pas accès à sa waiting list. ## 5.2 Protection contre le flood eMule ne fait toutefois pas confiance au **rythme** des messages. `CheckQueueRankFlood()` : - compte les QR non sollicités ; - suit les mauvaises requêtes ; - peut bannir avec la raison : ```text QR flood ``` Le contenu numérique du rang n’est pas vérifiable. La fréquence d’envoi, elle, peut être vérifiée localement. C’est exactement la distinction : ```text vérité distante : invérifiable comportement protocolaire : observable ``` ## 5.3 Utilisation du QR distant dans l’eMule officiel Point important découvert dans la 0.72a. `SwapToRightFile()` contient notamment des conditions : ```cpp GetRemoteQueueRank() <= 50 ``` et : ```cpp GetRemoteQueueRank() > 50 ``` dans des décisions liées au changement de fichier / Source Exchange. Donc : > **le Remote Queue Rank n’est pas purement décoratif dans le client officiel.** Un pair qui annonce volontairement un QR artificiel peut théoriquement influer sur cette branche de décision. Cela ne signifie pas qu’il obtient directement un meilleur upload chez nous. Cela signifie qu’une donnée invérifiable influence une heuristique locale de téléchargement. # 6. Notre Upload Queue est mieux protégée contre le Fake Rank Le dossier 04 a montré que notre propre score d’upload dépend de valeurs locales : ```text GetWaitStartTime() Credits()->GetScoreRatio() priorité locale du fichier ``` Un client distant ne fournit pas directement ces valeurs. Il ne peut donc pas envoyer : ```text score=999999 ``` dans un paquet standard pour devenir premier. ### Les attaques sont plutôt indirectes Exemples de domaines à surveiller : ```text identité clonée multiplication d’instances/adresses reconnexion/reask agressif changement du fichier après attribution du slot tentatives de contourner les règles de queue ``` Le détail de chacune doit être traité défensivement et mesuré. --- # 7. Hello Tags : syntaxiquement valides ≠ vrais `BaseClient.cpp` parse notamment : ```text CT_NAME CT_VERSION CT_PORT CT_MOD_VERSION CT_EMULE_UDPPORTS CT_EMULE_BUDDYUDP CT_EMULE_BUDDYIP ``` et les Info Packets contiennent notamment : ```text ET_COMPRESSION ET_UDPPORT ET_UDPVER ET_SOURCEEXCHANGE ET_COMMENTS ET_EXTENDEDREQUEST ET_COMPATIBLECLIENT ET_FEATURES ET_MOD_VERSION ``` Le parser vérifie les types. Mais la valeur vient du client distant. Exemple : ```cpp m_strModVersion = temptag.GetStr(); ``` Un Mod peut donc choisir la chaîne qu’il transmet. # 8. ModString, version et nickname ne doivent pas accorder de privilège Le corpus historique montre explicitement : ```text Clientversion Thief Mod Thief Nick Thief Ghost Mod Client Emulation random Modstring ``` Par conséquent : ## Mauvaise règle ```text si mod == "TrustedMod" donner un bonus ``` ## Meilleure règle Le ModString peut servir à : ```text diagnostic compatibilité historique statistiques indice faible ``` mais pas à accorder seul : ```text crédits slot prioritaire bypass queue confiance SX élevée ``` --- # 9. SecureIdent : exemple d’une donnée transformée en preuve Le UserHash reçu dans le Hello est lui aussi choisi par le pair. Pris seul : ```text UserHash = déclaration ``` Avec SecureIdent réussi : ```text UserHash + possession de la clé privée = preuve cryptographique historique ``` L’eMule officiel utilise cette distinction dans plusieurs endroits. Dans `ClientList::AttachToAlreadyKnown()` : - si deux connexions présentent un même UserHash ; - et que le client déjà connu possède une identité sécurisée validée ; - le nouveau client contradictoire peut être banni pour UserHash invalide. C’est une excellente philosophie à généraliser : > **la déclaration n’est pas le problème ; le privilège n’est accordé qu’après preuve.** --- # 10. Source Exchange : surface de pollution naturelle ## 10.1 Ce que SX transporte Selon la version : ```text IP/ID port serveur UserHash options de connexion ``` Toutes ces informations sont fournies par le pair SX. ## 10.2 Défenses déjà présentes `CPartFile::AddClientSources()` 0.72a vérifie : - version SX connue ; - cohérence exacte taille / nombre d’entrées ; - format de chaque version ; - IP valides ; - IPFilter ; - clients bannis ; - règles `CanAddSource()` ; - limite maximale de sources ; - dédoublonnage ultérieur via `CheckAndAddSource()`. Le commentaire SX1 dit explicitement que certaines vérifications réduisent le risque de : ```text wrong source data wrong userhashes duplicate sources ``` ## 10.3 Limite fondamentale Même après toutes ces vérifications : ```text IP syntaxiquement valide ``` ne prouve pas : ```text source en ligne source possédant ce fichier source répondant source utile ``` # 11. Capability Trust : annoncer une fonction ne suffit pas Exemple : ```text SX version = 4 ``` ```text CLAIMED_SX4 OBSERVED_VALID_SX4 OBSERVED_BROKEN_SX4 ``` Réaction possible : si un pair annonce une capacité mais produit plusieurs réponses invalides : ```text désactiver cette fonctionnalité pour ce pair ``` plutôt que : ```text bannir complètement le pair ``` Cela réduit les faux positifs et conserve la compatibilité. --- # 12. Fast Reask / requêtes agressives L’eMule officiel possède déjà une protection. `UploadClient.cpp::AddRequestCount()` Si le même fichier est redemandé avant : ```cpp MIN_REQUESTTIME = 10 minutes ``` et que le pair n’a pas de Friend Slot : ```text badrequests++ ``` À : ```cpp BADCLIENTBAN = 4 ``` le client peut être banni. C’est un exemple de protection **100 % comportementale** : eMule ne cherche pas à savoir quel Mod est utilisé. Il observe seulement : ```text ce client redemande trop vite ``` --- # 13. File Request Flood `BaseClient.cpp::CheckFailedFileIdReqs()` Les requêtes répétées portant sur des identifiants de fichiers invalides sont comptées. Après répétitions et accumulation dans `BadRequests`, le client peut être banni avec : ```text FileReq flood ``` Encore une fois : ```text preuve locale du comportement ``` plutôt que : ```text liste noire de noms de Mods ``` --- # 14. Queue Rank Flood Même logique : ```text QR reçus alors qu’ils n’étaient pas attendus ``` sont comptés. Après répétitions : ```text Ban("QR flood") ``` Le rang lui-même peut mentir. Le flood, lui, est objectivement observable. --- # 15. Spam L’eMule officiel possède aussi : ```cpp SetSpammer(true) → Ban("Identified as Spammer") ``` Le dossier Trust devra plus tard examiner précisément les critères de classification comme spam afin de distinguer les règles robustes des heuristiques historiques. --- # 16. Plusieurs clients depuis la même IP `UploadQueue.cpp` contient déjà une limitation afin d’éviter qu’une même IP occupe trop d’entrées dans la waiting queue. C’est une protection contre une forme simple de multiplication d’identités. Limite : ```text elle ne protège pas contre des pairs utilisant réellement plusieurs IP ``` et ne doit pas être étendue naïvement à des environnements NAT où plusieurs utilisateurs légitimes partagent une adresse. # 17. Données corrompues : une défense fondée sur les conséquences L’eMule 0.72a possède : ```text CorruptionBlackBox ``` Le système enregistre les plages reçues et leurs expéditeurs. Après vérification : ```text VerifiedData() CorruptedData() EvaluateData() ``` peuvent déterminer les clients ayant contribué aux zones corrompues. Le code peut ensuite bannir un client lorsque la proportion de données corrompues attribuées dépasse la limite prévue. ### Intérêt architectural C’est probablement l’un des meilleurs exemples de défense historique : ```text pas besoin de croire le ModString pas besoin de deviner l’intention ``` La sanction est liée à des octets réellement reçus et vérifiés comme mauvais. --- # 18. Hashes : la donnée finale ne dépend pas de la parole du pair Un client peut annoncer posséder une partie. Il peut même envoyer des octets. Mais la partie n’est considérée valide qu’après vérification : ```text ED2K / MD4 et éventuellement AICH ``` Il s’agit d’un excellent exemple de design zéro-confiance avant l’heure : ```text recevoir ≠ accepter comme valide ``` --- # 19. Part Status : déclaration utile, preuve faible La carte de parties du pair est nécessaire pour sélectionner les blocs. Mais elle est contrôlée par le pair distant. ```text Remote Claim ``` La confiance augmente seulement lorsque : ```text le pair accepte effectivement les requêtes correspondantes et fournit des données valides ``` ### À mesurer ```text parts advertised requests attempted requests fulfilled valid payload ``` Un taux faible n’est toutefois pas automatiquement une triche : ```text fichier modifié session interrompue source obsolète A4AF changement d’état ``` La détection doit être statistique et prudente. --- # 20. Pourquoi les vieilles blacklists sont insuffisantes Argos, DLP et d’autres systèmes ont souvent utilisé : ```text modstrings connues nick patterns version patterns hash patterns bad opcodes ``` Ils étaient utiles dans le contexte de leur époque. Mais une blacklist possède plusieurs défauts : ```text facile à contourner en changeant l’identifiant vieillit rapidement faux positifs maintenance externe nécessaire peut confondre client rare et client hostile ``` --- # 21. Ce que nous devons récupérer des anciens Anti-Leecher ## À conserver conceptuellement ```text détection multi-critères corrélation dans le temps différents niveaux d’anomalies historique par client réactions graduées protection anti-thief protection fast-reask analyse des incohérences protocole ``` ## À ne pas copier aveuglément ```text ban sur ModString seul ban sur pseudo seul ban permanent au premier signal community favoritism règles spécifiques à des Mods disparus signatures non expliquées ``` --- # 23. Exemple ```text PeerTrustProfile IdentityTrust UserHash claimed SecureIdent verified result HIGH ProtocolCompliance malformed packets 0 file request floods 0 QR floods 0 result HIGH QueueSignalTrust remote QR unverifiable suspicious changes 3 result UNKNOWN SourceExchangeQuality received 143 valid syntax 143 new 62 reachable 34 useful 19 result NORMAL DataIntegrity verified bytes 1.8 GiB corrupt attributable 0 result HIGH ``` Ce profil n’a pas besoin d’être affiché tel quel à l’utilisateur normal. Il sert au moteur et au diagnostic. --- # 24. Provenance de chaque donnée Proposition très importante : ```text LOCAL_MEASURED REMOTE_CLAIM CRYPTO_VERIFIED HASH_VERIFIED CROSS_CHECKED HEURISTIC ``` Exemples : ```text RemoteQueueRank value=17 origin=REMOTE_CLAIM CreditDownloadedBytes value=412 MiB origin=LOCAL_MEASURED SecureIdentity state=VALID origin=CRYPTO_VERIFIED BlockIntegrity state=GOOD origin=HASH_VERIFIED ``` Cela permet de voir immédiatement dans le code si une décision forte dépend d’une donnée faible. --- # 25. Trust Boundary Review ```text 1. quelles entrées utilise-t-elle ? 2. qui contrôle ces entrées ? 3. peuvent-elles être vérifiées ? 4. quel avantage accordons-nous ? 5. que se passe-t-il si elles sont fausses ? ``` Exemple actuel : ```text SwapToRightFile() entrée : RemoteQueueRank contrôlé par : pair distant preuve : aucune effet : influence décision de swap ``` Statut : ```text AUDIT PRIORITAIRE ``` --- # 26. Réactions graduées Proposition de niveaux : ## R0 — Observer ```text aucune conséquence log / métrique seulement ``` ## R1 — Ignorer la donnée Exemple : ```text RemoteQR jugé incohérent → ne plus l’utiliser pour une optimisation ``` ## R2 — Désactiver la capacité Exemple : ```text SX4 annoncé mais réponses répétées invalides → plus de SX avec ce pair pendant cette session ``` ## R3 — Cooldown Exemple : ```text requêtes trop rapides → ignorer temporairement ``` ## R4 — Disconnect Pour violation importante de session. ## R5 — Temporary Ban Réservé aux preuves fortes : ```text flood répété usurpation SecureIdent établie corruption attribuée au-delà du seuil ``` --- # 27. Le ban doit être explicable Chaque sanction devrait avoir : ```text reason_code evidence timestamp duration scope ``` Exemple : ```text BAN reason=QR_FLOOD evidence=3 unsolicited QR + badrequest threshold duration=historical default ``` Pas : ```text BAN reason=leecher ``` Cette différence sera très importante pour le support et le développement. --- # 28. Temporaire plutôt que définitif Le réseau est dynamique. Une IP peut plus tard appartenir à un autre utilisateur. Un NAT peut représenter plusieurs utilisateurs. Une erreur réseau peut imiter un comportement mauvais. ```text états à durée de vie limitée dégradation progressive réévaluation ``` sauf données persistantes cryptographiquement liées à une identité. --- # 29. Pas de bonus fondé sur des données non vérifiables > **Une Remote Claim ne doit jamais suffire seule à obtenir un privilège local significatif.** Exemples de privilèges : ```text slot immédiat crédit artificiel contournement de queue bypass de limites priorité spéciale ``` Cette règle aurait historiquement évité une partie des mécaniques exploitables par Fake Rank / Community Faker. --- # 30. Attention aux mécanismes communautaires Le corpus Applejuice comporte plusieurs fonctions : ```text Community Community Faker ignore hard limit for community boost special slots ``` Dès qu’un label distant ouvre un privilège local : ```text « je suis membre de la communauté » ``` il devient une cible naturelle de falsification. --- # 31. Telemetry : mesurer les attaques réellement présentes en 2026 Beaucoup de protections historiques ciblent peut-être aujourd’hui des comportements disparus. Nous ne devons pas supposer. Proposition : ```text [TRUST_ANOMALY] type=QR_UNSOLICITED peer=anon-id count=... [TRUST_ANOMALY] type=SX_INVALID_PACKET version=4 expected=... received=... [TRUST_ANOMALY] type=CAPABILITY_CONTRADICTION claimed=SX4 observed=invalid_sx4 [TRUST_ANOMALY] type=FILE_REASK_TOO_FAST delta=... ``` Après plusieurs semaines : ```text quelles attaques existent réellement ? quelles protections ne se déclenchent jamais ? quels signaux créent des faux positifs ? ``` --- # 32. Confidentialité des logs Un journal Trust ne doit pas devenir une base inutilement intrusive. Par défaut : ```text pas de UserHash complet pas d’IP persistée indéfiniment identifiant pseudonymisé de session durées de rétention courtes ``` Pour debug volontaire : ```text mode détaillé explicite ``` --- # 33. Patch immédiat 1 — annoter la confiance du Remote QR Aucun changement protocolaire. Première étape : ```cpp // Remote-provided, unverifiable queue position. // Never treat as authoritative. m_nRemoteQueueRank = nr; ``` Puis créer des compteurs d’usage : ```text remote_qr_received remote_qr_used_for_swap remote_qr_threshold_le50 remote_qr_threshold_gt50 ``` But : savoir si la branche `QR 50` est encore réellement importante. --- # 34. Patch immédiat 2 — Decision Trace autour de `SwapToRightFile` Ajouter uniquement du diagnostic. Exemple : ```text [A4AF_SWAP_DECISION] from=fileA to=fileB remoteQR=17 remoteQRTrust=UNVERIFIED sxAllowedA=yes sxAllowedB=no decision=SWAP reason=REMOTE_QR_LE_50 ``` Cette trace nous permettra de mesurer exactement l’influence d’une donnée falsifiable. --- # 35. Patch immédiat 3 — Source Exchange Quality Metrics Ajouter à la provenance SX : ```text provider client source announced source accepted source duplicate source rejected-IP source connected source useful ``` Ne pas punir dans un premier temps. **Observation seulement.** --- # 36. Patch immédiat 4 — centraliser les anomalies protocole Aujourd’hui, les protections sont dispersées : ```text CheckQueueRankFlood CheckFailedFileIdReqs AddRequestCount CorruptionBlackBox SecureIdent same-IP queue logic ``` Créer à terme : ## `PeerProtocolMonitor` qui collecte les événements sans nécessairement remplacer les mécanismes existants. Première phase : ```text miroir des événements ``` Aucun changement de comportement. --- # 37. Patch immédiat 5 — le CSPRNG SecureIdent du dossier 03 Il s’intègre directement au Trust Model. Le challenge SecureIdent est une preuve. Il doit donc être généré avec une source d’aléa adaptée. Remplacer : ```cpp rand()+1 ``` --- # 39. Fuzzing défensif Les parsers de paquets anciens sont un bon candidat au fuzzing local. Objectif : ```text aucun crash aucun dépassement aucune allocation aberrante aucun état impossible ``` Cibles prioritaires : ```text Hello / HelloAnswer MuleInfo SX1 / SX2 QueueRank FileRequest PartStatus AICH ``` Le fuzzing concerne ici la robustesse de notre propre client. Il ne nécessite aucune émission vers le réseau réel. --- # 41. Matrice de confiance proposée | Information | Source | Vérifiable ? | Utilisation recommandée | |---|---|---|---| | Nickname | pair | non | affichage | | ModString | pair | non | diagnostic / indice faible | | Client version | pair | partiellement par comportement | compatibilité prudente | | Remote QR | pair | non | heuristique faible | | UserHash | pair | oui via SecureIdent | identité après preuve | | Octets transférés | local | oui | crédits / stats | | Source SX | pair | seulement par essai | découverte avec filtrage | | Part Status | pair | seulement par transfert | planification prudente | | Bloc reçu | local | oui | provisoire jusqu’au hash | | Bloc/part vérifié | hash | oui | forte confiance | | Fréquence de requêtes | local | oui | anti-abus | | Paquet malformé | local | oui | compliance | | Corruption attribuée | local + hash | oui avec limites | forte anomalie | --- # 42. Architecture proposée ## `PeerTrustProfile` Données par pair. ## `PeerEvidenceStore` Événements avec : ```text type scope strength timestamp expiration ``` ## `PeerProtocolMonitor` Collecte : ```text flood malformed packet capability contradiction rate violation ``` ## `PeerQualityMonitor` Collecte : ```text SX yield valid data connection success part claim success ``` ## `PeerPolicy` Transforme les preuves en actions graduées. --- # 43. Ne pas utiliser une moyenne magique Exemple à éviter : ```text TrustScore = 72/100 ``` Pourquoi ? Parce qu’un client pourrait : ```text être excellent pour les données mais mauvais pour SX ``` Une moyenne cacherait cette distinction. Meilleure approche : ```text DataIntegrity = HIGH SXQuality = LOW QueueSignal = UNKNOWN Identity = VERIFIED ``` Puis chaque sous-système n’utilise que la dimension qui le concerne. --- # 44. Intégration à Source Discovery Manager Dossier 01 : ```text Source Discovery Manager ``` peut maintenant utiliser : ```text SXQuality ``` mais pas : ```text ModStringTrust ``` Exemple : deux pairs nous proposent 50 sources. ```text Pair A : 50 proposées 35 joignables historiquement Pair B : 50 proposées 2 joignables historiquement ``` --- # 45. Intégration à Upload Intelligence Dossier 04 : l’Upload Queue doit utiliser seulement : ```text temps local crédits locaux vérifiés priorité locale états localement observés ``` et jamais : ```text Remote Queue Rank Remote ModString Remote claimed ratio ``` pour accorder directement un slot. --- # 46. Intégration à Connectivity Intelligence Dossier 02 : les capacités de callback annoncées peuvent être : ```text CLAIMED ``` puis évoluer vers : ```text OBSERVED_WORKING ``` si une tentative réussit. --- # 47. Intégration à Identity Health Dossier 03 : SecureIdent devient la racine de confiance de l’identité historique. Un ModString ne doit pas remplacer SecureIdent. Un UserHash identique sans preuve cryptographique doit rester suspect lorsqu’il existe une identité sécurisée connue. --- # 48. Ce que nous pouvons tirer d’Argos et Client Analyzer L’idée la plus intéressante n’est pas la liste de Mods bannis. C’est la **corrélation multi-critères**. Argos documente : ```text modstring pseudo logiciel hash opcode fast ask SX exploit tags invalides violations protocole ``` ```text plusieurs signaux indépendants ``` mais donner beaucoup plus de poids aux signaux : ```text objectivement observables ``` qu’aux simples chaînes de caractères. --- # 49. Ordre de confiance suggéré Du plus solide au plus faible : ```text hash / signature cryptographique ↓ mesure locale directe ↓ comportement répété cohérent ↓ corrélation de plusieurs déclarations ↓ déclaration distante isolée ↓ ModString / nickname / réputation statique ``` --- # 51. Conclusion Le réseau eD2K/eMule a été conçu dans un environnement ouvert où les clients peuvent être modifiés. Les anciens Leecher Mods démontrent que : ```text un paquet valide peut contenir une information mensongère ``` et les anciens Anti-Leecher démontrent que cette réalité était connue depuis longtemps. Elle doit construire un modèle clair : > **déclaration → observation → vérification → décision proportionnée** avec une règle centrale : > **les avantages locaux doivent reposer autant que possible sur des faits mesurés par notre propre client.** --- --- # Dossier 06 — Parties, blocs, Enhanced Chunk Selection et Endgame {#g06} --- # 0. Résumé exécutif Ce dossier met en évidence deux sous-systèmes distincts qu’il faut éviter de mélanger : ```text A. PART SELECTION Quelle partie ED2K faut-il privilégier ? B. BLOCK SCHEDULING Quelles plages faut-il réserver à quelle source et combien faut-il en maintenir en vol ? ``` L’eMule officiel possède déjà un **Enhanced Chunk Selection** beaucoup plus riche que le simple « rarest first ». Dans la branche 0.72a étudiée, le classement d’une partie utilise notamment : - fréquence / rareté ; - prévisualisation ; - état déjà demandé ; - complétion ; - continuité avec la partie précédente de la source ; - nombre de sources actives pouvant fournir la partie ; - estimation de vitesse de complétion. En revanche, le scheduler standard protège fortement l’unicité des réservations : > **un bloc déjà demandé ne doit normalement pas être redemandé à une autre source.** C’est sain en régime normal mais crée le problème classique d’endgame : ```text GetUnrequestedSize() == 0 mais le fichier reste incomplet car les derniers blocs sont immobilisés chez une ou plusieurs sources lentes. ``` Notre ancien audit NetF WARP / Spike2 / Silversurfer apporte déjà les briques historiques nécessaires à une amélioration compatible : - Dynamic Block Requests ; - Drop Stalled Sources ; - Delayed NNP ; - PartCompletionSpeedUp. > **ne pas faire du duplicate endgame agressif ; adapter la granularité et réattribuer sélectivement les réservations qui deviennent contre-productives.** Un troisième axe apparaît avec notre étude anti-leecher : > **la rareté est calculée à partir de cartes de parties déclarées par les pairs et n’est donc pas une vérité de confiance.** --- # 0.1 Repères directs dans eMule Community 0.72a Les points principaux de ce dossier ont été recroisés directement dans l’archive source fournie. ```text Opcodes.h PARTSIZE ligne ~102 EMBLOCKSIZE ligne ~135 PartFile.cpp IsAlreadyRequested() ligne ~1662 ShrinkToAvoidAlreadyRequested() ligne ~1686 GetNextEmptyBlockInPart() ligne ~1765 UpdatePartsInfo() ligne ~2517 GetNextRequestedBlock() ligne ~4551 DownloadClient.cpp ProcessFileStatus() ligne ~495 CreateBlockRequests() ligne ~767 SendBlockRequests() ligne ~798 ``` La phrase de commentaire la plus importante se trouve au début de `GetNextRequestedBlock()` : ```text The selection of the chunk to download is one of the CRITICAL parts of the edonkey network. ``` Le code précise que l’objectif est d’assurer la meilleure diffusion des fichiers, pas seulement de minimiser l’ETA du client local. --- # 1. Contraintes protocolaires de base ## 1.1 Taille d’une partie ED2K eMule 0.50a et 0.72a : ```cpp PARTSIZE = 9728000 ``` Soit : ```text 9 728 000 octets ≈ 9,277 MiB ``` Cette valeur appartient à l’identité ED2K du fichier. La changer casserait : - le hash ED2K ; - les hashsets ; - les cartes de parties ; - l’interopérabilité avec les fichiers existants. --- # 2. Taille de bloc historique eMule 0.72a : ```cpp EMBLOCKSIZE = 184320 ``` Soit : ```text 180 KiB ``` Il s’agit d’une taille maximale logique de demande courante, pas d’une obligation d’envoyer exactement 180 KiB à chaque requête. `GetNextEmptyBlockInPart()` limite un bloc à cette frontière mais peut retourner une plage plus courte pour combler précisément un gap. Une partie pleine correspond donc à : ```text 52 blocs de 180 KiB + un dernier bloc de 143 360 octets ``` si on la parcourt depuis son début avec cette granularité. --- # 3. Le format de requête historique `edonkey-protocol.txt` : ```text × 3 × 3 ``` eMule 0.72a conserve cette structure legacy. Dans `DownloadClient.cpp`, `SendBlockRequests()` écrit : ```cpp aOffs[0..2] = starts aOffs[3..5] = ends ``` avec : ```cpp OP_REQUESTPARTS ``` ou : ```cpp OP_REQUESTPARTS_I64 ``` pour les offsets de grands fichiers. ### Conséquence Le format historique transporte au plus : ```text 3 ranges ``` dans un paquet de requête classique. Mais le client peut conserver localement davantage de blocs pending et envoyer plusieurs paquets successifs. --- # 4. Une optimisation ancienne qu’il ne faut pas perdre Commentaire Barry dans `DownloadClient.cpp` / `PartFile.cpp` : Historiquement, le code attendait autrefois d’avoir reçu un bloc complet de 180 KiB et demandait les données par blocs rigides. Conséquence moyenne signalée dans le commentaire : ```text ≈ 90 KiB perdus par rupture de connexion ``` La logique actuelle : - écrit les paquets reçus dans des buffers ; - remplit immédiatement les gaps ; - demande seulement les plages manquantes ; - limite une requête à 180 KiB mais autorise des plages plus courtes. --- # 5. Réservation locale des blocs `GetNextEmptyBlockInPart()` : 1. cherche un gap ; 2. borne la plage à la partie ; 3. borne la plage à `EMBLOCKSIZE` ; 4. vérifie `IsAlreadyRequested()` ; 5. si collision, appelle `ShrinkToAvoidAlreadyRequested()` ; 6. ne retourne que la portion non réservée. Extrait logique : ```cpp if (!IsAlreadyRequested(start, end, true)) return block; if (ShrinkToAvoidAlreadyRequested(tempStart, tempEnd)) return shrunken_block; ``` ### Conclusion La politique officielle 0.72a est : > **une plage déjà réservée ne doit pas être réservée à nouveau si une plage libre peut être trouvée.** Il n’existe pas ici de duplicate endgame général. --- # 6. `requestedblocks_list` Chaque bloc choisi est enregistré dans : ```cpp requestedblocks_list ``` avant d’être renvoyé au client demandeur. Ce registre sert notamment à : - éviter les doubles réservations ; - calculer le niveau de complétion « en vol » d’une partie ; - retirer les réservations lorsqu’un client abandonne ; - réouvrir les plages si nécessaire. Les blocs déjà demandés peuvent même être comptés comme pratiquement téléchargés dans le calcul relatif de complétion d’une partie. Le commentaire précise que cela peut faire dépasser temporairement `PARTSIZE` dans ce calcul comparatif ; la valeur est ensuite plafonnée. --- # 7. Source des informations de rareté `CPartFile::UpdatePartsInfo()` remet d’abord : ```cpp m_SrcPartFrequency[] ``` à zéro. Puis pour chaque source possédant une `PartStatus` : ```cpp m_SrcPartFrequency[i] += cur_src->IsPartAvailable(i); ``` Donc : ```text frequency(part i) = nombre de sources connues annonçant cette partie ``` Ce compteur alimente directement `GetNextRequestedBlock()`. --- # 8. Important : la carte de parties est déclarative `CUpDownClient::ProcessFileStatus()` : - vérifie que le nombre de parties reçu correspond au fichier ; - lit chaque bit de la carte ; - remplit `m_abyPartStatus[]`. Le code vérifie la cohérence structurelle : ```text nombre de parties correct taille de message correcte fichier correct ``` Il ne possède évidemment pas une preuve instantanée que le pair possède réellement tous les octets correspondant aux bits à 1. ### Conséquence Trust Un pair hostile peut annoncer une carte de parties fausse. Effets théoriques : 1. être candidat pour une partie qu’il ne peut pas réellement transmettre ; 2. influencer `m_SrcPartFrequency` ; 3. faire paraître une partie rare plus commune qu’elle ne l’est réellement ; 4. provoquer des décisions de sélection sous-optimales. ### Ce qu’il ne faut pas conclure Une variation de carte n’est pas automatiquement une attaque. Un pair est lui-même en téléchargement : sa carte peut légitimement évoluer. --- # 10. Enhanced Chunk Selection — origine eMule 0.50a porte explicitement : ```text [Maella -Enhanced Chunk Selection- (based on jicxicmic)] ``` La branche 0.72a conserve l’architecture et l’essentiel des coefficients, avec plusieurs raffinements. aMule conserve également une branche dérivée de cette même famille de sélection, avec une formule différente. Cela confirme : - le concept est largement partagé ; - les coefficients précis appartiennent à la politique client. --- # 11. Commentaire clé des sources La 0.72a décrit `GetNextRequestedBlock()` comme : ```text one of the CRITICAL parts of the edonkey network ``` Objectif déclaré : ```text ensure the best spreading of files ``` Ce point est crucial. Le scheduler n’est pas conçu uniquement pour réduire **notre** ETA. Il cherche aussi à améliorer la **diffusion des parties dans le swarm eD2K**. ```text ETA locale + santé / diffusion du fichier ``` Une optimisation purement égoïste de vitesse pourrait réduire la robustesse du réseau. --- # 13. Seuils de rareté eMule 0.72a ```cpp limit = max((GetSourceCount() + 9) / 10, 3); veryRareBound = limit; rareBound = 2 * limit; almostRareBound = 4 * limit; ``` Approximation : ```text very rare ≲ 10 % des sources, min 3 rare ≲ 20 % almost rare ≲ 40 % common au-delà ``` Les seuils restent exprimés en nombre brut, mais suivent le nombre de sources. --- # 14. Critère 1 — fréquence Pour chaque partie candidate : ```cpp frequency = m_SrcPartFrequency[part] ``` Plus la valeur est faible, plus la partie est rare. Le poids de cette fréquence est très fort dans les zones rare / very rare. Exemple : ```cpp very rare: rank += 75 * frequency rare: rank += 25 * frequency ``` Rang plus petit = meilleur. --- # 15. Critère 2 — preview Si la fonction de priorité preview est active et que le type de fichier est prévisualisable, eMule favorise : ```text première partie dernière partie avant-dernière partie si la dernière est très petite ``` Dans la 0.72a, la fonction n’est active que pour un fichier de taille > `2 * PARTSIZE`. # 16. Critère 3 — déjà demandé Le code détermine si des blocs de cette partie sont déjà en cours auprès d’autres sources. Ce critère n’est pas simplement : ```text jamais deux sources sur la même partie ``` Il est pondéré différemment suivant la zone de rareté. ### Raison Pour une partie très rare : ```text répliquer vite ``` peut justifier plusieurs sources travaillant sur différentes plages de la même partie. Pour les parties communes, eMule cherche plutôt à terminer intelligemment ce qui est en cours. --- # 17. Critère 4 — complétion La complétion est calculée en partant de : ```text taille de la partie - gaps restant + blocs déjà réservés ``` Le dernier chunk est volontairement traité comme s’il faisait la taille complète afin de ne pas recevoir un avantage artificiel simplement parce qu’il est plus petit. ### Priority inversion Une partie proche de 100 % peut dépasser une partie légèrement plus rare. Ce comportement est volontaire. --- # 18. Critère 5 — même partie que précédemment ```cpp sameChunk = cur_chunk.part == sender->m_lastPartAsked ``` Petit bonus pour continuer la partie avec laquelle cette source travaillait déjà. ### Intérêt - réduit les bascules ; - améliore la continuité ; - favorise la terminaison des parties ; - diminue les réservations dispersées. --- # 19. Critère 6 — sources actives capables de servir la partie Le code part du nombre de sources actuellement en téléchargement puis décrémente pour celles qui annoncent cette partie. Il utilise ainsi une information du type : ```text combien de clients actifs pourraient contribuer à finir cette partie ``` Dans les zones très rares / rares, elle influence le classement. --- # 20. Critère 7 — estimation de fin de partie La 0.72a calcule : ```cpp totalDownloadDatarateForThisPart ``` à partir des clients actifs possédant la partie. Puis : ```cpp bandwidthScore = min( (PARTSIZE - partSize) / (totalDownloadDatarateForThisPart * 5), 2000 ); ``` Ce score approxime donc l’effort restant rapporté au débit disponible. C’est déjà une tentative d’ETA locale par partie. # 21. Les rangs par zone Schéma simplifié 0.72a : ## Beaucoup d’A4AF Priorité très forte à la complétion. ## Très rare Base autour de : ```text 3000 + 75 × frequency ``` puis complétion / request state / même chunk / sources actives. ## Preview Zone : ```text ≈ 10000 ou 20000 ``` ## Rare Zone : ```text ≈ 10101 + 25 × frequency ``` ## Almost rare Zone : ```text ≈ 20101+ ``` avec hasard modéré et poids plus fort de complétion. ## Common Zone : ```text ≈ 30000+ ``` où la complétion et l’estimation de bande passante deviennent importantes. Le rang le plus bas gagne. --- # 22. Aléatoire en cas d’égalité Une fois le meilleur rang trouvé, eMule sélectionne aléatoirement une des parties exactement ex æquo. Commentaire : ```text avoid that everybody tries to download the same chunks at the same time ``` C’est une bonne protection contre les décisions synchronisées. ### Note Le code utilise `rand()` ici. Contrairement au SecureIdent du dossier 03, ce hasard n’a pas besoin d’être cryptographiquement sûr. Mais un générateur moderne local, thread-safe et testable pourrait rendre la logique plus propre. Priorité faible. --- # 23. Continuité 0.50a → 0.72a Le cœur du Enhanced Chunk Selection est déjà présent en 0.50a. On retrouve : - Maella / jicxicmic ; - rareté ; - preview ; - requested state ; - completion ; - same chunk ; - nombre de sources actives ; - bandwidth/completion score ; - random tie-break. La 0.72a conserve donc un algorithme dont la philosophie a au moins quinze ans. Cela ne veut pas dire qu’il est mauvais. Au contraire, il est assez sophistiqué. Mais c’est un candidat idéal pour : ```text instrumentation + simulation + comparaison avec des critères modernes ``` avant toute retouche. --- # 24. Comparaison avec l’archive aMule fournie L’arbre aMule fourni contient lui aussi : ```text [Maella -Enhanced Chunk Selection- (based on jicxicmic)] ``` mais sa formule est différente : - zones rareté différentes ; - moins de critères dans la version observée ; - traitement différent des parties déjà demandées ; - choix aléatoire parmi les ex æquo. Cette divergence confirme encore : > **le wire protocol impose la carte des parties et les ranges ; le ranking exact des parties est une politique locale.** --- # 25. Scheduling des blocs dans eMule 0.50a `SendBlockRequests()` : ```cpp blockCount = 3; ``` mais quand il reste peu de données : ```text ≤ 4 parties restantes ``` et que la source est lente : ```text < ~600 B/s / faible payload → 1 bloc < ~1200 B/s → 2 blocs sinon → 3 blocs ``` Commentaire source : ```text prevent locking of too many blocks when we are on a slow slot ``` ### Point historique important Le problème endgame était donc déjà identifié dans l’officiel sous une forme partielle. --- # 26. Scheduling des blocs dans eMule 0.72a La 0.72a généralise l’adaptation : ```cpp if eMule source && rate < 9 KiB/s: < 4 KiB/s -> 1 sinon -> 2 else if rate > 75 KiB/s: >150 KiB/s -> 9 sinon -> 6 else: 3 ``` ### Signification Le nombre de blocs préparés devient une forme de fenêtre de requêtes : ```text source lente → petite fenêtre source rapide → fenêtre plus profonde ``` C’est une évolution importante pour les connexions rapides. --- # 27. Mais le packet legacy reste à trois ranges `SendBlockRequests()` prend jusqu’à trois blocs non encore envoyés et les encode dans le paquet. Les blocs supplémentaires restent localement pending. Donc : ```text local pending window ≠ nombre de ranges par packet ``` On peut optimiser la **profondeur locale** sans changer le format de paquet. --- # 28. Problème potentiel de la logique actuelle à haut débit Avec : ```text 9 blocs pending × 180 KiB ≈ 1,58 MiB réservés à une source ``` une source rapide les termine très vite. Ce n’est pas problématique en régime normal. Mais à proximité de la fin : - plusieurs blocs peuvent représenter une part significative du reste ; - la source peut ralentir brusquement ; - les réservations peuvent immobiliser les dernières données. La 0.72a ne conditionne plus son `1/2/3/6/9` explicitement au fait d’être dans les quatre dernières parties comme la 0.50a. # 30. État NORMAL Critères possibles : ```text beaucoup de données non réservées ETA longue plusieurs parties disponibles ``` Comportement : - Enhanced Chunk Selection actuel ; - `EMBLOCKSIZE` standard ; - profondeur pending standard ; - aucun drop endgame. --- # 31. État APPROACH Déclenché par exemple lorsque : ```text ETA devient courte OU ≤ quelques parties restent ``` Comportement : - réduire progressivement la quantité confiée aux sources lentes ; - instrumenter l’ETA par bloc ; - conserver le scheduler normal de parties. --- # 32. État ENDGAME Le fichier possède encore des données libres, mais peu. Comportement : ```text Dynamic Block Requests ``` avec taille de plage dérivée de : ```text source_rate × target_time ``` et plafonnée à `EMBLOCKSIZE` dans une première version prudente. Objectif : ```text chaque réservation doit pouvoir finir bien avant l’ETA globale du fichier ``` --- # 33. État LOCKED Définition conceptuelle : ```text unrequested_bytes == 0 ET file incomplete ``` Tous les octets manquants sont donc déjà réservés. C’est la situation où le scheduler standard peut devenir prisonnier d’une source lente. --- # 34. Leçons du rapport NetF WARP / Spike2 / Silversurfer Notre audit antérieur a déjà établi : ## Dynamic Block Requests Adapte la taille des demandes à : - vitesse source ; - vitesse fichier ; - ETA globale. NetF utilisait des valeurs historiques : ```text min ≈ 12 KiB alignement 4 KiB fenêtre ≤ 90 s ``` et pouvait aller jusqu’à plusieurs `EMBLOCKSIZE`. ## Drop Stalled Sources Si aucun bloc n’est libre : - analyser les réservations ; - repérer une source bloquée/lente ; - annuler proprement son transfert ; - libérer ses blocs ; - permettre à une meilleure source de les reprendre. ## Delayed NNP Conserver brièvement une source qui semble `No Needed Parts` avant de la jeter, surtout en fin de fichier. ## PartCompletionSpeedUp Réduire la granularité près de la fin d’une partie. --- # 35. Signal historique Spike2 Spike2 a intégré : ```text Dazzle’s Faster Endgame ``` puis l’a retiré. Le changelog indique explicitement : ```text REMOVED: Dazzle's Faster Endgame (due to implementation of netfinity's Dynamic Block Requests) ``` # 36. Pourquoi ne pas faire un duplicate-request endgame simple ? Approche naïve : ```text dernier bloc déjà demandé à A → demander le même à B → garder le premier reçu ``` Avantage : ```text latence potentiellement minimale ``` Inconvénients : - octets dupliqués ; - gaspillage de bande passante upload des pairs ; - annulations tardives ; - corruption statistique des débits ; - complexité avec compression ; - davantage de contrôle réseau ; - risque de comportements agressifs. Dans un réseau coopératif ancien comme eD2K, ce coût est difficile à justifier en premier choix. # 37. Amélioration par rapport au Drop Stalled NetF L’audit historique a repéré une limite : NetF peut sélectionner une source stalled à abandonner sans vérifier explicitement que **la nouvelle source en attente possède réellement le bloc qu’on va libérer**. Pseudo-principe : ```text candidate pending block ∩ parts advertised by waiting source ≠ vide ``` Sinon : ```text ne pas dropper ``` --- # 38. ETA de bloc Pour chaque réservation : ```text remaining = (end - start + 1) - transferred eta = remaining / effective_rate ``` Mais `effective_rate` ne doit pas être seulement le débit instantané. Proposition à tester : ```text max( EWMA court terme, percentile bas récent, petit plancher ) ``` Éviter : - oscillations brutales ; - un pic de débit de quelques millisecondes ; - division par zéro. --- # 39. Définir « stalled » Ne pas reprendre directement les constantes NetF : ```text 5 s 10 s 15 s ``` Elles sont historiquement intéressantes mais arbitraires en 2026. Utiliser des conditions combinées : ```text aucun octet depuis X RTT / X secondes socket sans progression ETA bloc très supérieure à meilleure alternative source alternative prête pas de pression disque locale expliquant le retard ``` Très important pour DiskGuard : > **une source ne doit pas être déclarée stalled parce que notre propre disque bloque le pipeline.** --- # 42. Liaison avec le Dossier 05 — Trust / Anti-Leecher Le Dossier 05 a formalisé la distinction `REMOTE_CLAIM / LOCAL_MEASURED / CRYPTO_OR_HASH_VERIFIED`. Pour le scheduler de téléchargement : ```text Part Status reçu = REMOTE_CLAIM bloc effectivement reçu = LOCAL_MEASURED partie validée par hash = HASH_VERIFIED ``` --- # 43. Security / Trust : impact sur Endgame Un pair malveillant peut mentir sur sa part map. Un pair peut aussi : - annoncer une capacité ; - entrer en queue ; - servir lentement ou jamais ; - changer sa carte. Endgame est donc un endroit où un comportement hostile peut coûter cher : les derniers blocs ont une grande valeur marginale. # 43. Ne pas confondre Trust et ban Le futur score de confiance ne doit pas devenir : ```text une petite anomalie = ban ``` Utilisations possibles au départ : ```text télémétrie tie-break priorité de reallocation endgame choix entre deux sources équivalentes ``` Pas : ```text blacklist automatique permanente ``` sans preuves fortes. --- # 46. Première version : observation uniquement Avant toute modification, loguer pour chaque fichier : ```text [PART_SELECT] part=17 frequency=2 zone=VERY_RARE completion=41 already_requested=1 same_source_chunk=0 active_capable_sources=2 bandwidth_score=183 rank=3245 [BLOCK_RESERVE] source=... start=... end=... size=184320 pending_for_source=3 [ENDGAME_STATE] state=APPROACH remaining=18.2MiB unrequested=8.4MiB pending=9.8MiB eta=54s ``` --- # 47. Détecter le vrai « locked endgame » Trace : ```text [ENDGAME_LOCKED] remaining=721KiB unrequested=0 pending_blocks=5 active_sources=4 alternate_sources=3 ``` Puis détailler : ```text block A holder rate=42 KiB/s ETA=3s alternative=none block B holder rate=0.9 KiB/s ETA=124s alternative rate=220 KiB/s alternative has part=yes ``` C’est seulement dans le second cas qu’une réallocation devient évidente. --- # 48. Pseudocode de décision de réallocation ```text if state != LOCKED: do nothing for each pending block: current_holder = holder(block) alternatives = sources_that_can_serve(block) if alternatives.empty: continue current_eta = estimate(current_holder, block) best_alt_eta = best_estimate(alternatives, block) if current_holder is truly stalled OR current_eta >> best_alt_eta: consider targeted cancel/reassign ``` Ajouter garde-fous : ```text minimum time since reservation minimum confidence in alternate source no local disk pressure cooldown after reallocation only one rescue at a time initially ``` --- # 51. Rareté et ETA : objectifs parfois opposés Exemple : ```text Partie A 1 seule source 0 % téléchargé source à 15 KiB/s Partie B 5 sources 98 % téléchargé sources à 2 MiB/s ``` Le scheduler actuel possède déjà des mécanismes de priority inversion. ```text toujours ETA minimale ``` ni par : ```text toujours fréquence minimale ``` Il doit explicitement arbitrer : ```text résilience du swarm versus temps de complétion local ``` --- # 52. Métriques à collecter Par fichier : ```text part selections zone rarity distribution part completion latency number of simultaneous parts blocks reserved block reassignment candidates actual reassignments wasted bytes endgame duration time spent locked ``` Par source : ```text requested bytes received bytes mean block completion time timeout count part-map claim changes successful part claims ``` --- # 53. Métrique clé : Endgame Tax Définition proposée : ```text Endgame Tax = temps entre « 95 % de l’ETA théorique atteinte » et « fichier réellement terminé » ``` Ou plus mesurable : ```text temps réel des derniers N MiB / temps attendu au débit moyen précédent ``` Exemple : ```text 10 MiB restants débit précédent : 2 MiB/s théorique : 5 s réel : 85 s Endgame factor = 17× ``` Cette métrique permet de démontrer si Endgame Manager apporte réellement quelque chose. --- # 54. Métrique clé : Reservation Efficiency ```text bytes_reserved bytes_delivered bytes_cancelled bytes_reassigned bytes_duplicate ``` Objectif : ```text max(delivered) min(cancelled + duplicate) ``` --- # 55. Simulation avant activation ```text « j’aurais réalloué ce bloc » ``` sans le faire. Log : ```text [ENDGAME_SHADOW] block=... current_eta=92s alternate_eta=4s decision=WOULD_REASSIGN actual_result=current completed in 71s ``` Après suffisamment d’échantillons : ```text simulation gain estimé faux positifs réallocations qui auraient été inutiles ``` Cette approche est bien plus sûre qu’un portage immédiat d’un Mod de 2007. --- # 57. Changements sûrs à court terme ## C. Calcul shadow de l’état Endgame Aucune réallocation réelle. ## D. Distinguer dans les stats ```text announced availability effective availability ``` sans influencer encore le choix. --- # 58. Changements à réserver à une phase expérimentale - request size dynamique ; - réduction contextuelle de pending depth ; - targeted reallocation ; - NNP grace ; - tie-break fondé partiellement sur l’evidence source. Tous compatibles en principe avec le wire protocol historique, mais à tester avec : ```text eMule aMule anciens clients compressed blocks large files A4AF AICH obfuscation ``` --- # 59. Ce qu’il ne faut pas changer ```text PARTSIZE hash ED2K format des part maps sémantique OP_REQUESTPARTS structure des ranges legacy ``` sans extension négociée. Aucune nécessité n’apparaît actuellement de modifier ces éléments pour obtenir un meilleur endgame. --- # 60. Ce qu’il ne faut pas porter aveuglément depuis les Mods - constantes de vitesse anciennes ; - timers 5/10/15/30 s sans mesure ; - code de `SwitchBlocks()` historique ; - Dazzle DFE ; - drop d’une source sans vérifier qu’une alternative possède le bloc ; - comportement agressif de demandes dupliquées. Conserver les idées, pas les constantes. --- # 61. Première roadmap d’implémentation ## Étape 1 — PartSelection Trace Enregistrer les critères et rangs. ## Étape 2 — Pending Block Trace Enregistrer propriétaire, taille, âge, progression, ETA. ## Étape 3 — Endgame Shadow State NORMAL / APPROACH / ENDGAME / LOCKED / NNP_GRACE. ## Étape 4 — Shadow Reallocator Décision simulée, aucune action. ## Étape 5 — Dynamic Request Size Première modification réelle, plafonnée à `EMBLOCKSIZE`. ## Étape 6 — Targeted Reallocation Une seule réallocation simultanée, garde-fous stricts. ## Étape 7 — NNP Grace Seulement si les mesures montrent un gain. --- # 62. Conclusion L’étude du code officiel montre qu’eMule possède déjà un scheduler de parties sophistiqué, historiquement construit pour préserver la diffusion du fichier dans le réseau. Le problème principal n’est pas : ```text « eMule ne sait pas choisir les parties » ``` mais plutôt : ```text « la réservation de blocs devient trop rigide quand il ne reste presque plus rien à télécharger » ``` C’est exactement le problème que les Mods NetF, Spike2 et Silversurfer ont tenté de traiter. C’est une couche locale qui : ```text mesure prédit réduit la granularité et réalloue uniquement lorsqu’il existe une alternative réellement meilleure ``` tout en conservant le comportement standard hors endgame. --- --- # Dossier 07 — ED2K Hashset, ICH, AICH, corruption et récupération {#g07} --- # 0. Résumé exécutif Le pipeline d’intégrité de l’eMule Community 0.72a étudié est plus solide et plus stratifié que ne le laisse penser la vieille formule « eMule vérifie les chunks en MD4 ». Il faut distinguer au moins quatre couches : ```text 1. ED2K / MD4 identité historique du fichier et vérification des parties 2. ICH tentative de sauver une partie corrompue par retéléchargements progressifs 3. AICH arbre SHA-1 jusqu’à la granularité EMBLOCKSIZE = 180 KiB, récupération ciblée et authentifiée par le Master Hash 4. CorruptionBlackBox provenance locale des plages reçues et sanction éventuelle des sources ``` La règle d’intégrité centrale de la branche étudiée est très conservatrice : > **si MD4 et AICH sont tous les deux disponibles pour une partie, les deux doivent l’accepter.** Le mécanisme AICH ne fait pas confiance à un Recovery Packet isolé : il nécessite un Master Hash fiable, vérifie l’arbre reçu, compare les hashes locaux bloc par bloc puis repasse la partie reconstituée par `HashSinglePart()`. Le BlackBox historique attribue les plages aux **IP** et, dans son calcul de culpabilité, compte chaque enregistrement corrompu comme au moins : ```text EMBLOCKSIZE = 180 KiB ``` même si la source n’a fourni qu’une petite sous-plage. ```text Speculative Block Racing Partial Final-Tail Rescue réaffectations ciblées ``` qui peuvent créer une provenance plus fine et potentiellement plusieurs contributeurs autour du même bloc AICH. Ce n’est **pas un bug démontré** : les doublons déjà entièrement acquis sont rejetés avant enregistrement et l’Endgame nettoie ses races. Mais l’interaction doit être auditée. Le corpus historique renforce fortement cette priorité : NetF WARP avait introduit un **Dynamic Corruption Threshold « to avoid mass banning when using Dynamic Block Requests »** et un `CorruptionBlackBoxFix`. > **instrumenter l’attribution exacte sous E6bis avant de modifier le seuil de 32 %.** --- # 1. Racines protocolaires : Hashset eDonkey Le document historique `edonkey-protocol.txt` décrit déjà côté client-à-client : ```text 0x51 Hashset request 0x52 Hashset answer ``` et définit : ```text Part hash list ::= WORD HASH* File hash ::= HASH HASH ::= 16 bytes MD4 digest ``` Donc la vérification par hashes de parties ne vient pas d’une extension tardive d’eMule. Elle appartient au cœur historique eDonkey/eD2K. --- # 2. Taille fondamentale : PARTSIZE Dans la branche étudiée : ```cpp PARTSIZE = 9728000ui64; ``` Soit : ```text 9 728 000 octets ≈ 9,27734375 MiB ``` Cette valeur participe directement : - au découpage ED2K ; - au hashset ; - au FileHash ; - aux cartes de parties ; - à ICH ; - à l’organisation supérieure AICH. # 3. Taille fondamentale : EMBLOCKSIZE Dans `opcodes.h` : ```cpp EMBLOCKSIZE = 184320; ``` Soit : ```text 180 KiB ``` Cette valeur sert notamment : - à la granularité classique des demandes ; - au niveau inférieur de l’arbre AICH ; - à la récupération AICH ; - au calcul conservateur de CorruptionBlackBox. Contrairement à `PARTSIZE`, il faut distinguer : ```text taille logique historique utilisée par les mécanismes ``` et : ```text obligation absolue que toute requête fasse exactement 180 KiB ``` Une demande peut couvrir moins. AICH reste cependant organisé autour de feuilles de 180 KiB, sauf dernière feuille plus courte. --- # 4. Hash ED2K — comportement exact ## 4.1 Petit fichier Si le fichier tient dans une seule partie et ne tombe pas sur le cas frontière historique : ```text FileHash = MD4(file) ``` ## 4.2 Plusieurs parties Chaque partie reçoit un MD4 de 16 octets. Le hash final est calculé sur la concaténation des hashes de parties. ## 4.3 Cas historique des multiples exacts de PARTSIZE `KnownFile.cpp` et `FileIdentifier.cpp` confirment le comportement ED2K particulier. Pour : ```text taille = 1 × PARTSIZE ``` le hashset comporte : ```text hash(partie réelle) + MD4(partie vide) ``` Le hash MD4 du vide est le classique : ```text 31D6CFE0D16AE931B73C59D7E0C089C0 ``` `GetTheoreticalMD4PartHashCount()` reflète cette convention : ```cpp uResult = fileSize / PARTSIZE; if (uResult > 0) uResult++; ``` ### Conclusion Ce n’est pas un bug moderne à corriger. C’est un cas de compatibilité ED2K figé par l’histoire du réseau. --- # 5. Le Hashset n’est pas accepté aveuglément La branche 0.72a possède une classe `CFileIdentifier` qui vérifie la cohérence du hashset reçu. Pour MD4 : ```text FileHash attendu nombre théorique de hashes hashes de parties reçus → recalcul du FileHash → comparaison ``` Si le hashset ne reconstruit pas le FileHash attendu, il est rejeté. Donc : ```text un pair envoie une liste de hashes ≠ eMule l’accepte comme vérité ``` C’est déjà une barrière de confiance importante. --- # 6. Évolution du protocole Hashset `opcodes.h` de la branche étudiée contient encore : ```cpp OP_HASHSETREQUEST 0x51 // *DEPRECATED* OP_HASHSETANSWER 0x52 // *DEPRECATED* ``` mais également : ```cpp OP_HASHSETREQUEST2 0xB1 OP_HASHSETANSWER2 0xB2 ``` La nouvelle forme utilise : ```text FileIdentifier + Options + hashsets demandés ``` `CFileIdentifier::WriteHashSetsToPacket()` réserve notamment : ```text bit 0 → MD4 HashSet bit 1 → AICH HashSet ``` ### Intérêt documentaire Le site doit expliquer deux époques : ```text eDonkey / premier eMule : hashset MD4 dédié eMule moderne : FileIdentifier + MD4 + AICH ``` sans donner l’impression que les vieux opcodes ont simplement disparu. --- # 7. HashSinglePart() : le juge local `CPartFile::HashSinglePart()` est une fonction essentielle. Le commentaire source indique explicitement la politique : ```text si nous disposons de MD4 et AICH, les deux doivent être d’accord ``` Objectif du code : ```text ne jamais livrer comme correcte une partie que l’un des deux systèmes considère corrompue ``` ## MD4 Si le hashset MD4 est disponible : ```text hash réel de la partie vs hash MD4 attendu ``` ## AICH Si le Master Hash et le part-hashset sont disponibles : ```text arbre SHA local de la partie vs AICH Part Hash attendu ``` ## Retour ```cpp return !bMD4Error && !bAICHError; ``` # 8. Que se passe-t-il lorsqu’une partie complète échoue ? Dans `FlushBuffer()` : ```text partie devenue complète → HashSinglePart() ``` Si la fonction échoue : 1. log « part corrupt » ; 2. `AddGap()` rouvre la plage de la partie ; 3. la partie est ajoutée à `corrupted_list` ; 4. si AICH peut être utile, `RequestAICHRecovery()` est lancé ; 5. les statistiques de corruption sont augmentées. Point important : ```text les octets restent physiquement dans le .part ``` mais la gap list dit désormais : ```text ces positions ne sont plus considérées acquises ``` Cela permet justement à ICH/AICH de réhabiliter ensuite les zones prouvées bonnes. --- # 9. ICH — comportement réel dans 0.72a ICH ne possède pas une carte cryptographique fine indiquant immédiatement le bloc faux. Le mécanisme est opportuniste. Après corruption : ```text partie ouverte en gaps ``` Au fur et à mesure que des données sont retéléchargées, la branche vérifie de nouveau la partie. Dans `FlushBuffer()` : ```cpp else if (IsCorruptedPart(part) && (ICH enabled || forced)) { if (HashSinglePart(part)) { ... } } ``` Si la partie complète redevient valide avant d’avoir remplacé tous les octets : ```text les gaps restants sont refermés les anciennes données correspondantes sont sauvées ``` ### Caractérisation ```text ICH = récupération par revalidation globale ``` Ce n’est pas : ```text ICH = localisation précise du mauvais bloc ``` --- # 10. AICH — architecture du hash tree `SHAHashSet.h` documente directement l’organisation. Le niveau supérieur représente le fichier. Puis : ```text parties de 9,28 MiB ``` puis, à l’intérieur des parties : ```text blocs de 180 KiB ``` Le hash AICH fait : ```cpp HASHSIZE = 20; ``` et l’implémentation `CSHA` utilisée est SHA-1. Schéma conceptuel : ```text MasterHash ├── branche(s) │ ├── PartHash │ │ ├── 180K hash │ │ ├── 180K hash │ │ └── ... │ └── PartHash └── ... ``` Ce n’est pas un hashset plat. C’est un arbre permettant de fournir seulement les hashes nécessaires pour authentifier une partie jusqu’au Master Hash. --- # 11. AICH V2 et grands fichiers `SHAHashSet.h` précise que la version 2 du mécanisme prend en charge des identifiants de hashes 32 bits pour les grands fichiers. `CreatePartRecoveryData()` choisit : ```text identifiants 16 bits ou identifiants 32 bits ``` selon le type de fichier. Ce détail montre que l’AICH actuel a lui-même évolué pour suivre l’extension des tailles de fichiers eMule. --- # 12. États de confiance AICH La branche définit : ```cpp AICH_ERROR AICH_EMPTY AICH_UNTRUSTED AICH_TRUSTED AICH_VERIFIED AICH_HASHSETCOMPLETE ``` Il faut absolument conserver cette distinction dans la documentation. ### UNTRUSTED Le client a vu une valeur, mais n’a pas encore de raison suffisante de la considérer comme référence. ### TRUSTED Un consensus réseau suffisant a été atteint. ### VERIFIED La valeur vient d’un chemin où le client peut la considérer comme vérifiée. ### HASHSETCOMPLETE Pour un fichier local complet, l’arbre entier a été calculé. --- # 13. Consensus AICH : découverte de code `CAICHRecoveryHashSet::UntrustedHashReceived()` collecte plusieurs Master Hashes reçus et les origines qui les annoncent. La branche utilise actuellement : ```cpp MINUNIQUEIPS_TOTRUST = 10 MINPERCENTAGE_TOTRUST = 92 ``` et accepte le hash dominant lorsque : ```text au moins 10 groupes/origines ET au moins 92 % des déclarations ``` Une préférence : ```text AICHTrustEveryHash ``` permet de court-circuiter ce mécanisme. Dans `Preferences.cpp`, sa valeur par défaut est : ```text false ``` # 14. Anomalie documentaire mineure Près du code de confiance AICH, un commentaire historique parle encore d’un seuil de : ```text 95 % ``` alors que la constante exécutée vaut : ```text 92 ``` Comme pour le commentaire `2^25` rencontré dans le dossier HighID : ```text comportement du code = clair commentaire = légèrement obsolète ``` # 15. RequestAICHRecovery() Une récupération n’est demandée que si : ```text Master Hash valide ET status == TRUSTED ou VERIFIED ``` Le code refuse également : - les tailles trop petites pour lesquelles la récupération n’a pas de sens ; - une seconde demande déjà pending pour la même partie. Il vérifie d’abord si les données AICH nécessaires sont déjà en mémoire. --- # 16. Sélection du pair AICH `RequestAICHRecovery()` parcourt les sources. Une source n’est candidate que si : ```text support AICH + possède un AICH hash annoncé + aucune requête AICH déjà pending + Master Hash == notre Master Hash fiable ``` Les HighID sont préférés. Le choix final parmi les candidats utilise historiquement : ```cpp rand() ``` ### Analyse Ce `rand()` n’a pas la même gravité que le challenge SecureIdent du Dossier 03. Ici il ne produit pas un secret ni une preuve cryptographique. Il choisit simplement un pair parmi plusieurs candidats compatibles. Ce n’est donc **pas une priorité de sécurité**. --- # 17. Échec d’une source AICH `CAICHRecoveryHashSet::ClientAICHRequestFailed()` : ```text efface l’état pending récupère le contexte de la requête si le PartFile existe toujours → RequestAICHRecovery(part) de nouveau ``` Le système sait donc essayer une autre source. # 18. Structure du Recovery Packet `CreatePartRecoveryData()` détermine le nombre exact de hashes nécessaires : ```text hashes du chemin dans l’arbre + hashes des blocs EMBLOCKSIZE de la partie ``` `ReadRecoveryData()` : - vérifie la quantité attendue ; - refuse l’identifiant du Master Hash (`identifier == 1`) afin qu’un packet de recovery ne puisse pas remplacer la racine ; - vérifie les limites des identifiants ; - insère les hashes dans l’arbre ; - appelle `VerifyHashTree(true)` ; - vérifie ensuite que chaque hash de niveau bloc demandé est réellement présent. ### Conclusion Le Recovery Packet ne peut pas simplement décréter : ```text « tel bloc est bon » ``` sans rester cohérent avec le Master Hash déjà accepté. --- # 19. AICHRecoveryDataAvailable() Une fois l’arbre de la partie disponible : 1. `FlushBuffer()` force les données locales sur disque ; 2. eMule calcule son propre arbre SHA de la partie corrompue ; 3. boucle sur la partie par tranches `EMBLOCKSIZE` ; 4. compare hash local et hash vérifié ; 5. pour chaque bloc valide : - `FillGap()` - `RemoveBlockFromList()` - `CorruptionBlackBox.VerifiedData()` 6. pour chaque bloc faux : - `CorruptionBlackBox.CorruptedData()` 7. `CorruptionBlackBox.EvaluateData(part)`. C’est une excellente séparation : ```text AICH localise GapList décide ce qu’il manque BlackBox conserve la provenance ``` --- # 20. Sanity check après récupération Après réhabilitation des bons blocs, si la partie semble désormais complète : ```text HashSinglePart(part) ``` est relancé. Si MD4/AICH n’acceptent toujours pas la partie : ```text AddGap(part entière) ``` et, dans certains cas, l’état AICH peut passer en erreur. Donc : > **la récupération ne contourne jamais le juge final.** --- # 21. Rehash final du fichier `PartFileHashFinished()` effectue encore une vérification des parties complètes. Il calcule pour chaque partie : ```text MD4 AICH si disponible ``` Si l’un échoue : ```text la partie est rouverte en gap ``` Si MD4 et AICH sont tous deux présents mais ne donnent pas le même verdict : ```text DebugLogError ``` Le fichier ne passe en finalisation normale que s’il n’y a pas d’erreur. --- # 22. known2_64.met Dans la branche Community inspectée : ```cpp KNOWN2_MET_FILENAME = "known2_64.met" OLD_KNOWN2_MET_FILENAME = "known2.met" ``` Le fichier stocke les AICH Recovery Hashsets calculés localement. `SaveHashSet()` enregistre notamment : ```text Master Hash nombre de hashes de niveau inférieur hashes des blocs ``` `LoadHashSet()` : - retrouve le Master Hash ; - vérifie le nombre attendu ; - recharge les feuilles ; - recalcule l’arbre ; - compare le Master Hash reconstruit à celui attendu. # 23. CorruptionBlackBox : structure Constante : ```cpp CBB_BANTHRESHOLD = 32 ``` `TransferredData(start,end,client)` conserve : ```text partie plage relative IP source état BBR_NONE ``` Les enregistrements adjacents compatibles peuvent être fusionnés. Lorsque des octets sont réécrits par une autre source, le code ajuste/sépare les anciens enregistrements afin que la provenance reflète la dernière donnée conservée sur la plage. --- # 24. Provenance enregistrée avant écriture Dans `CPartFile::ProcessBlockPacket()` : ```text IsComplete(start,end) ? → si déjà écrit, retour avant BlackBox ``` Puis : ```cpp m_CorruptionBlackBox.TransferredData(start, end, client); ``` avant ajout au buffer. ### Implication pour Speculative Racing Une copie tardive qui arrive après que toute sa plage a déjà été acquise est normalement rejetée avant de remplacer la provenance. C’est un garde-fou important. Il réduit fortement le risque que le « perdant » d’une course soit arbitrairement considéré comme l’auteur des données gagnantes. --- # 25. VerifiedData() / CorruptedData() Après vérification : ```text BBR_NONE → BBR_VERIFIED ``` ou : ```text BBR_NONE / BBR_VERIFIED → BBR_CORRUPTED ``` Le code sait scinder les enregistrements lorsqu’une sous-plage seulement change de statut. L’architecture peut donc déjà conserver une provenance plus fine qu’une partie entière. --- # 26. EvaluateData() : calcul de culpabilité Lorsqu’un bloc AICH est déclaré corrompu : 1. liste les IP impliquées dans les records corrompus ; 2. ignore celles déjà bannies ; 3. parcourt les records du fichier ; 4. cumule par IP : - données `VERIFIED` - données `CORRUPTED` 5. calcule : ```text corrupt % = corrupt / (corrupt + verified) ``` Si : ```text corrupt % > 32 ``` l’adresse est bannie. Sinon elle est signalée/suivie. --- # 27. Détail crucial : plancher de 180 KiB Dans `EvaluateData()` : ```cpp aDataCorrupt[k] += max(record_length, EMBLOCKSIZE); ``` Donc un record corrompu de : ```text 20 KiB ``` peut compter comme : ```text 180 KiB ``` pour l’évaluation de cette source. ### Pourquoi ? AICH sait essentiellement dire : ```text ce bloc de contrôle de 180 KiB est faux ``` Il ne sait pas nécessairement prouver quel octet précis à l’intérieur a cassé le hash. Le code applique donc une pénalité conservatrice à l’échelle du bloc. --- # 28. Problème potentiel avec contributions mixtes Exemple conceptuel : ```text Bloc AICH de 180 KiB Source A : 150 KiB Source B : 30 KiB ``` Le bloc final échoue. La seule preuve cryptographique disponible est : ```text les 180 KiB assemblés ne correspondent pas au hash attendu ``` Elle ne permet pas de dire automatiquement : ```text A est fautive ``` ou : ```text B est fautive ``` Si les deux possèdent des records recouvrant ce bloc, une politique conservatrice peut charger lourdement les deux. ### E6bis `Partial Final-Tail Rescue` et les réallocations rendent les contributions mixtes sur les zones finales plus plausibles qu’avec le scheduler historique. D’où la nécessité d’un audit. --- # 29. Speculative Racing : pourquoi le risque est limité mais réel à étudier Le comportement actuel possède plusieurs protections : ```text plage déjà complète → paquet ignoré Racing Cleanup Partial Final-Tail Rescue ne demande que la gap réelle ``` Donc : ```text deux requêtes envoyées ≠ deux écritures conservées ``` Cependant les situations suivantes méritent mesure : - arrivée alternée de sous-plages ; - challenger qui remplit seulement une queue de bloc ; - source primaire ayant déjà fourni un préfixe ; - corruption d’un bloc AICH constitué de plusieurs provenances. ### Statut ```text RISQUE ARCHITECTURAL À MESURER PAS BUG E6bis DÉMONTRÉ ``` --- # 30. Confirmation historique : NetF WARP Le changelog NetF WARP 0.2a contient exactement : ```text CorruptionBlackBoxFix - ensure all data been processed before banning Dynamic Corruption Threshold - to avoid mass banning when using Dynamic Block Requests Only Recover Banned Data - only redownload data that is "known" to be corrupt ``` La présence conjointe : ```text DBR + CorruptionBlackBoxFix + Dynamic Corruption Threshold ``` est extrêmement instructive. Une modification de la granularité des requêtes avait déjà obligé un développeur de Mod à repenser l’évaluation de corruption. --- # 31. Mais NetF fournit aussi un avertissement Le même document annonce dans ses « Known bugs » : ```text 'Only Recover Banned Data' might cause looping downloads or complete stalls if corruption occurs and recovery fails ``` Et une version ultérieure mentionne avoir corrigé : ```text some corrupt parts downloading forever ``` ### Conclusion Ne pas porter `Only Recover Banned Data` tel quel. Le statut `banned` est une conclusion heuristique. Il ne doit pas devenir la source cryptographique de vérité permettant de décider quelles données sont bonnes. --- # 32. Pourquoi la logique « banni = mauvais octets » est circulaire Chaîne dangereuse : ```text CBB soupçonne A → A est banni → on décide que seules les données de A sont mauvaises → on conserve les autres → si l’hypothèse CBB était fausse ou incomplète : la partie ne peut jamais converger correctement ``` C’est précisément le genre de boucle capable d’expliquer : ```text redownload loop complete stall ``` # 34. Shadow audit CBB × E6bis Pour chaque bloc AICH finalement corrompu : ```text part aich_block_index contributors accepted_bytes_by_contributor raced? partial-tail-rescue? primary/challenger verified_history_by_contributor historical_CBB_charge historical_CBB_ratio historical_ban_decision ``` Puis produire un événement : ```text [CBB_SHADOW] Part 42 AICHBlock 17 BlockSize 184320 Source A bytes accepted 151552 role ENDGAME_PRIMARY CBB charge 184320 Source B bytes accepted 32768 role ENDGAME_CHALLENGER CBB charge 184320 Result block CORRUPT CBB would ban A no CBB would ban B yes mixed provenance yes ``` C’est exactement le type de cas que nous devons observer avant tout changement. --- # 35. Première amélioration possible : retarder une sanction ambiguë Si les mesures montrent réellement des faux positifs autour des blocs à provenance multiple, la première évolution ne devrait pas être : ```text augmenter 32 % à 50 % ``` pour tout le monde. Approche plus fine : ```text bloc corrompu + plusieurs contributeurs + aucune preuve permettant de distinguer le fautif → evidence = AMBIGUOUS ``` Puis : ```text ne pas déclencher seul un ban fort mais conserver l’anomalie et attendre une répétition indépendante ``` Cette stratégie colle mieux au Dossier 05 : ```text preuves graduées réactions graduées ``` --- # 36. Deuxième amélioration possible : identité sécurisée Le BlackBox historique indexe la provenance sur : ```text IP ``` `EvaluateData()` finit par : ```text FindClientByIP() ou AddBannedClient(IP) ``` ### Limite 2026 Une IP peut être : ```text dynamique partagée par NAT partagée par CGNAT sortie VPN ``` ### Prudence Ne pas remplacer le ban IP par un ban UserHash non authentifié. Un UserHash seul est falsifiable. --- # 37. Troisième amélioration possible : séparer attribution et sanction Architecture historique : ```text CorruptionBlackBox → mesure → décide ban ``` ```text RangeProvenance ↓ IntegrityEvidence ↓ Trust Policy ↓ action graduée ``` Avantage : la couche d’intégrité dit : ```text ce que nous savons ``` et la Policy dit : ```text ce que nous faisons ``` sans mélanger preuve et sanction. --- # 38. Pas de score global « leecher » Connexion directe avec Dossier 05. Une source qui a envoyé : ```text 1 bloc corrompu + 10 GiB valides ``` n’est pas équivalente à une source qui envoie : ```text 90 % de données corrompues ``` Et l’intégrité ne doit pas être fusionnée avec : ```text ModString QR distant nickname ``` Dimension dédiée : ```text DataIntegrity ``` avec preuve issue des hashes. --- # 39. AICH root trust : amélioration de diagnostic ```text AICH Master Hash state=UNTRUSTED source_count=... AICH Master Hash state=TRUSTED support=... consensus=... AICH Master Hash state=VERIFIED origin=... ``` sans exposer cette complexité dans l’UI normale. ### Intérêt Lorsqu’une récupération AICH ne démarre pas, l’utilisateur saurait si la raison est : ```text pas de Master Hash Master Hash non encore trusted aucun pair compatible avec le même hash recovery request en cours recovery data invalide ``` Aujourd’hui ces états sont surtout visibles dans les logs/debug. --- # 40. `AICHTrustEveryHash` Préférence actuelle : ```text AICHTrustEveryHash=false ``` ### Recommandation Conserver le défaut. ```text libellé explicite avertissement pas de recommandation d’activation ``` Car cette option désactive précisément le consensus destiné à empêcher qu’un seul pair impose une racine AICH. --- # 41. known2_64.met et résilience locale La récupération AICH dépend également de métadonnées locales. Avant d’ajouter un nouveau système : ```text ``` pour vérifier s’il protège déjà correctement : ```text .part.met .part.met.bak known.met known2_64.met preferences.dat cryptkey.dat preferencesKad.dat ``` # 42. Métadonnées PartFile : idée historique SF/IOM/X-Mod Le corpus des anciens Mods contient des approches de sauvegarde/reconstruction des métadonnées `.part.met`. Conceptuellement elles peuvent être utiles après : ```text part.met endommagé crash migration ``` ```text .part.met.bak + ses propres fonctions de backup ``` ### Statut ```text AUDIT AVANT PORTAGE ``` Ne pas dupliquer un mécanisme existant. --- # 43. MD4 et SHA-1 en 2026 Du point de vue cryptographique moderne : ```text MD4 = cassé SHA-1 = collision-resistant insuffisant pour de nouveaux designs ``` ```text remplacer MD4 et SHA-1 dans le protocole ``` ### ED2K Le MD4 fait partie de l’identité historique du fichier. Le changer donne un autre identifiant et casse les liens existants. ### AICH Le SHA-1 et le format du tree font partie de l’interopérabilité AICH. Un client seul ne peut pas faire comprendre un nouvel arbre aux anciens pairs. --- # 46. Réinitialisation de l’état Endgame après corruption Lorsqu’une partie complète devient corrompue : ```text AddGap() ``` réintroduit brutalement de nouveaux octets manquants. Cela change : ```text remaining bytes remaining blocks remaining parts part dispersion source usefulness ETA ``` E6bis calcule justement son risque à partir de ces dimensions. # 47. Delayed NNP retrouve ici tout son sens Dossier 06 : ```text Delayed NNP à auditer ``` Dossier 07 explique pourquoi la fonction avait été créée. Après corruption : ```text partie auparavant complète → devient gap → une source qui semblait NNP peut soudain redevenir utile ``` Donc la fenêtre de grâce historique de NetF n’est pas seulement une optimisation générale. Elle est directement liée au cycle d’intégrité. # 48. Corruption telemetry minimale à conserver ```text parts_corrupt ich_parts_saved ich_bytes_saved aich_recovery_requests aich_recovery_success aich_recovery_failover aich_bytes_recovered cbb_suspects cbb_bans cbb_mixed_provenance_blocks endgame_corrupt_parts raced_blocks_later_corrupt partial_tail_blocks_later_corrupt ``` Sans stocker durablement les IP ou UserHash complets. --- # 49. Indicateur public possible ```text INTÉGRITÉ Parties vérifiées 1 248 Corruptions détectées 3 ICH récupérées 1 AICH récupérées 2 Données sauvées AICH 17,6 Mio ``` Éviter : ```text 3 leechers détectés ``` car : ```text corruption détectée ≠ intention malveillante prouvée ``` --- # 50. Integrity Health diagnostic Vue avancée possible : ```text FICHIER ED2K Hash OK MD4 Hashset complet AICH Master TRUSTED AICH Part Hashset complet Recovery Data disponible PARTIE 42 MD4 CORRUPT AICH CORRUPT Blocs AICH 53 Vérifiés 52 À retélécharger 1 Recovery source nouvelle tentative ``` Cette vue serait extrêmement utile pour comprendre les cas « bloqué à 99,9 % ». --- # 59. Ordre de travail recommandé ## A — Audit CBB × E6bis Priorité la plus forte. Aucune modification comportementale initiale. ## B — Audit Endgame state après corruption Très lié à E6bis et facile à instrumenter. ## C — Audit Delayed NNP autour d’une réouverture de gap L’étude intégrité donne enfin le contexte exact. ## D — Integrity diagnostics Statistiques et raisons d’échec AICH. ## E — Backup metadata ## F — seulement si preuves Modifier politique de sanction CBB. --- # 60. Ce que nous ne devons pas faire ```text remplacer MD4 dans ED2K remplacer SHA-1 AICH unilatéralement accepter un AICH root unique non fiable faire confiance à un recovery packet sans arbre valide considérer toute corruption comme du leeching porter Only Recover Banned Data tel quel augmenter arbitrairement le seuil CBB désactiver le final rehash pour accélérer ``` --- # 63. Relation avec le Dossier 05 Trust Le Dossier 05 proposait : ```text REMOTE_CLAIM LOCAL_MEASURED HASH_VERIFIED CRYPTO_VERIFIED ``` Le présent dossier donne un cas parfait : ```text pair annonce sa PartStatus → REMOTE_CLAIM octets reçus → LOCAL_MEASURED bloc AICH comparé au tree → HASH_VERIFIED / HASH_FAILED partie MD4 + AICH valide → HASH_VERIFIED fort ``` Le futur Trust Engine ne doit jamais remplacer cette hiérarchie. Il doit la consommer. --- # 64. Relation avec le Dossier 06 Endgame Baseline : ```text Endgame by Akshaan E6bis ``` Le présent dossier n’a pas trouvé de raison de modifier son comportement immédiatement. Il révèle cependant deux audits obligatoires avant une évolution E8 : ```text CBB × provenance fine/racing corruption → recalcul état Endgame / NNP ``` Cela devient une dépendance explicite de la roadmap. --- # 65. Conclusion Le système d’intégrité eMule est l’un des sous-systèmes les mieux construits de l’ancien client : ```text Hashset → validation de partie ICH → récupération opportuniste AICH → récupération fine authentifiée par arbre CorruptionBlackBox → provenance et anti-abus ``` Elle se trouve à la jonction entre deux époques : ```text CorruptionBlackBox pensé pour des blocs historiques de 180 KiB vs Endgame E6bis capable de racing, de rescue partiel et de provenance plus fine ``` NetF WARP nous donne un signal historique très fort : son propre passage aux Dynamic Block Requests l’avait conduit à ajouter un `CorruptionBlackBoxFix` et un seuil de corruption dynamique. Mais son expérience montre aussi ce qu’il ne faut pas faire : `Only Recover Banned Data` pouvait conduire à des boucles et des blocages. > **mesurer précisément qui a réellement fourni quels octets, conserver les hashes comme arbitres, puis rendre les sanctions plus explicables si les mesures démontrent que le modèle IP/180K historique est devenu trop grossier.** --- --- # Dossier 08 — Source Exchange SX1/SX2, qualité des sources et pollution {#g08} --- # 0. Résumé exécutif Le Source Exchange est un mécanisme historique de découverte pair-à-pair qui complète : ```text serveurs eD2K Kad sources passives liens / sources explicites ``` Le principe est ancien : le document communautaire `edonkey-protocol.txt` de 2002 décrit déjà : ```text 0x81 eMule Sources request 0x82 eMule Sources answer ``` avec, par source : ```text client IP/ID client port server IP server port ``` La branche moderne conserve cette compatibilité sous le nom SX1 tout en ajoutant SX2 : ```text OP_REQUESTSOURCES2 OP_ANSWERSOURCES2 SOURCEEXCHANGE2_VERSION = 4 ``` SX2 n’est pas « le format v2 ». Il s’agit d’un mécanisme de négociation pouvant transporter aujourd’hui le format de source v1 à v4. La branche étudiée possède déjà plusieurs défenses solides : ```text temporisation par client et par fichier politique dépendant de la rareté soft source limit version connue taille exacte du paquet IsGoodIP IPFilter ban list CanAddSource DeadSourceList compatibilité crypt layer dédoublonnage A4AF ``` Mais le Dossier 08 met au jour deux limites architecturales intéressantes. ## Découverte D08-A — la provenance SX précise est perdue Au moment de parser une réponse, eMule connaît : ```text provider = le client qui a envoyé le paquet ``` mais les nouvelles sources sont ensuite seulement marquées : ```cpp SF_SOURCE_EXCHANGE ``` Le `pClient` fournisseur n’est pas conservé comme provenance durable de la source. Conséquence : ```text on sait QU'UNE source vient de SX mais plus QUI nous l'a donnée ``` Il devient donc impossible, avec le modèle actuel seul, de calculer a posteriori : ```text provider X → 100 sources annoncées → 73 nouvelles → 41 jointes → 25 ayant confirmé le fichier → 12 ayant transféré ``` Le Dossier 05 avait déjà proposé `Source Exchange Quality Metrics`. Le Dossier 08 montre maintenant où brancher cette idée. ## Découverte D08-B — pas de corrélation forte request/answer Le vieux protocole SX n’a pas de Request ID. Dans le chemin `OP_ANSWERSOURCES` / `OP_ANSWERSOURCES2` inspecté, la réponse est acceptée si notamment : ```text handshake terminé hash correspondant à un fichier téléchargé format SX valide ``` Il n’apparaît pas de contrôle strict : ```text « cette réponse correspond exactement à une demande SX encore pending » ``` ```text EXPECTED LATE UNSOLICITED ``` Aucun blocage immédiat n’est recommandé. ## Décision de ce dossier Ne pas réécrire SX. Ne pas accélérer globalement les reasks. Ne pas porter aveuglément les vieux `Source Cache` / `Save Load Sources`. Première expérimentation : > **SX Provider Provenance + Quality Metrics, en shadow telemetry uniquement.** Puis, si les mesures sont concluantes : ```text provider quality → tie-break pour choisir quel pair interroger → surtout en Tail Source Wakeup / SOURCE_LIMITED ``` sans toucher au wire protocol. --- # 1. Racine historique : SX1 Le protocole communautaire eDonkey/eMule de 2002 décrit dans ses extensions eMule : ```text ::= 0x81 ::= 0x82 <2 Byte len> [len]( <2 client.port> <4 server.ip> <2 server.port> ) ``` Ce document est historique et reverse-engineered/communautaire, pas une norme officielle. Mais il fournit une preuve directe que Source Exchange existe très tôt dans l’histoire d’eMule. --- # 2. Pourquoi SX est différent des serveurs et de Kad Serveur : ```text index centralisé sur le serveur connecté ``` Kad : ```text index distribué selon le DHT ``` Source Exchange : ```text connaissance locale d'un pair déjà lié au fichier ``` Chaque mécanisme a donc une surface d’échec différente. SX est particulièrement contextuel : ```text je demande à quelqu'un déjà impliqué dans le fichier ``` mais aussi particulièrement subjectif : ```text je ne reçois que ce que ce pair veut / peut me raconter ``` --- # 3. Évolution des opcodes Branche étudiée : ```cpp OP_REQUESTSOURCES 0x81 // deprecated OP_ANSWERSOURCES 0x82 // deprecated OP_REQUESTSOURCES2 0x83 OP_ANSWERSOURCES2 0x84 ``` Important : ```text deprecated ≠ supprimé ``` SX1 reste traité pour compatibilité. --- # 4. Capability negotiation Deux notions coexistent historiquement. ## SX1 version Une version SX1 est annoncée dans les options eMule historiques. Elle décrit le format de source que le client sait manipuler. ## Support SX2 Une capacité séparée annonce : ```text SupportsSourceExchange2 ``` SX2 permet alors au demandeur d’expliciter : ```text version maximale désirée options ``` Le répondant choisit : ```cpp min(requested_version, SOURCEEXCHANGE2_VERSION) ``` --- # 5. VERSION actuelle du contenu Dans la branche inspectée : ```cpp SOURCEEXCHANGE2_VERSION = 4 ``` Le même numéro 4 apparaît dans l’implémentation aMule inspectée. Cela fournit une corroboration indépendante du format historique moderne. --- # 6. SX2 request : options réservées Le client envoie actuellement : ```text RequestedVersion = 4 Options = 0 ``` Le répondant possède un paramètre `nRequestedOptions`, mais la branche ne définit aucun comportement utile pour des bits non nuls. Elle journalise les options inconnues. # 7. Nuance de documentation : ordre du paquet SX2 Le code d’émission séparée inspecté écrit : ```text Version Options FileHash ``` Le code aMule d’émission fait de même. Dans les multipackets, le contexte fichier est déjà porté par l’enveloppe et le sous-opcode SX2 ajoute : ```text Version Options ``` Certaines anciennes annotations d’opcodes sont moins explicites et peuvent suggérer un autre ordre. ### Règle documentaire ```text ne pas prendre le commentaire court d'opcodes.h comme spécification wire normative ``` Toujours vérifier : ```text writer + reader + implémentation indépendante / capture ``` avant de publier un layout octet par octet. --- # 8. Réponse SX2 `CreateSrcInfoPacket()` écrit : ```text Version réellement utilisée FileHash Count Sources... ``` Le récepteur lit explicitement la version de la réponse avant le FileHash. SX1 n’a pas ce préfixe de version explicite dans la réponse. --- # 9. Format v1 Par source : ```text ID/IP 4 ClientPort 2 ServerIP 4 ServerPort 2 ---------------- 12 octets ``` C’est le format décrit par le document historique. --- # 10. Format v2 Ajout : ```text UserHash 16 octets ``` Taille : ```text 28 octets/source ``` Le UserHash reçu reste une assertion faite par le fournisseur SX au sujet d’un tiers. Il ne faut pas lui donner le statut d’un SecureIdent validé. --- # 11. Format v3 Le nombre d’octets reste comparable à v2. Le changement important est l’interprétation de l’ID : ```text Hybrid ID ``` au lieu de l’ancienne représentation IP directe. Le parseur traite donc l’endianness / conversion différemment suivant la version. --- # 12. Format v4 Ajout d’un octet d’options de connexion : ```text bit 0 crypt layer supported bit 1 crypt layer requested bit 2 crypt layer required autres bits réservés ``` Dans le code d’envoi inspecté, le Direct UDP Callback n’est pas publié dans cet octet. ### Conséquence Ne pas documenter v4 comme contenant toute la connectivité moderne possible. Il contient ce que le writer met réellement. --- # 13. SX2 ≠ v2 ```text SX generation : SX1 / SX2 source record version : v1 / v2 / v3 / v4 ``` Toujours séparer les deux dans nos docs et logs. Proposition de champs : ```text sx_transport = 1 | 2 sx_record_version = 1..4 ``` --- # 14. Quand une demande SX peut partir `IsSourceRequestAllowed()` vérifie d’abord : ```text Extended Protocol disponible ET SX2 ou SX1 > 1 ET source count sous le soft limit ``` Puis la rareté et les timers décident. --- # 15. Constantes historiques Branche étudiée : ```text SOURCECLIENTREASKS = 40 min SOURCECLIENTREASKF = 5 min RARE_FILE = 50 MINCOMMONPENALTY = 4 ``` Ces constantes appartiennent à la politique locale. Elles ne sont pas intrinsèques au format wire SX. --- # 16. Très rare Cas approximatif : ```text uSources <= RARE_FILE / 5 <= 10 ``` La demande est facilitée. Pour le fichier directement demandé au pair, la contrainte principale est le timer client. ### Sens Sur un fichier presque sans sources : ```text la découverte vaut plus que l'économie de quelques paquets ``` --- # 17. Rare Jusqu’à environ : ```text 50 sources ``` la branche combine : ```text ~40 min par client ~5 min par fichier ``` avec plusieurs nuances A4AF et « valid source count ». --- # 18. Commun Pour un fichier déjà riche en sources : ```text client timer × MINCOMMONPENALTY file timer × MINCOMMONPENALTY ``` Ordres de grandeur : ```text 160 min 20 min ``` Le mécanisme évite donc naturellement le broadcast social permanent. --- # 19. Réponse à une demande distante Le serveur TCP client-à-client applique lui aussi un délai. Pour un partfile rare : ```text première demande ou > SOURCECLIENTREASKS ``` Pour un fichier non rare ou complet : ```text première demande ou > SOURCECLIENTREASKS * MINCOMMONPENALTY ``` ### Défense Cela limite l’utilisation de notre client comme « oracle de sources » sollicité en boucle. --- # 20. Séquence protocolaire préférée Le code rappelle qu’un client devrait normalement suivre : ```text OP_REQUESTFILENAME → OP_SETREQFILEID → OP_REQUESTSOURCES ``` Pourquoi ? Parce que cette séquence permet au répondant de connaître le Part Status du demandeur. Sans cette information, le choix des sources est moins précis. Le code indique alors qu’il pourra fournir un ensemble plus aléatoire. --- # 21. Source selection côté réponse `CreateSrcInfoPacket()` ne renvoie pas la totalité brute des clients connus. La liste candidate provient de la liste d’upload associée au fichier. Sont notamment exclus : ```text LowID requester lui-même client hors états d'upload utiles ``` --- # 22. Part-aware Source Exchange Si la carte des parties du demandeur est disponible : ```text source candidate ↓ possède-t-elle au moins une partie que le demandeur n'a pas ? ``` Si oui : ```text candidate utile ``` Si la source candidate ne fournit pas de Part Status exploitable : ```text elle peut tout de même être transmise ``` avec la philosophie historique : ```text send it and hope for the best ``` --- # 23. Lorsque le demandeur n’a pas de Part Status La branche essaie alors de transmettre des sources ayant au moins une partie complète. Si le statut du candidat est lui-même inconnu : ```text il peut encore être envoyé ``` Cela conserve la découvrabilité au prix d’une précision moindre. --- # 24. Pourquoi cette tolérance est importante Règle trop stricte : ```text absence de Part Status → ne jamais transmettre ``` risquerait de pénaliser : ```text anciens clients clients partiellement compatibles fichiers rares ``` Le protocole historique privilégie donc souvent : ```text interopérabilité avant certitude ``` --- # 25. Sources LowID Le writer SX classique exclut les sources LowID. Cela évite de diffuser des contacts qui ne sont pas directement joignables comme un HighID classique et dont la joignabilité dépend d’un serveur, Buddy ou mécanisme de callback. # 26. Limite de réponse Le compteur est incrémenté puis testé avec : ```text if (nCount > 500) break; ``` Donc la borne réelle peut atteindre : ```text 501 ``` C’est probablement un petit artefact historique. ### Statut ```text PAS IMPORTANT ``` Ne pas modifier pour « faire propre » sans nécessité : cela pourrait seulement créer des différences inutiles. --- # 27. Compression Si le paquet de réponse dépasse environ : ```text 354 octets ``` il peut être compressé. Le Source Exchange a donc déjà une optimisation d’overhead. --- # 28. Réception SX1 : inférer le format par la taille SX1 ne porte pas dans le paquet de réponse une version de record explicite équivalente à SX2. `AddClientSources()` utilise donc : ```text nCount + taille restante exacte + version SX annoncée par le client ``` pour décider si le contenu correspond à v1, v2/v3 ou v4. --- # 29. Défense structurelle importante Le commentaire historique du parseur explique explicitement que ce contrôle évite une probabilité plus élevée de : ```text wrong source data wrong userhashes duplicate sources ``` Si : ```text count × record_size != bytes restants ``` le paquet est rejeté. --- # 30. Réception SX2 Pour SX2 : ```text version explicite ``` Le parser refuse : ```text version 0 version > SOURCEEXCHANGE2_VERSION ``` puis applique lui aussi une vérification de taille cohérente avec le format. --- # 31. Filtrage d’entrée Pour chaque source HighID reçue : ```text IsGoodIP() IPFilter ban list ``` Puis : ```text CanAddSource() ``` vérifie notamment certains cas « nous-mêmes » et les impossibilités LowID↔LowID. --- # 32. Limite maximale du fichier Même un paquet entièrement valide ne peut pas forcer l’ajout indéfini de sources. Le parseur s’arrête lorsque : ```text GetMaxSources() <= GetSourceCount() ``` Cette borne protège : ```text mémoire connexions overhead ``` --- # 33. Après le parseur : CheckAndAddSource() Le candidat traverse encore `CDownloadQueue::CheckAndAddSource()`. Le code rejette notamment : ```text notre propre UserHash DeadSourceList globale DeadSourceList du fichier incompatibilité CryptLayer doublon ``` et fusionne avec les clients déjà connus. --- # 34. Dédoublonnage inter-fichiers Si le même pair existe déjà pour un autre fichier : ```text AddRequestForAnotherFile() ``` peut l’associer au nouveau téléchargement. C’est l’une des portes d’entrée d’A4AF. ### Conséquence Un paquet SX peut modifier : ```text non seulement srclist mais aussi la topologie A4AF ``` du Download Manager. --- # 35. Debug historique : « potential wrong source » `CheckAndAddSource()` contient un commentaire/debug particulièrement intéressant. Il reconnaît le cas où : ```text un pair nous envoie une source pour le mauvais fichier MAIS nous téléchargeons aussi le fichier auquel cette source appartient réellement ``` Le merge d’un client déjà connu peut alors devenir délicat. ### Leçon Le code lui-même reconnaît implicitement : ```text association SX source ↔ fichier = information distante potentiellement erronée ``` --- # 37. Découverte centrale : pClient est disponible puis perdu `AddClientSources(..., const CUpDownClient* pClient)` reçoit bien le client ayant fourni la réponse. Dans la branche eMule inspectée, cette information sert surtout au contexte/log. Dans aMule, le paramètre correspondant apparaît même explicitement ignoré dans la signature inspectée. La nouvelle source reçoit ensuite : ```cpp SetSourceFrom(SF_SOURCE_EXCHANGE) ``` mais pas : ```text SetSourceProvider(provider) ``` ### Conclusion Le moteur n’est pas structuré pour répondre à : ```text « qui nous a appris cette source ? » ``` après l’ajout. --- # 38. Ce que cela empêche Impossible sans instrumentation supplémentaire de construire proprement : ```text Provider A announced accepted duplicates unreachable file-confirmed useful bytes transferred verified bytes Provider B ... ``` Une future « réputation SX » ne peut donc pas être ajoutée correctement en regardant seulement les `CUpDownClient` existants. Il faut conserver la provenance dès le parsing. --- # 39. Trust Model Dossier 05 : ```text SX entry = REMOTE_CLAIM ``` La progression proposée est : ```text ANNOUNCED ↓ STRUCTURAL_VALID ↓ ADMITTED ↓ CONTACTABLE ↓ FILE_CONFIRMED ↓ USEFUL_PARTS ↓ TRANSFERRED ↓ HASH_VERIFIED ``` Ne pas réduire cette chaîne à : ```text GOOD / BAD ``` --- # 40. UserHash reçu via SX Version 2+ fournit : ```text UserHash ``` Mais le fournisseur dit : ```text « cette IP a ce UserHash » ``` Ce n’est pas l’équivalent : ```text « cette IP a prouvé cryptographiquement la possession de sa clé SecureIdent » ``` # 41. Crypt options reçues via SX v4 Même logique : ```text SupportsCryptLayer RequestsCryptLayer RequiresCryptLayer ``` sont annoncés par un tiers. Ils sont nécessaires pour décider si la connexion est plausible. Mais ils doivent pouvoir être corrigés par les informations reçues directement du client. ### Trust ```text initial = SX_CLAIM direct hello = DIRECT_CLAIM successful encrypted connection = LOCAL_OBSERVED ``` --- # 42. Pollution SX : catégories Ne pas tout appeler « fake source ». Séparer : ```text STALE source autrefois vraie, maintenant morte DUPLICATE source déjà connue UNREACHABLE adresse techniquement inaccessible WRONG_FILE pair joignable mais ne confirme pas ce fichier MALFORMED paquet ou record structurellement invalide FILTERED IPFilter / ban / règles locales USELESS_PARTS source valide mais aucune partie utile USEFUL source réellement exploitable ``` Cette taxonomie est cruciale pour éviter les faux positifs. --- # 43. Une source morte n’est pas une fraude Le vieillissement du réseau rend certaines annonces obsolètes sans faute du fournisseur. Facteurs : ```text DHCP / IP dynamique déconnexion redémarrage changement de port VPN changement de serveur suppression du fichier ``` ### Règle ```text STALE_RATE ne doit pas être assimilé directement à MALICIOUS_RATE ``` --- # 44. Métrique fournisseur minimale Proposition : ```text SXProviderStats packets_received records_announced records_valid_structure records_invalid_structure records_admitted records_duplicate records_filtered records_deadsource connect_attempted connect_success file_confirmed useful_parts_confirmed payload_received payload_hash_valid ``` Pas besoin d’un score composite dans la V1. --- # 45. Échantillon et confiance statistique Un fournisseur ayant : ```text 1 source annoncée 1 source morte ``` ne doit pas être classé comme catastrophique. Conserver : ```text sample_count first_observed last_observed ``` et n’utiliser la métrique pour une décision qu’après un minimum d’observations. Le seuil exact doit venir des mesures, pas être inventé maintenant. --- # 46. Comparaison au contexte global La qualité brute dépend de l’état du réseau. Exemple : ```text pendant 1 h taux connectable global = 15 % provider A = 18 % provider B = 14 % ``` B est proche de la normale. Autre cas : ```text global = 20 % provider B = 0,5 % sur 400 annonces ``` L’écart devient intéressant. # 47. Multi-source corroboration Une source annoncée par plusieurs fournisseurs indépendants est plus intéressante qu’une source annoncée une seule fois. Sans considérer cela comme une preuve absolue : ```text Provider A → source X Provider B → source X Provider C → source X ``` peut augmenter : ```text discovery confidence ``` ### Attention Une information fausse peut elle aussi être recopiée. Donc : ```text corroborated ≠ verified ``` --- # 48. Source attribution graph Structure locale possible : ```text FileHash | +-- Candidate Source X | | | +-- provider A | +-- provider B | +-- Candidate Source Y | +-- provider C ``` Après contact : ```text X → file confirmed Y → wrong file ``` Les fournisseurs A/B et C obtiennent alors une observation de qualité différente. --- # 49. Confidentialité Ne pas transformer ce mécanisme en base durable de pairs. Par défaut : ```text provider_session_id pseudonymisé pas d'IP persistante longue durée pas de UserHash complet dans le log standard TTL des métriques agrégation ``` Debug détaillé seulement sur activation explicite. Cohérent avec Dossier 05. --- # 50. Réponses SX non corrélées : observation de code Le handler `OP_ANSWERSOURCES` / `OP_ANSWERSOURCES2` vérifie : ```text handshake FileHash présent dans DownloadQueue format ``` puis appelle `AddClientSources()`. Dans le chemin inspecté, il n’y a pas de : ```text request nonce request id strict pending SX answer flag ``` Le protocole ancien n’en définit pas. ### Hypothèse à mesurer Un pair connecté pourrait donc fournir une réponse SX pour un fichier que nous téléchargeons sans qu’une demande immédiatement associée soit visible. Ne pas transformer cette observation en accusation. --- # 51. Pourquoi ne pas simplement rejeter toute réponse « unsolicited » Plusieurs risques de compatibilité : ```text réponse retardée état local nettoyé entre-temps multipacket client ancien différences d'implémentation ``` ### Palier D08-SX1 Telemetry seulement : ```text answer_expected answer_late answer_no_recent_request ``` Aucune modification du comportement. --- # 52. Si les réponses unsolicited existent réellement Étapes possibles, dans cet ordre : ```text 1. mesurer volume 2. mesurer utilité 3. distinguer legacy / client moderne 4. limiter le budget de traitement si nécessaire 5. seulement ensuite envisager drop ``` La protection la moins agressive pourrait être : ```text rate limit par pair ``` avant un rejet absolu. --- # 53. Capability Trust SX Dossier 05 proposait : ```text CLAIMED_SX4 OBSERVED_VALID_SX4 OBSERVED_BROKEN_SX4 ``` D08 précise l’implémentation. Événements : ```text SX_CAPABILITY_CLAIM SX_PACKET_VALID SX_PACKET_BAD_VERSION SX_PACKET_BAD_SIZE SX_PACKET_UNKNOWN_OPTIONS ``` Réaction future : ```text fonctionnalité SX dégradée pour ce pair ``` plutôt que : ```text ban global du pair ``` si le problème ne concerne que SX. --- # 54. Parser robustness Tests défensifs prioritaires : ```text count = 0 count max count × size overflow troncature 1 octet octets en trop version 0 version 5+ v2 sans UserHash complet v4 sans options byte invalid IP lowID edge cases ``` Objectif : ```text pas de crash pas d'allocation abusive pas de source partiellement initialisée ``` --- # 55. Fuzzing local Dossier 05 proposait déjà SX1/SX2 comme cible. D08 permet d’être précis : ```text AddClientSources SX1 AddClientSources SX2 OP_REQUESTSOURCES parser OP_ANSWERSOURCES parser OP_REQUESTSOURCES2 parser OP_ANSWERSOURCES2 parser ``` Sans émettre un seul paquet vers Internet. --- # 56. A4AF : relation structurelle Lorsqu’une source SX est déjà connue sur un autre fichier : ```text CheckAndAddSource → AddRequestForAnotherFile → A4AF list ``` Le Source Exchange nourrit donc naturellement A4AF. --- # 57. SX et choix du fichier `IsSourceRequestAllowed(partfile, sourceExchangeCheck)` est aussi utilisé lors des décisions de swap. Le moteur peut préférer un fichier offrant une meilleure opportunité de Source Exchange. Le Dossier 05 a déjà identifié un autre facteur dans cette zone : ```text Remote Queue Rank ``` qui est une donnée non vérifiable fournie par le pair. ### D08 Ne pas fusionner : ```text Remote QR trust SX provider quality ``` Ce sont deux dimensions différentes. --- # 58. Refactor futur : raison de swap explicite L’instrumentation proposée en D05 : ```text [A4AF_SWAP_DECISION] ``` doit ajouter : ```text sx_need_from sx_need_to sx_provider_quality_if_known source_deficit_from source_deficit_to ``` Ainsi nous verrons si SX influence encore fortement les swaps en pratique. --- # 59. AllowSwapForSourceExchange() La branche contient une protection simple : ```text 30 secondes ``` entre certains swaps motivés par Source Exchange. Cette hystérésis évite un basculement trop rapide d’un fichier à l’autre. # 60. Endgame E6bis : SOURCE_LIMITED Dossier 06 a introduit un état où : ```text il reste peu de sources réellement capables ``` et où multiplier les races ne peut plus créer de capacité réseau. C’est exactement le point de jonction avec D08. --- # 61. Tail Source Wakeup × SX Principe actuel documenté : ```text si trop peu de sources finales sont actives → réveiller A4AF / Source Exchange ``` D08 propose de rendre ce réveil plus sélectif. Pas : ```text reask plus vite partout ``` mais : ```text parmi les pairs déjà éligibles → choisir les meilleurs fournisseurs SX observés ``` --- # 62. Tie-break uniquement en première version Exemple : ```text A éligible B éligible mêmes contraintes protocole même rareté même timers ``` Si : ```text A provider-quality suffisamment documentée > B ``` alors : ```text demander A avant B ``` Aucune nouvelle demande n’est créée. On change uniquement l’ordre. --- # 63. Exception rare-file Si : ```text sources <= 10 ``` la diversité doit prendre le dessus. Même un provider peu performant peut détenir : ```text la seule nouvelle source utile ``` ### Politique possible ```text quality tie-break fort sur fichier courant quality tie-break faible sur fichier rare quality jamais exclusion dure sur fichier très rare ``` À valider par simulation/telemetry. --- # 64. Source completeness et demande SX `IsSourceRequestAllowed()` traite différemment les sources complètes et incomplètes dans les branches rare/très rare. Une source incomplète participe elle-même au graphe actif de téléchargement et a souvent davantage intérêt à connaître des sources. Une source complète peut toujours être utile, mais la politique la sollicite moins agressivement. ### À mesurer Ne pas supposer qu’en 2026 : ```text incomplete peer = meilleur provider ``` La telemetry pourra le confirmer ou non. --- # 65. Quality metrics par type de pair Axes d’analyse : ```text complete vs incomplete HighID client software SX1 vs SX2 record version SecureIdent verified vs unavailable ``` ### Important Ces dimensions servent à comprendre le réseau. Elles ne doivent pas devenir : ```text « tel logiciel est mauvais » ``` sans preuve comportementale. --- # 66. Source Cache historique Corpus Mods : ```text Off+ Source cache Save Load Source Optimization Source Exchange StulleMule / MorphXT heritage Save/Load Sources Load saved Sources Timer for ReAsk File Sources Reask after IP change Source Cache ``` Cela montre une préoccupation historique réelle : ```text ne pas perdre trop vite la connaissance de sources coûteuses à découvrir ``` --- # 67. Mais le nom de fonction ne suffit pas Nous n’avons pas encore, pour toutes ces variantes : ```text algorithme exact TTL exact format disque critères de validation interaction DeadSource interaction IP change ``` ### Statut ```text ARCHIVE / PISTE PAS PORTAGE IMMÉDIAT ``` --- # 68. Risque d’un cache persistant Un cache disque peut réinjecter : ```text IP réattribuées ports anciens LowID devenus invalides UserHash associé à une ancienne adresse client n'ayant plus le fichier ``` Le Source Exchange lui-même possède déjà un problème de vieillissement. Un cache persistant peut amplifier ce vieillissement. --- # 69. Mesure avant Source Cache ```text A. redémarrage sans cache B. temps jusqu'à N sources utiles C. sources serveur D. sources Kad E. sources SX F. premier transfert utile ``` Puis seulement comparer à une branche expérimentale de cache. Métriques : ```text time_to_first_source time_to_first_transfer time_to_10_valid_sources stale_ratio wrong_file_ratio connection_attempts_wasted ``` --- # 70. Cache court possible Si l’intérêt est démontré : ```text file hash endpoint last confirmed time discovery channel optional provider evidence TTL ``` avec invalidation agressive. Pas : ```text source éternelle ``` --- # 71. Reask after IP change Les vieux Mods mentionnent explicitement : ```text Reask after IP change ``` L’idée est logique : ```text notre identité réseau a changé → certaines relations de joignabilité peuvent changer ``` ```text nouveau HighID reconnexion serveur nouvelle IP publique Kad reconnect ``` avant de porter du code ancien. --- # 72. Source provider identity Pour conserver une provenance, quelles données utiliser ? Pas uniquement : ```text IP ``` Dossier 07 a déjà montré les limites : ```text NAT CGNAT VPN IP dynamique ``` --- # 73. Provider session identity Première version recommandée : ```text provider_session_id ``` identifiant local éphémère associé à l’instance `CUpDownClient`. Optionnellement : ```text secure_ident_verified = yes/no ``` Ne pas persister la clé ou le UserHash complet dans les logs standards. --- # 74. Plusieurs providers pour une même source Ne pas stocker un seul : ```text source.provider ``` car une source peut être annoncée par plusieurs pairs. Préférer : ```text CandidateSource providers[] ``` ou une table séparée d’événements de découverte. --- # 75. Donnée de provenance utile même après déduplication Aujourd’hui : ```text source déjà connue → pas de nouvelle source ``` Mais pour la qualité fournisseur : ```text le provider l'a quand même annoncée ``` Il faut donc enregistrer l’événement avant que `CheckAndAddSource()` ne détruise le candidat duplicate. Sinon on perd l’information statistique. --- # 76. Admission reason Le `bool` historique de `CheckAndAddSource()` est trop pauvre pour la telemetry. Il confond plusieurs raisons de `false`. ```text ADDED_NEW ADDED_A4AF DUPLICATE SELF DEAD_SOURCE CRYPT_INCOMPATIBLE BAD_IP IP_FILTERED BANNED UNREACHABLE_LOWID MAX_SOURCE_LIMIT OTHER_REJECT ``` ### Attention Faire cela sans changer les décisions existantes dans le premier palier. --- # 77. Wrong-file confirmation Une source SX pour Fichier A est vraiment confirmée lorsque l’échange direct montre qu’elle sert Fichier A. Événements possibles : ```text FILE_INFO_OK FILE_STATUS_OK FILENAME_ANSWER hash / FileIdentifier coherent ``` Le détail exact dépend de la génération du protocole. ### Qualité ```text provider said A direct peer confirms A → provider claim corroborated ``` --- # 78. NNP ne signifie pas « mauvaise source » `DS_NONEEDEDPARTS` peut vouloir dire : ```text le pair possède bien le fichier mais aucune partie qu'il a ne nous manque actuellement ``` Il ne faut donc pas compter : ```text NNP ``` comme : ```text wrong-file ``` Métrique distincte : ```text file_confirmed=yes useful_now=no ``` --- # 79. Queue Full ne signifie pas « source inutile » Même chose : ```text remote queue full ``` ou rang lointain : ```text source réellement liée au fichier mais pas immédiatement productive ``` Un provider SX peut donc être correct même si le transfert n’arrive jamais pendant notre fenêtre d’observation. --- # 80. Hiérarchie de succès SX Proposition : ```text L0 announced L1 accepted L2 endpoint contacted L3 file confirmed L4 useful parts L5 payload L6 hash-validated payload ``` Le niveau L3 est déjà un succès de découverte important. Ne pas juger uniquement L5/L6. --- # 81. Time decay La qualité d’un provider doit vieillir. Un client peut changer d’environnement. Proposition conceptuelle : ```text observations récentes > observations anciennes ``` sans fixer encore de demi-vie. ### Pourquoi Éviter qu’un mauvais épisode datant de plusieurs mois définisse éternellement un pair. --- # 82. Pas de sanction globale Si un pair fournit régulièrement de mauvaises listes SX : ```text action domaine SX ``` exemples futurs : ```text moins souvent choisi comme provider SX désactivé temporairement avec lui budget d'annonces réduit ``` Pas automatiquement : ```text upload ban IP ban credit penalty ``` --- # 83. Quand une sanction plus forte devient légitime Uniquement si d’autres mécanismes disposent de leurs propres preuves : ```text flood observable paquets invalides répétés SecureIdent conflict corruption hash-attributed ``` Chaque domaine décide selon ses preuves. Cohérent avec Trust Model. --- # 84. SX flooding Deux directions : ```text request flood answer flood ``` Le serveur d’entrée possède déjà un throttling des **requests reçues**. Le traitement des **answers reçues** doit être mesuré séparément. Ne pas supposer que les protections sont symétriques. --- # 85. Inbound budget possible Si la telemetry montre des réponses SX excessives : ```text max records processed / peer / fenêtre ``` pourrait être une défense locale peu intrusive. Mais elle doit préserver : ```text fichiers très rares réponses normales volumineuses legacy clients ``` Pas de seuil avant mesure. --- # 87. Privacy and abuse resistance Le graphe de provenance doit rester interne. Ne jamais envoyer : ```text « provider X est mauvais » ``` aux autres clients. ### Règle ```text local observations only ``` --- # 88. Pas de réputation globale partagée Une réputation distribuée exigerait : ```text identités fortes Sybil resistance signatures gestion des faux rapports vieillissement consensus ``` Ce serait un nouveau protocole complexe. Aucun besoin n’est démontré. --- # 89. Cross-check aMule L’implémentation aMule inspectée confirme plusieurs éléments : ```text OP_REQUESTSOURCES / 2 version SX2 request version/options formats v1-v4 sélection par Part Status limite ~500 compression contrôles taille/version ``` Cela renforce l’idée que ces comportements ne sont pas seulement un accident d’une branche eMule récente. --- # 90. Divergences d’implémentation Les lecteurs/parsers anciens ne sont pas toujours parfaitement symétriques ni parfaitement documentés. Il faut donc conserver la règle : ```text eMule writer eMule reader aMule writer aMule reader Wireshark captures ``` avant d’écrire une spécification wire définitive. Le Dossier 08 est d’abord un audit de comportement, pas encore une RFC eMule. --- # 101. Patch expérimental V1 proposé Nom de travail : ```text SX Evidence by Akshaan — V1 Shadow ``` Aucun changement de comportement. Hooks : ```text ListenSocket.cpp answer receive PartFile.cpp AddClientSources parse/admission DownloadQueue.cpp CheckAndAdd outcome DownloadClient.cpp connect/file confirmation/transfer milestones ``` --- # 102. Structures V1 Minimal : ```cpp enum class ESXDiscoveryOutcome { Announced, InvalidRecord, Filtered, Duplicate, AddedNew, AddedA4AF, DeadSource, ConnectFailed, Connected, FileConfirmed, UsefulParts, PayloadReceived }; ``` --- # 103. Provider key V1 : ```text runtime pointer/session id + optional SecureIdent state ``` Pas de persistance forte. Le diagnostic peut calculer un ID pseudonymisé pour les logs. --- # 104. Event example ```text [SX_EVIDENCE] file=anon:8C12 provider=session:37 transport=SX2 record_version=4 announced=42 valid=40 filtered=3 duplicate=21 new=16 later: connected=7 file_confirmed=6 useful=4 payload=3 ``` --- # 106. Packet Quality Mesures : ```text valid_version valid_size valid_count unknown_options parse_error ``` Cela évalue surtout : ```text compatibilité / comportement protocolaire ``` du provider. --- # 107. Source Outcome Mesures : ```text admitted duplicate deadsource connected file_confirmed useful ``` Cela évalue : ```text qualité pratique de l'information ``` Le provider peut être protocolairement impeccable mais disposer d’informations anciennes. --- # 108. Capability contradiction Cas : ```text Hello : supports SX2 puis SX2 systématiquement mal formé ``` Après répétition : ```text OBSERVED_BROKEN_SX2 ``` Réaction future : ```text fallback SX1 si compatible ou ne plus demander SX à ce pair ``` plutôt qu’un ban. --- # 109. Unknown options SX2 possède 16 bits réservés. Si un client demande des options inconnues : ```text log ``` La branche répond tout de même selon son comportement connu. # 110. Version future Le parseur actuel rejette les formats qu’il ne sait pas dimensionner. C’est raisonnable. ```text capability negotiation documentation publique aMule / autres clients compatibilité de parsing ``` Pas d’extension privée cachée. --- # 111. Source quality vs source priority Ne pas confondre : ```text provider quality ``` avec : ```text priority de téléchargement ``` Une source découverte par un provider médiocre peut être excellente. Chaque source doit être évaluée par ses propres interactions. --- # 112. Provider quality vs credits Ne pas utiliser la qualité SX pour : ```text crédits upload queue score friend slot ``` Ce serait une récompense non liée au service d’upload réel et facilement manipulable. --- # 113. Provider quality vs admission Première politique recommandée : ```text ne pas rejeter une source valide uniquement parce que son provider a un faible rendement ``` Utiliser la qualité pour : ```text choisir le prochain provider à interroger ``` est beaucoup moins risqué. --- # 114. Pourquoi le tie-break est élégant Il ne change pas : ```text nombre maximal de requêtes timers packet format source validation ``` Il améliore uniquement : ```text l'ordre de sélection ``` Donc faible risque d’interopérabilité. --- # 115. Information gain Une autre métrique possible : ```text new_unique_sources / announced_sources ``` Mais elle doit être contextualisée. Sur un fichier mature : ```text faible information gain ``` est normal. Sur un fichier rare : ```text une seule source nouvelle ``` peut être extrêmement précieuse. --- # 116. Useful discovery value Métrique plus intéressante : ```text confirmed_unique_source × rarity_weight ``` sans la transformer immédiatement en score de production. Exemple : ```text 1 source confirmée pour fichier à 2 sources > 10 doublons confirmés sur fichier à 600 sources ``` Cette logique rejoint le scheduler historique basé sur la rareté. --- # 117. Pas de machine learning nécessaire Le problème est suffisamment structuré pour commencer avec : ```text compteurs EWMA éventuelle seuil minimum d’échantillon tie-break ``` Un modèle opaque rendrait les décisions plus difficiles à expliquer. ```text mesure explicable ``` --- # 118. Source diversity Quand plusieurs providers ont des qualités proches, favoriser parfois la diversité peut empêcher : ```text toujours interroger le même sous-groupe ``` et améliorer la découverte d’une composante différente du graphe. Une petite randomisation contrôlée peut donc rester utile. --- # 119. Ne pas centraliser le réseau autour de « bons providers » Si tous les clients choisissaient toujours les mêmes pairs : ```text overhead concentré moins de diversité points chauds ``` Le but n’est pas de créer des « super nœuds SX ». --- # 121. Statistiques utilisateur possibles Vue avancée : ```text DÉCOUVERTE DES SOURCES Serveurs 124 Kad 83 Source Exchange 217 Passives 18 SX Annoncées 1 842 Nouvelles 311 Confirmées 126 Utiles 79 ``` Pas besoin d’afficher : ```text « client X est mauvais » ``` --- # 122. Statistiques par fichier Dans détails du fichier : ```text Sources actuelles 47 Découvertes par SX 18 Confirmées par SX 11 A4AF 7 Dernière réponse SX 4 min ``` Cela aiderait aussi à comprendre les fichiers rares. --- # 123. Journal utilisateur Éviter le bruit. Seulement événements significatifs : ```text [Sources] Fichier X : 8 nouvelles sources découvertes via SX. ``` Les détails de provider restent dans un mode diagnostic. --- # 124. Source freshness Une future source evidence peut conserver : ```text first_seen last_seen last_connected last_file_confirmed ``` sans nécessairement persister l’identité du provider. Cela aiderait également le sujet Source Cache. --- # 125. DeadSourceList comme signal Une source reçue qui est déjà dans : ```text global DeadSourceList file DeadSourceList ``` est un événement utile pour la qualité provider. Mais avec nuance : ```text provider peut l'avoir apprise avant qu'elle meure ``` Donc poids faible et temporel. --- # 126. IPFilter hit Source annoncée sur une IP filtrée : ```text rejected-IPFilter ``` Cela dit quelque chose sur l’adéquation à notre politique locale, pas nécessairement sur la malveillance du provider. Ne pas mélanger : ```text policy filtered ``` avec : ```text invalid source ``` --- # 127. Ban-list hit Même prudence. Provider peut annoncer un client déjà banni localement parce que : ```text il ne connaît pas notre ban ``` Ce n’est pas un mensonge en soi. Métrique séparée. --- # 128. SecureIdent provider Si le provider a un SecureIdent validé : ```text nous savons mieux que la même identité nous a fourni plusieurs listes ``` Cela améliore la continuité statistique dans la session. Mais SecureIdent ne prouve pas : ```text que ses listes sont vraies ``` Il prouve l’identité, pas la qualité. --- # 129. UserHash source Lorsque le record SX contient un UserHash : ```text ne pas utiliser le seul fait qu'il correspond à un client connu comme preuve du fichier ``` La source peut être la bonne identité mais ne plus posséder le fichier. --- # 130. Multi-channel corroboration Source X reçue par : ```text SX + Kad + serveur ``` peut être marquée : ```text multi-channel discovered ``` C’est un bon signal de pertinence opérationnelle. Mais la validation finale reste directe. --- # 131. Exploiter le canal sans lui faire confiance C’est probablement le meilleur résumé du Source Exchange : ```text utiliser l'information rapidement mais ne pas la transformer en privilège avant observation ``` --- # 132. Roadmap proposée ## D08-V1 — Provider provenance shadow Ajouter l’événement de provenance. Aucun changement de décision. ## D08-V2 — Outcome correlation Relier : ```text announced source → connect → file confirmation → useful state ``` ## D08-V3 — Provider selection tie-break Seulement si les distributions montrent une différence exploitable. ## D08-V4 — Tail Source Wakeup integration Tester en SOURCE_LIMITED. ## D08-V5 — Inbound answer budget Seulement si unsolicited/flood réellement observé. --- # 133. Critères de succès V1 V1 réussie si : ```text aucune différence de nombre de sources aucune différence de paquet réseau aucune différence de timing aucun changement A4AF faible overhead CPU/RAM corrélation provider→source exploitable ``` --- # 134. Critères de succès V2 V2 doit pouvoir répondre : ```text Parmi 100 sources annoncées par SX : combien étaient nouvelles ? combien ont été jointes ? combien ont confirmé le fichier ? combien avaient des parties utiles ? ``` sans stocker inutilement les identités réseau au long cours. --- # 135. Critères de succès V3 Comparer A/B : ```text historical provider selection vs quality tie-break ``` Même : ```text nombre de demandes SX timers soft source limit ``` Mesurer : ```text new confirmed sources / request useful sources / request connections wasted ``` --- # 136. Critères de succès Tail Source Wakeup Sur fichiers E6bis réellement SOURCE_LIMITED : ```text temps jusqu'à nouvelle source utile temps 99.x → 100 nombre de SX requests redondance CPU ``` Le gain doit être comparé à : ```text E6bis baseline ``` pas seulement eMule officiel. --- # 137. Ce que nous ne devons pas porter maintenant ```text Source Cache ancien sans audit Save Load Sources sans TTL moderne boost SX agressif générique Community Source Exchange propriétaires blacklists de Mods ban pour mauvais rendement SX ``` --- # 138. Ce que nous ne devons pas modifier ```text SX1 compatibility SX2 v4 wire format FileHash association rare-file timers soft source limits part-aware filtering A4AF semantics ``` sans preuve expérimentale claire. --- # 139. Documentation publique Pages possibles sur le site : ```text Source Exchange : le bouche-à-oreille d'eMule SX1 vs SX2 Pourquoi une source peut être morte Comment eMule évite le spam SX A4AF : une source, plusieurs fichiers ``` La fiche SITE de ce dossier peut servir de base à la première. --- # 141. Conclusion Source Exchange est beaucoup plus mûr qu’une simple liste d’IP échangée entre pairs. Il possède déjà : ```text compatibilité SX1 négociation SX2 formats v1-v4 throttling rareté soft limits Part Status awareness IP filtering dédoublonnage A4AF compression ``` C’est une absence de **mémoire explicite de provenance**. Le moteur reçoit : ```text Provider P → Source X ``` puis conserve essentiellement : ```text X vient de SOURCE_EXCHANGE ``` sans conserver : ```text P nous a annoncé X ``` Cette information manquante empêche d’évaluer la qualité réelle du canal par fournisseur. C’est donc une amélioration intéressante parce qu’elle respecte les quatre contraintes qui reviennent dans tout notre travail : ```text compatible avec les clients existants pas de nouveau wire protocol mesurable avant activation utile à Endgame sans le remplacer ``` La première implémentation doit rester purement observationnelle. Ensuite seulement, si les données montrent des écarts significatifs entre providers, la qualité SX pourra devenir un **tie-break local** pour choisir à qui demander des sources — particulièrement quand E6bis détecte un vrai état `SOURCE_LIMITED`. ================================================================================================ --- # Dossier 09 — A4AF, NNP, états d’une source et changement de fichier {#g09} --- # 0. Résumé exécutif Ce dossier ferme plusieurs questions ouvertes par D06, D07 et D08. Le moteur A4AF de la branche Community 0.72a est déjà sophistiqué : ```text un CUpDownClient → un m_reqfile actif → plusieurs fichiers alternatifs m_OtherRequests_list m_OtherNoNeeded_list chaque CPartFile → srclist active → A4AFsrclist ``` Le swap prend en compte : ```text priorités catégories/fichiers source limits reask timers NNP Source Exchange Remote Queue Rank suspensions de swap ``` La découverte la plus importante est toutefois sémantique : > **DS_NONEEDEDPARTS peut représenter plus d’une cause.** Dans `SendBlockRequests()`, si `CreateBlockRequests()` ne parvient à produire aucun nouveau bloc, le client est immédiatement : ```text SendCancelTransfer() SetDownloadState(DS_NONEEDEDPARTS) SwapToAnotherFile(...) ``` Or l’absence de bloc nouveau ne prouve pas nécessairement : ```text le pair ne possède aucune partie dont nous avons besoin ``` Elle peut aussi signifier : ```text le pair possède des parties utiles mais tous les gaps correspondants sont déjà réservés ``` Cette différence est particulièrement importante avec Endgame by Akshaan E6bis. Deuxième découverte importante : ```text FILEREASKTIME = 29 min NNP reask = 58 min ``` et `SwapToRightFile()` introduit en plus une rotation NNP : ```text 58 min × nombre de fichiers NNP associés au client ``` afin d’espacer les reasks entre plusieurs NNP. Sur un client associé à de nombreux fichiers, une association NNP peut donc rester longtemps sans être revisitée. C’est raisonnable si le pair distant n’a rien changé. Mais D07 a montré que **notre propre gap list peut changer localement** après : ```text corruption AICH Recovery ICH Endgame reservation release ``` Une source NNP peut donc redevenir utile **sans aucun changement de Part Status distant**. # **Event-Driven A4AF Reclassification** Principe : ```text événement local qui change les gaps → reclassifier localement les A4AF/NNP → aucun paquet réseau nécessaire → les timers réseau restent respectés → Tail Source Wakeup reçoit immédiatement une vue correcte des sources potentiellement utiles ``` Le vieux Delayed NNP NetF reste pertinent comme protection supplémentaire pour une **source actuellement en téléchargement** juste avant son passage NNP. Mais il ne devrait probablement pas être porté sous forme d’un simple timer global fixe de 30 s. --- # 1. A4AF : modèle de données réel ## 1.1 Fichier actif Chaque `CUpDownClient` possède : ```cpp m_reqfile ``` Le client appartient alors à : ```cpp m_reqfile->srclist ``` ## 1.2 Fichiers alternatifs Le même client conserve : ```cpp m_OtherRequests_list m_OtherNoNeeded_list ``` et chacun de ces fichiers conserve le client dans : ```cpp CPartFile::A4AFsrclist ``` Il existe donc une relation bidirectionnelle. --- # 2. `AddRequestForAnotherFile()` Dans 0.72a : ```cpp if (m_OtherNoNeeded_list.Find(file) || m_OtherRequests_list.Find(file)) return false; m_OtherRequests_list.AddTail(file); file->A4AFsrclist.AddTail(this); ``` ### Sens Une source découverte pour un fichier alors qu’elle est déjà utilisée ailleurs : ```text n’est pas clonée ``` Elle devient une source A4AF de l’autre fichier. C’est exactement ce que D08 observait lors du dédoublonnage inter-fichiers. --- # 3. Deux classes A4AF ## `m_OtherRequests_list` Association connue et potentiellement utilisable. ## `m_OtherNoNeeded_list` Association pour laquelle le client a précédemment été classé : ```text NNP ``` Cela permet des reask plus espacés et une sélection différente dans `SwapToRightFile()`. --- # 4. `DoSwap()` : ce que signifie réellement un swap Séquence : ```text ancien m_reqfile ↓ retiré de srclist nouveau fichier ↓ retiré de son A4AFsrclist ancien fichier ↓ ajouté dans A4AFsrclist ↓ OtherRequests ou OtherNoNeeded client ↓ SetDownloadState(DS_NONE) SetRequestFile(nouveau) nouveau fichier ↓ client ajouté dans srclist ``` Le client réseau reste le même. --- # 5. Classification de l’ancien fichier pendant le swap Dans `DoSwap()` : ```cpp if (GetDownloadState() == DS_NONEEDEDPARTS) m_OtherNoNeeded_list.AddTail(m_reqfile); else m_OtherRequests_list.AddTail(m_reqfile); ``` ### Conséquence L’état du client **au moment du swap** détermine durablement quelle liste A4AF recevra l’ancien fichier. C’est une raison supplémentaire pour ne pas classifier NNP trop tôt en Endgame. --- # 6. État NNP : origine par Part Status `ProcessFileStatus()` compare la carte distante avec nos besoins locaux. Si aucun besoin n’est visible en TCP : ```cpp SetDownloadState(DS_NONEEDEDPARTS); SwapToAnotherFile(...); ``` Donc : ```text NNP → A4AF swap immédiat ``` dans ce chemin. ### UDP Le même traitement UDP peut définir : ```text DS_NONEEDEDPARTS ``` mais l’appel `SwapToAnotherFile()` correspondant est commenté dans la branche étudiée. Ce n’est donc pas parfaitement symétrique. --- # 7. État NNP : origine par scheduler de blocs Dans `SendBlockRequests()` : ```cpp CreateBlockRequests(blockCount); if (m_PendingBlocks_list.IsEmpty()) { SendCancelTransfer(); SetDownloadState(DS_NONEEDEDPARTS); SwapToAnotherFile(...); return; } ``` C’est le point le plus important du dossier. Cette branche ne demande pas : ```text « la Part Status du pair a-t-elle zéro intersection avec nos gaps ? » ``` Elle constate seulement : ```text « aucun nouveau bloc n'a été placé dans la pending list » ``` --- # 8. NNP sémantiquement ambigu Il faut donc distinguer au moins : ## NNP-1 — réellement aucune partie utile ```text remote available parts ∩ local gaps = vide ``` ## NNP-2 — parties utiles mais travail déjà réservé ```text remote available parts ∩ local gaps != vide mais toutes les plages pertinentes sont déjà requested/reserved ``` Dans eMule historique, les deux peuvent converger vers : ```text DS_NONEEDEDPARTS ``` --- # 10. Baseline E6bis D06 établit : ```text Endgame by Akshaan E6bis = baseline ``` et documente déjà : ```text Locked Block Recovery Speculative Block Racing Partial Final-Tail Rescue Stale Pipeline Bypass Tail Source Wakeup SOURCE_LIMITED ``` D09 ne doit pas créer un second scheduler concurrent. Il doit fournir à E6bis une **classification correcte du cycle de vie des sources**. --- # 11. `SetDownloadState(DS_NONEEDEDPARTS)` modifie aussi le timer Le code contient explicitement : ```cpp case DS_NONEEDEDPARTS: SetLastAskedTime(); ``` Commentaire : ```text TCP asks never set re-ask time if the result is NNP ``` Donc le passage NNP n’est pas seulement visuel. Il modifie la prochaine fenêtre de reask. --- # 12. Timers de base `Opcodes.h` 0.72a : ```text FILEREASKTIME = 29 min MIN_REQUESTTIME = 10 min PURGESOURCESWAPSTOP = 15 min ``` `GetTimeUntilReask()` applique : ```text normal → 29 min NNP → 58 min short/aggr. → 10 min ``` Les 58 min sont : ```text FILEREASKTIME × 2 ``` --- # 13. Pourquoi le 29 min historique existe Ce timer protège le réseau contre les requêtes répétées. D09 ne propose pas de réduire globalement : ```text 29 min ``` ni : ```text 58 min ``` Le problème identifié n’est pas : ```text « les timers sont trop longs » ``` mais : ```text « un événement local peut rendre une source utile sans nécessiter un nouveau reask réseau » ``` --- # 14. Rotation de plusieurs NNP : détail remarquable Dans `SwapToRightFile()` : ```cpp allNnpReaskTime = FILEREASKTIME * 2 * (m_OtherNoNeeded_list.GetCount() + (current == NNP ? 1 : 0)); ``` Avec : ```text FILEREASKTIME = 29 min ``` cela donne : ```text 1 NNP → 58 min 2 NNP → 116 min 3 NNP → 174 min 5 NNP → 290 min 10 NNP → 580 min ``` ### Important Ce n’est pas un délai global de connexion. C’est une logique de rotation/revisite des associations NNP d’un même client. Elle cherche à répartir les reasks entre plusieurs fichiers NNP. --- # 15. Intention historique Commentaire du source : ```text wait two re-ask interval for each nnp file before re-asking an nnp file ``` L’intention est raisonnable : ```text ne pas reposer la même question à la même source pour dix fichiers toutes les 58 minutes simultanément ``` Cela répartit l’overhead. --- # 17. D07 donne la pièce manquante AICH peut transformer : ```text partie complète ``` en : ```text une ou plusieurs gaps de 180 KiB ``` ou ICH peut rouvrir la partie. À cet instant : ```text les besoins locaux changent immédiatement ``` mais les Part Status distantes déjà connues restent potentiellement exploitables. ### Conclusion D07 → D09 Après : ```text PART_CORRUPT AICH_RECOVERY GAP_REOPENED ``` il faut envisager une réévaluation locale A4AF/NNP. --- # 18. Réévaluer localement ≠ reask réseau C’est le cœur de la proposition. Nous possédons déjà souvent : ```text source->PartStatus ``` Donc après `AddGap()` : ```text for each A4AF/NNP source: cached remote PartStatus ∩ new local gap map ``` peut être recalculé sans paquet. --- # 19. Nouveau statut conceptuel Nom de travail : ```text A4AF_RECLASSIFY_LOCAL ``` ou : ```text SourceNeedReevaluation ``` Événement : ```text Gap topology changed ``` Causes : ```text corruption AICH ICH Endgame block release file completion of a part possibly manual priority/state change ``` --- # 20. Ce qui peut être recalculé immédiatement Sans réseau : ```text source owns newly-needed part? source belongs to this file? source currently A4AF? source in OtherNoNeeded? file priority current source count Endgame SOURCE_LIMITED ``` Ce qui ne peut pas être garanti : ```text source still online source still owns the part now remote queue state fresh QR fresh Part Status ``` Dossier 05 : ```text cached PartStatus remains REMOTE_CLAIM ``` mais c’est déjà la même base que le scheduler historique utilisait. --- # 21. Classification proposée des causes NNP ## `NNP_NO_PART_OVERLAP` Aucune partie annoncée du pair ne croise les gaps actuelles. ## `NNP_ALL_WORK_RESERVED` La source possède des parties utiles, mais toutes les plages pertinentes sont déjà réservées. ## `NNP_ENDGAME_TRANSIENT` Cas précédent dans un fichier E6bis actif avec forte probabilité de libération/racing/reallocation. ## `NNP_POST_INTEGRITY_CHANGE` Gap récemment rouverte par MD4/AICH/ICH. ## `NNP_STATUS_UNKNOWN` Part Status absente ou non exploitable. ## `NNP_STABLE` Classification stable après revalidation locale. --- # 22. Pourquoi cette classification est préférable à « Delayed NNP partout » Un délai uniforme de 30 s ferait aussi attendre : ```text une source réellement inutile ``` alors qu’elle pourrait immédiatement servir un autre fichier A4AF. Cela peut réduire le débit total. # 23. NetF Delayed NNP Le rapport historique NetF établit : ```text grâce ≈ 30 s reask Part Status puis seulement : CancelTransfer DS_NONEEDEDPARTS SwapToAnotherFile ``` Le descriptif NetF explique explicitement l’objectif : ```text keeps downloading sources a little bit longer in case a part turns out corrupt ``` Ce mécanisme répond donc bien à un vrai problème. --- # 24. Pourquoi ne pas copier les 30 secondes Trois raisons. ## 1. Matériel/réseau 30 s vient d’un contexte ancien. ## 2. E6bis Nous savons déjà mesurer : ```text rate collapse tail risk remaining work owners source limited ``` ## 3. Local gap events Après corruption, nous n’avons pas forcément besoin d’attendre une nouvelle Part Status. Nous pouvons souvent savoir immédiatement que la source **redevient potentiellement utile**. --- # 25. Delayed NNP adaptatif éventuel Si un délai de grâce reste nécessaire pour une source **actuellement en téléchargement** : ```text grace = bornée ``` et dérivée de facteurs comme : ```text RTT débit actuel temps depuis dernier payload Endgame state probabilité de gap liberation A4AF alternative disponible ``` Pas besoin de fixer maintenant une formule. Première phase : ```text shadow telemetry ``` --- # 26. Xtreme Just In Time Swapping Changelog Xtreme 4.2 beta 1 : ```text when you are downloading from a client and this client doesn't have anymore chunks for you, the download is continued with an A4AF ``` La branche 0.72a étudiée possède aujourd’hui : ```text SendBlockRequests() → no pending block → Cancel → NNP → SwapToAnotherFile ``` ### Conclusion historique prudente Le même problème et le même comportement général apparaissent dans Xtreme puis dans la branche moderne. Nous n’avons pas ici la preuve documentaire suffisante pour attribuer directement l’implémentation 0.72a à Xtreme. --- # 27. Pourquoi Just-In-Time Swapping est encore une bonne idée Hors Endgame : ```text source A n'a plus rien pour Fichier X mais possède Fichier Y slot distant déjà obtenu ``` Réutiliser immédiatement ce slot pour Y peut éviter : ```text perdre une occasion de transfert ``` Le problème n’est donc pas le swap lui-même. Le problème est de savoir si le diagnostic : ```text « plus rien pour X » ``` est réellement stable. --- # 28. Auto-A4AF Le changelog Xtreme indique aussi : ```text Auto-A4AF-check ``` et le corpus Mods souligne l’importance historique de la gestion A4AF dans Xtreme/Sivka/ZZUL. Ces références sont utiles pour la suite. Mais sans les sources exactes de chaque version, D09 ne transforme pas les noms de features en algorithmes supposés. ### Règle corpus ```text changelog = preuve d’existence/intention source = preuve de comportement détaillé ``` --- # 30. Sources limits dans `SwapToRightFile()` Le swap essaie de ne pas déplacer la source vers un fichier déjà saturé. Il traite aussi plus sévèrement les NNP autour d’environ : ```text 80 % de MaxSources ``` dans certaines branches. ### Sens A4AF est également un mécanisme de répartition des sources entre fichiers. --- # 31. Source Exchange et A4AF La logique compare aussi : ```text IsSourceRequestAllowed(file) AllowSwapForSourceExchange() ``` et peut conserver/déplacer la source afin d’obtenir une opportunité SX. Protection : ```text 30 s ``` sur `lastSwapForSourceExchangeTick`. D08 recommande de conserver cette hystérésis. --- # 32. Remote QR dans A4AF `SwapToRightFile()` contient : ```text DS_ONQUEUE && RemoteQueueRank <= 50 ``` et l’inverse : ```text RemoteQueueRank > 50 ``` dans les décisions liées à SX. D05 a établi : ```text Remote QR = REMOTE_CLAIM ``` ### D09 Ajouter aux traces : ```text remote_qr_influenced_swap=yes/no ``` sans modifier immédiatement le comportement. --- # 33. DS_REMOTEQUEUEFULL : fossile intéressant `ClientStateDefs.h` de la 0.72a contient : ```cpp DS_REMOTEQUEUEFULL // not used yet, except in statistics ``` Pourtant `SwapToAnotherFile()` et `SwapToRightFile()` contiennent encore des branches qui le testent. ### Hypothèse Il s’agit probablement : ```text d’une branche historique / défensive ou d’un état devenu pratiquement mort ``` # 34. Le vrai état Queue Full moderne La branche possède en parallèle un booléen/état : ```text RemoteQueueFull ``` distinct de `EDownloadState`. Au début d’un FileRequest vers un eMule : ```text SetRemoteQueueFull(true) SetRemoteQueueRank(0) ``` jusqu’à réception d’une information de queue. Les deux concepts ne doivent donc pas être confondus : ```text DS_REMOTEQUEUEFULL vs m_bRemoteQueueFull / IsRemoteQueueFull() ``` --- # 35. A4AF Save CPU La branche possède : ```text A4AFSaveCpu ``` défaut : ```text false ``` Lorsque activé, `SwapToAnotherFile()` peut renoncer très tôt à certaines évaluations si le client peut rester sur le fichier courant. ### Historique Cette option témoigne d’une époque où scanner fréquemment les associations A4AF pouvait être considéré coûteux. # 36. `DontSwapTo()` et suspension `DontSwapTo(file)` enregistre un timestamp. `IsSwapSuspended()` bloque alors un retour sur ce fichier pendant : ```text PURGESOURCESWAPSTOP = 15 min ``` sauf expiration. Exemples d’usage retrouvés : ```text File Not Found AICH mismatch ``` ### Important Dans `SetDownloadState(DS_NONEEDEDPARTS)` : ```cpp //DontSwapTo(m_reqfile); ``` est commenté. Un simple NNP ne déclenche donc pas ce verrouillage par cette voie. --- # 37. Pourquoi c’est logique NNP peut être temporaire. Interdire pendant 15 min tout retour au fichier après chaque NNP aurait pu empêcher des réaffectations utiles. Les reask timers suffisent déjà largement à limiter l’agitation. --- # 38. FNF ≠ NNP `UDPReaskFNF()` : ```text source dit : « je n'ai pas ce fichier » ``` → `DontSwapTo(m_reqfile)` → tentative d’autre fichier → sinon suppression source C’est beaucoup plus fort que : ```text « je possède le fichier mais rien dont tu as besoin maintenant » ``` ### Trust semantics ```text FNF ≠ NNP ``` --- # 39. AICH mismatch ≠ simple NNP Si le FileIdentifier/AICH ne correspond pas : ```text DontSwapTo(file) → tentative A4AF → sinon suppression ``` C’est encore une autre raison de changement de fichier. # 40. Le booléen `reason` actuel n’est pas structuré `SwapToAnotherFile()` reçoit une chaîne : ```text "A4AF for NNP file..." "Source says it doesn't have the file..." ... ``` utile au log. Mais pour l’analyse automatisée, une chaîne n’est pas idéale. # 41. Enum proposé ```text PeriodicReask NNPPartStatus NNPNoRequestableBlock FileNotFound AICHMismatch SourceExchangeOpportunity FileCompleted ManualSwap EndgameTailWakeup IntegrityGapReopened Unknown ``` V1 : ```text observation seulement ``` --- # 42. A4AF Decision Trace Format proposé : ```text [A4AF_DECISION] client=session:42 from=file:A to=file:B reason=NNP_NO_REQUESTABLE_BLOCK from_state=DOWNLOADING to_is_nnp=no priority_from=normal priority_to=high sources_from=38 sources_to=5 remote_qr=27 remote_qr_trust=UNVERIFIED sx_from_allowed=no sx_to_allowed=yes endgame=ACTIVE source_limited=yes decision=SWAP ``` --- # 43. Pourquoi la raison NNP doit être déterminée avant `SetDownloadState` Aujourd’hui, dans le chemin : ```text CreateBlockRequests() → Pending empty → NNP ``` l’information : ```text « pourquoi aucun bloc ? » ``` est perdue. # 44. Lien avec Dynamic Request Size D06 Si Dynamic Request Size est un jour testé : ```text moins de grandes réservations → moins de ALL_USEFUL_GAPS_RESERVED → moins de faux/transitoires NNP ``` D09 fournit donc une métrique indirecte pour juger ce candidat. --- # 45. Lien avec Stale Pipeline Bypass E6 avait justement trouvé : ```text anciennes réservations gagnées → encore présentes → fausse impression que tout est occupé ``` Stale Pipeline Bypass corrige ce type de situation. ### D09 Compteur intéressant : ```text NNP_NO_REQUESTABLE_BLOCK avant/après stale-pipeline filtering ``` Cela peut confirmer que le problème est déjà largement résolu par E6bis. --- # 46. Lien avec Speculative Racing Si : ```text source rapide possède le dernier bloc mais bloc déjà réservé ``` ancien eMule peut atteindre : ```text no requestable block ``` E6bis peut au contraire : ```text challenger/race ``` avant de déclarer la source NNP. ### Invariant ```text E6bis opportunity doit être évaluée avant A4AF NNP fallback ``` --- # 47. Lien avec Partial Final-Tail Rescue Même logique. Si une partie d’un bloc reste gap : ```text la source peut être utile sur cette sous-plage ``` avant que le client soit classé NNP. ### Audit Vérifier l’ordre d’appel réel : ```text gap discovery → E6bis rescue → pending construction → NNP fallback ``` --- # 48. Lien avec corruption D07 : ```text AICH Recovery → FillGap bons blocs → mauvaises zones restent gaps ``` ou un hash failure peut : ```text AddGap() ``` ### Question D09 Après cette mutation : ```text les sources dans m_OtherNoNeeded_list sont-elles reclassées immédiatement ? ``` Dans le code officiel inspecté : ```text UpdatePartsInfo() ``` recalcule les fréquences depuis `srclist`. Il ne constitue pas, à lui seul, un moteur général visible de migration : ```text OtherNoNeeded → OtherRequests ``` sur changement local de gaps. ### Statut ```text ``` --- # 49. Event-Driven A4AF Reclassification Nom proposé : ```text A4AF Need Reclassifier ``` Entrées : ```text FileGapChanged PartCorrupted AICHRecoveryFinished EndgameReservationReleased EndgameSourceLimitedEntered ``` Sortie : ```text liste de sources A4AF devenues potentiellement utiles ``` --- # 50. Première version : shadow Ne déplacer aucune source. Pour chaque événement : ```text scan A4AFsrclist scan source PartStatus compare new gaps ``` Log : ```text [A4AF_RECLASS_SHADOW] event=AICH_GAP_REOPENED file=anon:A a4af_sources=37 previous_nnp=19 now_locally_useful=4 best_candidate=session:72 connected=yes current_file=B current_file_priority=normal target_priority=high ``` --- # 51. Coût CPU Le scan peut être évité sur : ```text tous les fichiers ``` et limité au fichier dont la gap map change. Complexité approximative : ```text A4AF sources du fichier × part intersection ``` Sur un PC moderne, probablement faible. Mais mesurer avant de supprimer `A4AFSaveCpu`. --- # 52. Optimisation possible par part touchée Après AICH : ```text part 42 a changé ``` Il est inutile de rescanner toutes les parties. On peut rechercher uniquement : ```text source->IsPartAvailable(42) ``` pour chaque A4AF source. Encore plus léger. --- # 53. Après un Endgame reservation release Le besoin n’est pas une nouvelle partie. C’est une nouvelle **plage libre** dans une partie déjà manquante. Si la source possède cette partie : ```text candidate useful ``` E6bis peut alors utiliser son propre scheduler pour déterminer la plage précise. --- # 54. Ne pas convertir immédiatement la liste Une source dans `OtherNoNeeded` peut devenir localement utile, mais : ```text elle peut être occupée ailleurs être sous reask timer ne plus être connectée avoir une PartStatus ancienne ``` V1 doit simplement poser un marqueur : ```text locally_useful_again=true ``` --- # 55. Promotion possible à terme Après validation : ```text OtherNoNeeded → candidate for OtherRequests / swap ``` mais seulement à travers les mêmes règles : ```text priorité timers source limit A4AF Endgame policy ``` Ne pas modifier directement les listes depuis l’événement d’intégrité sans passer par le scheduler. --- # 58. Ce que la shadow telemetry doit mesurer ```text si nous avions attendu : la source serait-elle redevenue utile ? combien de temps ? combien d'octets aurait-elle pu servir ? pendant ce temps : un A4AF réel aurait-il transféré sur un autre fichier ? ``` Cette dernière métrique est essentielle. Delayed NNP a un **coût d’opportunité**. --- # 59. Métrique NetF historique Le rapport Endgame proposait déjà : ```text NNPGraceRecovered ``` D09 l’étend : ```text NNP_GRACE_REUSED NNP_GRACE_WASTED_TIME NNP_GRACE_A4AF_OPPORTUNITY_COST ``` --- # 60. Pas de délai fixe sans résultats 30 secondes est une excellente valeur historique de comparaison. Comparaisons shadow possibles : ```text 5 s 10 s 15 s 30 s adaptatif ``` sans réellement retenir la source. --- # 61. Reask Part Status pendant la grâce NetF redemandait la Part Status. ## Local need changed Exemple corruption locale : ```text PartStatus distante encore suffisante ``` Pas forcément besoin de reask. ## Remote availability suspected changed Exemple source vient de finir une partie : ```text reask peut être utile ``` mais seulement si les timers/protocole le permettent. --- # 62. Éviter un nouveau flood ```text chaque NNP → PartStatus reask immédiat ``` La philosophie D01/D08 reste : ```text réduire les requêtes inutiles ``` La reclassification locale est justement intéressante parce qu’elle coûte : ```text 0 paquet ``` --- # 63. A4AF et Remote Queue Rank D05 a démontré que le QR distant est falsifiable. D09 confirme qu’il influence effectivement : ```text SwapToRightFile() ``` ### Première évolution sûre Trace : ```text QR condition changed outcome? yes/no ``` Puis simulation : ```text decision without Remote QR ``` sans changer le comportement réel. --- # 64. Question à mesurer Combien de swaps seraient différents si : ```text Remote QR ``` était retiré de l’heuristique ? Si : ```text presque aucun ``` le code historique peut être simplifié. Si : ```text beaucoup ``` il faudra étudier une alternative basée sur des observations locales. --- # 65. Possible remplacement à long terme Pas une recommandation immédiate. Facteurs vérifiables : ```text temps depuis dernier transfert source count réel file priority source useful parts connection success local queue wait estimate impossible à connaître ``` Il n’existe pas de substitut parfait au QR parce que le QR apporte précisément une information sur la file distante. Il faut donc mesurer la valeur de cette information avant de la supprimer. --- # 66. A4AF et SX Provider Quality D08 proposait : ```text provider quality → tie-break pour choisir à qui demander SX ``` Cette métrique ne doit pas devenir : ```text file swap priority générale ``` Elle ne concerne qu’un rôle : ```text qualité comme fournisseur de sources ``` D09 garde les dimensions séparées. --- # 67. A4AF et source quality Même prudence. Une source qui transfère bien Fichier A peut être excellente pour A et NNP pour B. La qualité n’est pas seulement : ```text client global ``` mais souvent : ```text client × fichier × moment ``` --- # 68. Historique Sivka Le changelog Sivka documente : ```text A4AF handling v1.2 A4AF counter R4AF listing manual swapping all A4AF ``` Il montre que le sujet était déjà considéré comme complexe assez tôt dans l’histoire des Mods. Mais D09 ne déduit pas le comportement exact de ces fonctions sans source détaillée correspondante. --- # 69. Historique Xtreme Le corpus documente : ```text Auto-A4AF-check Just in Time Swapping drop NoNeeded drop FullQueue drop HighQR ``` Cela montre deux philosophies historiques : ```text réutiliser vite une source ``` et : ```text nettoyer les sources jugées peu productives ``` --- # 84. Première implémentation recommandée ## D09-V1 — A4AF Decision Trace Aucun changement de comportement. Ajouter : ```text raison from/to file list origin NNP classification shadow timers priority source counts QR influence SX influence E6bis state ``` --- # 85. D09-V2 — NNP Reason Classifier Shadow Dans le chemin `SendBlockRequests()` : avant : ```text SetDownloadState(DS_NONEEDEDPARTS) ``` calculer seulement pour log : ```text NO_PART_OVERLAP ALL_WORK_RESERVED ENDGAME_TRANSIENT UNKNOWN ``` Aucune modification des décisions. --- # 86. D09-V3 — Gap Event Reclassification Shadow Sur : ```text AddGap / corruption AICH recovery Endgame reservation release ``` rechercher les sources dans : ```text A4AFsrclist ``` qui redeviennent localement utiles. Aucun swap. --- # 88. D09-V5 — Adaptive NNP Grace Seulement si : ```text des sources actives utiles sont encore perdues malgré V4 + E6bis ``` Alors tester une grâce ciblée. Pas avant. --- # 89. Pourquoi cet ordre est préférable Il est possible que : ```text Stale Pipeline Bypass + Locked Recovery + local reclassification ``` résolvent presque tous les cas où NetF avait besoin de 30 secondes de délai. Dans ce cas : ```text pas besoin d'ajouter une nouvelle attente ``` ```text mesurer avant d'empiler des heuristiques ``` --- # 90. Architecture proposée ```text A4AFManager ├── ActiveFile ├── OtherRequests ├── OtherNoNeeded ├── SwapPolicy └── ReaskPolicy NeedClassifier ├── NoPartOverlap ├── AllWorkReserved ├── EndgameTransient └── IntegrityReopened A4AFEventReclassifier ├── GapChanged ├── Corruption ├── AICH └── ReservationReleased A4AFDecisionTrace ├── priority ├── timers ├── SX ├── QR └── Endgame ``` Ces noms sont conceptuels. Le premier port ne doit pas imposer un grand refactor. --- # 91. Invariant de protocole Tout ce travail reste : ```text 100 % local ``` Aucun nouveau : ```text opcode tag packet source exchange version server feature ``` n’est nécessaire. --- # 92. Invariant Endgame Baseline : ```text E6bis ``` Toute nouveauté D09 doit démontrer : ```text meilleur que E6bis ``` et non simplement : ```text meilleur que 0.72a officiel ``` --- # 93. Invariant Trust Ne pas accorder un privilège parce que : ```text Remote QR dit 4 ModString dit X client prétend être Complete Source ``` Les informations distantes restent des assertions. Les événements locaux : ```text gap reopened reservation released valid bytes received ``` ont davantage de poids. --- # 94. Invariant réseau Ne pas transformer la réévaluation locale en : ```text reask immédiat massif ``` Le principal intérêt de la proposition est justement : ```text réagir plus vite sans augmenter l'overhead ``` --- # 95. Découverte majeure du dossier D09 permet finalement de reformuler NNP. Dans l’interface historique : ```text No Needed Parts ``` semble décrire uniquement le pair. En réalité : > **NNP est une relation entre la Part Status du pair, notre gap list, l’état des réservations et l’instant où la décision est prise.** C’est beaucoup plus important. --- # 96. Décision actuelle Pas de port immédiat : ```text Delayed NNP = 30 s ``` La priorité est : ```text D09-V1 Decision Trace D09-V2 NNP reason shadow D09-V3 Event-driven reclassification shadow ``` Si les traces montrent que des sources fraîchement utiles restent dormantes à cause du modèle NNP historique : ```text ``` --- # 97. Gain potentiel attendu Pas nécessairement un gain sur tous les fichiers. Principalement : ```text fichiers rares corruption proche de la fin AICH recovery SOURCE_LIMITED beaucoup d'A4AF derniers blocs fortement disputés ``` Le gain attendu est surtout : ```text réduire le temps où une source utile existe déjà mais reste mal classée localement ``` --- # 98. Ce que nous ne devons pas faire ```text réduire FILEREASKTIME globalement supprimer les timers NNP attendre 30 s sur tous les NNP forcer tous les A4AF vers le fichier presque fini bannir les HighQR utiliser le QR distant comme vérité réinterroger le réseau après chaque AddGap dupliquer des CUpDownClient par fichier ``` --- # 99. Relation avec les vieux Mods ## NetF WARP Apporte : ```text Delayed NNP ``` et l’explication corruption/endgame. ## Xtreme Apporte historiquement : ```text Just In Time Swapping Auto-A4AF ``` et une forte culture de gestion dynamique des sources. ## Sivka Documente : ```text A4AF handling R4AF compteurs/visibilité ``` ## ZZ / ZZUL La branche officielle étudiée porte explicitement de nombreux marqueurs : ```text ZZ:DownloadManager ``` dans l’implémentation A4AF actuelle. Le moteur moderne est donc déjà le résultat d’années d’évolution communautaire. --- # 100. Conclusion A4AF n’est pas une petite option d’interface. C’est un véritable **ordonnanceur d’affectation des pairs aux fichiers**. La branche 0.72a sait déjà : ```text éviter les doublons de clients mémoriser plusieurs fichiers par source classer les associations NNP respecter les priorités espacer les reasks tenir compte de SX réutiliser une source quand elle devient meilleure ailleurs ``` Mais son modèle NNP reste fondé sur une logique historique où : ```text « plus de bloc immédiatement demandable » ``` est très proche de : ```text « plus rien d'utile chez cette source » ``` Endgame E6bis, AICH et notre Trust Model rendent cette assimilation moins satisfaisante. ```text Delayed NNP 30 s partout ``` mais : > **rendre NNP explicable, détecter pourquoi une source est momentanément inutile et réévaluer les associations A4AF lorsqu’un événement local change réellement nos besoins.** C’est une amélioration entièrement locale, compatible avec le réseau existant, directement utile à Tail Source Wakeup et particulièrement pertinente après corruption ou en `SOURCE_LIMITED`. --- --- # Dossier 10 — Serveurs eD2K : audit technique {#g10} --- # 0. Règle éditoriale pour la suite du projet À partir de ce dossier, nous séparons strictement deux choses. ## Couche publique Les pages pédagogiques expliquent : ```text ce qu'est eMule comment fonctionne eD2K comment fonctionne Kad les choix historiques les limites observées dans le protocole et les implémentations ``` Elles ne présentent pas une amélioration future comme si elle appartenait déjà au client. ## Couche interne Les rapports de travail peuvent contenir : ```text hypothèses pistes expériences instrumentation correctifs candidats comparaisons avec les Mods ``` Ces éléments restent internes jusqu’à : ```text implémentation test validation absence de régression ``` ## Publication ultérieure La logique éditoriale retenue est : > **« Après ces études, voici le fruit de notre travail. »** Une future version du logiciel sera donc présentée après validation comme résultat du travail, et non introduite par anticipation dans chaque fiche historique. Les dossiers publics 01–09 pourront être harmonisés selon cette règle lors d’une passe éditoriale ultérieure. --- # 1. Corpus de référence ## Historique `edonkey-protocol.txt`, v0.6, 2002. Valeur : ```text surface protocolaire ancienne opcodes formats client↔serveur UDP server.met ``` Limite : ```text document communautaire / reverse-engineered pas une spécification normative officielle exhaustive ``` ## Client eMule Community 0.72a : ```text tag eMule_v0.72a-community SHA ac3d52eafd88e9f6d9e0c2204c6dbead18db750b ``` Fichiers majeurs : ```text ServerConnect.cpp ServerSocket.cpp ServerList.cpp UDPSocket.cpp SharedFileList.cpp DownloadQueue.cpp Opcodes.h ``` ## Serveur indépendant eNode, `petermrg/eNode`. Utilité : ```text observer le côté serveur rejouer en laboratoire corroborer Login / OfferFiles / Search / GetSources / Callback ``` Ne pas prendre eNode comme norme de tous les serveurs historiques. --- # 2. Surface protocolaire historique Client → serveur : ```text 0x01 Login 0x14 Get Server List 0x15 Offer Files 0x16 Search 0x19 Get Sources 0x1C Callback Request 0x21 More Results ``` Serveur → client : ```text 0x32 Server List 0x33 Search Result 0x34 Server Status 0x35 Callback Requested 0x36 Callback Failed 0x38 Server Message 0x40 ID Change 0x41 Server Ident 0x42 Found Sources ``` UDP historique : ```text 0x96 Status Request 0x97 Status Result 0x98 Search 0x99 Search Result 0x9A Get Sources 0x9B Found Sources 0x9C Callback 0x9E Callback Fail 0xA1 Server List 0xA2 Server Desc Request 0xA3 Server Desc ``` --- # 3. Control plane / data plane Le serveur appartient principalement au : ```text CONTROL PLANE ``` Il aide à : ```text identifier indexer rechercher découvrir rendre joignable ``` Le contenu reste dans le : ```text DATA PLANE client ↔ client ``` Cette distinction sera utile pour tous les dossiers suivants. --- # 4. Modèle de connexion serveur Le client possède un `connectedsocket` courant. Plusieurs essais peuvent exister pendant la connexion, mais une fois l’un d’eux établi : ```text un seul serveur TCP principal ``` reste actif. En parallèle : ```text plusieurs serveurs UDP ``` peuvent être consultés. --- # 5. Login moderne `CServerConnect::ConnectionEstablished()` construit : ```text UserHash ClientID TCP port TagCount = 4 ``` Tags : ```text CT_NAME CT_VERSION CT_SERVER_FLAGS CT_EMULE_VERSION ``` Puis : ```text OP_LOGINREQUEST ``` --- # 6. Capabilities du client `CT_SERVER_FLAGS` annonce : ```text ZLIB NEWTAGS LARGEFILES UNICODE SUPPORTCRYPT REQUESTCRYPT REQUIRECRYPT ``` Le login est donc également une négociation de capacités. --- # 7. IDCHANGE `ServerSocket.cpp` traite `OP_IDCHANGE`. Le paquet moderne peut contenir : ```text ClientID server TCP flags public IP rapportée ``` Le client : ```text enregistre l'ID déduit l'IP depuis le HighID ou conserve l'IP rapportée si LowID ``` --- # 8. Confirmation indépendante par eNode eNode : ```text loginRequest → isFirewalled → LowID si firewalled → IPv4 comme ID si joignable ``` Le modèle est cohérent avec la lecture côté client. Les détails précis du test de firewall ne doivent pas être généralisés à tous les serveurs sans source supplémentaire. --- # 9. Après connexion : remise à zéro de la publication Une fois connecté : ```cpp ClearED2KPublishInfo(); SendListToServer(); ``` Une nouvelle session serveur repart donc avec un nouvel état de publication. --- # 10. `SendListToServer()` La fonction sélectionne les fichiers : ```text non encore publiés et compatibles avec les capacités du serveur ``` Puis les trie selon la priorité d’upload. --- # 11. Limite de publication Le client calcule : ```text limit = SoftFiles ``` puis force : ```text si 0 ou > 200 → 200 ``` Il faut rester prudent sur la sémantique serveur exacte de `SoftFiles`. Le fait solide est : ```text le client l'utilise pour borner son lot ``` --- # 12. Publication progressive Les fichiers envoyés deviennent : ```text PublishedED2K = true ``` Les suivants attendent un autre cycle. Constante : ```text ED2KREPUBLISHTIME = 1 minute ``` --- # 13. Compression Si le serveur annonce : ```text SRV_TCPFLG_COMPRESSION ``` `OP_OFFERFILES` peut être compressé. Le code essaie la compression et garde le paquet compressé seulement si elle est avantageuse. --- # 14. KeepAlive `KeepConnectionAlive()` envoie : ```text OP_OFFERFILES count = 0 ``` comme paquet de maintien de connexion. C’est une curiosité historique documentable. --- # 15. Publication côté serveur eNode : ```text offerFiles() → db.files.addSource(file, client.info) ``` Le modèle serveur est donc : ```text FileHash / FileMetadata ↔ Client source ``` et non stockage du contenu. --- # 16. Recherche serveur Historique : ```text OP_SEARCHREQUEST → OP_SEARCHRESULT ``` eNode : ```text db.files.find(query) ``` La recherche porte sur l’index serveur. --- # 17. Limite de la recherche L’index d’un serveur n’est pas : ```text l'intégralité du réseau eD2K ``` Les recherches UDP globales et Kad complètent cette vue. --- # 18. GetSources TCP Historique : ```text OP_GETSOURCES → OP_FOUNDSOURCES ``` La requête moderne du client ajoute la taille du fichier. Grand fichier : ```text Hash 0 marker uint64 size ``` Fichier classique : ```text Hash uint32 size ``` --- # 19. Variante obfuscation Si possible : ```text OP_GETSOURCES_OBFU → OP_FOUNDSOURCES_OBFU ``` La réponse enrichie peut fournir des données de connexion supplémentaires, dont un UserHash selon les options. --- # 20. Réception FoundSources Le client fait : ```text FileHash → CPartFile → AddSources() ``` Le serveur alimente donc le même pipeline de source admission que les autres canaux. --- # 21. Queue locale des demandes serveur `m_localServerReqQueue` La sélection tient compte de : ```text LastSearchTime DownloadPriority ``` afin de choisir quels fichiers interroger en premier. --- # 22. Batch TCP Valeur actuelle : ```text iMaxFilesPerTcpFrame = 15 ``` Le client concatène jusqu’à 15 requêtes dans une même frame. --- # 23. Cadence du batch TCP Après le frame : ```cpp 15 * (16 + 4) seconds ``` soit environ : ```text 300 s = 5 min ``` avant le batch suivant. Ce timer est distinct de la constante historique : ```text SERVERREASKTIME = 15 min ``` Ne pas les confondre dans la documentation. --- # 24. UDP GetSources multi-serveurs Le client peut interroger successivement les serveurs de `server.met`. Il saute : ```text serveur TCP courant serveurs trop défaillants ``` et vérifie : ```text source limit LargeFiles support UDP flags ``` --- # 25. Cadence UDP Constante : ```text UDPSERVERREASKTIME = 30 min ``` Le mécanisme n’est donc pas permanent. --- # 26. Limites de la requête UDP Constantes/logique : ```text MAX_UDP_PACKET_DATA = 510 MAX_REQUESTS_PER_SERVER = 35 ``` Plusieurs hashes peuvent être groupés. La taille de la requête varie avec : ```text GetSources1 GetSources2 LargeFiles ``` --- # 28. ServerStats Le client sonde périodiquement les serveurs. Cadence nominale : ```text ≈ 4,5 h ``` avec randomisation historique et minimum de 20 min pour certains repings. --- # 29. Challenge UDP Requête : ```text challenge uint32 ``` Réponse attendue : ```text même challenge ``` Si différent : ```text ignore ``` Niveau de confiance : ```text corrélation ``` pas authentification forte. --- # 30. Informations apprises par UDP Selon taille/version : ```text users files max users soft files hard files UDP flags LowID users UDP obfuscation port TCP obfuscation port server UDP key ``` --- # 31. Capacités UDP Bits connus dans la branche : ```text ExtGetSources ExtGetSources2 ExtGetFiles NewTags Unicode LargeFiles UDP obfuscation TCP obfuscation ``` --- # 32. Server Description Ancien format : ```text name description ``` Nouveau format : ```text challenge tag list ``` Le client rejette une nouvelle réponse au challenge inattendu. --- # 33. Callback serveur Historique : ```text OP_CALLBACKREQUEST → serveur → OP_CALLBACKREQUESTED vers LowID → LowID connecte le demandeur ``` eNode possède exactement ce modèle dans son code. --- # 34. Échec callback Si le LowID ciblé n’est plus dans la table du serveur : ```text CALLBACKFAILED ``` Le serveur ne peut pas joindre un client qu’il n’a plus en session. --- # 35. Extension callback moderne `OP_CALLBACKREQUESTED` peut contenir : ```text IP port crypt options UserHash ``` Si le UserHash reçu contredit celui déjà associé au client : ```text le client ne fait pas confiance aux options crypt ``` Bonne pratique historique à retenir dans le Trust Model. --- # 36. `server.met` historique Document 2002 : ```text 0x0E DWORD count IP port Meta list ``` Le document mentionne même l’existence possible de données de queue/tail non interprétées. --- # 37. `server.met` moderne Lecture : ```text 0xE0 ou 0x0E ``` Écriture : ```text 0xE0 ``` --- # 38. Métadonnées modernes conservées Le writer peut stocker : ```text name dynIP description fail count preference users files ping last ping max users soft files hard files version UDP flags LowID users UDP key UDP key IP TCP/UDP obfuscation ports ``` --- # 39. Sauvegarde transactionnelle simple Séquence : ```text écrire server.met.new ancien server.met → server_met.old server.met.new → server.met ``` C’est une protection utile contre une écriture directe détruisant le fichier courant. --- # 40. Sauvegarde périodique `CServerList::Process()` : ```text ≈ 17 min ``` pour persister les informations apprises. --- # 41. AutoUpdate Sources possibles : ```text URL configurée server.met local server_met.download staticservers.dat ``` Lors d’un update, une ancienne liste est conservée en backup. --- # 43. Server list fournie par un serveur `OP_SERVERLIST` Les nouvelles entrées sont créées : ```text SRV_PR_LOW ``` Une liste reçue d’un serveur n’est donc pas promue directement. --- # 44. Admission d’un serveur `AddServer()` vérifie : ```text GoodIP IPFilter duplicate address:port duplicate IP/TCP DynIP ``` --- # 45. Trust boundary serveur ## Mesuré localement ```text TCP connect UDP response ping callback abouti source connectée fichier confirmé ``` ## Déclaré par serveur ```text name description version users files LowID users source list search results capability flags ``` ## Corrélé ```text UDP challenge response description challenge ``` Ne pas confondre les trois niveaux. --- # 46. Fake content vs corruption Un fichier peut être : ```text parfaitement cohérent avec son ED2K hash ``` tout en étant : ```text mal nommé ou trompeur ``` AICH protège : ```text l'intégrité du contenu associé au hash ``` pas : ```text la vérité du nom affiché ``` --- # 47. Première campagne interne : rendement des canaux de sources Pour un ensemble de FileHash : ```text server TCP server UDP Kad SX ``` Tracer : ```text announced new duplicate filtered connected file-confirmed payload hash-valid ``` Aucune décision automatique dans la première phase. --- # 48. Impact du réglage Crypt Required Comparer sur périodes/fichiers comparables : ```text Crypt Required OFF Crypt Required ON ``` Mesures : ```text UDP server sources time-to-first-source time-to-first-transfer sources utiles totales ``` But : comprendre l’impact réel du chemin désactivé. Pas recommander un réglage avant résultats. --- # 49. Evidence par serveur Structure conceptuelle : ```text ServerEvidence Reachability SourceYield SearchYield CallbackReliability CapabilityConsistency ``` Ne pas créer un score unique 0–100. --- # 50. Callback evidence Tracer séparément : ```text server TCP callback server UDP callback Kad Buddy callback Direct UDP callback ``` Mesures : ```text attempt instruction received connection fail latency ``` --- # 51. Publication coverage Pour une grosse bibliothèque : ```text local file count published flag count server SoftFiles OfferFiles packet count time since connect ``` Question à tester : ```text temps réel pour publier l'ensemble ``` Le client montre ce qu’il envoie ; seul un test côté serveur peut confirmer ce qui est conservé. --- # 52. eNode comme serveur laboratoire Intérêt majeur : ```text contrôler les deux extrémités ``` Tests possibles : ```text HighID LowID OfferFiles GetSources Search Callback UDP ``` Permet captures et assertions précises sans dépendre d’un serveur public. --- # 53. Limite eNode Le projet contient encore des TODO et ne couvre pas nécessairement toutes les extensions modernes. Règle : ```text absence dans eNode ≠ absence dans le protocole ``` --- # 54. Wireshark comme troisième témoin Pour les futurs layouts binaires : ```text eMule writer eMule reader eNode/aMule Wireshark capture réelle ``` Trianguler avant de publier une spécification octet-par-octet. --- # 55. server.met provenance Le format ne porte pas une signature d’auteur. Première instrumentation potentielle : ```text origin URL/manual/server imported count filtered count duplicates DNS success UDP response TCP success ``` Pas de nouveau format nécessaire pour mesurer. --- # 56. Fuzzing local `server.met` Cibles : ```text AddServerMetToList AddTagFromFile ``` Cas : ```text version invalide count énorme troncature tag inconnu string length excessive DynIP malformé ``` Objectif : ```text pas de crash pas d'allocation aberrante pas d'état partiellement créé ``` --- # 57. Fuzzing TCP serveur Cibles : ```text OP_IDCHANGE OP_SERVERSTATUS OP_SERVERIDENT OP_SERVERLIST OP_FOUNDSOURCES OP_CALLBACKREQUESTED OP_SEARCHRESULT ``` --- # 58. Fuzzing UDP serveur Cibles : ```text GLOBSEARCHRES GLOBFOUNDSOURCES GLOBSERVSTATRES SERVER_DESC_RES ``` Les challenges/taille doivent être testés aux frontières. --- # 59. Performance : ne pas optimiser le CPU au mauvais endroit Sur une machine moderne, parser quelques dizaines de records serveur coûte peu. Le coût pratique peut plutôt venir de : ```text mauvaises connexions déclenchées pollution de listes timeouts recherches inutiles callbacks ratés ``` Les métriques doivent donc mesurer le résultat réseau, pas seulement les microsecondes CPU. --- # 60. Ne pas classer les serveurs uniquement par taille Un gros serveur peut fournir : ```text beaucoup de résultats ``` simplement parce qu’il indexe davantage de clients. Un plus petit peut apporter : ```text des hashes uniques des sources différentes ``` Mesurer aussi : ```text diversité nouveauté utilité ``` --- # 61. Search diversity Comparer : ```text hashes uniques par serveur duplications temps de réponse résultats recoupés avec Kad ``` Une divergence entre index n’est pas automatiquement frauduleuse. Les populations indexées sont différentes. --- # 62. Source diversity Même principe : ```text 10 nouvelles sources ``` peuvent être plus utiles que : ```text 100 doublons ``` Le rendement doit distinguer quantité et information réellement nouvelle. --- # 63. Failed count Les serveurs accumulent un compteur d’échecs. Ils peuvent être retirés après le seuil configuré. À tester sur : ```text DNS dynamique coupure temporaire VPN réseau mobile ``` avant toute modification de politique. --- # 64. Challenge pseudo-aléatoire Les challenges serveur utilisent encore des générateurs historiques. Ce n’est pas équivalent au problème SecureIdent. Ici le challenge sert essentiellement à : ```text corréler requête/réponse ``` et non à protéger une identité cryptographique. Priorité de modernisation faible. --- # 67. Conclusion Le serveur eD2K assure principalement : ```text session / Client ID indexation recherche discovery de sources callback LowID ``` La branche moderne ajoute : ```text multi-server UDP capability flags compression LargeFiles obfuscation server health server.met enrichi ``` L’étude ne justifie pas de « refaire » le protocole serveur. Le travail interne pertinent est plutôt de mesurer, sur le réseau actuel, la valeur réelle de chaque fonction et de chaque canal. Les conclusions applicables au futur client devront être publiées seulement après expérimentation et validation. --- --- # Dossier 11 — Kad : audit architecture, routage, bootstrap, publication et joignabilité {#g11} ## 1. Périmètre Composants audités : ```text kademlia/kademlia/Defines.h Kademlia.cpp Prefs.cpp Search.cpp SearchManager.cpp Indexed.cpp UDPFirewallTester.cpp net/KademliaUDPListener.cpp routing/Contact.cpp RoutingBin.cpp RoutingZone.cpp utils/UInt128.cpp DownloadQueue.cpp BaseClient.cpp Opcodes.h ``` ## 2. Source algorithmique Les sources eMule citent explicitement l’article Kademlia de 2002. La structure générale repose sur un ID 128 bits, une distance XOR, une table de routage, un lookup itératif et du stockage près de la clé. Mais il faut distinguer : ```text papier Kademlia et protocole Kad d'eMule ``` Kad contient de nombreuses règles spécifiques à l’écosystème eMule. ## 3. Paramètres centraux observés Dans `Defines.h` : ```text K = 10 ALPHA_QUERY = 3 SEARCH_JUMPSTART = 1 s ``` Les durées courantes sont de l’ordre de 45 s pour plusieurs recherches classiques et plus longues pour les opérations de stockage. ## 4. Le Kad ID local `Prefs.cpp` initialise : ```text m_uClientID.SetValueRandom() ``` et le persiste dans : ```text preferencesKad.dat ``` Le fichier mémorise notamment l’IP, un ancien champ de port devenu inutilisé et le Kad ID 128 bits. Si l’ID lu vaut zéro, il est régénéré. ## 5. Génération du Kad ID `CUInt128::SetValueRandom()` utilise actuellement : ```text CryptoPP::AutoSeededRandomPool ``` pour générer 16 octets aléatoires. C’est un point distinct des anciens usages de `rand()` encore présents ailleurs. ## 6. Distance Le code calcule fréquemment : ```cpp distance = ContactID; distance.Xor(target); ``` Les listes de candidats sont ordonnées par cette distance. La proximité XOR est donc réellement le critère de progression. ## 7. RoutingZone `RoutingZone.cpp` décrit un arbre binaire. Une zone est soit une branche possédant deux sous-zones, soit une feuille possédant un `RoutingBin`. Les IDs sont traités relativement au centre local, considéré comme `000...000`. ## 8. `nodes.dat` normal La racine charge : ```text config\nodes.dat ``` Le parser reconnaît l’ancien format et des versions modernes 1 à 3. Les formats modernes commencent par un count nul afin d’empêcher de très anciens clients d’interpréter incorrectement la suite. ## 9. Enregistrement d’un contact Selon la version du fichier : ```text Kad ID 16 octets IP 4 UDP port 2 TCP port 2 version 1 UDP key selon format verified selon format ``` Le code contrôle aussi que la taille restante du fichier est compatible avec le nombre de contacts annoncé. ## 10. Admission depuis `nodes.dat` Contrôles observés : ```text GoodIPPort IPFilter restriction du port 53 pour anciens nœuds non obfusqués type/version limites globales d'IP ``` Un vieux fichier sans contact marqué `verified` peut déclencher une compatibilité qui les marque temporairement vérifiés afin d’accélérer le bootstrap. ## 11. Bootstrap `nodes.dat` spécial Version 3 + `BootstrapEdition == 1`. Le commentaire du code prévoit typiquement beaucoup plus de contacts qu’un cache normal. Ils ne sont pas injectés directement dans la routing table mais utilisés comme candidats de bootstrap. ## 12. Sélection bootstrap La branche choisit jusqu’à : ```text 50 candidats ``` parmi les contacts valides, triés selon la proximité XOR avec notre propre ID. ## 13. Motivation réseau Le commentaire source explique qu’un petit `nodes.dat` identique distribué à tous entraînerait une concentration de connexions vers les mêmes nœuds. La bootstrap edition réduit ce risque en répartissant les tentatives. C’est un exemple important d’un réglage qui doit être raisonné au niveau du réseau entier, pas uniquement du client local. ## 14. Bootstrap réseau Le client envoie : ```text KADEMLIA2_BOOTSTRAP_REQ ``` Une réponse peut transmettre l’identité du répondant et jusqu’à environ 20 contacts. Ces contacts restent des **candidats** ; ils ne deviennent pas automatiquement des pairs de confiance. ## 15. Self lookup Après démarrage, la branche planifie un premier lookup de son propre ID dans les premières minutes, puis le renouvelle environ toutes les 4 heures. Ce mécanisme aide à rafraîchir la connaissance de la zone logique proche. ## 16. Consolidation La routing table possède un cycle de consolidation d’environ : ```text 45 min ``` pour regrouper des zones lorsque la structure peut être simplifiée. ## 17. Démarrage d’un lookup `CSearch::Go()` demande une réserve d’environ : ```text 50 contacts candidats ``` à la routing table, puis interroge les meilleurs selon `ALPHA_QUERY`. Pour un lookup NODE simple, le démarrage est plus conservateur. ## 18. ALPHA_QUERY Valeur actuelle : ```text 3 ``` Ce parallélisme constitue un compromis entre progression rapide et charge réseau. Il ne doit pas être augmenté sans mesure globale. ## 19. JumpStart Si aucune réponse n’est arrivée pendant environ trois secondes, le lookup peut tenter un autre candidat. Si la liste est épuisée : ```text PrepareToStop() ``` ## 20. Tolérance aux nœuds morts Lorsque les meilleurs nœuds attendus sont injoignables, le code possède une logique qui peut redemander davantage de contacts proches à un nœud ayant répondu. Il ne suppose donc pas que les premiers candidats sont vivants. ## 21. Limite du nombre de contacts reçus Une réponse qui contient plus de contacts que demandé est rejetée comme violation de protocole potentiellement malveillante. Ce contrôle est explicite dans `ProcessResponse()`. ## 22. Unicité IP Pendant une réponse de routage : ```text pas plusieurs Kad IDs pour la même IP ``` La branche impose aussi hors LAN une limite de deux contacts par `/24` dans une même réponse. ## 23. Portée anti-Sybil Ces limites empêchent des abus simples, mais ne constituent pas une défense complète contre un acteur disposant de nombreuses adresses/réseaux. Ne pas les présenter comme une preuve d’identité. ## 24. Vérification IP `VerifyContact()` associe : ```text Kad ID + IP observée ``` à un état `verified`. Toute instrumentation future doit distinguer : ```text appris contacté répondant verified ``` ## 25. Nœuds UDP firewalled Le listener évite d’intégrer les nœuds UDP firewalled à la routing table. Pour router efficacement, un nœud doit pouvoir répondre en UDP. ## 26. Firewall TCP Le client recueille plusieurs observations avant de conclure sur son état. La constante : ```text KADEMLIAFIREWALLCHECKS = 4 ``` montre que la décision n’est pas fondée sur un seul témoin. ## 27. Port UDP externe La branche demande le port vu de l’extérieur à plusieurs IP. Jusqu’à trois témoins sont utilisés et une concordance de deux observations permet d’accepter la valeur. Sinon, le port externe est considéré peu fiable. ## 28. Statistiques de firewall Le code maintient des estimations de ratios TCP/UDP firewalled parmi des nœuds compatibles observés. Ce ne sont pas des statistiques exhaustives du réseau. ## 29. Estimation des fichiers Kad `Prefs.cpp` dit explicitement qu’il n’existe pas de moyen réel de connaître exactement le nombre de fichiers de Kad. Le nombre affiché est une estimation dérivée notamment d’une moyenne eD2K ou de l’index local. ### Règle documentaire Ne jamais présenter cette valeur comme un comptage exact. ## 30. Republication Constantes 0.72a : ```text KADEMLIAPUBLISHTIMES = 5 h KADEMLIAPUBLISHTIMEN = 24 h KADEMLIAPUBLISHTIMEK = 24 h ``` Respectivement : ```text sources notes keywords ``` ## 31. Limites d’index Valeurs observées : ```text KADEMLIAMAXINDEX = 50 000 KADEMLIAMAXENTRIES = 60 000 KADEMLIAMAXSOURCEPERFILE = 1 000 KADEMLIAMAXNOTESPERFILE = 150 ``` Le détail de `Indexed.cpp` doit être étudié avant toute conclusion sur les seuils effectifs d’admission/remplacement. ## 32. Charge des nœuds Le moteur de publication reçoit des informations de charge. Après certains stores, la branche peut retarder l’utilisation d’un nœud très chargé. Il existe donc déjà un mécanisme local d’évitement des zones saturées. ## 33. Buddy Premier cycle `FindBuddy` après quelques minutes, puis réessais espacés lorsqu’un Buddy est nécessaire. Le Buddy est lié à l’état TCP firewalled et à la capacité de callback Kad. ## 34. Callback Kad Flux conceptuel : ```text lookup du Buddy → demande au Buddy → Buddy relaie la demande → client firewalled initie TCP ``` À distinguer absolument du callback serveur et du direct UDP callback. ## 35. Direct UDP Callback Capacité annoncée si : ```text Kad actif TCP firewalled UDP ouvert/vérifié ``` Cela permet à certains clients d’éviter le Buddy pour la sollicitation de callback. ## 36. Types de sources Kad `DownloadQueue::KademliaSearchFile()` distingue différents types : source joignable, source firewalled avec Buddy, direct callback, etc. Ils ne sont pas tous instanciés de la même façon. ## 37. Trust boundary Kad ### Observé ```text réponse UDP latence connexion TCP callback réussi payload ``` ### Déclaré ```text Kad ID contacts source metadata flags firewall port rapporté ``` ### Recoupé ```text port externe firewall checks IP verification ``` ## 45. Hypothèse : valeur de `verified` Mesurer si un contact verified présente réellement : ```text plus de réponses moins de churn meilleure stabilité ``` qu’un non-verified. Ne pas le supposer. ## 46. Hypothèse : impact CGNAT/VPS sur les limites /24 Les règles IPv4 historiques peuvent se comporter différemment dans un Internet où CGNAT et hébergement massif sont courants. Mesurer avant toute modification. ## 47. IPv6 La branche auditée reste fortement fondée sur IPv4. Ajouter IPv6 ne serait pas un changement local de type : ```text uint32 → IPv6 ``` Cela toucherait le wire protocol, les structures de contacts, les callbacks, les filtres et l’interop. À isoler dans une étude future. ## 48. Fuzzing `nodes.dat` Fixtures : ```text count énorme version inconnue troncature IP/port invalides clé UDP tronquée verified incohérent bootstrap edition malformée ``` Objectif : rejet propre, sans crash ni allocation aberrante. ## 49. Fuzzing Kad UDP Cibles : ```text BOOTSTRAP_RES HELLO_REQ/RES ROUTING_RES SEARCH_RES PUBLISH_REQ/RES FIREWALL CALLBACK ``` Tester notamment count, longueur, doublons et versions. ## 50. Rejeu déterministe Construire un petit harness offline qui injecte des paquets enregistrés/synthétiques dans le parser, avec assertions sur l’état final. C’est plus sûr que de tester des entrées malformées sur le réseau public. ## 51. Prudence sur les timers Un timer local peut changer la charge imposée à tout Kad. Aucune réduction de cadence ne doit partir d’une impression de lenteur. ## 52. Pas de « Kad Turbo » Augmenter naïvement : ```text ALPHA fréquence des lookups republication nombre de réponses ``` peut augmenter l’overhead sans produire plus de sources utiles. ## 53. Première amélioration raisonnable : observabilité Avant toute modification wire : ```text compteurs traces histogrammes motifs de rejet latences canal de découverte ``` ## 54. Diagnostic bootstrap Après instrumentation, un diagnostic pourrait distinguer : ```text aucun contact contacts morts UDP bloqué port externe instable Kad connecté mais TCP firewalled ``` Pour l’instant : mesurer seulement. ## 55. Ce que le code seul ne dit pas Il ne permet pas de connaître en 2026 : ```text nombre réel de nœuds actifs churn ratio firewalled réel part des sources utiles venant de Kad durée de vie pratique d'un nodes.dat ``` Ces réponses exigent des mesures actuelles. ## 56. Conclusion Le moteur Kad est déjà riche en défenses et en régulation : ```text limites de réponse contrôles IP/sous-réseaux verified state filtrage UDP firewalled JumpStart load handling publication bornée ``` La priorité de notre étude est : ```text comprendre mesurer rejouer fuzzer ``` avant de changer quoi que ce soit. Le Dossier 12 étudie ensuite le moment où deux clients découverts se rencontrent réellement : **Hello, HelloAnswer et négociation des capacités**. ================================================================================================ --- # Dossier 12 — Audit Hello / HelloAnswer / négociation des capacités {#g12} > Périmètre : établissement client-client et négociation des capacités. Les pistes ci-dessous sont des sujets de mesure ou de robustesse, pas des fonctions produit annoncées. ## 1. Sources auditées ```text BaseClient.cpp UpdownClient.h ClientList.cpp ClientUDPSocket.cpp Packets.cpp Opcodes.h edonkey-protocol.txt ``` ## 2. Handshake historique Le wire eDonkey contient déjà : ```text OP_HELLO 0x01 OP_HELLOANSWER 0x4C ``` Le document historique associe ce handshake à un `Client info` puis à l’adresse serveur. ## 3. Construction du Hello actuel `SendHelloPacket()` écrit : ```text uint8 = 16 SendHelloTypePacket() opcode = OP_HELLO ``` Le `16` correspond historiquement à la taille du UserHash qui ouvre le corps du Hello. ## 4. HelloAnswer `SendHelloAnswer()` appelle le même : ```text SendHelloTypePacket() ``` puis utilise `OP_HELLOANSWER`. Le contenu principal est donc symétrique. ## 5. Tronc commun du payload `SendHelloTypePacket()` écrit notamment : ```text UserHash 16 eD2K ID uint32 TCP port uint16 TagCount Tags... Server IP Server port ``` Les extensions modernes vivent surtout dans les tags. ## 6. `CT_NAME` Valeur : ```text pseudonyme utilisateur ``` Classe de confiance : ```text DISPLAY_CLAIM ``` Ne jamais utiliser ce champ comme identité forte. ## 7. `CT_VERSION` Valeur de base : ```text EDONKEYVERSION = 0x3C ``` Ce champ historique n’est pas la version logicielle complète eMule. ## 8. `CT_EMULE_UDPPORTS` Packing actuel : ```text bits 31..16 = Kad UDP port bits 15..0 = eMule UDP port ``` Le Kad port annoncé peut être le port externe vérifié ou le port interne configuré selon l’état du firewall tester. ## 9. Buddy tags Si : ```text Buddy présent ET client firewalled ``` la branche ajoute : ```text CT_EMULE_BUDDYIP CT_EMULE_BUDDYUDP ``` Ces champs influencent le chemin de callback. ## 10. `CT_EMULE_MISCOPTIONS1` Packing observé : ```text AICH version << 29 Unicode << 28 UDP version << 24 Data compression << 20 SecureIdent << 16 SX1 version << 12 Extended Requests << 8 Comments << 4 PeerCache << 3 NoViewSharedFiles << 2 MultiPacket << 1 Preview << 0 ``` Plusieurs champs occupent plusieurs bits. ## 11. Valeurs MISC1 actuelles Branche étudiée : ```text AICH = 1 Unicode = 1 UDPVer = 4 Compression = 1 SecureIdent = 3 si crypto disponible SX1 = 4 ExtendedRequests = 2 Comments = 1 MultiPacket = 1 Preview selon réglage PeerCache = 0 ``` Le commentaire SX1 rappelle un héritage de compatibilité. ## 12. `CT_EMULE_MISCOPTIONS2` Il regroupe notamment : ```text Kad version LargeFiles ExtMultiPacket reserved/mod bit SupportsCryptLayer RequestsCryptLayer RequiresCryptLayer SupportsSourceEx2 SupportsCaptcha DirectUDPCallback ``` Avant toute documentation octet-par-octet définitive, le packing exact doit être recoupé avec le parser, aMule et des captures. ## 13. Connect Options condensées `SetConnectOptions(uint8)` interprète : ```text bit 0 = crypt support bit 1 = crypt request bit 2 = crypt require bit 3 = direct UDP callback ``` Cette forme compacte réapparaît dans des réponses de sources et callbacks. ## 14. États contradictoires possibles Paquets théoriquement incohérents : ```text RequiresCrypt = 1 SupportsCrypt = 0 ``` ou : ```text DirectCallback = 1 sans port exploitable ``` Le parser doit rester robuste et la machine d’état choisir un comportement conservateur. ## 15. Ancien `OP_EMULEINFO` `SendMuleInfoPacket()` écrit : ```text eMule short version EMULE_PROTOCOL 7 tags ``` Tags actuels : ```text compression UDP version UDP port SourceExchange Comments ExtendedRequests Features ``` ## 16. `ET_FEATURES` Le champ legacy combine notamment SecureIdent et Preview. Il existe donc une duplication historique entre plusieurs mécanismes d’annonce. ## 17. `ProcessMuleInfoPacket()` Le parser conserve des déductions implicites pour d’anciennes versions : ```text UDP v1 SX1 v1 comments shared directories ``` Commentaire clé du code : ```text in the future do not use version to guess about new features ``` ## 18. Principe d’extension Toute future fonction qui exigerait une interop devrait préférer : ```text capability explicite ``` à : ```text if software version >= X ``` Mais aucun nouveau bit n’est justifié tant qu’un besoin wire réel n’existe pas. ## 19. Classification du logiciel `InitClientSoftwareVersion()` distingue entre autres : ```text cDonkey xMule aMule Shareaza lphant MLdonkey eDonkeyHybrid eMule-compatible ``` Utilité : ```text UI debug compatibilité ``` Pas : ```text preuve de confiance ``` ## 20. Mod string / version string Un pair peut falsifier une chaîne logicielle. Toute logique anti-leecher fondée uniquement sur ce nom serait fragile. Le D05 reste la référence pour une confiance fondée sur des observations. ## 21. Connexion obfusquée `CUpDownClient::Connect()` active la couche d’obfuscation si : ```text UserHash valide remote SupportsCrypt local Crypt enabled ET (remote RequestsCrypt OU local Preferred) ``` Sinon, elle reste en clair si la politique le permet. ## 22. Required Crypt Les incompatibilités sont également filtrées lors de l’admission des sources. Si une extrémité exige un mode que l’autre ne peut satisfaire : ```text source/connexion inutilisable ``` Cette interaction doit être étudiée avec D13. ## 23. Large Files La capability gouverne l’usage de variantes telles que : ```text OP_COMPRESSEDPART_I64 OP_SENDINGPART_I64 OP_REQUESTPARTS_I64 ``` Tester explicitement les frontières de taille. ## 24. MultiPacket Le protocole conserve plusieurs générations : ```text OP_MULTIPACKET deprecated OP_MULTIPACKET_EXT deprecated OP_MULTIPACKET_EXT2 moderne ``` Les anciennes branches sont des chemins de compatibilité susceptibles d’être peu testés aujourd’hui. ## 25. Hashset v2 / `FileIdentifier` Les opcodes : ```text OP_HASHSETREQUEST2 OP_HASHSETANSWER2 ``` utilisent un `FileIdentifier` plus riche et peuvent demander des données MD4/AICH selon options. À relier au D07 et au D14. ## 26. Direct UDP Callback Le bit local n’est activé que si : ```text Kad actif TCP firewalled UDP non-firewalled UDP verified ``` Il s’agit donc déjà d’une capability calculée à partir d’observations. ## 32. Evidence de capacités Structure conceptuelle locale : ```text PeerCapabilities { announced negotiated successfully_used failed } ``` Sans score unique. ## 33. Claimed vs observed Exemples : ```text annonce SX2 → requête SX2 → réponse valide ? ``` ```text annonce AICH → répond à AICH ? ``` Cela peut aider au diagnostic de clients atypiques. ## 34. Ne pas pénaliser immédiatement Une incohérence peut venir d’un vieux client, d’un bug, d’un Mod expérimental, d’un paquet partiel ou d’une mauvaise interprétation de notre parser. Première phase : ```text observer journaliser rejouer ``` pas bannir. ## 35. Dette de compatibilité Inventorier toutes les zones où : ```text version logicielle → capacité supposée ``` et les classer : ```text legacy indispensable legacy encore active supposition remplaçable par flag ``` ## 36. Machine d’état Tester aussi l’ordre des messages : ```text HelloAnswer sans Hello double Hello MuleInfo prématuré File Request avant fin du handshake ``` La robustesse dépend autant de l’ordre que des champs. ## 37. Identité Trois niveaux doivent rester séparés : ```text UserHash déclaré clé publique SecureIdent connue signature SecureIdent vérifiée ``` Ne jamais convertir le premier en troisième dans une métrique. ## 38. Vie privée / fingerprinting Le Hello révèle déjà : ```text nickname capabilities versions ports server Buddy éventuel ``` Chaque nouvelle capability publique augmente un peu le fingerprinting. Toute extension future doit donc avoir un besoin d’interop réel. ## 39. Compatibilité passive d’abord Pour une expérimentation locale, préférer : ```text instrumentation logs compteurs ``` à un nouveau tag/opcode. ## 40. Lien D12 → D13 D12 répond : ```text « comment savons-nous que le pair comprend l'obfuscation ? » ``` D13 répondra : ```text « que fait réellement cette obfuscation au flux ? » ``` Ne pas confondre négociation de capability et sécurité cryptographique. ## 41. Conclusion Le handshake eMule est une couche de négociation riche construite autour d’un cœur eDonkey très ancien. Les risques techniques intéressants aujourd’hui sont surtout : ```text legacy inference tags malformés flags incohérents chemins peu testés claims pris à tort pour des preuves ``` La meilleure première étape est la couverture de tests et l’observabilité, pas l’ajout de nouvelles capacités. ================================================================================================ --- # Dossier 13 — Audit de l’obfuscation du protocole {#g13} > Ce dossier distingue volontairement la fonction historique d’obfuscation de toute promesse de sécurité moderne. Les pistes de travail sont des mesures et tests, pas une annonce de nouveau protocole. ## 1. Sources auditées ```text EncryptedStreamSocket.cpp EncryptedStreamSocket.h EncryptedDatagramSocket.cpp EncryptedDatagramSocket.h BaseClient.cpp ServerConnect.cpp UDPSocket.cpp ClientUDPSocket.cpp Preferences.cpp Opcodes.h ``` ## 2. Terminologie Le code mélange historiquement : ```text Crypt Layer Encryption Layer Protocol Obfuscation Basic Obfuscation ``` Pour notre documentation : ```text « obfuscation » ``` doit rester le terme principal lorsqu’on parle de l’objectif de sécurité. ## 3. But déclaré par le source Le commentaire de `EncryptedStreamSocket.cpp` décrit explicitement : ```text random looking stream very limited protection against passive eavesdropping single connections ``` Donc le but documenté est : ```text réduire l'identification triviale du protocole ``` pas fournir un canal moderne authentifié. ## 4. Conséquence éditoriale Éviter les formulations : ```text eMule chiffre de façon sécurisée les transferts connexion protégée comme HTTPS ``` Préférer : ```text le flux est obfusqué au moyen d'un chiffrement de flux historique ``` ## 5. Client ↔ client : matière de clé Le handshake client-client utilise notamment : ```text remote UserHash RandomKeyPart 4 octets magic directionnelle ``` pour dériver des clés via MD5. Le UserHash doit donc être connu avant de pouvoir employer ce mode. ## 6. Direction des clés Des valeurs différentes sont utilisées pour : ```text send receive ``` afin de séparer les deux keystreams. Vérifier le détail exact dans chaque branche avant de publier une spécification wire complète. ## 7. RandomKeyPart Le commentaire source explique sa fonction : sans variation, plusieurs connexions entre les mêmes clients dériveraient les mêmes clés et RC4 produirait les mêmes sorties initiales. Le RandomKeyPart introduit une variation par connexion. ## 8. RNG moderne La branche actuelle possède : ```text CryptoPP::AutoSeededRandomPool cryptRandomGen ``` pour la génération liée à cette couche. C’est mieux que l’utilisation d’un PRNG faible pour le matériau aléatoire de handshake. ## 9. RC4 Le transform utilisé reste : ```text RC4 ``` avec discard des : ```text 1024 premiers octets ``` du keystream. ## 10. Pourquoi le discard RC4 possède des biais connus dans les premiers octets. Le discard est une mitigation historique. Il ne répare pas l’ensemble des faiblesses structurelles de RC4 et ne doit pas être vendu comme modernisation cryptographique. ## 11. Magic sync Constante observée : ```text MAGICVALUE_SYNC = 0x835E6FC4 ``` Le handshake vérifie cette valeur après transformation pour confirmer que les clés correspondent. ## 12. Negotiation state machine `CEncryptedStreamSocket` maintient plusieurs états : ```text ECS_UNKNOWN ECS_NONE ECS_ENCRYPTING ... ``` et des sous-états de négociation. La robustesse de cette machine est plus importante qu’une micro-optimisation du RC4. ## 13. Client-client handshake Structure commentée dans le source : ```text SemiRandom marker RandomKeyPart MagicValue EncryptionMethodsSupported EncryptionMethodPreferred PaddingLen RandomBytes ``` puis réponse avec méthode sélectionnée et padding. ## 14. Taille du padding Le padding est variable et contribue à changer la signature initiale de la connexion. Le parser doit imposer des bornes strictes au champ de longueur. ## 15. Overhead client-client Commentaire source : ```text 18–48 octets ~33 moyenne + en-têtes IP/TCP ``` Ce chiffre concerne le handshake d’obfuscation, pas tout le coût de la connexion. ## 16. Client ↔ serveur Le client ne dispose pas d’un UserHash serveur partagé. Le mode serveur utilise donc un accord DH historique afin de dériver un secret `S`. ## 17. Paramètres DH observés ```text p = groupe 768 bits embarqué g = 2 private exponent = historique, 128 bits ``` Ne pas présenter 768-bit DH comme un paramètre moderne recommandé. ## 18. Dérivation serveur Direction client sortante : ```text SendKey = MD5(S || magic 34) ReceiveKey = MD5(S || magic 203) ``` Le serveur inverse naturellement les directions. ## 19. Discard serveur Comme côté client-client : ```text 1024 bytes RC4 discarded ``` avant le handshake chiffré utile. ## 20. Handshake serveur Structure commentée : ```text Client: random marker G^A padding Server: G^B Magic methods padding Client: Magic selected method padding ``` La réponse finale peut être différée jusqu’au premier payload pour économiser une frame. ## 21. Overhead serveur Commentaire source : ```text 206–251 octets ~229 moyenne ``` plus les en-têtes. ## 22. Pourquoi DH n’apporte pas d’authentification Le propre commentaire explique qu’un DH non authentifié n’ajoute pas une protection substantielle contre l’écoute active/man-in-the-middle. Cela confirme la finalité d’obfuscation. ## 23. Security boundary ### Ce que l’obfuscation cache partiellement ```text marqueurs wire contenu applicatif à un observateur trivial signature de début fixe ``` ### Ce qu’elle ne cache pas ```text IP port timing volume durée pattern de trafic ``` ## 24. Négociation dans `BaseClient::Connect` Condition actuelle : ```text HasValidHash AND SupportsCryptLayer(remote) AND local enabled AND (remote requests OR local prefers) ``` Alors : ```text SetConnectionEncryption(true, remote UserHash) ``` Sinon false. ## 25. Required state L’état `RequiresCryptLayer` est également utilisé dans le filtrage des sources. Cela crée un impact en amont du transport : ```text découverte → admission → connexion ``` ## 26. Cas D10 : serveur UDP `Crypt Required` empêche le `Global UDP GetSources` historique parce que les sources reçues ne fournissent pas de UserHash utilisable pour cette exigence. À mesurer sur réseau actuel. ## 27. Cas Source Exchange SX moderne peut transporter UserHash/options dans certaines versions/conditions. Vérifier si cela améliore la compatibilité avec `Crypt Required` selon provenance de la source. ## 28. UDP client `EncryptedDatagramSocket` possède un mode spécifique pour des datagrammes client. Il faut distinguer : ```text obfuscation client UDP Kad UDP server UDP ``` Les clés et formats ne sont pas interchangeables. ## 29. Kad UDP Kad utilise des clés UDP dérivées/associées aux contacts et à l’IP. Les objectifs incluent : ```text obfuscation validation d'origine plus robuste que paquet totalement anonyme ``` mais pas une identité forte équivalente à SecureIdent. ## 30. Server UDP Les serveurs peuvent annoncer : ```text UDP obfuscation support server UDP key obfuscated UDP port ``` Le client mémorise ces données dans `CServer`. ## 38. Détection protocolaire Ne pas lancer de tests visant à contourner activement les politiques d’un FAI. Une étude locale/offline peut simplement comparer : ```text entropie apparente marqueurs fixes signature initiale ``` entre plain et obfusqué. ## 39. RC4 : dette technique vs interop Le fait que RC4 soit obsolète cryptographiquement ne signifie pas qu’on peut le remplacer localement. Compatibilité réseau : ```text les deux côtés doivent implémenter le même wire protocol ``` ## 40. Nouvelle crypto éventuelle : changement de protocole Une vraie évolution demanderait au minimum : ```text nouvelle capability nouveau handshake algorithme authentifié versioning fallback interop multi-client protection downgrade tests ``` C’est un projet séparé. ## 41. Ne pas bricoler un « AES à la place de RC4 » Changer seulement la primitive casserait le protocole et ne résoudrait pas : ```text authentification négociation downgrade gestion des clés compatibilité ``` ## 42. Question stratégique future Si une nouvelle couche devait être conçue, son but devrait être défini avant l’algorithme : ```text obfuscation DPI ? confidentialité ? authentification ? les trois ? ``` Sans but clair, aucune modernisation n’est sérieuse. ## 43. Fingerprinting résiduel Même avec contenu pseudo-aléatoire, un observateur peut utiliser : ```text ports durées burst sizes patterns de connexion réseau de pairs ``` Une nouvelle crypto ne rendrait pas automatiquement eMule anonyme. ## 44. Compatibilité serveurs Une extension future ne peut pas supposer que les serveurs eD2K actuels seront mis à jour. C’est une contrainte différente de client-client, où un nouveau protocole peut éventuellement être négocié entre deux clients modernes. ## 46. Conclusion La couche actuelle est un mécanisme historique cohérent avec son objectif : ```text rendre eMule moins trivialement identifiable ``` Elle ne correspond pas aux objectifs d’un protocole moderne de confidentialité authentifiée. Le travail utile immédiat consiste à vérifier sa robustesse et son impact réel sur la compatibilité, pas à annoncer sa substitution. ================================================================================================ --- # Dossier 14 — Audit des liens eD2K, FileIdentifier et collections {#g14} > Périmètre : parsing/génération des liens, identité de fichier, extensions de hashset/AICH, sources embarquées, Magnet et collections. Les propositions ci-dessous sont des tests et analyses, pas des fonctions déjà retenues. ## 1. Sources auditées ```text ED2KLink.cpp / .h FileIdentifier.cpp / .h Collection.cpp / .h CollectionFile.cpp / .h SHAHashSet.cpp / .h Opcodes.h DownloadQueue.cpp ``` ## 2. Forme minimale d’un lien fichier `CED2KFileLink::GetLink()` génère : ```text ed2k://|file||||/ ``` Le générateur minimal ne rajoute donc pas automatiquement hashset/AICH/sources dans cette fonction. ## 3. Validation du nom Le constructeur rejette : ```text name empty ``` Le nom passe par : ```text URLDecode OptUtf8ToStr Trim ``` Compatibilité d’encodage = surface importante de fuzzing. ## 4. Validation de la taille Le parser utilise : ```text _tstoi64 ``` puis exige : ```text size > 0 size <= MAX_EMULE_FILE_SIZE ``` et vérifie la capacité du filesystem au-delà de l’ancienne limite. ## 5. Limites de taille Constantes : ```text PARTSIZE = 9 728 000 bytes OLD_MAX_EMULE_FILE_SIZE = 4 290 048 000 MAX_EMULE_FILE_SIZE = 0x4000000000 = 2^38 = 256 GiB ``` La vieille limite ne doit pas être arrondie abusivement à `2^32`. ## 6. Validation MD4 Le parser exige : ```text 32 caractères ``` puis : ```text strmd4() ``` pour obtenir les 16 octets. Le champ est donc strictement contrôlé. ## 7. `p=` — part hashset Le parser tokenise : ```text HASH:HASH:HASH... ``` Chaque élément doit être un MD4 valide. Il construit un buffer : ```text FileHash uint16 HashCount PartHashes... ``` compatible avec la logique de hashset. ## 8. Double `p=` Le code considère un second hashset dans le même lien comme une erreur interne : ```text m_hashset != NULL → bError ``` Tester explicitement ce cas. ## 9. Hashset incorrect Si un part hash ne se parse pas : ```text bError = true ``` et le hashset entier est supprimé. Le lien fichier principal peut néanmoins rester exploitable selon la branche de parsing. À confirmer par tests unitaires. ## 10. Cohérence hashset / taille Question de robustesse : ```text nombre de part hashes ↔ taille théorique du fichier ``` Le parser du lien construit le hashset ; la validation finale peut intervenir plus tard dans `FileIdentifier`/`PartFile`. Tester la frontière pour éviter une incohérence persistante. ## 11. `h=` — AICH Le parser : ```text DecodeBase32(...) ``` vers la taille d’un `CAICHHash`. Si la taille décodée est correcte : ```text m_bAICHHashValid = true ``` ## 12. Double `h=` Inspecter le comportement avec plusieurs `h=` contradictoires. Le code visible ne montre pas la même protection explicite que `p=`. Fixture à créer. ## 13. Sources extension après `/` Le parser distingue : ```text paramètres avant / extensions après / ``` et cherche ensuite une chaîne contenant : ```text sources ``` Le format accepte une date d’expiration optionnelle. ## 14. Sources IP:port Pour chaque source : ```text parse IP parse port port != 0 port <= 65535 ``` Les LowID sont rejetés dans cette forme car l’adresse seule ne suffit pas à les contacter correctement. ## 15. Hostnames Si le texte n’est pas une IPv4 numérique : ```text SUnresolvedHostname ``` est créé. Le nom est borné par une longueur de 512 caractères dans cette branche. ## 16. Source count La boucle limite : ```text nCount < MAXSHORT ``` car le downstream utilise un count `uint16`. Un lien gigantesque reste toutefois un bon cas de fuzz/performance. ## 17. Source expiry Le format peut inclure : ```text sources@YYMMDD,... ``` Le parser reconstruit une date et ignore les sources si elles sont expirées. Cas à tester : ```text date impossible timezone leap year YY ambiguity ``` ## 18. Paramètre `s=` Le parser utilise `InternetCrackUrl`. Il extrait : ```text scheme hostname path username/password extra ``` mais conserve principalement URL + hostname comme source. ## 19. Surface SSRF / fetch Important : le simple parsing du lien ne doit pas conduire automatiquement à un fetch arbitraire non contrôlé. Auditer tout downstream consommant : ```text s=URL ``` et les WebSources éventuelles. But : comprendre les restrictions de schéma, redirections et réseau local. ## 20. Lien `server` Format : ```text ed2k://|server|host|port|/ ``` Port validé <= 65535. La validation d’IP/DNS et l’IPFilter interviennent ensuite côté ServerList. ## 21. `serverlist` Format : ```text ed2k://|serverlist|URL|/ ``` Le lien transporte une adresse de téléchargement. Les risques appartiennent ensuite au downloader HTTP et au parser `server.met`. ## 22. `nodeslist` Format : ```text ed2k://|nodeslist|URL|/ ``` Même séparation : ```text parsing du lien ≠ validation du nodes.dat ``` ## 23. `search` Format : ```text ed2k://|search|term|/ ``` Le terme passe par URL decode / UTF-8. Il devient ensuite une entrée vers le moteur de recherche. ## 24. Magnet Le parser accepte aussi : ```text magnet: ``` Champs pris en charge : ```text as= dn= xl= xs= xt= ``` ## 25. `xt=urn:ed2k:` Transformé en : ```text strHash ``` Même chose pour : ```text urn:ed2khash: ``` ## 26. `xt=urn:aich:` Transformé en paramètre interne : ```text h= ``` puis passe dans le même parser AICH. Bonne convergence du code. ## 27. `xs=ed2kftp://` Le parser en extrait une source : ```text sources,host:port ``` avant le premier `/`. Tester les hostnames et IPv4 atypiques. ## 28. `as=http://` Seuls les `as` commençant par : ```text http:// ``` sont transformés en `s=` dans ce code. Ne pas inventer HTTPS support dans cette branche sans vérifier le downstream/autres versions. ## 29. Unknown Magnet params Le parser ignore les paramètres inconnus. Bonne propriété d’extensibilité, mais tester : ```text paramètre sans = ordre différent duplications encodages ``` ## 30. `CFileIdentifier` Responsabilités : ```text MD4 master MD4 part hashset AICH master AICH part hashset ``` Le détail d’intégrité est déjà D07 ; ici l’intérêt est l’objet d’identité commun. ## 31. FileIdentifier dans le protocole moderne Utilisé par : ```text OP_MULTIPACKET_EXT2 OP_MULTIPACKETANSWER_EXT2 OP_HASHSETREQUEST2 OP_HASHSETANSWER2 ``` La couche client-client n’est donc plus limitée au simple MD4 de 16 octets. ## 32. Collections `CCollectionFile` stocke un ensemble de tags. Pour une entrée créée depuis un fichier : ```text FT_FILEHASH FT_FILESIZE FT_FILENAME FT_AICH_HASH éventuellement FT_FILECOMMENT éventuellement FT_FILERATING éventuellement ``` ## 33. Collection from link `InitFromLink()` exige un lien fichier valide et copie : ```text hash size name AICH ``` Les sources du lien ne deviennent pas forcément une propriété persistante de la collection. ## 34. Trust des types de fichiers Commentaire source intéressant : si un type a été reçu depuis un serveur/client, il pourrait être faux. La classe tente de combiner type déclaré et type déduit du nom selon contexte. À relier au D16 Search Trust. ## 35. Auteur de collection Tags : ```text FT_COLLECTIONAUTHOR FT_COLLECTIONAUTHORKEY ``` Auditer `Collection.cpp` pour distinguer : ```text nom auteur clé auteur signature éventuelle ``` avant toute explication cryptographique publique plus poussée. ## 41. Limite de `GetLink()` La fonction standard génère uniquement le lien minimal. Si le GUI propose des liens enrichis ailleurs, trouver le constructeur exact avant de documenter leur génération. Ne pas déduire de la capacité du parser que `GetLink()` les produit automatiquement. ## 44. MetMedic comme contexte L’étude MetMedic montre historiquement que manipuler des fichiers `.met` sans préserver leur cohérence peut produire des états incompatibles. Pour les collections et FileIdentifier, même principe : ```text inspection read-only d'abord édition seulement avec invariants testés ``` ## 45. Sécurité des URI handlers Si Windows associe `ed2k://` à eMule, un lien externe peut devenir une entrée réseau/application. Auditer : ```text longueur totale escaping quotes shell invocation URI registration ``` sans supposer que le parser C++ est l’unique couche. ## 46. Pas de source comme autorité Une source intégrée au lien est : ```text bootstrap hint ``` Elle ne doit pas obtenir un niveau de confiance supérieur à une source découverte par serveur/Kad/SX simplement parce qu’elle était dans le lien. ## 47. AICH fourni par lien Un AICH contenu dans le lien est une information externe. La cohérence avec le contenu reçu doit être validée par les mécanismes d’intégrité habituels. Ne pas confondre : ```text hash fourni et hash vérifié par données ``` ## 48. Hashset fourni par lien Même principe pour `p=`. Il peut accélérer le démarrage, mais toute incohérence avec le FileHash/taille doit être détectée. ## 50. Interop aMule Rejouer les mêmes liens dans : ```text eMule aMule ``` et comparer les champs acceptés. C’est une bonne manière de distinguer : ```text extension eMule vs pratique interopérable ``` ## 51. Limite publique Le D14 SITE doit rester centré sur : ```text identité ≠ localisation ``` Les détails de parser/fuzzing restent internes. ## 52. Conclusion Le lien eD2K est l’une des pièces les plus élégantes du protocole : ```text identité stable + localisation facultative ``` Les travaux internes intéressants sont surtout la robustesse du parser, la cohérence des extensions et l’interop, pas la création d’un nouveau format. ================================================================================================ --- # Dossier 15 — Audit des formats persistants et de la mémoire locale {#g15} > Périmètre : robustesse, compatibilité et récupération des fichiers persistants. Ce dossier n’introduit aucun nouveau format produit ; il cherche d’abord à comprendre et tester les formats existants. ## 1. Sources auditées ```text KnownFileList.cpp KnownFile.cpp PartFile.cpp DownloadQueue.cpp ClientCredits.cpp kademlia/Prefs.cpp kademlia/routing/RoutingZone.cpp ServerList.cpp Preferences.cpp Opcodes.h SafeFile.* ``` Références secondaires : ```text edonkey-protocol.txt documentation développeur aMule analyse MetMedic ``` ## 2. Cartographie des responsabilités ```text known.met → mémoire des fichiers *.part.met → état de chaque téléchargement clients.met → crédits / clé SecureIdent distante nodes.dat → contacts Kad server.met → serveurs eD2K preferencesKad.dat → Kad ID local cryptkey.dat → clé privée SecureIdent cancelled.met → fichiers annulés mémorisés ``` ## 3. `known.met` — headers Le loader accepte : ```text MET_HEADER = 0x0E MET_HEADER_I64TAGS = 0x0F ``` Le writer moderne écrit : ```text 0x0F ``` puis : ```text uint32 record count ``` ## 4. `known.met` — records Chaque entrée est chargée par : ```text CKnownFile::LoadFromFile() ``` et rejetée si ses invariants principaux ne sont pas satisfaits. Le code trace notamment les incohérences : ```text file size MD4 master part-hash count attendu/disponible ``` ## 5. Sauvegarde `known.met` Le writer écrit d’abord le nombre d’éléments de la map, puis peut le corriger si certaines entrées ne doivent pas être sauvegardées. Condition : ```text RememberDownloadedFiles OU fichier toujours partagé ``` ## 6. Cadence `known.met` `CKnownFileList::Process()` : ```text ~11 min ``` entre sauvegardes. Mesurer l’impact réel sur une très grosse base avant toute optimisation. ## 7. `cancelled.met` Format actuel documenté directement dans le code : ```text Header Version Seed Count [HashHash + TagCount + Tags]* ``` Headers : ```text ancien = 0x0E actuel = 0x0F version = 0x01 ``` ## 8. Conversion cancelled ancienne version L’ancienne version stockait le vrai hash. La nouvelle calcule une représentation dérivée de : ```text Seed || MD4 → MD5 ``` Le fichier n’expose donc plus simplement le FileHash original. ## 9. Seed `cancelled.met` Si seed = 0 : ```text nouveau seed pseudo-aléatoire non nul ``` Le mécanisme n’a pas vocation à être une protection cryptographique robuste du disque ; il évite surtout le stockage direct des hashes réels. ## 10. `part.met` — importance Le `.part` contient les données. Le `.part.met` porte : ```text identité taille hashsets gaps priorités état métadonnées ``` Perdre le `.part.met` peut donc rendre un gros fichier `.part` difficile à reconstruire correctement. ## 11. Versions `part.met` Constantes actuelles : ```text PARTFILE_VERSION = 0xE0 PARTFILE_SPLITTEDVERSION = 0xE1 PARTFILE_VERSION_LARGEFILE = 0xE2 ``` Étudier précisément les chemins de compatibilité dans `LoadPartFile()` avant de documenter le layout byte-à-byte. ## 13. Backup Après remplacement réussi : ```text CopyFile(part.met, part.met.bak) ``` sous réserve de l’option `bDontOverrideBak`. Cette séquence est déjà une petite transaction locale. ## 14. Échec d’écriture En cas d’exception : ```text Abort() remove(tmp) return false ``` Le fichier principal antérieur reste alors préférable à un temporaire incomplet. ## 15. Récupération startup `DownloadQueue::Init()` parcourt : ```text *.part.met ``` Si : ```text PLR_FAILED_METFILE_CORRUPT ``` il tente : ```text .part.met.bak ``` ## 16. Récupération `.tmp` Après le scan normal, le code cherche aussi : ```text *.part.met.tmp ``` Un temporaire valide peut être resauvegardé comme état principal. ## 17. Duplicate recovery Si le temporaire correspond à un partfile déjà chargé : ```text compare CompletedSize ``` et peut remplacer l’autre s’il est plus avancé. Bon exemple d’une récupération fondée sur état utile, pas uniquement timestamp. ## 18. Gaps La liste de gaps encode les plages absentes. Invariants : ```text start <= end end < file size gaps ordonnés gaps fusionnés si intersections ``` `AddGap()` maintient cet ordre/fusion. ## 19. Gaps et données bufferisées `IsCompleteBD()` prend également en compte des données reçues mais pas encore écrites sur disque. Donc : ```text complete logique ``` et : ```text complete + réellement écrit ``` peuvent temporairement différer. ## 20. Crash consistency Question interne importante : ```text à quel moment un buffer reçu est marqué comme présent par rapport à son flush disque et à la sauvegarde part.met ? ``` À auditer spécifiquement avec scénarios de coupure. ## 23. Hashsets Un `part.met` peut contenir des hashes MD4/AICH. Invariants : ```text part count théorique part count disponible file size master hash ``` Le D07 couvre l’algorithme ; D15 teste la persistance. ## 24. `clients.met` Headers : ```text CREDITFILE_VERSION_29 = 0x11 CREDITFILE_VERSION = 0x12 ``` Le loader lit la structure adaptée à la version. ## 25. Backup de crédits Au chargement, le code inspecte : ```text clients.met.bak ``` Si le backup existant est plus gros que le fichier courant, il évite de l’écraser. Hypothèse implicite : ```text fichier courant anormalement petit → pourrait être tronqué ``` ## 26. Expiration des crédits Seuil actuel : ```text 150 jours ``` Une entrée plus ancienne n’est pas réimportée dans la map active. Mesurer le volume réel des fichiers avant de considérer ce seuil comme problème. ## 27. Sauvegarde `clients.met` Writer : ```text version 0x12 count CreditStruct[] ``` Il ne sauvegarde que les records ayant au moins un octet uploadé ou downloadé. ## 28. Cadence crédits `Process()` : ```text ~13 min ``` ## 29. SecureIdent dans crédits `CreditStruct` peut conserver : ```text public key key size ``` Lorsqu’une clé est nouvellement vérifiée sur un record qui n’en avait pas, le code peut remettre les vieux totaux à presque zéro pour éviter une appropriation de crédits. ## 30. `cryptkey.dat` Fichier séparé pour la clé privée locale. Risque majeur : ```text perte → nouvelle identité SecureIdent corruption → crypto indisponible / régénération selon code ``` À sauvegarder avec le profil, mais ne jamais publier. ## 32. `nodes.dat` D11 a couvert la logique réseau. D15 se concentre sur le format persistant : ```text ancien count direct nouveau marker 0 + version v1/v2/v3 bootstrap edition ``` ## 33. Writer `nodes.dat` La branche sélectionne un échantillon de contacts via : ```text GetBootstrapContacts(...) ``` plutôt que de sérialiser naïvement toute la table. Le fichier est donc un cache sélectionné. ## 34. Protection contre écrasement prématuré Si un bootstrap nodes.dat est encore en cours et qu’aucun vrai contact n’est entré dans la routing table, le writer peut refuser d’écraser le bootstrap par un fichier vide. Bonne logique de récupération. ## 36. `preferencesKad.dat` Structure actuelle très petite : ```text IP uint32 old port uint16 KadID uint128 legacy tag-count byte on write ``` Un fichier absent est acceptable : ```text nouveau KadID ``` Mais il change l’identité logique Kad. ## 37. `server.met` D10 a déjà documenté : ```text 0x0E / 0xE0 records + tags .tmp/new/old strategy ``` Ici, ne pas dupliquer l’étude. ## 38. `preferences.ini` Une partie des anciennes préférences binaires a migré vers INI. Le format est plus lisible, mais cela n’élimine pas les problèmes de migration : ```text clé absente ancienne valeur valeur hors plage ``` ## 39. Stratégie commune de robustesse Pour chaque fichier persistant : ```text parse validate version validate count validate bounds construct temporary object commit object only if valid ``` Éviter de modifier l’état global avant validation complète. ## 40. Count × record-size overflow Tous les formats ayant : ```text count ``` doivent être testés pour : ```text count * minimumRecordSize ``` avec arithmetic 64-bit sûre. Le code `nodes.dat` possède déjà un contrôle de ce type. ## 41. Tag lengths Cibles de fuzzing transversales : ```text string length blob length tag count tag type unknown tag 64-bit tag EOF ``` ## 42. Atomicité Comparer les stratégies : ```text part.met → tmp + rename + bak server.met → new + old clients.met → backup au load + write direct known.met → write direct via SafeFile/Commit ``` Il n’existe pas une stratégie uniforme. ## 43. Étude M15-5 — matrice de durabilité Pour chaque fichier : ```text backup ? tmp ? atomic rename ? write-through ? validation au load ? récupération auto ? ``` Créer un tableau interne de criticité. ## 44. Criticité proposée pour l’étude Sans noter/ranker publiquement : ```text cryptkey.dat → identité non reconstructible par le réseau part.met → progression spécifique locale clients.met → historique de crédits known.met → mémoire reconstructible partiellement nodes.dat/server.met → bootstrap reconstructible via réseau/listes ``` Cette classification sert à choisir les tests de sauvegarde, pas à publier un « score ». ## 45. MetMedic L’analyse du corpus montre qu’un outil externe peut lire/modifier `known.met` et d’autres structures. Elle souligne aussi le risque d’écrire des tags ou stats non cohérents. Principe : ```text read-only inspector avant editor ``` ## 48. Réparation automatique : risque Un outil qui « devine » un hash, une taille ou des gaps peut transformer une situation récupérable en état faux. Toute réparation doit exiger : ```text preuve suffisante backup préalable dry-run journal exact ``` ## 49. Migration Avant toute modification de format interne future, préparer : ```text reader old writer old/new round-trip tests downgrade behavior backup ``` Ne jamais changer un header juste pour « moderniser ». ## 50. Corruption silencieuse Le cas le plus dangereux n’est pas toujours le crash. C’est : ```text parser accepte mais état est faux ``` Les tests doivent donc comparer les invariants après chargement, pas seulement « ne crash pas ». ## 51. Corpus de fixtures Créer un répertoire versionné : ```text tests/fixtures/met/ known/ part/ clients/ nodes/ server/ cancelled/ ``` avec provenance/version documentée. ## 52. Cross-implementation Comparer les lecteurs : ```text eMule aMule outils historiques ``` pour les fichiers réellement partagés entre implémentations. Une différence peut révéler une extension non standard. ## 53. Données personnelles Ces fichiers peuvent révéler : ```text noms de fichiers historique de téléchargements IP/contacts pseudonymes indirects crédits ``` Un rapport ou archive publique doit être nettoyé avant partage. ## 54. Clés privées Jamais inclure : ```text cryptkey.dat ``` dans un corpus public réel. Pour les tests, générer une clé synthétique. ## 55. Conclusion La persistance eMule est déjà plus robuste qu’un simple « save file » : ```text versions tags backups temp files recovery validation ``` Mais les stratégies sont hétérogènes et anciennes. Le travail prioritaire est donc de construire des parsers testés, des fixtures et des scénarios de coupure avant d’envisager toute réécriture. ================================================================================================ --- # Dossier 16 — Audit du moteur de recherche eD2K/Kad et du filtre anti-spam {#g16} > Périmètre : construction des requêtes, choix du canal, recherche globale UDP, recherche Kad, agrégation des résultats et filtre anti-spam. Le MultiServer n’est pas un objectif de recherche ici : ce dossier étudie le protocole et la qualité des résultats. ## 1. Sources auditées ```text SearchResultsWnd.cpp SearchList.cpp SearchList.h SearchFile.cpp SearchParams.h SearchParamsWnd.cpp SearchExpr.* Scanner.* ServerSocket.cpp UDPSocket.cpp ServerList.cpp kademlia/kademlia/Search.cpp SearchManager.cpp Opcodes.h ``` Référence historique : ```text edonkey-protocol.txt ``` ## 2. Types de recherche exposés `StartSearch()` distingue : ```text SearchTypeAutomatic SearchTypeEd2kServer SearchTypeEd2kGlobal SearchTypeKademlia SearchTypeContentDB ``` `ContentDB` sort du protocole P2P et ouvre une requête web historique. ## 3. Sélection Automatic Si un seul réseau est disponible : ```text server only → ED2K Server Kad only → Kad ``` Si les deux sont connectés, la branche applique une heuristique historique. ## 4. Heuristique Automatic actuelle Le serveur est choisi si le serveur courant est statique ou s’il vérifie notamment : ```text users > 40 000 users < 2 000 000 files > 5 000 000 server list < 40 ``` Sinon Kad. Le commentaire dit explicitement que cette logique pourrait être optimisée. ## 5. Interprétation Ces seuils ne sont pas des propriétés du protocole. Ils représentent une heuristique codée dans une certaine époque du réseau. Avant toute modification : ```text mesurer le réseau actuel ``` ## 6. Construction d’une recherche eD2K `DoNewEd2kSearch()` : ```text vérifie connexion serveur détermine support 64-bit/LargeFiles GetSearchPacket() annule ancienne recherche ED2K crée SearchID enregistre nouvelle SearchList envoie OP_SEARCHREQUEST ``` ## 7. SearchID La fenêtre attribue un identifiant local de recherche afin de rattacher résultats, onglet et état. Important pour : ```text réponses tardives plusieurs recherches successives annulation UDP global ``` ## 8. Search packet `GetSearchPacket()` transforme l’expression et les filtres en une représentation binaire de recherche. Le protocole possède un arbre d’expressions, pas une simple chaîne libre. ## 9. Nœuds booléens Types historiques : ```text AND OR AND NOT ``` Le parser/serializer construit un arbre récursif. Cas de fuzzing naturel : ```text profondeur excessive arbre tronqué nombre énorme de nœuds ``` ## 10. Termes texte Une recherche peut écrire un paramètre texte simple. Pour Kad, un mot-clé principal est extrait séparément afin de construire la cible de routage. ## 11. Métadonnées chaîne Le builder prend notamment en charge : ```text FT_FILETYPE FT_FILEFORMAT FT_MEDIA_CODEC FT_MEDIA_TITLE FT_MEDIA_ALBUM FT_MEDIA_ARTIST FT_FILENAME ``` Le détail exact de compatibilité varie entre vieux/nouveaux tags. ## 12. Comparaisons numériques La syntaxe supporte des opérateurs de comparaison pour les attributs numériques. Exemples : ```text FileSize Sources Bitrate Length CompleteSources ``` à confirmer attribut par attribut dans `SearchExpr`/builder. ## 13. 32-bit / 64-bit Le builder peut produire une requête nécessitant 64 bits. La branche mémorise : ```text m_b64BitSearchPacket ``` pour éviter d’envoyer cette requête à un serveur UDP incapable de LargeFiles. ## 14. Envoi local TCP Paquet : ```text OP_SEARCHREQUEST ``` vers le serveur connecté. En mode Global, une copie du payload est conservée pour le parcours UDP. ## 15. Timeout serveur local En Global : ```text TimerServerTimeout = 50 s ``` Si le serveur courant tarde trop, la recherche globale UDP peut démarrer. Il ne s’agit pas d’un timeout de téléchargement. ## 16. Timer global Une fois le global actif : ```text intervalle ≈ 750 ms ``` La liste des serveurs est parcourue progressivement. ## 17. Serveurs morts Le parcours saute les serveurs dont le failed-count a atteint le seuil `DeadServerRetries`. Donc la qualité de `server.met` influence directement le coût d’une recherche globale. ## 18. Recherche UDP v1 Serveur ancien : ```text OP_GLOBSEARCHREQ ``` si la requête est compatible. ## 19. Recherche UDP v2 Si : ```text SRV_UDPFLG_EXT_GETFILES ``` le client utilise : ```text OP_GLOBSEARCHREQ2 ``` ## 20. Recherche UDP v3 Si le serveur supporte les extensions adéquates et LargeFiles : ```text OP_GLOBSEARCHREQ3 ``` avec un tag de flags : ```text CT_SERVER_UDPSEARCH_FLAGS SRVCAP_UDP_NEWTAGS_LARGEFILES ``` ## 21. Skip LargeFiles Une recherche nécessitant 64 bits est ignorée pour un serveur UDP qui ne supporte pas cette capacité. Cela évite d’envoyer une requête qu’il interpréterait incorrectement. ## 22. Notification d’origine Après un envoi UDP : ```text SentUDPRequestNotification(searchID, serverIP) ``` La SearchList peut ainsi associer certaines réponses à leur serveur d’origine. Cette provenance est importante pour l’anti-spam. ## 23. Réception UDP `UDPSocket.cpp` transmet les résultats à : ```text ProcessUDPSearchAnswer() ``` avec : ```text server IP server port ``` Les résultats de plusieurs serveurs sont donc agrégeables avec provenance. ## 24. `More Results` `SearchMore()` envoie : ```text OP_QUERY_MORE_RESULT ``` au serveur TCP courant. Constante historique : ```text MAX_MORE_SEARCH_REQ = 5 ``` Le commentaire indique une limite totale dépendant du nombre de réponses par page serveur. ## 25. Kad Search `DoNewKadSearch()` : ```text extrait premier mot-clé gère quotes Trim construit les contraintes vérifie chars interdits PrepareFindKeywords() ``` Le `SearchID` vient alors de l’objet `CSearch` Kad. ## 26. Keyword principal Le premier token n’est pas nécessairement toute l’expression booléenne. Il sert à choisir la clé distribuée. Les autres conditions accompagnent la recherche pour filtrer les entrées. ## 27. Requête Kad et recherche serveur Une partie de la logique de construction des contraintes est partagée. Mais le transport et le mécanisme de découverte sont complètement différents. ## 28. Agrégation `SearchList` reçoit des résultats de : ```text server TCP server UDP Kad client shared-files selon contexte ``` Elle doit regrouper les variantes et conserver les origines. ## 29. Identité de regroupement Le FileHash est la base naturelle de regroupement. Les variantes de nom doivent être conservées comme observations, pas transformées en fichiers distincts lorsque le hash est identique. ## 30. Métadonnées contradictoires Cas : ```text même hash taille différente ``` devrait être considéré comme incohérent car taille+hash font partie de l’identité opérationnelle. Cas : ```text même hash noms différents ``` est possible. Tester les décisions actuelles de `SearchList`. ## 31. Anti-spam : architecture `CSearchList::DoSpamRating()` combine : ```text heuristiques générales et apprentissage à partir des choix utilisateur ``` Le commentaire du code reconnaît la fiabilité limitée des seules heuristiques. ## 32. Hash connu spam Un FileHash déjà marqué spam peut produire un score décisif. Un hash marqué explicitement non-spam peut au contraire devenir une forte preuve négative. ## 33. Noms Le filtre compare : ```text nom exact nom similaire nom après suppression des mots de recherche et séparateurs ``` Il prend aussi en compte les variantes parent/enfant d’un même résultat groupé. ## 34. Tailles similaires Une taille connue spam peut influencer un résultat si elle est proche : ```text < 5 Mio d'écart ET < 5 % ``` Ce signal a un poids relativement faible seul. ## 35. Serveur UDP d’origine Le filtre conserve des IP de serveurs associés à du spam. Le score dépend du contexte : ```text seul serveur à avoir donné le résultat plusieurs serveurs nombre de serveurs déjà marqués ``` ## 36. Ratio spam d’un serveur `m_mUDPServerRecords` conserve pour chaque serveur : ```text results spam results ``` Un ratio est calculé. Seuil historique visible : ```text UDP_SPAMRATIO_THRESHOLD = 50 ``` ## 37. Sources initiales Le filtre peut également apprendre des sources clientes associées à des résultats spam. Cela montre que l’ancien système avait déjà une notion de corrélation multi-signal. ## 38. Heuristiques générales Le commentaire source cite notamment : ```text forte disponibilité sur un serveur UDP mais pas les autres archive/program entre ~0,1 et 10 MB 100 % complete sources + forte disponibilité ``` Ces règles visaient des campagnes historiques de faux résultats. ## 39. Poids visibles Exemples de constantes : ```text similar name ~40–60 similar size 10 UDP server 10–21 only spam servers 30 source hit 39 heuristic base 39 heuristic max 60 ``` Et : ```text SEARCH_SPAM_THRESHOLD = 60 ``` Ces valeurs doivent être traitées comme heuristiques historiques, pas comme probabilités. ## 40. Problème du score additif Additionner plusieurs signaux corrélés peut surpondérer un même phénomène. Exemple : ```text même campagne de spam → nom similaire → taille similaire → même serveur → mêmes sources ``` Ces signaux ne sont pas statistiquement indépendants. ## 41. Risque de dérive temporelle Les heuristiques « petit .exe/.zip très disponible » correspondaient à des campagnes anciennes. En 2026, le profil du spam peut avoir changé. Question centrale : ```text quelle précision ont encore ces règles ? ``` ## 42. Dataset de validation Créer un corpus offline avec résultats réels/synthétiques étiquetés : ```text spam non-spam incertain ``` Éviter de prétendre mesurer précision/recall sans labels humains. ## 49. Ce que « utile » doit signifier Pas seulement : ```text beaucoup de lignes ``` Mais : ```text hashs uniques résultat choisi par utilisateur sources ensuite trouvées fichier finalement transférable ``` ## 50. Pas de « meilleur réseau » global Kad peut mieux fonctionner sur une requête rare. Un gros serveur peut être meilleur sur une autre. Le but n’est pas de déclarer : ```text Kad > serveur ``` mais de comprendre les conditions. ## 51. MultiServer Le projet possède déjà sa propre réflexion/maîtrise du MultiServer. D16 ne doit donc pas repartir sur une conception « Smart MultiServer ». Ici nous étudions seulement : ```text la recherche UDP globale du protocole la provenance des résultats leur qualité ``` ## 52. Query parser fuzzing Cibles : ```text arbre profond nœud incomplet opérateur invalide string énorme tag inconnu uint64 frontière expressions imbriquées ``` ## 53. Result parser fuzzing Cibles : ```text count énorme tag count énorme troncature FileHash absent taille 0 taille > max Unicode malformé duplicate tags ``` ## 54. UDP correlation Une réponse UDP doit être associée à une recherche active et à une origine attendue. Vérifier le comportement sur : ```text réponse tardive serveur jamais interrogé ancienne SearchID onglet fermé ``` ## 55. Kad late results Même question pour Kad : ```text search stopping 15 s grace late answer deleted GUI search ``` Le moteur prévoit déjà une phase de stop avant destruction. ## 56. Privacy audit Journaliser une recherche peut révéler les termes de l’utilisateur. Toute télémétrie expérimentale doit privilégier : ```text SearchID local hash du terme si nécessaire compteurs ``` et éviter la conservation longue des requêtes textuelles. ## 57. Search evidence local-only Structure conceptuelle : ```text SearchEvidence { search_id channel origin result_hash first_seen duplicate metadata_conflict spam_reasons } ``` Sans upload externe par défaut. ## 58. UI et filtre Un filtre anti-spam amélioré ne doit pas supprimer silencieusement des résultats légitimes sans possibilité de diagnostic. Pour les tests : ```text score reasons hidden/visible ``` doivent être observables. ## 59. Noms alternatifs Un même FileHash ayant plusieurs noms peut fournir une information utile contre certains fakes. Mais le « nom majoritaire » n’est pas automatiquement vrai. Mesurer seulement. ## 60. Sources complètes Le nombre de complete sources est une métadonnée utile mais déclarative. Ne pas confondre : ```text complete sources annoncées ``` avec : ```text sources réellement vérifiées ``` ## 61. Recherche par hash Lorsqu’on possède déjà un lien ED2K, la recherche textuelle devient secondaire. Le téléchargement peut directement utiliser le FileHash et lancer la découverte de sources. D14 et D16 doivent donc rester clairement séparés. ## 62. Robustesse sur 64-bit Tests frontières : ```text OLD_MAX_EMULE_FILE_SIZE - 1 OLD_MAX_EMULE_FILE_SIZE OLD_MAX_EMULE_FILE_SIZE + 1 MAX_EMULE_FILE_SIZE MAX + 1 ``` sur : ```text builder server local global UDP capability filtering Kad ``` ## 63. Performance locale Les listes de résultats peuvent être nombreuses. Profiling à faire sur : ```text grouping spam recalculation sorting UI refresh string normalization ``` Sur PC moderne, l’UI peut être plus coûteuse que le parsing réseau. ## 64. Pas d’optimisation par suppression de vérifications Si un profil montre du coût dans le spam filter ou le regroupement, ne pas supprimer d’abord les contrôles de cohérence. Séparer : ```text algorithme allocations rafraîchissement GUI ``` ## 65. Recherche liée à Trust Model Les résultats de recherche sont des : ```text remote claims ``` Leur confiance peut se renforcer par : ```text observations indépendantes cohérence de métadonnées succès du téléchargement ``` mais aucune de ces dimensions ne doit devenir une « vérité » avant validation. ## 66. Conclusion Le moteur de recherche eMule est déjà une architecture multi-canal sophistiquée : ```text query tree server TCP global UDP Kad result grouping spam heuristics user feedback ``` Les zones qui méritent le plus d’étude sont : ```text pertinence actuelle du mode Automatic rendement de chaque canal qualité des métadonnées validité du filtre anti-spam historique robustesse des parsers ``` La prochaine étape n’est pas de changer ces règles immédiatement, mais de construire le corpus de test et les métriques permettant de savoir lesquelles fonctionnent encore réellement. ================================================================================================ --- # Dossier 17 — Audit d’une implémentation serveur eD2K : eNode {#g17} > **Règle du projet :** ce rapport sert à comprendre et tester. Il ne présente aucune modification comme une fonction future validée. eNode est utilisé comme laboratoire et implémentation de référence secondaire, jamais comme norme absolue du protocole. --- ## 1. Pourquoi ce dossier existe Le Dossier 10 décrivait essentiellement : ```text ce que le client eMule attend d'un serveur ``` D17 examine : ```text ce qu'une implémentation serveur doit faire pour satisfaire ces attentes ``` et cherche aussi à déterminer si eNode peut servir de serveur de laboratoire fiable après remise en état. ## 2. Source historique primaire `edonkey-protocol.txt` v0.6 documente dès 2002 : ```text OP_LOGINREQUEST OP_OFFERFILES OP_GETSERVERLIST OP_SEARCHREQUEST OP_GETSOURCES OP_CALLBACKREQUEST OP_SERVERLIST OP_SEARCHRESULT OP_SERVERSTATUS OP_CALLBACKREQUESTED OP_CALLBACKFAILED OP_SERVERMESSAGE OP_IDCHANGE OP_SERVERIDENT OP_FOUNDSOURCES ``` Il documente également les équivalents UDP. Cette source établit le **contrat réseau historique**, pas l’architecture interne. ## 3. Source serveur Repository : ```text petermrg/eNode ``` Description : ```text Ed2k/Emule server written in Node.js ``` README : ```text experimental-buggy-testing-pre-alpha Not ready to use ``` Création : ```text 2013 ``` Dernier push principal : ```text 29 septembre 2013 ``` ### Conséquence Toute incohérence détectée doit être interprétée comme l’état d’un prototype ancien, et non comme représentative de tous les serveurs eD2K. ## 4. Arborescence eNode Composants utiles : ```text ed2k/ tcpserver.js tcpoperations.js udpserver.js udpoperations.js packet.js buffer.js tcpcrypt.js udpcrypt.js client.js lowidclients.js globals.js storage/ storage.js engine.mysql.js engine.mongodb.js misc/ enode.sql ``` Architecture suffisamment simple pour servir de laboratoire. ## 5. Listener TCP `tcpserver.js` crée un `net.Server`. Pour chaque socket entrant : ```text client.info = { ipv4 logged storageId id hasLowId } ``` Puis : ```text Packet(client) TcpCrypt éventuel data → processData() ``` ## 6. Fin de session Sur `close` : ```text si LowID → lowIdClients.remove() db.clients.disconnect() ``` La couche storage marque ensuite : ```text client offline sources offline ``` Le lifecycle est donc explicitement lié à la durée du socket TCP. ## 7. Packet parser `packet.js` traite : ```text Protocol byte Length uint32 Opcode Payload ``` et sait reconstruire : ```text fragment partiel paquet exact paquet + données excédentaires ``` Il rappelle une règle importante de TCP : ```text un read() != un paquet ``` ## 8. Problème de robustesse du parser Le code contient explicitement : ```text TODO do checkings here ``` avant l’allocation : ```text this.data = new Buffer(this.size) ``` ### Risque conceptuel Une longueur réseau non bornée peut provoquer : ```text allocation excessive exception DoS mémoire ``` ## 9. Buffer helpers `buffer.js` monkey-patche `Buffer.prototype`. Il gère : ```text uint8 uint16 uint32 uint64 strings hashes tags file lists search expressions ``` Cette centralisation est pratique pour un prototype, mais rend une erreur de borne transversale. ## 10. `conf.noAssert` Configuration : ```text noAssert = false ``` par défaut. Donc les primitives Node historiques font normalement des contrôles d’offset. Si `noAssert` est activé : ```text beaucoup de protections runtime disparaissent ``` À ne jamais activer dans un harness réseau non fiable. ## 11. Login `receive.loginRequest()` lit : ```text hash 16 bytes id uint32 port uint16 tags ``` Puis : ```text db.clients.isConnected(hash) ``` avant le test de firewall. ## 12. Politique doublon UserHash Backend MySQL : ```sql SELECT id FROM clients WHERE hash = ? AND online = 1 LIMIT 1 ``` Si trouvé : ```text connexion refusée ``` C’est une politique propre à eNode. Le protocole historique n’impose pas nécessairement cette règle. ## 13. Firewall test `isFirewalled()` crée un mini-client eD2K serveur→client. Séquence : ```text connect TCP vers remoteAddress:announcedPort si crypt : handshake obfuscation Hello attente HelloAnswer ``` Si réponse valide : ```text HighID ``` sinon : ```text LowID ``` ## 14. Timeout firewall Configuration : ```text connectionTimeout = 2000 ms ``` Deux secondes sont un choix du prototype. Ne pas prendre cette valeur comme comportement Lugdunum ou valeur réseau recommandée. ## 15. Fallback crypt → plain Si l’handshake d’obfuscation timeout : ```text retry isFirewalled(client, false) ``` Le prototype peut donc tester : ```text obfusqué puis clair ``` avant de déclarer firewalled. ## 16. HighID eNode Si joignable : ```text client.info.id = client.info.ipv4 hasLowId = false ``` Ce comportement correspond au modèle IPv4 historique vu depuis le client. ## 17. LowID eNode Si firewalled : ```text lowIdClients.add(client) ``` puis attribution d’un ID dans : ```text 1 .. 0xFFFFFF ``` si disponible. ## 18. Audit critique : `lowidclients.js` Cette classe contient plusieurs incohérences manifestes dans le `master` actuel. ### A. `_count` n’est jamais incrémenté dans `add()` Conséquences : ```text count() reste 0 test de capacité incorrect remove() peut rendre _count négatif ``` ## 19. Audit critique : détection de collision LowID Dans `_nextId()` : ```js while (lowIdClients._clients.hasOwnProperty[lowIdClients._next]) ``` Le code indexe la fonction `hasOwnProperty` au lieu de l’appeler. Il devrait conceptuellement tester : ```text _clients.hasOwnProperty(id) ``` ### Conséquence probable La boucle d’évitement des IDs déjà utilisés ne fonctionne pas comme prévu. ## 20. Audit critique : récupération LowID `get(id)` vérifie correctement : ```text _clients.hasOwnProperty(id) ``` mais retourne : ```text lowIdClients.clients[id] ``` au lieu de : ```text lowIdClients._clients[id] ``` ### Conséquence probable Le callback LowID ne récupère pas correctement le socket stocké. ## 21. Conséquence sur `CallbackRequest` `tcpoperations.js` fait : ```text clientWithLowId = lowIdClients.get(lowId) ``` puis tente : ```text send.callbackRequested(clientWithLowId, requester) ``` Le bug précédent rend ce chemin non fiable dans l’état actuel du repository. ### Conclusion eNode ne doit pas être lancé comme serveur de référence sans correction et tests. ## 22. `receive.handShake()` Après attribution ID : ```text db.clients.connect() logged = true storageId ``` puis envoi : ```text ServerMessage ServerMessage(version) ServerStatus IDChange ServerIdent ``` Cette séquence fournit un bon scénario de test client. ## 23. Audit critique : API storage incohérente `storage/storage.js` expose : ```text clients.count() files.count() files.add() servers.count() ``` Mais `tcpoperations.js` appelle à plusieurs endroits : ```text db.clients.getCount() db.files.getCount() db.servers.getCount() db.files.addSource() ``` Ces méthodes ne sont pas exposées sous ces noms dans `storage.js`. ### Effets possibles ```text ServerStatus TCP cassé ServerList TCP cassé OfferFiles cassé logs de publication cassés ``` dans l’état exact du master audité. ## 24. Signification de cette incohérence Il s’agit vraisemblablement d’un : ```text refactor inachevé ou API renommée sans mise à jour complète ``` Le README disait déjà : ```text pre-alpha / buggy ``` Ces découvertes confirment que le dépôt est avant tout une base de recherche. ## 25. Backend MySQL : clients Table : ```text clients ``` Colonnes principales : ```text id interne hash id_ed2k ipv4 port time_login online ``` Le UserHash est unique dans cette table. ## 26. Backend MySQL : files Table : ```text files ``` Clé logique : ```text (hash, size) ``` Colonnes : ```text time_creation time_offer source_id source_port sources completed ``` ## 27. Backend MySQL : sources Table relationnelle : ```text sources ``` Clé unique : ```text (id_file, id_client) ``` Donc : ```text un client ne possède qu'une ligne source par fichier ``` ## 28. Métadonnées source La table `sources` conserve : ```text name extension type rating title artist album length bitrate codec online complete time_offer ``` Cette modélisation accepte naturellement plusieurs noms/métadonnées pour le même hash. ## 29. Publication souhaitée `engine.mysql.js` possède : ```text files.add(file, clientInfo) ``` qui : ```text upsert files(hash,size) → get file id → addSource() ``` C’est la bonne architecture théorique. ## 30. Mais `OfferFiles` ne l’appelle pas Le chemin TCP appelle actuellement : ```text db.files.addSource(...) ``` alors que le wrapper public expose : ```text db.files.add(...) ``` ### Correctif laboratoire évident Avant tout test eNode : ```text raccorder OfferFiles à files.add() ``` puis écrire un test d’intégration. ## 31. Déconnexion `engine.mysql.js` : ```text UPDATE clients SET online=0 UPDATE sources SET online=0 ``` mais ne supprime rien immédiatement. Une purge ultérieure n’est pas visible dans le code audité. ## 32. Problème d’expiration Le schéma garde : ```text time_offer ``` mais aucune tâche de garbage collection complète n’apparaît dans les fichiers audités. ### Risque long terme ```text files/sources historiques → croissance permanente ``` Même si `online=0` permet de distinguer l’état actuel. ## 33. GetSources `files.getSources(hash,size)` : ```sql ORDER BY s.online DESC, s.time_offer DESC LIMIT 255 ``` ### Observation La requête ne contient pas : ```sql WHERE s.online = 1 ``` Elle peut donc renvoyer des sources offline si moins de 255 sources online existent. ## 34. Conséquence GetSources Le tri met les online d’abord, mais : ```text offline != exclu ``` Cela peut créer : ```text sources mortes timeouts côté client ``` ### À ne pas généraliser C’est un comportement d’eNode, pas une preuve sur les serveurs Lugdunum. ## 35. GetSources UDP v1 `getSourcesByHash()` cherche par : ```text hash seul ``` alors que TCP/GetSources2 peut utiliser : ```text hash + size ``` Cohérent avec les limitations des anciennes variantes de protocole. ## 36. Search engine `Buffer.getSearchString()` traduit l’arbre de recherche vers une expression SQL. Opérations prises en charge : ```text text AND OR AND NOT type extension codec size > / < sources > bitrate > duration > complete sources > ``` ## 37. SQL injection Le code utilise : ```text sql.esc(value) ``` pour les valeurs. L’arbre lui-même produit des fragments d’opérateur à partir d’un switch fermé. C’est meilleur qu’une concaténation directe libre, mais le parser doit rester fuzzé pour la structure, la profondeur, les types et la troncature. ## 38. Query result Le backend : ```sql GROUP BY s.id_file LIMIT 255 ``` et sélectionne une ligne de métadonnées source pour représenter le fichier. ### Question Avec plusieurs noms/métadonnées divergents : ```text quelle source gagne ? ``` Sans agrégation explicite de chaque champ, le résultat dépend du moteur SQL/plan. À tester. ## 39. `files.sources` et `completed` Après `addSource()`, une requête calcule : ```text COUNT(*) AS sources SUM(complete) AS completed ``` sans filtrer : ```text online = 1 ``` ### Conséquence Les compteurs persistants peuvent inclure des sources offline historiques. Le serveur de démonstration peut donc surévaluer l’actualité du fichier. ## 40. UDP Server Status contient volontairement de fausses valeurs `globServStatRes()` envoie : ```text db.clients.count() + 2000 lowIdClients.count() + 1000 ``` avec commentaires : ```text fake value, for testing ``` ### Conclusion importante Les statistiques produites par eNode ne doivent jamais être utilisées pour inférer le comportement réel du réseau. ## 41. Soft/Hard Files eNode Le même paquet contient : ```text 10000 // soft file limit ??? 20000 // hard file limit ??? ``` Les commentaires indiquent explicitement l’incertitude. ### Conclusion eNode n’est pas une source pour définir précisément la sémantique Lugdunum de SoftFiles/HardFiles. ## 42. Server List eNode Le backend contient une liste codée en dur : ```text 111.222.111.222:1234 123.123.234.234:2345 ``` Ce sont clairement des placeholders. Ne pas utiliser cette partie comme modèle de propagation réel des serveurs. ## 43. UDP Search eNode gère : ```text OP_GLOBSEARCHREQ OP_GLOBSEARCHREQ3 ``` mais pas nécessairement toutes les variantes ou subtilités. Pour v3, le code contient : ```text TODO: not sure about what to do here ``` sur le tag SearchTree. ## 44. UDP GetSources Implémentations : ```text OP_GLOBGETSOURCES OP_GLOBGETSOURCES2 ``` et réponse : ```text OP_GLOBFOUNDSOURCES ``` Le code gère aussi les Large Files dans GetSources2. Bon laboratoire pour compatibilité client. ## 45. Search UDP response framing Le source possède un TODO mentionnant la concaténation de plusieurs réponses UDP dans une même frame. eNode envoie actuellement chaque fichier dans un datagramme séparé. ### Question laboratoire Comparer avec : ```text ce qu'attend eMule 0.72a captures de serveurs réels Wireshark ``` ## 46. UDP Status Challenge eNode renvoie le challenge reçu. Cela correspond bien au modèle attendu côté eMule : ```text corrélation request/response ``` ## 47. UDP Description Deux modes : ```text ancien → name + description moderne → challenge + tags ``` Le serveur expérimental gère les deux. ## 48. TCP obfuscation serveur eNode implémente le handshake DH/RC4 historique décrit au D13. C’est particulièrement intéressant comme serveur de laboratoire pour tester : ```text obfuscation client↔server fallback flags ports ``` après correction du reste. ## 49. UDP obfuscation serveur `udpcrypt.js` pré-calcule de nombreuses clés RC4 dérivées d’une clé serveur et d’un identifiant 16 bits. Le système est historique et doit être traité uniquement comme compatibilité de protocole. ## 50. Audit critique UDP key storage Le code crée un fichier : ```text udpkeys-.dat ``` de clés pré-calculées. Cette optimisation est spécifique à eNode. Elle n’est pas une exigence eD2K. ## 51. `Packet.make()` Le générateur calcule la taille du payload selon le type d’item. Types supportés : ```text uint8 uint16 uint32 string hash tags ``` Il ne couvre donc pas automatiquement tous les types de tags/packets modernes. ## 52. Extended tags `buffer.js` traite une forme de : ```text Lugdunum extended tag ``` en exploitant le bit haut du type. Intérêt : ```text interop avec serveurs modernes historiques ``` À comparer avec le parser eMule avant d’en faire une documentation normative. ## 68. Architecture de serveur de laboratoire proposée Sans prétendre produire un serveur public : ```text eNode-Lab │ ├─ parser original corrigé ├─ backend mémoire ou SQLite simple ├─ journal wire ├─ fixtures ├─ tests eMule 0.72a └─ aucun accès Internet nécessaire ``` Le but serait : ```text comprendre et tester le client ``` pas exploiter un serveur public. ## 69. Pourquoi un backend mémoire peut être préférable au début Pour les tests : ```text déterministe reset instantané pas de dépendance MySQL inspection simple ``` MySQL peut revenir plus tard pour les tests d’échelle. ## 70. Oracle de compatibilité Pour chaque opération serveur : ```text eDonkey protocol doc ↕ eMule 0.72a client expectation ↕ eNode implementation ``` Si deux sources concordent et la troisième diverge : ```text analyser ``` Ne pas « corriger » automatiquement selon intuition. ## 71. Wireshark Le dissector eDonkey du corpus peut servir de quatrième témoin : ```text layout paquet opcode tags ``` Très utile pour éviter les erreurs d’endianness ou de longueur. ## 72. Ce qu’eNode apporte malgré ses bugs Il rend visibles des concepts que le client masque : ```text client table file table source relation lowID socket map server-side firewall probe query execution disconnect cleanup ``` C’est précisément la valeur du projet. ## 73. Ce qu’eNode ne peut pas nous apprendre Il ne documente pas de façon fiable : ```text architecture des serveurs Lugdunum scalabilité réelle anti-abuse de production policy Soft/Hard Files exacte expiration réelle historique ranking search historique communication inter-serveurs propriétaire éventuelle ``` Pour ces points, il faudrait d’autres archives ou code serveur historique. ## 75. Découverte principale du dossier Le serveur peut être compris comme deux plans. ### Plan persistant/index ```text Clients Files Sources Metadata ``` ### Plan temps réel ```text TCP sessions LowID map firewall test callbacks UDP requests ``` Un serveur fonctionnel doit garder ces deux plans cohérents. ## 76. Point faible central d’un prototype naïf Si la base dit : ```text source online ``` mais que le socket a disparu, les clients reçoivent une source morte. Si le socket existe mais la base n’est pas à jour, le fichier devient introuvable. La cohérence entre : ```text session et index ``` est donc l’un des vrais problèmes du serveur. ## 78. Pas de score serveur unique Même règle que D05/D10 : ```text SearchQuality SourceFreshness CallbackReliability Reachability Latency ``` doivent rester des dimensions séparées. ## 79. Sécurité du laboratoire Le serveur de test ne doit pas être exposé publiquement pendant les fuzzings. Utiliser : ```text 127.0.0.1 LAN isolé VM deux instances eMule de test ``` Cela évite d’envoyer des paquets expérimentaux au réseau eD2K réel. ## 80. Conclusion interne D17 confirme deux choses. Premièrement, l’architecture logique d’un serveur eD2K est maintenant beaucoup plus claire : ```text session → HighID/LowID → publication → index → search/GetSources → callback → cleanup ``` Deuxièmement, **eNode est extrêmement intéressant comme laboratoire mais n’est pas utilisable tel quel**. Son propre README le dit, et l’audit trouve plusieurs incohérences concrètes dans la table LowID et l’API de stockage. La meilleure suite n’est donc pas d’utiliser eNode directement sur Internet. La suite raisonnable est : ```text corriger le laboratoire écrire les tests le faire dialoguer avec eMule 0.72a capturer les paquets puis comparer avec le protocole historique ``` C’est probablement la meilleure manière d’obtenir enfin une vue expérimentale des deux côtés du protocole eD2K. ================================================================================================ --- # Dossier 18 — Cycle de vie complet d’un pair / d’une source {#g18} --- # 0. Résumé exécutif Ce dossier est volontairement transversal. Les dossiers précédents ont étudié séparément : ```text discovery connectivity handshake queue blocks integrity SX A4AF servers Kad ``` D18 montre que tous ces mécanismes agissent sur **le même CUpDownClient** au cours du temps. La découverte centrale est architecturale : > **un pair n’a pas un état unique ; plusieurs machines d’état indépendantes coexistent.** Dans `ClientStateDefs.h` : ```cpp EUploadState EDownloadState EChatState EKadState EConnectingState ``` Le même client peut donc être simultanément : ```text DownloadState = DS_ONQUEUE UploadState = US_ONUPLOADQUEUE ConnectingState = CCS_NONE KadState = KS_NONE ``` ou encore être en train de télécharger chez nous pendant que nous attendons chez lui. Une grande partie de la complexité historique d’eMule vient de l’orchestration entre ces états, plus que du wire protocol lui-même. --- # 1. États upload `ClientStateDefs.h` : ```cpp enum EUploadState : uint8 { US_UPLOADING, US_ONUPLOADQUEUE, US_CONNECTING, US_BANNED, US_NONE }; ``` Ces états concernent : ```text ce que le pair fait chez NOUS ``` Ils ne décrivent pas notre téléchargement depuis lui. --- # 2. États download La branche contient : ```cpp DS_DOWNLOADING DS_ONQUEUE DS_CONNECTED DS_CONNECTING DS_WAITCALLBACK DS_WAITCALLBACKKAD DS_REQHASHSET DS_NONEEDEDPARTS DS_TOOMANYCONNS DS_TOOMANYCONNSKAD // unused since 0.49b DS_LOWTOLOWIP DS_BANNED DS_ERROR DS_NONE DS_REMOTEQUEUEFULL // not used yet, except in statistics ``` Le dernier confirme le constat D09 : ```text DS_REMOTEQUEUEFULL ``` est pratiquement fossile, alors que l’état remote queue full est suivi par ailleurs. --- # 3. États de connexion ```cpp CCS_NONE CCS_DIRECTTCP CCS_DIRECTCALLBACK CCS_KADCALLBACK CCS_SERVERCALLBACK CCS_PRECONDITIONS ``` Ce champ décrit **comment la tentative actuelle est menée**. Il est donc orthogonal au DownloadState. --- # 4. États Kad ```text FWCHECK BUDDY UDP firewall check ``` Un même `CUpDownClient` peut participer à un rôle Kad qui n’a rien à voir avec un téléchargement de fichier. --- # 5. Cycle nominal Modèle de référence : ```text SOURCE DISCOVERED → admitted → AskForDownload → TryToConnect → handshake → File Request → Part Status / Hashsets → StartUploadReq → ONQUEUE → periodic reask → ACCEPTUPLOADREQ → DOWNLOADING → Block Requests → Data → verification → queue / NNP / disconnect / completion ``` --- # 6. Admission Les canaux D01 aboutissent au pipeline d’admission. Objectifs : ```text dédoublonner éviter IP invalides / filtrées vérifier source limit éviter DeadSource associer A4AF si client déjà connu tester compatibilité crypt ``` Une source reçue du réseau n’est donc pas automatiquement créée comme nouveau pair. --- # 7. Provenance à conserver D01 et D08 montrent deux niveaux : ```text source_from = SERVER / KAD / SX / PASSIVE / LINK ``` mais dans le cas SX : ```text provider précis ``` est actuellement perdu après admission. Pour D18, la provenance idéale d’une source doit survivre aux transitions. --- # 8. Avant connexion : choix du fichier A4AF peut signifier que le pair est connu pour : ```text A B C ``` mais `m_reqfile` n’en désigne qu’un. La décision du fichier précède ou accompagne la tentative de connexion. Il faut donc journaliser : ```text active_file_at_connect alternative_files reason_for_choice ``` --- # 9. `TryToConnect()` comme machine de routage de connexion Le commentaire source décrit plusieurs scénarios. Les chemins réellement matérialisés par `EConnectingState` sont : ```text DIRECTTCP DIRECTCALLBACK KADCALLBACK SERVERCALLBACK PRECONDITIONS ``` Le code possède également des early exits : ```text déjà connecté déjà connecting pair invalide ban/filter incompatibilité crypt ``` --- # 10. Direct TCP Chemin idéal : ```text connect(remote IP, remote TCP port) ``` Puis : ```text ConnectionEstablished() → Hello ``` La réussite TCP ne signifie pas encore que le fichier existe chez le pair. --- # 11. Direct UDP Callback Conditions générales issues de D02/D11/D12 : ```text pair TCP firewalled Kad/direct callback capability Kad UDP port utilisable connect options compatibles ``` Paquet : ```text OP_DIRECTCALLBACKREQ ``` avec : ```text notre TCP port notre UserHash connect options ``` Le pair est invité à ouvrir TCP vers nous. --- # 12. Server Callback Si le pair LowID appartient au serveur auquel nous sommes connecté : ```text CCS_SERVERCALLBACK OP_CALLBACKREQUEST ``` Le serveur doit ensuite envoyer au LowID : ```text OP_CALLBACKREQUESTED ``` D10/D17 couvrent la partie serveur. --- # 13. Kad Callback Si le contexte serveur n’est pas utilisable mais que Kad/Buddy l’est : ```text CCS_KADCALLBACK ``` le moteur de connexion utilise la logique Kad. Il faut conserver la distinction dans toute télémétrie de réussite. --- # 14. Wait optimisation LowID Le code possède un cas où il évite d’initier un callback si le LowID est déjà dans notre upload queue et devrait revenir lui-même. C’est une optimisation intéressante car elle combine : ```text UploadState + DownloadState ``` pour économiser du trafic. C’est une preuve directe que les deux machines d’état doivent être vues ensemble. --- # 15. `DS_CONNECTING` Lorsque nous essayons activement de créer la relation nécessaire au download : ```text DS_CONNECTING ``` puis selon le chemin : ```text DS_WAITCALLBACK DS_WAITCALLBACKKAD ``` peuvent apparaître. --- # 16. Handshake D12 : ```text Hello / HelloAnswer ``` permet d’obtenir : ```text UserHash ClientID ports capabilities server Buddy crypt options ``` À ce stade : ```text peer identity claimed capabilities claimed ``` mais toutes ne sont pas encore observées avec succès. --- # 17. Attachement à un client déjà connu `ClientList::AttachToAlreadyKnown()` joue un rôle important lorsqu’un handshake entrant révèle qu’un objet correspond à un client déjà suivi. Ce mécanisme évite les duplications d’identité mais gère aussi les conflits UserHash/SecureIdent. D05/D03 doivent être connectés à D18 ici. --- # 18. File Request Après handshake, `SendFileRequest()` / multipacket moderne prépare les demandes liées à `m_reqfile`. Échanges potentiels : ```text filename File Status hashset AICH/FileIdentifier source exchange ``` selon capabilities et données déjà connues. --- # 19. `DS_REQHASHSET` Lorsqu’un hashset manque : ```text DS_REQHASHSET ``` peut devenir un état intermédiaire. Il ne s’agit ni d’une queue ni d’un transfert de payload. --- # 20. File Not Found Un FNF provoque une sémantique forte : ```text peer says it doesn't have requested file ``` D09 montre : ```text DontSwapTo(file) → A4AF éventuel → sinon suppression source ``` Cette cause doit être journalisée séparément de NNP. --- # 21. Part Status `ProcessFileStatus()` construit la carte des parties distantes. Cette carte influence : ```text NNP part selection rarity SX A4AF ``` Mais D05/D06 la classent : ```text REMOTE_CLAIM ``` jusqu’à observation des données. --- # 22. Demande de slot Opcode historique : ```text OP_STARTUPLOADREQ 0x54 ``` Le pair distant peut : ```text nous mettre dans sa queue ou nous accorder le slot ``` --- # 23. Queue state Etat courant : ```text DS_ONQUEUE ``` Le client conserve notamment : ```text last asked time remote queue rank remote queue full flag UDP reask information ``` --- # 24. Remote Queue Rank Deux paquets historiques : ```text OP_QUEUERANK OP_QUEUERANKING ``` avec tailles différentes. D05 a montré : ```text SetRemoteQueueRank() ``` accepte essentiellement la valeur après validation de format/flood. Elle doit rester une donnée non vérifiée. --- # 25. Queue Rank Flood `CheckQueueRankFlood()` protège notamment contre les QR non sollicités excessifs. Le lifecycle trace doit pouvoir distinguer : ```text valid rank update unsolicited flood ``` --- # 26. Reask normal D09 : ```text FILEREASKTIME ≈ 29 min ``` Le reask est une politique client historique. Il ne faut pas le confondre avec : ```text SERVERREASKTIME SX timers Kad timers DeadSource cooldown ``` --- # 27. Minimum fast ask `Opcodes.h` : ```text MIN_REQUESTTIME = 10 min BADCLIENTBAN = 4 ``` Des demandes nettement trop fréquentes sont suivies comme comportement mauvais. Toute optimisation de reask doit donc respecter aussi les protections du pair distant. --- # 28. Reask UDP Quand supporté, un reask léger peut utiliser : ```text OP_REASKFILEPING ``` puis réponse : ```text OP_REASKACK OP_QUEUEFULL OP_FILENOTFOUND ``` Cela évite une connexion TCP complète pour une simple maintenance de queue. --- # 29. Callback reask Un LowID peut aussi nécessiter des variantes : ```text OP_REASKCALLBACKUDP OP_REASKCALLBACKTCP ``` Le cycle de queue dépend donc de la connectivité. --- # 30. Slot accordé Opcode : ```text OP_ACCEPTUPLOADREQ 0x55 ``` La réception conduit au lancement du pipeline de téléchargement. Le code doit nettoyer/initialiser : ```text pending blocks download session stats last data rate compression state ``` selon contexte. --- # 31. `DS_DOWNLOADING` C’est l’état productif : ```text payload attendu/reçu ``` D06 couvre : ```text CreateBlockRequests SendBlockRequests GetNextRequestedBlock ``` --- # 32. Nombre de requêtes en vol La branche 0.72a adapte le nombre de blocs demandés au débit source dans certains cas. Exemples historiques observés D06 : ```text lent → 1 ou 2 normal → 3 rapide → 6 ou 9 pending localement ``` avec au maximum trois nouvelles plages dans un paquet historique de requête. --- # 33. Requested blocks global file Le `CPartFile` conserve aussi des réservations globales pour éviter que plusieurs sources reçoivent la même plage en régime normal. C’est ce qui crée le cas Endgame : ```text unrequested = 0 file incomplete ``` --- # 34. Data receive Pendant le transfert, nous obtenons enfin : ```text LOCAL_MEASURED ``` pour : ```text bytes rate timestamps source of ranges ``` C’est un changement de niveau de confiance important. --- # 35. Integrity transition Après complétion de partie : ```text LOCAL_MEASURED → MD4/AICH → HASH_VERIFIED ``` ou : ```text CORRUPTED ``` D07 intervient alors. --- # 36. Corruption peut modifier le lifecycle sans événement distant Si AICH/ICH rouvre une zone : ```text GapList changes ``` Une source anciennement NNP peut redevenir utile. D09 propose donc l’Event-Driven A4AF Reclassification comme piste interne. --- # 37. Plus de nouveaux blocs Dans `SendBlockRequests()` : ```text CreateBlockRequests() → pending empty → CancelTransfer → DS_NONEEDEDPARTS → SwapToAnotherFile() ``` Point crucial D09 : ```text pending empty ``` ne signifie pas toujours : ```text peer lacks useful part ``` mais parfois : ```text all useful gaps already reserved ``` --- # 39. `OP_CANCELTRANSFER` Opcode : ```text 0x56 ``` Il termine une session de transfert active côté protocole. Raison réelle à tracer : ```text NNP swap completion session rotation error disconnect ``` --- # 40. `OP_OUTOFPARTREQS` Opcode : ```text 0x57 ``` Sémantique : ```text plus de parties appropriées / plus de demandes possibles ``` Ne doit pas être automatiquement interprété comme hostilité ou DeadSource. --- # 41. Déconnexion `Disconnected()` est l’un des points centraux du lifecycle. Sa réponse dépend de : ```text DownloadState UploadState raison socket phase handshake whether we were actively downloading ``` --- # 42. Déconnexion pendant download Constat important : ```text DS_DOWNLOADING → disconnected → souvent DS_ONQUEUE ``` Le pair est conservé comme relation de source. Cela empêche une coupure temporaire d’effacer tout l’historique. --- # 43. Cas où le client peut être supprimé Le retour de `Disconnected()` et les états autour déterminent si l’objet doit rester connu ou être supprimé. Exemples plus graves : ```text connect failed wrong file invalid protocol unrecoverable state source no longer associated ``` --- # 44. DeadSourceList La DeadSourceList n’est pas un ban. Elle représente un cache temporaire d’échecs visant à éviter les tentatives répétées. Deux portées : ```text global local file ``` --- # 45. Timers DeadSource Dans `DeadSourceList.cpp`, ordres de grandeur observés : ```text global HighID : 15 min global LowID : 30 min local file : 45 min ``` avec nettoyage périodique autour d’une heure. Ces valeurs sont des politiques locales historiques. --- # 46. Pourquoi deux portées Une source peut être mauvaise pour : ```text un fichier précis ``` sans être globalement inutilisable. Inversement, un échec de connectivité peut justifier un cooldown plus global. Le double niveau permet d’éviter une suppression trop grossière. --- # 47. DeadSource et rediscovery Une source reçue à nouveau pendant son cooldown doit être reconnue comme déjà morte temporairement, sinon les canaux SERVER/KAD/SX pourraient contourner involontairement la protection. D01, D08 et D18 doivent donc partager le même identifiant de source. --- # 48. Upload queue parallèle Le pair peut simultanément être présent dans notre : ```text UploadQueue ``` Son cycle : ```text STARTUPLOADREQ reçu → admission → US_ONUPLOADQUEUE → ranking → US_UPLOADING → rotation / cancel ``` --- # 49. Queue purge `MAX_PURGEQUEUETIME` : ```text 1 heure ``` Un pair qui ne renouvelle plus sa demande est retiré de notre upload waiting list. Ce timer ne doit pas être mélangé avec le reask download. --- # 50. Client tracking Le code conserve aussi pendant un temps certaines informations sur des clients anciennement en upload queue : ```text KEEPTRACK_TIME ≈ 2h TRACKED_CLEANUP_TIME ≈ 1h ``` Ces mécanismes servent notamment à éviter certains abus de réentrée/reset d’attente. --- # 51. Connection timeouts `Opcodes.h` contient notamment : ```text DOWNLOADTIMEOUT = 100 s CONSERVTIMEOUT = 25 s ``` Ils ne sont pas des reask timers. Leur rôle appartient à la détection d’une session/tentative devenue improductive. --- # 52. D18-A — Le vrai objet à instrumenter est la transition Un snapshot : ```text DS_ONQUEUE ``` est moins informatif que : ```text DS_CONNECTING → 1.2 s → DS_CONNECTED → 0.4 s → DS_ONQUEUE ``` La durée entre états est souvent la métrique réellement utile. --- # 53. `PeerLifecycleEvent` Proposition de modèle de trace local : ```text timestamp peer_id_local file_id_local event old_state new_state reason connection_path source_origin remote_qr bytes rate ``` Pas besoin de changer le wire protocol. --- # 54. Événements minimums ```text SOURCE_DISCOVERED SOURCE_ADMITTED SOURCE_DUPLICATE CONNECT_ATTEMPT CONNECT_PATH_SELECTED CONNECTED HELLO_COMPLETE FILE_CONFIRMED FILE_NOT_FOUND PARTSTATUS_RECEIVED HASHSET_REQUEST QUEUE_ENTER QUEUE_RANK REASK SLOT_ACCEPTED DOWNLOAD_START BLOCK_REQUESTED FIRST_BYTE PART_VERIFIED PART_CORRUPTED NNP A4AF_SWAP DISCONNECTED DEADSOURCE_ADD DEADSOURCE_EXPIRE SOURCE_REMOVED ``` --- # 55. D18-B — First useful byte Mesurer : ```text source discovered → first useful byte ``` décomposé en : ```text discovery delay connect delay queue delay slot-to-first-byte ``` Cela permet de savoir où le temps est réellement perdu. --- # 56. D18-C — Path success Pour chaque type de connexion : ```text Direct TCP Direct UDP Callback Server Callback Kad Callback Wait-for-incoming ``` mesurer : ```text attempts success median latency timeout payload eventually received ``` --- # 57. D18-D — Reask effectiveness Pour chaque reask : ```text old QR new QR queue full? slot? NNP? file not found? ``` Mesurer le rendement du trafic de maintenance avant de discuter des timers. --- # 58. D18-E — Disconnect reasons Aujourd’hui, beaucoup d’analyses regroupent trop facilement : ```text source disconnected ``` Il faut distinguer : ```text remote close local cancel timeout NNP transition A4AF swap file completion protocol error ban connection reset ``` --- # 59. D18-F — DeadSource usefulness Mesurer : ```text deadsource insertions rediscovery during cooldown retry after expiry success after expiry ``` Question : ```text 15/30/45 min évitent-ils réellement des tentatives inutiles en 2026 ? ``` --- # 60. D18-G — Upload/download cross-effect Le cas historique : ```text LowID in our upload queue → wait for incoming instead of callback ``` mérite un compteur. Mesurer combien de callbacks sont réellement évités. --- # 61. D18-H — Source object lifetime Un `CUpDownClient` peut survivre à plusieurs sockets. Mesurer : ```text object creation number of connect cycles files associated A4AF swaps eventual destruction ``` Intéressant pour CPU/RAM et compréhension du cache de clients. --- # 62. D18-I — Source count decomposition Pour chaque fichier, produire expérimentalement : ```text TOTAL KNOWN active downloading on queue connecting NNP A4AF DeadSource banned error ``` Cela donnerait une image beaucoup plus honnête que le nombre brut de sources. --- # 63. D18-J — Timeline debug Exemple de trace : ```text 12:00:00.000 SOURCE_DISCOVERED channel=SX provider=P 12:00:00.001 SOURCE_ADMITTED file=A 12:00:02.220 CONNECT_ATTEMPT path=DIRECT_TCP 12:00:02.510 CONNECTED 12:00:02.690 HELLO_COMPLETE 12:00:02.740 FILE_CONFIRMED 12:00:02.900 QUEUE_ENTER qr=248 12:29:12.100 REASK qr=173 12:58:21.021 REASK qr=51 13:27:30.412 SLOT_ACCEPTED 13:27:30.921 FIRST_BYTE 13:36:42.330 DISCONNECTED reason=REMOTE_CLOSE 13:36:42.331 STATE DS_DOWNLOADING→DS_ONQUEUE ``` Ce format rend immédiatement visibles les délais. --- # 64. Trust dans la timeline Associer aux événements leur niveau de preuve : ```text QR_RECEIVED → REMOTE_CLAIM PARTSTATUS → REMOTE_CLAIM CONNECTED → LOCAL_MEASURED FIRST_BYTE → LOCAL_MEASURED PART_OK → HASH_VERIFIED SecureIdent → CRYPTO_VERIFIED ``` Cela unifie D05 avec le lifecycle. --- # 65. Endgame dans la timeline D06 doit fournir des événements : ```text ENDGAME_ENTER LOCKED RESERVATION_RELEASE SPECULATIVE_RACE RESCUE ``` D09 peut alors observer si une source passe artificiellement NNP à cause de réservations. --- # 66. DiskGuard / causes locales Une source ne doit pas être accusée d’être lente si le pipeline est bloqué localement par : ```text disk wait hashing decompression write buffer ``` Le lifecycle trace doit pouvoir corréler réseau et pression disque. --- # 67. Pas de nouveau protocole nécessaire Tout le chantier D18 peut commencer avec : ```text logs metrics event IDs ``` sans modifier un seul paquet eD2K. C’est l’une des raisons pour lesquelles ce dossier est une base de travail particulièrement sûre. --- # 68. Fuzzing de machine d’état Au-delà du parser, tester les séquences impossibles : ```text ACCEPTUPLOADREQ avant File Request block data avant slot QueueRank avant demande double Hello double Accept Cancel après socket fermé FileNotFound en plein download ``` L’objectif est : ```text pas de crash pas d'état coincé cleanup correct ``` --- # 73. Nettoyage des états fossiles Candidats déjà identifiés : ```text DS_REMOTEQUEUEFULL DS_TOOMANYCONNSKAD ``` Ne rien supprimer avant instrumentation. Ajouter des compteurs : ```text state_enter_count ``` et mesurer sur semaines/mois. --- # 75. Conclusion technique D18 change le niveau d’analyse. Au lieu d’étudier : ```text une fonction ``` nous étudions : ```text une trajectoire dans le temps ``` C’est probablement la bonne abstraction pour la phase suivante du projet. La question devient : ```text quelle décision a fait passer ce pair de l'état A à l'état B, avec quelle preuve, pour quel coût réseau, et avec quel résultat utile ? ``` Une fois cette timeline mesurable, beaucoup de vieux paramètres pourront être étudiés objectivement. --- # 76. Résumé des expérimentations prioritaires ```text L18-1 Connection Path Success L18-2 Time to First Useful Byte L18-3 Reask Effectiveness L18-4 Disconnect Reason Taxonomy L18-5 DeadSource Retry Outcome L18-6 Upload/Download Cross-Effect L18-7 Source-State Decomposition L18-8 A4AF/NNP Event Correlation L18-9 Endgame Lifecycle Correlation L18-10 Disk/Network Causality L18-11 State-machine fuzzing L18-12 Legacy-state hit counters ``` Première phase : > **100 % observation et tests.** Aucune modification fonctionnelle n’est nécessaire pour commencer. --- ## Sources principales - eMule Community 0.72a : - `ClientStateDefs.h` - `BaseClient.cpp` - `DownloadClient.cpp` - `UploadClient.cpp` - `DownloadQueue.cpp` - `UploadQueue.cpp` - `DeadSourceList.cpp` - `ClientList.cpp` - `Opcodes.h` - Dossiers internes 01–17. - Documentation protocolaire eDonkey/eMule déjà intégrée au corpus. **Statut : première version consolidée.** ================================================================================================ --- # Dossier 19 — Atlas des paquets, opcodes et encapsulation {#g19} ## 1. Pourquoi un atlas du wire protocol eMule ne possède pas un unique espace d'opcodes. La signification d'un message dépend au minimum du transport, de la famille de protocole et du contexte dans lequel l'opcode est reçu. Un identifiant utile d'un message est donc plutôt : ```text transport + famille de protocole + direction/contexte + opcode ``` Un même nombre d'opcode peut avoir une signification différente dans deux familles distinctes. ## 2. Octets de famille de protocole Dans les sources eMule étudiées, les valeurs principales sont : ```cpp OP_EDONKEYHEADER = 0xE3 OP_KADEMLIAHEADER = 0xE4 OP_KADEMLIAPACKEDPROT = 0xE5 OP_PACKEDPROT = 0xD4 OP_EMULEPROT = 0xC5 OP_MLDONKEYPROT = 0x00 OP_EDONKEYPROT = OP_EDONKEYHEADER ``` Interprétation : ```text 0xE3 espace historique eDonkey/eD2K 0xC5 extensions eMule 0xD4 paquet générique compressé 0xE4 Kad 0xE5 Kad compressé 0x00 marqueur MLDonkey dans le code étudié ``` ## 3. Framing TCP Le framing TCP eD2K/eMule étudié utilise un header de 6 octets : ```text +----------+----------------+--------+ | protocol | packet length | opcode | | 1 octet | 4 octets | 1 oct. | +----------+----------------+--------+ | payload | +------------------------------------+ ``` Le champ `packetlength` inclut l'octet opcode et le payload. Autrement dit : ```text packetlength = 1 + payload_size wire_size = 5 + packetlength = 6 + payload_size ``` TCP étant un flux, ce champ de longueur est nécessaire pour reconstruire les frontières entre messages. ## 4. Framing UDP UDP conserve la frontière du datagramme. Le framing est donc conceptuellement plus court : ```text protocol + opcode + payload ``` La taille du datagramme fournit déjà la limite extérieure du message. ## 5. Un opcode n'est pas une identité globale Exemple important : la valeur `0x01` peut désigner des messages différents selon la famille et le canal. Un analyseur correct ne doit donc jamais faire : ```text opcode == 0x01 => message X ``` sans avoir auparavant déterminé : ```text E3 ? C5 ? E4 ? E5 ? TCP ? UDP ? client ↔ serveur ? client ↔ client ? ``` ## 6. Compression de paquet et compression de données fichier Deux concepts distincts coexistent. `OP_PACKEDPROT` (`0xD4`) désigne un paquet générique dont le contenu protocolaire est compressé, typiquement avec zlib dans les échanges qui le supportent. Cela ne doit pas être confondu avec `OP_COMPRESSEDPART`, qui concerne les données transférées d'un fichier. Donc : ```text OP_PACKEDPROT → compression d'un paquet protocolaire OP_COMPRESSEDPART → compression du contenu d'un bloc/segment de fichier ``` Les serveurs peuvent annoncer une capability de compression, notamment `SRVCAP_ZLIB`. ## 7. MultiPacket Plusieurs générations de MultiPacket apparaissent dans le code : ```cpp OP_MULTIPACKET = 0x92 // ancien / déprécié OP_MULTIPACKET_EXT = 0xA4 // ancien / déprécié OP_MULTIPACKET_EXT2 = 0xA9 OP_MULTIPACKETANSWER_EXT2 = 0xB0 ``` `MULTIPACKET_EXT2` s'appuie sur `FileIdentifier`. Un MultiPacket n'est pas simplement plusieurs trames TCP collées dans un seul `send()`. Il s'agit d'un payload protocolaire regroupant plusieurs sous-commandes logiques liées à un même contexte. ## 8. Les tags Les tags constituent une couche de sérialisation placée dans le payload de nombreux messages. Conceptuellement : ```text nom/id du tag + type + valeur ``` Le protocole historique a connu plusieurs formes : ```text noms de tags textuels identifiants numériques courts entiers 8/16/32/64 bits chaînes blobs chaînes de taille fixe ``` Les tags expliquent une partie importante de l'extensibilité du protocole : un message peut transporter de nouvelles propriétés sans nécessairement redéfinir toute sa structure fixe. ## 9. Parsing défensif L'atlas montre plusieurs surfaces classiques à valider avant d'utiliser les données : ```text longueur de paquet nombre de tags longueur des chaînes longueur des blobs type de tag opcode valide dans la famille courante payload minimal attendu compression réellement décompressable ``` Le protocole ayant accumulé des variantes historiques, un parseur ne peut pas supposer qu'une donnée distante est cohérente uniquement parce qu'elle utilise un opcode connu. ## 10. Résumé du dossier 19 Le point central est que le wire protocol d'eMule est une superposition de namespaces et d'extensions. Pour comprendre un paquet, il faut lire sa famille, son transport, son opcode puis son payload. Les tags et les MultiPackets ajoutent ensuite des niveaux de sérialisation supplémentaires. --- # Dossier 20 — Large Files et transition 32 → 64 bits {#g20} ## 1. Le problème historique des 4 Gio Les premières structures eD2K reposaient largement sur des tailles et offsets 32 bits. Une limite historique utilisée par eMule est : ```cpp OLD_MAX_EMULE_FILE_SIZE = 4290048000 ``` Cette valeur se situe légèrement sous `2^32` afin de rester compatible avec les anciennes hypothèses et découpages du logiciel. La limite moderne étudiée est : ```cpp MAX_EMULE_FILE_SIZE = 0x4000000000 ``` soit : ```text 2^38 octets = 256 Gio ``` ## 2. La taille d'une part n'a pas changé La compatibilité Large Files n'a pas consisté à redéfinir le découpage eD2K. La taille classique reste : ```cpp PARTSIZE = 9 728 000 octets ``` Les gros fichiers contiennent simplement davantage de parts et nécessitent des champs capables d'exprimer des tailles et offsets dépassant 32 bits. ## 3. Négociation de capacité Le support Large Files est annoncé pendant le handshake eMule via `CT_EMULE_MISCOPTIONS2`. Dans l'implémentation étudiée, le bit `Supports Large Files` se trouve dans ce champ de capabilities. Le commentaire du code associe également cette capacité au support des tags 64 bits nécessaires pour les grosses tailles. Le principe d'interopérabilité est donc : ```text ancien pair → continuer à employer les formats historiques pair Large Files → employer les variantes 64 bits lorsqu'elles sont nécessaires ``` ## 4. Opcodes de transfert 64 bits Trois variantes importantes apparaissent pour les offsets dépassant 32 bits : ```cpp OP_COMPRESSEDPART_I64 = 0xA1 OP_SENDINGPART_I64 = 0xA2 OP_REQUESTPARTS_I64 = 0xA3 ``` Ces variantes évitent de casser la signification des anciens paquets utilisés par les clients qui ne connaissent que les offsets 32 bits. ## 5. Tags de taille 32 ou 64 bits Le protocole de tags permet de sérialiser la taille avec une représentation adaptée. Dans le code étudié, l'écriture de la taille du fichier dépend notamment de `IsLargeFile()` : ```text fichier classique → entier 32 bits lorsque possible fichier Large File → entier 64 bits ``` Cette coexistence est visible aussi dans aMule et confirme que l'extension n'est pas propre à une seule fonction isolée du client. ## 6. Serveurs eD2K et Large Files Le client ne peut pas supposer qu'un serveur comprend les grands fichiers. Lors de la publication de la liste de fichiers partagés, `SendListToServer()` filtre les fichiers trop grands lorsque le serveur n'annonce pas la capability Large Files. Le même principe s'applique aux recherches et échanges associés : le client doit tenir compte des capacités du correspondant afin de ne pas envoyer une représentation impossible à interpréter. ## 7. Persistance `.part.met` Les fichiers de métadonnées des téléchargements ont eux aussi dû évoluer. Versions importantes étudiées : ```cpp PARTFILE_VERSION = 0xE0 PARTFILE_SPLITTEDVERSION = 0xE1 PARTFILE_VERSION_LARGEFILE = 0xE2 ``` La présence d'une version spécifique Large File montre que la transition 64 bits ne concerne pas uniquement le réseau : elle traverse également la persistance locale. ## 8. FileIdentifier et structures modernes `CFileIdentifier` centralise progressivement plusieurs éléments d'identité du fichier : ```text MD4 master hashset MD4 AICH master hashset AICH ``` Les mécanismes plus récents, notamment MultiPacketExt2, utilisent cette abstraction afin de transporter une identité plus riche tout en restant compatible avec l'identité eD2K historique. ## 9. Leçon d'architecture de la transition 64 bits La migration n'a pas consisté à annoncer « protocole v2 » et à abandonner l'ancien réseau. Elle s'est faite par : ```text capability négociée + tags 64 bits + opcodes I64 + formats de persistance adaptés + filtrage côté serveur/pair selon capacité ``` C'est un exemple très clair de la manière dont eMule a conservé l'interopérabilité en faisant cohabiter ancien et nouveau format. --- # Dossier 21 — Cycle complet de publication d'un fichier {#g21} ## 1. Avant le réseau : identifier le fichier Un fichier partagé doit d'abord être connu localement. eMule construit son identité à partir du hash eD2K et du hashset de parts ; les versions compatibles AICH peuvent également conserver l'identité AICH. Les informations connues sont persistées notamment dans `known.met` afin d'éviter de retraiter inutilement les mêmes fichiers à chaque démarrage. ## 2. Publication serveur : `OP_OFFERFILES` Côté serveur eD2K, le mécanisme central est `OP_OFFERFILES`. Le message annonce des fichiers partagés au serveur. Il transporte les identifiants et métadonnées permettant au serveur de construire son index. Le serveur ne reçoit jamais le contenu du fichier lui-même. Il reçoit essentiellement une affirmation du type : ```text ce client peut servir le FileHash X voici son nom, sa taille et ses métadonnées ``` ## 3. `CSharedFileList::SendListToServer()` Dans la branche étudiée, cette fonction applique plusieurs règles avant publication : - ne prendre que les fichiers éligibles et pas déjà publiés pour la séquence courante ; - ne publier les Large Files que si le serveur annonce `SupportsLargeFilesTCP()` ; - ordonner la sélection en tenant compte de la priorité d'upload ; - respecter le soft-file limit fourni par le serveur ; - limiter localement à 200 fichiers par paquet ; - construire chaque entrée via `CreateOfferedFilePacket()` ; - compresser le paquet lorsque le serveur annonce la compression correspondante. ## 4. Marqueurs de fichier complet/incomplet Les variantes modernes des offres peuvent utiliser des valeurs spéciales dans certains champs IP/port afin d'indiquer l'état complet ou incomplet du fichier au serveur compatible. Ce détail est une convention d'implémentation/extension et non une preuve que le serveur transporte le fichier. ## 5. Publication Kad : trois objets différents Kad ne publie pas un unique enregistrement « fichier ». L'étude distingue au moins : ```text publication de mots-clés publication de source publication de notes ``` Ces trois opérations répondent à trois besoins différents. ## 6. Publication de mots-clés Pour rendre un fichier trouvable par recherche textuelle, eMule extrait des mots-clés et calcule les clés Kad correspondantes. Le code étudié s'appuie notamment sur `CPublishKeyword`. Caractéristiques relevées : - seuls les fichiers complets sont utilisés pour la publication de mots-clés dans le chemin étudié ; - un envoi de publication est limité à un nombre fini d'identifiants de fichiers ; - la valeur étudiée est de 150 File IDs par publication de mot-clé avant rotation/continuation. ## 7. Publication de source La publication de source répond à la question : ```text qui possède actuellement des données du FileHash X ? ``` Cette information est temporaire et doit être rafraîchie périodiquement. Avant de publier, le client tient compte de sa capacité réelle à être contacté : client ouvert, client firewalled avec Buddy, ou possibilité de callback UDP direct lorsqu'elle est disponible. ## 8. Publication de notes Les Notes Kad transportent des métadonnées sociales associées au hash : nom observé, rating, description/commentaire et, avec les pairs compatibles, taille du fichier. Elles sont indépendantes du fait d'être une source du fichier. ## 9. Expiration et republication Kad doit être compris comme un ensemble d'annonces temporaires. Les ordres de grandeur relevés dans l'implémentation étudiée sont : ```text source Kad ≈ 5 heures mot-clé Kad ≈ 24 heures notes Kad ≈ 24 heures ``` La cadence d'émission est elle-même limitée ; la publication n'inonde pas immédiatement le réseau avec toutes les entrées disponibles. ## 10. Publication et état réel Une publication n'est pas une copie permanente de la vérité. Elle représente l'état annoncé au moment de l'enregistrement puis expire. Le système reste donc cohérent grâce à une boucle : ```text état local → publication → durée de vie → expiration → republication si l'information reste vraie ``` --- # Dossier 22 — Commentaires, ratings et Kad Notes {#g22} ## 1. Deux types d'informations à ne pas confondre eMule permet d'associer à un fichier : ```text un rating un commentaire texte ``` Ces informations peuvent être échangées directement entre clients et, dans les versions compatibles, publiées dans Kad via les Notes. ## 2. Longueur du commentaire Dans l'implémentation étudiée : ```cpp MAXFILECOMMENTLEN = 128 ``` La fonctionnalité a donc toujours été pensée comme une annotation courte, pas comme un système de discussion complet. ## 3. Commentaire direct d'une source Lorsqu'un pair fournit un rating/commentaire, l'information est associée à cette source et au fichier concerné. Cela permet notamment à l'interface d'afficher les avis connus sans exiger une infrastructure centrale. ## 4. Publication Kad Notes `CKnownFile::PublishNotes()` publie une Note lorsque : ```text commentaire non vide OU rating non nul ``` Le prochain instant de publication est ensuite repoussé selon `KADEMLIAREPUBLISHTIMEN`, de l'ordre de 24 heures dans la branche étudiée. ## 5. Nombre maximum de Notes La constante relevée est : ```cpp KADEMLIAMAXNOTESPERFILE = 150 ``` Cette limite évite une croissance sans borne des résultats associés à une même clé. ## 6. Contenu d'une Note Selon les versions/capabilities, une Note peut fournir notamment : ```text nom de fichier rating description/commentaire taille du fichier ``` Les champs disponibles dépendent du format compris par les participants. ## 7. Ce système n'est pas une réputation forte Une Note n'est pas une signature de l'auteur du fichier et n'est pas une certification. Il faut la traiter comme : ```text une assertion publiée par un participant ``` et non comme : ```text une preuve cryptographique que le fichier est bon, authentique ou sûr ``` La pollution et les avis artificiels restent donc possibles. ## 8. Identité du fichier et stabilité de l'avis L'avantage de rattacher la Note au hash plutôt qu'au seul nom est qu'un renommage local ne suffit pas à déplacer l'avis vers un autre contenu. En revanche, cela ne résout pas le problème de fiabilité de l'auteur de l'avis. --- # Dossier 23 — Messages privés, Captcha et anti-spam {#g23} ## 1. Messagerie client-client historique Le protocole contient depuis longtemps une messagerie directe entre clients. Opcode relevé : ```cpp OP_MESSAGE = 0x4E ``` Le chat est indépendant du transport des fichiers mais partage l'identité et la connexion client-client. ## 2. Pourquoi un anti-spam a été ajouté Une fonction de messagerie ouverte à des inconnus a naturellement attiré des robots et publicités automatiques. Les versions plus récentes ont donc ajouté plusieurs couches locales : ```text accepter uniquement les amis selon préférence filtres de texte configurables heuristiques sur le premier message détection d'URL marquage spammer selon comportement Captcha ``` ## 3. Capability Captcha Le support Captcha est annoncé dans `CT_EMULE_MISCOPTIONS2`. Dans l'implémentation étudiée, `SupportsCaptcha` correspond au bit 11 de ce champ de capacités. ## 4. Opcodes Captcha ```cpp OP_CHATCAPTCHAREQ = 0xA5 OP_CHATCAPTCHARES = 0xA6 ``` Le premier transporte la demande/challenge ; le second transporte la réponse ou le résultat associé. ## 5. Séquence typique Le chemin étudié peut être résumé ainsi : ```text message d'un pair inconnu → message retenu → génération d'un Captcha → image envoyée au pair → réponse saisie → validation → message original affiché si réussite ``` ## 6. Nombre d'essais La logique étudiée autorise jusqu'à trois tentatives avant d'abandonner le challenge. ## 7. Validation de l'image reçue Le code applique des contrôles grossiers avant d'accepter une image Captcha distante, notamment sur la taille du payload et sur les dimensions décodées. L'ordre de grandeur observé pour la taille est approximativement : ```text 128 à 4096 octets ``` avec des limites de dimensions afin d'éviter des entrées absurdes. ## 8. Ce que prouve réellement le Captcha Le Captcha apporte au mieux une indication : ```text un humain a probablement interagi avec cette conversation ``` Il ne prouve pas : ```text l'identité civile du pair l'authenticité de son logiciel la sécurité d'un fichier la fiabilité générale de ce client ``` ## 9. Anti-spam heuristique Le filtrage repose en partie sur des observations comportementales. Un premier message contenant certains motifs, ou une série de messages sans interaction réciproque, peut être classé comme suspect. Ces règles sont une politique locale du client, pas une obligation du wire protocol. --- # Dossier 24 — Timeouts, reasks et cadence globale du réseau {#g24} ## 1. Pourquoi les timers sont structurants eMule peut connaître des centaines de sources, plusieurs serveurs et des dizaines de milliers de contacts Kad potentiels. Sans temporisations, chaque client pourrait générer en permanence des demandes de statut, de sources, de callbacks et de publication. Les timers sont donc une forme de régulation distribuée. ## 2. Constantes représentatives Les valeurs relevées dans la branche étudiée incluent notamment : ```cpp CONNECTION_TIMEOUT = 40 s FILEREASKTIME = 29 min SERVERREASKTIME = 15 min UDPSERVERREASKTIME = 30 min SOURCECLIENTREASKS = 40 min SOURCECLIENTREASKF = 5 min KADEMLIAREASKTIME = 1 h DOWNLOADTIMEOUT = 100 s CONSERVTIMEOUT = 25 s MIN_REQUESTTIME = 10 min MAX_PURGEQUEUETIME = 1 h PURGESOURCESWAPSTOP = 15 min ``` À cela s'ajoutent les cadences de publication Kad vues au dossier précédent. ## 3. Le reask de fichier d'environ 29 minutes Lorsqu'un client attend dans la file d'une source, il n'est pas nécessaire de garder une connexion TCP ouverte pendant toute l'attente. Le client peut revenir périodiquement pour : ```text confirmer qu'il demande toujours le fichier obtenir l'état distant actualisé mettre à jour son Queue Rank lorsque disponible rafraîchir les informations de parts ``` La valeur emblématique est `FILEREASKTIME ≈ 29 min`. ## 4. UDP reask Lorsque les deux pairs possèdent les capacités nécessaires et que l'état réseau le permet, certaines mises à jour de file peuvent passer par UDP. Le but est de réduire le coût d'une reconnexion TCP complète pour une petite quantité d'information. Si le chemin UDP n'est pas utilisable, le client conserve un chemin TCP de repli. ## 5. Les reasks ne forment pas un unique timer fixe La cadence effective dépend de nombreux états : ```text source normale source firewalled NNP A4AF source exchange connexion en cours source morte temporaire queue pleine fichier stoppé ``` Des délais spécifiques et backoffs empêchent qu'un pair soit interrogé de façon répétitive lorsqu'aucun progrès utile n'est attendu. ## 6. DeadSource n'est pas un bannissement La DeadSourceList agit comme un cooldown : un endpoint ayant récemment échoué peut être temporairement ignoré avant d'être redécouvert ou retenté. L'étude du cycle de vie a montré que les portées locale/globale et les durées peuvent varier selon le type de source et la raison de l'échec. ## 7. Purge de la queue `MAX_PURGEQUEUETIME ≈ 1 h` concerne la conservation d'une entrée distante sans activité suffisante. Cette logique est distincte du reask de fichier lui-même. ## 8. Publication Kad Les publications ont elles aussi une cadence destinée à éviter l'inondation : ```text sources ≈ 5 h keywords ≈ 24 h notes ≈ 24 h ``` Une boucle interne espace aussi les opérations de store individuelles ; tout n'est pas envoyé en rafale au même instant. ## 9. Timers de serveur Deux notions différentes doivent être séparées : ```text re-demander les sources d'un fichier au serveur connecté interroger d'autres serveurs en UDP ``` Les ordres de grandeur étudiés sont respectivement 15 et 30 minutes, avec une file locale qui regroupe et espace les demandes TCP. ## 10. Timers et protocole La plupart de ces constantes ne font pas partie d'une spécification wire obligatoire. Elles représentent des politiques de l'implémentation eMule qui se sont néanmoins imposées comme comportement de fait du réseau. Il faut donc distinguer : ```text format du message ≠ moment où le logiciel décide de l'envoyer ``` ## 11. Résumé du dossier 24 Le réseau eMule repose autant sur ce qu'il n'envoie pas que sur ce qu'il envoie. Les temporisations empêchent la connaissance de centaines de sources de devenir des centaines de connexions et de requêtes permanentes. --- # Dossier 25 — Synthèse générale : cycle complet eD2K/eMule {#g25} ## 1. eMule comme ensemble de couches L'étude complète montre qu'eMule combine plusieurs systèmes : ```text serveurs eD2K Kad client-client files d'upload crédits et SecureIdent Source Exchange A4AF MD4/AICH persistance locale publication périodique ``` Aucun de ces mécanismes ne décrit à lui seul « le protocole eMule ». ## 2. Au démarrage Le client recharge sa mémoire locale : ```text server.met nodes.dat known.met part.met clients.met preferencesKad.dat preferences.ini ``` Chaque fichier joue un rôle différent : bootstrap réseau, mémoire des fichiers, récupération des téléchargements, crédits, identité Kad ou préférences. ## 3. Rejoindre eD2K et Kad Le client peut utiliser simultanément : ```text un serveur eD2K Kad ``` Le serveur fournit un Client ID et peut déterminer la joignabilité TCP historique HighID/LowID. Kad construit une vue distribuée du réseau à partir de contacts et d'identifiants 128 bits. ## 4. Rechercher Le serveur interroge son index central. Kad route vers les nœuds proches de la clé dérivée du mot recherché. Dans les deux cas, le résultat doit ensuite être ramené à l'identité du contenu : hash eD2K et taille, avec métadonnées associées. ## 5. Trouver les sources Une source peut venir de : ```text serveur connecté TCP autres serveurs UDP Kad Source Exchange source passive lien explicite ``` La provenance n'est pas la preuve que la source sera utile ou joignable. ## 6. Établir la connexion `TryToConnect()` représente une véritable machine de choix du chemin : ```text TCP direct Direct UDP Callback Server Callback Kad/Buddy Callback attente optimisée LowID ``` La sélection dépend de la joignabilité des deux pairs et des informations déjà connues. ## 7. Handshake HELLO / HELLOANSWER permettent l'échange de l'identité et des capabilities. Les extensions importantes incluent notamment : ```text Unicode compression SecureIdent Source Exchange Extended Requests AICH Large Files Kad obfuscation Captcha Direct UDP Callback MultiPacket ``` Une capability est une annonce de support ; elle ne doit pas être confondue avec une preuve de comportement correct. ## 8. Confirmer le fichier et les parts Une fois connecté, le pair doit confirmer qu'il connaît le fichier demandé. Les échanges de statut de parts indiquent ensuite quelles portions sont annoncées comme disponibles. Cette information distante reste une assertion ; l'intégrité réelle des données ne sera connue qu'après réception et vérification cryptographique/hash. ## 9. Entrer en queue La source distante reçoit une demande d'upload. Elle applique sa politique locale : limites, doublons, fréquence de demandes, score d'attente, priorité de fichier, crédits éventuels et slots spéciaux. Le Queue Rank donne une indication de position, mais n'est pas une promesse ferme de date de démarrage. ## 10. Crédits et SecureIdent Les crédits sont locaux à chaque client : Alice mémorise ce qu'Akshaan lui a envoyé ; Bob ne reçoit pas automatiquement cette information. SecureIdent protège l'association entre l'identité persistante et les crédits en utilisant un mécanisme challenge-response avec clé privée/publique. ## 11. A4AF et NNP Une même source peut posséder plusieurs fichiers recherchés. A4AF évite de la traiter comme plusieurs relations indépendantes. Un fichier est actif, les autres restent candidats alternatifs. `DS_NONEEDEDPARTS` signifie qu'aucune part utile n'est actuellement disponible pour ce fichier/source ; cela n'implique pas nécessairement une inutilité définitive. ## 12. Transférer par blocs Le fichier est divisé en parts eD2K de 9 728 000 octets, puis en requêtes plus petites. Plusieurs sources alimentent simultanément des zones différentes du même fichier. Le choix des parts tient compte de la disponibilité/rareté et de l'état local des requêtes déjà réservées. ## 13. Vérifier Le hashset MD4 vérifie les parts complètes. AICH ajoute une structure de hashes plus fine permettant de localiser et récupérer plus précisément des zones corrompues. La réception d'octets n'est donc pas équivalente à leur acceptation définitive. ## 14. Fin de téléchargement À mesure que le nombre de blocs libres diminue, le problème change : il faut éviter que les derniers blocs restent immobilisés derrière une source lente ou indisponible. Les mécanismes Endgame historiques/mods et la gestion moderne des requêtes cherchent à réduire cette longue traîne sans violer le format de transfert. ## 15. Devenir source Lorsque le fichier est complet et validé, il rejoint les fichiers partagés. Le client peut alors annoncer sa disponibilité : ```text OP_OFFERFILES vers le serveur publication de mots-clés Kad publication de source Kad éventuelles Notes Kad ``` Il devient ainsi une nouvelle source pour les autres utilisateurs. ## 16. La boucle complète ```text IDENTIFIER ↓ RECHERCHER ↓ DÉCOUVRIR LES SOURCES ↓ SE CONNECTER ↓ NÉGOCIER LES CAPACITÉS ↓ ENTRER EN FILE ↓ REASK / ATTENTE ↓ TRANSFÉRER ↓ VÉRIFIER ↓ RÉPARER SI NÉCESSAIRE ↓ TERMINER ↓ PARTAGER ↓ PUBLIER ↓ DEVENIR UNE SOURCE DU RÉSEAU ``` ## 17. Ce que le serveur n'est pas Le serveur n'héberge pas les fichiers. Il indexe des annonces et maintient l'état des clients connectés afin d'offrir recherche, GetSources et callbacks. ## 18. Ce que Kad n'est pas Kad n'est pas un transport de fichiers. Il est principalement un système distribué de découverte et de publication : mots-clés, sources, Notes, routage et assistance de joignabilité. ## 19. Ce que HighID signifie réellement HighID indique principalement qu'un client est directement joignable en TCP selon le modèle IPv4 historique d'eD2K. LowID ne veut pas dire « client lent » ; il signifie qu'un chemin de callback/intermédiation sera nécessaire pour certaines connexions entrantes. ## 20. Ce que les crédits ne sont pas Ils ne sont pas une monnaie globale, ni une réputation centrale. Ils constituent une mémoire bilatérale locale, liée à l'identité du pair et protégée autant que possible par SecureIdent. ## 21. Ce que l'obfuscation ne fait pas L'obfuscation rend le trafic moins trivialement identifiable. Elle ne fournit ni anonymat, ni VPN, ni sécurité équivalente à TLS moderne. ## 22. Pourquoi le protocole a survécu La stratégie d'évolution est répétitive : ```text conserver l'ancien format annoncer une capability ajouter un nouveau message ou une nouvelle représentation utiliser l'extension uniquement avec un pair compatible ``` Large Files, AICH, Source Exchange 2, MultiPacketExt2, obfuscation et Captcha suivent tous, à des degrés différents, cette logique d'extension progressive. ## 23. Wire protocol et politique locale Cette distinction traverse toute l'étude. Le wire protocol fixe surtout : ```text familles de paquets opcodes sérialisation hashes identifiants handshakes formats compatibles ``` Le client décide localement de nombreux comportements : ```text timers nombre de sources choix de la prochaine source score de queue nombre de slots sélection des parts A4AF DeadSource anti-spam cadence pratique de nombreuses opérations ``` Deux clients peuvent donc être parfaitement interopérables sans appliquer exactement la même stratégie locale. ## 24. Une donnée distante est une assertion avant d'être une vérité Cette règle explique beaucoup de protections du logiciel. Exemples d'assertions distantes : ```text Queue Rank Part Status capabilities annoncées sources transmises par SX métadonnées de recherche statistiques serveur contacts Kad ratings/commentaires ``` À l'inverse, un hash validé sur des données réellement reçues constitue une preuve locale beaucoup plus forte. ## 25. Résumé final L'ensemble du protocole peut être ramené à quatre besoins fondamentaux : ```text QUOI ? identifier précisément le contenu OÙ ? trouver les pairs qui le possèdent QUAND ET COMMENT ? organiser les échanges sans saturer les participants ni le réseau EST-CE CORRECT ? vérifier les données reçues avant de les considérer comme acquises ``` Serveurs, Kad, Source Exchange, queues, crédits, A4AF, MD4, AICH, callbacks, Large Files et timers sont les mécanismes bâtis autour de ces quatre problèmes.