IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Cours d'introduction à TCP/IP


précédentsommairesuivant

14. Compléments sur les sockets Berkeley

14-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.

Il y a deux manières d'assigner un N° de port à une socket :

  • Le processus spécifie le numéro. C'est typiquement ce que fait un serveur. On suppose bien évidemment que les clients sont au courant ou qu'ils disposent d'un mécanisme qui peut les renseigner (cf cours sur les RPC) ;
  • 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(178).

Codé sur deux octets non signés, le numéro de port permet 65 536 possibilités de 0 à 65 535. 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...

14-1-1. Réservation de port - Ancienne méthode

  • Port N° 0 ce numéro n'est pas utilisable pour une application, c'est une sorte de « joker » 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és 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 à 65 535 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 !

14-1-2. Réservation de port - Nouvelle méthode

  • Port N° 0 ce numéro n'est pas utilisable pour une application, c'est une sorte de « joker » qui indique au système que c'est à lui de compléter automatiquement le numéro (voir plus loin de 49 152 à 65 535).
  • 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 à 49 151 est la zone des services enregistrés par l'IANA et qui fonctionnent avec des droits ordinaires.
  • Port de 49 152 à 65 535 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.

14-2. Ordre des octets sur le réseau

Nous reprenons ici un point déjà évoqué plus tôt :

Image non disponible
figure XIII.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 ne transforme pas les octets de la couche Internet qui elle-même ne modifie pas ceux de la couche de Transport(179).

Pour cette raison, le numéro de port inscrit dans l'entê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 cas(180) !

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 prototypes :

 
Sélectionnez
#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 */

Par exemple, pour affecter le numéro de port 13 (service « daytime ») au champ sin port d'une structure de type sockaddr in :

 
Sélectionnez
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'affectation du même numéro de port, il eût fallu écrire :

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

14-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 repré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 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.

 
Sélectionnez
#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 existe des outils similaires, issus du système V : memcpy, memcmp, memset...

14-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.

14-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éseau. Dans le cadre d'un nouveau développement, on leur préfèrera les fonctions décrites après.

 
Sélectionnez
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr (char *) ; /* in_addr_t == unsigned long */
char * inet_ntoa (struct in_addr) ;

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.

Exemple d'utilisation :

 
Sélectionnez
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.

14-4-2. Conversion d'adresse - Compatible IPv4 et IPv6

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

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

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.

Exemple d'utilisation :

 
Sélectionnez
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.

14-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 , 6.1 et 6.2 présentent une approche traditionnelle, seulement valable avec IPv4 alors que le paragraphe 7 expose une démarche beaucoup plus récente et adaptée également à IPv6. L'écriture de nouveaux codes ne devrait faire appel qu'à cette nouvelle API.

14-5-1. Une adresse IP à partir d'un nom d'hôte

 
Sélectionnez
#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]

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.

gethostbyname.c
Sélectionnez
/*
* $Id: gethostbyname.c 46 2007-12-03 19:39:16Z fla $
* Exemple d'utilisation de la fonction "gethostbyname".
*/
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/socket.h> /* AF_INET */
#include <netinet/in.h> /* struct in_addr */
#include <netdb.h> /* gethostbyname */
#include <arpa/inet.h> /* inet_ntoa */
#define DIT_FAMILY(x) (x)==AF_UNSPEC?"AF_UNSPEC":(x)==AF_UNIX?"AF_UNIX":\
(x)==AF_INET?"AF_INET":(x)==AF_INET6?"AF_INET6":"other..."
#define USAGE "Usage:%s liste de machines distantes\n"

void
impnet(struct in_addr **list)
{
   struct in_addr *adr ;
   while ((adr = *list++))
      (void)printf("Adresse Internet : %s\n",inet_ntoa(*adr)) ;
}

int
main(int argc,char *argv[])
{
   register char *ptr ;
   register struct hostent *pth ;
   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   while (--argc > 0) {
      ptr = *++argv ;
      if (!(pth = gethostbyname(ptr))) {
         (void)fprintf(stderr,"%s : hote inconnu !\n",ptr) ;
         exit(EX_SOFTWARE) ;
      }
      (void)printf ("Nom officiel : %s\n",pth->h_name) ;
      while ((ptr = *(pth->h_aliases))!=NULL) {
         (void)printf("\talias : %s\n",ptr) ;
         pth->h_aliases++ ;
      }
      (void)printf("Type d'adresse : %s\n",DIT_FAMILY(pth->h_addrtype)) ;
      if (pth->h_addrtype == PF_INET)
         impnet((struct in_addr **)pth->h_addr_list) ;
      else
         (void)printf("Type d'adresse non reconnu !\n") ;
   }
   exit(EX_OK) ;
}

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

 
Sélectionnez
$ 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 !

14-5-2. Un nom d'hôte à partir d'une adresse IP

le problème inverse se résout avec la fonction gethostbyaddr. La définition du prototype se trouve au même endroit que précédemment, la fonction renvoie un pointeur sur une structure du type hostent.

 
Sélectionnez
1.
2.
3.
#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

14-6. Conversion N° de port &#8211; service

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

14-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.

 
Sélectionnez
#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 alias 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

 
Sélectionnez
$ 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
getservbyname.c
Sélectionnez
/*
* $Id: getservbyname.c 47 2007-12-03 19:41:04Z fla $
* Exemple d'utilisation de la fonction "getservbyname".
*/
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <sys/types.h>
#include <netinet/in.h> /* Pour "ntohs" */
#include <netdb.h> /* Pour "getservbyname" */

#define USAGE "Usage:%s <nom de service> [<nom de protocole>]\n"
#define MSG1 "Le service %s est reconnu dans /etc/services\nprotocole :%s - N° de port :%d\n"
#define MSG2 "Le service %s (%s) est introuvable dans /etc/services !\n"

int
main(int argc,char *argv[])
{
   struct servent *serv ;

   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   if ((serv = getservbyname(argv[1],argv[2]?argv[2]:"tcp")))
      (void)printf(MSG1,serv->s_name,serv->s_proto,ntohs(serv->s_port)) ;
   else
      (void)printf(MSG2,argv[1],argv[2]?argv[2]:"") ;
   exit(EX_OK) ;
}

14-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é.

 
Sélectionnez
#include <netdb.h>
struct servent * getservbyport (int port, char *proto) ;

Exemples d'usage :

 
Sélectionnez
$ 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 :

getservbyport.c
Sélectionnez
/*
* $Id: getservbyport.c 2 2007-09-12 20:00:17Z fla $
* Exemple d'utilisation de la fonction "getservbyport".
*/
#include <stdio.h>
#include <stdlib.h> /* Pour "atoi". */
#include <sysexits.h>
#include <sys/types.h>
#include <netinet/in.h> /* Pour "ntohs" */
 #include <netdb.h> /* Pour "getservbyport" */

#define USAGE "Usage:%s <numéro de port> [<nom de protocole>]\n"
#define MSG1 "Le port %d correspond au service \"%s\" (protocole %s).\n"
#define MSG2 "Le port %s (%s) est introuvable dans /etc/services !\n"

int
main(int argc,char *argv[])
{
   struct servent *serv ;
   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   if ((serv = getservbyport(atoi(argv[1]),argv[2]?argv[2]:"tcp")))
      (void)printf(MSG1,ntohs(serv->s_port),serv->s_name,serv->s_proto) ;
   else
      (void)printf(MSG2,argv[1],argv[2]?argv[2]:"") ;
   exit(EX_OK) ;
}

14-7. Getaddrinfo, pour IPv4 et IPv6

Les API 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 et la manipulation des adresses IP est rendue plus aisée en retour, puisque 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 qui ne contient que les adresses IP.

14-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.

14-7-1-1. Prototype de getaddrinfo
 
Sélectionnez
#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 apparaît 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'erreur supplémentaires spécialisés.

14-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. Ils 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.

14-7-1-3. La structure addrinfo

Voici les membres de la structure addrinfo, définis dans le fichier d'entête netdb.h :

 
Sélectionnez
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 autoré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ée(181) 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'occurrence), 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.

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 primitive 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édemment 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, 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).
14-7-1-4. En résumé

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

14-7-1-5. Exemple d'usage à la place de gethostbyname

Cet exemple remplace le code précédent. Le code n'est pas plus simple.

getaddrinfo 1.c
Sélectionnez
/*
* $Id$
* Exemple d'utilisation de la fonction "getaddrinfo" avec IPv4
* et en remplacement de 'gethostbyname'
*/
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h> /* AF_INET */
#include <netinet/in.h> /* struct in_addr */
#include <netdb.h> /* getaddrinfo */
#include <arpa/inet.h> /* inet_ntop */

#define USAGE "Usage:%s liste de machines distantes\n"
#define DIT_FAMILY(x) (x)==AF_UNSPEC?"AF_UNSPEC":(x)==AF_UNIX?"AF_UNIX":\
(x)==AF_INET?"AF_INET":(x)==AF_INET6?"AF_INET6":"other..."

int
main(int argc,char *argv[])
{
   char *pt ;
   int ret ;
   char buf[INET_ADDRSTRLEN] ;
   struct addrinfo profil,*lstres,*lstres0 ;
   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   while (--argc > 0) {
      pt = *++argv ;
      bzero(&profil,sizeof(profil)) ;
      profil.ai_flags = AI_CANONNAME ;
      profil.ai_family = AF_INET ;
      profil.ai_socktype = SOCK_DGRAM ;
      if ((ret=getaddrinfo(pt,NULL,&profil,&lstres0))) {
         (void)fprintf(stderr,"%s",gai_strerror(ret)) ;
         exit(EX_SOFTWARE) ;
      }
      (void)printf ("Nom officiel :%s\n",lstres0->ai_canonname) ;
      (void)printf("Type d'adresse :%s\n", DIT_FAMILY(lstres0->ai_socktype)) ;
      for (lstres=lstres0;lstres!=NULL;lstres=lstres->ai_next)
         (void)printf("Adresse Internet : %s\n",\
      inet_ntop(lstres->ai_socktype,
                        &((struct sockaddr_in*)lstres->ai_addr)->sin_addr,
                        buf,sizeof buf));

   }
   freeaddrinfo(lstres0) ; /* Ne sert à rien ici ! */
   exit(EX_OK) ;
}
  • Ligne 13 Il faut inclure ce fichier d'entête 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 parce que 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.
14-7-1-6. Exemple d'usage à la place de getservbyname

Cet exemple remplace le code précédent.

getaddrinfo 2.c
Sélectionnez
/*
* $Id$
* Exemple d'utilisation de la fonction "getaddrinfo" avec IPv4
* et en remplacement de 'getservbyname'
*/
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h> /* AF_INET */
#include <netdb.h> /* getaddrinfo */

#define USAGE "Usage:%s <nom de service>\n"

int
main(int argc,char *argv[])
{
   int ret ;
   struct addrinfo profil,*lstres,*lstres0 ;
   struct protoent *myproto ;
   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   bzero(&profil,sizeof(profil)) ;
   profil.ai_family = AF_INET ;
   if ((ret=getaddrinfo(NULL,argv[1],&profil,&lstres0))) {
      (void)fprintf(stderr,"%s\n",gai_strerror(ret)) ;
      exit(EX_SOFTWARE) ;
   }
   for (lstres=lstres0;lstres !=NULL;lstres=lstres->ai_next) {
      myproto = getprotobynumber(lstres->ai_protocol) ;
      (void)printf("Port : %d - Protocole : %s\n",\
                              ntohs(((struct sockaddr_in*)lstres->ai_addr)->sin_port),
                              myproto->p_name) ;
   }
   freeaddrinfo(lstres0) ; /* Ne sert à rien ici ! */
   exit(EX_OK) ;
}
  • Ligne 27 on précise IPv4.
  • Ligne 33 usage de la fonction getprotobynumber dont le prototype est décrit au paragraphe 8 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 » !

14-7-1-7. En résumé

...

14-8. Conversion nom de protocole - N° 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/getprotobynumber l'exploite.

 
Sélectionnez
#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 protocole.
  • p aliases La liste des synonymes, le dernier pointeur est NULL.
  • p proto Le numéro du protocole, dans /etc/services.
getprotobyname.c
Sélectionnez
/*
* $Id: getprotobyname.c 2 2007-09-12 20:00:17Z fla $
* Exemple d'utilisation de la fonction "getprotobyname".
*/
#include <stdio.h>
#include <sysexits.h>
#include <netdb.h> /* Pour getprotobyname */

#define USAGE "Usage:%s <nom du protocole>\n"
#define MSG1 "Le protocole %s est reconnu dans /etc/protocols - N° : %d\n"
#define MSG2 "Le protocole %s est introuvable dans /etc/protocols !\n"

int
main(int argc,char *argv[])
{
   struct protoent *proto ;

   if (argc <2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit(EX_USAGE) ;
   }
   if (proto = getprotobyname(argv[1]))
      (void)printf(MSG1,proto->p_name,proto->p_proto) ;
   else
      (void)printf(MSG2,argv[1]) ;
   exit(EX_OK) ;
}

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

 
Sélectionnez
$ 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

14-9. Diagnostic

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

Quelques-unes des erreurs ajoutées au fichier d'entê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'adresses 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 interrompu sa connexion
EHOSTDOWN L'hôte n'est pas en marche
EHOSTUNREACH Pas de route vers cet hôte

14-10. Exemples de mise en application

14-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 :

 
Sélectionnez
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), i.e. on pourra employer send et recv au lieu de sendto et recvfrom.
 
Sélectionnez
/* $Id: open_tcp1.c 2 2007-09-12 20:00:17Z fla $
*
* Fonction "tcp_open" + exemple d'utilisation avec "daytime"
* et "time".
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#define USAGE "Usage:%s <nom de machine distante>\n"

struct sockaddr_in tcp_serv_addr ; /* Adresse Internet du serveur. */
struct servent tcp_serv_info ; /* Infos de "getservbyname". */
struct hostent tcp_host_info ; /* Infos de "gethostbyname". */
open tcp.c
Sélectionnez
int
tcp_open(char *host,char *service,int port)
{
   int fd ;
   unsigned long inaddr ;
   struct hostent *hp ;
   struct servent *sv ;

   bzero ((char *)&tcp_serv_addr, sizeof tcp_serv_addr) ;
   tcp_serv_addr.sin_family = AF_INET ;
   if (*service) {
      if (!(sv = getservbyname(service,"tcp"))) {
         (void)fprintf(stderr,"tcp_open:service inconnu:%s/tcp\n",service) ;
      return -1 ;
   }
   tcp_serv_info = *sv ;
   if (port > 0)
      tcp_serv_addr.sin_port = htons(port) ;
   else
      tcp_serv_addr.sin_port = sv->s_port ;
   }
   else {
      if (port <= 0) {
         (void)fprintf(stderr,"tcp_open:numéro de port non spécifié !\n") ;
         return -1 ;
      }
      tcp_serv_addr.sin_port = htons(port) ;
   }

   if ((inaddr = inet_addr(host)) != INADDR_NONE) { /* netid.hostid ? */
      bcopy((char *)&inaddr,(char *)&tcp_serv_addr.sin_addr,sizeof inaddr)
      cp_host_info.h_name = (char *)NULL ;
   }
   else {
      if (!(hp = gethostbyname(host))) {
         (void)fprintf(stderr,"tcp_open: erreur de nom de machine : %s : %s\n",
         host,strerror(errno)) ;
         return -1 ;
      }
      tcp_host_info = *hp ;
      bcopy(hp->h_addr,(char *)&tcp_serv_addr.sin_addr,hp->h_length) ;
   }

   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      (void)fprintf(stderr,"tcp_open: impossible de créer la socket !\n") ;
      return -1 ;
   }
   if (connect(fd,(struct sockaddr *)&tcp_serv_addr,sizeof tcp_serv_addr)<0)
      (void)fprintf(stderr,"tcp_open: impossible de se connecter !\n") ;
      (void)close(fd) ;
      return -1 ;
   }

   return fd ;
}

int
main(int argc,char *argv[])
{
   int sfd ;
   int n ;
   unsigned long temps ;
   char buf[256] ;
   if (argc < 2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit (EX_USAGE) ;
   }

   /*
   * Connexion au serveur de date
   */
   if ((sfd = tcp_open(argv[1],"daytime",0)) < 0)
      exit(EX_SOFTWARE) ;
   if ((n=read(sfd,(void *)buf, sizeof(buf)-1))< 0)
      perror("read/daytime") ;
   else {
      buf[n]='\0';
      (void)printf("Date(%s)=%s",argv[1],buf) ;
   }
   (void)close(sfd) ;

   /*
   * Connexion au serveur de temps
   */
   if ((sfd = tcp_open(argv[1],"",37)) < 0)
      exit (EX_SOFTWARE) ;
   if (read(sfd,(void *)&temps, sizeof temps) < 0)
      perror("read/port 37") ;
   else
      (void)printf("Temps(%s)=%lu\n",argv[1],ntohl(temps)) ;

   exit (EX_OK) ;
}

Exemple d'usage :

 
Sélectionnez
$ ./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.

 
Sélectionnez
/* $Id: open_udp1.c 2 2007-09-12 20:00:17Z fla $
*
* Fonction "udp_open" + exemple d'utilisation avec "daytime"
* et "time".
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#define USAGE "Usage:%s <nom de machine distante>\n"
struct sockaddr_in udp_serv_addr ; /* Adresse Internet du serveur. */
struct sockaddr_in udp_cli_addr ; /* Adresse Internet du client. */
struct servent udp_serv_info ; /* Infos de "getservbyname". */
open udp.c
Sélectionnez
int
udp_open(char *host,char *service,int port,int conn)
{
   int fd ;
   unsigned long inaddr ;
   struct hostent *hp ;
   struct servent *sv ;

   bzero ((char *)&udp_serv_addr, sizeof udp_serv_addr) ;
   udp_serv_addr.sin_family = AF_INET ;
   if (*service) {
      if (!(sv = getservbyname(service,"udp"))) {
         (void)fprintf(stderr,"udp_open:service inconnu:%s/udp\n",service) ;
         return -1 ;
      }
      udp_serv_info = *sv ;
      if (port > 0)
         udp_serv_addr.sin_port = htons(port) ;
      else
         udp_serv_addr.sin_port = sv->s_port ;
   }
   else {
      if (port <= 0) {
         (void)fprintf(stderr,"udp_open:numéro de port non spécifié !\n") ;
         return -1 ;
      }
      udp_serv_addr.sin_port = htons(port) ;
   }

   if ((inaddr = inet_addr(host)) != INADDR_NONE) { /* netid.hostid ? */
      bcopy ((char *)&inaddr,(char *)&udp_serv_addr.sin_addr,sizeof inaddr)
      udp_host_info.h_name = (char *)NULL ;
   }
   else {
      if (!(hp = gethostbyname(host))) {
         (void)fprintf(stderr,"udp_open:erreur de nom de machine:%s:%s\n",
                  host,strerror(errno)) ;
         return -1 ;
      }
      udp_host_info = *hp ;
      bcopy(hp->h_addr,(char *)&udp_serv_addr.sin_addr,hp->h_length) ;
   }
   if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
      (void)fprintf(stderr,"udp_open: impossible de créer la socket !\n") ;
      return -1 ;
   }
   bzero ((char *)&udp_cli_addr,sizeof udp_cli_addr) ;
   udp_cli_addr.sin_family = AF_INET ;
   udp_cli_addr.sin_addr.s_addr = htonl(INADDR_ANY) ;
   udp_cli_addr.sin_port = htons(0) ;
   if (bind(fd,(struct sockaddr *)&udp_cli_addr,sizeof udp_cli_addr) < 0) {
      (void)fprintf(stderr,"udp_open:erreur avec l'adresse locale !\n") ;
      (void)close(fd) ;
      return -1 ;
   }
   if (conn == 1) { /* Figer l'adresse du serveur. */
      if (connect(fd,(struct sockaddr *)&udp_serv_addr,
            sizeof udp_serv_addr)<0) {
         (void)fprintf(stderr,"udp_open: connexion impossible avec %s !\n",\
         (void)close(fd) ;
         return -1 ;
      }
   }
   return fd ;
}

int
main(int argc,char *argv[])
{
   int sfd ;
   int n ;
   unsigned long temps ;
   char buf[256] ;

   if (argc < 2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit (EX_USAGE) ;
   }

   /*
   * Connexion au serveur de date
   */
   if ((sfd = udp_open(argv[1],"daytime",0,1)) < 0)
      exit(EX_SOFTWARE) ;
   if (send(sfd,(void *)" ",1,0) < 0)
      perror("send") ;
   else
      if ((n=recv(sfd,(void *)buf, sizeof buf,0)) < 0)
         perror("recv") ;
      else {
         buf[n]='\0' ;
         (void)printf("Date(%s)=%s",argv[1],buf) ;
      }
   (void)close(sfd) ;

   /*
   * Connexion au serveur de temps
   */
   if ((sfd = udp_open(argv[1],"",37,0)) < 0)
      exit(EX_SOFTWARE) ;

   n = sizeof udp_serv_addr ;
   if (sendto(sfd,(void *)" ",1,0,(void *)&udp_serv_addr, n) < 0)
      perror("sendto") ;
   else
      if (recvfrom(sfd,(void *)&temps,sizeof temps,0,\
                  (void *)&udp_serv_addr, &n) < 0)
         perror("recvfrom") ;
      else
         (void)printf("Temps(%s)=%lu\n",argv[1],ntohl(temps)) ;
         exit(EX_OK);
}

Exemple d'usage :

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

14-10-2. Nouvelle méthode (usage de getaddrinfo)

Cette nouvelle version combine tcp open et udp open en un seul source. 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.

 
Sélectionnez
/* $Id$
*
* Fonction "sock_open" + exemple d'utilisation avec "daytime"
* et "time" en UDP et TCP.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sysexits.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#define USAGE "Usage:%s <host distant>\n"

int
sock_open(char *host,char *service,int socktype)
{
   int ret ;
   int mysoc ;
   struct addrinfo profil, *res, *lstres0 ;
   bzero(&profil,sizeof(profil)) ;
   profil.ai_family = AF_INET ;
   profil.ai_socktype = socktype ;
   profil.ai_flags = AI_PASSIVE ;
   if ((ret = getaddrinfo(host,service,&profil,&lstres0))) {
      (void)fprintf(stderr,"%s",gai_strerror(ret)) ;
      return -1 ;
   }
   for (res=lstres0;res!=NULL;res=res->ai_next) {
      if ((mysoc=socket(res->ai_family,res->ai_socktype,
                     res->ai_protocol)) < 0)
         continue ;
      if (connect(mysoc,res->ai_addr,res->ai_addrlen) == 0)
         break ; /* Gotcha ! */
      (void)close(mysoc) ; /* inutilisable */
      mysoc=-1 ;
   }
   freeaddrinfo(lstres0) ;
   if (mysoc < 0)
      (void)fprintf(stderr,"Unable to connect to %s:%s\n",host,service) ;
   return mysoc ;
}

int
main(int argc,char *argv[])
{
   int sfd ;
   int n ;
   unsigned long temps ;
   char buf[256] ;

   if (argc < 2) {
      (void)fprintf(stderr,USAGE,argv[0]) ;
      exit (EX_USAGE) ;
   }
   /*
   * Connexion au serveur de date avec UDP (socket dédiée)
   */
   if ((sfd = sock_open(argv[1],"daytime",SOCK_DGRAM)) >= 0) {
      if (send(sfd,(void *)" ",1,0) < 0)
         perror("send - Daytime") ;
   else
      if ((n=recv(sfd,(void *)buf, sizeof(buf)-1,0)) < 0)
         perror("recv - Daytime") ;
      else {
         buf[n]='\0' ;
         (void)printf("Date/udp/%s=%s",argv[1],buf) ;
      }
      (void)close(sfd) ;
   }
   /*
   * Connexion au serveur de temps avec UDP (socket dédiée)
   */
   if ((sfd = sock_open(argv[1],"37",SOCK_DGRAM)) >= 0) {
      if (send(sfd,(void *)" ",1,0) < 0)
      perror("send - Time") ;
   else
      if (recv(sfd,(void *)&temps,sizeof temps,0) < 0)
         perror("recv - Time") ;
      else
         (void)printf("Temps/udp/%s=%u\n",argv[1],ntohl(temps)) ;
         (void)close(sfd) ;
   }
   /*
   * Connexion au serveur de date avec TCP
   */
   if ((sfd = sock_open(argv[1],"daytime",SOCK_STREAM)) >= 0) {
      if ((n=read(sfd,(void *)buf, sizeof(buf)-1))< 0)
         perror("read - Daytime") ;
      else {
         buf[n]='\0';
         (void)printf("Date/tcp/%s=%s",argv[1],buf) ;
      }
      (void)close(sfd) ;
   }
   /*
   * Connexion au serveur de temps avec TCP
   */
   if ((sfd = sock_open(argv[1],"37",SOCK_STREAM)) >= 0) {
      if (read(sfd,(void *)&temps, sizeof temps) < 0)
         perror("read - Time") ;
   else
      (void)printf("Temps/tcp/%s=%u\n",argv[1],ntohl(temps)) ;
   (void)close(sfd) ;
   }

   exit(EX_OK);
}

Exemple d'usage :

 
Sélectionnez
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

14-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 surtout 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érence :

  • 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 réé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

précédentsommairesuivant
http://www.iana.org/assignments/port-numbers pour se tenir au courant des évolutions de cette liste
Nous n'abordons pas ici la question de la transmission de données hétérogènes au niveau applicatif, elle sera examinée dans le cours sur les XDR
Pour les machines qui respectent naturellement le NBO, comme les stations HP (processeur risc) ou SUN (processeur sparc), IBM (processeur ppc) ces fonctions sont des macros « vides » contrairement à toutes les architectures de type i386
Type Abstrait de Données (TAD) très répandu, cf : http://fr.wikipedia.org/wiki/Liste_chainee

Copyright © 2009 François Laissus. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.