Geek++ · Dossier 18 / 25

Cycle de vie complet d’un pair / d’une source

Étude technique publique · 2,626 mots · ≈ 12 min de lecture attentive

Étude intégraleSans réflexion CODEXSources & constantes conservées
Cycle de vie d’un pair
Découvert→Connecté→Queue→Slot→Transfert→Déconnexion

0. Résumé exécutif

Ce dossier est volontairement transversal.

Les dossiers précédents ont étudié séparément :

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 :

EUploadState
EDownloadState
EChatState
EKadState
EConnectingState

Le même client peut donc être simultanément :

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 :

enum EUploadState : uint8
{
    US_UPLOADING,
    US_ONUPLOADQUEUE,
    US_CONNECTING,
    US_BANNED,
    US_NONE
};

Ces états concernent :

ce que le pair fait chez NOUS

Ils ne décrivent pas notre téléchargement depuis lui.


2. États download

La branche contient :

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 :

DS_REMOTEQUEUEFULL

est pratiquement fossile, alors que l’état remote queue full est suivi par ailleurs.


3. États de connexion

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

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 :

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 :

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 :

source_from = SERVER / KAD / SX / PASSIVE / LINK

mais dans le cas SX :

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 :

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 :

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 :

DIRECTTCP
DIRECTCALLBACK
KADCALLBACK
SERVERCALLBACK
PRECONDITIONS

Le code possède également des early exits :

déjà connecté
déjà connecting
pair invalide
ban/filter
incompatibilité crypt

10. Direct TCP

Chemin idéal :

connect(remote IP, remote TCP port)

Puis :

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 :

pair TCP firewalled
Kad/direct callback capability
Kad UDP port utilisable
connect options compatibles

Paquet :

OP_DIRECTCALLBACKREQ

avec :

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

CCS_SERVERCALLBACK
OP_CALLBACKREQUEST

Le serveur doit ensuite envoyer au LowID :

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 :

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 :

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 :

DS_CONNECTING

puis selon le chemin :

DS_WAITCALLBACK
DS_WAITCALLBACKKAD

peuvent apparaître.


16. Handshake

D12 :

Hello / HelloAnswer

permet d’obtenir :

UserHash
ClientID
ports
capabilities
server
Buddy
crypt options

À ce stade :

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 :

filename
File Status
hashset
AICH/FileIdentifier
source exchange

selon capabilities et données déjà connues.


19. DS_REQHASHSET

Lorsqu’un hashset manque :

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 :

peer says it doesn't have requested file

D09 montre :

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 :

NNP
part selection
rarity
SX
A4AF

Mais D05/D06 la classent :

REMOTE_CLAIM

jusqu’à observation des données.


22. Demande de slot

Opcode historique :

OP_STARTUPLOADREQ 0x54

Le pair distant peut :

nous mettre dans sa queue
ou
nous accorder le slot

23. Queue state

Etat courant :

DS_ONQUEUE

Le client conserve notamment :

last asked time
remote queue rank
remote queue full flag
UDP reask information

24. Remote Queue Rank

Deux paquets historiques :

OP_QUEUERANK
OP_QUEUERANKING

avec tailles différentes.

D05 a montré :

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 :

valid rank update
unsolicited
flood

26. Reask normal

D09 :

FILEREASKTIME ≈ 29 min

Le reask est une politique client historique.

Il ne faut pas le confondre avec :

SERVERREASKTIME
SX timers
Kad timers
DeadSource cooldown

27. Minimum fast ask

Opcodes.h :

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 :

OP_REASKFILEPING

puis réponse :

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 :

OP_REASKCALLBACKUDP
OP_REASKCALLBACKTCP

Le cycle de queue dépend donc de la connectivité.


30. Slot accordé

Opcode :

OP_ACCEPTUPLOADREQ 0x55

La réception conduit au lancement du pipeline de téléchargement.

Le code doit nettoyer/initialiser :

pending blocks
download session stats
last data rate
compression state

selon contexte.


31. DS_DOWNLOADING

C’est l’état productif :

payload attendu/reçu

D06 couvre :

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 :

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 :

unrequested = 0
file incomplete

34. Data receive

Pendant le transfert, nous obtenons enfin :

LOCAL_MEASURED

pour :

bytes
rate
timestamps
source of ranges

C’est un changement de niveau de confiance important.


35. Integrity transition

Après complétion de partie :

LOCAL_MEASURED
→ MD4/AICH
→ HASH_VERIFIED

ou :

CORRUPTED

D07 intervient alors.


36. Corruption peut modifier le lifecycle sans événement distant

Si AICH/ICH rouvre une zone :

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() :

CreateBlockRequests()
→ pending empty
→ CancelTransfer
→ DS_NONEEDEDPARTS
→ SwapToAnotherFile()

Point crucial D09 :

pending empty

ne signifie pas toujours :

peer lacks useful part

mais parfois :

all useful gaps already reserved

39. OP_CANCELTRANSFER

Opcode :

0x56

Il termine une session de transfert active côté protocole.

Raison réelle à tracer :

NNP
swap
completion
session rotation
error
disconnect

40. OP_OUTOFPARTREQS

Opcode :

0x57

Sémantique :

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 :

DownloadState
UploadState
raison socket
phase handshake
whether we were actively downloading

42. Déconnexion pendant download

Constat important :

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 :

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 :

global
local file

45. Timers DeadSource

Dans DeadSourceList.cpp, ordres de grandeur observés :

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 :

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 :

UploadQueue

Son cycle :

STARTUPLOADREQ reçu
→ admission
→ US_ONUPLOADQUEUE
→ ranking
→ US_UPLOADING
→ rotation / cancel

49. Queue purge

MAX_PURGEQUEUETIME :

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 :

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 :

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 :

DS_ONQUEUE

est moins informatif que :

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 :

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

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 :

source discovered
→ first useful byte

décomposé en :

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 :

Direct TCP
Direct UDP Callback
Server Callback
Kad Callback
Wait-for-incoming

mesurer :

attempts
success
median latency
timeout
payload eventually received

57. D18-D — Reask effectiveness

Pour chaque reask :

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 :

source disconnected

Il faut distinguer :

remote close
local cancel
timeout
NNP transition
A4AF swap
file completion
protocol error
ban
connection reset

59. D18-F — DeadSource usefulness

Mesurer :

deadsource insertions
rediscovery during cooldown
retry after expiry
success after expiry

Question :

15/30/45 min évitent-ils réellement des tentatives inutiles en 2026 ?

60. D18-G — Upload/download cross-effect

Le cas historique :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

pas de crash
pas d'état coincé
cleanup correct

73. Nettoyage des états fossiles

Candidats déjà identifiés :

DS_REMOTEQUEUEFULL
DS_TOOMANYCONNSKAD

Ne rien supprimer avant instrumentation.

Ajouter des compteurs :

state_enter_count

et mesurer sur semaines/mois.


75. Conclusion technique

D18 change le niveau d’analyse.

Au lieu d’étudier :

une fonction

nous étudions :

une trajectoire dans le temps

C’est probablement la bonne abstraction pour la phase suivante du projet.

La question devient :

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

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 18 terminé

Cycle de vie complet d’un pair / d’une source

← Dossier 17Dossier 19 →