Book 6
Book 6
Book 6
Décembre 2018
2
Table des matières
I Environnement et commandes de base 7
1 Les adresses et les chiers de conguration 9
1.1 L'adressage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.1.1 Le principe d'adressage d'origine . . . . . . . . . . . . . . . . . . . . . . . . 9
1.1.2 Adresses particulières . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.3 Adresses de multidistribution (classe D) . . . . . . . . . . . . . . . . . . . . 11
1.1.4 Adresses de classe E . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.1.5 Multidomicile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Agrégation d'adresses et sous-adressage . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.1 Sous-réseaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.2 Adressage agrégé ou sur-adressage . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Routage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1 Tables de routage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.2 L'interface IP / Ethernet . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Les chiers de conguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.1 Le chier /etc/hosts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.2 Le chier /etc/networks . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.3 Le chier /etc/services . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.4 Le chier /etc/protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.5 Cas des NIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Les processus démons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5.2 Le super-démon inetd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.5.3 Le chier /etc/inetd.conf . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6 Les chiers d'équivalence UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.1 Le chier /etc/hosts.equiv . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.2 Le chier /.rhosts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3
4 TABLE DES MATIÈRES
II Programmation réseaux 23
3 Les structures et les fonctions d'accès 25
3.1 Les chiers d'include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Les librairies standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3 Les types Posix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.4 Les adresses des machines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.5 Les fonctions de manipulation des adresses . . . . . . . . . . . . . . . . . . . . . . . 26
3.5.1 inet_aton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.5.2 inet_ntoa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.6 La représentation des nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.7 La structure hostent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.8 Les fonctions de consultation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.8.1 gethostname . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.8.2 gethostbyname . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.8.3 gethostbyaddr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4 Les sockets 29
4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.1 Qu'est-ce qu'une socket ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.2 Type d'une socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.3 Domaine d'une socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2 Primitives communes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2.1 La création d'une socket : socket . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2.2 La suppression d'une socket : close . . . . . . . . . . . . . . . . . . . . . . 31
4.2.3 L'attachement d'une socket à une adresse : bind . . . . . . . . . . . . . . . 31
4.3 La scrutation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3.2 La primitive select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
7
Chapitre 1
1.1 L'adressage
Une ou plusieurs adresses logiques sont attribuées à chaque machine hôte. L'adresse INTER-
NET ou adresse IP est constituée d'une adresse de réseau et d'une adresse de machine sur ce
réseau. Une adresse IPv4 occupe 4 octets ou 32 bits, une adresse IPv6 128 bits. Les adresses IPv4
sont en général données sous la forme n1.n2.n3.n4 (chacun des n représente la valeur d'un octet
comprise entre 0 et 255).
Classe Intervalle
A 0.0.0.0 à 127.255.255.255
B 128.0.0.0 à 191.255.255.255
C 192.0.0.0 à 223.255.255.255
D 224.0.0.0 à 239.255.255.255
E 240.0.0.0 à 255.255.255.255
Table 1.1 Intervalles des adresses des diérents classes de réseaux
Adresses de classe A
Elles correspondent aux grands réseaux (relativement peu nombreux et abritant un très grand
nombre de machines).
9
10 Les adresses et les chiers de conguration
0 1 7 8 31
0 Adresse du réseau Identication machine
Adresses de classe B
Elles correspondent aux réseaux de taille moyenne.
L'adresse réseau occupe 2 octets, l'adresse de la machine sur ce réseau 2 octets.
0 1 2 15 16 31
1 0 Adresse du réseau Identication machine
Adresses de classe C
Elles correspondent aux petits réseaux (très nombreux et abritant un petit nombre de ma-
chines).
L'adresse réseau occupe 3 octets, l'adresse de la machine sur ce réseau 1 octet.
0 1 2 3 23 24 31
1 1 0 Adresse du réseau Id. machine
L'adresse dont tous les bits de l'identiant de machine sont à 1 permet de désigner l'ensemble
des machines de ce réseau, on l'appelle adresse de diusion dirigée (directed broadcast address).
Elle permet d'envoyer le même message à toutes les machines.
L'adresse de diusion dirigée possède un identiant de réseau, associée à un identiant de
machine, indiquant par sa valeur (tous les bits à 1) la diusion. Une adresse de diusion dirigée
peut être interprétée sans ambiguïté parce qu'elle identie le réseau cible sur lequel s'applique la
diusion mentionnée.
Une autre forme d'adresse de diusion, appelée adresse de diusion limitée ou adresse de diusion
locale, permet d'indiquer une diusion locale, sur le réseau où se trouve la machine qui l'émet ou
la reçoit, indépendamment de toute adresse IP.
Par convention l'adresse 127.0.0.1 est aectée à l'interface de bouclage (loopback). Tout ce qui
est envoyé à cette adresse boucle et devient une réception. Cette adresse est normalement connue
par le nom INADDR_LOOPBACK.
1.2. Agrégation d'adresses et sous-adressage 11
Ce sont les adresse de classe D qui sont utilisées dans l'adressage multipoint (multicast). Chaque
groupe de diusion possède une adresse de diusion de groupe (classe D) unique. Une machine
peut rejoindre un groupe à tout moment et appartenir à plusieurs groupes. Son appartenance à
un groupe détermine le fait qu'elle reçoive l'information destinée à ce groupe. Une machine peut
envoyer des informations à un groupe sans y appartenir. Les 4 premiers bits sont utilisés pour
désigner la classe, les 28 bits suivants pour numéroter les groupes.
0 1 2 3 4 31
1 1 1 0 Identication de groupe
1.1.5 Multidomicile
Traditionnellement le terme de machine multidomiciliée (multihomed) fait référence à une ma-
chine avec plusieurs interfaces réseaux, comme deux Ethernet par exemple ou un Ethernet et un
modem. Chaque interface a son adresse IPv4 unique.
Il est désormais possible d'attribuer plusieurs adresses IP à la même interface, ce qui lui donne
ainsi plusieurs interfaces logiques. Aussi la dénition d'une machine multidomiciliée doit être
étendue à une machine à plusieurs interfaces, sans faire la distinction si ces interfaces sont physiques
ou logiques.
Le masque de sous-réseau le plus souvent utilisé par les réseaux de classe B est 255.255.255.0.
Il permet d'agrandir la partie réseau d'une adresse de classe B d'un octet supplémentaire.
Les deux premiers octets dénissent le réseau de classe B, le troisième l'adresse du sous-réseau,
le quatrième la machine reliée au sous-réseau.
Par exemple, le département d'Informatique utilise un réseau de classe B pour la pédagogie
(ce réseau n'est pas relié à l'Internet). Il est utilisé avec des sous-réseaux et se note 172.18.0.0/24.
L'allocation d'un grand nombre d'adresses de classe C au lieu d'une adresse de classe B permet
d'économiser des numéros de classe B, mais il apparaît un autre problème : au lieu d'avoir une
seule entrée par entreprise, une table de routage en comporte alors un grand nombre.
La technique dite CIDR (Classless Inter-Domain Routing ou routage inter-domaine sans classe)
permet de résoudre ce problème. De façon conceptuelle, CIDR fusionne ou agrège un ensemble
contigü d'adresse de classe C en une seule entrée représentée par un couple (adresse_de_réseau,
compteur) dans lequel adresse_de_réseau est la plus petite adresse de réseau du bloc et compteur
donne le nombre total de réseau du bloc. Par exemple, (193.52.16.0, 3) permet de spécier les
trois adresses réseau 193.52.16.0, 193.52.17.0, 193.52.18.0.
En pratique, CIDR ne restreint pas les numéros de réseau aux adresses de classe C et n'a pas
besoin de spécier la taille du bloc comme un nombre entier. CIDR exige que chaque bloc d'adresse
soit une puissance de deux et utilise un maque binaire pour identier la taille du bloc.
Etand donné qu'identier un bloc CIDR nécessite de dénir deux items, une adresse et un
masque, une notation abrégée a été dénie (appelé notation slash). Cette notation indique la
longueur du masque en décimal et utilise le signe slash (/) pour séparer cette valeur de l'adresse.
1.3. Routage 13
1.3 Routage
1.3.1 Tables de routage
Chaque machine du réseau (hôte ou passerelle) doit déterminer sa voie d'acheminement des
datagrammes :
si l'hôte de destination se trouve sur le même réseau local, les données sont transmises à
l'hôte de destination,
si l'hôte de destination se trouve sur un réseau distant, les données sont expédiées à une
passerelle locale.
L'acheminement est donc déterminé en fonction de la partie réseau de l'adresse, selon les
indications de la table de routage locale. Celle-ci est créée soit par l'administrateur-système, soit
au moyen des protocoles de routage.
ftp 21/tcp
telnet 23/tcp
$ ypcat hosts
172.16.1.18 cisco6 cisco6.univ-brest.fr
193.52.16.18 cisco5 cisco5.univ-brest.fr
1. en anglais, daemon
1.6. Les chiers d'équivalence UNIX 15
autre_machine toto
une_autre_machine toto
l'utilisateur toto sur les machines autre_machine ou une_autre_machine pourra s'identier sur
la machine ma_machine sous le nom arthur sans avoir à donner le mot de passe de cet utilisateur.
N.B. : c'est un "trou de sécurité important", à utiliser avec précaution.
Chapitre 2
$ ping kelenn-gw
kelenn-gw.univ-brest.fr is alive
$ arp -a
kelenn-gw.univ-brest.fr (193.52.16.26) at 8:0:20:74:f9:df
odet (172.16.2.2) at 8:0:20:20:83:31
Lorqu'aucune résolution d'adresse n'a été faite dernièrement, il n'y a pas d'entrée. En utili-
sant La commande ping, on force la conversion d'adresse et on peut ensuite visualiser l'adresse
demandée.
$ arp terre
terre (193.52.16.59) -- no entry
$ ping terre
terre is alive
$ arp terre
terre (193.52.16.59) at 8:0:20:2:c8:38
17
18 Les commandes d'administration
$ /sbin/ifconfig -a
lo0: flags=849<UP,LOOPBACK,RUNNING,MULTICAST> mtu 8232
inet 127.0.0.1 netmask ff000000
le0: flags=863<UP,BROADCAST,NOTRAILERS,RUNNING,MULTICAST> mtu 1500
inet 193.52.16.37 netmask ffffffc0 broadcast 193.52.16.0
le1: flags=863<UP,BROADCAST,NOTRAILERS,RUNNING,MULTICAST> mtu 1500
inet 172.18.3.1 netmask ffffff00 broadcast 172.18.3.0
Chacune de ces interfaces peut être utilisée (UP), autorise la diusion sur le réseau (ou le sous-
réseau) (BROADCAST), n'a pas d'en-queue (NOTRAILERS), est en fonctionnement (RUNNING), autorise
le multi-adressage (MULTICAST).
Le Maximum Transmit Unit (MTU) est de 8232 sur l'interface de bouclage, de 1500 octets sur
les interfaces Ethernet.
Le masque de sous-réseau associé à le0 est ffffffc0 (en binaire
11111111111111111111111111000000, 26 1 suivis de 6 0). Le réseau (de classe C) auquel
est connecté le0 utilise le sous-adressage (/26) : 2 bits sont utilisés pour l'identication des
sous-réseaux, 6 bits pour l'identication des machines dans le sous-réseau (ce qui permet de
dénir 4 (22 ) sous-réseaux de 64 (26 ) machines).
Le masque de sous-réseau associé à le1 est ffffff00 (en binaire
11111111111111111111111100000000, 24 1 suivis de 8 0). Le réseau (de classe B) auquel
est connecté le1 utilise le sous-adressage (/24) : 8 bits sont utilisés pour l'identication des
sous-réseaux, 8 bits pour l'identication des machines dans le sous-réseau (ce qui permet de
dénir 256 (28 ) sous-réseaux de 256 (28 ) machines).
Avec l'option -i, on obtient des statistiques sur les interfaces du système local (nom de l'interface,
taille maximum des paquets transmis, réseau connecté, nom logique, paquets émis et reçus, erreurs
et collisions) :
$ netstat -i
Name Mtu Net/Dest Address Ipkts Ierrs Opkts Oerrs Collis Queue
le0 1500 193.52.16.0 penfeld-gw 1293594 11 983956 14 152016 0
le1 1500 172.16.2.0 penfeld 898285 0 1181685 28 1729 0
lo0 1536 loopback localhost 16593 0 16593 0 0 0
2.5. La commande nslookup 19
Le champ Name contient le nom attribué à l'interface avec ifconfig. Une astérisque (*) indique
que l'interface n'a pas été activée.
Le champ MTU contient le Maximum Transmit Unit.
Le champ Net/Dest indique le réseau, le sous-réseau (auquel cas il faut appliquer le masque)
ou l'hôte auquel l'interface permet d'accéder.
Le champ Address contient l'adresse IP attribuée à cette interface.
Le champ Ipkts indique le nombre de paquets que cette interface a reçu.
Le champ Ierrs indique le nombre de paquets endommagés que cette interface a reçu.
Le champ 0pkts indique le nombre de paquets que cette interface a envoyé.
Le champ 0errs indique le nombre de paquets générant une erreur que cette interface a envoyé.
Le champ Collis indique le nombre de collisions Ethernet détectées par cette interface.
Le champ Queue indique le nombre de paquets gurant dans la le d'attente et attendant leur
transmission par cette interface (normalement à 0).
$ netstat -r
Routing tables
Destination Gateway Flags Refcnt Use Interface
venan paludenn-gw UGHD 0 238 le0
default cisco5 UG 10 67909 le0
193.52.16.0 penfeld-gw U 36 24917 le0
172.16.2.0 penfeld U 3 167449 le1
L'option -s donne des statistiques par protocole ou sur le routage (avec -r).
Enn, avec l'option -a, on visualise toutes les sockets actives (on peut restreindre au domaine
Unix -f unix ou au domaine internet -f inet).
$ nslookup
Default Server: cassis-gw.univ-brest.fr
Address: 192.70.100.29
> kelenn
Server: cassis-gw.univ-brest.fr
Address: 192.70.100.29
Name: kelenn.univ-brest.fr
Address: 172.16.1.24
20 Les commandes d'administration
> kelenn-gw
Server: cassis-gw.univ-brest.fr
Address: 192.70.100.29
Name: kelenn-gw.univ-brest.fr
Address: 193.52.16.26
> whitehouse.gov
Server: cassis-gw.univ-brest.fr
Address: 192.70.100.29
Non-authoritative answer:
Name: whitehouse.gov
Address: 198.137.241.30
> exit
NAME - print info about the host/domain NAME using default server
NAME1 NAME2 - as above, but use NAME2 as server
help or ? - print help information
exit - exit the program
set OPTION - set an option
all - print options, current server and host
[no]debug - print debugging information
[no]d2 - print exhaustive debugging information
[no]defname - append domain name to each query
[no]recurse - ask for recursive answer to query
[no]vc - always use a virtual circuit
domain=NAME - set default domain name to NAME
root=NAME - set root server to NAME
retry=X - set number of retries to X
timeout=X - set time-out interval to X
querytype=X - set query type to one of A,CNAME,HINFO,MB,MG,MINFO,MR,MX
type=X - set query type to one of A,CNAME,HINFO,MB,MG,MINFO,MR,MX
server NAME - set default server to NAME, using current default server
lserver NAME - set default server to NAME, using initial server
finger [NAME] - finger the optional NAME
root - set current default server to the root
ls NAME [> FILE]- list the domain NAME, with output optionally going to FILE
view FILE - sort an 'ls' output file and view it with more
2.6. La commande finger 21
$ finger gire
Login name: gire In real life: Sophie Gire
Directory: /home/ens/gire Shell: /bin/csh
Last login Wed Jul 30 12:43 on console
New mail received Mon Oct 13 14:35:44 1997;
unread since Mon Aug 18 18:21:11 1997
Plan:
Etre toute ma vie en "mise en disponibilite pour convenances personnelles"
et tout le temps tout le temps tout le temps tout le temps
faire des patates a l'ail a John Cassavetes
$ finger president@whitehouse.gov
[whitehouse.gov]
$ rusers
kelenn3 root
kelenn-gw.un ballot ballot ballot ballot
$ uname -a
SunOS penfeld-g 4.1.3_U1 2 sun4m
$ hostname ; hostid
penfeld-gw
8074f8a8
Deuxième partie
Programmation réseaux
23
Chapitre 3
#include<netinet/in.h>
struct in_addr {
u_long s_addr; };
25
26 Les structures et les fonctions d'accès
Sur Sun, on trouve la dénition suivante, compatible avec la première, et permettant d'accéder
à diérentes interprétations :
#include<netinet/in.h>
struct in_addr {
union {
struct { uint8_t s_b1,s_b2,s_b3,s_b4; } _S_un_b;
struct { uint16_t s_w1,s_w2; } _S_un_w;
uint32_t _S_addr;
} _S_un;
#define s_addr _S_un._S_addr /* should be used for all code */
};
Sur certains Linux (qui devraient pourtant être Posix . . .), on trouve la dénition suivante :
#include<netinet/in.h>
struct in_addr {
unsigned int s_addr; };
3.5.2 inet_ntoa
#include <arpa/inet.h>
char * inet_ntoa(struct in_addr in);
Cette fonction convertit une adresse sous la forme d'un entier 32-bit nommée in en une adresse
en notation pointée. La fonction renvoie l'adresse de la chaîne si l'appel a réussi, NULL sinon.
htnol et ntohl permettent la manipulation des adresses, et htnos et ntohs permettent celles
des numéros de port :
#include<sys/socket.h>
#include<netdb.h>
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* pointer to array of pointers to alias names */
int h_addrtype; /* host address type : AF_INET or AF_INET6 */
int h_length; /* length of address : 4 or 16 */
char **h_addr_list; /* ptr to array of ptrs with IPv4 or IPv6 addrs */
#define h_addr h_addr_list[0] /* first address in list */
};
#include <unistd.h>
int gethostname(char *name, size_t len);
28 Les structures et les fonctions d'accès
Cette fonction permet de récupérer à l'adresse name le nom de la machine locale (terminé par
un \0). len contient la taille de la zone pointée par name. La fonction renvoie 0 si l'appel a réussi,
-1 sinon.
3.8.2 gethostbyname
Il est possible d'obtenir une structure hostent associée à une machine de nom donné en
paramètre : si un serveur de nom est actif, il est interrogé ; sinon les NIS et en dernier le chier
/etc/hosts.
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
struct hostent *gethostbyname(const char *name);
Le pointeur renvoyé pointe en zone statique et chaque appel écrase le résultat du précédent :
il faut donc recopier les résultats avec memcpy par exemple.
3.8.3 gethostbyaddr
Lorsqu'on connait l'adresse Internet, on peut aussi obtenir une structure hostent.
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
struct hostent *gethostbyaddr(const char *addr, int len, int type);
Dans le cas d'une adresse Internet v4, addr pointe sur un entier long, et donc len est égal à
sizeof(long) et type est égal à AF_INET.
Chapitre 4
Les sockets
4.1 Introduction
4.1.1 Qu'est-ce qu'une socket ?
Une socket est un point de communication par lequel un processus peut émettre ou recevoir
des informations.
A l'intérieur d'un processus, une socket sera identiée par un descripteur de même nature que
ceux identiant les chiers (c'est-à-dire dans le même ensemble). Cette propriété est essentielle
car elle permet la redirection des chiers d'entrée-sortie standard (descripteurs 0, 1 et 2) sur des
sockets et donc l'utilisation d'applications standards sur le réseau.
Cela signie également que tout nouveau processus (créé par fork) hérite des descripteurs de
son père.
A toute socket est associée dans le système un objet ayant la structure socket, dénie dans le
chier standard <sys/socketvar.h> qui contient l'ensemble des caractéristiques de cet objet.
Du fait de la nature des protocoles TCP et UDP, une socket de type SOCK_DGRAM s'utilise en
mode non-connecté et sans garantie de abilité
et une socket de type SOCK_STREAM s'utilise en mode connecté et avec garantie du maximum de
abilité.
29
30 Les sockets
Les constantes symboliques SOCK_DGRAM et SOCK_STREAM sont dénies dans le chier standard
<sys/socket.h>.
Pour un domaine et un type, il n'existe souvent qu'un seul type de protocole utilisable, dans ce
cas on choisit 0 pour le protocole et le système choisit le protocole adapté.
#include <sys/types.h>
#include <sys/socket.h>
int socket(
int domain, /* AF_UNIX, AF_INET, ... */
int type, /* SOCK_DGRAM, SOCK_STREAM, ... */
int protocol /* 0 protocle par defaut */
);
La valeur de retour est un descripteur sur la socket nouvellement créée si cette création est
possible et -1 en cas d'erreur. En cas d'erreur, errno vaut :
EACCES : incompatibilité type / protocole,
EMFILE : table des descripteurs pleine,
EPROTONOSUPPORT : protocole non-supporté,
... (peut dépendre du système utilisé).
Une socket est supprimée lors de la fermeture du dernier descripteur permettant d'y accéder
(appel-système close).
Cette suppression libère toutes les ressources allouées.
Cette primitive peut être bloquante dans le cas d'utilisation d'une socket de type SOCK_STREAM
dans le domaine AF_INET au cas où le tampon d'émission n'est pas vide.
#include <sys/types.h>
#include <sys/socket.h>
int bind(
int sockfd, /* descripteur de la socket a attacher */
const struct sockaddr *my_addr, /* pointeur sur l'adresse a utiliser */
32 Les sockets
#include <sys/socket.h>
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
Il faut, en fonction du domaine, lui substituer la structure correspondante (voir 4.1.3).
N.B. Pour les appels bind, accept, connect et sendto, l'utilisation de la structure du domaine
provoque un warning à la compilation 1 , du genre "warning : passing arg 2 of 'bind' from incom-
patible pointer type". Pour éviter ce warning, il faudrait faire une conversion (cast) à l'appel.
La valeur de retour est 0 si cet attachement est possible et -1 en cas d'erreur. En cas d'erreur,
errno vaut :
EACCES : adresse protégée,
EADDRINUSE : adresse déja utilisée,
EADDRNOTAVAIL : adresse incorrecte,
EBADF : s est un descripteur invalide,
... (peut dépendre du système utilisé).
% netstat -f unix
Active UNIX domain sockets
Address Type Recv-Q Send-Q Vnode Conn Refs Nextref Addr
ff64668c stream 0 0 0 ff65f18c 0 0
ff656a8c stream 0 0 0 ff674e0c 0 0 /tmp/.X11-unix/X0
ff64688c dgram 0 0 ff13d6b8 0 0 0 /dev/log
Une socket de ce domaine apparait avec un s en début des permissions.
% ls -l /tmp/.X11-unix/X0
srwxrwxrwx 1 moi 0 Nov 3 08:07 /tmp/.X11-unix/X0
% ls -l /dev/log
srw-rw-rw- 1 root 0 Sep 19 09:57 /dev/log
La primitive socketpair permet de créer deux sockets non nommées et des les associer (utili-
sable pour les deux types de sockets SOCK_DGRAM et SOCK_STREAM).
1. C n'est pas un langage-objet, struct sockaddr_in et struct sockaddr_un devraient être des sous-types de
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(
int domain, /* AF_UNIX */
int type, /* SOCK_DGRAM, SOCK_STREAM */
int protocol, /* 0 */
int sv[2] /* tableau de 2 entiers qui contiendra les sockets */
);
Les avantages par rapport à un tube nommé POSIX sont que la paire de sockets permet
une communication duplex (alors qu'il faudrait deux tubes nommés et quatre descripteurs) et
qu'on peut choisir son mode de communication SOCK_DGRAM ou SOCK_STREAM ce qui conduit à des
sémantiques diérentes.
#include<netinet/in.h>
struct in_addr {
u_long s_addr;
};
struct sockaddr_in {
short sin_family; /* AF_INET */
u_short sin_port; /* numero du port associe */
struct in_addr sin_addr; /* adresse internet de la machine */
char sin_zero[8]; /* tableau de 8 caracteres nuls */
};
Les champs peuvent avoir, à l'appel de bind, des valeurs particulières :
La valeur INADDR_ANY de sin_addr.s_addr permet d'associer la socket à toutes les adresses
IP possibles de la machine (particulièrement important pour les machines "passerelles" : la
socket pourra être indiéremment contactée sur n'importe quel des réseaux auquel appar-
tient la passerelle),
sinon il faut utiliser les primitives gethostname et gethostbyname pour connaître l'adresse
de la machine locale.
L'attachement à un numéro de port particulier est indispensable si ce numéro est public et
connu (exemple d'un service dont les clients connaissent le numéro).
Il existe des cas où le numéro de port peut être attribué par le système, en spéciant 0 dans
le champ sin_port. Ceci peut conduire le processus à ne pas connaître le port qu'il utilise,
il peut le demander au système au moyen de la primitive getsockname.
#include <sys/types.h>
#include <sys/socket.h>
int getsockname(
int s, /* descripteur de la socket */
struct sockaddr *name, /* pointeur sur une structure adresse
a remplir par getsockname */
34 Les sockets
A l'appel *namelen est la taille de la structure adresse reservée pour récuperer le résultat,
au retour *namelen aura pour valeur la longueur eective de l'adresse.
Attention : les valeurs des champs sin_addr.s_addr et sin_port sont renvoyées en format
réseau (voir 3.6).
N.B. Les noms name et namelen sont mal choisis, ce ne sont pas des noms de machines mais des
adresses. Les noms corrects sont my_addr et addrlen (comme pour bind).
4.2. Primitives communes 35
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
/* cr'eation de la socket */
if ((desc = socket(AF_INET, type, 0)) == -1) {
perror("Creation socket impossible\n");
return -1;
}
return desc;
}
36 Les sockets
4.3 La scrutation
4.3.1 Introduction
Dans un certain nombre de situations, un processus est amené à communiquer (de manière non
régulière) avec diérentes entités du système et utilise pour cela des objets de nature quelconque
(chiers, tubes, sockets, ...). Les opérations qu'il est amené à réaliser sont très souvent bloquantes
et dans le cas d'un serveur, par exemple, il est essentiel que les requêtes soient prises en compte
le plus rapidement possible après qu'elles aient été transmises. Le processus est donc amené à
scruter ce qui se passe sur diérents descripteurs sans prendre le risque d'être bloqué. Il existe
deux possibilités :
si les descripteurs ont étés positionnés en mode non-bloquant au moyen de la primitive
fcntl ou ioctl :
int on=1; ... ; ioctl(desc, FIOSNBIO; &on); ... } /* ou FIONBIO */
on peut essayer de lire alternativement sur chacun des descripteurs (polling) ;
sinon on peut demander à être averti lorsqu'une opération peut être réalisée sur l'un des
descripteur au moyen de la primitive select, le processus appelant cette primitive se met
alors en attente sur des événements relatifs aux lectures et écritures sur un ensemble de
descripteurs.
La primitive select
Cette primitive permet de scruter plusieurs descripteurs (chiers, tubes, sockets, ...) , en se
mettant en attente des événements relatifs aux lectures et écritures.
Trois types d'événements sont susceptibles d'être examinés par la primitive select :
la possibilité de lire sur un descripteur donné,
la possibilité d'écrire sur un descripteur donné,
l'existence d'une condition exceptionnelle sur une ressource d'un descripteur pour lequel
ce concept existe (comme par exemple l'arrivée d'un message urgent sur une socket du
domaine Internet et de type SOCK_STREAM).
4.3. La scrutation 37
int select(
int width, /* ensemble de descripteurs a examiner 0, 1, ..., width - 1 */
fd_set *readfds, /* ensemble de descripteurs en lecture */
fd_set *writefds, /* ensemble de descripteurs en ecriture */
fd_set *execptfds, /* ensemble de descripteurs en exception */
struct timeval *timeout /* temporisation */
);
readfds pointe en entrée sur un ensemble de descripteurs sur lequel on souhaite pouvoir réaliser
une lecture.
Une valeur NULL de ce paramètre correspond à l'ensemble vide (rien à surveiller en lecture).
L'existence dans l'ensemble correspondant d'un descripteur sur un chier régulier non verrouillé
en mode non exclusif de manière non impérative provoque un retour immédiat de la primitive
select (en eet, la lecture sur un tel chier n'est pas bloquante).
writefds pointe en entrée sur un ensemble de descripteurs sur lequel on souhaite pouvoir
réaliser une écriture.
Une valeur NULL de ce paramètre correspond à l'ensemble vide (rien à surveiller en écriture).
L'existence dans l'ensemble correspondant d'un descripteur sur un chier régulier non verrouillé
impérativement (que ce soit en mode partagé ou exclusif) provoque un retour immédiat de la
primitive select (en eet, l'écriture sur un tel chier n'est pas bloquante).
width doit au moins être égal à la valeur du plus grand descripteur appartenant à l'un
des trois ensembles précédents augmentée de 1. Il indique que l'opération porte sur les width
descripteurs (0, 1, ..., width -1).
timeout pointe sur une structure de type timeval qui dénit un temps maximal d'attente
avant que l'une des opérations souhaitées soit possible.
Une valeur NULL correspond à un temps d'attente inni.
Retour de la primitive
Un processus appelant est bloqué jusqu'à ce que l'une des conditions suivantes se réalise :
L'un des évenements attendus sur un des descripteurs de l'un des ensembles s'est produit.
Les ensembles *readfds, *writefds et *execptfds sont mis à jour et contiennent les
descripteurs sur lesquels l'opération correspondante est possible : la macro FD_ISSET permet
de tester l'appartenance des descripteurs à ces ensembles.
En cas d'utilisation de select dans une boucle, il est donc nécessaire de réinitialiser les
trois ensembles avant de rappeler la primitive select.
La valeur de retour est le nombre total de descripteurs sur lesquels une opération est
possible.
38 Les sockets
5.1 Principes
C'est le mode de communication associé aux sockets de type SOCK_STREAM. Il permet
d'échanger des séquences de caractères continues et non structurées en messages.
Dans le cas du domaine AF_INET, le protocole sous-jacent est TCP, la communication est donc
able.
Ainsi que l'indique son nom, le mode connecté nécessite l'établissement d'une connexion (un
circuit virtuel) entre deux points.
Alors que le mode de communication par datagrammes présente un aspect symétrique dans la
mesure où chacun des points de transport (les sockets) sont dans le même état et peut prendre
l'initiative de la connexion, le mode connecté met en lumière la dissymétrie des deux processus
impliqués :
l'une des deux entités, en position de serveur, est en attente passive de demande de
connexion,
l'autre entité, en position de client, doit prendre l'initiative de la connexion en demandant
au serveur s'il accepte la connexion.
Après cette phase initiale, on retrouve la symétrie : chaque extrémité peut envoyer et recevoir
des caractères.
lorsqu'un client prend l'initiative de la connexion, le processus serveur est débloqué et une
nouvelle socket, appelée socket de service, est créée :
c'est cette socket de service qui est connectée à la socket du client.
le serveur peut alors déléguer le travail à un nouveau processus créé par fork et reprendre
son attente sur la socket de rendez-vous,
ou traiter lui-même la demande (mais il risque de faire attendre des nouveaux clients).
Cette primitive permet à un serveur de déclarer un service ouvert auprès de son entité TCP
locale.
Après cet appel, l'entité TCP locale commence à recevoir les demandes de connexions (appelées
des connexions pendantes), le paramètre backlog dénit la taille d'une le d'attente où sont
mémorisées les connexions pendantes.
La valeur de retour est 0 en cas de réussite. En cas d'erreur, la valeur de retour est -1 et errno
vaut :
EBADF : s est un descripteur invalide,
ENOTSOCK : s n'est pas une socket,
EOPNOTSUPP : type de socket incorrect.
Cette primitive permet à un serveur d'extraire une connexion pendante de la le d'attente
associée à la socket de rendez-vous s.
Ainsi le serveur prend en compte un nouveau client : une nouvelle socket de service est créée, est
connectée au client et son descripteur est renvoyé par la primitive accept.
L'adresse de la socket du client avec lequel la connexion est établie est écrite dans la zone pointée
par addr si sa valeur est diérente de NULL (la longueur est alors donnée par *addrlen).
Un appel à accept est normalement bloquant s'il n'y a aucune connexion pendante. En cas de
succès, il renvoie un entier non-négatif, constituant un descripteur pour la nouvelle socket. En cas
d'erreur, la valeur de retour est -1 et errno vaut :
EBADF : s est un descripteur invalide,
EINTR : le processus appelant accept a reçu un signal avant qu'un des clients attendus soit
arrivé, et le signal a interrompu l'appel-système,
5.2. Le point de vue du serveur 41
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define TRUE 1
/* creation de la socket de RV */
if ((socket_RV = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
/* preparation de l'adresse locale */
port = (unsigned short) atoi(argv[1]);
adresseRV.sin_family = AF_INET;
adresseRV.sin_port = htons(port);
adresseRV.sin_addr.s_addr = htonl(INADDR_ANY);
lgadresseRV = sizeof(adresseRV);
42 La communication en mode connecté (TCP)
entier_envoye = 2006 ;
if (write(socket_service, &entier_envoye, sizeof(int)) != sizeof(int))
{
perror("write");
exit(7);
}
printf("Entier envoye : %d\n", entier_envoye);
close(socket_service);
}
close(socket_RV);
}
44 La communication en mode connecté (TCP)
Contrairement à ce qui se passait dans le cas d'une demande de pseudo-connexion (voir 6.5),
le processus appelant connect est bloqué jusqu'à ce que la négociation entre les deux entités TCP
concernées soit achevée (il est cependant possible de rendre l'appel non bloquant).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
exit(2);
}
adresse_serveur.sin_family = AF_INET;
adresse_serveur.sin_port = htons(port);
bcopy(hote->h_addr, &adresse_serveur.sin_addr, hote->h_length);
printf("L'adresse du serveur en notation pointee %s\n", inet_ntoa(adresse_serveur.sin_addr));
fflush(stdout);
Une diérence essentielle est que, dans la communication via des sockets de type SOCK_DGRAM,
le découpage de l'envoi en messages n'est pas préservée à la reception (une lecture peut provenir
de plusieurs écritures).
Par ailleurs dans le cas du domaine AF_INET, le protocole sous-jacent (TCP) garantit que les
caractères seront reçus dans le même ordre que celui de leur envoi, à l'exception de la possibilité
d'adresser un caractère dit urgent ou hors bande (Out Of Band).
#include <sys/types.h>
#include <sys/uio.h>
int read(int fd, /* descripteur */
void *buf, /* adresse du buffer pour recevoir les donnees */
int count /* nombre d'octets maximum \`a recevoir */
);
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s,
void *msg,
int len,
unsigned int flags
);
Elle permet de rendre compte, au niveau de la fermeture d'une socket, de l'aspect duplex de la
communication.
Le paramètre how détermine le niveau de fermeture souhaité :
0 : la socket n'accepte plus d'opérations de lecture : un appel à read ou à recv renverra 0 ;
1 : la socket n'accepte plus d'opérations d'écriture : un appel à write ou à send provoquera
la génération d'un exemplaire du signal SIGPIPE, et s'il est capté, une valeur de retour -1
(errno = EPIPE) ;
2 : la socket n'accepte plus ni opérations de lecture, ni opérations d'écriture.
#include <stdio.h>
50 La communication en mode connecté (TCP)
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define TRUE 1
adresse.sin_family=AF_INET;
adresse.sin_addr.s_addr=htonl(INADDR_ANY);
adresse.sin_port=htons(*ptr_port);
if (ptr_adresse != NULL)
getsockname(desc, ptr_adresse, &longueur);
return desc;
}
/* detachement du terminal */
if (fork()!=0) exit(0);
setsid();
printf("serveur de pid %d lance\n", getpid());
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
adresse.sin_family=AF_INET;
adresse.sin_addr.s_addr=htonl(INADDR_ANY);
adresse.sin_port=htons(*ptr_port);
if (ptr_adresse != NULL)
getsockname(desc, ptr_adresse, &longueur);
return desc;
}
if (argc<3){
fprintf(stderr, "Nombre de parametres incorrect\n");
exit(2);}
/* test d'existence du serveur */
if ((hp=gethostbyname(argv[1]))==NULL){
fprintf(stderr, "Serveur %s inconnu\n", argv[1]);
exit(2);}
printf("connexion acceptee\n");
client_service(socket_client, argc-3, argv+3);
}
5.6. Squelettes de serveur et de client 55
56 La communication en mode connecté (TCP)
Chapitre 6
6.1 Principes
Quelque soit le domaine (AF_UNIX ou AF_INET), les principales caractéristiques de la commu-
nication par datagrammes sont :
lorsqu'un datagramme est envoyé, l'expéditeur n'obtient aucune information sur l'arrivée
de son message à destination,
les limites des messages sont préservées.
La communication avec UDP (échange de datagrammes) est réalisée par l'intermédiaire de
sockets de type SOCK_DGRAM dans le domaine AF_INET.
Un processus souhaitant émettre un message à destination d'un autre doit :
d'une part disposer d'un point de communication local (descripteur de socket sur le système
local obtenu avec la primitive socket, et éventuellement nommé avec la primitive bind),
d'autre part connaître une adresse (adresse IP et numéro de port) sur le système auquel ap-
partient son interlocuteur (en espérant que cet interlocuteur dispose d'une socket attachée
à cette adresse ... ce type de communication ne permettant pas d'en être certain).
Les sockets sont utilisés en mode non connecté : en principe, toute demande d'envoi doit
comporter l'adresse de la socket destinataire (bien qu'il soit possible d'établir une pseudo-connexion
entre deux sockets de type SOCK_DGRAM).
57
58 La communication en mode non connecté (UDP)
#include <sys/types.h>
#include <sys/socket.h>
int sendto(
int s, /* descripteur de la socket d'emission */
const void *msg, /* adresse du message a envoyer */
int len, /* longueur du message */
unsigned int flags, /* option = 0 pour le type SOCK_DGRAM */
const struct sockaddr *to, /* pointeur sur l'adresse du destinataire */
int tolen /* longueur de l'adresse de la socket destinataire */
);
Un appel à sendto correpond à la demande d'envoi, via la socket s, du message pointé par
msg de longueur len (éventuellement nulle), à destination de la socket attachée à l'adresse pointée
par to de longueur tolen.
La valeur de retour est le nombre de caractères envoyé en cas de réussite, et -1 en cas d'échec.
Seules les erreurs locales sont détectées, notamment il n'y a aucune détection qu'une socket est
eectivement attachée à l'adresse destinataire (si c'est faux, le message sera perdu et l'émetteur
n'en sera pas avisé).
En cas d'erreur, errno vaut :
EBADF : s est un descripteur invalide,
EINTR : le processus appelant sendto a reçu un signal avant que les données aient été
"buerisés", et le signal a interrompu l'appel-système,
EINVAL : longueur d'adresse incorrecte,
... (peut dépendre du système utilisé).
6.3. Un exemple 59
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(
int s, /* descripteur de la socket de reception */
void *buf, /* adresse de recuperation du message recu */
int len, /* taille de la zone allouee a l'adresse buf */
unsigned int flags, /* 0 ou MSG_PEEK */
struct sockaddr *from, /* pointeur pour recuperer l'adresse d'expedition */
int *fromlen /* pointeur sur la longueur de l'adresse
de la socket d'expedition */
);
Cette primitive permet à un processus d'extraire un message sur une socket dont il possède un
descripteur, s'il existe un message (sinon l'appel est bloquant).
Le rôle du paramètre len est de donner la longueur de la taille reservée pour mémoriser le message
(des caractères seront perdus si cette taille est inférieure à la longueur du message).
Dans le cas où from est diérent de NULL, au retour from pointe sur l'adresse d'expédition et
*fromlen contient la longueur de cette adresse.
En cas d'erreur, errno vaut :
EBADF : s est un descripteur invalide,
EINTR : le processus appelant recvfrom a reçu un signal avant que les données aient été
reçus, et le signal a interrompu l'appel-système,
EWOULDBLOCK : la socket est non-bloquante et l'opération demandée serait bloquante,
... (peut dépendre du système utilisé).
La valeur de retour est le nombre de caractères reçus en cas de réussite, et -1 en cas d'échec.
6.3 Un exemple
L'"émetteur" envoie la chaîne de caractères "salut" et attend la réponse. Le "receveur" attend
l'envoi et répond "tchao".
60 La communication en mode non connecté (UDP)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
/* cr'eation de la socket */
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket"); exit(1);}
/* echange de datagrammes */
strcpy(buf, "salut");
lgadr = sizeof(adr);
if ((envoye = sendto(sock, buf, strlen(buf)+1, 0, &adr, lgadr)) != strlen(buf)+1) {
perror("sendto"); exit(1);}
printf("salut envoye\n");
lgadr = sizeof(adr);
if ((recu = recvfrom(sock, buf, 256, 0, &adr, &lgadr)) == -1) {
perror("recvfrom"); exit(1);}
printf("j'ai recu %s\n", buf);
}
6.3. Un exemple 61
/* cr'eation de la socket */
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket"); exit(1);}
/* 'echange de datagrammes */
lgadr = sizeof(adr);
if ((recu = recvfrom(sock, buf, 256, 0, &adr, &lgadr)) == -1) {
perror("recvfrom"); exit(1);}
printf("j'ai recu %s\n", buf);
strcpy(buf, "tchao");
if ((envoye = sendto(sock, buf, strlen(buf)+1, 0, &adr, lgadr)) != strlen(buf)+1) {
perror("sendto"); exit(1);}
printf("reponse envoyee ...adios\n");
}
62 La communication en mode non connecté (UDP)
Les fragments sont rassemblés en un seul message avant envoi, et lors de la reception le message
reçu est récupéré sous forme de fragments qui sont stockés selon les indications données par une
structure de type struct msghdr.
#include <sys/types.h>
#include <sys/socket.h>
int connect(int s,
struct sockaddr *serv_addr,
int addrlen
);
La primitive connect permet d'associer à une socket locale, identiée par s à la socket d'adresse
*serv_addr (de longueur addrlen).
Tout nouvel appel à connect annule la demande précédente, et si serv\_addr a la valeur NULL, la
socket s n'est plus associée à aucune autre.
#include <sys/types.h>
#include <sys/socket.h>
int send(int s,
const char *msg,
int len,
unsigned int flags
);
Par rapport à write, la primitive send n'apporte rien pour les sockets de type SOCK_DGRAM
dans la mesure où aucune option n'est supportée.
La réception de message peut se faire par la primitive standard sur les descripteurs : read,
dans ce cas, s'agissant d'une communication en mode datagramme, un appel à read provoquera
l'extraction d'un message complet.
On peut utiliser la primitive spécique aux sockets :
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s,
char *buf,
int len,
unsigned int flags
);
L'emploi de la primitive recv permet de consulter la socket sans rien extraire, en utilisant
l'option MSG_PEEK.