next up previous contents index
Next: XV Éléments de serveurs Up: D Sockets BSD et Previous: XIII Généralités sur les   Contents   Index

Subsections

XIV Compléments sur les sockets Berkeley

1 Réservation des ports

Au chapitre précédent nous avons utilisé la primitive bind pour assigner une adresse à une socket, dans ce paragraphe nous précisons comment choisir le numéro de port qui va bien, selon le type d'application envisagé. Nous avons déjà examiné ce point dans les grandes lignes page [*].

Il y a deux manières d'assigner un N$^{\circ }$ de port à une socket :

  1. Le processus spécifie le numéro. C'est typiquement ce que fait un serveur. On suppose bien évidement que les clients sont au courant ou qu'ils disposent d'un mécanisme qui peut les renseigner (cf cours sur les RPC).

  2. Le processus laisse le système lui assigner automatiquement un numéro de port. C'est typiquement ce que fait un client, sauf cas contraire exigé par le protocole d'application (cf cours sur les `` remote execution '').

En règle générale le développeur d'application ne s'attribue pas au hasard un (ou plus) numéro de port. Il doit respecter quelques contraintes comme ne pas utiliser les ports déjà attribués. Ceux-ci figurent dans une RFC particulière. La dernière en date est la RFC 1700 [Reynolds & Postel 1994]) au paragraphe `` WELL KNOWN PORT NUMBERS ''. Plus simplement, sur toute machine Unix à jour, une liste de ces ports se trouve dans le fichier /etc/services XIV1.

Codé sur deux octets non signés, le numéro de port permet 65536 possibilités de 0 à 65535. Cette échelle est fragmentée de deux manières, l'ancienne ou la nouvelle méthode. Toutes les applications sur tous les systèmes d'exploitation n'ont pas encore adopté la nouvelle approche, les deux vont donc cohabiter un certain temps, ne serait-ce qu'à cause d'applications plus anciennes non encore mises à jour...

1.1 Réservation de port -- Ancienne méthode

Port N$^{\circ }$ 0
Ce numéro n'est pas utilisable pour une application, c'est une sorte de `` jocker '' qui indique au système que c'est à lui de compléter automatiquement le numéro (voir plus loin de 1024 à 5000).

Port de 1 à 255
Pour utiliser cette zone il faut avoir les droits du root à l'exécution (UID=0) pour que le bind ne retourne pas une erreur. Les serveurs `` classiques '' (domain, ftp, smtp, telnet, ssh, http, snmp...) se situent tous dans cette partie.

Ports de 256 à 511
Jadis considéré comme une `` réserve '' des serveurs officiels commencent à s'y installer, faute de place dans la zone précédente. Il faut également avoir un UID=0 pour utiliser un numéro de port dans cette zone.

Port de 512 à 1023
Une fonction rresvport permet l'attribution automatique d'un numéro de port dans cette zone, pour des applications ayant un UID=0. Par exemple, c'est dans cette zone qu'inetd (cf cours sur les serveurs) attribue des ports automatiquement pour les outils en `` r '' de Berkeley (rlogin, rcp, rexec, rdist,...).

Port de 1024 à 5000
Zone d'attribution automatique par bind. Lorsque l'utilisateur (non root) utilise 0 comme numéro, c'est le premier port libre qui est employé. Si tous les utilisateurs peuvent s'attribuer `` manuellement '' un numéro dans cette zone, il vaut mieux éviter de le faire, la suivante est prévue pour cela.

5001 à 65535
Zone `` libre '' attention cependant car de très nombreux serveurs y ont un port réservé, et pas des moindres comme le serveur X11 sur le port 6000 !

1.2 Réservation de port -- Nouvelle méthode

Port N$^{\circ }$ 0
Ce numéro n'est pas utilisable pour une application, c'est une sorte de `` jocker '' qui indique au système que c'est à lui de compléter automatiquement le numéro (voir plus loin de 49152 à 65535).

Port de 1 à 1023
Pour utiliser cette zone il faut avoir les droits du root à l'exécution pour que le bind ne retourne pas une erreur. Les serveurs `` classiques '' (domain, ftp, smtp, telnet, ...) se situent tous dans cette partie.

Port de 1024 à 49151
est la zone des services enregistrés par l'IANA et qui fonctionnent avec des droits ordinaires.

Port de 49152 à 65535
est la zone d'attribution automatique des ports, pour la partie cliente des connexions (si le protocole n'impose pas une valeur particulière) et pour les tests de serveurs locaux.

2 Ordre des octets sur le réseau

Nous reprenons ici un point déjà évoqué page [*] :

\includegraphics{fig.socket+.01.ps}
figure XIV.01 -- Ordre des octets sur le réseau

Le problème de l'ordre des octets sur le réseau est d'autant plus crucial que l'on travaille dans un environnement avec des architectures hétérogènes.

La couche Réseau (page [*]) ne transforme pas les octets de la couche Internet (page [*]) qui elle même ne modifie pas ceux de la couche de TransportXIV2(page [*]).

Pour cette raison, le numéro de port inscrit dans l'en-tête TCP (vs UDP) de l'émetteur est exploité tel quel par la couche de transport du récepteur et donc il convient de prendre certaines précautions pour s'assurer que les couches de même niveau se comprennent.

D'un point de vue plus général, les réseaux imposent le `` poids fort '' avant le `` poids faible '', c'est le `` Network Byte Order ''. Les architectures qui travaillent naturellement avec cette représentation n'ont donc théoriquement pas besoin d'utiliser les fonctions qui suivent, de même pour les applications qui doivent dialoguer avec d'autres ayant la même architecture matérielle. Néanmoins écrire du code `` portable '' consiste à utiliser ces macros dans tous les casXIV3 !

Pour se souvenir de ces fonctions, il faut connaître la signification des quatre lettres utiles :

s `` short '' Entier court sur 16 bits, un numéro de port par exemple.
l `` long '' Entier long sur 32 bits, une adresse IP par exemple.
h `` host '' La machine sur laquelle s'exécute le programme.
n `` network '' Le réseau sur lequel on envoie les données.

D'où les protoptypes :

#include <sys/types.h>
u_long htonl (u_long) ; /* host to network -- long */
u_short htons (u_short) ; /* host to network -- short */
u_long ntohl (u_long) ; /* network to host -- long */
u_short ntohs (u_short) ; /* network to host -- short */

cm

Par exemple, pour affecter le numéro de port 13 (service `` daytime '') au champ sin_port d'une structure de type sockaddr_in :

cm

saddr.sin_port = htons(13) ;

Cette écriture est valable quelle que soit l'architecture sur laquelle elle est compilée. S'il avait fallu se passer de la macro htons sur une architecture Intel (`` little endian ''), pour l'affection du même numéro de port, il eut fallu écrire :

saddr.sin_port = 0x0D00 ; /* 0D hexadécimal == 13 décimal */

3 Opérations sur les octets

Dans le même ordre d'idée qu'au paragraphe précédent, les réseaux interconnectent des architectures hétérogènes et donc aussi des conventions de réprésentation des chaînes de caractères différentes. Pour être plus précis, le caractère NULL marqueur de fin de chaîne bien connu des programmeurs C, n'est pas valide partout, voire même est associé à une autre signification !

En conséquence, pour toutes les fonctions et primitives qui lisent et écrivent des octets sur le réseau, les chaînes de caractères sont toujours associées au nombre de caractères qui les composent.

Le corollaire est que les fonctions `` classiques '' de manipulation de chaînes en C (strcpy, strcat, ...) ne sont à utiliser qu'avec une extrême prudence.

Pour copier des octets d'une zone vers une autre il faut utiliser bcopy, pour comparer deux buffers, bcmp, enfin pour mettre à zéro (remplir d'octets NULL) une zone, bzero.

#include <string.h>

void bcopy (const void *src, void *dst, size_t len) ;
int bcmp (const void *b1, const void *b2, size_t len) ;
void bzero (const void *b, size_t len) ;

bcopy
Attention, len octets de src sont copiés dans dst et non l'inverse, comme dans strcpy.

bcmp
Compare b1 et b2 et renvoie 0 si les len premiers octets sont identiques, sinon une valeur non nulle qui n'est pas exploitable vis à vis d'une quelconque relation d'ordre (à la différence de strcmp qui suppose les caractères dans la table ASCII).

bzero
Met des octets NULL (0) len fois à l'adresse b.

Il exite des outils similaires, issus du système V : memcpy, memcmp, memset,....

4 Conversion d'adresses

La plupart du temps, par exemple dans une forme de saisie pour la configuration d'un appareil, les adresses IP sont fournies par l'utilisateur dans le format `` décimal pointé '', or la structure d'adresse (sockaddr_in) a besoin d'un entier non signé sur 32 bits qui respecte le NBO. Une conversion est donc nécessaire pour passer d'une représentation à une autre.

4.1 Conversion d'adresse - IPv4 seul

La fonction inet_addr convertit une adresse décimale pointée en un entier long non signé et qui respecte le NBO. La fonction inet_ntoa effectue le travail inverse. Ces deux fonctions ne sont valables que pour les adresses 32 bits d'IPv4 et sont présentes dans la majeure partie des codes réseaux. Dans le cadre d'un nouveau développement on leur préfèrera les fonctions décrites après.

cm

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

cm

in_addr_t inet_addr (char *) ; /* in_addr_t == unsigned long */
char * inet_ntoa (struct in_addr) ;

cm

Remarque : Ces deux fonctions ne sont pas symétriques, le fait que inet_addr ne renvoie pas une structure du type in_addr n'est pas une inconsistance mais est dû au fait que la structure in_addr était prévue au départ pour évoluer.

cm Exemple d'utilisation :

struct	sockaddr_in saddr ;
if ((saddr.sin_addr.s_addr = inet_addr("138.195.52.130")) != INADDR_NONE)
    printf("Adresse IP = %s\n",inet_ntoa(saddr.sin_addr)) ;
else
    printf("Erreur sur l'argument de 'inet_addr' !\n" );

Il faut juste noter qu'en cas d'erreur la fonction inet_addr renvoie la constante INADDR_NONE.

4.2 Conversion d'adresse - Compatible IPv4 et IPv6

Avec les mêmes fichiers d'en-tête, les deux nouvelles fonctions de conversion :

cm

const char * inet_ntop (int af, const void *src, char *dst, size_t size);
int inet_pton (int af, const char *src, void *dst);

cm

Le p signifie `` presentation '', comprendre lisible par l'humain, alors que le n signifie `` numeric '', c'est à dire compréhensible par le noyau (entier qui respecte le NBO). Donc ntop convertit le format système vers l'humain et pton effectue le travail inverse.

Du fait de leur compatibilité entre les familles de protocoles, ces fonctions sont un peu plus compliquées à l'usage : Il faut préciser PF_INET ou PF_INET6.

cm Exemple d'utilisation :

struct sockaddr_in saddr ;
if (inet_pton(PF_INET,"138.195.52.130",&saddr.sin_addr) != 1)
    (void)fprintf(stderr,"L'adrese n'est pas exploitable !\n") ; 
else {
    char adr[INET_ADDRSTRLEN] ;	/* 16 == 4 x 3(digits) + 3 (.) + 1 (NULL) */
    printf("Adresse IP = %s\n",inet_ntop(PF_INET,&saddr.sin_addr,adr,sizeof(adr))) ;
}

Il faut noter que le code de retour de la fonction inet_pton peut prendre les valeurs -1, 0 ou 1. 1 signifie que transformation l'adresse transcodée est utilisable.

Le code de retour de inet_ntop est soit NULL si la conversion a échoué, ou un pointeur sur la chaîne affichable. Ici, par construction, on suppose que la conversion sera toujours réussie.

5 Conversion hôte - adresse IPv4

Se pose régulièrement le problème de convertir un nom symbolique en une adresse IP et inversement. L'origine de cette information dépend de la configuration de la station de travail : c'est un serveur de noms (DNS), c'est un fichier (/etc/hosts) ou tout autre système de propagation des informations (NIS...). Dans tous les cas l'information arrive à un programme via une entité nommée le resolver, qui unifie les sources d'informations.

Les paragraphes 5.1, 5.2 (p. [*]), 6.1 (p. [*]) et 6.2 (p. [*] présentent une approche traditionnelle, seulement valable avec IPv4 alors que le paragraphe 7 (p. [*]) expose une démarche beaucoup plus récente et adaptée également à IPv6. L'écriture de nouveaux codes ne devraient faire appel qu'à cette nouvelle api.


5.1 Une adresse IP à partir d'un nom d'hôte

#include <netdb.h>

struct hostent * gethostbyname (char *name) ;

 struct hostent {
    char	*h_name ;       /* Le nom officiel	*/
    char	**h_aliases ;   /* Tableau de synonymes	*/
    int		h_addrtype ;    /* PF_INET pour ARPA	*/
    int		h_length ;      /* Long. de l'adresse	*/
    char	**h_addr_list ; /* Adresses possibles	*/
 } ;
 #define	h_addr	h_addr_list[0]

cm

la macro h_addr sert à assurer la compatibilité avec les premières versions dans lesquelles il n'y avait qu'une seule adresse IP possible par hôte.

Le nom `` officiel '' s'oppose aux noms synonymes. Par exemple, soit une machine officiellement baptisée pasglop.mon-domain.fr ; si pour répondre au besoin d'une certaine application l'administrateur du réseau lui donne le surnom www.mon-domain.fr, celui-ci sera considéré comme un `` alias '' vis à vis du nom officiel et donc lu dans h_aliases. (voir page [*])

\includegraphics{gethostbyname.eps} gethostbyname.c

La fin du tableau de pointeurs est marquée par un pointeur NULL.

La liste des adresses est un tableau de pointeurs, le marqueur de fin de liste est également un pointeur NULL. Chaque adresse est une zone de h_length octets (cf fonction impnet dans l'exemple ci-après).

Le programme d'usage qui suit affiche toutes les informations contenues dans cette structure pour les hôtes dont le nom est passé en argument (le code source de cet exemple, gethostbyname.c, est à la page suivante).

$ gethostbyname gw-sio.sio.ecp.fr srv-sio.sio.ecp.fr
Nom officiel     : gw-sio.sio.ecp.fr
Type d'adresse   : AF_INET
Adresse Internet : 138.195.52.33
Adresse Internet : 138.195.53.1
Adresse Internet : 138.195.10.52
Nom officiel     : srv-sio.sio.ecp.fr
Type d'adresse   : AF_INET
Adresse Internet : 138.195.52.130

$ gethostbyname  anna.sio.ecp.fr    
anna.sio.ecp.fr : hote inconnu !


5.2 Un nom d'hôte à partir d'une adresse IP

le problème inverse se résoud avec la fonction gethostbyaddr. La définition du prototype se trouve au même endroit que précédement, la fonction renvoie un pointeur sur une structure du type hostent.
#include <netdb.h>

struct hostent * gethostbyaddr (char *addr, int len, int type) ;

addr : Pointe sur une structure du type in_addr
len : Est la longueur de addr
type : PF_INET quand on utilise la pile ARPA

6 Conversion N$^{\circ }$ de port - service

Couramment les ports bien connus sont donnés par leur nom plutôt que par leur valeur numérique, comme par exemple dans les sorties de la commande tcpdump.


6.1 Le numéro à partir du nom

Un tel programme a besoin de faire la conversion symbolique -- numérique, la fonction getservbyname effectue ce travail. L'utilisateur récupère un pointeur sur une structure du type servent, NULL dans le cas d'une impossibilité. La source d'informations se trouve dans le fichier /etc/services.

#include <netdb.h>
struct servent * getservbyname (char *name, char *proto) ;

 struct servent {
    char        *s_name ;
    char        **s_aliases ;
    int         s_port ;
    char        *s_proto ;
 } ;

s_name
Le nom officiel du service.
s_aliases
Un tableau de pointeurs sur les aliases possibles. Le marqueur de fin de tableau est un pointeur à NULL.
s_port
Le numéro du port (il respecte le Network Byte Order).
s_proto
Le nom du protocole à utiliser pour contacter le service (TCP vs UDP).

Voici un programme de mise en application de la fonction, le code source de l'exemple, getservbyname.c se trouve à la page suivante.

$ getservbyname domain
Le service domain est reconnu dans /etc/services
protocole :tcp - N de port :53

$ getservbyname domain udp
Le service domain est reconnu dans /etc/services
protocole :udp - N de port :53

\includegraphics{getservbyname.eps} getservbyname.c


6.2 Le nom à partir du numéro

Symétriquement la fonction getservbyport effectue le travail inverse. Elle renvoie aussi un pointeur sur une structure servent, NULL dans le cas d'une impossibilité.

#include <netdb.h>

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

Exemples d'usage :

$ getservbyport 53
Le port 53 correspond au service "domain" (protocole tcp).
$ getservbyport 53 udp
Le port 53 correspond au service "domain" (protocole udp).

Exemple de programmation :

\includegraphics{getservbyport.eps} getservbyport.c

7 Getaddrinfo, pour IPv4 et IPv6

Les apis des deux paragraphes qui précèdent (gethostbyname et getservbyname et leur symétrique) sont des standards de facto et ce depuis le début des années 80. On les trouve sur toutes les variantes d'Unix et même au delà, ce qui a participé à une grande portabilité du code écrit qui les utilise.

L'arrivée d'IPv6 et de sa probable très longue cohabitation avec IPv4 oblige à modifier les habitudes de programmation au profit d'une nouvelle approche, que ces concepteurs souhaitent aussi stable et largement répandue que la précédente. L'écriture de tout nouveau code devrait s'appuyer sur cette nouvelle API, définie dans la RFC 3493.

La nouvelle fonction getaddrinfo de la libc ne se contente pas seulement de synthétiser gethostbyname et getservbyname en une seule fonction, elle banalise également l'usage d'IPv6.

La démarche est plus concise (une fonction au lieu d'une), et la manipulation des adresses IP est rendue plus aisée en retour, puisque que la structure de données utilisée par la fonction contient directement une structure d'adresse conforme à la famille de protocole utilisée, sockaddr_in pour IPv4, directement utilisable par exemple avec les primitives bind, connect,...On peut songer par comparaison au champ h_addr_list de la structure hostent (page [*]) qui ne contient que les adresses IP.

7.1 La fonction getaddrinfo

La fonction getaddrinfo combine les fonctionnalités de gethostbyname et getservbyname pour les protocoles IPv4 et IPv6. Son prototype est donc le reflet de sa relative complexité, par contre son fonctionnement est très logique , il découle de celui des deux fonctions qu'elle remplace.

7.1.1 Prototype de getaddrinfo

#include <netdb.h>
int getaddrinfo(const char *hostname, const char *servname,
                      const struct addrinfo *hints, struct addrinfo **res);

Comme il y a beaucoup d'informations à transmettre et à recevoir, tout (ou presque) s'effectue via une nouvelle structure de données nommée addrinfo. Elle apparait en troisième argument (informations fournies à l'appel) et quatrième (dernier) argument, le résultat.

Si cette fonction ne retourne pas une valeur nulle (0), le code d'erreur est à exploiter à l'aide d'une fonction spécialisée, gai_strerror qui ajoute une bonne dizaine de messages d'erreurs supplémentaires spécialisés.

7.1.2 Description des arguments

Les deux premiers arguments, hostname ou servname, sont soit des chaînes de caractères, soit un pointeur nul. Il ne peuvent pas être tous les deux nuls.

hostname
Les valeurs acceptables sont soit un nom d'hôte valide ou une adresse IP exprimée sous sa forme décimale pointée.

servname
est soit un nom, soit un numéro de service présent dans /etc/services.

hints
La structure est optionnelle (NULL dans ce cas) et permet de piloter finement le comportement de la fonction. L'explication de son usage passe par un survol de la constitution de sa structure.

res
Le dernier argument est un pointeur de pointeur sur une structure du même type que hints. C'est par ce moyen que la fonction va renvoyer le résultat construit, en modifiant la valeur du pointeur (il nous faut passer l'adresse du pointeur puisque sa valeur va changer, d'où le pointeur de pointeur).

La fonction alloue la mémoire nécessaire pour le stockage des données, au programme appelant de la restituer au système. Il dispose à cet effet d'une fonction spécialisée : freeaddrinfo.

7.1.3 La structure addrinfo

Voici les membres de la structure addrinfo, définis dans le fichier d'en-têtes netdb.h :

struct addrinfo {
        int     ai_flags;       /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
        int     ai_family;      /* PF_xxx */
        int     ai_socktype;    /* SOCK_xxx */
        int     ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
        socklen_t ai_addrlen;   /* length of ai_addr */
        char    *ai_canonname;  /* canonical name for hostname */
        struct  sockaddr *ai_addr;      /* binary address */
        struct  addrinfo *ai_next;      /* next structure in linked list */

};

Signalons tout de suite au lecteur le dernier membre de la structure, ai_next. Il est du même type que celui de la structure elle même (structure auto-référente en langage C) ce qui signifie qu'il peut pointer vers une autre structure du même type. Il s'agit alors d'une liste chaînéeXIV4 afin de retourner une information multiple, comme peut l'être par exemple la réponse d'une requête DNS (type A ou PTR en l'occurence), ou la liste des protocoles prévus pour un service donné. La fin de la liste est marquée par un pointeur de valeur nulle (NULL).

La structure hints doit être mise à zéro avant chaque usage. Les quatre premiers champs sont utilisés à l'appel, les autres éléments sont à zéro lors de la transmission à la fonction.

ai_family
Pour indiquer la famille de protocole utilisée. C'est le même argument que celui qu'on utilise en première position avec la primitive socket, cf page [*].

Il peut prendre la valeur PF_UNSPEC quand le protocole n'est pas fixé.

ai_socktype
Pour préciser le type de socket demandé, c'est à dire le mode connecté ou datagramme. C'est le deuxième argument de la primite socket.

Si la valeur est laissée à 0 cela signifie que n'importe quel protocole est accepté.

ai_protocol
Pour préciser le nom du protocole. C'est le troisième argument de socket. Même remarque que précédement concernant la valeur 0.

ai_flags
Ce drapeau est éventuellement une combinaison de valeurs binaires assemblées avec l'opérateur | du langage C (ou inclusif).

AI_ADDRCONFIG
Seules les adresses (IPv4 ou IPv6) configurées sur le système local sont retournées.

AI_ALL
Combiné avec AI_V4MAPPED donne toutes les adresses IPv4 et IPv6. Sans effet si AI_V4MAPPED n'est pas présent.

AI_CANONNAME
Si l'information est accessible (DNS...) le champ ai_canonname de la première structure res pointe sur le nom canonique de hostname.

AI_NUMERICHOST
Précise que hostname doit être traité comme une adresse IP délivrée sous sa forme numérique, c'est à dire décimale pointée pour IPv4.

AI_NUMERICSERV
Indique que si servname est un port donné sous forme numérique (la chaîne encode la représentation du nombre, comme par exemple `` 23 '').

AI_PASSIVE
Sert pour l'attribution automatique du numéro de port de la structure d'adresse, sockaddr_in pour IPv4, accessible via le pointeur générique sockaddr.

AI_V4MAPPED
Utilisé avec IPv6 (AF_INET6).

7.1.4 En résumé

Il y a 6 variables à configurer, non toutes utiles selon les utilisations. Il est évident que certaines des nombreuses possibilités offertes par la combinatoire ne sont pas consistante. Le bon sens doit prédominer et le test de retour de la fonction être toujours exploité... !

7.1.5 Exemple d'usage à la place de gethostbyname

Cet exemple remplace celui de la page [*]. Le code n'est pas plus simple.

\includegraphics{getaddrinfo_1.eps} getaddrinfo_1.c

Ligne 13
Il faut inclure ce fichier d'en-têtes pour avoir le prototype de getaddrinfo.

Ligne 26
Déclaration d'une structure de type addrinfo et de deux pointeurs du même type.

Ligne 31
On décrémente avant de faire le test. Donc quand argc passe par 0 on sort de la boucle. La dernière valeur utilisée de argc est 1.

Ligne 32
pt pointe sur argv[1], puis sur argv[2], etc...On utilise argc valeurs très exactement.

Lignes 33
Mise à zéro de tous les bits de la structure

Lignes 34, 35 et 36
On veut les noms canoniques, pour IPv4. L'usage de SOCK_DGRAM est un artifice pour éviter d'avoir deux réponses, une avec TCP et l'autre avec UDP.

Ligne 37
Ne pas oublier de conserver le code de retour pour pouvoir éventuellement l'exploiter à l'aide de la fonction gai_strerror, comme ligne 38.

Ligne 41 et 42
Affichage des informations de la première structure (lstres0).

Ligne 43
Boucle for pour explorer tous les éléments de la liste chaînée. La condition d'arrêt est de rencontrer un pointeur nul.
Ligne 44, 45, 46 et 47
Affichage de l'adresse IP extraite de la structure d'adresse, et mise en forme par la fonction inet_ntop.

Notez l'utilisation des éléments de la structure d'information pour compléter les arguments d'appel de inet_ntop. Il faut utiliser un cast (struct sockaddr_in *) afin d'accéder au champ sin_addr de la structure d'adresse IPv4. La structure ai_addr est générique et n'y donne pas accès.

Ligne 50
Restitution de la mémoire allouée, pour l'exemple parceque le noyau va de toute manière recycler toute la mémoire du processus lors de l'opération de fin provoquée ligne 51.

7.1.6 Exemple d'usage à la place de getservbyname

Cet exemple remplace celui de la page [*]

\includegraphics{getaddrinfo_2.eps} getaddrinfo_2.c

Ligne 27
On précise IPv4.

Ligne 33
Usage de la fonction getprotobynumber dont le prototype est décrit au paragraphe 8 page [*] et qui sert à retrouver la valeur symbolique d'un protocole, connaissant son codage numérique (fichier /etc/protocols).

Ligne 34, 35 et 36
Affichage du numéro de port et du nom du protocole. Notez l'usage de la fonction ntohs pour présenter les octets du numéro de port dans `` le bon ordre '' !

7.1.7 En résumé

...

8 Conversion nom de protocole - N$^{\circ }$ de protocole

Les fonctions getservbyname et getservbyport délivrent un nom de protocole, donc un symbole.

Lors de la déclaration d'une socket le troisième argument est numérique, il est donc nécessaire d'avoir un moyen pour convertir les numéros de protocoles (IPPROTO_UDP, IPPROTO_TCP, IPPROTO_IP,IPPROTO_ICMP,...) en symboles et réciproquement.

Le fichiers /etc/protocols contient cette information, et la paire de fonctions getprotobyname et getprotobynumber l'exploitent.

#include  <netdb.h> 
struct protoent *getprotobyname (const char *name) ;
struct protoent *getprotobynumber (int proto) ;
struct	protoent {
    char	*p_name ;
    char	**p_aliases ;
    int		p_proto ;
} ;

p_name
Le nom officiel du protocol.
p_aliases
La liste des synonymes, le dernier pointeurs est NULL.
p_proto
Le numéro du protocole, dans /etc/services.

\includegraphics{getprotobyname.eps} getprotobyname.c

Le source qui précède donne un exemple de programmation de la fonction getprotobyname. Usage de ce programme :

$ getprotobyname ip
Le protocole ip est reconnu dans /etc/protocols - N : 0
$ getprotobyname tcp
Le protocole tcp est reconnu dans /etc/protocols - N : 6
$ getprotobyname vmtp
Le protocole ipv6 est reconnu dans /etc/protocols - N : 41


9 Diagnostic

Chaque retour de primitive devrait être soigneusement testé, le code généré n'en est que plus fiable et les éventuels disfonctionnements plus aisés à détecter.

Quelques unes des erreurs ajoutées au fichier d'en-tête errno.h

erreur Description de l'erreur
ENOTSOCK Le descripteur n'est pas celui d'une socket
EDESTADDRREQ Adresse de destination requise
EMSGSIZE Le message est trop long
EPROTOTYPE Mauvais type de protocole pour une socket
ENOPROTOOPT Protocole non disponible
EPROTONOSUPPORT Protocole non supporté
ESOCKTNOSUPPORT Type de socket non supporté
EOPNOTSUPP Opération non supportée
EAFNOSUPPOR Famille d'adresse non supportée
EADDRINUSE Adresse déjà utilisée
EADDRNOTAVAIL L'adresse ne peut pas être affectée
ENETDOWN Réseau hors service
ENETUNREACH Pas de route pour atteindre ce réseau
ENETRESET Connexion coupée par le réseau
ECONNABORTED Connexion interrompue
ECONNRESET Connexion interrompue par l'hôte distant
ENOBUFS Le buffer est saturé
EISCONN La socket est déjà connectée
ENOTCONN La socket n'est pas connectée
ESHUTDOWN Transmission après un shutdown
ETIMEDOUT `` time-out '' expiré
ECONNREFUSED Connexion refusée
EREMOTERELEASE L'hôte distant a interrompue sa connexion
EHOSTDOWN L'hôte n'est pas en marche
EHOSTUNREACH Pas de route vers cet hôte

10 Exemples de mise en application

10.1 Ancienne méthode (usage de gethostbyname)

Deux premiers exemples de fonctions de connexion à un serveur : tcp_open et udp_open. Toutes les deux renvoient un descripteur de socket prêt à l'emploi, ou -1 en cas d'erreur (le message d'erreur doit être généré par les fonctions elles mêmes). Ces deux fonctions sont basées sur l'usage de l'api gethosbyname, en voici les prototypes :

int		tcp_open(char *host, char *service, int port) ;
int		udp_open(char *host, char *service, int port, int conn) ;

Description des arguments :

host
Une chaîne de caractères qui est l'adresse de l'hôte distant. Cette adresse est soit sous forme décimale pointée, soit c'est un nom symbolique. Elle ne peut pas être nulle.

service
Si cette chaîne n'est pas nulle, elle contient le nom symbolique du service distant sur lequel il faut se connecter.

port
Le numéro du port distant. S'il est négatif alors service est obligatoirement non nulle. Dans le cas où service est non nulle et port > 0, cette dernière valeur l'emporte sur celle trouvée dans le fichier /etc/services.

conn
Cet argument n'a d'usage que pour la fonction udp_open. Égal à un, il précise que la socket crée est dédiée au serveur host (avec un bind), ie on pourra employer send et recv au lieu de sendto et recvfrom.

\includegraphics{opentcp1.eps}

\includegraphics{opentcp2.eps}

\includegraphics{opentcp3.eps}

open_tcp.c

Exemple d'usage :

$ ./open_tcp localhost
Date  : Sun Dec  2 16:12:57 2001
Temps : 3216294777

Remarque : Pour transmettre la structure d'adresse du serveur à la fonction appelante, La fonction udp_open complète une structure globale.

\includegraphics{openudp1.eps}

\includegraphics{openudp2.eps}

\includegraphics{openudp3.eps} open_udp.c

Exemple d'usage :

$ ./open_udp localhost
Date  : Sun Dec  2 16:12:17 2001
Temps : 3216294737

10.2 Nouvelle méthode (usage de getaddrinfo)

\includegraphics{sockopen1.eps}

Cette nouvelle version combine tcp_open et udp_open en un seul souce. La fonction sock_open génère un descripteur de socket prêt à l'emploi, comme précédemment. Le main appelle quatre fois cette nouvelle fonction avec les mêmes hypothèses de travail que pour les anciennes versions.

\includegraphics{sockopen2.eps}

Exemple d'usage :

Date/udp/srv-sio=Mon Nov  3 19:19:11 2008
Temps/udp/srv-sio=3434725151
Date/tcp/srv-sio=Mon Nov  3 19:19:11 2008
Temps/tcp/srv-sio=3434725151


11 Conclusion et bibliographie

Outre les pages de manuel (man) des primitives et fonctions rencontrées, le lecteur pourra consulter avec grand profit les ouvrages suivants :

RFC 1700
J. Reynolds, J. Postel, `` ASSIGNED NUMBERS '', 10/20/1994. (Pages=230) (Format=.txt) (Obsoletes RFC1340) (STD 2)

Consultez surtour le site http://www.iana.org/

RFC 3493
Basic Socket Interface Extensions for IPv6. R. Gilligan, S. Thomson, J. Bound, J. McCann, W. Stevens. February 2003. (Format: TXT=82570 bytes) (Obsoletes RFC2553) (Status: INFORMATIONAL)

Et des ouvrages de références :

  • Samuel J. Leffler, Robert S. Fabry, William N. Joy, Phil Lapsley -- `` An Advanced 4.4BSD Interprocess Communication Tutorial '' -- CSRG University of California, Berkeley Berkeley, California 94720. Ce document est reédité dans `` Programmer's Supplementary Documents '' éditeur O'Reilly, ou sous forme de fichier ascii dans le répertoire :
    /usr/share/doc/psd/21.ipc/paper.ascii.gz

  • W. Richard Stevens -- `` Unix Network Programming '' -- Prentice All -- 1990

  • W. Richard Stevens -- `` Unix Network Programming '' -- Second edition -- Prentice All -- 1998

  • W. Richard Stevens - Bill Fenner - Andrew M. Rudoff -- `` Unix Network Programming '' -- Third edition, volume 1 -- Prentice All -- 2004

  • Douglas E. Comer - David L. Stevens -- `` Internetworking with TCP/IP - Volume III '' (BSD Socket version) -- Prentice All -- 1993

next up previous contents index
Next: XV Éléments de serveurs Up: D Sockets BSD et Previous: XIII Généralités sur les   Contents   Index
François Laissus 2009-02-27