Je tiens à dire du mal de DHCP, du serveur DHCP fourni par ISC et du noyau Linux. Je vais certainement critiquer à tort certaines choses, mais je pense qu'il y aura aussi des petits détails vrais.

Tout d'abord, DHCP, c'est super pratique, mais alors quand il s'agit d'intégrer cela avec autre chose, c'est horrible. Bon, ce n'est pas la faute du protocole en tant que tel, car il faut dire que les bonnes implémentations ne courent pas les rues. Mais le fait que l'implémentation la plus répandue (celle d'ISC) ne soit pas parfaite ne donne pas envie d'aimer DHCP.

Qu'ai-je contre l'implémentation de DHCP faite par ISC ? Et bien c'est, à mes yeux, du code antique. Je ne sais pas trop par où commencer...

  • Le configure n'est pas standard. A la rigueur, ce n'est pas très grave, mais quand on fait ./configure --help (ce qui semble légitime d'essayer, non ?), on n'obtient pas une aide, mais il se passe quelque chose d'intéressant : le code est configuré pour être compilé sur un système de type --help.
  • Une fois qu'on comprend qu'il faut faire ./configure sans argument et qu'il ne faut vraiment pas chercher à comprendre, on réalise que cette ligne de commande crée un répértoire du type work.system-type contenant des liens symboliques vers tous les fichiers à compiler. Pas très standard, encore une fois. Mais toujours pas très grave.
  • Une fois que j'ai mon work.linux-2.2 et que je fais un make à l'intérieur, je constate que tous les fichiers, même ceux totalement inutiles car ne concernant que Solaris, ou *BSD ou Ultrix sont compilés ! En fait, les fonctions sont écartés avec des ifdef et on compile un fichier qui, au final, ne contient pas de code. Très utile...
  • Le code concernant Linux n'est pas totalement récent, notamment le code pour lister les interfaces : des ioctl sont utilisés alors qu'il faut utiliser une socket netlink. Le problème avec les ioctl est qu'ils ne fournissent pas les informations sur toutes les interfaces (en particulier les interfaces qui ne sont pas montées). Donc il faut faire un gros hack. Pas très grave, mais bon, ça peut s'améliorer.
  • Là, c'est un point qui me fait hérisser les cheveux. Les interfaces sont identifiées de manière unique dans le système par un index. Donc lorsqu'on constate qu'il existe une structure interface_info qui contient des informations pour chaque interface et que cette structure contient un champ index, on s'attend à ce que cet index soit celui qui identifie l'interface dans le système. Et bien non, ce serait trop simple. C'est un index, comme ça, fait à la main.

Mais ce n'est pas tout. Le pire, c'est ce qu'on voit dans le README :

We have noticed that on some systems where we are using a packet filter, if you set up a firewall that blocks UDP port 67 and 68 entirely, packets sent through the packet filter will not be blocked. However, unicast packets will be blocked.

C'est notamment le cas sous Linux avec netfilter. Le serveur DHCP envoie les paquets au travers du pare-feu. C'est possible car le serveur utilise une socket PF_PACKET, que ne supporte pas netfilter. Mais il n'y a aucune raison d'utiliser une telle socket. Une socket raw est largement suffisante. Le code est même là, presque prêt à être compilé. Mais apparemment il ne fonctionnait pas avec certaines implémentation des sockets raw il y a trois ans, donc l'idée n'est plus à l'ordre du jour. Au lieu d'utiliser les sockets raw, ils font tout à la main (ils construisent tous les en-têtes ethernet !). Donc ils ont du code beaucoup plus compliqué. Il faut avouer que cela leur permet de faire des petites choses en plus (le serveur peut envoyer une réponse en unicast Ethernet alors que le client se trouve sur une machine qui n'a pas encore d'adresse IP).

Vous allez me dire que je dois faire un patch. C'est au programme. En fait, j'en ai déjà un, mais il est loin d'être propre et complet...

Et le noyau Linux dans tout ça ? Pourquoi ai-je un problème avec ? Je passe sur le fait que netfilter ne voit pas les paquets injectés avec une socekt PF_PACKET : j'ai vu un patch datant de deux ans sur la lkml qui n'a visiblement pas été accepté. Non, ce qui m'a fortement agacé, c'est une configuration par défaut des interfaces qui est mauvaise (du moins, à mon avis) et cause des problèmes. Il s'agit de /proc/sys/net/ipv4/conf/*/rp_filter : cette option active le Reverse Path Filtering (traduction approximative : filtrage par chemin inverse), qui consiste, en gros, à rejeter des paquet reçus sur une interface provenant d'une source A si le noyau n'aurait pas émis sur cette interface un paquet à destination de A (en français : ). Cela permet d'éviter le spoofing, mais cela casse pas mal de choses, et en particulier DHCP qui n'utilise plus les sockets PF_PACKET, puisque l'interface non configurée est sans adresse IP et n'est donc pas censée recevoir de trafic, et ne peut donc pas recevoir les réponses DHCP contenant l'adresse IP allouée. Et le pire, c'est qu'on n'est pas du tout averti par défaut que ces paquets sont rejetés (pas de log, et le compteur des paquets IP entrants rejetés n'est pas incrémentés). Pour découvrir pourquoi je perdais ces paquets, il m'a fallu recourir à des printk dans le noyau...

Pourquoi toute cette histoire ? Parce que j'ai tenté d'intégrer DHCP avec un autre logiciel et que j'ai perdu plusieurs jours à essayer de comprendre pourquoi DHCP ne fonctionnait pas correctement, alors qu'il aurait tout simplement dû fonctionner directement.