9.5. Gestionnaires de file d'attente basés sur les classes

Les gestionnaires de mise en file d'attente basés sur des classes sont très utiles si vous avez différentes sortes de trafic qui doivent être traités différemment. L'un d'entre eux est appelé CBQ, pour Class Based Queueing. Il est si souvent mentionné que les personnes identifient les gestionnaires de mise en file d'attente basés sur des classes uniquement à CBQ, ce qui n'est pas le cas.

CBQ est le mécanisme le plus ancien, ainsi que le plus compliqué. Il n'aura pas forcément les effets que vous recherchez. Ceci surprendra peut-être ceux qui sont sous l'emprise de « l'effet Sendmail », qui nous enseigne qu'une technologie complexe, non documentée est forcément meilleure que toute autre.

Nous évoquerons bientôt, plus à propos, CBQ et ses alternatives.

9.5.1. Flux à l'intérieur des gestionnaires basés sur des classes & à l'intérieur des classes

Quand le trafic entre dans un gestionnaire de mise en file d'attente basé sur des classes, il doit être envoyé vers l'une de ses classes ; il doit être « classifié ». Pour déterminer que faire d'un paquet, les éléments appelés « filtres » sont consultés. Il est important de savoir que les filtres sont appelés de l'intérieur d'un gestionnaire, et pas autrement !

Les filtres attachés à ce gestionnaire renvoient alors une décision que le gestionnaire utilise pour mettre en file d'attente le paquet vers l'une des classes. Chaque sous-classe peut essayer d'autres filtres pour voir si de nouvelles instructions s'appliquent. Si ce n'est pas le cas, la classe met le paquet en file d'attente dans le gestionnaire de mise en file d'attente qu'elle contient.

En plus de contenir d'autres gestionnaires, la plupart des gestionnaires de mise en file d'attente basés sur des classes réalisent également de la mise en forme. Ceci est utile pour réaliser à la fois l'ordonnancement (avec SFQ, par exemple) et le contrôle de débit. Vous avez besoin de ceci dans les cas où vous avez une interface à haut débit (ethernet, par exemple) connectée à un périphérique plus lent (un modem câble).

Si vous n'utilisez que SFQ, rien ne devait se passer, dans la mesure où les paquets entrent et sortent du routeur sans délai : l'interface de sortie est de loin beaucoup plus rapide que la vitesse réelle de votre liaison ; il n'y a alors pas de files d'attente à réordonnancer.

9.5.2. La famille des gestionnaires de mise en file d'attente : racines, descripteurs, descendances et parents

Chaque interface à « un gestionnaire de mise en file d'attente racine » de sortie (egress root qdisc). Par défaut, le gestionnaire de mise en file d'attente sans classes mentionné plus tôt pfifo_fast. Chaque gestionnaire et classe est repéré par un descripteur (handle), qui pourra être utilisé par les prochaines déclarations de configuration pour se référer à ce gestionnaire. En plus du gestionnaire de sortie, une interface peut également avoir un gestionnaire d'entrée (ingress), qui réglemente le trafic entrant.

Ces descripteurs sont constitués de deux parties : un nombre majeur et un nombre mineur : <major>:<minor>. Il est habituel de nommer le gestionnaire racine 1:, ce qui est équivalent à 1:0. Le nombre mineur d'un gestionnaire de mise en file d'attente est toujours 0.

Les classes doivent avoir le même nombre majeur que leur parent. Le nombre majeur doit être unique à l'intérieur d'une configuration egress ou ingress. Le nombre mineur doit être unique à l'intérieur d'un gestionnaire de mise en file d'attente et de ses classes.

9.5.2.1. Comment les filtres sont utilisés pour classifier le trafic

Pour récapituler, une hiérarchie typique pourrait ressembler à ceci :

                     1:   Gestionnaire de mise en file d'attente racine
                      |
                     1:1    classe enfant
                   /  |  \
                  /   |   \
                 /    |    \
                 /    |    \
              1:10  1:11  1:12   classes enfants
               |      |     | 
               |     11:    |    classe terminale
               |            | 
               10:         12:   Gestionnaire de mise en file d'attente
              /   \       /   \
           10:1  10:2   12:1  12:2   classes terminales

Mais ne laissez pas cet arbre vous abuser ! Vous ne devriez pas imaginer le noyau être au sommet de l'arbre et le réseau en dessous, ce qui n'est justement pas le cas. Les paquets sont mis et retirés de la file d'attente à la racine du gestionnaire, qui est le seul élément avec lequel le noyau dialogue.

Un paquet pourrait être classifié à travers une chaîne suivante :

1: -> 1:1 -> 12: -> 12:2

Le paquet réside maintenant dans la file d'attente du gestionnaire attaché à la classe 12:2. Dans cet exemple, un filtre a été attaché à chaque nœud de l'arbre, chacun choisissant la prochaine branche à prendre. Cela est réalisable. Cependant, ceci est également possible :

1: -> 12:2

Dans ce cas, un filtre attaché à la racine a décidé d'envoyer le paquet directement à 12:2.

9.5.2.2. Comment les paquets sont retirés de la file d'attente et envoyés vers le matériel

Quand le noyau décide qu'il doit extraire des paquets pour les envoyer vers l'interface, le gestionnaire racine 1: reçoit une requête dequeue, qui est transmise à 1:1 et qui, à son tour, est passée à 10:, 11: et 12:, chacune interrogeant leurs descendances qui essaient de retirer les paquets de leur file d'attente. Dans ce cas, le noyau doit parcourir l'ensemble de l'arbre, car seul 12:2 contient un paquet.

En résumé, les classes « emboîtées » parlent uniquement à leur gestionnaire de mise en file d'attente parent ; jamais à une interface. Seul la file d'attente du gestionnaire racine est vidée par le noyau !

Ceci a pour résultat que les classes ne retirent jamais les paquets d'une file d'attente plus vite que ce que leur parent autorise. Et c'est exactement ce que nous voulons : de cette manière, nous pouvons avoir SFQ dans une classe interne qui ne fait pas de mise en forme, mais seulement de l'ordonnancement, et avoir un gestionnaire de mise en file d'attente extérieur qui met en forme le trafic.

9.5.3. Le gestionnaire de mise en file d'attente PRIO

Le gestionnaire de mise en file d'attente ne met pas vraiment en forme le trafic ; il ne fait que le subdiviser en se basant sur la manière dont vous avez configuré vos filtres. Vous pouvez considérer les gestionnaires PRIO comme une sorte de super pfifo_fast dopé, où chaque bande est une classe séparée au lieu d'une simple FIFO.

Quand un paquet est mis en file d'attente dans le gestionnaire PRIO, une classe est choisie en fonction des filtres que vous avez donnés. Par défaut, trois classes sont créées. Ces classes contiennent par défaut de purs gestionnaires de mise en file d'attente FIFO sans structure interne, mais vous pouvez les remplacer par n'importe quels gestionnaires disponibles.

Chaque fois qu'un paquet doit être retiré d'une file d'attente, la classe :1 est d'abord testée. Les classes plus élevées ne sont utilisées que si aucune des bandes plus faibles n'a pas fourni de paquets.

Cette file d'attente est très utile dans le cas où vous voulez donner la priorité à certains trafics en utilisant toute la puissance des filtres tc et en ne se limitant pas seulement aux options du champ TOS. Vous pouvez également ajouter un autre gestionnaire de mise en file d'attente aux trois classes prédéfinies, tandis que pfifo_fast est limité aux simples gestionnaires FIFO.

Puisqu'il ne met pas vraiment en forme, on applique le même avertissement que pour SFQ. Utilisez PRIO seulement si votre lien physique est vraiment saturé ou intégrez-le à l'intérieur d'un gestionnaire de mise en file d'attente basé sur des classes qui réalisent la mise en forme. Ce dernier cas est valable pour pratiquement tous les modems-câbles et les périphériques DSL.

En termes formels, le gestionnaire de mise en file d'attente PRIO est un ordonnanceur Work-Conserving.

9.5.3.1. Paramètres PRIO & usage

Les paramètres suivants sont reconnus par tc :

bands

Nombre de bandes à créer. Chaque bande est en fait une classe. Si vous changez ce nombre, vous devez également changer :

priomap

Si vous ne fournissez pas de filtres tc pour classifier le trafic, le gestionnaire PRIO regarde la priorité TC_PRIO pour décider comment mettre en file d'attente le trafic.

Ceci fonctionne comme le gestionnaire de mise en file d'attente pfifo_fast mentionné plus tôt. Voir la section correspondante pour plus de détails.

Les bandes sont des classes et sont appelées par défaut majeur:1 à majeur:3. Donc, si votre gestionnaire de mise en file d'attente est appelé 12:, tc filtre le trafic vers 12:1 pour lui accorder une plus grande priorité.

Par itération, la bande 0 correspond au nombre mineur 1, la bande 1 au nombre mineur 2, etc ...

9.5.3.2. Configuration simple

Nous allons créer cet arbre :

     racine 1: prio
          1:   Gestionnaire racine
         / | \ 
       /   |   \
       /   |   \
     1:1  1:2  1:3    classes
      |    |    |
     10:  20:  30:    gestionnaire gestionnaire
     sfq  tbf  sfq
bande 0    1    2

Le trafic de masse ira vers 30: tandis que le trafic interactif ira vers 20: ou 10:.

Les lignes de commande :

# tc qdisc add dev eth0 root handle 1: prio 
## Ceci crée *instantanément* les classes 1:1, 1:2, 1:3
  
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq                                

Regardons maintenant ce que nous avons créé :

# tc -s qdisc ls dev eth0 
qdisc sfq 30: quantum 1514b 
 Sent 0 bytes 0 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 0 bytes 0 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 132 bytes 2 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 174 bytes 3 pkts (dropped 0, overlimits 0) 

Comme vous pouvez le voir, la bande 0 a déjà reçu du trafic, et un paquet a été envoyé pendant l'exécution de cette commande !

Nous allons maintenant générer du trafic de masse avec un outil qui configure correctement les options TOS, et regarder de nouveau :

# scp tc ahu@10.0.0.11:./
ahu@10.0.0.11's password: 
tc                   100% |*****************************|   353 KB    00:00    
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b 
 Sent 384228 bytes 274 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 2640 bytes 20 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 2230 bytes 31 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 389140 bytes 326 pkts (dropped 0, overlimits 0) 

Comme vous pouvez le voir, tout le trafic a été envoyé comme prévu vers le descripteur 30:, qui est la bande de plus faible priorité. Maintenant, pour vérifier que le trafic interactif va vers les bandes de plus grande priorité, nous générons du trafic interactif :

# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b 
 Sent 384228 bytes 274 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 2640 bytes 20 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 14926 bytes 193 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 401836 bytes 488 pkts (dropped 0, overlimits 0) 

Ca a marché. Tout le trafic supplémentaire a été vers 10:, qui est notre gestionnaire de plus grande priorité. Aucun trafic n'a été envoyé vers les priorités les plus faibles, qui avaient reçu au préalable tout le trafic venant de notre scp.

9.5.4. Le célèbre gestionnaire de mise en file d'attente CBQ

Comme dit avant, CBQ est le gestionnaire de mise en file d'attente disponible le plus complexe, celui qui a eu le plus de publicité, qui est le moins compris et qui est probablement le plus farceur lors de sa mise au point. Ce n'est pas parce que les auteurs sont mauvais ou incompétents, loin de là, mais l'algorithme CBQ n'est pas remarquablement précis et il ne correspond pas vraiment à la façon dont Linux fonctionne.

En plus d'être basé sur des classes, CBQ sert également à la mise en forme de trafic et c'est sur cet aspect qu'il ne fonctionne pas très bien. Il travaille comme ceci : si vous essayez de mettre en forme une connexion de 10mbit/s à 1mbits/s, le lien doit être inactif 90% du temps. Si ce n'est pas le cas, nous devons limiter le taux de sorte qu'il soit inactif 90% du temps.

Ceci est assez dur à mesurer et c'est pour cette raison que CBQ déduit le temps d'inactivité du nombre de microsecondes qui s'écoulent entre les requêtes de la couche matérielle pour avoir plus de données. Cette combinaison peut être utilisée pour évaluer si le lien est chargé ou non.

Ceci est plutôt léger et l'on arrive pas toujours à des résultats convenables. Par exemple, qu'en est-il de la vitesse de liaison réelle d'une interface qui n'est pas capable de transmettre pleinement les données à 100mbit/s, peut-être à cause d'un mauvais pilote de périphérique ? Une carte réseau PCMCIA ne pourra jamais atteindre 100mbit/s à cause de la conception du bus. De nouveau, comment calculons-nous le temps d'inactivité ?

Cela devient même pire quand on considère un périphérique réseau "pas-vraiment-réel" comme PPP Over Ethernet ou PPTP over TCP/IP. La largeur de bande effective est dans ce cas probablement déterminée par l'efficacité des tubes vers l'espace utilisateur, qui est énorme.

Les personnes qui ont effectué des mesures ont découvert que CBQ n'est pas toujours très exact, et parfois même, très éloigné de la configuration.

Cependant, il marche bien dans de nombreuses circonstances. Avec la documentation fournie ici, vous devriez être capable de le configurer pour qu'il fonctionne bien dans la plupart des cas.

9.5.4.1. Mise en forme CBQ en détail

Comme dit précédemment, CBQ fonctionne en s'assurant que le lien est inactif juste assez longtemps pour abaisser la bande passante réelle au débit configuré. Pour réaliser cela, il calcule le temps qui devrait s'écouler entre des paquets de taille moyennne.

En cours de fonctionnement, le temps d'inactivité effectif (the effective idletime) est mesuré en utilisant l'algorithme EWMA (Exponential Weighted Moving Average), qui considère que les paquets récents sont exponentiellement plus nombreux que ceux passés. La charge moyenne UNIX (UNIX loadaverage) est calculée de la même manière.

Le temps d'inactivité calculé est soustrait à celui mesuré par EWMA et le nombre résultant est appelé avgidle. Un lien parfaitement chargé a un avgidle nul : un paquet arrive à chaque intervalle calculé.

Une liaison surchargée a un avgidle négatif et s'il devient trop négatif, CBQ s'arrête un moment et se place alors en dépassement de limite (overlimit).

Inversement, un lien inutilisé peut accumuler un avgidle énorme, qui autoriserait alors des bandes passantes infinies après quelques heures d'inactivité. Pour éviter cela, avgidle est borné à maxidle.

En situation de dépassement de limite, CBQ peut en théorie bloquer le débit pour une durée équivalente au temps qui doit s'écouler entre deux paquets moyens, puis laisser passer un paquet et bloquer de nouveau le débit. Regardez cependant le paramètre minburst ci-dessous.

Voici les paramètres que vous pouvez spécifier pour configurer la mise en forme :

avpkt

Taille moyenne d'un paquet mesurée en octets. Nécessaire pour calculer maxidle, qui dérive de maxburst, qui est spécifié en paquets.

bandwidth

La bande passante physique de votre périphérique nécessaire pour les calculs du temps de non utilisation (idle time).

cell

La durée de transmission d'un paquet n'augmente pas nécessairement de manière linéaire en fonction de sa taille. Par exemple, un paquet de 800 octets peut être transmis en exactement autant de temps qu'un paquet de 806 octets. Ceci détermine la granularité. Cette valeur est généralement positionnée à 8, et doit être une puissance de deux.

maxburst

Ce nombre de paquets est utilisé pour calculer maxidle de telle sorte que quand avgidle est égal à maxidle, le nombre de paquets moyens peut être envoyé en rafale avant que avgidle ne retombe à 0. Augmentez-le pour être plus tolérant vis à vis des rafales de données. Vous ne pouvez pas configurer maxidle directement, mais seulement via ce paramètre.

minburst

Comme nous l'avons déjà indiqué, CBQ doit bloquer le débit dans le cas d'un dépassement de limite. La solution idéale est de le faire pendant exactement le temps d'inutilisation calculé, puis de laisser passer un paquet. Cependant, les noyaux UNIX ont généralement du mal à prévoir des événements plus courts que 10 ms, il vaut donc mieux limiter le débit pendant une période plus longue, puis envoyer minburst paquets d'un seul coup et dormir pendant une durée de minburst.

Le temps d'attente est appelé offtime. De plus grandes valeurs de minburst mènent à une mise en forme plus précise dans le long terme, mais provoquent de plus grandes rafales de données pendant des périodes de quelques millisecondes.

minidle

Si avgidle est inférieur à 0, nous sommes en dépassement de limite et nous devons attendre jusqu'à ce que avgidle devienne suffisamment important pour envoyer un paquet. Pour éviter qu'une brusque rafale de données n'empêche le lien de fonctionner pendant une durée prolongée, avgidle est remis à minidle s'il atteint une valeur trop basse.

La valeur minidle est spécifiée en microsecondes négatives : 10 signifie alors que avgidle est borné à -10µs.

mpu

Taille minumum d'un paquet. Nécessaire car même un paquet de taille nulle est encapsulé par 64 octets sur ethernet et il faut donc un certain temps pour le transmettre. CBQ doit connaître ce paramètre pour calculer précisément le temps d'inutilisation.

rate

Débit du trafic sortant du gestionnaire. Ceci est le « paramètre de vitesse » !

En interne, CBQ est finement optimisé. Par exemple, les classes qui sont connues pour ne pas avoir de données présentes dans leur file d'attente ne sont pas interrogées. Les classes en situation de dépassement de limite sont pénalisées par la diminution de leur priorité effective. Tout ceci est très habile et compliqué.

9.5.4.2. Le comportement CBQ classful

En plus de la mise en forme, en utilisant les approximations idletime mentionnées ci-dessus, CBQ peut également agir comme une file d'attente PRIO dans le sens où les classes peuvent avoir différentes priorités. Les priorités de plus faible valeur seront examinées avant celles de valeurs plus élevées.

Chaque fois qu'un paquet est demandé par la couche matérielle pour être envoyé sur le réseau, un processus weighted round robin (WRR) démarre en commençant par les classes de plus faibles numéros.

Celles-ci sont regroupées et interrogées si elles ont des données disponibles. Après qu'une classe ait été autorisée à retirer de la file d'attente un nombre d'octets, la classe de priorité suivante est consultée.

Les paramètres suivants contrôlent le processus WRR :

allot

Quand le CBQ racine reçoit une demande d'envoi de paquets sur une interface, il va essayer tous les gestionnaires internes (dans les classes) tour à tour suivant l'ordre du paramètre priority. A chaque passage, une classe ne peut envoyer qu'une quantité limitée de données. Le paramètre allot est l'unité de base de cette quantité. Voir le paramètre weight pour plus d'informations.

prio

CBQ peut également agir comme un périphérique PRIO. Les classes internes avec les priorités les plus élevées sont consultées en premier et, aussi longtemps qu'elles ont du trafic, les autres classes ne sont pas examinées.

weight

Le paramètre weight assiste le processus Weighted Round Robin. Chaque classe a tour à tour la possibilité d'envoyer ses données. Si vous avez des classes avec des bandes passantes significativement plus importantes, il est logique de les autoriser à envoyer plus de données à chaque tour que les autres.

Vous pouvez utiliser des nombres arbitraires dans la mesure où CBQ additionne tous les paramètres weight présents sous une classe et les normalise. La règle empirique qui consiste à prendre rate/10 semble fonctionner correctement. Le paramètre weight normalisé est multiplié par le paramètre allot pour déterminer la quantité de données à envoyer à chaque tour.

Notez, s'il vous plaît, que toutes les classes à l'intérieur d'une hiérarchie CBQ doivent avoir le même nombre majeur !

9.5.4.3. Paramètres CBQ qui déterminent le partage & le prêt du lien

En plus de purement limiter certains trafics, il est également possible de spécifier quelles classes peuvent emprunter de la bande passante aux autres classes ou, réciproquement, prêter sa bande passante.

isolated/ sharing

Une classe qui est configurée avec isolated ne prêtera pas sa bande passante à ses classes soeurs. Utilisez ceci si vous avez sur votre lien deux agences concurrentes ou qui ne s'apprécient pas et qui ne veulent pas se prêter gratuitement de la bande passante.

Le programme de contrôle tc connait également sharing, qui agit à l'inverse du paramètre isolated.

bounded/ borrow

Une classe peut aussi être bornée (bounded), ce qui signifie qu'elle n'essaiera pas d'emprunter de la bande passante à ses classes enfants. tc connait également borrow, qui agit à l'inverse de bounded.

Une situation typique pourrait être le cas où vous avez deux agences présentes sur votre lien qui sont à la fois isolated et bounded. Ceci signifie qu'elles sont strictement limitées à leur débit et qu'elles ne prêteront pas aux autres leur bande passante.

A l'intérieur de ces classes d'agence, il pourrait y avoir d'autres classes qui sont autorisées à échanger leur bande passante.

9.5.4.4. Configuration simple

               1:           gestionnaire de mise en file d'attente racine
               |
              1:1           classe enfant
             /   \
            /     \
          1:3     1:4       classes terminales
           |       |
          30:     40:       gestionnares de mise en file d'attente
         (sfq)   (sfq)

Cette configuration limite le trafic d'un serveur web à 5 mbit et le trafic SMTP à 3 mbit. Il est souhaitable qu'ils n'occupent pas plus de 6 mbit à eux deux. Nous avons une carte réseau à 100 mbit et les classes peuvent s'emprunter mutuellement de la bande passante.

# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit         \ 
  avpkt 1000 cell 8
# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit  \
  rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20      \
  avpkt 1000 bounded

Cette partie installe la racine et la classe 1:1 habituelle. La classe 1:1 est bornée, la bande passante totale ne pourra donc pas excéder 6 mbit.

Comme dit avant, CBQ a besoin de NOMBREUX paramètres. Tous ces paramètres sont cependant expliqués au-dessus. La configuration HTB correspondante est beaucoup plus simple.

# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit  \
  rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20      \
  avpkt 1000
# tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit  \
  rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20      \
  avpkt 1000

Ce sont nos deux classes. Notez comment nous avons configuré la valeur du paramètre weight en fonction du paramètre rate. La bande passante de l'ensemble des deux classes ne pourra jamais dépasser 6 mbit. En fait, les identifieurs de classe (classid) doivent avoir le même numéro majeur que le gestionnaire de mise en file d'attente parent !

# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq

Les deux classes ont par défaut un gestionnaire de mise en file d'attente FIFO. Nous les remplaçons par une file d'attente SFQ de telle sorte que chaque flux de données soit traité de manière égale.

# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
  sport 80 0xffff flowid 1:3
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
  sport 25 0xffff flowid 1:4

Ces commandes, directement attachées à la racine, envoient le trafic vers le bon gestionnaire de mise en file d'attente.

Notez que nous utilisons tc class add pour CREER des classes à l'intérieur d'un gestionnaire de mise en file d'attente, et que nous utilisons tc qdisc add pour véritablement configurer ces classes.

Vous vous demandez peut-être ce qui arrive au trafic qui n'est classifié par aucune des deux règles. Dans ce cas, les données seront traitées à l'intérieur de 1:0, et le débit ne sera pas limité.

Si le trafic SMTP+web tente de dépasser la limite de 6 mbit/s, la bande passante sera divisée selon le paramètre weight, donnant 5/8 du trafic au serveur web et 3/8 au serveur smtp.

Avec cette configuration, vous pouvez également dire que le trafic du serveur web sera au minimum de 5/8 * 6 mbit = 3.75 mbit.

9.5.4.5. D'autres paramètres CBQ : split & defmap

Comme précisé avant, un gestionnaire de mise en file d'attente basé sur des classes doit appeler des filtres pour déterminer dans quelle classe un paquet sera mis en file d'attente.

En plus d'appeler les filtres, CBQ offre d'autres options : defmap & split. C'est plutôt compliqué à comprendre et, de plus, ce n'est pas vital. Mais, étant donné que ceci est le seul endroit connu où defmap & split sont correctement expliqués, je vais faire de mon mieux.

Etant donné que nous voulons le plus souvent réaliser le filtrage en ne considérant que le champ TOS, une syntaxe spéciale est fournie. Chaque fois que CBQ doit trouver où le paquet doit être mis en file d'attente, il vérifie si le nœud est un nœud d'aiguillage (split node). Si c'est le cas, un de ses sous-gestionnaires a indiqué son souhait de recevoir tous les paquets configurés avec une certaine priorité. Celle ci peut être dérivée du champ TOS ou des options des sockets positionnées par les applications.

Les bits de priorités des paquets subissent un ET logique avec le champ defmap pour voir si une correspondance existe. En d'autres termes, c'est un moyen pratique de créer un filtre très rapide, qui ne sera actif que pour certaines priorités. Un defmap de ff (en hexadécimal) vérifiera tout tandis qu'une valeur de 0 ne vérifiera rien. Une configuration simple aidera peut-être à rendre les choses plus claires :

# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 \
  cell 8 avpkt 1000 mpu 64
 
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit    \
  rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20        \
  avpkt 1000

Préambule standard de CBQ. Je n'ai jamais pris l'habitude de la quantité de nombres nécessaires !

Le paramètre defmap se réfère aux bits TC_PRIO qui sont définis comme suit :

TC_PRIO..          Num  Correspond à TOS
-------------------------------------------------
BESTEFFORT         0    Maximalise la Fiabilité 
FILLER             1    Minimalise le Coût 
BULK               2    Maximalise le Débit (0x8)  
INTERACTIVE_BULK   4                               
INTERACTIVE        6    Minimise le Délai (0x10)      
CONTROL            7                               

Les nombres TC_PRIO.. correspondent aux bits comptés à partir de la droite. Voir la section pfifo_fast pour plus de détails sur la façon dont les bits TOS sont convertis en priorités.

Maintenant, les classes interactive et de masse :

# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit     \
  rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20        \
  avpkt 1000 split 1:0 defmap c0

# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit     \
  rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20        \
  avpkt 1000 split 1:0 defmap 3f

La gestion de mise en file d'attente d'aiguillage (split qdisc) est 1:0 et c'est à ce niveau que le choix sera fait. C0 correspond au nombre binaire 11000000 et 3F au nombre binaire 00111111. Ces valeurs sont choisies de telle sorte qu'à elles deux, elles vérifient tous les bits. La première classe correspond aux bits 6 & 7, ce qui est équivalent aux trafics « interactif » et de « contrôle ». La seconde classe correspond au reste.

Le nœud 1:0 possède maintenant la table suivante :

priorité       envoyer à
0               1:3
1               1:3
2               1:3
3               1:3
4               1:3
5               1:3
6               1:2
7               1:2

Pour d'autres amusements, vous pouvez également donner un « masque de changement » qui indique exactement les priorités que vous souhaitez changer. N'utilisez ceci qu'avec la commande tc class change. Par exemple, pour ajouter le trafic best effort à la classe 1:2, nous devrons exécuter ceci :

# tc class change dev eth1 classid 1:2 cbq defmap 01/01

La carte des priorités au niveau de 1:0 ressemble maintenant à ceci :

priorité       envoyer à
0               1:2
1               1:3
2               1:3
3               1:3
4               1:3
5               1:3
6               1:2
7               1:2

FIXME: tc class change n'a pas été testé, mais simplement vu dans les sources.

9.5.5. Seau de jetons à contrôle hiérarchique (Hierarchical Token Bucket)

Martin Devera(<devik>) réalisa à juste titre que CBQ est complexe et qu'il ne semble pas optimisé pour de nombreuses situations classiques. Son approche hiérarchique est bien adaptée dans le cas de configurations où il y a une largeur de bande passante fixée à diviser entre différents éléments. Chacun de ces éléments aura une bande passante garantie, avec la possibilité de spécifier la quantité de bande passante qui pourra être empruntée.

HTB travaille juste comme CBQ, mais il n'a pas recourt à des calculs de temps d'inoccupation pour la mise en forme. A la place, c'est un Token Bucket Filter basé sur des classes, d'où son nom. Il n'a que quelques paramètres, qui sont bien documentés sur ce site.

Au fur et à mesure que votre configuration HTB se complexifie, votre configuration s'adapte bien. Avec CBQ, elle est déjà complexe même dans les cas simples ! HTB3 (voir sa page principale pour les détails des versions HTB) fait maintenant parti des sources officielles du noyau (à partir des versions 2.4.20-pre1 et 2.5.31 et supérieures). Il est encore cependant possible que vous soyez obligé de récupérer la version mise à jour de 'tc' pour HTB3. Les programmes de l'espace utilisateur et la partie HTB du noyau doivent avoir le même numéro majeur. Sans cela, 'tc' ne marchera pas avec HTB.

Si vous avez déjà un noyau récent ou si vous êtes sur le point de mettre à jour votre noyau, considérez HTB coûte que coûte.

9.5.5.1. Configuration simple

Fonctionnellement presque identique à la configuration simple CBQ présentée ci-dessus :

# tc qdisc add dev eth0 root handle 1: htb default 30

# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k

# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k

L'auteur recommande SFQ sous ces classes :

# tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
# tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
# tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10

Ajouter les filtres qui dirigent le trafic vers les bonnes classes :

# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20

Et, c'est tout. Pas de vilains nombres non expliqués, pas de paramètres non documentés.

HTB semble vraiment merveilleux. Si 10: et 20: ont atteint tous les deux leur bande passante garantie et qu'il en reste à partager, ils l'empruntent avec un rapport de 5:3, comme attendu.

Le trafic non classifié est acheminé vers 30:, qui a une petite bande passante, mais qui peut emprunter tout ce qui est laissé libre. Puisque nous avons choisi SFQ en interne, on hérite naturellement de l'équité.