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 :
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 :
#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 :
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 :
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.
#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.
#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 :
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 :
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 :
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▲
#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.
/*
* $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
(
"
\t
alias : %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
$ 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.
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 – 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.
#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
$ 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
/*
* $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é.
#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 :
/*
* $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▲
#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 :
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.
/*
* $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.
/*
* $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.
#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.
/*
* $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 :
$ 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 :
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.
/* $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". */
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 :
$ ./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.
/* $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". */
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 :
$ ./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.
/* $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 :
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