9.5. Klassenbasierende Warteschlangenregeln (classful qdisc)

Klassenbasierende Warteschlangen (classful QDiscs) sind sehr nützlich, wenn du verschiedene Arten von Traffic mit unterschiedliche Bearbeitungen haben solltest. Einer der classful QDiscs heißt 'CBQ', 'Class Based Queueing' wird von den meisten Menschen bei Queueing mit Klassen nur als CBQ identifiziert, aber das ist nicht korrekt.

CBQ ist lediglich schon am längsten vorhanden - und auch das Komplexeste. Es macht nicht immer das, was du eigentlich willst. Das schockiert viele, denen dabei der "sendmail-Effekt" einfällt, er lehrte uns, auch komplexe Technologie muss nicht mit eine Dokumentation kommen die zu den Besten zählt.

Mehr über CBQ und seine Alternativen folgt in Kürze.

9.5.1. Fluss innerhalb klassenbasierende QDiscs & Klassen

Wenn der Traffic zu einer classful QDisc kommt, muss er an eine innere Klassen gesendet werden - er wird 'klassifiziert' (classified). Um zu bestimmen, was mit einem Paket passieren soll, werden die so genannten 'Filter' konsultiert. Wichtig zu wissen ist, dass die Filter werden vom inneren der QDisc aufgerufen, und nicht umgekehrt!

Die an der QDisc angehangenen Filter geben eine Entscheidung zurück und die QDisc sortiert das Paket in eine der Klassen ein. Jede Unterklasse kann nach anderen Filter sehen, ob weitere Anweisungen gelten. Wenn nicht, reiht die Klasse das Paket an die enthaltene QDisc ein.

Sie beinhalten andere QDiscs, die meisten classful QDiscs dienen auch dem Shaping. Dies ist nützlich, um sowohl Paket-Scheduling (zum Beispiel mit SFQ) und Frequenzsteuerung durchzuführen. Du brauchst dies in Fällen, bei denen du an einer Hochgeschwindigkeitsschnittstelle (zum Beispiel Ethernet) eine langsamere Gerät (ein Kabelmodem) anschliest.

Lässt du nur SFQ laufen, passiert nichts weiter, als das Pakete in den Router kommen und ihn unverzüglich wieder verlassen: die Ausgangsschnittstelle ist weit schneller als deine tatsächliche Verbindungsgeschwindigkeit. Es gibt kein Queue zu planen.

9.5.2. Die QDisc-Familie: Wurzeln, Handle, Geschwister und Eltern

Jede Schnittstelle verfügt über eine Egress (Austritt) 'root qdisc'. Standardmäßig sind das die bereits erwähnten classless pfifo_fast queueing discipline (klassenlosen pfifo_fast Warteschlangendisziplinen). Jede QDisc und Klasse ist ein Handle (Griff), welche durch spätere Konfigurationsanweisungen verwendet und der QDisc zugeordnet werden können. Neben einer Egress-QDisc kann eine Schnittstelle auch eine Ingress-QDisc (Eingangs-QDisc) haben, und den einkommenden Traffic mit Polices überwachen.

Die Handles dieser QDiscs bestehen aus zwei Teilen, einer Major- und einer Minor-Nummer: <major>:<minor>. Es ist üblich, die root-qdisc '1:' zu nennen, '1: 0' ist das gleiche. Die Minor-Nummer einer QDisc ist immer 0.

Klassen müssen die gleiche Major-Nummer wie die Eltern haben. Diese Major-Nummer muss eindeutig innerhalb eines Egress oder Ingress sein. Die Minor-Nummer muss innerhalb einer QDisc und seiner Klassen eindeutig sein.

9.5.2.1. Filter verwendet, um Traffic zu klassifizieren

Eine typische Hierarchie kann wie folgt aussehen:

                     1:   Wurzel-QDisc (root qdisc)
                      |
                     1:1    Kindklasse (child class)
                   /  |  \
                  /   |   \
                 /    |    \
                 /    |    \
              1:10  1:11  1:12   Kindklassen (child classes)
               |      |     | 
               |     11:    |    leaf-Klasse (leaf class)
               |            | 
               10:         12:   QDisc
              /   \       /   \
           10:1  10:2   12:1  12:2   leaf-Klassen (leaf classes)

Aber lasst euch nicht von diesen Baum täuschen! Du sollst dir *nicht* den Kernel an der Spitze des Baumes vorstellen, an dem das Netzwerk hängt, so ist es einfach nicht. Pakete werden an der root-QDisc eingereiht und entfernt, das ist die einzige Stelle wo der Kernel mitredet.

Ein Paket kann folgendermassen in einer Kette (Chain) klassifiziert werden:

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

Das Paket befindet sich jetzt im Queue der angehangenen QDisc von Klasse 12:2. In diesem Beispiel wurde in jedem 'Knoten' (node) des Baums ein Filter befestigt, der jeweils eine Entscheidung zum nächsten Zweig durchführt. Dies kann sinnvoll sein. Möglich ist jedoch ebenfalls:

1: -> 12:2

In diesem Fall ist ein Filter an der Wurzel befestigt, um das Paket direkt an 12:2 zu senden.

9.5.2.2. Wie Pakete aus der Warteschlange zur Hardware entfernt werden

Wenn der Kernel festellt, die Pakete müssen extrahiert werden, um sie zur Schnittstelle senden zu können,senden, die root-QDisc 1: bekommt eine Anforderung zur Warteschlangenausgliederung (dequeue request), welche auf 1:1 passt, und auf 10:, 11: und 12:, und jeder von denen fragt seine Geschwister, und versucht mit dequeue() aus ihrer Warteschlange zu entfernt. In diesem Fall muss der Kernel den gesamten Baum durchangeln, denn nur 12:2 enthät ein Paket.

Kurz gesagt, verschachtelte Klassen reden NUR mit ihren Mutter-QDiscs, niemals zu einer Schnittstelle. Nur die root-QDisc kann vom Kernel aus der Warteschlange entfernt werden!

Als Folge ist es nicht erlaubt, dass Klassen schneller als ihre Eltern aus der Warteschlange entfernt werden. Und genau das wollen wir: auf diese Weise können wir eine SFQ in einer inneren Klasse haben, die nicht jedes Shaping durchführt, sondern nur plant, und benutzen ein Shaping ausserhalb von QDisc, das dann das eigentliche Shaping durchführt.

9.5.3. Die PRIO QDisc

Die PRIO QDisc formt nicht wirklich, es gliedert nur Datenverkehr basierend auf deine konfigurierten Filter. Du kannst dir die PRIO QDisc als eine Art pfifo_fast auf Steroiden vorstellen, wobei jedes Band ist eine separate Klasse anstelle eines einfachen FIFO berücksichtig.

Wenn ein Paket in die PRIO QDisc -Warteschlange eingereiht ist, wird auf Basis deiner Filter-Befehle eine Klasse ausgewählt. Standardmäßig werden drei Klassen erstellt. Diese Klassen enthalten von Hause aus reine FIFO-QDiscs ohne innere Struktur, aber du kannst diese in jeder zur Verfügung stehenden QDisc ersetzen.

Immer, wenn ein Paket aus der Warteschlange entfernt werden muss, wird als erstes Klasse :1 versucht. Höhere Klassen werden nur verwendet, wenn alle unteren Bänder kein Paket aufgeben.

Diese QDisc ist sehr nützlich, wenn du bestimmte Arten von Traffic ohne nur TOS-Flags aber mit aller Macht der tc-Filter priorisieren möchtest. Du kannst auch eine andere QDisc zu den 3 vordefinierten Klassen hinzuzufügen, während pfifo_fast auf einfache FIFO-QDiscs begrenzt ist.

Weil es kein echtes formen ist, gilt die gleiche Warnung wie für SFQ: entweder verwendest du es nur, wenn deine physische Verbindung wirklich voll ist oder du bringst es mit einer classful-QDisc in Form. Letzteres gilt für fast alle Kabelmodems und DSL-Geräte.

Formal ausgedrückt ist die PRIO-QDisc ein Arbeits-Einsparer-Scheduler.

9.5.3.1. PRIO Parameter & Nutzung

Die folgenden Parameter werden von TC erkannt:

bands

Anzahl der zu erstellenden Bänder. Jedes Band ist in der Tat eine Klasse. Wenn du diese Nummer änderst, musst du auch das ändern:

priomap

Wenn du Traffic ohne tc-Filter klassifizierst, sucht die PRIO-QDisc bei der TC_PRIO Priorität, um zu entscheiden, wie der Traffic einzureihen ist.

Dies funktioniert genauso wie mit der bereits erwähnten pfifo_fast-QDisc, siehe dort für mehr Details.

Die Bänder sind Klassen und werden standardmäßig von Major:1 bis Major:3 bezeichnet, also wenn deine PRIO-QDisc mit 12: bezeichnet ist, setzt der tc-Filter den Traffic auf 12:1, um eine höhere Priorität einzuräumen.

Anders ausgedrückt geht Band 0 zur Minor-Nummer 1! Band 1 zu Minor-Nummer 2, usw.

9.5.3.2. Beispielkonfiguration

Wir wollen diese Bamstruktur herstellen:

          1:   root qdisc
         / | \ 
       /   |   \
       /   |   \
     1:1  1:2  1:3    classes
      |    |    |
     10:  20:  30:    qdiscs    qdiscs
     sfq  tbf  sfq
band  0    1    2

Massentraffic will zu 30:, interaktiver Datenverkehr zu 20: oder 10: gehen.

Kommandozeilen:

# tc qdisc add dev eth0 root handle 1: prio 
## This *instantly* creates 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                                

Nun wollen wir schauen, was wir angerichtet haben:

# 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) 
Wie du sehen kannst, hat Band 0 bereits einigen Traffic, und ein Paket wurde gesendet als dieser Befehl ausgeführt wurde!

Wir erzeugen jetzt einigen Massendatentransfer mit einem Werkzeug, das korrekte TOS-Flags setzt, und schauen nach:

# 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) 
Wie du siehst, ging der gesamte Datenverkehr auf 30:, welches das Band mit der niedrigste Priorität, so war es beabsichtigt. Um nun zu prüfen, ob der interaktive Verkehr zu den höheren Bändern geht, erzeugen wir interaktiven Datenverkehr:

# 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) 

Es funktioniert - sämtlicher zusätzlichen Verkehr ist auf 10: gegangen, was unsere QDisc mit der höchsten Priorität ist. Es wurde kein Verkehr auf die niedrigste Priorität gesendet, welche bisher unsere gesamte scp empfangen hat.

9.5.4. Die berühmte CBQ-QDisc

Wie bereits erwähnt, ist CBQ verfügt über die komplexeste, die angesagteste, am wenigsten verstandene und wahrscheinlich am schwierigsten richtig eingesetzte QDisc. Aber nicht, weil die Autoren sind böse oder inkompetent sind, ganz im Gegenteil, es liegt am CBQ Algorithmus, der gar nicht der Art und Weise, von Funktionen wie Linux arbeitet, entspricht.

Abgesehen davon ist es klassenbehaftet (classful), CBQ ist ebenfalls Gestalter (shaper) und das in einer Hinsicht, die nicht wirklich sehr gut ist. So sollte es funktionieren. Wenn du versuchst, aus einer 10Mbit/s-Verbindung eine mit 1 Mbit/s zu machen, sollte die Verbindung zu 90% der Zeit im Leerlauf sein. Wenn nicht, müssen wir so drosseln, bis 90% der Zeit Leerlauf ist.

Das ist ziemlich schwer zu messen, CBQ ermittelt die Leerlaufzeit aus der Anzahl der Mikrosekunden, die zwischen den Anforderungen der Hardware-Schicht liegen, für zusätzliche Daten. In Kombination kann man diese verwendet, um ungefähr zu ermitteln wie voll oder leer die Verbindung ist.

Das ist ziemlich umständlich und gelangt nicht immer zu den richtigen Ergebnissen. Zum Beispiel, was ist, wenn die tatsächliche Verbindungsgeschwindigkeit einer Schnittstelle die nicht in der Lage ist, die vollen 100Mbit/s an Daten zu übertragen, vielleicht wegen eines schlecht umgesetzten Treibers? Eine PCMCIA-Netzwerkkarte wird nie 100Mbit/s erreichen, der Bus wurde dafür nicht entwickelt, wie können wir die Leerlaufzeit berechnen?

Es wird noch schlimmer, wenn wir vituelle Netzwerkgeräte wie PPP over Ethernet oder PPTP over TCP/IP prüfen. Die effektive Bandbreite wird in diesem Fall wahrscheinlich durch die Effizienz der Pipes zum Userspace bestimmt - welche riesig ist.

Leute, die Messungen machten, mussten feststellen, dass CBQ nicht immer sehr genau ist und manchmal die Marke komplett verfehlte.

Aber in vielen Fällen funktioniert es gut. Mit dieser Dokumentation ist vorgesehen, dich in die Lage zu versetzen, es so zu konfigurieren, dass es in den meisten Fällen gut funktioniert.

9.5.4.1. CBQ-Shaping im Detail

Wie bereits erwähnt, arbeitet CBQ, indem sichergestellt wird, dass die Verbindung im Leerlauf gerade lang genug ist, um die reale Bandbreite auf die konfigurierten Raten zu senken. Dafür berechnet es die Zeit, die zwischen den durchschnittlichen Pakete vergehen sollte.

Während des Betriebs wird die effektive Leerlaufzeit mit Hilfe eines exponentiell gewichteten gleitenden Durchschnitts (exponential weighted moving average / EWMA) gemessen, unter der Annahme, dass die letzten Pakete exponentiell wichtiger als die Vergangenen sind. Das UNIX loadaverage wird genau so berechnet.

Die berechnete Leerlaufzeit wird von der gemessenen EWMA abgezogen, der sich ergebende Wert wird als 'avgidle' bezeichnet. Ein perfekt ausgelasteter Link hat eine avgidle von Null: Pakete kommen genau einmal pro berechnetem Intervall an.

Ein überlasteter Link hat eine negative avgidle und wenn es zu negativ wird, schaltet CBQ für eine Weile ab und ist dann im 'overlimit'.

Umgekehrt kann eine Leerlaufverbindung einen großen avgidle anhäfen, was nach ein paar stillen Stunden unendliche Bandbreiten wäre. Um dies zu verhindern, wird die avgidle durch maxidle begrenzt.

Theoretisch kann bei overlimit CBQ sich selbst drosseln, wenn genau in der berechneten Zeitspanne zwischen den übergebenen Paketen, ein weiteres Paket kommt, diese wieder drosseln. Aber schau weiter unten beim Parameter 'minburst'.

Dies sind die Parameter, die du zur Konfiguration von shaping benutzen kannst:

avpkt

Durchschnittliche Größe eines Pakets, gemessen in Bytes. Wird benötigt beim Berechnen von maxidle, welche sich aus maxburst abgeleitet, spezifiziert in den Paketen.

bandwidth

Die physikalische Bandbreite des Geräts, benötigt für die Berechnung der Leerlaufzeit.

cell

Die Zeit die ein Paket bei einer schrittweisen Übertragung über ein Gerät benötigt, basierend auf der Paketgröße. Dies setzt die Granularität - als Beispiel: wie lange ein 800 und ein 806 grosses Packet gesendet wird. Meistens auf '8' gesetzt. Es muss eine ganzzahlige Potenz von Zwei sein.

maxburst

Die zu verwendende Anzahl von Paketen um maxidle zu berechnen, sobald avgidle maxidle erreicht hat. Die durchschnittliche Anzahl an Paketen bei Burts ehe avgidle auf 0 fällt. Stelle es höher ein um toleranter bei Bursts zu sein. Du kannst maxidle nicht direkt setzen, das geht nur über diesen Parameter.

minburst

Wie bereits erwähnt, braucht CBQ das um im Falle von overlimit zu drosseln. Die ideale Lösung um die Leerlaufzeit genau berechnen zu können, übergib 1 Paket. Für Unix-Kernel ist es jedoch in der Regel schwer, Ereignissen unter 10 ms zu planen, so drosselt man besser für längere Zeit und übergibt minburst Pakete in einem Rutsch, und dann schläft minburst mal länger.

Die Wartezeit wird als OFFTIME bezeichnet. Höhere Werte bei minburst führen langfristig zu genauerem Shaping, aber größen Bursts im Millisekundenbereich.

minidle

Wenn avgidle unter 0 ist, sind wir im Overlimit und müssen warten, bis avgidle groß genug wird, um ein Paket zu senden. Um ein plötzliches Herunterfahren des Link über eine längere Zeitdauer zu verhindern, wird avgidle auf minidle zurückgesetzt sobald es zu niedrig wird.

Minidle wird in negativen Mikrosekunden angegeben. 10 bedeutet, dass avgidle auf -10μs begrenzt ist.

mpu

Mindestpaketgröße - notwendig, da auch ein Null-Paket im Ethernet auf 64 Byte aufgefüllt ist und somit eine gewisse Zeit zum übertragen braucht. CBQ brenötigt das, um die Lerlaufzeit genau zu berechnen.

rate

Gewünschte Rate des Traffics beim Verlassen dieser QDisc - das ist der 'Geschwindigkeitsknopf'!

Im CBQ gibt es viel Feinabstimmung. Beispielsweise werden Klassen, die bekanntermaßen keine Daten in die Warteschlange gestellt haben, nicht abgefragt. Overlimit Klassen werden durch Einsenkung ihrer effektiven Priorität bestraft. Alles sehr raffiniert & kompliziert.

9.5.4.2. Verhalten von CBQ classful

Neben dem Shaping und der Verwendung der oben genannten idletime Annäherungen, wirkt CBQ ebenfalls wie die PRIO-Warteschlange. Im Sinne, dass Klassen unterschiedliche Prioritäten haben können und kleiner nummerierte Prioritäten vor den höheren Priorität abgefragt werden.

Jedes Mal, wenn ein Paket von der Hardware-Schicht aufgefordert und an das Netzwerk gesendet wird, beginnt ein geWichtetes Round-Robin-Verfahren ("WRR"), angefangen mit der niedrigsten Prioritätsklasse.

Diese werden dann gruppiert und abgefragt sobald Daten zur Verfügung stehen. Wenn ja, werden sie zurückgegeben. Nachdem eine Klasse zustimmt eine Anzahl von Bytes aus der Warteschlange zu entfernt, wird die nächste Klasse innerhalb dieser Priorität geprobt.

Die folgenden Parameter kontrollieren den WRR-Prozess:

allot

Wenn die äußere CBQ aufgefordert wird, ein Paket an eine Schnittstelle zu senden, werden alle inneren QDiscs (in der Klasse) ebenso versucht, und zwar in der Reihenfolge der 'Priorität'-Parameter. Jedes Mal, wenn eine Klasse dran ist, kann sie nur eine begrenzte Menge an Daten senden. 'Allot' ist die Grundeinheit dieses Menge. Siehe Parameter 'weight' für weitere Informationen.

prio

Die CBQ kann auch wie der PRIO-Gerät handeln. Innere Klassen mit höherer Priorität (kleinste Werte) werden zuerst probiert, und so lange sie Traffic haben, werden andere Klassen nicht für den Verkehr abgefragt.

weight

Gewichtung hilft beim Weighted Round Robin-Prozess (WRR). Jede Klasse bekommt eine Chance, wiederholt zu senden. Hast du Klassen mit deutlich höherer Bandbreite, ist es sinnvoll ihnen mehr Daten pro Runde senden zu lassen.

Ein CBQ addiert alle Gewichte unterhalb einer Klasse und normalisiert sie, sodass du beliebige Zahlen verwenden kannst: Nur die Verhältnisse sind wichtig. Die meisten Leute benutzen die 'rate/10' als Faustregel und es scheint gut zu funktionieren. Renormalisierte Gewichtung wird durch den Parameter 'allot' multipliziert, um die Anzahl der geschickten Daten pro Durchgang zu bestimmen.

Bitte achte darauf, dass alle Klassen in einer CBQ-Hierarchie sich die gleiche Hauptnummer teilen müssen!

9.5.4.3. CBQ Parameter, die Link-Sharing & Verleih bestimmen

Neben reiner Begrenzung bestimmter Arten von Datenverkehr, kann man auch festlegen, welche Klassen sich Kapazitäten von anderen Klassen leihen können, oder Bandbreite verleihen.

Isoliert/geteilt (isolated/sharing)

Eine Klasse, die mit 'isolated' konfiguriert ist, wird kein Bandbreite an Geschwisterklassen verleihen. Verwende diese Option, wenn du im Wettbewerb stehst oder feindselige Agenturen auf deinem Link hast, die sich nicht gegenseitig Werbegeschenke machen wollen.

Das Steuerprogramm tc kennt auch 'sharing', was das Gegenteil von 'isoliert' ist.

bounded/borrow

Eine Klasse kann auch 'bounded' (begrenzt) sein, was soviel bedeutet, dass es nicht versucht sich Bandbreite von Geschwisterklassen zu leihen (borrow). tc kennt ebenfalls 'borrow', was das Gegenteil von 'bounded' ist.

Eine typische Situation könnte sein, bei der man zwei Agenturen auf Ihren Link sowohl "isolated" als auch "bounded" hat. Das hat zur Folge, dass sie wirklich auf ihre zugewiesenen Rate begrenzt sind und gegenseitiges Leihen nicht zulassen wird.

Innerhalb einer solchen Agentur Klasse kann es anderen Klassen geben, die berechtigt sind Bandbreite einzutauschen.

9.5.4.4. Beispielkonfiguration


               1:           root qdisc
               |
              1:1           child class
             /   \
            /     \
          1:3     1:4       leaf classes
           |       |
          30:     40:       qdiscs
         (sfq)   (sfq)

Diese Konfiguration begrenzt den Webservertraffic auf 5Mbit und den SMTP-Traffic auf 3 Mbit. Zusammen bekommen sie nicht mehr als 6mbit. Wir haben eine 100Mbit Netzwerkkarte und die Klassen können sich Bandbreite voneinander leihen.

# 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
Dieser Teil installiert die root-Qdisc und die übliche 1:1 Klasse. Die 1:1-Klasse ist in der Gesamtbandbreite auf 6Mbit begrenzt (bounded).

Wie schon erwähnt, benötigt CBQ eine *Menge* Knöpfe. Alle Parameter sind oben erklärt. Die entsprechende HTB-Konfiguration ist viel einfacher.

# 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

Das sind unsere beiden leaf-Klassen. Beachte wie wir die Gewichtung mit der konfigurierten Rate skalieren. Beide Klassen sind selbst unbegrenzt, aber da sie in Klasse 1:1 gebunden sind, wird das Ganze begrenzt. Also wird die Summe der beiden Klassenbandbreiten nie mehr als 6mbit sein. Übrigens müssen die KlassenIDs innerhalb der selben Hauptnummer der Eltern sein.

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

Beide Klassen haben standardmäßig eine FIFO-QDisc. Aber wir ersetzt diese durch ein SFQ-Queue, so wird jeder Datenfluss gleich behandelt.

# 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

Diese Befehle, direkt bei root eingetragen, senden den Traffic zu den richtigen QDiscs.

Beachte, wir benutzen 'tc class add' um innerhalb einer QDisc Klassen zu erzeugen, doch wir verwenden streng genommen 'tc qdisc add' um QDiscs in Klassen hinzu zufügen.

Jetzt fragst du dich vielleicht, was mit dem Traffic passiert, der nicht durch eine der beiden Regeln classifiziert ist. Es scheint, dass in diesem Fall die Daten dann innerhalb von 1:0 verarbeitet werden und nicht limitiert sind.

Sollten SMTP+web zusammen versuchen, den eingestellten Grenzwert von 6Mbit/s zu überschreiten, wird die Bandbreiten nach dem Parameter der Gewichtung unterteilt, 5/8 des Traffics für den Webserver und 3/8 für den Mail-Server.

Bei dieser Konfiguration kannst du auch sagen, dass der Webserver-Traffic immer ein Minimum von 5/8 * 6Mbit = 3,75Mbit bekommt.

9.5.4.5. Andere CBQ-Parameter: split & defmap

Wie schon erwähnt, eine classful QDisc benötigt einen Filteraufruf, um die Klasse eines Pakets zu bestimmen und einzureihen.

Neben dem Aufruf des Filters bietet CBQ andere Optionen, defmap & split. Das ist ziemlich kompliziert zu verstehen, und es ist nicht wichtig. Aber hier ist die einzige bekannte Stelle, um defmap & split richtig zu erklären, ich gebe mein Bestes.

Willst du nur öfter den 'Type of Service Feld' filtern, wird eine spezielle Syntax zur Verfügung gestellt. Immer wenn die CBQ herauszufinden versucht, wo ein Paket in die Warteschlange eingereiht werden soll, wird geprüft ob dieser Knoten ein 'split Knoten' ist. Wenn ja, deutet einer der sub-QDiscs an, dass es alle Pakete mit einer bestimmten konfigurierten Priorität erhält, welche aus dem TOS-Feld oder den durch Anwendungen gesetzten Socket-Optionen abgeleitet werden.

Im defmap Feld kann man Übereinstimmungen der Paketprioritätbits sehen. Mit anderen Worten, dies ist ein kurzer Weg zur Schaffung eines sehr schnellen Filters, wenn nur bestimmte Prioritäten übereinstimmt. Bei einer defmap von ff (hex) wird alles, und bei 0 nichts übereinstimmen. Eine Beispielkonfiguration kann dazu beitragen die Dinge klarer zu sehen:

# 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
Standard CBQ Präambel. Ich habe mich nie an die schier benötigte Menge von Zahlen gewöhnt!

Defmap bezeichnet TC_PRIO Bits, die wie folgt definiert sind:

TC_PRIO..          Num  entspricht TOS
--------------------------------------------------
BESTEFFORT         0    maximale Zuverlässigkeit       
FILLER             1    minimale Kosten 
BULK               2    maximaler Durchsatz (0x8)  
INTERACTIVE_BULK   4                               
INTERACTIVE        6    minimale Verzögerung (0x10)      
CONTROL            7                               

Die TC_PRIO .. Nummer entspricht den Bits von rechts gezählt. Siehe im pfifo_fast-Abschnitt für weitere Details, wie bei TOS-Bits die Prioritäten umgesetzt werden.

Jetzt die Interaktiven und die Bulk-klassen:

# 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

Die 'split qdisc' ist 1:0, hier wird die Auswahl gemacht werden. C0 steht für binär 11000000, 3F für 00111111, so passen diese beiden zusammen auf alles. Stimmt die erste Klasse überein mit Bits 7 & 6 und das steht für 'interaktiven' und 'kontrolleieten' Verkehr. Den Rest stimmt mit der zweiten Klasse überein.

Knoten 1:0 hat jetzt folgende Tabelle:

priority	send to
0		1:3
1		1:3
2		1:3
3		1:3
4		1:3
5		1:3
6		1:2
7		1:2

Als zusätzlichen Spaß, kannst du auch eine 'change mask' benutzen, die genau anzeigt, mit welcher Prioritäte du etwas ändern möchtest. Du musst dies nur verwenden, wenn du 'tc class change' am laufen hast. Um Beispielspielweise 'best effort-traffic' zu 1:2 hinzuzufügen, könnten wir dies ausführen:

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

Die Prioritätenkarte bei 1:0 sieht nun so aus:

priority	send to
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' ist nicht getestet, sah es nur im Sourcecode.

9.5.5. Hierarchischer Token Bucket

Martin Devera (<devik>) hat zu Recht erkannt, dass CBQ sehr komplex ist und nicht für viele typische Situationen optimiert erscheint. Sein hierarchischer Ansatz ist für Setups geeignet, bei denen du eine feste Bandbreite für verschiedene Zwecke teilen möchtest, so dass fü jeden Zweck eine garantierte Bandbreite gewährleistet wird, mit der Angabeoption, wie viel Bandbreite ausgeliehen werden kann.

HTB funktioniert genauso wie CBQ aber ohne Leerlaufzeitberechnungen. Stattdessen ist es ein classful Token Bucket Filter - daher der Name. Es kennt nur wenige Parameter, die auf dieser Seite gut dokumentiert sind.

Selbst wenn deine HTB-Konfiguration komplexer wird, skaliert sie gut. Mit CBQ wird es schon in einfachen Fällen komplex! HTB3 (Weitere Details über HTB-Versionen findest du auf der Homepage) ist seit Kernelversion 2.4.20-pre1 bzw. 2.5.31 offizieller Teil der Kernelsourcen. Eventuell brauchst du noch eine auf HTB3 gepatchte Version von 'tc': HTB-Kernel und Userspace-Teile müssen die gleiche Hauptversion haben sonst arbeitet 'tc' nicht mit HTB zusammen.

Solltest du bereits einen modernen Kernel haben, wovon man mitlerweile ausgehen kann, oder du in der Lage bist den Kernel zu patchen, schau dir unbedingt HTB an.

9.5.5.1. Beispielkonfiguration

Funktionell fast identisch mit der CBQ Beispielkonfiguration:

# 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

Der Autor empfiehlt SFQ unterhalb dieser Klassen:

# 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

Füge Filter mit direktem Traffic in den richtigen Klassen ein:

# 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
Und das war es schon - keine unschönen unerklärliche Zahlen, keine undokumentierten Parameter.

Mit HTB sieht es auf jeden Fall wunderbar aus - beide, 10: und 20:, haben ihre garantierte Bandbreite und noch etwas zum teilen bleibt übrig, sie leihen in einem 5:3-Verhältnis, so wie man es erwarten würde.

Traffic ohne Zuordnung wird auf 30: geleitet. Selbst hat es wenig Bandbreite aber kann sich von den anderen das leihen, was dort übrig geblieben ist. Weil wir SFQ intern entscheiden lassen, bekommen wir Fairness kostenlos dazu!