0. Résumé exécutif
Le protocole impose principalement
OP_STARTUPLOADREQ
OP_ACCEPTUPLOADREQ
OP_OUTOFPARTREQS
OP_REQUESTPARTS
OP_SENDINGPART
OP_QUEUERANK / OP_QUEUERANKING
Le client décide localement
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.
- Queue Observatory : rendre mesurable et explicable chaque décision d’ordonnancement ;
- 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 ;
- 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 :
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 :
OP_REQUESTPARTS 0x47
OP_STARTUPLOADREQ 0x54
OP_ACCEPTUPLOADREQ 0x55
OP_OUTOFPARTREQS 0x57
OP_QUEUERANK 0x5C
OP_QUEUERANKING 0x60La 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 :
OP_STARTUPLOADREQeMule :
- vérifie que le handshake est terminé ;
- cherche le fichier partagé correspondant au hash ;
- enregistre ce fichier comme fichier demandé ;
- appelle :
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 :
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
softQueueLimit = thePrefs.GetQueueSize();
hardQueueLimit =
softQueueLimit
+ max(softQueueLimit, 800) / 4;Avec la valeur par défaut historique :
soft = 5000
hard = 6250
Entre soft et hard
Un client standard n’est encore accepté que si :
GetCombinedFilePrioAndCredit()
>= moyenne de la fileou s’il dispose d’un Friend Slot.
Cette fonction combine :
10 * priorité fichier * créditRé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 :
extension collection
taille < 50 KiB
peut contourner la file et obtenir un slot direct si le client est connecté.
Constante :
Opcodes.h:129
MAXPRIORITYCOLL_SIZE = 50 * 10245.2 Friend Slot
Dans GetScore() :
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
dwBaseValue =
GetTickCount() - GetWaitStartTime();
score_seconds =
dwBaseValue / 1000;
if (CreditSystem)
score *= CreditRatio;
score *= FilePriority / 10;Résumé :
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 :
requpfileidau 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 :
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 :
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é :
m_bAddNextConnect = true;Il sera privilégié lorsqu’il reviendra.
11.2 Calcul du QR
UploadQueue.cpp:830-840
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
OP_QUEUERANKINGPaquet 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 :
OP_OUTOFPARTREQSavant 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
PARTSIZE = 9 728 000
SESSIONMAXTRANS = PARTSIZE + 20*1024L’option :
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 :
≈ 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
SESSIONMAXTIME = 1 heureUploadQueue.cpp:794-815
Deux mécanismes :
- session > 1 h ;
- 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 :
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
MAX_PURGEQUEUETIME = 1 heureFindBestClientInQueue()
Un client est retiré si :
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
MIN_UP_CLIENTS_ALLOWED = 2
MAX_UP_CLIENTS_ALLOWED = 100Mais AcceptNewClient() autorise en pratique directement jusqu’à :
max(MIN_UP_CLIENTS_ALLOWED, 4)donc quatre slots dans cette première condition.
Débit cible
UploadQueue.cpp:397-409
<= 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 :
UPLOAD_CLIENT_DATARATE = 3072 bytes/s
MIN_UP_CLIENTS_ALLOWED = 2
MAX_UP_CLIENTS_ALLOWED = 100L’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’à :
25 KiB/s par client
Le changelog inclus indique au 1er mars 2010 :
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 :
// Equal bandwidth for all slotsLe 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 :
// 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 :
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 :
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 :
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 :
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 :
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 :
- comprendre l’objectif historique ;
- mesurer sa fréquence d’activation en 2026 ;
- identifier les effets secondaires ;
- seulement ensuite décider.
26. Une règle historique probablement devenue presque invisible
UploadClient.cpp:227-228
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.
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 :
QR distant reçu
de :
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 :
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 :
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.