squinnondation/Readme.tex

239 lines
15 KiB
TeX

\documentclass[a4paper,10pt,oneside]{report}
%\usepackage{concmath}
\usepackage[utf8]{inputenc} % codage latin1
\usepackage[T1]{fontenc} % codage des fonts
\usepackage[francais]{babel} % typographie et césures francaises
\usepackage{lastpage}
\usepackage{amsthm} %pour qed
\usepackage{amsmath} %pour \dfrac
\usepackage{amssymb,mathrsfs} % amsfonts
\usepackage{graphicx}
% \usepackage[colorlinks]{hyperref}
\usepackage{tikz} % L A TEX
\usepackage[margin=1.0cm,top=2.5cm]{geometry}
\usepackage{fancyhdr} % pour les en-têtes et les pieds de page : APRES LA GEOMETRIE DE LA PAGE
\pagestyle{fancy} % 6 par défaut 3 en tête et 3 pied
% \pagestyle{empty} % supprime les num\'eros de page qui sont automatiques
% \usepackage{xcolor}
% \definecolor{gris}{gray}{0.9}
\setlength{\parskip}{0.15cm} % hauteur entre chaque paragraphe
\setlength{\parindent}{0.0cm} % indentation de paragraphe
%*********************************************************************************
%********************************************************************************
% Macros
%********************************************************************************
%*********************************************************************************
\newcommand{\R}{{\mathbb R}}
\newcommand{\Q}{{\mathbb Q}}
\newcommand{\C}{{\mathbb C}}
\newcommand{\Z}{\mathbb Z}
\newcommand{\dd}{\displaystyle}
\newcommand{\Id}{\mathrm{[}\! \mathrm{[}}
\newcommand{\If}{\mathrm{]}\! \mathrm{]}}
\newcommand{\N}{{\mathbb N}}
\newcommand{\E}{{\mathbf E}}
\newcommand{\Hx}{{\mathbf H(X)}}
\newcommand{\Prob}{{\mathbb P}}
\newcommand{\V}{{\mathbf V}}
\newcommand{\X}{{\mathcal X}}
\newcommand{\be}{\begin{enumerate}}
\newcommand{\ee}{\end{enumerate}}
\newcommand{\bi}{\begin{itemize}}
\newcommand{\ei}{\end{itemize}}
\newcommand{\ba}{\begin{array}}
\newcommand{\ea}{\end{array}}
\newcommand{\ite}{\item[$\bullet$]}
\newcommand{\ssi}{\Longleftrightarrow}
\newcommand{\Card}{\mathop{ \mathrm{Card}}}
\everymath{\displaystyle} %comme son nom l'indique
%*********************************************************************************
%*********************************************************************************
% Document
%*********************************************************************************
%*********************************************************************************
% changement de la numerotation
\setcounter{secnumdepth}{5}
% \renewcommand{\thechapter}{\Alph{chapter}}
\renewcommand{\thechapter}{\arabic{chapter}}
% \renewcommand{\thesection}{\Roman{section})}
\renewcommand{\thesection}{ }
\renewcommand{\thesubsection}{\alph{subsection})}
\renewcommand{\thesubsubsection}{$\bullet$ }
% \renewcommand{\thesubsubsection}{\thesubsection\arabic{subsubsection}.}
\renewcommand{\theparagraph}{\roman{paragraph})}
\begin{document}
\setlength{\headheight}{13.0pt}
\fancyhead[L]{DEPRES Mathilde-D'ANELLO Yohann}
% \renewcommand{\headrulewidth}{0pt}
\fancyhead[C]{{\sf Projet réseau}}
\fancyhead[R]{page {\thepage} / \pageref{LastPage} }
% \pagestyle{empty} % 6 par défaut 3 en tête et 3 pied
\fancyfoot{}
\vspace{0.0cm}
\section{Introduction}
Notre projet est rédigé en Python, et fonctionne dans les versions plus récentes que Python 3.7. Il comprend une interface graphique, implémentée pour nous amuser et rendre les tests plus aisés.
L'interface graphique utilise le module curses de Python. Le module curses (et donc le projet en entier) ne fonctionne que si le projet est exécuté dans un terminal. Le projet supporte les encodages markdown (gras, italique, souligné ...) et l'utilisation d'emojis à partir du moment où le terminal les supporte.
\subsection{Lancer une instance du projet}
Pour lancer une instance du projet, il faut se placer dans le répertoire racine du projet, et exécuter
> ./main.py <adresse IPv6 ou IPv4 de l'hôte ou localhost pour lancer en local> <numéro du port à utiliser pour la connexion> [ options].
Les options sont:
\begin{itemize}
\item[$\bullet$] \textbf{- -client\_address <addresse IPv6 ou IPv4>} : pour spécifier l'adresse d'un premier voisin nécessaire à l'insertion du nouveau pair dans le réseau.
\item[$\bullet$] \textbf{- -client\_port <numéro de port>} : pour spécifier le port sur lequel écouter le premier voisin.
\item[$\bullet$] \textbf{-h, - -help} : pour obtenir l'aide
\item[$\bullet$] \textbf{-d, - -debug} : pour activer l'affichage des messages systèmes, par exemple lorsqu'on reconnait un nouveau voisin, ou qu'on reçoit certains TLVs.
\item[$\bullet$] \textbf{-mc, - -mulicast} : pour activer la découverte multicast sur le por 1212.
\item[$\bullet$] \textbf{-ne, - -no-emoji} : une option graphique si on ne veut pas afficher d'emoji.
\item[$\bullet$] \textbf{-nm, - -no-markdown} : une option graphique si on ne veut pas utiliser les encodages markdown.
\end{itemize}
\subsection{Utiliser une instance du projet}
Au lancement, l'instance demande à ce que l'utilisateur rentre un pseudo. Une fois le pseudo validé, on accède au projet en lui même.
Si le mode debug est activé, des lignes du type <system> ... apparaissent, il s'agit des messages de debug.
Sinon, les lignes sont du type <pseudo> ... . Si l'affichage est plein, on peut accéder à l'historique en appuyant sur les flèches.
Pour rentrer un message, il suffit d'écrire des lettres sur le clavier (mais pas de / ni de caractères trop particuliers). Le message apparait en bas de l'écran. Il est possible d'effacer ce qu'on a écrit si on s'est trompé. Pour envoyer, il suffit d'appuyer sur entrée.
On peut aussi accéder à des commandes spéciales. Pour cela, il faut taper '/', suivi du nom de la commande. Les commandes disponibles sont les suivantes :
\begin{itemize}
\item /help : pour voir la liste des commandes.
\item /connect <address> <port> : pour ajouter un nouveau voisin à notre instance. Il et enregistré comme un voisin potentiel.
\item /hello <address> <port> : pour envoyer un hello court à un voisin.
\item /unban <address> <port> : pour faire en sorte qu'un voisin ne soit plus banni
\item /info <id> ou <pseudo> ou <address> <port> : pour afficher des informations (id, pseudo, adresse et port) sur un voisin.
\item /active : affiche la liste des voisins actifs.
\item /potential :affiche la liste des voisins potentiels.
\item /debug : met/enlève le mode debug.
\item /emojis : met/enlève le mode emoji.
\item /markdown : met/enlève le mode markdown.
\end{itemize}
\subsection{Architecture du projet}
Le projet consiste en 4 fichiers de code, et un fichier main.py, qui permet de lancer une instance du projet.
Le fichier term\_manager.py permet d'initialiser l'utilisation du terminal par curses, il est sans grand intérêt pour la partie réseau du projet.
Le fichier squinnondation.py contient le parseur d'arguments qu'on utilise pour récupérer les adresses de l'hôte et du premier voisin donnés par l'utilisateur. Il se conclut par le lancement de plusieurs threads qui constituent le client MIRC en lui-même, et dont les classes sont définis dans les deux derniers fichiers.
Le fichier messages.py contient les définitions de tout les TLVs, qui sont définis comme des classes python.
Le fichier peers.py contient les définitions de la classe des pairs, la classe de l'hôte ainsi que les classes du listener, du listener multicast, du gérant des voisins et de l'inondateur. Ils contient aussi l'actualisation de l'affichage.
\section{Choix techniques}
\textbf{Remarque :} Notre projet utilise 5 fils concurrents car il nous a semblé que c'était une manière propre de gérer les actions qui doivent arriver à certains intervalles de temps (envoi de HelloTLV, ...). On a essayé de protéger les accès mémoire via des spinlocks, mais on a rencontré plusieurs problèmes de bloquage en continu des accès, du coup il est possible que certaines fonctions ne soient pas protégées comme elles le devraient. Afin d'éviter des bloquages infinis, chaque verrou expire au bout d'une seconde.
\subsection{Gestion des TLVs}
La classe \textbf{TLV} représente l'abstraction d'un TLV. Elle est sous-classée en chacun des types individuels de TLV (Pad1TLV, PadNTLV, ...). Chaque classe de TLV est équipée d'une fonction marshall qui transforme un objet de la classe en un tableau d'octets prêt à être envoyé, et d'une fonction unmarshall, qui transforme des octets en un objet de la classe.
Chaque classe de TLV possède également une fonction construct, qui permet au programme de construire un objet de la classe, et d'une fonction handle, qui indiquee ce qui doit être fait quand ce type de TLV est reçu. Pour des raisons de sécurité, certaines classes sont équipées d'une fonction validate\_data, qui s'assure que certaines propriétés du TLV concordent, par exemple sa longueur annoncée et sa longueur réelle, et qui lancent une erreur si ça n'est pas le cas. Cela permet en particulier d'indentifier des pairs malicieux qui envoient des TLVs malformés.
Les clients sont autorisés à laisser un dernier octet à 0 dans un message de données à par sécurité. Dans ce cas, le zéro ajouté est retiré à la lecture.
Les messages physiques sont représentés par la classe Packet, qui pourrait permettre l'agrégation de TLVs, bien qu'on ne l'ait pas implémentée.
Le fichier peer.py contient une classe Message qui est une classe théorique.
\subsection{Inondation}
Les messages récents sont placés dans un dictionnaire indexé par les paires (Id de l'émetteur, nonce). On stocke un paquet construit à partir du DataTLV contenant le message prêt à être envoyé, l'âge du message, pour le supprimer lorsqu'il devient trop vieux, on a fixé l'âge maximal d'un message à 2 minutes; et un dictionnaire d contenant les pairs à inonder, indexé par (adresse IP, port).
Le dictionnaire contient l'objet Peer du pair, la date du prochain envoi et le nombre d'envois déjà réalisé dans une liste. Lorsque la date du prochain envoi est dépassée, on envoie le message, on incrémente le nombre d'envoi, et la date du prochain envoi est calculée en faisant appel à l'aléatoire.
L'inondation est effectuée dans un thread Inondator dédié.
Les nonce sont implémentés par un compteur séquentiel propre à chaque pair.
\subsection{Gestion des voisins}
Un voisin est un objet de la classe Peer. Les voisins de l'instance utilisateur sont stockés dans le dictionnaire neighbours, qui est une propriété de la classe User.
Un voisin actif est un voisin dont la propriété active est Vrai. La liste des voisins actifs est recalculée à la volée lorsque c'est nécessaire.
Certains voisins sont bannis (l'instance utilisateur est bannie à l'initialisation), parce qu'ils ont commis trop d'infractions aux protocole. L'instance utilisateur ignore les messages des pairs bannis.
Un voisin potentiel est un voisin qui n'est pas actif et qui n'est pas banni.
La gestion des voisins est effectuée dans un thread PeerManager dédié, qui vérifie régulièrement si les voisins sont symétriques, envoie des HelloTLV longs aux voisins actifs et des HelloTLVs court à des voisins potentiels si c'est nécessaire. Toutes les minutes, on envoie à chaque voisin P une liste des voisins symétriques. P n'est jamais dans la liste, bien que ça aurait fortement simplifié le code, puisqu'on aurait pu envoyer le même message constitué de NeighbourTLVs agrégés à chaque voisin.
\subsection{Interfaces réseau}
Le projet utilise une socket qui est bind sur le port indiqué au lancement. Puisque le processus de réception sur un socket est bloquant, on utilise un listener, qui tourne dans un Thread dédié. Le listener écoute sur le socket, récupère les paquets et les traite.
Il est possible de communiquer en IPv4, en les traitant comme des IPv6 en préfixant l'adresse IPv4 par \texttt{::ffff:} (64 zéros et 32 uns). Une adresse donnée est d'abord résolue en IPv6, et si cela ne fonctionne pas en IPv4 en ajoutant ce préfixe.
\subsection{Extensions}
Notre projet supporte de réaliser plusieurs inondations à la fois (la manière dont l'inondation est codée le supportait nativement), il supporte l'adressage multiple des pairs, il a une relativement bonne sécurité, et il permet la découverte par multicast.
\subsubsection{Adressage multiple}
Pour supporter les adresses multiples, la classe Peer est équipée d'un objet addresses de type set(). Tout pair a une adresse principale, que ses voisins utilisent prioritairement pour communiquer avec lui.
Lorsqu'un pair P contacte l'instance utilisateur avec une nouvelle adresse, on vérifie qu'on a pas déjà un pair Q, soit avec la même adresse, soit avec le même identifiant. Si on en a, on fusionne P et Q, c'est à dire qu'on ajoute à la liste des adresses connues de Q l'adresse de P, et qu'on met à jour les informations sur Q. Par exemple si Q a quitté et rejoint le canal de discussion, on remet à jour son identifiant, qui a changé, et son pseudo.
L'instance utilisateur est voisine (pas actif) d'elle-même dès le début, et ignore ses propres messages. Ceci nous évite qu'elle se découvre lui-même en multicast, où que d'autre pairs lui envoient elle-même. Une instance de notre projet n'envoie jamais lui-même à un pair, sauf si il a plusieurs adresses et qu'on ne sait pas encore qu'il s'agit du même pair, mais d'autres instances pourraient ne pas le faire.
\subsubsection{Sécurité}
Lorsqu'un message est trop long, la partie supplémentaire est ignorée, de même pour les TLVs, on suppose qu'ils font la taille annoncée (ou prévue par le protocole). Dans certains cas où le message/les TLVs sont mals construits, le code lève une exception. On compte le nombre d'exception levé par chaque voisin, et si ce nombre dépasse 5, le pair est banni, on ignore tous ses messages.
Notre implémentation refuse les messages des pairs qui ne se sont pas déjà annoncés par un Hello.
\subsubsection{Multicast}
Si l'option -mc est activée au démarrage, l'hôte bind une autre socket en mode multicast IPV6 sur le groupe ff02::4242:4242 et le port 1212. Il envoie toutes les minutes un Hello court via ce socket, en multicast sur le groupe.
Lorsqu'un tel message Hello court est reçu par un pair, il traite le Hello comme il le ferait normalement : il vérifie qu'il n'a jamais vu la personne qui lui a parlé, puis l'ajoute à sa liste de voisins actifs et lui envoie un message Hello long. Puisqu'il est impossible que ce pair identifie le port principal de l'hote, le message Hello long est envoyé sur le port 1212. Cependant l'hote va répondre avec son port principal, et grâce à notre système d'adressage multiple, le pair sait qu'il s'agit de la même personne.
Pour éviter que d'autres messages soient envoyés sur le port 1212, une adresse avec un pair différent de 1212 est toujours préférée pour être l'adresse principale d'un pair.
Nous avons testé le multicast en réseau local ethernet, et il semble y avoir quelques artefacts, mais nous avons réussi à faire se connecter deux pairs qui originellement ne se connaissaient pas.
\end{document}