{% extends "_layout_fr.html" %} {% block title %}À faire / Sur le feu{% endblock %} {% block content %} Traduction de février 2011. Version anglaise actuelle
Vous trouverez ci-dessous une exposition plus détaillée (bien que toujours incomplète) des principales zones du développement du cœur du réseau I2P, embrassant les nouvelles versions éventuellement planifiées. Ceci n'inclut ni le transport stéganographique, ni les outils de sécurisation de la machine locale et l'adaptation aux périphériques radio (WiFi), pas plus que les applications clientes qui sont tous essentiels pour le succès du réseau I2P. Il y a probablement d'autres choses qui viendront, particulièrement quand I2P sera mieux testé, mais il s'agit là des principaux "gros morceaux". Voir aussi la feuille de route. Vous voulez nous aider? Engagez-vous!
La fonctionnalité permettant aux routeurs de participer pleinement aux réseau même lorsqu'ils se trouvent derrière un pare-feu ou un traducteur d'adresses réseau (NAT) hors de contrôle nécessite quelques opérations avec une route réservée (car ces pairs ne peuvent pas recevoir de connexions entrantes). Pour y parvenir, il y a deux façons de considérer les pairs:
Pour ce faire, les pairs privés se connectent simplement à quelques autres, créent un tunnel à travers chacun d'eux, et en publient une référence dans leur structure RouterInfo de la base de donnée du réseau.
Lorsque quelqu'un souhaite joindre un routeur particulier, il doit d'abord obtenir sa "RouterInfo" à partir de la base de donnée, et il saura s'il peut se connecter directement (cas du pair cible public) ou indirectement. Les connexions directes se passent normalement, alors que les indirectes se font via les tunnels publiés.
Quand un routeur veut seulement envoyer un ou deux messages à un pair caché, il peut simplement utiliser le tunnel indirect publié pour envoyer les données utiles. Cependant, si le routeur doit converser souvent avec le pair privé (par exemple en tant que participant à un tunnel), il doit envoyer un message "routé-à-la-garlic" à travers le tunnel indirect au pair caché, qui le déballe pour y trouver... un message destiné au routeur originaire. Le pair caché établit alors une connexion sortante au routeur originaire et à partir de là ces deux routeurs peuvent converser directement sur cette nouvelle connexion.
Ce scénario ne peut bien sûr fonctionner que si le pair originaire peut recevoir des connexions (c'est-à-dire qu'il ne soit pas lui-même caché). Cependant, s'il est caché lui aussi, il peut indiquer dans le message initial de revenir par son propre tunnel d'entrée.
Ceci n'est pas destiné à fournir aux pairs un moyen de dissimuler leur adresse IP, mais plutôt pour permettre à ceux opérant derrière un pare-feu ou un NAT de participer normalement au réseau. La dissimulation d'adresse IP demande plus de travail, comme décrit plus bas.
Avec cette méthode, n'importe quel routeur peut participer à tout rôle dans un tunnel. Dans un but d'efficacité, opérer en pair caché est un mauvais choix pour officier en tant que passerelle d'entrée, et dans un tunnel donné, deux pairs voisins ne devraient pas être cachés. Mais ça n'est pas indispensable.
La communication TCP standard dans Java nécessite habituellement des appels de socket bloquants, et pour pour empêcher un socket de bloquer le bloquer tout le système, ces appels sont faits dans leur propres tâches. Notre transport TCP actuel est implémenté de façon naïve - pour chaque pair en relation, nous avons une tâche de lecture et une tâche d'écriture. Celle de lecture boucle simplement sur un paquet d'appels read(), construit les messages I2NP et les ajoute à notre file d'attente interne entrante. Il y a une file d'attente de messages sortants par connexion. La tâche d'écriture en extrait les messages et passe les données à travers les appels write().
Nous effectuons ceci assez efficacement d'un point de vue charge UC - à tout moment, presque toutes ces tâches sont au repos, bloquées dans l'attente de grain à moudre. Cependant, chacune d'entre elles consomme des ressources réelles (par exemple sur de très vieux noyaux Linux, chacune sera souvent mise en œuvre en tant que processus fork()'ed). À mesure de la croissance du réseau, le nombre de pairs auxquels chaque routeur voudra s'adresser augmentera (souvenez-vous, I2P est entièrement connecté, c'est-à-dire que chaque pair doit savoir comment passer des messages à tous les autres. Le support des routes réservées ne réduira probablement pas sensiblement le nombre de connexions nécessaires). Ce qui veut dire que dans un réseau de 100000 routeurs, chaque routeur aura jusqu'à 199998 tâches rien que pour s'occuper des connexions TCP!
Évidement, ça ne marchera pas. Nous avons besoin d'une couche transport adaptative. Dans Java, nous disposons de deux approches principales:
L'envoi et la réception de datagrammes UDP sont des opérations sans connexion - si on communique avec 100000 pairs, on colle simplement les paquets UDP dans une file d'attente et on fait avec une seule tâche pour les extraire et les pousser dans le tuyau (et pour recevoir, une seule tâche en tire les paquets UDP reçus et les ajoute dans une file d'entrée).
L'ennui, c'est que l'utilisation de UDP fait perdre les avantages de TCP (réordonnancement, gestion de la congestion, découverte du MTU, etc). Mettre ce code en œuvre demandera un travail important, mais I2P n'a pas besoin d'être aussi solide que TCP. Précisément, lorsque j'ai fait des mesures au simulateur et en réel sur Internet, la plupart des messages transférés tenaient facilement dans un seul paquet UDP non fragmenté, et les plus gros messages prenaient 20 à 30 paquets. Comme mule le mit en évidence, TCP ajoute une surcharge certaine quand il s'agit de gérer autant de petits paquets, vu que les ACKs sont du même ordre de grandeur. Avec UDP, nous pouvons optimiser le transport tant pour l'efficacité que pour la souplesse en prenant en compte les besoins spécifiques d'I2P.
Ça ne va quand-même pas être un travail énorme.
Java 1.4 propose un jeu de paquetage "New I/O", permettant aux développeurs de tirer avantage des possibilités d'E/S non bloquantes du système d'exploitation - vous pouvez maintenir un grand nombre d'opérations d'E/S simultanées sans avoir besoin de définir une tâche dédiée pour chacune. Cette approche est très prometteuse, car nous pouvons utiliser un grand nombre de connexions simultanées et nous n'avons pas besoin d'écrire une mini-pile TCP pour UDP. Cependant, selon les développeurs de Freenet les paquetages NIO n'ont passé l'épreuve du feu. De plus, le recours aux NIO impliquerait une incompatibilité avec les JVM open-sources telles que Kaffe, car GNU/Classpath ne les prend en charge que partiellement. (note: ça ne devrait pas durer car il y a eu quelques progrès dans la la Classpath des NIO, bien qu'en quantité inconnue)
Dans la même veine, il y a l'alternative du paquetage Non blocking IO - essentiellement une implémentation NIO de salle blanche (écrite avant la parution des NIO). Il utilise du code natif du SE pour faire les E/S non bloquantes, en présentant les évènements via Java. Il semble fonctionner avec Kaffe, mais il a peu d'activité de développement à son sujet (probablement à cause de la parution des NIO de Java 1.4).
Dans l'implémentation actuelle de la base de donnée réseau et de la gestion de profil, on s'est permis quelques raccourcis pratiques. Par exemple, nous n'avons pas de code pour nettoyer les références aux pairs dans les K-buckets, vu que nous n'avons pas assez de pairs pour avoir ne serait-ce qu'une petite chance d'en remplir une. Alors on garde les pairs dans n'importe quelle liste appropriée. Un autre exemple avec les profils de pairs: la quantité de mémoire nécessaire pour maintenir chaque profil est suffisamment faible pour que nous puissions garder des milliers de profils bien détaillés sans aucun problème. Comme nous avons la possibilité d'utiliser des profils réduits (dont nous pouvons maintenir des centaines de milliers en mémoire), nous n'avons pas non-plus le code qui ferait la conversion de détaillé en réduit et vice-versa, ni même la purge des deux. Ça ne serait pertinent d'écrire ce code maintenant car nous ne pas sommes pas près d'en avoir besoin.
Ceci dit, nous devons quand même garder ça en tête car la taille du réseau grandit. Il restera du travail, mais on peut le remettre à plus tard.
À l'heure actuelle, quand Alice initie un tunnel entrant à quatre sauts commençant par Elvis, puis passant par Dave, puis Charlie,puis Bob, et se terminant chez elle, Alice (A<--B<--C<--D<--E), tous les cinq sont au fait qu'ils participent au tunnel "123", car les messages en font état (TunnelID commun). Nous voulons donner à chaque saut un identifiant unique de saut de tunnel: Charlie recevra des messages sur le tunnel 234 et les transmettra à Bob par le tunnel 876. Le but est d'empêcher Bob ou Charlie de savoir qu'ils participent au tunnel d'Alice, car quand chaque saut a le même tunnelID pour un tunnel donné, les attaques par coalition sont simples à mettre en œuvre.
L'ajout d'un TunnelID unique par saut n'est pas difficile, mais c'est insuffisant: Si Dave et Bob sont contrôlés par le même attaquant, ils ne pourraient plus identifier un tunnel par leur participation commune via l'information TunnelID, mais seraient quand-même capable de le faire par simple comparaison des corps de messages et des structures de vérification. Pour l'empêcher, le tunnel doit appliquer un cryptage étagé tout au long du chemin, à la fois sur la charge utile et les structures de vérification (utilisées pour empêcher les attaques par marquages). On a besoin de modifications simples du TunnelMessage, et aussi d'inclure par saut, des clés secrètes générées pendant la création du tunnel et passées à la passerelle du tunnel. Nous devons définir une longueur maximale de tunnel (p.e. 16 sauts) et indiquer à la passerelle de chiffrer le message pour chacune des 16 clés, en ordre inverse, et de crypter la signature du hachage de la charge utile (cryptée) à chaque étape. La passerelle envoie alors ce message au premier saut chiffré 16 fois, accompagné d'un plan à 16 entrée chiffré 16 fois. Le premier saut déchiffre le plan et la charge utile avec leur clef secrète pour chercher dans le plan l'entrée associée à son propre saut (indiquée par le tunnelID unique par saut) et pour vérifier la charge utile en la confrontant au hachage signé associé.
La passerelle dispose encore de plus d'informations que les autre pairs, et sa compromission avec un participant leur permettrait d'identifier leur participation à un tunnel donné. De toute façon, les pairs voisins savent qu'ils participent au même tunnel, car ils savent à qui ils envoient un message (et avec les transports IP sans routes réservées, ils savent aussi de qui ils reçoivent). Malgré tout, les deux techniques ci-dessus augmentent très sensiblement le coût d'obtention d'échantillons signifiants dans des tunnels très longs.
Comme Connelly a proposé de s'occuper du problème de l'attaque par prédécesseur (mise à jour 2008), conserver l'ordre des pairs au sein d'un tunnel (autrement dit, chaque fois qu'Alice crée un tunnel à l'aide de Bob et de Charlie,le saut suivant Bob sera toujours Charlie), nous en sommes protégés car Bob ne peut pas obtenir une connaissance substantielle du groupe de sélection de pairs d'Alice. Nous pourrions même restreindre le mode de participation de Bob à seulement recevoir de Dave et envoyer à Charlie - et l'un d'entre-eux n'est pas disponible (surcharge, déconnexion, etc...), éviter de demander à Bob de participer à un tunnel tant qu'ils ne sont pas de nouveau disponibles.
Une analyse plus poussée est nécessaire pour repenser la création du tunnel: pour l'instant, nous piochons et ordonnons aléatoirement le premier tiers des pairs (ceux qui ont des capacités élevées et rapides).
L'ajout d'un ordre strict des pairs dans un tunnel améliore aussi l'anonymat des pairs des tunnels à zéro saut, car sinon, le fait que la passerelle d'un pair ait toujours la même passerelle serait rédhibitoire. Cependant, les pairs avec un tunnel à zéro saut pourraient de temps en temps utiliser un tunnel à un saut pour simuler la défaillance du pair passerelle habituellement fiable (donc toutes les MTBF*(durée du tunnel)minutes, utiliser un tunnel à un saut).
Sans la permutation de longueur de tunnel, s'il advenait que quelqu'un puisse de quelque façon détecter qu'une destination se trouve à un certain nombre de sauts, il pourrait mettre à profit cette information pour identifier le routeur se trouvant à cette destination, par l'attaque du prédécesseur. Par exemple, si tout le monde a des tunnels à deux saut, si Bob reçois un message de tunnel de Charlie et le transfère à Alice, Bob déduit qu'Alice est le routeur final du tunnel. Si Bob pouvait identifier la destination à laquelle mène ce tunnel (au moyen d'une collusion avec la passerelle et en collectant tous les baux de la base de donnée du réseau), il trouverait le routeur hébergeant la destination (et sans les routes réservées, ça indiquerait l'adresse IP de la destination).
C'est pour contrer ce comportement que les longueurs de tunnel doivent être permutées, en se servant d'algorithmes basés sur la longueur demandée (par exemple, le changement de longueur de 1/MTBF pour les tunnels à zéro saut passés en revue ci-dessus).
La fonctionnalité de route réservée décrite précédemment était simplement un défaut de fonctionnement: comment laisser des pairs communiquer, qui sans cela ne le pourraient pas. Cependant, l'idée d'autoriser des routes réservées apporte de nouvelles possibilités. Par exemple, si un routeur ne peut absolument pas prendre le risque de communiquer directement avec des pairs sans confiance pré-établie, il peut monter des liens de confiance à travers eux, pour les utiliser pour à la fois envoyer et recevoir tous ses messages. Ces pairs cachés qui souhaitent rester complètement isolés devraient aussi refuser de se connecter aux pairs qui les y invitent (comme démontré dans le schéma de la technique de routage en tête d'ail "garlic routing" exposée précédemment) - ils peuvent simplement prendre la gousse ("clove") qui présente une requête d'envoi à un pair particulier, et router le message dans un tunnel vers un des liens de confiance du pair caché, avec les instructions pour le faire suivre comme demandé.
Dans le réseau, nous avons besoin de dissuader les gens de consommer de trop nombreuses ressources ou de créer trop de pairs dans le but de lancer une attaque de Sibylle. Les techniques traditionnelles telles permettre à un pair de voir qui demande une ressource ou est un autre pair ne sont pas pertinentes dans I2P, car cela compromettrait l'anonymat du système. Nous nous tournons vers la méthode consistant à rendre certaines requêtes coûteuses.
Hashcash est une technique que nous pouvons utiliser pour anonymement accroitre le coût de certaines activités, telles que créer une nouvelle identité de routeur (faite une seule fois pendant l'installation), créer une nouvelle destination (faite une seule fois à la création d'un service), ou demander qu'un pair participe à un tunnel (faite souvent, peut-être 200 à 300 fois par heure). Nous ne connaissons encore pas le coût "correct" de chaque type de certificat, mais avec un peu de recherche et d'expérimentation, nous devrions pouvoir élaborer un "tarif" dissuasif tout en restant une charge abordable pour ceux dotés de peu de ressources.
Il y a peu d'autres algorithmes à explorer pour enlever la gratuité des ces requêtes, et des recherches plus approfondies dans ce domaine sont souhaitables.
Du point de vue d'observateurs passifs externes et puissants comme d'une grande coalition interne, le routage en tunnel standard est vulnérable aux attaques par analyse de trafic (à simplement regarder la taille et la fréquence des messages passant entre les routeurs). Pour nous en prémunir, nous modifions principalement quelques uns des tunnels au niveau de leurs propres éléments constituants: retarder les messages reçus sur la passerelle et les transférer regroupés, les réordonner si nécessaire, et injecter des messages fictifs (indiscernables des autre "vrais" messages de tunnel par les pairs du chemin). Il y a eu sur ces algorithmes un gros travail de recherche sur lequel nous pouvons nous appuyer pour commencer à implémenter les diverses de mélange de tunnel.
En plus des aspects d'anonymat du mode opératoire plus varié des tunnels, il y a aussi une dimension fonctionnelle. Chaque pair n'a qu'une partie des données qui sont destinées au réseau, et pour éviter qu'un tunnel donné ne consomme trop de bande passante, il peut y introduire quelques freins. Par exemple, un tunnel peut être configurer pour ralentir après avoir passé 600 messages (1 par seconde), 2,4Mo (4kb/s), ou dépasser quelque peu la moyenne mobile (8kb/s pendant dernière minute). Les messages en excès peuvent se voir retardés ou brutalement ignorés. Avec ce genre de régulation, les pairs peuvent offrir une QoS de genre ATM à leurs tunnels, en refusant d'allouer plus de bande passante qu'ils n'en disposent.
En complément, nous pourrions implémenter du code destiné à re-router dynamiquement des tunnels pour éviter les pairs défaillants ou pour introduire des sauts supplémentaires dans le chemin. Ceci peut être réalisé en envoyant un message routé "à la garlic" à un pair particulier du tunnel pour lui fournir les instructions destinées à redéfinir le saut suivant du tunnel.
Au-delà de la stratégie de regroupement et de mélange par tunnel, il y a d'autres moyens de se protéger des attaques des attaquants puissants, comme permettre à chaque saut dans un chemin routé en tête d'ail de définir un délai ou un intervalle au bout ou pendant lequel il devrait être transféré. Ceci protègerait des attaques contre les intersections de longue durée, car un pair pourrait envoyer un message qui paraitrait parfaitement standard à la plupart des pairs qui le font suivre, sauf à ceux où la gousse exposée inclus des instructions de délai.
Les améliorations de performances sont répertoriées sur une page dédiée aux performances.
Traduction un poil laborieuse de fin février 2011. Toute amélioration est bienvenue. magma {% endblock %}