Geek++ · Dossier 02 / 25

HighID, LowID, callbacks et joignabilité

Étude technique publique · 1,844 mots · ≈ 9 min de lecture attentive

Étude intégraleSans réflexion CODEXSources & constantes conservées
Joignabilité
HighID
joignable
⇄
LowID
callback
Serveur · Buddy · UDP

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^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

#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     0x36

4.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 :

  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 :

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 = 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 :

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 open

9.3 Paquet

Opcodes.h

OP_DIRECTCALLBACKREQ = 0x95

Format :

<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_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 :

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_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 :

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

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 :

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^25

La 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.



Dossier 02 terminé

HighID, LowID, callbacks et joignabilité

← Dossier 01Dossier 03 →