Ah ah ! Tu croyais que je t'avais oublié hein ! Et bien non, je profite simplement d'un de mes rares moments de sobriété pour finir ce que j'avais commencé. La dernière fois, on s'était occupé de marquer les paquets avec iptables. Tu as donc maintenant des jolies paquets avec des étiquettes dessus pour dire ce que c'est, il va donc s'agir de les ranger dans le bonne ordre et de pas faire n'importe quoi avec.

Bande pas Sante !

Maintenant, on va vraiment pouvoir rentrer dans le lard de la bestiole, la gestion de la QoS sous Linux. D'abord, il faut que tu saches que c'est un merdier infâme, avec des couches dans tous les sens et des morceaux que plus personne sait très bien à quoi ils servent. Enfin, il y a probablement des gens qui savent, mais pour le clampin de base, c'est juste imbuvable.

L'outil de base se nomme tc (traffic control) et il permet de contrôler le trafic :merci captain obvious:. Le seul problème de cet outil, c'est que sa syntaxe est absolument inutilisable (comme la plupart des outils réseaux sous Linux). Le plus simple est donc de ne pas l'utiliser et de passer plutôt par un autre outil tcng (traffic control next generation) qui fait la même chose mais en plus lisible.

D'autre part, il existe des dizaines d'algo pour faire de la qualité de services. Des seaux à jetons (token bucket), des seaux percés (leaky bucket), des seaux de plages :joke:, etc… Toutes ces méthodes ont des avantages et des inconvénients. On pourrait discuter pendant des heures des différentes méthodes, mais ce ne serait pas forcément intéressants et ça ne permettrait pas de faire un truc rapide, facile et indigeste pour de la gestion basique.

On va donc s'intéresser uniquement au hierarchical token bucket qui présente l'intérêt d'avoir à peu près toutes les fonctionnalités possibles (limitation de bande passante, minimum réservé, priorité, etc…) sans être trop prise de tête à mettre en place.

Envoyons du pâté :proutprout:

Posons les bases. Admettons que ton interface de sortie sur le grand Nain Ternet soit eth0 et que tu disposes de 1000kbps de bande passante montante. Tu utilises le zoli marquage de paquets avec 3 classes différentes : EF, AF41 et AF42. On commence donc avec un fichier texte très basique qui ne fait que reprendre cette définition et poser quelques variables :

#include <fields.tc>

dev eth0 {
        egress {
                class( <$ef> )
                        if ip_dscp == 0b101110
                ;
                class( <$af41> )
                        if ip_dscp == 0b100010
                ;
                class( <$af42> )
                        if ip_dscp == 0b100100
                ;
        htb() {
                class ( rate 1000kbps ) {
                }
        }
        }
}

On définit donc les différentes classes qu'on va utiliser en se référant à notre magnifique tableau de conversion qui permet de les avoir en un clin d'œil. Ici, je ne me sers volontairement que du champs DSCP (d'où le include <fields.tc>), mais on pourrait très bien déterminer l'importance des classes directement via tc (c'est juste franchement moins souple).

Ensuite, on crée un htb (hierarchical token bucket) qdisc (queueing discipline), une sorte de super-classe. Rien ne pourra sortir de ce cadre et l'upload général de la machine sera limité à 1000kbps. D'où l'intérêt de connaître assez bien sa connexion. Cette super-classe est très importante puisqu'elle va permettre de gérer plus facilement le minimum et le maximum de bande passante de chaque classe DSCP. Sans elle, tc n'appliquera que le minimum mais sera incapable de déterminer s'il peut accorder plus de bande passante pour le maximum.

T'as la classe baby !

À l'intérieur de cette classe, il n'y a plus qu'à mettre les autres classes, comme par exemple EF :

class( rate 32kbps, ceil 32kbps, prio 0 ) {
                                $ef = class( rate 32kbps, ceil 32kbps );
}

On indique le minimum réservé rate (ne pourra pas descendre en dessous sauf si une application plus prioritaire demande de la bande passante), le maximum qu'elle pourra atteindre ceil (pour éviter que le blog de ta petite sœur n'explose ta connexion après que ta petite sœur a publié les photos de sa dernière IRL wakfu) et la priorité. Comme c'est EF, on passe en priorité 0, la plus élevée.

Concrètement ici, dès que des paquets EF arrivent, ils se voient réserver 32kbps de bande passante, au détriment des autres classes si nécessaire, mais ne peuvent pas dépasser 32kbps. Cela correspond à une conversation téléphonique, pas 2).

Voilà une autre exemple qui ferait une très bonne classe pour du HTTP (AF41) et du SMTP (AF42) :

class ( rate 256kbps, ceil 512kbps, prio 1 ) {
          $af41 = class( rate 128kbps, ceil 384kbps );
          $af42 = class( rate 16kbps, ceil 128kbps );
}

Dans la même classe, on se retrouve donc avec AF41 et AF42 qui partage le mini et le maxi, mais pas de la même manière. Ici, il n'y a pas de question de priorité, mais seulement de partage de bande passante. Si il y a engorgement, les deux classes prendront cher.

Application du bouzin.

Et maintenant ? On a un joli fichier qui contient des jolies définitions, il ne reste plus qu'à demander à tcng de transformer ça en charabia illisible :

# tcng qos.tc

# ================================ Device eth0 ================================

tc qdisc add dev eth0 handle 1:0 root dsmark indices 4 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 htb
tc class add dev eth0 parent 2:0 classid 2:1 htb rate 125000bps
tc class add dev eth0 parent 2:1 classid 2:2 htb rate 4000bps ceil 4000bps prio 0
tc class add dev eth0 parent 2:2 classid 2:3 htb rate 4000bps ceil 4000bps prio 0
tc class add dev eth0 parent 2:1 classid 2:4 htb rate 32000bps ceil 64000bps prio 1
tc class add dev eth0 parent 2:4 classid 2:5 htb rate 16000bps ceil 48000bps prio 1
tc class add dev eth0 parent 2:4 classid 2:6 htb rate 2000bps ceil 16000bps prio 1
tc filter add dev eth0 parent 2:0 protocol all prio 1 tcindex mask 0x3 shift 0
tc filter add dev eth0 parent 2:0 protocol all prio 1 handle 3 tcindex classid 2:6
tc filter add dev eth0 parent 2:0 protocol all prio 1 handle 2 tcindex classid 2:5
tc filter add dev eth0 parent 2:0 protocol all prio 1 handle 1 tcindex classid 2:3
tc filter add dev eth0 parent 1:0 protocol all prio 1 u32 match u8 0xb8 0xfc at 1 classid 1:1
tc filter add dev eth0 parent 1:0 protocol all prio 1 u32 match u8 0x88 0xfc at 1 classid 1:2
tc filter add dev eth0 parent 1:0 protocol all prio 1 u32 match u8 0x90 0xfc at 1 classid 1:3

Un bon coup de copier/coller dans ton terminal et le tour est joué !!

Il va te rester encore un peu de boulot pour tester les règles et vérifier que tout fonctionne correctement. Mon conseil : tenter un téléchargement bête de fichier et voir comment réagissent les plafonds et les minima. C'est en général un très bon test et relativement facile à faire.

Bon, vais essayer de retrouver cette liqueur de banane moi…