2 - Les Sockets Raw et quelques rappels par Nico

Préambule :

Cet article a pour but de vous familiariser avec la programmation des sockets en C sous Linux pour peu que vous sachiez déjà programmer dans ce beau langage et que vous ayez déjà lu un tutorial simple sur les sockets pour vous dégrossir sur les notions de client/serveur. Ce serait bien aussi d’avoir lu le mag n°1 qui explique les protocoles, et les explications de Tipiax sur les applications client/server en C++, et puis les RFC sur IP, TCP, ICMP et UDP (les différents protocoles quoi). Enfin pour ceux qui veulent tout savoir, je leur conseille « Unix Network Programming » de W. Richard Stevens, il coûte la peau du cul et il est en anglais, mais il est vraiment très complet.

 

A - La toolbox du programmeur
On va d’abord passer en revue les fonctions C à connaître pour programmer avec les sockets. Je rappelle que l’article s’adresse à ceux qui veulent programmer les sockets sur un système Unix/Linux et qui ont par conséquent une documentation extrêmement riche sur ces fonctions car il serait un peu idiot de recopier les pages man :-). L’idée c’est donc de donner ici une sorte d’aide-mémoire pour vous aider à programmer qu’il faudra compléter par les pages man quand vous regarderez ce qui se passe un peu plus en détail.
Quand une fonction sera marquée comme ça « fonction (x) », ça signifie que si vous voulez des détails il faudra faire : #man x fonction.
A.1 - Les fonctions :
a. Les fonctions spécifiques aux sockets :
Toutes ces fonctions nécessitent au moins l’un des deux entêtes suivants (je vous conseille de mettre les 2 par défaut pour être tranquilles même s’il faut avouer que ce n’est pas une bonne méthode de programmation) :
#include <sys/types.h>
#include <sys/socket.h>
socketpair (2)
déclaration :
int socketpair ( int domain, int type, int protocole, int sv[2]);
explications:
Si vous lisez ce type de mag, c’est qu’il y a peu de chance que cette fonction vous intéresse puisqu’elle crée une paire de sockets connectées internes au système (c’est-à-dire surtout pour des sockets de domaine PF_LOCAL). Les descripteurs de fichiers des sockets sont entrés dans sv[]. Comme beaucoup de fonctions l’entier renvoyé permet un contrôle d’erreur.
Socket (2)
Déclaration :
int socket( int domain, int type, int protocol);
explications:
La fonction crée la socket et renvoie le descripteur de fichier correspondant. Le domaine représente la famille d’adresses que vous allez utiliser avec la socket, par exemple PF_INET signifie que vous utiliserez les adresses Internet (type ipv4). Le type précise le protocole et la façon d’échanger les datas :
SOCK_STREAM pour le mode connecté, flux « full-duplex »
SOCK_DGRAM mode non connecté, pour le protocole UDP
SOCK_RAW pour le protocole IP Le protocole est choisi par le noyau lorsque vous mettez protocol=0, mais rien ne vous empêche de préciser votre protocole sous réserve qu’il soit compatible avec le type et le domaine choisis. Les protocoles sont placés dans /etc/protocols
Bind (2)
Déclaration :
int bind( int sockfd, struct sockaddr *my_addr, int addrlen);
explications:
Bind lie la socket de descripteur sockfd à une adresse locale afin de pouvoir être joint de l’extérieur. Bind peut choisir le port quand on met zero dans la structure my_addr, ou l’adresse quand on met INADDR_ANY dans la même structure. Mais on doit au moins choisir l’un des 2 quand on fait appel à bind(). Dernière chose, il n’est pas nécessaire d’appeler bind() quand on crée une socket client, mais on peut…
Connect :
Déclaration :
int connect( int sockfd, struct sockaddr *servaddr, int addrlen);
explications:
Connect() lie la socket à l’adresse du serveur distant don’t les coordonnées ont été préalablement rentrées dans la structure servaddr. C’est une connexion en mode connecté et juste une indication d’adresse en mode non connecté.
Listen (2)
Déclaration :
int listen( int s, int backlog);
explications:
Cette fonction utilisée avant un appel de accept() indique le nombre de personnes que l’on acceptera dans la file d’attente de connexion (pour les sockets serveur).
Accept (2)
Déclaration :
Int accept( int s, struct sockaddr *addr, int *addrlen) ;
Explications:
Cette fonction se connecte s avec une socket appelante. Il faut noter qu’ici addr et addrlen sont ce que Stevens appelle des « value-result » arguments, c’est-à-dire que la valeur de ces arguments sera modifiée à l’appel de la fonction et c’est pour ça qu’on les passe par référence. Deuxième remarque et valables pour les autres fonctions demandant un argument de type *sockaddr : la plupart du temps on utilise la structure sockaddr_in (voir plus bas le paragraphe sur les structures utilisées) pour entrer les coordonnées du client ou du serveur, mais les fonctions n’acceptent souvent que l’argument *sockaddr, aussi il faudra « caster » (les programmeurs connaissent déjà ce mot) l’argument de type *sockaddr_in comme dans l’exemple ci-dessous.
Accept( s, (struct sockaddr *) &addr, &addrlen);
Où: addr est une structure de type sockaddr_in, et addrlen un entier représentant la taille de addr.
b. Les fonctions d’adressage de sockets
Ces fonctions nécessitent l’entête : #include <sys/types.h>
Getsockname (2)
Déclaration :
Int getsockname( int s, struct sockaddr *name, socklen_t *namelen) ;
Explications :
Rappelez-vous ce qu’on a dit sur les « value-result » arguments détaillés plus haut. La fonction récupère l’adresse de la socket s et rentre les données dans la structure name (adresse ip, et port). Il faudra aussi faire attention aux conversions de présentation quand on utilise ce type de fonctions car l’adresse contenue dans la socket a déjà été convertie en ‘Network Byte Order’, et n’est donc pas lisible pour nous…
Getpeername (2)
Déclaration :
Int getpeername( int s, struct sockaddr *name, socklen_t namelen) ;
Explications :
Cette fonction ressemble comme deux gouttes d’eau à getsockname, sauf qu’ici on récupère l’adresse du correspondant (remote) connecté sur la socket.
c. Les fonctions qui permettent de lire sur une socket
Read (2)
Déclaration :
#include <unistd.h>
#include <sys/types.h>
ssize_t read( int fd, void *buf, ssize_t count);
explications:
Read lit dans le descripteur de fichier fd count bytes (octets) et remplit buf avec. On n’oubliera pas de ‘caster’ l’argument buf, histoire que la fonction sache sur quelle genre de truc il pointe… La fonction renvoie -1 en cas d’échec et le nombre de bytes lus sinon.
Readv (2)
Déclaration :
#include <sys/uio.h>
int readv( int fd, const struct iovec *vector, int count);
struct iovec {
ptr_t iov_base ;
size_t iov_len ;
} ;
explications:
Je n’utilise pas cette fonction (!). Mais si elle vous plait… :-)
Recv (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int recv( int s, void *buf, int len, unsigned flags);
explications :
Le premier avantage de cette fonction est qu’elle fonctionne en mode connecté mais aussi en protocole UDP. Le second est qu’on peut placer des options en argument, par exemple, MSG_OOB permet de recevoir les messages hors-bande qui sont ignorés par défaut. La fonction lit les données depuis la sockets et les places dans le buffer buf ( de taille len)
Recvfrom (2)
Déclaration :
#include : idem recv
int recvfrom( int s, void *buf, int len, unsigned flags, struct sockaddr *from, int fromlen);
explications :
Recvfrom fonctionne comme recv sauf que l’adresse de l’émetteur sera entrée dans le buffer from (dont la taille initialise fromlen), et elle joue exactement le même role lorsqu’on remplace from par 0. Reportez vous aux man pages pour des précisions sur les flags que vous pouvez entrer comme arguments. A titre d’exemple, le flag MSG_OOB vous permet de recevoir les datas out-of-band, et MSG_PEEK vous permet de lire sans enlever de données. Les flags sont séparés par un ‘|’.
Recvmsg (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int recvmsg ( int s, struct msghdr *msg, unsigned int flags);
explications :
Cette fonction est puissante mais je ne sais pas l’utiliser. So si vous voulez aller plus loin vous savez ce qu’il vous reste à faire…
d. les fonctions qui permettent d’écrire sur une socket
Write (2)
Déclaration :
#include <unistd.h>
ssize_t write( int fd, const void *buf, size_t count);
explications:
Ici, fd sera votre socket, la fonction écrira count octets dans fd depuis buf. Il faut noter que cette fonction ne marche qu’en mode connecté. Donc si vous voulez utiliser une fonction qui marche dans tous les cas, il vaut mieux utiliser sendto.
Writev (2)
Déclaration :
#include <unistd.h>
int writev( int fd, const struct iovec *vector, int count);
explications :
C’est la partie écriture de readv comme vous l’avez déjà deviné puisque vous êtes rusés J. Ici l’idée c’est toujours de prendre les données à partir d’un vecteur comme buffer au lieu d ‘écrire dedans comme toute à l’heure.
Send (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int send( int s, const void *msg, int len, unsigned int flags);
explications : cf sendto()
Sendto (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int sendto( int s, const void *msg, int len, unsigned flags, const struct sockaddr *to, int tolen);
explications :
On donne ici les éléments sur send puisque send est strictement équivalente à la fonction sendto avec l’argument ‘to’ null. Send ne marche qu ‘en mode connecté et c’est pour ça qu’on n’a pas besoin de donner l’adresse du correspondant en argument puisqu’elle a déjà été entrée à l’appel de connect().
Msg= message à envoyer
Len= sa taille
To= l’adresse de votre correspondant ;)
Tolen= taille de la structure sur laquelle to pointe
Les flags sont séparés d’yn ‘|’ comme avant si vous en mettez plusieurs, pour ceux qui veulent fouiner :
MSG_OOB on en a déjà parlé de celui-là
MSG_DONTROUTE pour ne pas router le packet
MSG_DONTWAIT ne pas bloquer avant d’écrire
MSG_NOSIGNAL ne pas émettre de signal
Sendmsg (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int sendmsg( int s, const struct msghdr *msg, unsigned int flags);
explications :
Demandez à KoP ; )
e. Autres Entrées/Sorties
Select (2)
Déclaration :
#include
#include
#include
int select( int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
explications :
Si vous débutez avec les sockets, vous pouvez sauter cette partie qui va un peu plus loin que les fonctions qui ont déjà été évoquées et qui ne vous sera pas d’une grande utilité pour l’instant.
Pour utiliser cette fonction il faut aussi connaître :
Struct timeval {
Long tv_sec ; /*durée en secondes*/
Long tv_usec; /*durée en milliseconds*/
};
Et les macros détaillées plus bas.
Dans un souci de faire un article à peu près homogène, je détaille le fonctionnement de cette fonction dans la partie “concept avancés” pour les curieux…
f. contrôle des sockets
shutdown (2)
Déclaration :
#include <sys/socket.h>
int shutdown( int s, int how);
explications:
Cette fonction sert à fermer votre socket de façon séléctive, contrairement à close qui libère complètement la socket.
s = votre socket
how = votre flag, à savoir:
_ SHUT_RD = 0, ferme votre socket en lecture
_ SHUT_WR = 1, ferme votre socket en écriture
_ SHUT_RDWR = 2, je suis sûr que vous avez deviné, en fait c’est équivalent à close()
Getsockopt (2)
Déclaration :
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt( int s, int level, int optname, void *optval, socklen_t *optlen);
explications:
Comme select(), je détaillerai les options des sockets dans la 2ème partie parce qu’il y a beaucoup de choses à dire.
Setsockopt (2)
#include <sys/typse.h>
#include <sys/socket.h>
int setsockopt( int s, int level, int optname, const void * optval, soclen_t optlen);
explications: cf précédemment
Dup (2)
Déclaration:
#include <unistd.h>
int dup( int oldfd);
explications:
Ca sert quand vous voulez travailler sur une socket sans l’appeler elle-même. En fait oldfd est l’entier qui décrit votre socket et le int renvoyé par dup est le nouveau descripteur de fichier sur lequel vous allez pouvoir travailler avec par exemple read, write, etc…
Fnctl (2)
Déclaration :
#include <unistd.h>
#include <fnctl.h>
int fnctl( int fd, int cmd, long arg);
explications:
Cette fonction sert à travailler sur le descripteur de fichier de votre socket, c’est-à-dire l’entier qui la définit. Vous allez penser que ça devient une habitude mais là encore je détaillerai ça dans la suite, parce fnctl tout comme ioctl qui suit juste après, ne font pas partie des concepts de base, donc pour ceux qui veulent pousser, rendez-vous dans la 2ème partie !
Ioctl (2)
Déclaration :
#include <sys/ioctl.h>
int s, z, flag;
z = ioctl( s, SIOCATMARK, &flag);
if ( z == -1)
abort();
if ( flag != 0)
puts(“At mark”);
else
puts(“Not at mark”);
g. fonctions de conversion et fonctions réseau
Ce serait long et pénible de toutes les citer ici, donc on ne donne que les plus importantes, notamment pour les conversions.
Première chose à faire : #man 3 byteorder
Cela vous donne la doc sur plusieurs fonctions qui méritent une petite explication. Byteorder c’est la façon de vote ordi de stocker les octets en mémoire. Il y a principalement 2 façons, Big Endian et Little Endian. Votre ordi stocke probablement les octets avec la méthode Little Endian qui -parce qu’on suit toujours la loi de Murphy- est l’inverse de celle qui est en vigueur sur le réseau, Big Endian. Ca veut dire que quand vous rentrez une adresse ip par exemple, il faut la convertir en Big Endian pour qu’elle soit comprise sur le réseau. La plupart du temps vous convertirez des adresses en Network Byte Order (Big Endian). Les fonctions qui concernent ce type de conversion sont dans :
Unsigned long htonl( unsigned long hostlong) ;
Unsigned short htons( unsigned short hostshort);
Unsigned long ntohl( unsigned long netlong);
Unsigned short ntohs( unsigned short netshort);
En fait vous vous dites peut-être qu’elles se ressemblent et que vous n’allez pas vous en souvenir, en fait c’est très facile, parce que le nom des fonctions n’a pas été choisi au hasard J : je donne un exemple et pour pourrez décoder les autres noms à partir de ça
Htonl = « Host to network long »
Avec à mémoriser simplement que host est en little endian alors que network est en big endian.
Deuxième chose à faire : #man 3 inet
Une adresse ip en notation 127.0.0.1 n’est pas comprise par l’ordi, ni le réseau, donc il faut la convertir en binaire et c’est le rôle des fonctions de la classe inet. Mais attention, ici encore, l’ordre des bytes du réseau intervient.
Pour utiliser ces fonctions :
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long int inet_addr( const char *cp);
C’est celle que vous utiliserez la plus souvent, elle prend comme argument votre adresse de type 127.0.0.1 et elle la convertit en binaire en network byte order. Dans le même ordre d’idée inet_aton convertit votre 127.0.0.1 en binaire, mais ensuite il faudra la mettre en network byte order. Autre remarque, pour vos conversions vous pouvez aussi utiliser htonl( atoi() ), atoi transforme votre chaîne de caractère (c’est souvent comme ça que vous entrerez votre ip) en entier puis htonl vous le met en network byte order.
Enfin vous pourrez aussi jeter un coup d’œil aux fonctions qui convertissent dans l’autre sens qui pourront servir quand vous voudrez récupérer une adresse du type 127.0.0.1 à partir du champ d’une structure qui contient l’adresse en binaire network byte order.
Troisième chose à faire : #man 3 getservent
Dedans on y trouve les fonctions suivantes, qui nécessitent <netdb.h>:
Struct servent *getservent( void ) ;
Struct servent *getservbyname( const char *name, const char *proto) ;
Struct servent *getservbyport( int port, const char *proto) ;
Void setservent( int stayopen) ;
Void entservent( void ) ;
Explications :
La structure servent est expliquée un peu plus bas. Les fonctions listées ci-dessus se réfèrent au fichier /etc/services (je crois qu’il est à la même place dans toutes les distributions de linux, sinon faite une recherche…), que vous avez le DROIT de consulter pour info J. Les 3 premières fonctions permettent de retrouver un service en fonction des champs qui sont renseignés dans la structure correspondante.
Setservent () « rembobine » le fichier /etc/services et assure que le fichier restera ouvert pour les appels suivants à ce fichier si stayopen vaut 1. On peut imaginer l’utilisation de telles fonctions dans un scanner de ports par exemple ; ) pour vous afficher des infos sur les services qui tournent sur les ports ouverts.
Quatrième chose à faire : #man 3 getprotoent
Cette partie ressemble énormément à la précédente, sauf qu’au lieu de parler de services on parle de protocoles. Le fichier associé est cette fois /etc/protocols (#cat /etc/protocols | more, ou si vous voulez par exemple tout ce qui est associé au protocole tcp par exemple, vous tapez #cat /etc/protocols | grep tcp). Les fonctions ont exactement le même rôle que les précédentes en remplaçant « serv » par « proto » dans les fonctions, et elles font appel cette fois à la structure protoent (cf plus bas).
h. Denière étape dans cette liste fastidieuse , les fonctions de résolution de noms ; )
Gethostname (2)
Déclaration :
#include <unistd.h>
int gethostname( char *name, size_t len);
int sethostname( const char *name, size_t len);
explications:
Comme souvent len représente la taille du buffer en argument. La première fonction lit le nom d’hôte de la machine et le place dans le buffer pointé par name. La 2ème change le nom d’hôte de la machine en plaçant celui contenu dans le buffer pointé par name. Ces fonctions font déjà sûrement germer des idées dans votre tête…
Getdomainname (2)
Déclaration :
#include <unistd.h>
int getdomainname( char *name, size_t len);
explications:
Comme vous l’avez compris cette fonction fait exactement la même chose que gethostname() sauf qu’elle travaille sur le nom de domaine. Ainsi avec les 2 fonctions précédentes vous pouvez obtenir ‘localhost.localdomain’ et les changer.
Gethostbyname
Je veux parler ici du groupe de fonctions qu’elle représente et pas seulement de la fonction en elle-même. Pour utiliser ces fonctions : #include <netdb.h>
Les 2 plus importantes (bien que tout soit relatif ; ) ) sont :
Struct hostent *gethostbyname( const char *name) ;
Struct hostent *gethostbyaddr( const char *addr, int len, int type);
Explications:
Les 2 travaillent avec la structure hostent que l’on verra un peu plus bas. Leur fichier associé est /etc/hosts et /etc/host.conf (allez jeter un coup d’œil dans ces fichiers, si vous voyez marqué KoP quelque part c’est que vous avez été piraté ;))).
La première accepte en argument pour name un pointeur sur une adresse ip classique (127.0.0.1) ou bien un nom d’hôte, la 2ème n’accepte que les adresses AF_INET (ip, en fait ipv4 plus exactement, mais bon on va quand même pas traiter ipv6 ici surtout que je ne le maîtrise pas du tout J). Vous comprendrez mieux ce que peuvent faire ces fonctions quand vous verrez la tête d’une structure hostent.
Si vous voulez aller un peu plus loin vous pouvez aussi regarder les fonctions sethostent et endhostent.
 
A.2 les structures
a. structures d’adresse
Sockaddr
#include <sys/socket.h>
struct sockaddr {
sa_family_t sa_family ; /*famille d’adresses, pour nous AF_INET */
char sa_data[14] ; /* données */
} ;
Rappel : c’est toujours cette structure qui est utilisée en argument pour les fonctions, c’est pour ça qu’il faudra caster vos structures sockaddr_in dans les appels aux fonctions.
Sockaddr_un
#include <sys/un.h>
Struct sockaddr_un {
Sa_family_t sun_family ; /* famille d’adresses */
Char sun_path[108] ; /* données */
} ;
Remarque : Ces structures sont utilisées pour les sockets du domaine unix, mais si vous lisez cet article, c’est probablement pas ce qui vous intéresse le plus… :-)
Sockaddr_in et in_addr
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family ; /* PF_INET */
uint16_t sin_port ; /* port de la victime J */
struct in_addr sin_addr; /* adresse ip de la victime J */
unsigned char sin_zero[8]; /* ?? */
};
struct in_addr {
uint32_t s_addr; /*adresse internet */
};
struct linger
#include <linux/socket.h>
struct linger {
int l_onoff; / *explications dans la partie 2.2 * /
int l_linger ;
};
Remarque: souvenez-vous que le n° de port et l’adresse ip doivent être convertis en binaire & network byte order avant de remplir les structures.
Il y a d’autre structures pour des protocoles plus ou moins exotiques mais vous pourrez jeter un coup d’œil dans votre doc linux pour plus de détails, personnellement je trouve qu’il y a déjà assez de choses à dire sur ipv4…
b. autres structures
servent
struct servent {
char *s_name; /*nom du service */
char **s_aliases; /*liste des autres pseudos possibles*/
int s_port; /* n° de port*/
char s_proto ; /*protocole*/
};
protoent
struct protoent {
char *p_name ; /nom du protocole*/
char **p_aliases ; /*liste de ses autres petits noms J*/
int p_proto ; /*n° du protocole
} ;
hostent
struct hostent {
char *h_name; /*nom de l’hôte*/
char **h_aliases ; /*liste de ses autres petits noms*/
int h_addrtype ; /*type d’adresse de l’hôte*/
int h_length ; /*longueur de l’adresse*/
char **h_addr_list ; /*liste des addresses possibles pour joindre l’hôte*/
} ;
Remarque : visiblement, il faut mettre #define h_addr h_addr_list[0] dans vos entêtes pour des raisons de compatibilité.
Ici encore je n’ai pas listé toutes les structures, notamment celles qui ont un rapport avec recvmsg et sendmsg puisque je ne sais pas utiliser ces fonctions à l’heure où j’écris, mais vous les trouverez dans les pages man se rapportant aux fonctions qui les utilisent.
 
B. Notions plus avancées sur la programmation des sockets
Cette partie n’est lisible que si vous avez compris ce qui précède, en tout cas en grande partie. Tout ce qui suit n’est pas forcément nécessaire pour écrire de beaux programmes avec des sockets, mais comme on n’en parle jamais dans d'autres tutoriaux et que HackerStorm vous offre des infos différentes J et ben on va en parler quand même !
Il faut quand même savoir que pour W. Richard Stevens - qui était pour moi la référence dans le domaine - certaines de ces parties sont à ranger dans les concepts élémentaires…
B.1 Multiplexage avec les fonctions select() et poll()
Préambule :
J’insiste sur le fait que cette partie n’est pas obligatoire pour savoir programmer des sockets en C, elle s’adresse plutôt à ceux qui veulent améliorer leur code, la fonction select apportant des atouts pour coder des serveurs mais aussi des clients plus robustes et pour avoir une maîtrise un peu plus approfondie de ce qui se passe. Si je dis ça c’est pour que vous ne me reprochiez pas de vous être endormis en lisant ce qui suit. ; )
Ensuite je me suis rendu compte en écrivant l’article que la fonction poll() est plus compliquée que select(), et comme je ne la maîtrise pas bien je préfère finalement la passer sous silence (elle peut en fait prendre en argument une pléiade d’options). Si vraiment ça vous intéresse, ben dites-le à KoP par mail et puis j’essaierai de faire un truc dessus dans le prochain mag.
Ceci étant dit, on peut rentrer dans le vif du sujet J.
Le multiplexage avec select() : c’est quoi ? ça sert à quoi ?
Prenons l’exemple d’un client qui est bloqué en lecture sur son entrée standard après un appel à fgets(), et que le processus du serveur se termine en envoyant un FIN. Ben le client ne verra le EOF que quand il lira dans la socket, et si par malheur il a décidé d’écrire sa vie il saura trop tard que ça servait à rien d’écrire tout ça puisque le serveur ne traitera pas sa demande (je sais j’ai un humour à chier). Select() va nous servir à prévenir le client que le serveur lui a envoyé un FIN. Mais ça peut servir encore à d’autres choses, voici quelques scénarios :
- le client a une entrée interactive et une socket sur le réseau
- le client gère plusieurs sockets en même temps (si vous voulez coder un scanner rapide par exemple ; ))
- un serveur a une socket listen et une socket connectée (ça c’est tout le temps J)
- le serveur accepte plusieurs protocoles
Pour bien saisir il faut comprendre ce qu’est une Entrée/Sortie bloquante. On prend l’exemple du protocole UDP. Après un appel de recvfrom(), le noyau se bloque en attendant qu’un paquet arrive. Quand il arrive, le noyau ‘réveille’ l’application appelante pour traiter le paquet. On voit bien ici que le blocage se fait sur recvfrom(). Le multiplexage permet de transférer le blocage sur select() qui est prévenue par le noyau quand un paquet arrive et on appelle alors recvfrom(). 1ère remarque : on pourrait en dire beaucoup plus sur les modèles d’E/S non-bloquants, gérés par signaux ou asynchrones, mais je n’ai ni le temps ni les compétences d’écrire un bouquin la-dessus.
2ème remarque : vous allez me dire ‘à quoi elle sert ta fonction select() si le noyau est bloqué de toute façon sur cette fonction ??’ Ben la différence, c’est qu’on peut attendre comme ça les réponses de plusieurs sockets, des dizaines si ça vous plaît !
 
La fonction select()
La fonction dit au noyau de réveiller le processus appelant quand un événement parmi plusieurs se produit (on peut aussi utiliser la fonction select() avec d’autres descripteurs de fichier que les sockets).
Int select( int maxfdp1, fd_set *readset, fd_set *write_set, fd_set *exceptset, const struct timeval *timeout);
Timeout: on a donné la structure timeval un peu plus haut. Cet argument dit combien de temps on doit attendre au maximum si aucun des événements attendus ne se produit. Sachant que, un pointeur NULL correspond à un temps infini, et si la structure pointée par timeout est nulle (notez BIEN la différence) la fonction vérifie et retourne immédiatement (c’est ce qu’on appelle le polling). Il faut aussi noter que l’attente peut être interrompue par l’arrivée d’une interruption (signal).
Dernière chose, pour Linux timeout est un ‘value-result’ argument (on en a déjà parlé) et donc il faudra initialiser la variable avant chaque appel de select().
 
Exceptset : sert à traiter des événements spéciaux comme l’arrivée d’un message out-of-band par exemple.
Avant de voir les macros qui s’appliquent aux variables de type fd_set il faut comprendre sur quoi travaille select().
Si vous ne connaissiez pas le mot ‘descripteur de fichier’ vous l’avez utilisé sans le savoir : un descripteur de fichier c’est l’entier qui caractérise un flux, donc par exemple l’entier renvoyé par fopen, ou bien simplement l’entier qui définit une socket. En fait, la fonction select() travaille sur un tableau d’entiers, par exemple de 32 bits, dans lequel chaque bit de chaque entier représente un descripteur de fichier (ça devient assez exotique là non ?? J). Donc par exemple le premier entier du tableau contient le set des descripteurs 0 à 31. Quand je dis ‘contient le set’ c’est assez approximatif. En fait je veux dire par là que le bit de chaque descripteur est ‘activé’ ou ‘déactivé’ (0). Lorsque le bit d’un descripteur est activé, ça veut dire que select va dire au noyau de surveiller ce descripteur et de réveiller le processus appelant s’il se passe quelque chose dessus. J’espère que je suis assez clair… Mais de toute façon je vais vous filer un exemple pour que ça soit totalement limpide.
Maintenant un petit tour du côté des macros qui travaillent sur les variables de type fd_set :
Void FD_ZERO( fd_set *fdset) ; /* mets tous les bits de fdset à 0 */
Void FD_SET( int fd, fd_set *fd_set) ; /* active le bit correspondant à fd dans fset */
Void FD_CLR( int fd, fd_set *fdset); / * inverse de la précédente */
Int FD_ISSET( int fd, fd_set *fdset) ; /* le bit caractérisant fd est-il active dans fdset ? renvoie 0 si non */
Un exemple maintenant pour définir un set et activer les bits pour les descripteurs 1 et 4 :
Fd_set reset ;
FD_ZERO( &reset);
FD_SET( 1, &reset);
FD_SET( 4, &reset);
Maxfdp1: en fait c’est le n° du descripteur le plus élevé +1 et au maximum 1024.
Remarque : les fd_set étant des value-result arguments, les descripteurs non-testés auront leur bit correspondant mis à 0 au retour de select.
La fonction select() teste si les descripteurs invoqués sont ‘ready’ ou non en lecture et en écriture. Pour qu’une socket soit ‘ready’ en lecture ou plus simplement ‘lisible’, il faut qu’il reste des données à lire, ou qu’elle aie reçu un FIN (elle ne bloquera pas et retournera 0), ou qu’elle soit en mode listen, ou enfin qu’une erreur soit attendue à l’appel de la socket( elle ne bloquera pas mais renverra -1).
Pour qu’une socket soit ‘ready’ en écriture (ça je sais pas le traduire ; )), il faut qu’il reste des données à écrire, ou que la partie écriture soit fermée( après un appel de shutdown avec SHUT_WR en 2ème argument ), ou enfin qu’une erreur soit attendue (pour les mêmes raisons que l’explication en lecture).
Pour illustrer tout ça et pour en finir avec select(), deux petites illustrations que j’ai prises dans le bouquin de feu Stevens (ben ouais il est décédé et franchement c’est une sacrée perte pour l’informatique te l’humanité en général) :
                                                                   Client
                                             -------------------------------------
                                             |                  data ou eof                   |
                                             -------------------------------------
                                             |                       stdin                        |
                                             |                                                      |
                                             |                                                      |
                                             |                                                      |
                                             |                      socket                      |
                                             --------------------------------------
                                                                     /   |   \
                                                           error  /    |    \ EOF
                                                                    /    |      \
                                                                   /     |       \
                                                                  /      |        \
                                                                 /       |         \
                                                                /        |          \
                                                               /         |           \
                                                              /          |            \
                                                             /           |             \
                                                            /            |              \
                                                           /             |               \
                                                          /              |                \
                                                         /               |                 \
                                                        /                |                  \
                                                       /                 |                   \
                                                      /                  |                    \
                                       |           RST          DATA              FIN
                               TCP |
                                       |
                                       |
On voit ici TRES CLAIREMENT (rigolez pas c’est pas facile de dessiner comme ça !) le types d’événements que permet de traiter select(), et ce de façon simple et efficace. On imagine que le client est connecté comme sur le dessin et qu’il exécute la fonction cli qui reçoit en argument un descripteur fp (en réalité stdin) et en 2ème argument le descripteur sockfd de la socket connectée :
Void cli( FILE *fp, int sockfd)
{
int maxfdp1;
fd_set reset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO( &reset);
For( ;; )
{
FD_SET( fileno(fp), &reset); / *fileno renvoie le n° du descripteur fp */
FD_SET( sockfd, &reset) ;
Maxfdp1 = max( fileno(fp), sockfd) +1;
Select( maxfdp1, &reset, NULL, NULL, NULL); / *rappelez-vous le sens du pointeur NULL pour timeval * /
If ( FD_ISSET( sockfd, &reset))
{
     if ( readline( sockfd, recvline, MAXLINE) == 0)
     err_quit( “cli: serveur fini prématurément”);
     fputs( recvline, stdout) ;
}
if ( FD_ISSET( fileno(fp), &reset))
{
     if( fgets( sendline, MAXLINE, fp) == NULL) return;
     writen( sockfd, sendline, sizeof(sendline));
}
}
}
Remarque: pour les maniaques du Ctrl-C Ctrl-V, sachez que ce code n’est pas exploitable. En effet, readline, writen, et err_quit sont des fonctions que Stevens a écrites pour ses programmes parce qu’il faut toujours qu’il corrige les fonctions qui existent déjà et qui ne lui plaisent jamais tel quel. Malgré ça je trouve que le code est vraiment clair et qu’il illustre bien tout ce qu’on a dit avant, maintenant si vous avez lu jusque là et que vous n’avez pas compris ben c’est que je ne suis pas clair et dans ce cas pourquoi ne pas poser vos questions sur le forum, ça le fera vivre un peu.

 

B.2 Les options des sockets
Bon pour commencer, quelques avertissements… Cette partie peut vous paraître indigeste ( un peu comme tout ce que j’écris J), mais elle n’est pas à connaître sur le bout des doigts ! C’est plutôt un petit guide de référence assez exhaustif (mais pas complet car linux a également d’autres options supplémentaires) qui vous servira j’en suis sûr quand vous commencerez à affecter des options à vos sockets. On rappelle les 2 fonctions essentielles évoquées plus haut pour traiter les options ( toutes les 2 dans <sys/socket.h>) :
Int getsockopt( int sockfd, int level, int optname, void *optval, socklen_t *optlen) ;
Int setsockopt( int sockfd, int level, int optname, const void *optval, socklen_t optlen);
Les deux fonctions renvoient 0 en cas de succès et -1 en cas d’échec.
Pour la documentation vous pouvez aussi faire #man 2 getsockopt, car la documentation de C. Blaess (Programmation système en C sous Linux) est bien faite.
Sockfd : à ce niveau du texte j’imagine que vous savez ce que c’est ; )
Level : SOL_SOCKET au niveau de la socket et valeur du protocole correspondant quand on se place plus haut (-> page man corrspondante)
Optval :
- pour getsockopt() ce sera un pointeur vers un buffer où la valeur courante de l’option invoquée sera stockée
- pour setsockopt() ce sera un pointeur vers un buffer contenat la valeur de la nouvelle option
optlen : on y range la taille de optval, en notant que dans getsockopt(), c’est un ‘value-result’ argument qui sera modifié avec la nouvelle taille de optval après le retour de la fonction.
On distingue 2 principaux types d’options :
- Les options ‘flags’ qui activent / désactivent certains comportements. Pour celles-la, *optval = 0 si le comportement est désactivé et !=0 dans le cas contraire.
- Les autres options traitent et retournent des valeurs qu’on peut initialiser / examiner.
Les tableaux suivants ( Stevens ) listent les options des sockets avec leurs caractéristiques et si on peut les obtenir avec getsockopt ou les modifier avec setsockopt :
Level optname get set description flag Type données
SOL_SOCKET SO_BROADCAST * * Permet la diffusion de datagrammes Broadcast * int
  SO_DEBUG * * Traçage pour débuguer une connexion * int
  SO_DONTROUTE * * Ne regarde pas dans la table de routage * int
  SO_ERROR * non Donne les erreurs en attente et retourne non int
  SO_KEEPALIVE * * Teste régulièrement si la connexion est toujours OK * int
  SO_LINGER * * Bloque un close() s’il reste des données à envoyer non Struct linger non Struct linger
  SO_OOBINLINE * * Traite les messages out-of-band comme normaux * int
  SO_RCVBUF * * Taille du buffer de réception non int
  SO_SNDBUF * * Taille du buffer d’émission non int
  SO_RCVLOWAT * * Taille mini du buffer de réception non int
  SO_SNDLOWAT * * Taille mini du buffer d’émission non int
  SO_RCVTIMEO * * Timeout en réception non non Struct timeval
  SO_SNDTIMEO * * Timeout en émission non non Struct timeval
  SO_REUSEADDR * * Autorise la réutilisation de l’adresse locale * int
  SO_REUSEPORT * * Idem avec le port * int
  SO_TYPE * non Donne le type de socket non int
  SO_USELOOPBACK * * Les sockets de routage loggent ce qui passe * int
Le tableau suivant donne les options du protocole IPv4 :
Level optname get set description flag Type données
IPPROTO_IP IP_HDRINCL * * Entête IP + données * int
  IP_OPTIONS * * Options d’entêtes IP non Détails plus bas
  IP_RECVDSTADDR * * Retourne l’adresse IP de destination * int
  IP_RECVIF * *   * int
  IP_TOS * * Type de service non int
  IP_TTL * * Time to Live (durée de vie du paquet sur le réseau) non int
  IP_MULTICAST_IF * * Indique l’interface d’émission non Struct in_addr
  IP_MULTICAST_TTL * * Indique le TTL d’émission non U_char
  IP_MULTICAST_LOOP * * Spécifie loopback non U_char
  IP_ADD_MEMBERSHIP non * Intégrer un groupe multicast non Struct ip_mreq
  IP_DROP_MEMBERSHIP non * Quitter un groupe multicast non Struct ip_mreq
Pour finir les options du protocole TCP :
Level optname get set description flag Type données
IPPROTO_TCP TCP_KEEPALIVE * * Temps d’attente avant de vérifier la connexion non int
  TCP_MAXRT * * Temps maxi de retransmission non int
  TCP_MAXSEG * * Taille maxi d’un segment TCP non int
  TCP_NODELAY * * Désactive l’algorithme Nagle (sais pas ce que c’est:) * int
  TCP_STDURG * * Interprétation d’un pointeur urgent * int
Ces options sont notifiées dans le fichier d’entête <sys/socket.h>.

 

B.3 les notions importantes des RFC
RFC 791 : THE INTERNET PROTOCOL
Cette RFC date de 1981, vous allez dire ‘c’est dépassé!’. Ben non, y’a tout ce qu’il nous faut pour être heureux là dedans, et notamment les headers dont on vous a déjà parlé dans le premier magazine qui vont nous servir pour travailler avec les raw sockets (dernier chapitre). Donc voici un petit rappel qui ne fera de mal à personne je pense, en tout cas pas à moi :-). Si vous trouvez ça redondant, sautez cette partie.
La vocation du protocole internet, comme vous le savez est de véhiculer des datagrammes à travers le réseau et éventuellement de les couper en petits morceaux quand ils sont trop gros.
Pour y parvenir, le protocole s’appuie sur 4 mécanismes : Type of Service, Time To Live, Options et Header Checksum.
Sans donner de remarques générales sur les 4 mécanismes cités, le plus parlant à mon avis est de décortiquer l’entête que vous voyez ci-dessous en expliquant champ par champ. A mon avis ça ne vous dispense pas de lire la RFC correspondante même si je trouve que certains passages ne sont pas à la portée de tout le monde qui plus est quand on le lit en anglais. Je sais que vous pouvez trouver des traductions sur le web, mais le problème c’est que les champs resteront en anglais parce que c’est comme ça, alors parfois il vaut mieux apprendre directement dans cette langue de barbares.
 0                          1                            2                            3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Version     |        IHL        |Type Of Service|   Total Length  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Identification          | Flags |          Fragment Offset          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Time to Live  |     Protocol     |         Header Checksum          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                   Source Address                                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                Destination Address                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Options                 |                      Padding              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
Les chiffres que vous voyez en haut ne sont pas des décorations, ils représentent des bits, et il faudra être précis avec ça sinon votre paquet ne sera pas lisible.
Version
Nous on va se contenter de parler d’Ipv4, donc la version, je vous le donne en mille : 4. En passant, il faut que vous sachiez écrire en binaire, c’est-à-dire en base 2. Comment on fait ça ? Ben on décompose un nombre suivant les puissances décroissantes de 2. Par exemple, pour écrire 4 : 4 = 1*2^2 + 0*2^1 + 0*2^0. ce qui, en binaire, donne : 100. Maintenant si vous voulez coder 7 : 7 = 1*2^2 + 1*2^1 + 1*2^0, donc en binaire 111. Pour que tout soit logique je précise que la décomposition en base 2 est unique (c’est pour ça qu’on appelle ça une base, pour ceux qui aiment bien les math J). Donc dans le champ version, il faudra rentrer : 0100
IHL
Internet Header Length représente la taille de votre header, mais attention, cette taille est exprimée en nombre de mots de 32 bits, ou 4 octets. La taille minimum à allouer est de 5, c’est-à-dire 5*32 bits, ou plus simplement 20 octets. Rappelez-vous juste de la taille minimum de 20 octets qui font un IHL de 5 (donc en binaire 0101).
Type of Service
ToS pour les intimes. Ce champ représente la qualité de service que vous souhaitez pour votre paquet. Pour une fois, on va rentrer (un peu ) dans le détail avec ce champ qui pose pas mal de problèmes à tout le monde.
Voilà la gueule du champ :
     0        1        2       3        4       5        6        7
+-----+-----+-----+-----+-----+-----+-----+-----+
|                              |         |         |         |         |         |
|   PRECEDENCE  |   D   |    T   |    R   |    0   |    0   |
|                              |         |         |         |         |         |
+-----+-----+-----+-----+-----+-----+-----+-----+
Précédence : c’est la priorité de votre paquet. Je vous fait un copier / coller de la RFC qui vous donne les différentes priorités. Personnellement je ne comprends pas tout, sauf que routine semble indiquer la priorité courante ; ) m’enfin si vous vous trouvez, faîtes moi signe :
111 Network Control (semble que ce soit pour les réseaux locaux)
110 Internetwork control (par opposition au précédent, visiblement utilisé par les passerelles uniquement))
101 CRITIC/ECP ( ?)
100 Flash Override ( ?)
011 Flash
010 Immediate
001 Priority
000 Routine
D : représente le ‘delay’, 0->normal, 1->bas, visiblement c’est la rapidité d’acheminement du paquet, mais je suis pas sûr.
T : représente ‘Troughoutput’, 0->normal, 1->haute ( ?)
R :représente ‘reliability’, ça c’est pour la sûreté de votre paquet, c’est-à-dire les chances qu’il se perde, 0->normal, 1->haute (donc 1 pour plus de sécurité).
D’après l’exemple de la RFC, j’ai l’impression qu’il faut faire un compromis, c’est-à-dire qu’on ne peut pas vraiment avoir un paquet utltra-prioritaire, rapide, et assuré d’arriver à bon port. C’est logique parce que sinon tout le monde mettrait le même ToS et dans ce cas on nous poserait même pas la question de ce qu’il faut mettre pour remplir les champs ; ). Donc mesdames messieurs, faites vos choix.
En conclusion, on peut donner un exemple de choix qui paraît viable : 00011000 pour un paquet rapide et peu sûr.
Total Length
Ici comme vous l’avez deviné, vous mettez la taille totale de votre paquet (header + data ). Quelques remarques là-dessus… D’abord, la taille est exprimée en octet. Ensuite, il faut avoir quelques règles en tête : la première c’est qu’un header ne doit pas excéder 60 octets, ensuite il faut savoir qu’à partir d’une certaine taille votre paquet a de fortes chances d’être fragmenté, et dans ce cas il faut remplir soigneusement les champs correspondant comme on va le voir après. Pour la partie data, si vous ne voulez pas de fragmentation, restez en-dessous de 512 octets, ou mieux 256 octets. (et puis souvenez-vous que le champ doit être rempli en binaire).
Identification
Lorsqu’un paquet est fragmenté, ce champ est le même pour tous les fragments et permet de différencier les fragments d’un autre paquet original. Pour que le réseau identifie les fragments provenant du même paquet original, il faut qu’ils aient la même source, la même destination, le même protocole et le même identifiant. L’identifiant, c’est cool, c’est vous qui choisissez librement.
Avant d’examiner les champs « flags » et « fragment offset », il faut expliquer comment se passe la fragmentation. Si vous décidez d’envoyer un paquet trop gros pour un certain réseau, le paquet sera coupé en morceaux pour qu’il puisse passer dans ce réseau, et celui à qui vous envoyez le paquet n’aura plus qu’à reconstituer le puzzle avec les pièces numérotées.
Voilà pour la vue d’ensemble. Maintenant, il faut que chaque morceau soit parfaitement défini pour le réassemblage. D’abord, comme on l’a dit, les morceaux auront même source, même destination, même identifiant et même protocole. Ensuite, le champ ‘flags’ dit simplement s’il reste des morceaux à suivre ou non. Enfin, le ‘fragment offset’ va donner la place du fragment dans le paquet original. En fait c’est assez simple.
Flags
      0         1         2
+------+------+------+
|           |    D    |    M    |
|     0    |    F     |    F     |
+------+------+------+
Le champ est donc codé sur 3 bits. Le premier, ça va nous reposer, on s’en occupe pas il reste toujours à 0. Le 2ème est le champ « don’t fragment », si on le met à 1, ça veut dire qu’on ne veut pas que le paquet soit fragmenté même si c’est au risque de le perdre, le mettre à 0 indique qu’on accepte une éventuelle fragmentation. Le 3ème champ s’appelle « more fragment ». S’il est à 1, ça signifie que d’autres morceaux doivent suivre, s’il est à 0 c’est que le paquet n’est pas fragmenté OU que ce morceau est le dernier fragment d’un paquet découpé.
Fragment offset
Il faut rentrer un peu plus dans le détail. Pour illustrer ça on reprend l’exemple du paquet coupé en deux de la RFC. Quand le paquet est fragmenté, la partie data du premier morceau sera mise à la taille maxi acceptée par le réseau, avec une taille qui doit être un multiple de 8 octets, ce nombre de blocs de 8 octets est appelé NFB (Number of Fragment Blocks).
Ensuite, le champ « fragment offset » du premier morceau est copié à partir du datagramme dont il est issu (par exemple, si le datagramme de départ n’a pas encore été découpé, il aura un fragment offset de 0). Ensuite, dans le champ fragment offset du 2ème morceau, on met le fragment offset du datagramme de départ + le NFB calculé précédemment, comme ça on a bien l’emplacement exact du fragment dans le paquet original et il sera possible de réassembler. Ca me paraît assez clair, maintenant si je me suis mal exprimé -> Forum du site.
Time to live
C’est la durée de vie maximale qu’on accorde au paquet sur le réseau histoire que s’il se perd il tourne pas en rond pendant des années. Cette durée est réactualisée à chaque fois que le paquet est traité par un routeur ou une passerelle. En fait, cette durée est exprimée en secondes et le champ est décrémenté à chaque fois de la durée de traitement du paquet dans le routeur. Comme la durée est en secondes, le TTL est décrémenté d’au moins une unité à chaque passage même si le routeur met moins de temps qu’une seconde pour le traiter. Le TTL maxi est de 255 secondes.
Protocol
Le protocole représente ici le protocole supérieur éventuel du paquet. Le protocole IP dont on parle est le plus bas que l’on puisse atteindre (avec les raw sockets), mais si on veut concevoir un paquet traité par le protocole TCP par exemple, ben c’est là qu’on l’indique. Pour la liste des protocoles, vous pouvez trouver ça dans la RFC 790 ou plus simplement dans /usr/include/netinet/in.h
En réalité, la doc Linux est plus actuelle car les choses ont évolué depuis les années 80 pour les protocoles… Tiens pendant que j’y pense, une bonne adresse pour les RFC : ftp://src.doc.ic.ac.uk/rfc
Quelques protocoles que vous pouvez retenir :
IPPROTO_ICMP = 1
IPPROTO_UDP = 17
IPPROTO_RAW = 255
Header Checksum
C’est un champ qui, comme TTL est modifié à chaque traitement dans une passerelle ou un routeur. Il permet de vérifier de points en points que les données ont été correctement transmises.
Source address & Destination address
Ce sont les addresses IP source et de destination après avoir été converties en binaire puis agencée en Network Byte Order.
Options
Ce serait complètement fou de vouloir vous exposer toutes les otpions, non seulement parce que je ne les maîtrise pas mais aussi parce qu’ils y en a beaucoup et que ça reviendrait à recopier une grosse partie de la RFC. Je préfère vous dire comment ça marche et illustrer ça avec un exemple.
D’abord il faut préciser que vous n’êtes probablement pas obligés à votre niveau de mettre des options dans votre paquet, donc vous pouvez sauter cette partie si vous voulez.
Maintenant, les curieux, ce paragraphe est fait pour vous !
Une option sera définie par :
Un type d’option
La taille de l’option
La partie « données » de l’option
On codera les 3 à la suite en binaire.
Un type d’option est codé sur un octet qui contient 3 champs :
1 bit copied flag
2 bits option class
5 bits option number
Le premier bit indique simplement si l’option doit être copiée dans tous les morceaux en cas de fragmentation (1) ou non (0).
Les 2 bits suivants précisent la classe de l’option, sur les 4 classes possibles, 2 sont utilisées : 0 pour la classe de contrôle, et 2 pour le débogage réseau.
Les 5 bits suivants indiquent le n° de l’option que vous voulez dans la classe choisie..
Une liste d’options doit impérativement être terminée par un octet nul (00000000). Si vous décidez de mettre plusieurs options dans votre header, il faut simplement séparer vos octets contenant les options par l’octet 00000001, qui représente une instruction nulle
On prend maintenant l’exemple des options de sécurité, elles ont le n° 2 dans la classe de contrôle et ont la longueur 11. On peut déjà écrire le type d’option :
10000010 / *1er bit : l’option sera copiée dans tous les fragments s’il y en a, 2 suivants : classe 0, 5 suivants : n°2 */
00001011 / *11 en binaire pour la longueur de l’option */
Ensuite il faut coller aux 2 octets précédents la partie « données » de l’option. Imaginons que l’on veuillent écrire l’option « top secret », cette option est codée sur 2 octets comme suit : 01101011 11000101.
D’autres champs peuvent suivre éventuellement, mais nous n’avons pas accès à ces données ou alors si vous vous savez où on peut trouver ça, je suis vraiment très intéressé. Avec les options, on peut aussi par exemple forcer le chemin à prendre par notre paquet (ça c’est une chose très intéressante, parce que quand vous spoofez, la différence entre votre paquet et celui pour lequel il se fait passer, c’est qu’ils ne prennent pas le même chemin, et il est donc théoriquement possible, si on se rend compte de votre attaque, de remonter jusqu’à vous en cherchant la route empruntée par le paquet malicieux, et là il n’y a même pas besoin de téléphoner à votre provider, bon ok, il faudrait être vraiment motivé pour vous retrouver, m’enfin de nos jours…), vous pouvez demander au paquet d’enregistrer le chemin par lequel il passe, et encore bien d’autres choses (RFC 791).
Padding
Ce champ sert seulement à garantir que le paquet se termine bien sur un multiple de 32 bits. De toute façon, on n’y met que 0.
Voilà, j’espère que vous avez bien saisi ce qui précède pour qu’on puisse s’attaquer aux protocoles plus élevés en toute sérénité :). Bon puisque vous êtes en forme ce soir, on va poursuivre avec le protocole TCP.
RFC 793 : THE TRANSMISSION CONTROL PROTOCOL
Le protocole TCP permet plusieurs opérations supplémentaires par rapport au protocole IP. Mais plutôt que de vous assommer de remarques générales on va disséquer un header TCP et expliquer les choses au fur et à mesure. Je vous rappelle que vous avez des informations là-dessus dans le premier mag.
Là il va falloir s’accrocher car c’est le protocole le plus complexe avec ICMP à cause de la quantité d’événements qui peuvent se produire. Je pars du principe que vous savez comment s’initialise une connexion TCP ( ‘the three way hand shake’) et comment on la ferme, c’est-à-dire l’existence des SYN, ACK et FIN, mais pas dans le détail, puisqu’on va le voir maintenant.
Sans plus attendre, celui que vous attendez tous, j’ai nommé le header TCP !!
  0                                  1                                   2                                    3
  0  1  2 3  4  5 6  7  8  9  0 1  2  3  4 5  6  7  8 9  0  1 2  3  4  5  6  7 8  9  0  1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Port                       |                Destination Port                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                               Sequence Number                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                         Acknowledgment Number                                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Data    |                      |U|A |P |R |S|F|                                                             |
|    Offset  |    Reserved    |R|C |S |S |Y|I |                        Window                       |
|               |                      |G|K|H |T|N|N|                                                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Checksum                      |                    Urgent Pointer                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Options                        |                        Padding                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                          data                                                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Source port et Destination port
Je pense que vous avez compris. Seule chose à ne pas oublier, il faudra convertir vos n° de port en Network Byte Order (on en a parlé plus haut) quand vous remplirez vos entêtes pour utiliser les raw sockets.
Sequence number & Acknowledgment number
J’ai regroupé les deux champs pour l’explication parce qu’ils marchent en étroite collaboration. Le n° de séquence est défini par la RFC comme le n° du premier octet de données de votre paquet par rapport au début de la transmission. C’est là qu’il faut se concentrer si vous ne connaissez pas ces trucs là.
Quand le premier SYN est envoyé pour initialiser une connexion (on verra plus bas comment activer le flag SYN), on attribue au paquet un n° de séquence initial (ISN) qui peut aller de 0 à 2^32 -1 (c’est-à-dire un entier codé sur 32 bits). ISN sert à identifier votre connexion, c’est pour ça qu’on donne un éventail de valeurs aussi large comme ça il y a peu de chances que deux paquets se pointent en demandant une connexion avec le même ISN.
Dans ce cas le n° de votre premier octet de données sera ISN+1.
En fait on peut préciser encore un peu plus les choses. En effet, l’initialisation de la connexion va définir un ISN émetteur (celui qui envoie le premier SYN) ou ISS, et un ISN récepteur (IRS) qui sera ‘appris’ par le récepteur après réception de l’ISS. Ceci explique la raison d’une ‘three way handshake’ qui est en fait la synchronisation des deux ISN.
Pour ce faire, la synchronisation nécessite un accusé de réception, ou ACK . Le champ selon la RFC contient de n° de séquence du prochain octet de données que le récepteur s’attend à recevoir. En fait, le premier SYN ne contient jamais de données puisque l’émetteur n’est pas sûr que sa connexion va être acceptée, donc dans la 2ème phase de la ‘three way hand shake’ (désolé mais c’est tellement explicite en anglais que je ne préfère pas faire une traduction pourrie du genre : le serrage de mains à 3 étapes J), le ACK renvoyé par le récepteur contient ISS+1.
Enfin, pour terminer la synchronisation, l’émetteur lui renvoie un ACK ISS+1 pour montrer qu’il a bien compris le n° de séquence du récepteur.
Ensuite, la connexion est établie. L’émetteur envoie alors une requête au récepteur, qui lui répond avec le ACK de la requête. Puis l’émetteur dit au récepteur qu’il a bien reçu sa réponse en renvoyant le ACK de la réponse.
Tous les paquets contiendront des ACK, enfin des n° de séquence si on considère l’émetteur et le récepteur ( ce qui sécurise malheureusement vachement la connexion ; )). Pour les actualiser à chaque paquet, il suffira d’incrémenter la valeur du précédent avec la taille des paquets qu’on a reçu entre temps et le tour est joué. Cela permet aussi à l’émetteur de savoir si son paquet a bien été reçu, parce que dans le cas contraire, il retransmet une copie du paquet au bout d’un certain timeout. De l’autre côté, si l’original du paquet a déjà été reçu, la copie est détruite, sinon il la reçoit puisque ça correspond bien à ce qu’il attend.
On peut aussi avoir des scénarios plus exotiques mais bon dans ce cas il faudrait recopier la RFC pour les traiter.
Data offset
Ce champ donne la taille de l’entête en nombre de blocs de 4 octets (32 bits pour les intimes). Souvenez-vous, c’est la même chose que ce qu’on a dit sur le header IP : si l’entête n’est pas un multiple de 32 bits, le padding la complète avec une chaîne de 0.
Les Flags
Bon il y’en a déjà deux qu’on connaît : SYN et ACK. Pour activer les flags, il suffit de mettre le bit à 1. Donc vous mettez 1 pour le bit SYN quand vous initialisez une connexion par exemple.
Pour le flag URG, il dit simplement de lire les données dans le champ pointeur de données urgentes, donc on en parlera un peu plus bas.
Le flag RST sert à réinitialiser une connexion quand un truc s’est mal passé, attention, vous ne pouvez pas utiliser les RST de façon intempestive (je dis ça parce que je suis sûr que vous y pensiez déjà J). Un reset n’est validé que si le paquet qui l’envoie contient un n° de séquence qui correspond à quelque chose, sinon la connexion est abandonnée avec le malotru.
La fonction PUSH est définie pour que l’émetteur soit rapidement assuré que son paquet a bien été reçu, donc quand l’option est activée TCP achemine le paquet le plus vite possible au destinataire (m’enfin ça a l’air trop beau pour être aussi simple, parce que sinon tout le monde mettrait des PUSH dans ses paquets, alors si vous avez des infos plus précises, je suis preneur).
Window
La RFC nous dit que c’est ‘le nombre d’octets à partir de la position marquée dans l’accusé de réception que le récepteur est capable de recevoir’. Plus précisément, c’est le nombre d’octets que le récepteur est capable de recevoir avant d’avoir reçu un accusé de réception de l’émetteur lui indiquant qu’il a bien compris que le récepteur a bien reçu les paquets, ET en même temps, ça donne une taille maxi de paquet au-delà de laquelle le paquet devra être fragmenté sans quoi les données hors fenêtre seront mises à la poubelle.
Checksum
Ce champ est rempli de la même façon que celui des paquets IP à l’aide d’une fonction qui traite les données de l’entête et vérifie comme ça son intégrité. On verra la syntaxe de tout ça avec les sockets raw.
Urgent pointer
Le champ n’est donc lu que si le flag URG est mis à 1. En fait le pointeur indique la position d’une donnée urgente dans le paquet en donnant son déplacement par rapport au n° de séquence du paquet. Il pointe sur l’octet qui suit la donnée urgente. C’est la même idée que les messages Out-Of-Band. En clair, ce que je viens de dire, c’est que si vous utilisez un message OOB avec une stream socket, arf, vaut mieux mettre un exemple :
On prend le cas d’un client qui s’adresse à un serveur echo, vous lui envoyez la chaîne « coucou » sans option, et ensuite la chaîne « aaa » avec l’option MSG_OOB, puis « bbb ».
Ben lui il va vous répondre : ‘b’ ‘coucou’ ‘aa’ ‘bb’, parce que c’est le premier octet qui suit la chaîne OOB qui est considérée comme donnée urgente.
Options
Ben là c’est tranquille, je peux me reposer, parce que c’est exactement la même chose que ce qu’on a dit pour les options des paquets IP :).
Padding aussi, on l’a déjà expliqué ; ).
Bon, les cailles, on a fait le plus dur , enfin presque ! Je vais juste donner l’entête UDP avec la rfc correspondante, mais c’est beaucoup plus simple que le protocole TCP. Et puis pour le protocole ICMP, vous avez un truc assez complet dans le premier mag. ( si y’a besoin de compléments on y reviendra)
LA RFC 768 : USER DATAGRAM PROTOCOL
La différence avec le protocole TCP, c’est que celui-là ne fonctionne pas en mode connecté (enfin normalement, parce que Stevens est vraiment capable de tout ; )).
 0          7 8       15 16      23 24     31
+--------+--------+--------+--------+
|         Source         |      Destination     |
|           Port           |           Port           |
+--------+--------+--------+--------+
|                            |                             |
|         Length         |       Checksum      |
+--------+--------+--------+--------+
|
|                      data octets ...
+---------------- ...
User Datagram Header Format
Pffff, ridicule l’entête après ce qu’on vient de se farcir ; ) En plus avec tout ce qu’on a vu avant, je me rends compte qu’il n’y même pas besoin d’explications. Notez juste que vous n’êtes pas obligé de remplir le port source et que comme d’habitude, il faut se souvenir que Length contient la taille totale du paquet : header + data. Pour Checksum, on va voir ça en même temps que les sockets raw. Vous pouvez aller voir les ports classiques assignés à UDP dans la RFC 762.
Enfin dernière chose pour clore la discussion sur les protocoles : lorsque vous voulez construire un paquet TCP par exemple, il suffit de coller l’entête TCP à l’entête IP, et c’est la même chose pour UDP et TCP.
 
C. La programmation des Sockets Raw
Faut-il vous avouer que le but de l’article était cette partie ? Je sais, elle arrive un peu tard, mais si vous connaissez bien ce qu’on a dit avant, notamment sur les protocoles, coder ces sockets ne vous paraîtra pas insurmontable. Elles sont juste un peu plus compliquées à coder que les autres parce qu’ils faut remplir les paquets qu’on envoie, m’enfin si on comprend bien comment les paquets sont remplis ça se fait !
Pour commencer, on va voir de quels outils on dispose dans notre système d’exploitation préféré, j’ai nommé Windows (mais non faites pas cette tête, je rigole).
Les Sockets Raw se créent comme les autres :
Sockfd = socket( PF_INET, SOCK_RAW,IPPROTO_xxx) ;
Pour les protocoles, pour pouvez jeter un coup d’oeil dans netinet/in.h, il donne la plupart des protocoles supportés par le noyau. Vous mettez la même notation que dans le fichier, ou vous mettez le n° correspondant. Cependant, il faut bien noter que si on attribue l’option IP_HDRINCL comme on va le faire un peu plus bas, on peut alors remplir totalement le header de notre paquet qui ne sera donc pas traité par le noyau. Si je vous dis ça, c’est parce que du coup, rien ne vous empêche de mettre un protocole dans votre header qui n’est a priori pas supporté par le noyau (cool non ?).
Comme notre but est effectivement de paramétrer totalement l’entête de notre paquet , il faut le dire au noyau. Vous vous souvenez comment marche setsockopt() ?
Parfait. C’est avec l’option IP_HRDINCL qu’on peut dire au noyau que nous serons seuls maîtres du contenu de l’entête :
Int un = 1 ;
Setsockopt( sockfd, IPPROTO_IP, IP_HDRINCL, &un, sizeof(un)) ;
Comme c’est noté dans le tableau des options l’option IP_HRDINCL est un flag, donc pour l’activer il vaut la mettre à 1, ce qui explique la syntaxe au-dessus.
Maintenant il faut construire le paquet qu’on veut envoyer. On va construire un paquet TCP pour illustrer (et puis aussi parce que c’est plus délicat que de coder un paquet UDP).
Pour voir les structures des headers gérées par votre noyau, #cat /usr/include/netinet/ip.h, #cat /usr/include/netinet/tcp.h puis pour les autres protocoles vous savez où c’est ; ).
Je pense que je ne prends pas grand risque à penser que votre ordi fonctionne en Little Endian, cependant, si votre OS est assez exotique, vous devez en connaître suffisamment pour aller voir dans netinet/in.h et changer les détails nécessaires.
Tout d’abord, la structure de l’entête IP :
Struct iphdr {
Unsigned int ihl :4 ; / * ça nous permet de préciser que ihl fait 4 bits et non pas 32 comme un unsigned short int normal * /
Unsigned int version :4 ; / * même remarque */
U_int8_t tos ;
/ * attention depuis le début de l’article certaines majuscules me sont imposées par word en début de ligne, alors de grâce n’en mettez pas dans vos déclaration J*/
u_int16_t tot_len ;
u_int16_t id; / * n° de séquence, cf entête IP */
u_int16_t frag_off ;
u_int8_t ttl ;
u_int8_t protocol ;
u_int16_t check ; / *checksum */
u_int32_t saddr ;
u_int32_t daddr ; / *c’est ici que vous mettrez des options si vous en voulez , d’ailleurs, dans ip.h vous trouvez pas mal de définitions de constantes pour les options*/
} ;
Maintenant, voilà la gueule de l’entête TCP (tcp.h) , toujours en Little Endian:
Struct tcphdr {
U_int16_t th_sport ;
U_int16_t th_dport;
Tcp_seq th_seq;
Tcp_seq th_ack;
U_int8_t th_x2:4; / *partie ‘reserved’ dans l’entête */
U_int8_t th_off:4; / *data offset dans l’entête, vous remarquez qu’on inverse l’ordre des 2 champs par rapport à l’entête, c’est parce qu’on est en Little Endian */
U_int8_t th_flags ; / *dans tcp.h vous trouverez les constantes des flags en hexadécimal * /
U_int16_t th_win ;
U_int16_t th_sum ; / * checksum * /
U_int16_t th_urp;
};
Avant de pousser plus en avant, je suis obligé de parler du champ checksum des entêtes IP et TCP, car vous avez dû remarquer que j’avais soigneusement évité le sujet depuis le début… Comme on l’a dit le checksum compilé à chaque passage dans un routeur vérifie l’intégrité du paquet. Oui mais comment ??? C’est la fonction in_cksum qui remplit ce champ, voici la source ( Stevens) :
Unsigned short in_cksum( unsigned short *addr, int len)
{
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
/ * Our algorithm is simple, using a 32 bit accumulator (sum), we add sequential 16 bit word to it, and at the end fold back all the carry bits from the top 16 bits into the lower 16 bits * /
while ( nleft > 1 )
{
      sum += *w++;
      nleft -= 2;
}
/ * mop up an odd byte, if necessary * /
if ( nleft == 1)
{
*(unsigned char *) (&answer) = *(unsigned char *) w; / *ici ça caste à mort :)* /
sum += answer;
}
/ *add back carry outs from top 16 bits to low 16 bits * /
sum = (sum >> 16) + (sum & 0xffff);/ * add hi 16 to low 16 * /
sum += (sum >> 16); / * add carry * /
answer = ~sum;
return (answer);
};
La première partie de l’algorithme additionne tout les mots de 16 bits (unsigned short int) en les mettant dans sum. Ensuite, dans la seconde partie, l’opérateur >> qui représente un décalage de bits vers la droite, sert à additionner les 16 dernier bits de sum avec les 16 premiers ( il faut savoir que 0xffff représente en hexadécimal 4 fois plus de bits car 1 hexa->4 bits, donc 0xffff fait bien 16 bits). & représente l’opérateur bit à bit tel que : 1 & 1 == 1, et 1 & 0 == 0 Donc en fait, sum & 0xffff met à 0 les 16 premiers bits de sum en partant de la droite. Ensuite on ajoute encore les 16 bits de droite de sum, puis on inverse tous les bits avec l’opérateur ~. Tout va bien ? Cool parce qu’on en a pas encore fini avec ce putain de checksum. Quand vous calculez le checksum d’un paquet avec un entête IP seul alors c’est pas compliqué : avec la structure ip de type iphdr, on fait
Ip->check = in_cksum( (unsigned short *)&ip, sizeof(struct iphdr));
Si vous avez 126 octets de data dans votre paquet, vous mettez:
Ip->check = in_cksum( (unsigned short *)&ip, sizeof(struct iphdr) + 126) ;
Bon là où ça part en couille un peu c’est quand on ajoute un entête tcp (ou udp c’est pareil). Parce que là le checksum de l’entête tcp couvre un ‘pseudo header’ qui a cette tronche :
+--------+--------+--------+--------+
|                  Source Address                |
+--------+--------+--------+--------+
|                Destination Address            |
+--------+--------+--------+--------+
|    zero    |  PTCL  |      TCP Length     |
+--------+--------+--------+--------+
Héhé, je vous avais prévenu… Cette entête de 12 octets est préfixée à l’entête TCP.
La ‘TCP length’ compte la longueur de l’entête TCP + les données du message - les 12 octets de cette pseudo-entête.
Pour coder cette entête en C, on utilise la structure pseudohdr ( j’ai trouvé ça dans l’article de NitrOgen de www.exile2k.org):
Struct pseudohdr {
Unsigned long saddr ;
Unsigned long daddr;
Unsigned char zero;
Unsigned char protocol;
Unsigned short length;
};
Pour l’initialiser il faut mettre les addresses source et distante de l’entête IP (en network byte order). Et comme on l’a dit length = sizeof( struct tcphdr) + taille des données. Il faut aussi rentrer le protocole dans PTCL.
Donc pour calculer le checksum de votre entête TCP (struct tcphdr tc), il vous faut faire , en supposant que vous ayez 128 octets de données :
Tc->th_sum = in_cksum( (unsigned short *)&pseudo, sizeof(struct tcphdr)+sizeof(struct pseudohdr) +128) ;
Bon j’espère que c’est clair parce que je trouve que c’est assez épineux de découvrir tout à coup que l’entête TCP à cette pseudo daube attachée qui apparaît comme un joker qui sort d’une boîte à surprise…
Une fois que votre paquet est créé, il vous suffit de l’envoyer. Comme on n’a pas utilisé connect() (sisi, même avec les raw sockets on peut l’utiliser, mais bon ici on donne une vue d’ensemble, si on rentrait dans tous les détails, faudrait écrire un bouquin entier, et puis ça servirait à rien parce qu’il serait moins bon que Stevens…) on utilisera sendto() , donc si votre paquet s’appelle paquet, et que vkus avez entré les coordonnées du distant dans la structure de type sockadd_in saddr :
Sendto( sockfd, paquet, ip->tot_len, 0, (struct sockaddr *)&saddr, sizeof( saddr));
Voilà, normalement pour pouvez programmer les raw sockets! Pour connaître la structure dans laquelle on stocke l’entête UDP, vous pouvez jeter un coup d’œil dans netinet/udp.h. Pour illustrer tout ça, on va construire une procédure complète qui envoie un paquet TCP SYN pour initialiser une connexion :
Copier/Coller------------------------------------------------------------------------
#include <tout ce qu'il faut>
int main (int argc, char **argv)
{
struct sockaddr_in servaddr ;
int sockfd;
struct iphdr *head;
struct tcphdr *neck;
char packet[256];
Struct pseudohdr {
Unsigned long saddr ;
Unsigned long daddr;
Unsigned char zero;
Unsigned char protocol;
Unsigned short length;
}pseudohead;
if ( argc != 3)
{
printf(“\nUsage: %s \n”, argv[0]);
exit (1) ;
}
bzero( &servaddr, sizeof(servaddr));
servaddr.sin_family = PF_INET;
servaddr.sin_port = htons( atoi( argv[2])) ;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
if ( ( sockfd = socket( PF_INET, SOCK_RAW, IPPROTO_TCP) < 0)
{
fprintf(stderr, “%s\n”, strerror(errno));
exit (1);
};
head = (struct iphdr *)packet;
neck = (struct tcphdr *) packet + sizeof( struct iphdr);
/ * beaucoup le savent je pense, mais pour les autres, quand on a un tableau de type tab[], alors tab est un pointeur qui pointe sur tab[0] , donc dans la ligne du dessus on incrémente le pointeur pour qu’il pointe après head pour rentrer la structure qui contient l’entête TCP */
bzero( &packet, sizeof(packet)) ;
/ *maintenant il faut remplir les entêtes J * /
head->ihl = 5 ;
head->version = 4 ;
head->tos = 0;
head->tot_len = 256;
head->id = htons( 765454); / *si vous avez bien compris, vous savez que cette valeur est unique (!) * /
head->frag_off = 0 ;
head->ttl = 255;
head->protocol = IPPROTO_TCP;
head->check = 0 / * pour l’instant */
head->saddr = inet_addr(“127.0.0.1”);
head->daddr = servaddr.sin_addr.s_addr;
/ * on n’a pas d’options, sinon on les mettrait là * /
/ * ATTENTION NOTEZ QUE LE CHAMP FLAGS EST AUSSI REMPLI PAR FRAG_OFF QUI S’ETALE SUR 16 BITS !! */
/ *on remplit l’entête tcp */
neck->th_sport = htons( 6666) ;
neck->th_dport = servaddr.sin_port;
neck->th_seq = htons( 66666);
neck->th_ack = htons(0);
neck->th_off = 0;
neck->th_flags = 0x02; / *flag SYN en hexa * /
neck->th_win= 512;
neck->th_sum = 0: / *pour l’instant */
neck->th_urp = 0;
/ * NOTEZ QU’ON A PAS BESOIN D’INITIALISER LES VARIABLES A ZERO PUISQU’ON L’A FAIT POUR PAQUET ENTIER MAIS COMME CA VOUS VOYEZ BIEN TOUT LES CHAMPS * /
/ *On a fait le gros du boulot, il reste les checksum * /
bzero(&pseudohead, sizeof(struct pseudohdr)) ;
pseudohead.saddr = servaddr.sin_addr.s_addr ;
pseudohead.daddr = inet_addr(“127.0.0.1”);
pseudohead.protocol = IPPROTO_TCP;
pseudohead.length = sizeof(packet) - sizeof(struct iphdr) -12;
neck->th_sum = in_cksum((unsigned short*)&pseudohead, pseudohead.length);
head->check = in_cksum((unsigned short *)packet, sizeof(struct iphdr));
if(sendto(sockfd, packet, head->tot_len, 0, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
fprintf(stderr, “\n%s\n”, strerror(errno));
exit(1);
}
puts(“\nPacket SYN sent”);
return 0;
}
-------------------------------------------------------------------------------------
Voilà, suite au prochain episode…. Je rappelle que si vous avez trouvé ça pourri ou si vous avez des questions ->forum ! A+
Par Nico