• Aucun résultat trouvé

7 6 5 4 3 2 1

APDU PPDU SPDU TPDU Paquet Trame Bit application

présentation

session

transport

réseau

liaison de données

physique

application

présentation

session

transport

réseau

liaison de données

physique protocole d’application

protocole de présentation

protocole de session

protocole de transport routeurs

Hôte A Hôte B

Classiquement, la communication Unix/Linux se fait avec le protocole TCP/IP. Nous nous intéresserons principalement aux protocoles de niveau 4/transport « TCP » (communication par connexion) et « UDP » (communication par paquets). Ces deux protocoles s’appuient sur le protocole de communication « IP » (niveau 3/réseau), qui s’appuie lui-même sur des protocoles de niveau 2 de type Ethernet, ou Wifi... (802.x).

A noter que toute machine d’un réseau IP est identifiée au niveau 3 par une adresse IP.

Dans le protocole IP version 4, une adresse IP est constituée de 4 octets.

De plus, les machines possèdent un nom de dommaine sous la forme : nom_noeud. nom_ss-domaine[...].nom_domaine_racine

Le service réseau qui assure la traduction « nom » <-> « adresse IP » est le DNS (Domain Name Service).

Enfin, chaque service (mail, DNS, pop, http, etc) est identifié par un numéro, appelé

« numéro de port » :

réseau

serveur

SGBD Mails

Web

port

Identification du service par un numéro de port

client

Par convention, les numéros de ports sont assignés dans les plages suivantes :

Port n° 0 : non utilisable pour une application. C'est un « jocker » qui indique au système que c'est à lui de compléter le numéro (Cf. N°49152 à 65535) ;

Ports 1 à 1023 : ports réservés au superutilisateur (root/Administrateur). On y trouve les serveurs « classiques » (DNS, FTP, SMTP, Telnet, SSH...). Il existait anciennement un découpage 1-255, 256-511, et 512-1023 qui n’est plus utilisé ;

Ports 1024 à 49151 : services enregistrés par l'IANA (Internet Assigned Numbers Authority), accessibles aux utilisateurs ordinaires ;

Ports 49152 à 65535 : zone d'attribution automatique des ports, pour la partie cliente.

Sous Unix/Linux, l’API qui permet d’établir une communication entre un client et un serveur s’appuie sur la notion de « socket » (une socket s’apparente à une fiche). Il existe deux types de sockets :

les sockets en mode datagrammes, qui s’appuient sur le protocole UDP,

et les sockets en mode connecté, qui s’appuient sur le protocole TCP.

11.3 Les fonctions et structures propres à TCP/IP

11.3.1 La normalisation des entiers (endianness)

Certains microprocesseur (motorola 68000, sparc...) stockent les mots de plusieurs octets en mettant par convention l’octer de poid fort en premier (Exemple : le mot de 32 bits 0xa0b1c2d3 verra l’octet 0xa0 stocké en premier, 0xb1 en second, 0xc2 en 3ème, puis 0xd3 en dernier. On dit que ces microprocesseurs sont gros-boutistes, ou fonctionne en mode big-endian.

Inversement, par convention, d’autre CPU (Pentium par exemple) stockent les octes de poids faibles en premier. On parle de microprocesseurs petits-boutistes, ou qu’ils fonctionnent en little-endian.

Or, sans y faire attention, si un ordinateur gros-boutiste envoie un mot de 4 octets à un ordinateur petit-boutiste, les octets des mots se trouvent inversés, ce qui serait catastrophique.

Aussi, une convention a été choisie pour représenter les mot sur les réseaux IP (en fait, il s’agit de la convention « octets de poids fort en premier »). Il existe 4 fonctions (htonl(), htons(), ntohl(), ntohs()) qui permettent de normaliser les entiers (adresses IP, numéros de port, etc.), afin de les convertir dans la convention d’Internet. Si la machine locale a un CPU qui a la même convention que celle d’Internet, ces 4 fonctions ne font rien. Sinon, elles inversent les octets de poid faibles avec les octets de poids fort.

<<

SYNOPSIS

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

DESCRIPTION

La fonction htonl() convertit l’entier non signé hostlong depuis l’ordre des octets de l’hôte vers celui du réseau.

La fonction htons() convertit l’entier court non signé hostshort depuis l’ordre des octets de l’hôte vers celui du réseau.

La fonction ntohl() convertit l’entier non signé netlong depuis l’ordre des octets du réseau vers celui de l’hôte.

La fonction ntohs() convertit un entier court non signé netshort depuis l’ordre des octets du réseau vers celui de l’hôte.

>>

11.3.2 La résolution de nom

Les fonctions qui font appel au DNS utilisent la structure hostent qui est définie par un

« #include <netdb.h> » : struct hostent {

char *h_name; /* nom officiel de l’hôte */

char **h_aliases; /* liste d’alias */

int h_addrtype; /* type d’adresse (tjs AF_INET */

/* pour IP v4 ou AF_INET6 pour */

/* IP v6) */

int h_length; /* longueur de l’adresse */

char **h_addr_list; /* list d’addresses */

};

Les tableaux h_aliases et h_addr_list ont comme dernier élément la valeur NULL.

La traduction IP vers nom se fait à l’aide de la fonction gethostbyaddr(), et la traduction nom vers adresse IP se fait avec la fonction gethostbyname() :

<<

SYNOPSIS

#include <netdb.h>

#include <sys/socket.h> /* pour avoir AF_INET */

extern int h_errno;

struct hostent *gethostbyname(const char *name);

struct hostent *gethostbyaddr(const void *addr, \ int len, int type);

DESCRIPTION

La fonction gethostbyname() renvoie une structure de type hostent pour le nom d’hôte. La chaîne name est soit un nom d’hôte, soit une adresse IPv4 en notation pointée standard, soit une adresse IPv6 avec la notation

points-virgules et points (Cf RFC 1884 pour la description des adresses IPv6). Si name est une adresse IPv4 ou IPv6, aucune recherche supplémentaire n’a lieu et

gethostbyname() copie simplement la chaîne name dans le champ h_name et le champ équivalent struct in_addr dans le champ h_addr_list[0] de la structure hostent renvoyée.

Si name ne se termine pas par un point, et si la variable d’environnement HOSTALIASES est configurée, le fichier d’alias pointé par HOSTALIASES sera d’abord parcouru à la recherche de nom (voyez hostname(7) pour le format du fichier). Le domaine courant et ses parents sont parcourus si name ne se termine pas par un point.

La fonction gethostbyaddr() renvoie une structure du type hostent pour l’hôte d’adresse addr. Cette adresse est de longueur len et du type type. Les types d’adresse valides sont AF_INET et AF_INET6. L’argument adresse de l’hôte est

un pointeur vers une structure de type dépendant du type de l’adresse, par exemple struct in_addr * (probablement obtenu via un appel à inet_addr()) pour une adresse de type

AF_INET.

[...]

La structure hostent est définie ainsi dans <netdb.h> :

struct hostent {

char *h_name; /* Nom officiel de l’hôte. */

char **h_aliases; /* Liste d’alias. */

int h_addrtype; /* Type d’adrs. de l’hôte. */

int h_length; /* Longueur de l’adresse. */

char **h_addr_list;/* Liste d’adresses. */

};

#define h_addr h_addr_list[0] /* pour compatibilité*/

Les membres de la structure hostent sont : h_name Nom officiel de l’hôte.

h_aliases

Un tableau, terminé par un pointeur NULL, d’alternatives au nom officiel de l’hôte.

h_addrtype

Le type d’adresse : toujours AF_INET ou AF_INET6.

h_length

La longueur, en octets, de l’adresse.

h_addr_list

Un tableau, terminé par un pointeur NULL, d’adresses réseau pour l’hôte, avec l’ordre des octets du

réseau.

h_addr La première adresse dans h_addr_list pour respecter la compatibilité ascendante.

VALEUR RENVOYÉE

Les fonctions gethostbyname() et gethostbyaddr() renvoient un pointeur vers la structure hostent, ou un pointeur NULL si une erreur se produit, auquel cas h_errno contient le code d’erreur. Lorsqu’elle n’est pas NULL, la valeur de retour peut pointer sur une donnée statique.

ERREURS

La variable h_errno peut prendre les valeurs suivantes : HOST_NOT_FOUND

L’hôte indiqué est inconnu.

NO_ADDRESS ou NO_DATA

Le nom est valide mais ne possède pas d’adresse IP.

NO_RECOVERY

Une erreur fatale du serveur de noms est apparue.

TRY_AGAIN

Une erreur temporaire du serveur de noms est apparue, essayez un peu plus tard.

NOTES

Les fonctions gethostbyname() et gethostbyaddr() peuvent renvoyer des pointeurs sur des données statiques

susceptibles d’être écrasées d’un appel à l’autre. Copier la structure struct hostent ne suffit pas car elle contient elle-même des pointeurs. Une copie en

profondeur est indispensable.

>>

Pour IP version 4, les adresses sont stockées dans des structures de type struct sockaddr_in, qui est constituée des champs suivants :

struct sockaddr_in {

short sin_family; /* AF_INET */

u_short sin_port; /* Num. de port en endian Internet */

struct in_addr sin_addr; /* Adresse IP (en endian Internet) */

char sin_zéro [8]; /* Bourrage */

};

struct in_addr {

u_long s_addr;

};

11.3.3 La résolution de service

Comme nous l’avons vu, chaque service est identifié par un numéro de port, choisi par convention. Sous Unix/Linux, les numéros de ports standards sont listés dans le fichier

« /etc/services ».

Pour connaître le numéro de port associé à un service (et réciproquement), il existe deux fonctions : getservbyname() et getservbyport() :

<<

SYNOPSIS

#include <netdb.h>

struct servent *getservbyname (const char *name, \ const char *proto);

struct servent *getservbyport (int port, const char *proto);

DESCRIPTION

La fonction getservbyname() renvoie une structure servent pour l’enregistrement du fichier /etc/services qui

correspond au service nommé name et utilisant le protocole proto. Si proto est NULL, n’importe quel protocole sera accepté.

La fonction getservbyport() renvoie une structure servent pour l’enregistrement correspondant au port indiqué (dans l’ordre des octets du réseau) et utilisant le protocole proto. Si proto est NULL, n’importe quel protocole sera accepté.

La structure servent est définie dans <netdb.h> ainsi : struct servent {

char *s_name; /* Nom officiel du service */

char **s_aliases; /* Liste d’alias */

int s_port; /* Numéro de port */

char *s_proto; /* Protocole utilisé */

}

Les membres de la structure servent sont : s_name Le nom officiel du service.

s_aliases

Une liste terminée par zéro contenant d’autres noms utilisables pour le service.

s_port Le numéro de port, donné dans l’ordre des octets du réseau.

s_proto

Le nom du protocole utilisé par ce service.

VALEUR RENVOYÉE

Les fonctions getservbyname() et getservbyport() renvoient une structure servent, ou un pointeur NULL si une erreur se produit, ou si la fin du fichier est atteinte.

>>

11.4 La communication par datagrammes

Coté serveur, la création d’un socket() est réalisée à l’aide de la fonction socket(). Puis, le socket est attaché à une adresse à l’aide de la fonction bind(). L’échange de datagrammes se fait à l’aide des fonctions sendto() et recvfrom(), qui permettent de spécifier le numéro de port sur lequel l’écoute est faite (avec le protocole UDP). Enfin, le socket est détruit à l’aide de la fonction close().

Coté client, comme pour le serveur, la création d’un socket() est réalisée à l’aide de la fonction socket().L’échange de datagrammes se fait à l’aide des fonctions sendto() et recvfrom(). Enfin, le socket est détruit à l’aide de la fonction close().

Un échange client-serveur pour se schématiser ainsi :

client serveur

socket() socket()

bind() sendto()

recvfrom() sendto() recvfrom()

close() close()

(etc...)