Avalanche : Téléchargement peer-to-peer de fichier par combinaisons linéaires de blocs

Sujet proposé par Laurent Viennot
http://gyroweb.inria.fr/~viennot/

Difficulté : moyen (**) ou difficile (***) avec l'extension réseau.

Page de suivi et challenges :
http://gyroweb.inria.fr/~viennot/enseignement/projets/avalanche/


1  Introduction

Le téléchargement de pair à pair (ou « peer-to-peer » en anglais) de fichier consiste à utiliser la bande-passante des clients pour servir efficacement un grand nombre de clients. Le fichier est ainsi découpé en n blocs, ce qui permet aux clients en cours de téléchargement de s'échanger des blocs directement sans passer par la source. Ce principe d'échange est par exemple à la base du protocole BitTorrent [1] qui l'utilise pour inciter les clients à donner une partie de leur bande-passante : un client donne de préférence à ceux qui lui donnent, c'est le principe du « prêté pour un rendu » (ou « tit for tat » en anglais).

Cependant, quand le nombre de clients augmente la décision de télécharger tel ou tel bloc depuis tel ou tel client devient difficilement optimisable. BitTorrent utilise une heuristique consistant à essayer d'obtenir le bloc qui paraît le plus rare. Cependant des situation de blocage ou de ralentissement peuvent être observés pour certains clients. Par exemple, un client qui ne possède que des blocs possédés par tous les autres clients n'intéressera personne et obtiendra une vitesse de téléchargement faible.

Pour lever ce frein, le protocole Avalanche [2] propose d'échanger des combinaisons linéaires de blocs. Chaque combinaison possédant des informations provenant de tous les blocs est ainsi susceptible d'intéresser tout client qui n'a pas fini son téléchargement : il suffit de télécharger n combinaisons indépendantes pour être capable de reconstruire le fichier.

Le présent sujet propose de programmer la partie concernant le calcul des combinaisons linéaires, la vérification de leur intégrité et la reconstruction du fichier. Même si la compréhension du sujet nécessite d'introduire quelques notions sur le corps des entiers modulo un grand nombre premier (dont les opérations sont fournies dans les librairies de java), la réalisation repose sur des notions élémentaires d'algèbre linéaire.

En extension, on pourra traiter la partie réseau du client similaire à BitTorrent qui consiste à se connecter à d'autres clients et à s'échanger des données.

2  Description du protocole

Un fichier F est découpé en n blocs B1,…,Bn. Chaque bloc Bi est découpé en k nombres entiers positifs Bi=(xi1,…,xik). Pour effectuer les opérations de combinaison, on se donne un grand nombre premier q de plusieurs centaines de bits (chaque xij est supposé strictement inférieur à q). La source fournit des combinaisons aléatoires a1B1+⋯ +anBn=(a1x11+⋯ +anxn1, …,a1x1k+⋯ +anxnk) modq : les aj sont tirés aléatoirement et les opérations sont effectuées modulo q. (On travaille bien dans un corps puisque q est premier.) Les coefficients aj sont transmis en sus de la combinaison, induisant un certain sur-coût de communication.

2.1  Vérification des combinaisons

Une des difficulté du protocole réside dans la vérification de l'intégrité d'une combinaison : connaissant a1,… an, comment vérifier que la combinaison linéaire C=(y1,… yk) qu'on a reçu est bien égale à a1B1anBn ? (Il faut pouvoir se protéger d'un client malicieux ou bogué qui introduit de mauvaises combinaisons.) Pour cela, le protocole propose d'utiliser une fonction de hachage h homomorphe, c'est-à-dire telle que h(a1B1+⋯ +anBn) =h(B1)a1h(Bn)an. Ainsi la connaissance de h(B1),… ,h(Bn) suffit pour vérifier l'intégrité de toute combinaison.

On utilisera ainsi la fonction de hachage obtenue en trouvant deux nombre premiers p et q tels que p−1=dq (q divise p−1), en se donnant k nombres aléatoires g1,… ,gk et en posant :
h(y1,… yk) = g1dy1gkdykmodp
(On prendra pour les gi des nombres aléatoires de même longueur que p.) La propriété d'homomorphisme découle alors du petit théorème de Fermat qui stipule gp−1=1modp pour tous g≠ 0 et p est premier. Comme p et q sont choisis tels que p−1=dq, si yj=a1x1j+⋯ +anxnj modq, on a dyj=d(a1x1j+⋯ +anxnj) + m(p−1) pour un certain entier m. Comme gjm(p−1)=1modp, gjdyj=gjd(a1x1j+⋯ +anxnj) modp, soit gjdyj=(gjdx1j)a1⋯ (gjdxnj)anmodp. On a donc bien h(a1B1+⋯ +anBn) =h(B1)a1h(Bn)an modp.

La sécurité de cette fonction de hachage repose sur la difficulté de calculer des logarithmes discrets. Il est par exemple facile de produire de faux blocs d'empreinte donnée connaissant i,j,x,y tels que gidx = gjdy modp. Une description plus complète de la fonction de hachage ci-dessus est décrite dans [3].

2.2  Reconstruction du fichier

Le protocole repose sur le fait que n combinaisons aléatoires ont peu de chances d'être linéairement dépendantes. Étant donné n combinaisons C1,… Cn supposées indépendantes, on reconstruira le fichier en utilisant la méthode du pivot de Gauss. En effet, le problème revient à résoudre un système linaire du type AB=C : notons ai1,…,ain les coefficients de la combinaison Ci = ai1B1⋯ +ainBn = (yi1, … ,yin) modq. Chaque vecteur B'j=(x1j, …,xnj) est alors solution du système AB'j=C'j avec C'j=(y1j,… ,ynj).

Le pivot de Gauss consiste à se ramener à un système triangulaire. On utilise la première ligne L1 : a11x1j + ⋯ + a1nxnj = y1j pour éliminer la dépendance en x1j des autres lignes. Pour cela, il suffit de remplacer chaque ligne Li par Liai1/a11 L1. (a11 est appelé pivot.) On utilise ensuite la deuxième ligne pour éliminer la dépendance en x2j des lignes suivantes et ainsi de suite jusqu'à obtenir un système triangulaire qui devient facile à résoudre : la valeur xnj est donnée par la dernière ligne. En reportant cette valeur dans l'avant dernière ligne, on obtient alors xn−1j. En remontant ainsi de suite, on obtient xn−2j,… ,x1j.

Dans le cas où on tombe sur un pivot nul, il faut trouver une ligne qui fournit un pivot non nul et l'échanger avec la ligne courante. (On notera cependant, qu'une telle éventualité est peu probable.)

Pour obtenir le fichier entier, il faut effectuer les mêmes opérations sur les k systèmes linéaires AB'j=C'j, 1≤ jk.

3  Travail minimal

Le travail minimal consiste à programmer le codage et le décodage d'un fichier. Pour ces deux opérations, on utilisera un fichier de configuration config.txt contenant les nombres p,q,km,g1d,…,gkmd. Le fichier sera dans un format textuel et contiendra un nombre par ligne sous forme décimale dans l'ordre p,q,km,g1d,…,gkmd (km désigne le nombre maximal de nombres dans un bloc).




L'opération de codage qui prend en argument un nom de fichier de configuration, un nombre de blocs n et un nom f de fichier à coder considérera f comme une suite de n blocs B1,…, Bn de ⌈ s/n(lq−1) ⌉ nombres de lq−1 bits où s désigne la taille du fichier en bits et lq le nombre de bits de q. (Le dernier bloc sera éventuellement complété par des zéros.) Elle produira deux fichiers :


Le décodage consiste à reconstituer le fichier originel dans f.dec à partir du même fichier de configuration et des deux fichiers f.ava et f.dat codant un fichier. Un appel particulier permettra de plus de générer un fichier de configuration étant donné lp, lq, kmax. Fournir un programme permettant d'effectuer mot pour mot le test suivant sur un fichier f :
javac *.java
java Avalanche config 1024 256 3000 conf.txt
java Avalanche coder conf.txt 32 f 
java Avalanche decoder conf.txt f
diff f f.dec
Le premier paramètre indique l'opération à effectuer. L'opération config indique de construire un fichier de configuration conf.txt avec lp=1024, lq=256, km=3000. L'opération coder prend aussi en paramètre le nombre de blocs.

Il est important de respecter les formats de fichier spécifiés ci-dessus pour que votre programme puisse utiliser des fichiers de configuration, ou de codage qu'il n'a pas généré (comme les challenges proposés sur la page web de suivi).

L'opération de décodage produira une erreur si :


Pour calculer sur des grands entiers, on utilisera la classe BigInteger de java ou la librairie GMP [4] en C. Pour trouver deux nombres p et q de lp et lq bits respectivement tels que p=dq+1, on tire aléatoirement un nombre d pair de lplq bits et un nombre premier q de lq bits, et on construit p=dq+1. Il suffit de quelques centaines d'essais pour tomber sur un nombre p premier. (Les deux librairies précédentes fournissent des fonctions de génération de nombres premiers aléatoires et de test de primalité ainsi que toutes les fonctions nécessaires au calcul modulo un grand entier comme l'élévation d'un nombre à une puissance modulo p ou le calcul d'un inverse modulo q).




Une des difficultés du sujet consiste à rendre les opérations de codage, de décodage et de vérification rapides. Une étude des temps de codage et de décodage d'un fichier pour diverses valeurs de n et k sera grandement appréciée. On donnera par exemple le débit moyen de décodage (nombre de kbits de fichier décodés par seconde) d'un fichier de plusieurs méga-octets pour n=40. (On essaiera d'atteindre plus de 100 kbits/s.) Au besoin, on pourra augmenter l'espace mémoire utilisé par java avec l'option -Xmx de java (voir man java).

En particulier, on pourra utiliser l'optimisation suivante pour optimiser le temps de décodage (un temps de codage plus long est acceptable dans une application réelle). Pour n grand, le coût du pivot de Gauss devient prohibitif, on peut donc supposer k nettement plus grand que n. La vérification des combinaisons peut alors être optimisée en vérifiant plusieurs combinaisons d'un coup : il suffit pour cela de produire une combinaisons linéaire aléatoire de ces combinaisons et de la vérifier. Si cette combinaison de combinaisons est valide, elles le sont probablement toutes. Sinon, il faudra trouver la ou les combinaisons fautives. En supposant que peu de combinaisons sont corrompues, ce mécanisme peu accélérer grandement la vérification de l'intégrité des combinaisons.




Une autre difficulté réside dans la lecture et l'écriture au bit près dans un fichier. On fournira pour cela un module indépendant permettant de lire ou d'écrire dans un fichier une suite de nombres binaires de longueur en bits donnée. On utilisera de préférence une variable de type int pour stocker les bits intermédiaires qu'il faudra parfois stocker entre deux lectures ou deux écritures. Rappelons les opérations java pour manier les bits d'un int :

4  Extension réseau

L'extension proposée ici consiste à programmer le client permettant de s'échanger des combinaisons linéaires avec d'autres clients en s'inspirant du protocole BitTorrent[1]. Chaque client sera capable de jouer le rôle de « tracker » (qui consiste à donner l'adresse d'autres clients auxquels se connecter), de source (tout client qui possède le fichier en entier devient une source) et de téléchargeur.

Pour télécharger un fichier, un client doit posséder le fichier f.ava et connaître l'adresse d'une source ou d'un client qui connaît une source. Il se connecte à ce client ou à cette source qui lui renvoie d'autres adresses de clients à qui se connecter. Chaque client se connecte ainsi à plusieurs autres clients.

Chaque client propose à tout client auquel il est connecté une combinaison aléatoire de toutes les combinaisons qu'il possède (les combinaisons d'une source sont les blocs eux-mêmes). Il calcule les coefficients exprimant cette combinaison comme combinaison des blocs originaux et l'envoie. Si le destinataire constate que cela n'augmente pas le rang de la matrice des combinaisons qu'il possède déjà, il refuse l'échange.

Un client ne sert des données qu'à quatre des clients auxquels il est connecté par périodes de dix secondes. Un client envoie des données en priorité aux trois qui lui ont le plus donné durant la période précédente. Toutes les dix secondes, ce choix est réévalué. De plus le client sert en permanence un quatrième pair sélectionné au hasard toutes les trente secondes.

Les connections entre clients s'effectueront au moyen de sockets TCP (voir la classe java.net.Socket de java) qui permettent d'échanger des données par le réseau de manière similaire à l'écriture et à la lecture dans un fichier. Pour accepter plusieurs connections, chaque client se comportera comme un serveur (voir la classe java.net.Socket de java).

Références

[1]
B. Cohen. Incentives build robustness in BitTorrent. Workshop on Economics of Peer-to-Peer Systems, 2003. http://www.bittorrent.com/.

[2]
C. Gkantsidis, P. Rodriguez. Network coding for large scale content distribution. IEEE Infocom, 2005. http://research.microsoft.com/~pablo/avalanche.htm.

[3]
M. Krohn, M. Freedman, D. Mazieres. On-the-Fly Verification of Rateless Erasure Codes for Efficient Content Distribution. IEEE Symp. on Security and Privacy, 2004. http://www.scs.cs.nyu.edu/~dm/papers/krohn:authcodes.pdf

[4]
Gnu Multiple Precision Arithmetic Library. http://directory.fsf.org/gnump.html.

Ce document a été traduit de LATEX par HEVEA