OpenBSD PF : ‘match’ et priorité des ACK
Du fait du fonctionnement du protocole TCP, il arrive très souvent que lorsque l’on télécharge et reçoit simultanément plusieurs fichiers, la vitesse soit fortement réduite, bien en deçà des capacités de notre connexion internet. Ce comportement est très amplifié par les connexions ADSL, par nature asymétriques.
Je propose dans cet article une manière efficace et rapide de corriger cela sur une passerelle OpenBSD à l’aide de quelques règles PF en utilisant une première approche avec le moteur de priorisation ALTQ et la nouvelle souplesse des règles match depuis la version 4.6, puis en utilisant le nouveau moteur de priorisation intégré disponible à partir de la version 5.1.
1. Détail du problème
Lorsque l’on télécharge un fichier en utilisant le protocole TCP, les données que nous recevons sont contenues dans des paquets TCP ayant des données « utiles ». Selon le fonctionnement du protocole TCP, nous devons acquitter au fur et à mesure les données reçues, en envoyant au serveur des paquets TCP vides, mais acquittant de la réception des données (ces paquets contiennent le drapeau ACK). Lorsque nous envoyons un fichier sur internet, c’est le mécanisme inverse qui se produit : la machine recevant notre fichier nous envoie des paquets d’acquittement. Pour plus de détails, lire la RFC 793.
Si à un moment donné, les paquets d’acquittement sont ralentis ou perdus, alors le trafic est ralenti puisque la machine envoyant les données, doit attendre voir ré-émettre les données : car les paquets auront été acquittés tardivement ou pas du tout. Avec les connexions ADSL, fortement asymétriques, que nous avons aujourd’hui, on voit aisément le problème qui peut survenir : si nous lançons sur notre connexion internet, plusieurs envois/réceptions de fichiers simultanés, il est facile de saturer notre débit d’émission. Ce qui a pour effet de ralentir l’émission des paquets d’acquittement pour les fichiers que l’on télécharge, et donc de ralentir la vitesse de téléchargement de nos fichiers.
2. Solution A
Le filtre de paquets PF d’OpenBSD possède depuis longtemps un moteur très souple, ALTQ, permettant de prioriser les flux suivant différents algorithmes et paramètres. Sa configuration est détaillée dans la page de manuel pf.conf(5). Nous ne détaillerons pas sa syntaxe, juste les quelques règles qui nous intéressent. Voyons tout d’abord comment créer une queue permettant de prioriser les paquets. Ajoutons cette section au fichier /etc/pf.conf :
ext_if = vr3
altq on $ext_if priq bandwidth 920Kb queue { q_pri, q_def }
queue q_pri priority 7
queue q_def priority 1 priq(default)
Ici nous avons créé une macro ext_if, nommant notre interface externe vr3 sur laquelle nous allons prioriser nos paquets. Puis, nous créons une queue mère sur l’interface $ext_if en utilisant l’algorithme priq (simple algorithme de priorité). Cette queue mère se voit affecter 820Kb et deux queues filles : q_pri ayant une priorité élevée de 7 et q_def la queue par défaut ayant une priorité plus basse de 1.
L’algorithme priq est le plus simple présent dans ALTQ : on affecte une priorité différente aux sous-queues (de 0 à 15), les paquets de la queue ayant la priorité la plus élevée sont traités en premier.
Dans la déclaration de la queue mère, j’ai réservé 920Kb de bande passante. Ma connexion internet synchronise à environ 6Mb en réception et 1Mb en émission. Avec l’encapsulation IPoA chez Free, j’ai un débit effectif d’environ 920Kb. Je vous conseille de faire quelques essais : si vous ne réservez pas assez de bande passante, la priorisation sera inefficace, et si vous réservez trop de débit, vous allez brider inutilement votre connexion.
Une fois les queues déclarées, passons maintenant à leur affectation dans nos règles de filtrage. J’écrivais précédemment que depuis OpenBSD 4.6, une nouvelle façon de filtrer est apparue avec le mot clé match. En effet, depuis cette version, de profonds changements structurels ont été apportés à PF, résultant en une bien meilleure souplesse d’écriture. Le mot clé ‘match’ permet ainsi de « matcher » certains flux pour modifier leur filtrage à la volée, sans arrêter leur évaluation dans le fichier de règles : c’est à dire sans modifier l’action finale block ou pass.
Alors que précédemment, l’affectation des queues ALTQ aux flux se faisait à chaque règle de filtrage, il est maintenant possible d’affecter nos queues par une simple règle match au début des règles de filtrage :
match on $ext_if inet proto tcp queue (q_def, q_pri)
Ainsi, toutes les connexions TCP sur l’interface externe $ext_if se verront affecter nos deux queues : q_def et q_pri. L’évaluation des règles continuera ensuite normalement.
Notons que l’ordre dans lequel nous avons mis les deux queues est indispensable. La première queue précisée dans la règle match est la queue par défaut : q_def. Si nous précisons une deuxième queue (c’est ce que nous avons fait), q_pri, alors PF lui affecte automatiquement les paquets ayant l’option IP ToS positionnée à « nodelay » ainsi que les paquets TCP sans charge utile ayant le drapeau ACK. C’est exactement le comportement que nous recherchons : nos acquittements TCP seront ainsi priorisés par rapport aux autres paquets et nous diminuerons les risques de ralentissement.
3. Solution B
La solution précédente relativement simple, peut être encore beaucoup plus simplifiée, depuis OpenBSD 5.1. En effet, de gros travaux sont en cours pour intégrer totalement à PF les fonctionnalités de ALTQ. Aujourd’hui, ALTQ peut être très pénalisant en terme de performance et sa complexité rend difficile son évolution.
Ainsi, le mot-clé prio est apparu, permettant à chaque règle d’affecter une priorité au trafic visé. Ce nouveau système de priorisation peut être combiné avec les règles match. Ainsi, les quelques lignes précédentes avec ALTQ peuvent être simplifiées en :
match on $ext_if inet proto tcp prio (2, 5)
Ici, plus besoin de déclaration de queues et d’algorithme. Nous affectons une priorité 2 au trafic normal et une priorité 5 aux paquets ayant un champ IP TOS nodelay, ou les paquets TCP ACK sans donnée utile.
A noter qu’en utilisant ce nouveau mécanisme de priorisation, nous avons à notre disponibilité 8 queues (priorité de 0 à 7. De plus, certains trafics sont automatiquement priorisés :
- Les priorités des VLAN sont héritées,
- Le trafic CARP est priorisé.
Attention : la syntaxe est sujette à évolution, puisque l’intégration des autres parties de ALTQ est en cours.
4. Illustration
Voici maintenant en pratique le résultat. Le premier graphe correspond à notre politique de filtrage sans ALTQ, le second avec ALTQ.
Dans ce premier graphe, les trois premières minutes consistent à télécharger quelques fichiers. Ensuite, je lance une émission de fichier simultanée aux téléchargements durant les quatre minutes suivantes. On peut voir que non seulement la réception de mes fichiers (en vert) est perturbée par rapport à la première partie du graphe, mais également l’émission de mon fichier qui n’utilise pas les 1Mb de bande passante disponible.
Dans ce second graphe, les deux premières minutes correspondent à plusieurs téléchargements de fichiers. Puis, je lance une émission de fichier durant quatre minutes. On peut voir que ALTQ fait correctement son travail : la réception de mes fichiers n’est absolument pas perturbée par l’émission de mon autre fichier qui se fait en utilisant au maximum les 1Mb de capacité d’émission de mon lien internet.