Compare commits

...

2 commits

Author SHA1 Message Date
9104f40aaf Conform to datId = senderId 2016-11-27 13:26:27 +01:00
6d3774bc99 Update readme, start report 2016-11-27 13:07:01 +01:00
8 changed files with 193 additions and 37 deletions

View file

@ -6,7 +6,7 @@ Projet de cours "réseau", M1 <https://tobast.fr/m1/nw-project.pdf>
* `g++` (version supportant c++14) ou tout autre compilateur c++ (éditer le
Makefile pour changer `CXX`)
* Bibliothèques standard POSIX
* pthread
## Compiler
@ -23,13 +23,11 @@ arguments.
* `bootstrap [ID du nœud] [adresse IPv6 du nœud] [port]` : déclare le nœud
comme nœud de bootstrap. L'adresse peut être IPv4-mapped, eg.
`::FFFF:42.42.42.42`.
* `data [id de donnée] [donnée]` : déclare une donnée à propager. Si l'id est
0, il sera tiré au hasard puis enregistré. La donnée peut contenir des
* `data [donnée]` : déclare une donnée à propager. La donnée peut contenir des
espaces, et s'étend jusqu'à la fin de la ligne.
Le programme, à l'initialisation, lit le fichier puis le réécrit avec
éventuellement des données tirées au hasard si nécessaire (eg. ID du nœud, des
données).
éventuellement des données tirées au hasard si nécessaire (eg. ID du nœud).
Le programme produit des logs verbeux mais humainement lisibles sur sa sortie
d'erreur (stderr).

View file

@ -10,7 +10,7 @@
#include <cstdlib>
using namespace std;
ConfigFile::ConfigFile() {
ConfigFile::ConfigFile() : curDataSum(0) {
selfId = randId();
}
@ -48,11 +48,6 @@ bool ConfigFile::read(const char* path) {
bootstrapNodes.push_back(Neighbour(id, addr));
}
else if(attr == "data") {
u64 id;
handle >> hex >> id >> dec;
if(id == 0)
id = randId();
string dataStr;
getline(handle, dataStr);
@ -63,7 +58,16 @@ bool ConfigFile::read(const char* path) {
if(nonWSPos == dataStr.size())
continue;
data.push_back(make_pair(id, dataStr.substr(nonWSPos)));
std::string realDat = dataStr.substr(nonWSPos);
if(curDataSum + realDat.size() + 12 + 2 > 0xff) {
fprintf(stderr, "Too much data, won't fit. Discarding `%s`\n",
realDat.c_str());
continue;
}
curDataSum += realDat.size() + 2;
data.push_back(realDat);
}
else if(attr.empty())
continue;
@ -96,8 +100,7 @@ bool ConfigFile::write(const char* path) {
}
for(auto dat : data) {
handle << "data " << hex << dat.first << dec << " "
<< dat.second << '\n';
handle << "data " << dat << '\n';
}
handle.close();

View file

@ -12,6 +12,7 @@
class ConfigFile {
public:
typedef std::string CfgData;
ConfigFile();
bool read(const char* path);
@ -26,7 +27,7 @@ class ConfigFile {
};
/** Bootstrap nodes. No setter: wouldn't be useful. */
const std::vector<std::pair<u64, std::string> >& getData() const {
const std::vector<CfgData>& getData() const {
return data;
}
/** Data to publish. */
@ -36,6 +37,7 @@ class ConfigFile {
u64 selfId;
std::vector<Neighbour> bootstrapNodes;
std::vector<std::pair<u64, std::string> > data;
std::vector<CfgData> data;
size_t curDataSum;
};

View file

@ -85,22 +85,31 @@ void DataStore::setFlooded(u64 id) {
void DataStore::dump() {
for(auto it=data.begin(); it != data.end(); ++it) {
printf(">> DATA %lX (%u) ", it->first, curSeqno[it->first]);
Bytes dat = it->second.data;
if(dat.size() < 2) {
printf("INVALID\n");
continue;
while(dat.size() >= 2) {
printf(">> DATA %lX (%u) ", it->first, curSeqno[it->first]);
if(dat.size() < 2) {
printf("INVALID\n");
continue;
}
u8 type, len;
dat >> type >> len;
if(dat.size() < len) {
printf("INVALID\n");
continue;
}
if(type == csts::TLV_DATA_TEXT) {
string val;
for(int i=0; i < len; i++) {
u8 c;
dat >> c;
val += c;
}
printf("'%s'\n", val.c_str());
}
else
printf("type=%d\n", type);
}
u8 type, len;
dat >> type >> len;
if(type == csts::TLV_DATA_TEXT) {
char val[1024];
dat.writeToBuffer(val, 1023);
val[min((int)dat.size(), 1023)] = '\0';
printf("'%s'\n", val);
}
else
printf("type=%d\n", type);
}
}

View file

@ -70,15 +70,15 @@ int main(int argc, char** argv) {
Protocol proto(addr, cfg.getSelfId());
DataStore dataStore(&proto);
Bytes selfData;
for(auto dat : cfg.getData()) {
Bytes pck;
pck << csts::TLV_DATA_TEXT << (u8)dat.second.size();
for(u8 chr : dat.second)
pck << chr;
dataStore.addData(pck, time(NULL), dat.first, true);
fprintf(stderr, "[INFO] Adding data `%s` (%lX)\n",
dat.second.c_str(), dat.first);
selfData << csts::TLV_DATA_TEXT << (u8)dat.size();
for(u8 chr : dat)
selfData << chr;
fprintf(stderr, "[INFO] Adding data `%s`\n",
dat.c_str());
}
dataStore.addData(selfData, time(NULL), cfg.getSelfId(), true);
Neighbours neighboursManager(&proto, &dataStore);

3
report/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.aux
*.log
*.pdf

6
report/Makefile Normal file
View file

@ -0,0 +1,6 @@
TEX=report.tex
all: $(TEX)
pdflatex $(TEX)
pdflatex $(TEX)

135
report/report.tex Normal file
View file

@ -0,0 +1,135 @@
% vim: spelllang=fr
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[francais]{babel} %% FRENCH, FIXME if typing in english
\usepackage[T1]{fontenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{graphicx}
\usepackage[left=2cm,right=2cm,top=2cm,bottom=2cm]{geometry}
% Custom packages
\usepackage{my_listings}
\usepackage{math}
\author{Théophile \textsc{Bastian}}
\title{Rapport~: projet réseau}
\date{\today}
\begin{document}
\maketitle
\begin{abstract}
L'implémentation du protocole en C++ respecte la spécification sur tous les
cas observés. De plus, les paquets sont agrégés tant que possible pour
éviter une surcharge du réseau, l'inondation est un peu optimisée et
n'interrompt pas le reste du protocole et le programme affiche ses
informations dans la console sur demande. Toutefois, il n'est pas possible
de modifier dynamiquement les données propagées.
Le support de l'IPv4 et de l'IPv6 à la fois devrait être possible, mais n'a
pas pu être testé.
\end{abstract}
\section{Utilisation}
\subsection{Dépendances}
Seuls un compilateur C++ supportant C++14 et la bibliothèque pthread sont
nécessaires au fonctionnement du programme sur un environnement POSIX standard.
\subsection{Compilation}
\lstbash{make} suffit à compiler. Il est éventuellement possible d'utiliser un
autre compilateur C++ en éditant le \texttt{Makefile}.
\subsection{Utilisation}
Le programme fourni prend en argument le chemin vers un fichier de
configuration, dont chaque ligne commence par un mot-clé suivi de ses
arguments.
\begin{itemize}
\item \lstbash{id [ID du programme]}~: laisser vide par défaut, sera généré
automatiquement.
\item \lstbash{bootstrap [ID du nœud] [adresse IPv6 du nœud] [port]}~:
déclare le nœud comme nœud de bootstrap. L'adresse peut être
IPv4-mapped, eg. `::FFFF:42.42.42.42`.
\item \lstbash{data [donnée]}~: déclare une donnée à propager. La donnée
peut contenir des espaces, et s'étend jusqu'à la fin de la ligne.
\end{itemize}
Le programme, à l'initialisation, lit le fichier puis le réécrit avec
éventuellement des données tirées au hasard si nécessaire (eg. ID du nœud).
\subsection{Entrée/sortie}
Le programme produit des logs verbeux mais humainement lisibles sur sa sortie
d'erreur (stderr).
Le programme affiche son état actuel (voisins + infos sur eux, données + infos
sur elles) lors d'un appui sur \textsc{return}.
%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Implémentation}
Tout d'abord, mon code est très --- trop --- long pour le programme demandé.
J'ai manqué de temps pour ce projet (beaucoup de choses à faire en parallèle)
et j'ai donc plus produit du code rapidement qu'intelligemment\ldots
\subsection{Threads}
Le programme contient deux threads~:
\begin{itemize}
\item thread principal, gérant la plupart des choses~;
\item thread de lecture des paquets, réceptionnant les paquets et les
stockant dans une file pour que le thread principal puisse les
analyser.
\end{itemize}
Des mutexes sont utilisées quand nécessaire (accès à la file, en particulier).
\subsection{Lecture de l'entrée standard}
Pour éviter d'être bloquant sur la lecture de l'entrée standard, j'utilise dans
\texttt{main.cpp} \lstc{select} pour mettre un timeout d'une seconde sur
\lstc{getchar}. Ceci remplace \lstc{sleep} dans la boucle principale.
\subsection{Agrégation des paquets}
L'idée suggérée est implémentée~: la fonction \lstcpp{Protocol::sendBody}
agrège les TLVs qu'on lui donne dans une map de paquets (sans headers, \ie{}
une suite de TLVs), mappant les IDs de nœuds sur leur paquet en cours d'envoi.
Lorsqu'elle souhaite agréger plus d'un MTU~$- 12$ dans un paquet (\ie{}
MTU~$-$~taille des headers), le paquet dans la map est envoyé, vidé, puis on
agrèg le TLV qu'on souhaitait insérer.
À la fin de chaque itération de la boucle principale, tous les paquets en cours
sont envoyés, garantissant ainsi un délai inférieur à une seconde ---
acceptable compte-tenu des délais prévus dans le protocole.
\subsection{Amélioration de l'inondation}
Lorsqu'un premier IHU est reçu d'un pair, on inonde immédiatement la
donnée.
\subsection{Inondation non-bloquante}
L'objet \lstcpp{DataStore} décide périodiquement de republier ses données. Dans
ce cas, il met à jour la donnée stockée, puis met à jour un champ demandant la
propagation d'une certaine donnée. Ce champ est récupéré régulièrement par
l'objet \lstcpp{Neighbours}, pouvant lui aussi décider d'inonder une donnée
vers \emph{un} pair lorsque celui-ci envoie son premier IHU\@.
Lorsque \lstcpp{Neighbours} reçoit, d'une manière ou d'une autre, une requête
d'inondation, il crée un objet \lstcpp{Flooder} pour l'ID de la donnée à
inonder initialisé avec la liste des voisins vers qui on souhaite inonder.
Régulièrement, \lstcpp{Flooder::update} est appelé, et se charge d'inonder
plusieurs fois la donnée vers chaque pair de l'ayant pas encore acquittée.
\end{document}