Home Page TP 2 Réseaux 1ère Année IUT Info Aix-en-Provence (c ...
Transformation décimale pointée vers entier (version corrigée) .... Dans cet
exercice, on va déterminer la classe d'une adresse IP en notation décimale
pointée ...
part of the document
catifs. Ainsi, la transformation de 20 (décimal) donne 10100 en binaire. Or, le plus souvent, cette transformation doit rendre un octet entier, soit 00010100.
II.2.A. Adresses IP sous forme binaire
Soient les 4 adresses IP suivantes, codées sur 32 bits, où les bits sur regroupés ici en octets pour en faciliter la lecture :
* 10010011 11011000 01100111 10111110
* 01101100 10100100 10010101 11000101
* 11100000 10000001 10100010 01010001
* 11010110 01011100 10110100 11010001
Pour chaque adresse :
1. L'écrire en notation décimale pointée.
2. Déterminer sa classe à partir de la représentation binaire.
3. Isoler sa partie classe + id. réseau de sa partie id. station si cela a un sens, et déterminer l'écriture binaire de l'adresse de son réseau d'appartenance (appelée aussi " son adresse de réseau ").
4. Écrire son adresse de réseau en notation décimale pointée.
Corrigé
II.2.B. Adresses IP en notation décimale pointée
Soient les 4 adresses IP suivantes, exprimées selon la notation décimale pointée :
* 139.124.5.25
* 194.199.116.255
* 12.34.56.78
* 224.0.0.2
Pour chaque adresse :
1. Écrire en binaire sur un octet, le nombre de gauche de l'adresse (jusqu'au premier point).
2. Les bits codant la classe de l'adresse sont contenus dans cet octet, quelque soit la classe de l'adresse. En déduire sa classe.
3. Selon que la classe est A, B ou C, la partie id. station correspond respectivement aux 3 derniers, 2 derniers ou au dernier octet de l'adresse. En déduire son adresse de réseau en notation décimale pointée sans transformer l'écriture en binaire.
Corrigé
II.2.C. Classes et intervalles du premier octet
1. Puisque le premier octet d'une adresse IP suffit à déterminer la classe de cette adresse, déterminer, pour chaque classe, l'intervalle des valeurs décimales que peut prendre le premier octet.
2. En déduire les classes des adresses dont le premier octet est :
a. 10
b. 241
c. 192
d. 172
e. 230
Corrigé
II.3. Réajustement des limites
Certaines adresses sont réservées à un usage particulier (réseaux privés). Elles sont utilisables localement dans un réseau mais ne doivent pas circuler sur Internet et sont rejetées par les routeurs. Elles ne sont donc attribuables à aucun réseau ni aucune station pour se relier à Internet. Ces adresses s'étendent sur les plages suivantes :
* 10.0.0.0 à 10.255.255.255
* 172.16.0.0 à 172.31.255.255
* 192.168.0.0 à 192.168.255.255
* 169.254.0.0 à 169.254.255.255
Ajuster les résultats obtenus dans l'exercice II.1.A. pour tenir compte de ces contraintes supplémentaires.
Corrigé
III. Développement d'utilitaires
III.1. Extraction de la classe
Dans cet exercice, on va déterminer la classe d'une adresse IP en notation décimale pointée lue au clavier et contenue dans une chaîne. Par exemple, à partir de "19.234.5.33", il faudra indiquer que la classe est A.
Pour cela, il faut traiter la partie de la chaîne représentant le premier octet de l'adresse IP et la transformer en un entier représentant cette valeur. Pour "19.234.5.33", il faut transformer "19" en 19. La transformation doit être effectuée par l'algorithme de Horner décrit ci-dessous. Ensuite, on peut utiliser les intervalles trouvés dans l'exercice II.2.C.
Algorithme de Horner pour transformer une chaîne en entier
Pour obtenir l'entier représentant le premier octet, il faut utiliser l'algorithme de Horner en parcourant la chaîne à partir du début jusqu'à rencontrer un point ou la fin de chaîne tout en calculant l'entier représenté par la séquence lue. Au départ, cet entier vaut 0 et il évolue au fur et à mesure que l'on rencontre des caractères numériques pendant le parcours.
Exemples :
* pour "19.234.5.33", il faut s'arrêter au premier point et avoir fait l'opération suivante 1×10 + 9. En effet, après avoir lu "1", on doit avoir 1, puis, après avoir lu "19", on doit obtenir 1×10 + 9.
* pour "236.4.12.63", ce serait (2×10 + 3)×10 + 6.
Il faut donc transformer les caractères numériques en entiers pour réaliser ce calcul : '1' en 1, '2' en 2, ... Soit c un caractère numérique, alors l'expression : c - '0' retourne la valeur numérique (entier) de c.
Dans le namespace nsRes de nsRes.h, rajouter la déclaration suivante :
char GetClasseIP (const std::string & StrIP);
GetClasseIP() prend en entrée la chaîne StrIP qui contient une adresse IP en notation décimale pointée. Elle retourne le caractère de la classe de cette adresse : 'A', 'B', 'C', 'D' ou 'E'. Si l'adresse ne correspond à aucune classe connue, alors GetClasseIP() doit renvoyer 'X'. Il n'est pas demandé de valider l'adresse.
Définir GetClasseIP() dans le fichier nsRes.cxx du répertoire util.
Dans le répertoire tp2, écrire le programme exo_01.cxx qui, dans une boucle :
* lit au clavier une chaîne contenant l'adresse IP en notation décimale pointée
* sort de la boucle si la chaîne est vide
* écrit la classe renvoyée par GetClasseIP()
Compiler et tester exo_01.
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.1) :
nsRes.h nsRes.cxx exo_01.cxx
III.2. Transformation décimale pointée vers entier (version intermédiaire)
Dans cet exercice, on va transformer une adresse IP en notation décimale pointée, en sa représentation en 32 bits codée dans un entier. Par exemple, à partir de "19.234.5.33", il faut obtenir l'entier 334103841. Cet entier est issu de la projection suivante :
Mathématiquement, 334103841 est calculé par : 19 × 224 + 234 × 216 + 5 × 28 + 33
Cette expression est équivalente à : 19 × 2563 + 234 × 2562 + 5 × 256 + 33
car 256 = 28.
Elle s'écrit aussi : ((19 × 256 + 234) × 256 + 5) × 256 + 33
On reconnaît là l'opération effectuée par un algorithme de Horner : parcourir la chaîne et faire évoluer un entier représentant les octets lus.
Cependant, il faut utiliser un algorithme de Horner à 2 niveaux car l'expression précédente se traduit par :
(([1×10 + 9] × 256 + [[2×10 + 3]×10 + 4]) × 256 + [5]) × 256 + [3×10 + 3]
Il faut donc :
* utiliser un niveau pour calculer l'entier représentant chaque octet : 1 × 10 + 9 pour "19", (2 × 10 + 3) × 10 + 4 pour "234", ...
* utiliser un niveau pour calculer l'entier représentant les octets (entiers) lus jusque là. Par exemple, lorsque "19.234." a été lu, l'entier doit valoir 19 × 256 + 234. Puis, lorsque "19.234.5." a été lu, l'entier doit valoir ((19 × 256) + 234) × 256 + 5. Lorsque tous les octets ont été lus et pris en compte, cet entier contient la valeur recherchée.
Dans le fichier nsRes.h, et avant la déclaration du namespace, rajouter la directive de compilation suivante qui inclut le fichier stdint.h où est déclaré le type uint32_t :
#include // uint32_t
uint32_t est le type " entier non signé sur 32 bits ". Ses 32 bits peuvent donc accueillir la représentation binaire d'une adresse IP.
Dans le namespace nsRes de nsRes.h, déclarer la fonction suivante :
uint32_t IPstringToIPuint32 (const std::string & StrIP);
qui prend en entrée la chaîne StrIP qui contient une adresse IP en notation décimale pointée et qui retourne l'entier non signé sur 32 bits qui correspond à cette adresse.
Définir IPstringToIPuint32() dans le fichier nsRes.cxx du répertoire util.
Dans le répertoire tp2, écrire le programme exo_02.cxx qui, dans une boucle :
* saisit au clavier une chaîne contenant l'adresse IP en notation décimale pointée
* sort de la boucle si la chaîne est vide
* écrit l'entier renvoyé par IPstringToIPuint32()
Compiler et tester exo_02 avec "19.234.5.33" puis "33.5.234.19".
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.2) :
nsRes.h nsRes.cxx exo_02.cxx
III.3. Comparaison avec inet_addr()
En fait, il existe une fonction, provenant des librairies standards du langage C, qui effectue cette opération. C'est la fonction de prototype :
#include // inet_addr()
uint32_t inet_addr (const char * cp);
Elle renvoie un entier non signé codé sur 32 bits, correspondant à l'adresse IP en notation décimale pointée contenue dans la chaîne pointée par cp. Pour plus de précisions, consulter le manuel en ligne de Linux, en tapant : man inet_addr (il indique que inet_addr() renvoie un unsigned long int, mais en consultant le contenu de /usr/include/arpa/inet.h, on s'aperçoit que le header stdint.h y est inclus et qu'inet_addr() renvoie bien un uint32_t).
cp est un pointeur sur caractères, ce qui est le type " chaîne de caractère " en langage C. Pour obtenir un objet cp de type const char * à partir d'un objet Str de type string, on utilise la fonction membre c_str() de Str qui renvoie un objet de type const char * pointant sur une chaîne au sens du langage C.
Copier exo_02.cxx dans exo_03.cxx.
Pour comparer ce que renvoient inet_addr() et IPstringToIPuint32(), rajouter dans exo_03.cxx un appel à inet_addr() et l'écriture de ce qu'elle renvoie.
Compiler et tester exo_03 avec "19.234.5.33". Est-ce le même résultat ? Tester à nouveau avec "33.5.234.19". Que peut-on en déduire ?
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.3) :
exo_03.cxx
III.4. Écriture en notation décimale pointée avec inet_ntoa()
Pour mieux comprendre ce qui se passe, il est peut-être intéressant d'utiliser une autre fonction provenant des librairies standards du langage C, effectuant l'opération inverse de inet_addr(). C'est la fonction de prototype :
#include // struct in_addr
#include // inet_ntoa()
char * inet_ntoa (struct in_addr in);
Cette fonction renvoie une chaîne au sens du langage C (chaîne NTCTS), contenant l'adresse IP en notation décimale pointée, représentée par l'entier contenu dans le champ s_addr de la structure in_addr. Cette structure sera étudiée plus en détails l'année prochaine. Son champ s_addr est de type uint32_t et sert à stocker l'entier correspondant à une adresse IP.
Copier exo_03.cxx dans exo_04.cxx. Ajouter à exo_04.cxx :
* la déclaration d'une variable in, de type struct in_addr.
* deux appels à inet_ntoa() :
* un avec le champ s_addr initialisé à la valeur renvoyée par IPstringToIPuint32(),
* l'autre avec le champ s_addr initialisé à la valeur renvoyée par inet_addr().
Compiler exo_04 et tester avec "19.234.5.33" puis avec "33.5.234.19". Que peut-on maintenant en déduire ?
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.4) :
exo_04.cxx
III.5. Étude de l'ordre des octets
On s'aperçoit que l'appel à inet_ntoa() pour l'entier retourné par IPstringToIPuint32() à partir de "19.234.5.33" rend la chaîne "33.5.234.19", soit une adresse IP où les octets ont été inversés. De même, pour l'entier calculé pour "33.5.234.19", elle renvoie "19.234.5.33".
Le problème vient du fait que toutes les fonctions réseau du langage C pour TCP/IP utilisent ou renvoient des entiers codés en ordre réseau (Network Byte Order), ce qui n'est pas le cas de IPstringToIPuint32() qui fabrique un entier dont le codage dépend de la machine utilisée.
Or, le codage utilisé sur une machine n'est pas forcément le même que le codage réseau, ainsi que le montrent les exercices précédents. La machine que nous utilisont est petit-boutiste (codage Little Endian) alors que l'ordre réseau correspond à un codage gros-boutiste (Big Endian). La différence entre les deux se situe dans l'interprétation des (2, 4 ou plus) octets qui constituent un entier. Soit un entier sur 32 bits, placé en mémoire à l'adresse n. Ses 4 octets se trouvent aux adresses n, n + 1, n + 2, et n + 3 :
* pour le petit-boutiste, le poids des octets croît avec leur adresse : l'octet en n est l'octet de poids faible, alors que l'octet en n + 3 est celui de poids fort ;
* pour le gros-boutiste, le poids des octets décroît avec leur adresse : l'octet en n est l'octet de poids fort, alors que l'octet en n + 3 est celui de poids faible ;
* certains codages mixtes existent avec des machines (et compilateurs) travaillant avec des mots de 16 bits pour coder des nombres sur 32 bits : le premier mot de 16 bits contient les octets de poids fort et le suivant contient ceux de poids faible, mais l'ordre des octets est inversé dans les mots.
Ces différences entre les poids des octets selon leur emplacement sont illustrées par le tableau suivant (cf. cours) :
Adresses
n
n + 1
n + 2
n + 3
petit-boutiste
très faible
faible
fort
très fort
gros-boutiste
très fort
fort
faible
très faible
mixte
fort
très fort
très faible
faible
Pour terminer cet aperçu des différences, un petit utilitaire " maison " affiche 2 tables présentant les différences de codage entre la machine et l'ordre réseau (gros-boutiste). Dans les deux tables, la première colonne indique des adresse IP, la seconde colonne indique les entiers correspondants en décimal. Les quatre colonnes qui suivent contiennent les valeurs des quatre octets formant l'entier codé en machine, et les quatre dernières colonnes contiennent les valeurs des octets de l'entier en codage réseau. Les octets apparaissent dans l'ordre croissant de leurs adresses. La valeur de ces octets est écrite en binaire dans la première table, et en hexadécimal dans la seconde.
Ouvrir une assez haute et très large fenêtre sur Linux, et exécuter le programme visucodages disponible dans le répertoire ~cpb/public. Observer les différences de codage entre cette machine (duo) et l'ordre réseau.
Ces différences apparaissant aussi entre les diverses machines connectées à Internet, il est nécessaire de normaliser les échanges entre programmes d'applications afin qu'il n'y ait aucune ambiguïté sur l'interprétation des données reçues. L'émetteur d'un message doit envoyer ses données en les ayant transformées selon une norme (protocole) connue, approuvée et attendue par le récepteur. Celui transforme ensuite les données de la norme vers son propre codage afin de les analyser.
III.6. Transformation décimale pointée vers entier (version corrigée)
Pour revenir à IPstringToIPuint32() et son problème, il suffit de modifier l'ordre des octets de l'entier quelle renvoie en utilisant la fonction htonl() des librairies standards du langage C, de prototype :
#include // htonl()
uint32_t htonl (uint32_t hostlong);
htonl() veut dire : " host to network long ". Elle renvoie l'entier codé (sur 32 bits) en ordre réseau (network), calculé à partir de l'entier hostlong, codé (sur 32 bits) selon le codage de la machine (host). La fonction effectuant l'opération inverse est ntohl(). Leurs homologues sur 16 bits sont htons() et ntohs(). :
#include // htonl()
uint32_t ntohl (uint32_t netlong);
uint16_t htons (uint16_t hostshort);
uint16_t ntohs (uint16_t netshort);
Dans le namespace nsRes du fichier nsRes.h, déclarer la fonction de prototype :
uint32_t IPstringToNetIPuint32 (const std::string & StrIP);
qui retourne l'entier, en ordre réseau, correspondant à l'adresse IP sous forme décimale pointée contenue dans StrIP. IPstringToNetIPuint32() se contente de transformer ce que renvoie IPstringToIPuint32() en appelant htonl().
Dans le fichier nsRes.hxx, définir la fonction IPstringToNetIPuint32() en tant que fonction inline. Ajouter l'inclusion de nsRes.hxx dans nsRes.h. Mettre à jour le fichier INCLUDE_H pour tenir compte de la dépendance de nsRes.h envers nsRes.hxx.
Copier exo_04.cxx dans exo_05.cxx. Ajouter à exo_05.cxx un appel à IPstringToNetIPuint32() et à inet_ntoa() pour comparer avec les affichages déjà effectués.
Compiler et tester exo_05.
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.6) :
nsRes.h nsRes.hxx INCLUDE_H exo_05.cxx
III.7. Détermination du codage
Si la machine utilisée est gros-boutiste, il n'est pas nécessaire de transformer un entier par htonl() pour qu'il soit en ordre réseau. Cependant, les programmes d'application bien écrits ne doivent pas (trop) tenir compte de l'architecture matérielle utilisée. Ceci pour rester " portables " et être compilables et opérationnels sur d'autres " plateformes ". Ils doivent toujours utiliser htonl() ou htons() pour communiquer un entier aux fonctions réseau et éventuellement à d'autres machines, selon le protocole qu'elles utilisent.
Ainsi, dans le cas d'une machine gros-boutiste, htonl() et ses acolytes (htons(), ntohl(), et ntohs()), sont des fonctions ne faisant rien. Lors de leur compilation, il a fallu déterminer le codage utilisé par la machine pour savoir si elles devaient ou non effectuer une transformation et si oui, laquelle.
Une méthode simple pour réaliser ce test consiste à attribuer une valeur à un entier Entier de type uint32_t, de manière à ce que tous ses octets aient des valeurs différentes et connues. Puis, en utilisant un pointeur sur octet (par exemple PtrOctet de type uint8_t *), il suffit de parcourir et d'examiner les quatre octets de Entier et d'en déduire le codage réalisé par le compilateur et la machine.
Pour parcourir les octets de Entier, il faut initialiser PtrOctet à l'adresse de Entier par :
PtrOctet = (uint8_t *) (& Entier);
// ou PtrOctet = reinterpret_cast (& Entier);
puis utiliser la notation élément de tableau pour connaître la valeur des octets : PtrOctet[0] pour le premier octet, PtrOctet[1] pour le second,....
Dans le namespace nsRes du fichier nsRes.h, rajouter les déclarations suivantes :
enum { CstBigEndian = 1, CstLittleEndian, CstMiddleEndian };
int GetTypeCodage ();
GetTypeCodage() renvoie un entier valant respectivement CstBigEndian ou CstLittleEndian, selon que le codage est gros-boutiste ou petit-boutiste. Dans les autres cas (il peut y avoir plusieurs codages mixtes), elle renvoie CstMiddleEndian.
Dans le fichier nsRes.cxx du répertoire util, définir la fonction GetTypeCodage().
Écrire le programme exo_06.cxx qui teste GetTypeCodage() et qui écrit un message indiquant le type de codage utilisé.
Compiler et tester exo_06. Son résultat correspond-t-il à celui attendu ?
Corrigés (accessibles dans ~cpb/public/tpres/tp2/corrigesIII.7) :
nsRes.h nsRes.cxx exo_06.cxx
III.8. Transformation petit-boutiste vers ordre réseau
Dans le namespace nsRes du fichier nsRes.h, ajouter la déclaration suivante :
uint32_t LittleEndianToNetuint32 (uint32_t Entier);
qui renvoie l'entier en codage réseau sur 32 bits correspondant à l'Entier en codage petit-boutiste passé en paramètre.
Dans le fichier nsRes.cxx du répertoire util, définir la fonction LittleEndianToNetuint32() sans utiliser la fonction htonl().
Il y a plusieurs possibilités pour réaliser cette transformation :
* Par l'utilisation de pointeurs sur caractères :
* déclarer un entier de type uint32_t qui contiendra l'entier transformé ;
* déclarer deux pointeurs sur octet (et éventuellement un indice) qui permettent d'attribuer à chaque octet du résultat, le bon octet de Entier.
* Par les opérations arithmétiques de décalage et de masquage (à préférer) :
* en utilisant les opérateurs >>, > 24) & 0x000000ff.
* Pour l'octet de poids faible, on le place et on l'isole par : (Entier