Passer à l'échelle avec Dnsmasq (pour le DHCP)

Dnsmasq est un serveur DHCP et DNS (et même TFTP) qu'on ne présente probablement plus. Léger, facile à configurer (il lit par défaut les configurations habituelles du système, comme /etc/resolv.conf, /etc/hosts, /etc/ethers, etc. Et en plus utilise inotify pour les recharger si nécessaire) et combinant deux services essentiels en un seul programme pour avoir un réseau fonctionnel.

Du côté du probablement moins connu, Dnsmasq contient également aussi beaucoup d'options, qui permettent de l'adapter à beaucoup de situations. Un exemple est sa capacité à ajouter les adresses IP des noms qu'il résout en DNS dans des ipset. Loin d'être anodine, cette fonctionnalité permet par exemple de mettre en place des autorisations/interdictions en fonction de noms de domaines (ce qui a forcément des effets de bords, notamment du fait des CDN de plus en plus nombreux, mais c'est un autre sujet).

Pour ces raisons, nous utilisons Dnsmasq sur beaucoup de nos équipements. Reste une question : ce petit démon, léger, embarqué habituellement pour sa simplicité, peut-il passer à l'échelle ? Son site officiel le définit comme « Dnsmasq provides network infrastructure for small networks ». Quand on lit le mot small, un premier réflexe pourrait être de basculer vers des solutions comme isc-dhcp-server quand le réseau grossit. Mais si l'on souhaite avoir un parc homogène, peut-on imaginer garder Dnsmasq pour des réseaux plus volumineux ? (spoiler : la réponse est oui, voici un retour d'expérience).

Le premier palier, qui n'existe pas vraiment

La première configuration est relativement évidente. Par défaut, la valeur pour dhcp-lease-max est configurée à 1000. C'est rapidement insuffisant, et l'augmenter va de soit. À noter que la page de manuel parle d'un risque sur la consommation mémoire, mais il est à relativiser. Un dnsmasq avec plus de 10 000 baux en mémoire consomme moins de 5Mo de RAM. On peut l'augmenter sans crainte sur la plupart des équipements.

Second paramètre à prendre en compte, l'option dhcp-ignore-names. Par défaut Dnsmasq va insérer les noms annoncés par les clients en DHCP dans ses entrées DNS. C'est sympa/pratique sur un petit réseau local personnel. C'est par contre à bannir rapidement sur un réseau professionnel. Si malgré les risques de sécurité quelqu'un souhaite cependant la conserver sur un grand réseau, on voit rapidement apparaître des problèmes de performances dès quelques milliers de baux en mémoire. Notamment car Dnsmasq va vérifier à chaque requête DHCP que ce nom n'existe pas déjà sans sa liste, ce qui est très lent.

Avec ces deux paramètres, vous pouvez déjà avoir un réseau de grande taille (plusieurs milliers de baux) sans détecter la moindre consommation CPU de Dnsmasq.

Le bug qui coûte une seconde par bail

Le palier suivant a été détecté chez nous sur une configuration qui dépassait les 15 000 baux alloués dans la journée. Sur ce site spécifique, ces baux étaient libérés durant la nuit (durée de quelques heures, et aucune activité réseau après minuit. Atypique pour nous, qui avons plutôt des utilisateurs nocturnes et de longue durée). Nous avons remarqué une consommation CPU anormale, Dnsmasq consommant bien plus de CPU la nuit (alors qu'il ne faisait que supprimer ses baux) qu'en journée.

Après analyse, il s'agit d'un bug de Dnsmasq qui déclenche un nettoyage de ses sessions à l'heure exacte d'expiration du prochain bail, mais ne le nettoie effectivement qu'une seconde après cette expiration. Résultat, durant une seconde Dnsmasq boucle sur ce nettoyage qu'il relance en permanence sans accomplir la moindre action. On perd ainsi une seconde de temps processeur pour chaque bail supprimé.

Cet effet est complètement invisible sur un petit réseau. Dans notre cas, en libérant 15 000 baux en quelques heures, notre processeur était occupé pendant plus de quatre heures pour rien. Pour la petite histoire, on voyait même apparaître la fonction difftime avec perf tant Dnsmasq passait son temps à vérifier si un bail de sa liste était expiré et la date de prochaine expiration.

Nous avons donc proposé un patch, rapidement accepté (et déployé immédiatement sur nos équipements), qui permet de revenir sur une situation beaucoup plus calme (on ne voit plus Dnsmasq consommer du CPU durant la nuit, ce qui est tout de même plus logique).

Encore plus loin

Arrivé là, on a déjà un Dnsmasq qui consomme de façon très raisonnable des ressources pour un réseau voyant passer plus de 20 000 équipements différents dans la journée. Cela devrait suffire pour la plupart des réseaux.

Pour aller plus loin, le prochain problème est l'écriture du fichier dnsmasq.leases par Dnsmasq à chaque changement d'état. Sur ce type de réseau, on se retrouve à ré-écrire un fichier de milliers de lignes à chaque seconde ou presque (voir plus souvent). De plus, comme effet de bord, Dnsmasq ré-écrit le fichier plutôt que d'effectuer une opération atomique avec un fichier temporaire, provoquant des effets de bords dans d'éventuels programmes externes tentant de le lire (le fichier est souvent invalide, partiellement écrit). Cette ré-écriture est suffisamment intense pour qu'on voit apparaître la fonction vsnprintf avec perf, ce qui n'est pas très bon signe.

Heureusement Dnsmasq permet une nouvelle fois par ses nombreuses options d'éviter ça. Avec l'option dhcp-script, un script est appelé à chaque changement d'état DHCP (ajout, suppression, modification). On peut donc maintenir une base des baux nous même, et permet déjà à des outils de supervision d'éviter les problèmes de ré-écriture en permanence du fichier dnsmasq.leases. Cependant, on ne diminue pas encore la consommation de ressource.

La solution arrive avec leasefile-ro, qui permet cette fois de s'affranchir entièrement de ce fichier. L'astuce à ajouter est de permettre à Dnsmasq de retrouver les baux DHCP actifs lors d'un redémarrage. C'est assez simple, dans ce cas Dnsmasq appelle le script habituel avec en paramètre init. Il suffit de lui afficher sur stdin les baux actuels dans le même format que son fichier habituel et tout roule. C'est assez simple à faire si l'étape précédente de maintien d'une base externe fonctionne déjà.

À noter que Dnsmasq permet d'utiliser des scripts en lua plutôt que des scripts externes (et qu'on pourrait donc s'attendre à des gains en ressource), mais qu'à ce jour les scripts lua ne permettent pas de charger les baux au démarrage avec init. Ils ne sont donc pas utilisables pour ce cas d'usage.

Prochain palier ?

En conclusion, Wifirst utilise donc Dnsmasq sur des réseaux qui ne voient que quelques équipements dans la journée à des réseaux de bien plus grande envergure. Et ça se passe aujourd'hui très bien, la consommation de ressource de Dnsmasq étant négligeable avec ces configurations par rapport aux autres fonctionnalités.

Nous avions commencé à stocker notre propre base de baux DHCP pour des raisons de supervision (une autre solution serait d'améliorer Dnsmasq pour faire des changements atomique sur le fichier). L'utiliser pour améliorer les performances est un petit bonus, pas indispensable d'après notre expérience sur des réseaux ayant jusqu'à 20 000 baux alloués et libérés dans la journée.

Et nous attendons donc avec impatience de voir ce que pourrait être le prochain point de blocage. Un suspect probable est à regarder du côté des appels à dhcp-script, mais nous n'imaginons pas pour le moment voir de réseaux d'une taille suffisante pour que ce soit détectable.