Programmation socket
Petit rappel Application A Application B Application N Couche 7 Programmes utilisateur mode user UDP TCP Couche 4 ICMP IP ARP RARP Noyau du système d'exploitation mode Kernel Couche 3 Couche 2 Couche 1 Cartes matérielles
Client/serveur Serveur propose un service Le client dispose de ce service. Exemple de serveurs : Serveur Web renvois une page Web Serveur ftp dispose de fichier Serveur mail entrepose des mails. Exemple de clients: Navigateur Web demande des pages Client ftp accède à ces fichiers Client Mail demande les mails entreposés
Client/Serveur Serveur Software = Application rendant le service Serveur Hardware = Machine faisant tourner l application rendant le service. En générale on met plus plusieurs services par machines. Par exemple votre box propose plusieurs services Serveur DHCP Serveur Web Serveur DNS
Client/Serveur Machine A Serveur http Client FTP Application Serveur NFS Transport Réseau Liaison Physique Machine B Serveur FTP Application Client http Transport Réseau Liaison Physique Machine C Client http Client NFS Application Transport Réseau Liaison Physique Machine = IP Service = port
Serveur l'application serveur sur la machine d'adresse IPS : demande un numéro de port prédéfini NS au noyau de son système d exploitation (en fait à sa couche Transport) affectation statique d'un numéro de port au serveur se met en attente de requêtes sur ce numéro de port une application serveur est un démon (daemon) : programme qui tourne en arrière plan nfsd, ftpd, httpd, inetd,...
Client demande un numéro de port quelconque au noyau de son OS (couche Transport) affectation dynamique d'un numéro de port par l'os envoie une requête au serveur : message pour le port N à l'adresse IPS
Communication à la réception de la requête, le module UDP (ou TCP) sur la machine d adresse IPS vérifie si numéro de port NS existe si oui passe la requête à l application serveur associée au port NS sinon envoie message d'erreur ICMP (port inaccessible ) à la machine du client l application serveur répond : envoie la réponse au numéro de port et à l'@ip du client
Ports les ports bien connus (well-known ports) [1-1023] ou ports Systèmes les ports enregistrés (registered ports) [1024-49151] ou ports User les ports dynamiques et/ou privés (dynamic/ private ports) [49152-65535] de l'iana pour voir les numéros de ports http://www.iana.org/assignments/portnumbers
Port < 1024 ports réservés pour affectation statique à des applications réseau bien connues réservés à des processus "root" 7 service echo pour UDP et TCP 9 service discard pour UDP et TCP 21 service ftp-commande pour UDP et TCP 80 service page web
Port [1024-49151] les ports enregistrés (registered ports) [1024-49151] ou ports User ports utilisés pour affectation statique pour les applications utilisateur: par des applications de l'internet mais qui se sont déclarées auprès de l'iana ne nécessitant pas d'être root : Exemple : 6000-6063 X Window 1099 RMI Registry On ne devrait utiliser que les services déclarer à l IANA
Port > 49152 les ports dynamiques et/ou privés (dynamic/private ports) [49152-65535] utilisés en statique pour des applications serveurs privées une entreprise, un utilisateur qui crée son propre service : peut demander à l'os l'affectation statique d'un numéro puis communique le numéro de port de son service à ces futurs clients utilisés pour l'affectation dynamique (un n quelconque) par OS numéro de port quelconque attribué à un client FreeBSD choisissait dans [1024, 5000[ Solaris dans [32768, 65535] Linux (voir en TP) L IANA n attribuera jamais ces numéros de port à un service
Client/serveur : Connection A Bourse de New-York D Réseau local Token-Ring B Serveur de Valeurs Interconnexion de réseaux C Réseau local Ethernet X Y Z Boursicoteur Salle de marchés d'une banque à Nancy
API : Application Programming Interface interface entre les programmes d'application et les logiciels des protocoles de communication exemple : entre les programmes d'applications et les modules TCP ou UDP de la couche Transport Bibliothèques de fonctions Ces API dépendent : du langage de programmation (C, Java, ) du système d'exploitation (windows, linux, android,...) Application API UDP/TCP IP Liaison Physique
API : Application Programming Interface Application Pour Unix : historiquement : deux APIs différentes Unix BSD : sockets System V : TLI ( Transport Layer Interface) POSIX a choisi les sockets API UDP/TCP IP Liaison Physique
Socket : kézako? un point de communication entre une application et un protocole réseau (en général de la couche Transport) API des sockets = ensemble de primitives pour : ouvrir ce point de communication nommer envoyer/recevoir des données par ce point de communication des structures de données pour créer, nommer utiliser ce point de communication
L'API des sockets Bibliothèque de fonctions C (primitives) les mêmes fonctions sont utilisables avec des piles de protocoles différentes (famille) : pile TCP/IP, pile OSI (X25, RNIS) Xerox, IPX, et même local (Unix) le profil de la fonction est le même quelle que soit la pile de protocoles la famille sera un paramètre à passer dans la primitive : directement ou indirectement (champ d une structure passée en paramètre)
Socket sous unix En interne dans le noyau de l OS Sockets traités comme des fichiers donc partagent les mêmes (ou presque) tables système que les fichiers : Table globale des points de communication (sockets) créés Pour chaque processus : table des descripteurs d un processus = ensemble commun de descripteurs pour les fichiers ouverts, les «pipes» ouverts et les sockets (points de communication) créés héritage par fork de la table des descripteurs du processus père donc
Création de socket int socket(int famille, int type, int protocole) rend un descripteur de socket : un entier toutes les autres primitives utiliseront ce descripteur ensemble commun de descripteurs pour les fichiers et les sockets famille : famille de protocoles réseaux définies par des constantes dans <sys/socket.h> AF_INET ou PF_INET : famille TCP/IP AF_NS ou PF_NS : famille Xerox AF_LOCAL ou PF_LOCAL : communication entre processus locaux à la même machine ; rien ne sort sur le réseau = pipe Unix
configuration de la prise type : type du protocole avec lequel on crée le point de communication défini par des constantes dans <sys/socket.h> SOCK_STREAM : protocole transport de type connecté SOCK_DGRAM : protocole transport de type non connecté SOCK_RAW : protocoles des couches basses (IP, ICMP, X25, ) protocole : numéro du protocole du type choisi de service choisi avec lequel on crée le point de communication utile si plusieurs protocoles de même type (connecté ou non connecté) inutile pour TCP/IP : service en mode connecté TCP service en mode non connecté UDP
Prototypes Comme API des sockets doit être indépendantes de la pile de protocoles réseau Mêmes primitives pour la pile TCP/IP, la pile X25, En fait deux types «structures» prédéfinies une structure de données générique «prototype» servant à définir les profils des primitives Servant à typer les paramètres des primitives une structure de données réelle dépendante de chaque famille de protocoles réseau (IP, X25, ISO, ) : La taille et le format d une adresse IP numéro de téléphone X25
Concrètement struct sockaddr { u_short sa_family ; /* famille de protocoles */ char sa_data[14] ; /* pour stocker les infos sur le point de communication */ } c est le champ sa_family qui permet à la primitive de l API appelée de s adapter à la famille si famille = TCP/IP sa_data contiendra par exemple une adresse IP sur? octets si famille = X25 sa_data contiendra un numéro de téléphone la structure sock_addr est utilisée pour "typer" les paramètres dans les prototypes des fonctions Exemple : int bind (int sock_desc, struct sockaddr *name, int namelen)
Branchement de la prise Nommage de la prise : pour TCP/IP : un numéro de port et une adresse IP int bind (int sock_desc, struct sockaddr *name, int namelen) Il faut remplir une structure de type sockaddr_in sock_desc : descripteur du socket concerné addr : pointeur sur une structure de type sockaddr_in correctement et complètement remplie avec l'adresse qu'on veut associer au point de communication addrlen : taille réelle en octets de la structure (sizeof)
SockAddr_In struct sockaddr_in { u_short sin_family ; /* famille de protocoles */ u_short sin_port ; /* numéro de port */ struct in_addr sin_addr ; /* adresse IP */ char sin_zero [8]; /* pour compléter le tableau de 14 octets*/ } struct in_addr { u_long s_addr ; } /* adresse IP sur 32 bits*/
Paramètres par défaut Paramètres par défaut : si pas de bind on laisse l'os tout choisir (n port et @IP) si dans la structure pointée par addr numéro de port = 0 on laisse l'os choisir le numéro de port @IP = INADDR_ANY on laisse l'os choisir l'adresse IP le numéro de port et l'@ip choisis par l'os sont rangés dans une structure interne au noyau (comme la structure FILE) pour connaître leurs valeurs : utiliser la primitive getsockname (voir manuel) Remarque le nommage d'un point de communication ne peut se faire que localement c'est-à-dire sur la machine qui exécute le bind
Fermeture close (int descripteur)
Et l IPv6? Faire en TP. struct in6_addr { }; uint8_t s6_addr[16]; PF_INET6 AF_INET6 struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* numéro de port */ uint32_t sin6_flowinfo; /* identificateur de flux */ struct in6_addr sin6_addr; /* adresse IPv6 */ uint32_t sin6_scope_id; /* ensemble d'interfaces correspondant * à la portée de l'adresse */ };
En résumé On sait ouvrir et nommer des sockets On a deux modes: Connecté (TCP) Non connecté (UDP)
Cadre UDP Serveur socket() Client bind() recvfrom() socket() bind() optionnel Bloqué jusqu'à réception d'une requête du client Traite la requête sendto() requête sendto(). close() réponse recvfrom(). close()
Cadre UDP Serveur Client socket() Architecture : un client bind() envoie une requête à un serveur (envoi de données) en socket() la faisant passer par son point de communication local primitive recvfrom() sendto cette requête transite sur le réseau le serveur Bloqué jusqu'à récupère réception (lit) la requête sur son point de d'une requête du client sendto() communication local primitive Traite recvfrom la requête et lui répond sendto() primtive sendto. requête réponse bind() optionnel recvfrom() close() close().
sendto int sendto (int sock_desc, void *buf, int len, int flags, struct sockaddr *to, int tolen) sock_desc : descripteur du socket concerné buf : zone mémoire où seront rangés les octets à envoyer len : nombre d'octets qu'on veut envoyer to : pointeur sur une structure de type sockaddr_in correctement et complètement remplie avec l'adresse de destination des données tolen : taille réelle en octets de la structure pointée par to flags : ensemble d'indicateurs pour paramétrer l'interface avec le protocole de transport (voir manuel) fonctionnement par défaut : 0
recvfrom int recvfrom (int sock_desc, void *buf, int len, int flags, struct sockaddr *from, int *fromlen) sock_desc : descripteur du socket concerné buf : zone mémoire où seront rangés les octets reçus len : nombre d'octets qu'on attend from : pointeur sur une structure de type sockaddr_in qui sera remplie par le protocole de transport avec les informations sur l'expéditeur des données : numéro de port et @IP fromlen : pointeur sur une variable contenant en entrée la taille initiale de la structure from en sortie la taille réellement remplie par le protocole de transport
recvfrom int recvfrom (int sock_desc, void *buf, int len, int flags, struct sockaddr *from, int *fromlen) flags : ensemble d'indicateurs pour paramétrer l'interface avec le protocole de transport (voir manuel) fonctionnement par défaut : 0 Remarque : par défaut recvfrom est bloquant
Serveur socket() Cadre TCP Client bind() listen() accept() Bloqué jusqu'à connexion d'un client établissement de la connexion read() socket() bind() connect() write() optionnel Traite la requête write(). close() réponse requête read(). close()
TCP Architecture non symétrique le client : avant d'envoyer la première requête, demande à son module TCP d établir une connexion avec le module TCP du serveur en utilisant le bon numéro de port (J dans l exemple) primitive connect (ouverture active de la connexion) envoie une requête : primitive write lit la réponse : primitive read
Connect int connect (int sock_desc, struct sockaddr *name, int namelen) demande à son module TCP d'établir une connexion avec le point de communication défini dans la structure pointée par name sock_desc : descripteur du socket local name : pointeur sur une structure de type sockaddr_in correctement et complètement remplie avec l'adresse IP et numéro de port du point de communication distant avec lequel on veut établir la connexion namelen : taille réelle en octets de la structure pointée par name
Read/Write Comme des fichiers pour le mode connecté int write (int sock_desc, void *buf, int len) sock_desc : descripteur du socket concerné buf : zone mémoire où sont rangés les octets à envoyer len : nombre d'octets qu'on veut envoyer int read (int sock_desc, void *buf, int len) sock_desc : descripteur du socket concerné buf : zone mémoire où seront rangés les octets reçus len : nombre d'octets qu'on attend Attention aux valeurs retour de ces primitive!
Listen int listen (int sock_desc, int backlog) met le module TCP en attente d'ouverture de connexion sur le point de communication défini par sock_desc c est-à-dire : demande à TCP : de prendre en compte les demandes de connexion envoyées par d'éventuels clients et d y répondre établir la connexion de stocker ces connexions établies dans une file d'attente dans laquelle l'application serveur viendra les chercher
Listen int listen (int sock_desc, int backlog) sock_desc : descripteur du socket concerné backog : fournit la taille maximale de la file d'attente : taille = f (backlog) fonction f dépend de l'implantation de l API dans la file d'attente se trouve toutes les connexions : déjà ouvertes et prises en compte par TCP mais pas encore prises en compte par l'application serveur lorsque l'application serveur "prélève" un client dans cette file cela libère une place dans la file d attente
Accept int accept (int sock_desc, struct sockaddr *addr, int * addrlen ) prélève dans la file d'attente "un client" qui a déjà établi une connexion au niveau TCP rend un nouveau descripteur de socket associé à cette connexion l échange de données se fera par ce nouveau point de communication afin de laisser l ancien descripteur pour «accepter» plus tard les autres clients : les clients qui ont aussi établi une connexion TCP pour ce serveur les clients qui feront plus tard une demande de connexion
Accept int accept (int sock_desc, struct sockaddr *addr, int * addrlen ) sock_desc : descripteur du socket sur lequel on attend les demandes de connexion addr : Remplis avec les information du connecté (adresse+port). addrlen : Donne la taille de addr.
Little vs big andian Exemple : l entier 4 est stocké en mémoire 0004H sur une architecture Big Endian (Motorola, Apple) 0400H sur une architecture Little Endian (Intel, AMD, )
Les macros Représentation normalisée réseau octets de poids forts en tête, bits de poids fort en tête (Big andian) fonctions/macros de passage de représentation interne à représentation réseau et réciproquement : htons, htonl, ntohs, ntohl (voir manuel) Host TO Network Short Host TO Network Long
Bon à savoir Traduction adresse IP sur 32 bits normalisée réseau adresse décimale pointée : inet_aton, inet_addr (voir manuels) Remplissage à zéro d'une structure : memset ne pas oublier de remplir toutes les structures de type sockaddr_in à zéro avant de les compléter Récupération de l'adresse locale d'un socket auprès de l'os int getsockname (voir manuel)