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à :
0x40 <- ID change
dans le dialogue client-serveur.
1.2 eMule 0.72a
OtherFunctions.h:482-485
inline bool IsLowID(uint32 id)
{
return id < 16777216u;
}Soit :
0x01000000
16 777 216
2^24
Petite anomalie documentaire trouvée
Le commentaire de OtherFunctions.h écrit :
0x01000000u or 2^25mais 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
#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
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
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 :
0x1C Client callback request -> serveur
0x35 <- Server callback request
0x36 <- Callback fail
eMule 0.72a Opcodes.h :
OP_CALLBACKREQUEST 0x1C // <ID 4>
OP_CALLBACKREQUESTED 0x35 // <IP 4><PORT 2>
OP_CALLBACK_FAIL 0x364.2 Séquence
Client A est joignable, B est LowID.
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 :
- déjà connecté / connexion en cours ;
- échec immédiat ;
- connexion TCP sortante directe ;
- callback UDP direct ;
- attente volontaire ;
- callback serveur ;
- callback Kad.
6. Chemin 3 — connexion TCP directe
BaseClient.cpp:1385-1399
Si le client n’a pas de LowID :
if (!HasLowID() ...)
{
...
Connect();
}Connect() utilise :
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 :
theApp.CanDoCallback(this)En cas d’impossibilité :
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 :
DS_LOWTOLOWIP
m_bReaskPending = trueLe 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 :
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 :
direct callback is only possible if connected to kad,
TCP firewalled and verified UDP open9.3 Paquet
Opcodes.h
OP_DIRECTCALLBACKREQ = 0x95Format :
<TCPPort 2>
<UserHash 16>
<ConnectionOptions 1>
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 :
OP_CALLBACKREQUESTavec 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 :
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 :
KADEMLIA_CALLBACK_REQLe 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 :
UDP_FIREWALLTEST_CLIENTSTOASK = 2Deux 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
HIGHEST_LOWID_ED2K_KAD = 16777216Son 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 :
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 :
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 :
return id < 16777216u; //0x01000000u or 2^25La valeur et le test sont corrects, mais :
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 :
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.