15.10. Exemple d'une solution de traduction d'adresse avec de la QoS

Je m'appelle Pedro Larroy

. Je décris ici une configuration dans le cas où de nombreux utilisateurs seraient connectés à Internet à travers un routeur Linux qui possède une adresse publique et qui réalise de la traduction d'adresse (NAT). J'utilise cette configuration QoS pour fournir l'accès à 198 utilisateurs dans une cité universitaire dans laquelle je vis et où j'administre le réseau. Les utilisateurs sont de gros consommateurs de programmes "peer to peer" et un contrôle de trafic correct est nécessaire. J'espère que ceci servira d'exemples pratiques à tous les lecteurs intéressés par le lartc.

Dans un premier temps, la configuration sera réalisée pas à pas et, à la fin, j'expliquerai comment rendre ce processus automatique au démarrage. Le réseau utilisé pour cet exemple est un réseau local connecté à Internet à travers un routeur Linux ayant une adresse publique. L'ajout d'un ensemble de règles iptables permettrait facilement l'extension à plusieurs adresses IP publiques. Les éléments suivants sont nécessaires :

Linux 2.4.18 ou une version du noyau supérieure installée

Si vous utilisez le noyau 2.4.18, vous devrez appliquer la mise à jour HTB.

iproute

Soyez également sûr que le binaire "tc" est compatible avec HTB. Un binaire pré compilé est distribué avec HTB.

iptables

15.10.1. Commençons l'optimisation de cette rare bande passante

Tout d'abord, nous allons configurer des gestionnaires de mise en file d'attente dans lesquels nous allons classifier le trafic. Nous créons un gestionnaire htb composé de 6 classes avec des priorités croissantes. Nous avons alors des classes qui obtiendront le débit alloué et qui pourront, de plus, utiliser la bande passante dont les autres classes n'ont pas besoin. Rappelons que les classes de plus hautes priorités (correspondant aux nombres prio les plus faibles) obtiendront en premier l'excès de bande passante. Notre liaison ADSL à un flux descendant de 2Mbits/s et un flux montant de 300 kbits/s. J'utilise un débit de seuil (ceil rate) de 240 kbits/s car, au-delà de cette limite, les problèmes de latence commence à prendre de l'ampleur. Ceci est dû au remplissage d'un tampon placé quelque part entre nous et les hôtes distants.

Ajuster la variable CEIL à 75% de votre bande passante montante maximum et ajuster le nom de l'interface (eth0 dans la suite) à celle qui a l'adresse publique Internet. Exécutez ce qui suit dans un shell root :

CEIL=240
tc qdisc add dev eth0 root handle 1: htb default 15
tc class add dev eth0 parent 1: classid 1:1 htb rate ${CEIL}kbit ceil ${CEIL}kbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 80kbit ceil 80kbit prio 0
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 80kbit ceil ${CEIL}kbit prio 1
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 20kbit ceil ${CEIL}kbit prio 2
tc class add dev eth0 parent 1:1 classid 1:13 htb rate 20kbit ceil ${CEIL}kbit prio 2
tc class add dev eth0 parent 1:1 classid 1:14 htb rate 10kbit ceil ${CEIL}kbit prio 3
tc class add dev eth0 parent 1:1 classid 1:15 htb rate 30kbit ceil ${CEIL}kbit prio 3
tc qdisc add dev eth0 parent 1:12 handle 120: sfq perturb 10
tc qdisc add dev eth0 parent 1:13 handle 130: sfq perturb 10
tc qdisc add dev eth0 parent 1:14 handle 140: sfq perturb 10
tc qdisc add dev eth0 parent 1:15 handle 150: sfq perturb 10

Nous avons juste créé une arborescence htb avec un seul niveau de profondeur. Quelque chose comme ceci :

+-----------+
| racine 1: |
+-----------+
      |
+---------------------------------------+
| classe 1:1                            |
+---------------------------------------+
  |      |      |      |      |      |
+----+ +----+ +----+ +----+ +----+ +----+
|1:10| |1:11| |1:12| |1:13| |1:14| |1:15|
+----+ +----+ +----+ +----+ +----+ +----+
classid 1:10 htb rate 80kbit ceil 80kbit prio 0

Ceci est la classe de priorité la plus élevée. Les paquets de cette classe auront le plus faible délai et obtiendront en premier l'excès de bande passante. C'est donc une bonne idée de limiter le débit de seuil de cette classe. Nous enverrons dans cette classe les paquets qui ont un avantage à avoir un faible délai, tel que le trafic interactif : ssh, telnet, dns, quake3, irc, et les paquets avec le bit SYN activé.

classid 1:11 htb rate 80kbit ceil ${CEIL}kbit prio 1

Nous avons ici la première classe dans laquelle nous commençons à mettre du trafic de masse. Dans mon exemple, j'ai le trafic provenant de mon serveur web local et les requêtes pour les pages web : respectivement le port source 80 et le port destination 80.

classid 1:12 htb rate 20kbit ceil ${CEIL}kbit prio 2

Dans cette classe, je mettrai le trafic configuré avec le champ TOS "Débit Maximum" activé, ainsi que le reste du trafic provenant des processus locaux de mon routeur vers Internet. Les classes suivantes ne recevront donc que du trafic routé par cette machine.

classid 1:13 htb rate 20kbit ceil ${CEIL}kbit prio 2

Cette classe est pour le trafic des autres machines «NATées» (NdT : bénéficiant du service de traduction d'adresse) qui ont besoin d'une priorité plus grande dans leur trafic de masse.

classid 1:14 htb rate 10kbit ceil ${CEIL}kbit prio 3

Le trafic mail (SMTP, pop3,...) et les paquets configurés avec le champ TOS "Coût Minimum" seront envoyés dans cette classe.

classid 1:15 htb rate 30kbit ceil ${CEIL}kbit prio 3

Finalement, nous avons ici le trafic de masse des machines "NATées" se trouvant derrière le routeur. Les paquets liés à kazaa, edonkey et autres iront ici pour ne pas interférer avec les autres services.

15.10.2. Classification des paquets

Nous avons configuré le gestionnaire de mise en file d'attente, mais aucune classification de paquets n'a encore été faite. Pour l'instant, tous les paquets sortants passent par la classe 1:15 (car, nous avons utilisé : tc qdisc add dev eth0 root handle 1: htb default 15). Nous devons donc maintenant indiquer où doivent aller les paquets. Ceci est la partie la plus importante.

Nous allons maintenant configurer les filtres de tel sorte que nous puissions classifier les paquets avec iptables. Je préfère vraiment le faire avec iptables car celui-ci est très souple et que nous avons un compteur de paquets pour chaque règle. De plus, avec la cible RETURN, les paquets n'ont pas besoin de traverser toutes les règles. Nous exécutons les commandes suivantes :

tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 1 fw classid 1:10
tc filter add dev eth0 parent 1:0 protocol ip prio 2 handle 2 fw classid 1:11
tc filter add dev eth0 parent 1:0 protocol ip prio 3 handle 3 fw classid 1:12
tc filter add dev eth0 parent 1:0 protocol ip prio 4 handle 4 fw classid 1:13
tc filter add dev eth0 parent 1:0 protocol ip prio 5 handle 5 fw classid 1:14
tc filter add dev eth0 parent 1:0 protocol ip prio 6 handle 6 fw classid 1:15

Nous indiquons simplement au noyau que les paquets qui ont une valeur FWMARK spécifique (handle x fw) vont dans la classe spécifiée (classid x:x). Voyons maintenant comment marquer les paquets avec iptables.

Tout d'abord, nous devons comprendre comment les paquets traversent les filtres avec iptables :

        +------------+                +---------+               +-------------+
Paquets-| PREROUTING |--- Décision----| FORWARD |-------+-------| POSTROUTING |- Paquets
entrant +------------+   de routage   +-­-------+       |       +-------------+  sortants
                             |                          |
                        +-------+                    +--------+
                        | INPUT |-- Processus locaux-| OUTPUT |
                        +-------+                    +--------+

Je suppose que toutes vos tables ont leur politique par défaut configurée à ACCEPT (-P ACCEPT), ce qui devrait être le cas si vous n'avez pas encore touché à iptables. Notre réseau privé est une classe B avec l'adresse 172.17.0.0/16 et notre adresse publique est 212.170.21.172.

Nous indiquons au noyau de faire de la traduction d'adresse NAT; les clients du réseau privé peuvent alors commencer à dialoguer avec l'extérieur.

echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 172.17.0.0/255.255.0.0 -o eth0 -j SNAT --to-source 212.170.21.172

Vérifions maintenant que les paquets transitent bien à travers 1:15 :

tc -s class show dev eth0

Vous pouvez commencer à marquer les paquets en ajoutant les règles dans la chaîne PREROUTING de la table mangle.

iptables -t mangle -A PREROUTING -p icmp -j MARK --set-mark 0x1
iptables -t mangle -A PREROUTING -p icmp -j RETURN

Vous devriez maintenant être capable de voir l'évolution du compteur de paquets quand vous pinguez des sites sur Internet depuis les machines du réseau privé. Vérifiez que le compteur de paquets augmente dans 1:10 :

tc -s class show dev eth0

Nous avons mis -j RETURN de manière à ce que les paquets ne traversent pas toutes les règles. Les paquets icmp ne scruteront pas les autres règles définies sous RETURN. Gardez ceci à l'esprit. Nous commençons maintenant à ajouter d'autres règles pour gérer les champs TOS :

iptables -t mangle -A PREROUTING -m tos --tos Minimize-Delay -j MARK --set-mark 0x1
iptables -t mangle -A PREROUTING -m tos --tos Minimize-Delay -j RETURN
iptables -t mangle -A PREROUTING -m tos --tos Minimize-Cost -j MARK --set-mark 0x5
iptables -t mangle -A PREROUTING -m tos --tos Minimize-Cost -j RETURN
iptables -t mangle -A PREROUTING -m tos --tos Maximize-Throughput -j MARK --set-mark 0x6
iptables -t mangle -A PREROUTING -m tos --tos Maximize-Throughput -j RETURN

Donnons la priorité aux paquets SSH :

iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 22 -j MARK --set-mark 0x1
iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 22 -j RETURN

Une bonne idée est de donner la priorité aux paquets initiant une connexion tcp, à savoir ceux qui ont le bit SYN activé :

iptables -t mangle -I PREROUTING -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j MARK --set-mark 0x1
iptables -t mangle -I PREROUTING -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j RETURN

Et ainsi de suite. Après la mise en place des règles dans la chaîne PREROUTING de la table "mangle", nous terminons par :

iptables -t mangle -A PREROUTING -j MARK --set-mark 0x6

Ainsi, le trafic non marqué est dirigé vers 1:15. En fait, cette dernière étape n'est pas nécessaire puisque la classe par défaut est 1:15. Un marquage est quand même réalisé de manière à avoir une cohérence pour l'ensemble de la configuration. De plus, il est utile d'avoir une comptabilité pour cette règle.

C'est une bonne idée de faire de même avec la chaîne OUTPUT. Répétez ces commandes avec -A OUTPUT à la place de PREROUTING (s/PREROUTING/OUTPUT/). Le trafic généré localement (sur le routeur Linux) sera alors également classifié. Je termine la chaîne OUTPUT par -j MARK --set-mark 0x3 de tel sorte que le trafic local ait une priorité plus grande.

15.10.3. Améliorer notre configuration

Toute notre configuration est maintenant opérationnelle. Prenez du temps pour regarder les graphes et observer où votre bande passante est la plus utilisée et cela correspond à vos souhaits. J'ai fait ceci pendant de nombreuses heures, ce qui m'a permis d'avoir une connexion Internet fonctionnant très bien. Autrement, vous serez confronté en permanence à des "timeout" et des allocations de bande passante presque nulles pour les nouvelles connexions tcp.

Si vous repérez des classes qui sont pleines la plupart du temps, ce peut être une bonne idée de leur attacher un autre gestionnaire de mise en file d'attente de manière à ce que le partage de la bande passante soit plus équitable :

tc qdisc add dev eth0 parent 1:13 handle 130: sfq perturb 10
tc qdisc add dev eth0 parent 1:14 handle 140: sfq perturb 10
tc qdisc add dev eth0 parent 1:15 handle 150: sfq perturb 10

15.10.4. Rendre tout ceci actif au démarrage

Il est certain que ceci peut être fait de différentes façons. Dans mon cas, j'ai un shell script /etc/init.d/packetfilter qui accepte les arguments [start | stop | stop-tables | start-tables | reload-tables]. Celui-ci configure les gestionnaires de mise en file d'attente et charge les modules du noyau nécessaires et se comporte donc comme une démon. Le même script charge les règles iptables à partir de /etc/network/iptables-rules. Je vais l'embellir un peu et le rendrait disponible sur ma page web ici