Folder name for network pages
This commit is contained in:
parent
ab15c9a50a
commit
1270258505
2 changed files with 0 additions and 0 deletions
318
content/reseau/4_API_C/index.md
Normal file
318
content/reseau/4_API_C/index.md
Normal file
|
@ -0,0 +1,318 @@
|
|||
---
|
||||
title: "Programmation réseau avec l'API C"
|
||||
date: 2018-09-26
|
||||
modify: 2018-10-03
|
||||
tags: ["programmation", "C"]
|
||||
categories: ["Réseau", "Cours"]
|
||||
---
|
||||
# API C pour un paquet UDP (exemple du DNS)
|
||||
|
||||
1. `socket()` : Appel système qui retourne un identifiant unique (entier non
|
||||
signé) représentant une socket ouverte par le noyau, dans le style d'un
|
||||
descripteur de fichier.
|
||||
2. `bind(53)` : Le processus annonce qu'il se positionne sur le port 53 en
|
||||
écoute (serveur DNS)
|
||||
3. `rcvfrom()` : récupère les données (datagrammes) depuis le noyau, c'est un
|
||||
appel bloquant.
|
||||
4. `sento()` : le processus fournit au noyau les données pour le transfert (
|
||||
l'écriture). C'est l'équivalent de `write()` utilisé poour les fichiers.
|
||||
5. `close()` : ferme la connexion.
|
||||
|
||||
Ces étapes décrites ci-dessus sont valables pour la partie serveur, la partie
|
||||
client est plus simple : `socket()` -> `sendto()` -> `rcvfrom()` -> `close()`
|
||||
|
||||
# API pour un transfert TCP (avec connexion)
|
||||
|
||||
# côté serveur
|
||||
|
||||
1. `socket()`
|
||||
2. `bind(80)`
|
||||
3. `listen()` : annonce l'ouverture du port et l'écoute
|
||||
4. `accept()` : attend des connexion avec un client (état disponible)
|
||||
5. `read()` : appel système lit les données provenant du réseau, en HTTP ce
|
||||
serait la demande de la page `index.html` par exemple.
|
||||
6. `write()` : renvoi les données vers le réseau qui seront ensuite envoyées
|
||||
par le noyau vers le client.
|
||||
|
||||
## côté client
|
||||
|
||||
1. `connect()` : appel cloquant pour initier la connexion.
|
||||
2. `write()` : envoyer les donnés vers le client (la requete HTML par exemple)
|
||||
3. `read()` : lit les données (bloquant). Dès que les données sont prête, elles
|
||||
sont envoyées à l'application. Si rien n'est lut, l'appel se terminera alors
|
||||
seulement au moment du `close()`
|
||||
|
||||
# Socket
|
||||
|
||||
Pour démarrer une socket, plusieurs information son importante et changerons la
|
||||
manière de procéder :
|
||||
|
||||
- la famille d'adresse : IPv4, IPv6
|
||||
- le type de socket : échange simple de datagrammes (UDP) ou par une
|
||||
connexion (TCP)
|
||||
- le protocole : TCP, UDP, ICMP ...
|
||||
- l'adresse locale (IP et port)
|
||||
- l'adresse distante (IP et port)
|
||||
|
||||
# API C
|
||||
|
||||
Il faut bien avoir à l'esprit qu'en réseau, les données sont toujours au forman
|
||||
big-endian. Pour avoir plus d'explications voir la page Wikipedia sur
|
||||
[l'endianess](https://fr.wikipedia.org/wiki/Endianness).
|
||||
|
||||
Il faut donc penser à utiliser des fonctions de conversion :
|
||||
|
||||
```c
|
||||
/* Conversion vers/depuis ordre réseau */
|
||||
uint16_t htons(uint16_t hostshort); #host to network short
|
||||
uint16_t ntohs(uint16_t netshort); #network to host short
|
||||
uint32_t htonl(uint32_t hostlong); #host to network long
|
||||
uint32_t ntohl(uint32_t netlong); #network to host long
|
||||
|
||||
/* Conversion ascii/binaire */
|
||||
int inet_aton(const char *ascii, struct in_addr *binaire);
|
||||
char *inet_ntoa(struct in_addr binaire);
|
||||
|
||||
/* De même, mais portables v4/v6 */
|
||||
int inet_pton(sa_family_t af, const char *ascii, void *binaire);
|
||||
const char *inet_ntop(sa_family_t af, const void *binaire, char *ascii, socklen_t size);
|
||||
```
|
||||
|
||||
## Structure pour IPv4
|
||||
|
||||
```c
|
||||
struct in_addr {
|
||||
/* ... */
|
||||
__be32 s_addr;
|
||||
}
|
||||
struct sockaddr_in {
|
||||
sa_family_t sin_family; /* AF_INET6 */
|
||||
struct in6_addr sin_addr;
|
||||
__be16 sin_port;
|
||||
}
|
||||
```
|
||||
|
||||
## Structure pour ipv6
|
||||
|
||||
```c
|
||||
struct in6_addr {
|
||||
/* ... */
|
||||
u8 s6_addr[16];
|
||||
}
|
||||
struct sockaddr_in {
|
||||
sa_family_t sin6_family; /* AF_INET6 */
|
||||
struct in6_addr sin6_addr;
|
||||
__be16 sin6_port;
|
||||
}
|
||||
```
|
||||
|
||||
## API UDP
|
||||
|
||||
```c
|
||||
/* Créer une socket */
|
||||
int socket(int domain, int type, int protocol);
|
||||
/* e.g. fd = socket(AF_INET, SOCK_DGRAM, 0); */
|
||||
|
||||
int bind(
|
||||
int sockfd, # numéro de descripeur de socket
|
||||
const struck sockaddr *addr # sock_addr peut être de type 4 ou 6 (cast)
|
||||
socklen_t addrlen # taille de a structure sock_addr
|
||||
)
|
||||
/* Choisir l’adresse distante (IP et port), optionnel, pour utiliser
|
||||
send/write/recv/read plutôt que sendto/recvfrom */
|
||||
|
||||
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
|
||||
/* Envoyer un datagramme UDP à un service d’une machine donnée */
|
||||
ssize_t sendto(
|
||||
int sockfd,
|
||||
const void *buf,
|
||||
size_t len,
|
||||
int flags,
|
||||
const struct sockaddr *dest_addr,
|
||||
socklen_t addrlen
|
||||
);
|
||||
|
||||
/* Recevoir un datagramme UDP depuis n’importe quelle machine */
|
||||
ssize_t recvfrom(
|
||||
int sockfd,
|
||||
void *buf, #tampon de données (ici à recevoir)
|
||||
size_t len,
|
||||
int flags,
|
||||
struct sockaddr *src_addr,
|
||||
socklen_t *addrlen
|
||||
);
|
||||
|
||||
/* Fermer la socket */
|
||||
int close(int fd);
|
||||
```
|
||||
|
||||
## API C TCP
|
||||
|
||||
```c
|
||||
* voir man 7 tcp pour plus d’explications */
|
||||
/* Créer une socket */
|
||||
int socket(int domain, int type, int protocol);
|
||||
/* e.g. fd = socket(AF_INET, SOCK_STREAM, 0); */
|
||||
|
||||
/*** Partie client ***/
|
||||
/* Se connecter à un service d’une machine donnée */
|
||||
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
|
||||
/*** Partie serveur ***/
|
||||
/* Attacher à un port local donné */
|
||||
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
|
||||
/* Passer en mode serveur */
|
||||
int listen(int sockfd, int backlog);
|
||||
|
||||
/* Accepter une nouvelle connexion */
|
||||
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||
|
||||
/* Fermer la socket */
|
||||
int close(int fd);
|
||||
|
||||
/* Récupérer l’adresse locale */
|
||||
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||
|
||||
/* Récupérer l’adresse distante */
|
||||
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||
```
|
||||
|
||||
# `write()`, `read()`, Tampons noyau
|
||||
|
||||

|
||||
|
||||
Pour chacune des connexions établies le noyau Linux créer des tampons : un pour
|
||||
la lecture et un pour l'écriture. Pour la l'écriture, lorsqu'un processus
|
||||
invoque un `write()`, les données sont d'abord écrites dans un tampon. S'il est
|
||||
plein (sa taille est définie à l'avance), le noyau bloque l'appel `write()` et
|
||||
lorsque de la place se libère, il le débloque.
|
||||
|
||||
De l'autre côté, les données sont reçues dans le tampon `read()` et lorsqu'elles
|
||||
sont transférées au processus, un segment *ACK* est envoyé à l'expéditeur. le
|
||||
destinataire averti l'expéditeur de la place disponible dans son tampon `read()`
|
||||
par le biais du champ `window size` (octet 16 à 18 d'un sergent TCP) dans un
|
||||
segment *ACK*. Si le tampon contient, par exemple 2ko mais que le `read()` en
|
||||
demande 10ko, les 2Ko sont automatiquement transférés au processus.
|
||||
|
||||
Les tampons peuvent fausser complètement les outils de mesure de performance
|
||||
réseau, en effet lors d'un appel `write()` les données sont transmise dans le
|
||||
tampon très rapidement faussant alors la mesure du débit.
|
||||
|
||||
## visualiser les buffers.
|
||||
|
||||
Il est possible de visualiser les données en attentes dans les buffets avec la
|
||||
commande `netstat` :
|
||||
|
||||
```shell
|
||||
[root@eph-archbook ephase]# netstat
|
||||
Active Internet connections (w/o servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
tcp 0 0 eph-archbook:47954 mail.gandi.net:imaps ESTABLISHED
|
||||
tcp 0 0 eph-archbook:52264 imap.u-bordeaux.fr:imap ESTABLISHED
|
||||
tcp 32 0 ?192.168.1.91:34456 vpn-0-24.aquilene:https CLOSE_WAIT
|
||||
tcp 32 0 192.168.1.91:33424 vpn-0-24.aquilene:https CLOSE_WAIT
|
||||
...
|
||||
```
|
||||
|
||||
L'étal des tampons est indiqué par les champs `Recv-Q` et `Send-Q`.
|
||||
|
||||
## option de socket TCP
|
||||
|
||||
Les entêtes complète d'un datagramme TCP pèse au moins 54 octets (entêtes
|
||||
Ethernet et IP comprises). Afin d'optimiser l'envoi de données, il est souvent
|
||||
préférable de concaténer plusieurs appels `write()` ou d'utiliser `printf()` et
|
||||
les mécanisme de tampons de la libc. Il est aussi possible de demander à TCP
|
||||
d'attendre l'arrivée d'autres données avant d'envoyer un segment avec bien
|
||||
évidemment la gestion d'un *timeout* pour ne pas attendre trop longtemps
|
||||
(algorithme de [Nagle][l_nagle]). Ce n'est cependant pas adapté à tous les
|
||||
protocoles : si ce système fonctionne bien pour HTTP, ce n'est pas le cas de SSH
|
||||
ou la latence induite par ce mécanisme pose problèmes.
|
||||
|
||||
```C
|
||||
#include <sys/socket.h>
|
||||
|
||||
int setsockopt (int sockfd, int level, int optname, const void *optval, socklen_t optlen);
|
||||
int getsockopt (int sockfd, int level, int optname, void *optval, socklen_t optlen);
|
||||
```
|
||||
|
||||
- `int level` : niveau de la pile ou appliquer l'option, `SOL_TCP` signifiera
|
||||
la partie TCP.
|
||||
- `int optname` : l'option à activer, par exemple `TCP_NODELAY` afin de
|
||||
désactiver l'algorithme de Nagle. Il est possible aussi de mettre un
|
||||
bouchon au tampon `write()` avec `TCP_CORK`. Il faudra alors le désactiver
|
||||
pout permettre au noyau d'envoyer les segments depuis les données du
|
||||
*buffer*
|
||||
|
||||
[l_nagle]:https://fr.wikipedia.org/wiki/Algorithme_de_Nagle
|
||||
### l'option `SO_REUSEADDR` de `SOL_SOCKET`
|
||||
|
||||
Par défaut, le système empêchera la connexion à une socket si un service y
|
||||
était quelques minutes auparavant. Dans le cadre du test d'un programme ou pour
|
||||
du debug, il est parfois nécessaire de désactiver ce comportement, et c'est
|
||||
possible avec :
|
||||
|
||||
```C
|
||||
setsockopt ( fd, SOL_SOCKET, SO_REUSEADDR, "1");
|
||||
```
|
||||
|
||||
# La Qos (Qualité de Service)
|
||||
|
||||
En réseau il est possible d'obtenir :
|
||||
|
||||
- une bonne latence
|
||||
- un bon débit
|
||||
- de la fiabilité
|
||||
|
||||
Il ne sera pas possible d'obtenir les trois. Il existe plusieurs solutions pour
|
||||
prioriser les flux. Par exemple les petits paquets : leurs tailles indique
|
||||
sûrement que l'on recherche une meilleure latence plutôt qu'un gros débit.
|
||||
|
||||
Il est aussi possible de faire du DPI *Deep Packet Inspection* que ne serait pas
|
||||
très approprié du point de vue de l'éthique et de la loi (neutralité du net...)<
|
||||
En TCP il existe le champs ToS *Type of service" Mais celui-ci est inutilisé du
|
||||
fait de son implémentation différentes chez les fabricants de matériels et
|
||||
logiciels.
|
||||
|
||||
Les équipements sur Internet ont tous un système de buffer, ce qui n'est pas
|
||||
sans poser problèmes pour la *QoS* : s'il est facile de maitriser son routeur,
|
||||
éventuellement le DSLAM si son fournisseur d'accès à Internet le permet, il est
|
||||
est tout autres pour les équipements par lesquels transitent nos paquets.(voir
|
||||
le [Bufferbloat][l_buffetbloat])
|
||||
|
||||
[l_bufferbloat]:https://en.wikipedia.org/wiki/Bufferbloat
|
||||
|
||||
# API réseau et threads
|
||||
|
||||
Il est tout à fait possible d'utiliser les *threads* et les *processus* pour
|
||||
créer des buffers différents et ainsi paralléliser l'envoi / réception de
|
||||
données et ainsi éviter de bloquer l'ensemble de son application avec un
|
||||
`write()` ou un `read()`. Il est aussi possible d'utiliser l'API `SELECT` et
|
||||
ainsi éviter de complexifier son code avec des threads / processus.
|
||||
|
||||
```C
|
||||
void FD_ZERO(fd_set *set);
|
||||
void FD_CLR(int fd, fd_set *set);
|
||||
void FD_SET(int fd, fd_set *set);
|
||||
int FD_ISSET(int fd, fd_set *set);
|
||||
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
|
||||
|
||||
/* Attend des données lisibles sur fd1 ou fd2, traite en conséquence */
|
||||
int attend(int fd1, int fd2) {
|
||||
fd_set set;
|
||||
int max;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd1, &set);
|
||||
FD_SET(fd2, &set);
|
||||
max = MAX(fd1, fd2);
|
||||
if (select(max+1, &set, NULL, NULL, NULL) == -1)
|
||||
return -1;
|
||||
if (FD_ISSET(fd1, &set))
|
||||
traite(fd1);
|
||||
if (FD_ISSET(fd2, &set))
|
||||
traite(fd2);
|
||||
return 0;
|
||||
}
|
||||
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue