Remarques préliminaires :
hostname_t et Gethostname()Recherche d'une machine par son nom Internet
exo_01 : ppal()
/etc/hostsRecherche d'une machine par son adresse Internet
CExcFctNet
Classe CHostent
Gethostbyname()
exo_02 : ppal()
Gethostbyaddr()Découverte des services (numéros de port)
CExcAddrIP
AddrIP() V.1
exo_03 : ppal()
Nouvelle version de Gethostbyname()
Recherche d'un service par son nomClients Internet de services universels/etc/servicesRecherche d'un service par son numéro de port
Getservbyname()
CServent
Getservbyname()
exo_04 : ppal()Getservbyport()
exo_05 : ppal()
Liste des machines, et des services disponibles sur la machine hôteClient Internet non connecté d'echo
AddrIP()Client Internet non connecté de daytime
Init_AddrIn()
ClientSockInDgram() et ClientConnectSockIn()
CstCodErrNet.h
exo_06c : ppal() du clientSendto() et Recvfrom()
exo_07c : ppal() du client
Surcharge d'Init_AddrIn()Serveur echo UDP itératif pseudo-parallèle
Getsockname()
ServeurSockIn() et ServeurSockInDgram()
exo_09s : ppal() du serveur
exo_09c : ppal() du client
CExcPortIPServeur getpwnam UDP itératif
Getportbyname()
exo_10s : ppal() du serveur
exo_10c : ppal() du client
exo_11s : ppal() du serveur
exo_11c : ppal() du client
exo_01
int gethostname (char *name, size_t namelen); |
remplit la zone mémoire pointée par name avec le nom de la machine hôte. Le paramètre namelen indique la taille maximale de la zone pointée.
Si le nom de la machine est plus long, le comportement de la fonction dépend largement de l'implémentation :
Si le nom est plus court, un '\0' est ajouté en fin de nom. La taille maximale d'un nom de machine est donnée (en principe ...) par la constante MAXHOSTNAMELEN accessible par le fichier <sys/param.h> [1].
Dans l'espace des noms nsNet, définir le type hostname_t qui doit être un vecteur de caractères de taille suffisante pour recevoir tout nom de machine, et le wrapper de profil suivant :
void Gethostname (hostname_t Nom) throw (CExcFctSyst); |
Dans le fichier exo_01.cxx, écrire un programme qui affiche à l'écran le nom de la machine hôte.
Corrigés : nsNet.h - nsNet.hxx - exo_01.cxx
exo_02
extern int h_errno; |
Dans les fichiers CExcFctNet.h et CExcFctNet.hxx du répertoire tpnet/include, écrire la classe CExcFctNet de l'espace de noms nsNet, dérivée de CExcFct, dont le constructeur utilise h_errno comme code d'erreur. Modifier le fichier INCLUDE_H en conséquence.
La fonction C gethostbyname() renvoie un pointeur vers une structure qui contient des informations concernant la machine dont le nom lui est passé en paramètre.
Cette structure hostent est déclarée ainsi dans le fichier /usr/include/netdb.h :
struct hostent
{ char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses from name server */ #define h_addr h_addr_list[0] /* address, for backward compatiblity */ }; |
Attention : lorsque le nom de la machine passé en paramètre est sous la forme nnn.nnn.nnn.nnn, la fonction gethostbyname() se contente (dans cette implémentation) :
La classe CHostent, de l'espace de noms nsNet, est mise à votre disposition.
Elle est dérivée de la struct hostent et offre les fonctionnalités suivantes :
int GetNbreAliases (void) const throw ();
// renvoie le nombre d'aliases
int GetNbreAddr (void) const throw (); // renvoie le nombre d'adresses Internet // de la liste des adresses const char * Get_h_name (void) const throw (); // renvoie le nom de la machine const char * Get_h_aliases (const int i) const throw (); // renvoie le ieme alias de la // machine (pointeur nul si i est invalide) in_addr Get_h_addr (const int i) const throw (); // renvoie l'adresse Internet de rang i // de la liste des adresses renvoie // l'adresse INADDR_NONE si l'indice est invalide) |
Récupérer les fichiers CHostent.h, CHostent.hxx et CHostent.cxx dans les répertoires appropriés (tpnet/include et tpnet/util : voir Chemins d'accès aux sources des corrigés). Les placer dans vos répertoires correspondants (include et util) et faire les modifications nécessaires des fichiers INCLUDE_H et MakeUtil.
Seuls les corps des deux fonctions GetNbreAliases() et GetNbreAdresses() sont placés dans le fichier CHostent.cxx. Le type in_addr est déclaré dans le fichier <netinet/in.h>.
Passer un peu de temps à analyser cette classe.
CHostent * Gethostbyname (const char * Nom) throw (CExcFctNet) [3]; |
qui renvoie un pointeur vers un objet de la classe CHostent décrite ci-dessus. La fonction C (de niveau 3) gethostbyname() renvoie un pointeur nul en cas d'échec et positionne la variable globale h_errno. Parmi toutes les causes d'erreurs, la plus fréquente est la machine qui n'est pas trouvée. Cette cause d'erreur peut être identifiée si la variable h_errno a pour valeur HOST_NOT_FOUND.
Afin de faciliter la tâche de l'utilisateur, ajouter dans l'exception levée par Gethostbyname() le nom de la machine en cause.
Compléter le fichier Makefile.
Compiler et essayer sur la machine host locale (dont vous avez obtenu le nom dans l'exercice précédent), puis sur n'importe quelle machine que vous pouvez connaître (attention, si elle n'existe pas ou si elle est éloignée et peu fréquemment accédée, ça peut durer ...un certain temps!!). A défaut d'idée, chercher la machine www.altavista.digital.com puis altavista.com. Essayer aussi sur un nom de machine bidon.
Remarque :
Dans cet exercice, vous venez de réaliser une partie de la commande nslookup(8), que vous pouvez essayer :
nslookup www.altavista.digital.com |
Corrigés : exo_02.cxx - Makefile - CExcFctNet.h - CExcFctNet.hxx - INCLUDE_H - MakeUtil - nsNet.h - nsNet.cxx
exo_03
struct hostent *gethostbyaddr (const char *addr, int len, int type); |
Dans la version actuelle de cette fonction, le paramètre addr doit obligatoirement pointer sur une adresse IPv4 (dans l'ordre réseau [5] : malgré les apparences, addr ne pointe pas sur une chaîne de caractères, mais sur le premier octet d'un entier stocké sur 4 octets, un int sur cette machine).
Le paramètre len est obligatoirement le nombre d'octets d'une adresse IPv4 (4 octets).
Le type est obligatoirement une adresse Internet (= AF_INET). Il est donc inutile d'obliger l'utilisateur à utiliser ce profil compliqué.
Dans les fichiers nsNet.h et nsNet.cxx, ajouter le wrapper Gethostbyaddr() de profil :
CHostent * Gethostbyaddr (const in_addr & Adresse) throw (CExcFctNet); |
à laquelle on se contente de passer une adresse Internet de type in_addr (fichier <netinet/in.h>). Comme précédemment, la fonction doit lever une exception CExcFctNet dans tous les cas d'erreurs et ajouter dans le libellé la valeur de l'adresse IP en cause. Comme celle-ci est de type in_addr, donc pas éditable directement, il faut la transformer en une chaîne de caractères, en notation pointée, grâce à la fonction inet_ntoa() (fichier <arpa/inet.h>).
L'adresse IP d'une machine est souvent connue en notation pointée : nnn.nnn.nnn.nnn.
La fonction C inet_aton() (fichier <arpa/inet.h>), de profil :
int inet_aton (const char * cp, struct in_addr * inp); |
convertit l'adresse IPv4 qui lui est passée sous forme d'une chaîne de caractères en notation pointée, par son paramètre cp, en une adresse in_addr (dans l'ordre réseau) qu'elle renvoie par le paramètre inp. Elle renvoie une valeur nulle si l'adresse est invalide.
Dans les fichiers CExcFctNet.h et CExcFctNet.hxx, ajouter la classe CExcAddrIP, dérivée de la classe CExcFct.
Ajouter une donnée-membre m_Adresse de type string.
Le constructeur de la classe CExcAddrIP doit avoir :
On rappelle qu'un destructeur virtuel est nécessaire (car la classe dérive de façon lointaine de CEditable).
Une nouvelle donnée-membre ayant été ajoutée, la fonction _Edit() doit être surchargée.
Comme cela a été fait précédemment, nous centraliserons tous les codes d'erreurs nécessaires aux TPs de réseau dans un fichier unique : recopier le fichier tpsys/include/CstCodErr.h dans tpnet/include/CstCodErrNet.h.
Supprimer tous les codes d'erreurs, et, toujours dans l'espace de noms std, ajouter les codes suivants :
CstAddrIPInval = 201, // Adresse IP invalide
CstPortIPInval = 202, // Port IP invalide CstPortIPNoExist = 203, // Service inexistant |
qui nous serviront ultérieurement.
Mettre à jour les macros CEXCFCTNET_H et CEXCFCTNET_HXX dans le fichier INCLUDE_H et ajouter la macro CSTCODERRNET_H.
Dans les fichiers nsNet.h et nsNet.cxx, ajouter le wrapper AddrIP() de la fonction inet_aton(), de profil :
in_addr AddrIP (const char * Nom) throw (CExcAddrIP); [4] |
qui renvoie l'adresse IP ou lève une exception CExcAddrIP de code CstAddrIPInval si la fonction C inet_aton() qu'il appelle renvoie 0.
Dans le fichier MakeUtil, ajouter la nouvelle dépendance de nsNet.cxx à CstCodErrNet.h.
Recopier le fichier exo_02.cxx dans le fichier exo_03.cxx. Le programme doit maintenant afficher les caractéristiques d'une machine dont l'adresse IP lui est passée en argument de la commande sous forme de chaîne nnn.nnn.nnn.nnn.
Compiler et tester. Essayer par exemple 127.0.0.1. Essayer aussi une adresse invalide (333.333.333.333 par exemple).
Corrigés : exo_03.cxx - nsNet.h - nsNet.hxx - nsNet.cxx - CExcFctNet.h - CExcFctNet.hxx - CstCodErrNet.h
Refaire l'édition de lien du fichier exo_02.o et tester. La commande nslookup(8) déjà citée plus haut effectue le même traitement.
Corrigés : nsNet.h - nsNet.cxx
exo_04
Chaque ligne peut être considérée comme un élément d'un tableau - une entrée (entry) - contenant les informations suivantes :
# Description: The services file lists the sockets and protocols used for
# Internet services. # # Syntax: ServiceName PortNumber/ProtocolName [alias_1,...,alias_n] [#comments] # # ServiceName official Internet service name # PortNumber the socket port number used for the service # ProtocolName the transport protocol used for the service # alias unofficial service names # #comments text following the comment character (#) is ignored |
et, plus loin :
echo 7/tcp
echo 7/udp discard 9/tcp sink null discard 9/udp sink null daytime 13/tcp daytime 13/udp netstat 15/tcp quote 17/udp text |
struct servent * getservbyname (const char * name, const char * proto); |
La structure servent est déclarée ainsi dans le fichier /usr/include/netdb.h :
struct servent
{ char *s_name; /* Nom officiel du service */ char **s_aliases; /* Liste des aliases */ int s_port; /* Numéro de port dans l'ordre réseau */ char *s_proto; /* Protocole à utiliser */ }; |
int GetNbreAliases (void) const throw ();
// renvoie le nombre d'aliases
const char * Get_s_name (void) const throw (); // renvoie le nom officiel du service const char * Get_s_aliases (const int i) const throw (); // renvoie le ieme alias du // service (pointeur nul si // i est invalide) unsigned short Get_s_port (void) const throw (); // renvoie le numero de port du // service (ordre reseau) const char * Get_s_proto (void) const throw (); // renvoie le protocole utilise |
Récupérer les fichiers CServent.h, CServent.hxx et CServent.cxx dans les répertoires appropriés (include et util : voir Chemins d'accès aux sources des corrigés). Les placer dans vos répertoires correspondants (include et util) et faire les modifications nécessaires des fichiers INCLUDE_H et MakeUtil.
CServent * Getservbyname (const char * Nom, const char * Protocole) throw (); |
Modifier le fichiers Makefile : exo_04.cxx dépend de $(CSERVENT_H).
Tester sur des services existant ou pas (à comparer avec le contenu de /etc/services).
Corrigés : exo_04.cxx - Makefile - MakeUtil - INCLUDE_H - nsNet.h - nsNet.hxx
exo_05
A l'espace de noms nsNet (fichiers nsNet.h et nsNet.hxx), ajouter le wrapper de prototype :
CServent * Getservbyport (unsigned short Port, const char * Protocole) throw (); |
qui appelle la fonction C getservbyport(), et renvoie un pointeur sur un objet de la classe CServent contenant les caractéristiques du service dont le numéro de port (en ordre réseau) et le protocole lui sont passés en paramètres. Ces caractéristiques sont celles indiquées dans le fichier /etc/services. Le wrapper renvoie un pointeur nul si le service n'est pas trouvé.
Recopier le fichier exo_04.cxx dans le fichier exo_05.cxx. Le modifier pour qu'il admette comme premier paramètre soit le nom d'un service, soit son numéro de port dans l'ordre host (on se fondera sur le premier caractère, numérique ou non).
Compiler et tester.
Corrigés : exo_05.cxx - nsNet.h - nsNet.hxx
exo_06
void Init_AddrIn (::sockaddr_in & Addr, unsigned short Port,
const ::in_addr & Adresse) throw (); |
A l'espace de noms nsNet (fichiers nsNet.h et nsNet.cxx), ajouter les fonctions ClientSockInDgram() et ClientConnectSockIn() de profils :
int ClientSockInDgram (void) throw (CExcFctSyst);
void ClientConnectSockIn (int sd, const ::in_addr & Adresse, unsigned short int Port) throw (CExcFctSystFile); |
La fonction ClientSockInDgram() est semblable à la fonction ClientSockUnDgram(), étudiée dans le TP sur les sockets Unix. Cependant, l'appel de la fonction bind() n'est pas nécessaire pour que le serveur reçoive l'adresse de l'expéditeur.
La fonction ClientConnectSockIn() effectue une pseudo-connexion entre un socket descriptor et un service désigné par une adresse IP et un numéro de port donné dans l'ordre réseau.
La fonction connect() appelée à l'intérieur doit recevoir une adresse de type sockaddr_in, définie par :
struct sockaddr_in
{ sa_family_t sin_family; // doit être AF_INET unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }; |
CstTimeOut = 204, // Délai expiré |
Essayer plusieurs machines (alpha, romarin, www.altavista.digital.com, etc.., par exemple www.microsoft.com pas toujours très rapide !.).
Après cette tentative peut-être infructueuse, il est temps de doter le client d'un timer non cyclique : ajouter une alarme (fonction alarm()) permettant d'abréger l'attente au bout d'un temps qui sera passé en argument de la commande. Si la lecture s'effectue normalement avant épuisement du délai, il importe de désarmer le timer le plus rapidement possible afin de ne pas être interrompu ultérieurement (::alarm (0)). La réception du signal correspondant doit terminer le programme après affichage d'un message. La constante CstTimeOut peut alors être renvoyée à la fonction main().
Compiler et retester sur les mêmes machines.
Corrigés : CstCodErrNet.h - nsNet.h - nsNet.cxx - exo_06c.cxx
exo_07
size_t Recvfrom (int socket, void * buffer,
size_t length, int flags = 0) throw (CExcFctSystFile); size_t Recvfrom (int socket, void * buffer,
size_t Sendto (int socket, const void * buffer,
|
La première fonction peut être utilisée avec n'importe quel type de socket (par exemple Unix ou Internet) : le processus qui reçoit le datagramme ne cherche pas à connaître l'expéditeur.
Les deux autres fonctions, acceptant une adresse de type sockaddr_in, sont destinées à des applications non connectées communiquant par Internet exclusivement.
Pour interroger le serveur, on peut en principe lui envoyer n'importe quelle donnée, y compris de longueur nulle. Le protocole IP envoie alors une trame vide (ne contenant que les entêtes UDP et IP), et la fonction recvfrom() à l'autre extrémité renvoie 0 (ce qui ne signifie pas une "fin-de-fichier", sans signification en mode non connecté !). Il semble cependant que certaines implémentations n'envoient rien si la longueur est nulle, et il est donc prudent d'envoyer un caractère au moins.
On peut garder le principe de l'alarme introduite dans l'exercice précédent.
Essayer plusieurs machines dont celle sur laquelle vous travaillez.
Corrigés : nsNet.h - nsNet.hxx - exo_07c.cxx
exo_08
Les profils des fonctions (pas tous dans le man) sont les suivants :
void sethostent (int stayopen);
struct hostent * gethostent (void); void endhostent (void); |
Dans le fichier exo_08.cxx, écrire un programme qui affiche les noms de toutes les machines répertoriées dans le fichier local de la base de données réseau de la machine hôte, /etc/hosts. Utiliser pour cela les fonctions sethostent(), gethostent() et endhostent() décrites ci-dessus.
Compiler et tester.
Symétriquement existent les trois fonctions setservent(), getservent() et endservent() qui explorent le fichier /etc/services. Compléter le fichier exo_08.cxx pour qu'il affiche aussi la liste des services disponibles.
Corrigés : exo_08.cxx
exo_09
Le champ sin_port doit être le numéro de port (numéro de service) que le serveur met à disposition. Il doit être exprimé en ordre "réseau". S'il s'agit d'un service nouvellement créé (non encore répertorié par l'administrateur-système dans le fichier /etc/services), il est conseillé de ne pas donner un numéro de port particulier, mais de le laisser choisir par le système. Ainsi, on peut être assuré que le service n'existe pas au moment où le serveur demande sa création. Pour cela il faut mettre le champ sin_port à zéro. Après exécution de la fonction bind(), le numéro de port devra être récupéré (voir plus loin la fonction getsockname()).
Le champ sin_addr doit contenir l'adresse IP de la machine qui héberge le service (donc le serveur, donc le processus en train d'être écrit).
Cette adresse peut être obtenue par la fonction gethostname() par exemple, mais il est conseillé d'utiliser plutôt l'adresse "passe-partout" INADDR_ANY qui laisse au système le soin de remplir ce champ.
Rappelons en effet qu'une même machine peut avoir plusieurs adresses IP et qu'il n'y a aucune raison qu'un serveur n'accepte de demandes de connexion que sur une seule de ses adresses IP.
La macro INADDR_ANY n'est pas de type in_addr, nécessaire pour être utilisée comme paramètre de Init_AddrIn().
Afin d'éviter à l'utilisateur de devoir systématiquement résoudre ce problème (un simple transtypage ne suffit pas ici), il est plus élégant de proposer une surcharge de la fonction admettant une adresse IP de type unsigned long :
void Init_AddrIn (::sockaddr_in & Addr,
unsigned short Port = 0, unsigned long AdresseIP = INADDR_ANY) throw (); |
void Getsockname (int sd, sockaddr_in & Addr) throw (CExcFctSystFile); |
Analogue au wrapper Getsockname(), écrit pour les sockets Unix, elle remplit une structure sockaddr_in à partir d'un descripteur sd de socket Internet, ce qui permet de connaître l'adresse IP et le numéro de port correspondant.
Ajouter à l'espace de noms nsNet (fichiers nsNet.h et nsNet.cxx) les fonctions de profils :
int ServeurSockIn (unsigned short & NumPort, const int Type) throw (CExcFctSyst);
int ServeurSockInDgram (unsigned short & NumPort) throw (CExcFctSyst); |
Comme précédemment la fonction ServeurSockUn(), la fonction ServeurSockIn() doit être privée (dans l'espace de noms anonyme du fichier nsNet.cxx). En résumé, elle appelle successivement les fonctions socket(), bind() puis getsockname() afin de récupérer le numéro de port.
La fonction ServeurSockInDgram() se contente d'appeler la fonction ServeurSockIn() avec le type de socket SOCK_DGRAM.
Dans le fichier exo_09s.cxx, écrire le serveur UDP sur Internet qui renvoie la date à tout client qui interroge sa socket (faire une boucle infinie). Passer le numéro de port de la socket en paramètre de la commande (essayer le numéro qui vous a été attribué, puis essayer la valeur 0). Après création de la socket par appel de la fonction ServeurSockInDgram(), afficher le numéro de port renvoyé par la fonction.
Recopier le fichier exo_07c.cxx dans le fichier exo_09c.cxx pour qu'il puisse tester votre serveur : il doit lire le numéro de port en argument. Vérifier que la réponse qu'il reçoit est bien en provenance du serveur auquel il s'est adressé.
Compiler et tester.
Corrigés : nsNet.h - nsNet.hxx - nsNet.cxx - exo_09s.cxx - exo_09c.cxx
exo_10
Tout d'abord ajouter l'exception CExcPortIP analogue à l'exception CExcAddrIP dans les fichiers adéquats. La donnée-membre m_Port stocke le numéro de port invalide sous forme de chaîne de caractères (string).
Puis ajouter à l'espace des noms nsNet (fichiers nsNet.h et nsNet.cxx) les fonctions :
unsigned short Getportbyname (const char * Nom) throw (CExcPortIP);
unsigned short Getportbyname (const char * Nom, const char * Protocole) throw (CExcPortIP); |
Attention : ces fonctions ne sont pas des wrappers de fonctions systèmes, mais des utilitaires permettant de transformer un numéro de port passé sous forme d'une chaîne, ou un couple {nom de service, nom de protocole} en un numéro de port directement utilisable (en ordre réseau).
La fonction Getportbyname() renvoie le numéro de port (en "ordre réseau")
Recopier le fichier exo_09s.cxx dans le fichier exo_10s.cxx. Modifier le serveur UDP pour qu'il renvoie à tout client le message qu'il en reçoit (faire une boucle infinie).
Recopier le fichier exo_09c.cxx dans le fichier exo_10c.cxx. Ajouter l'argument délai (en secondes) à la commande de lancement du client. Utiliser la fonction Getportbyname() ci-dessus pour convertir le numéro de port.
Le client doit envoyer au serveur plusieurs fois (par exemple 5 fois) le même message par exemple "Emission toutes les xxx secondes", puis récupérer et afficher la réponse du serveur à l'écran. Tester avec plusieurs clients simultanés, de périodicités différentes.
Corrigés : nsNet.h - nsNet.cxx - CExcFctNet.h - CExcFctNet.hxx - exo_10s.cxx - exo_10c.cxx
exo_11
Ecrire dans le fichier exo_11c.cxx le client UDP qui envoie au serveur un username, et affiche le répertoire home et le shell qu'il reçoit en retour. Si le username n'existe pas, il reçoit seulement le message : "username inconnu".
Corrigés : exo_11s.cxx - exo_11c.cxx
[1] En réalité, définie dans le fichier <asm/param.h> dans la version actuelle de Linux RedHat 6, et vaut 64.
[2] La version actuelle de la fonction Gethostbyname() ne comporte qu'une ligne et mériterait d'être inline. Cependant, elle sera augmentée dans un exercice ultérieur, ce qui justifie sa présence dans le fichiers nsNet.cxx.
[3] Le profil actuel de la fonction Gethostbyname() sera complété dans l'exercice suivant.
[4] Le profil actuel de la fonction AddrIP() sera complété ultérieurement.
[5] Rappel : les transformations d'ordre "host" en ordre "net" et inverses sont effectuées au moyen des fonctions C ntohs(), htons(), ntohl(), htonl().