17.5. QoS - Bandbreitenregelung

Es gibt eine Vielzahl Spezialdistributionen für Linux, die für ein Unternehmen eine Fertiglösung quasi sind, CD einlegen, warten, Firewall konfigurieren über Web-Interface. So z.B. IPCop Firewall Was den meisten Firewalls fehlt, ist eine vernünftige Bandbreiten - Regelung, QoS (Quality of Service) genannt. Dasselbe gilt auch für einfache Linux Distributionen, die als DSL - Router aufgebaut werden. Immer wieder kommt es aufgrund zu massiven FTP - Downloads, oder bei VoIP über SIPGATE oder SKYPE z.B. zu "Verklemmungen". Dasselbe gilt auch für Webserver, die so massiv mit Mail-Traffic zu tun haben, daß keine Bandbreite mehr für das Ausliefern von Webseiten übrig ist. Das folgende Tutorial behandelt ausführlich diese beiden Fälle, und bietet Skripte an, welche sich auf jedem Linux - Rechner nachrüsten lassen.

Linux enthält seit Kernel 2.4.20 eine Bandbreitenregelung nach den Hierarchial Token Buckets (HTB). Im Gegensatz zu allen anderen Bandbreiten - Regelungen ist das HTB Verfahren sehr einfach zu verstehen und anzuwenden. Es basiert auf einem Baum, der die Bandbreiten auf die verschiedenen Protokolle (SMTP, WWW, TELNET, SSH, ...) aufteilt. Hierzu wird jedem Protokoll eine bestimmte Bandbreite garantiert. Wird diese nicht genutzt, so dürfen andere Protokolle, entsprechend der Aufteilung, proportional mehr Bandbreite nutzen. Es ist zwar ein starres Verfahren, jedoch sehr übersichtlich. Im Gegensatz hierzu sind die anderen Verfahren, z.B. CBQ (Class Based Queueing) zwar leistungsfähiger, jedoch viel schwieriger zu implementieren und zu überwachen. Angesichts der möglichen Wechselwirkungen der verschiedensten statistischen Verfahren (CBQ, CSZ, RED, GRED, SFQ, PRIO, TBF, BFIFO, PIFO, DSMARK, TEQL, HTB, WRR), die der Linux Kernel anbietet, ist dies eine Wissenschaft für sich. Tatsächlich wurden darüber schon Doktorarbeiten geschrieben, nach welchen die Linux Kernel - Module entworfen wurden. Diese sind jedoch nicht nur zur Bandbreitenregelung eines Interfaces gedacht, sondern in Verbindung mit dem Paket iproute, iproute2 zum Umschalten der Netzwerkrouten in Abhängigkeit vom Traffic, was z.B. für Ausfallrouten, für die Paketumsetzung zwischen Ethernet und ATM geeignet ist.

Daher zunächst einmal ein einfaches Beispiel mit HTB. Hierzu müssen die Toolkits tc und iproute2, welche die Bedienungswerkzeuge für die Kernel - Module darstellen, auf dem System vorhanden sein. Nun wird die Bandbreiten - Regelung (queuing discipline) aktiviert:

 
tc qdisc add dev eth0 root handle 1:0 htb default 10
Hier wird für dem Interface eth0 ein root handle (Bezeichner) mit der Ziffer 1:0 (oft auch nur als 1: geschrieben) zugeordnet, auf welches im folgenden sich die (Traffic) - Klassen dann beziehen. Die root - Klasse aktiviert hierbei die Bandbreitenregelung nur. Löscht man diese, z.B. mit tc qdisc del dev eth0 root, so ist die Bandbreitenregelung wieder deaktiviert. htb gibt an, welches statistische Messverfahren zu verwenden ist (Kernel Modul), und default 10 gibt die Klasse an, in welche die Pakete umgeleitet werden, auf die die Bandbreitenreservierung nicht zutrifft, wie z.B. alle anderen Protokolle, die nicht explizit definiert wurden. So kann man z.B. Traffic für bestimmte Protokolle über andere Netzwerke übertragen lassen.

Nun müssen dem root - Handle Traffic-Klassen zugeordnet werden, beginnend mit der parent - Klasse 1:0, die die Wurzel des Baumes der weiteren Traffic - Klassen darstellt:

tc class add dev eth0 parent 1:0 classid 1:1 htb rate 100mbit ceil 100 mbit 
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 80mbit ceil 100mbit 
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10mbit ceil 100mbit
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 10mbit ceil 100mbit
Hier nun werden zuerst dem qdisc - root handle 1:0 Parent - Klassen zugeordnet, welche dann z.B. auch wieder ein parent 1:0 - Handle enthalten, welches sich auf das root handle bezieht.
                     1:   root qdisc
                      |
                     1:1    
                   /  |  \
                  /   |   \
                 /    |    \
                 /    |    \
              1:10  1:11  1:12   
               |      |     | 
               |     11:    |    
               |            | 
               10:         12:   qdisc
              /   \       /   \
           10:1  10:2   12:1  12:2   
Es ist möglich, weitere qdisc root handles an z.B. der Klasse 1:12 anzufügen, um eine bestimmte Bandbreite, z.B. aus einem anderen Subnetz stammend, nochmals nach Protokollen, wie SMTP, FTP, HTTP zu unterteilen, z.B. auch mit SFQ:
tc qdisc add dev eth0 parent 1:10 handle 12: sfq perturb 10
tc qdisc add dev eth0 parent 1:12 handle 12: sfq perturb 10
Die "Marker" (10,11,12) ist die Verbindung zum Linux Paketfilter der aus dem Datenstrom Pakete identifiziert und für QoS - Klassen markiert. Dies geschieht entweder durch das iptables - Kommando, oder mit ip:
iptables -A POSTROUTING -t mangle -o eth0 -p tcp --sport 80 -j MARK --set-mark 11
Hierbei werden die Pakete, die vom Port 80 ausgehen (--sport 80) markiert (-j MARK), und für die Bandbreitenklasse 10 MBit reserviert (--set-mark 11).

        --------------                 -----------
        | PREROUTING |     Routing     |   QoS   |
 ppp0-->| Markierung |---------------> |         |-->eth0, lokale Rechner
        |  ipchains  |  Netzwerkmasken | Klassen |
        --------------                 -----------
Die eigentliche Zuordnung der markierten Paketen zu den Klassen erfolgt erst mit noch einem weiteren Kommando:
tc filter add dev eth0 parent 1: prio 0 protocol ip handle 11 fw flowid 1:11
Hier erst werden die markierten Pakete aus dem Filter - Modul an die Bandbreitenregelung übergeben, bzw zugeordnet, wobei man noch verschiedene Prioritäten angeben kann. Da man ja mehrere Protokolle filtern lassen kann (IP, IPX, IPSec, ...), und einer Traffic - Klasse zuordnen können muß, ist dies notwendig. In dem Beispiel wird also das Protokoll ip mit dem Handle 11 der Klasse 1:11 zugeordnet. Nicht markierte Pakete landen dann automatisch in der Klasse 1:11 gebunden, an welche dann wieder zwei Unterklassen gebunden werden. Prinzipiell könnte man wohl den Traffic Port 80 an die Klasse 1:1 binden, was jedoch keinen Sinn macht. Man benötigt mindestens eine parent Klasse (1:1), die in zwei Unterklassen (1:10, 1:11) aufgeteilt ist, eine, für die iptables die Pakete aus dem Datenstrom herausfischt (1:11), und eine, die für den Rest der Pakete zuständig ist (1:10).

rate legt fest, wie die Bandbreite im Verhältnis aufgeteilt werden soll (90% zu 10%), wenn Vollast anliegt (100mbit). ceil definiert ein oberes, maximales Limit, welches genutzt werden darf, wenn kein anderer Traffic vorhanden ist.

Einen Überblick über die im Kernel aktivierten qdisc und ihre Klassen erhält man mit den verschiedenen tc - Kommandos:

tc qdisc show dev eth0
tc class show dev eth0
tc filter show dev eth0

Fassen wir zusammen: Das Kommando iptables... verwendet die Tabelle mangle, um eine bestimmte Klasse von Traffic zu filtern, dann zu markieren, und an die bereits aktivierte Bandbreitenregelung zu übergeben. Zuerst müssen die Klassen definiert werden, dann erst darf der gefilterte Traffic zugeordnet werden. Dem ausgehenden Traffic auf Port 80 des Webservers wird ein Marker 11 der Bandbreitenregelung zugeordnet, welche dafür sorgt, daß der Webserver auch dann, wenn z.B. SMTP oder FTP - Traffic den Server hoch belastet, mit mindestens 10mbit noch Webseiten ausliefert. Hier nun ein mit script /QoS-Protokoll mitgeschnittenes Beispiel, welches im Wechselspiel die Kommandos und das Ergebnis zeigt, nämlich das, was dann im Kernel angekommen ist:

[root]# tc qdisc del dev eth0 root
[root]# tc class show dev eth0
[root]# tc qdisc show dev eth0
[root]# tc qdisc add dev eth0 root handle 1:0 htb default 10
[root]# tc qdisc show dev eth0
qdisc htb 1: r2q 10 default 10 direct_packets_stat 0
[root]# tc qdisc del dev eth0 root
[root]# tc qdisc show dev eth0
[root]# tc qdisc show dev eth0
[root]# tc qdisc add dev eth0 root handle 1:0 htb default 10
[root]# tc qdisc show dev eth0
qdisc htb 1: r2q 10 default 10 direct_packets_stat 0
[root]# tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbit
[root]# tc qdisc show dev eth0
qdisc htb 1: r2q 10 default 10 direct_packets_stat 13
[root]# tc class show dev eth0
class htb 1:1 root prio 0 rate 100Mbit ceil 100Mbit burst 132644b cburst
132644b
[root]# tc class add dev eth0 parent 1:1 classid 1:10 htb
rate 90mbit ceil 100mbit
[root]# tc class add dev eth0 parent 1:1 classid 1:11 htb
rate 10mbit ceil 100mbit
[root]# tc class show dev eth0
class htb 1:11 parent 1:1 prio 0 rate 10Mbit ceil 100Mbit burst 14704b
cburst 132644b
class htb 1:1 root rate 100Mbit ceil 100Mbit burst 132644b cburst 132644b
class htb 1:10 parent 1:1 prio 0 rate 90Mbit ceil 100Mbit burst 119545b
cburst 132644b
[root]# iptables -A POSTROUTING -t mangle -o eth0 -p tcp --sport 80 -j MARK --set-mark 11
[root]# iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MARK       tcp  --  anywhere             anywhere           tcp spt:http MARK set 0xb
[root]# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
[root]# tc filter add dev eth0 parent 1: prio 0 protocol ip handle 11 fw flowid 1:11
[root]# tc filter show dev eth0
filter parent 1: protocol ip pref 49152 fw
filter parent 1: protocol ip pref 49152 fw handle 0xb classid 1:11
Und wenn man alles wieder löschen will, kann man das mit folgenden Kommandos tun:
[root]# tc qdisc del dev eth0 root
[root]# tc filter show dev eth0
[root]# tc class show dev eth0
[root]# tc qdisc show dev eth0
[root]#
Und da noch eine Filterregel existiert, die auch noch gelöscht werden muß:
[root]# iptables -t mangle -F
[root]# iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
[root]#

Ein weiteres Beispiel zeigt, wie man in einem Unternehmen, welches via Linux und DSL am Internet angeschlossen ist, verhindert, daß FTP Downloads die Bandbreite schlucken, sodaß es zu keinen Behinderungen kommt:

## Löschen aller Klassen für ppp0 und der Filterregeln
tc qdisc del dev ppp0 root
iptables -t mangle -F


## Wir aktivieren die Queueing DISCipline für ppp0 Device, die 
## auf Klasse 1, Handle 12 (1:12) default-mäßig zeigt

tc qdisc add dev ppp0 root handle 1:0 htb default 12 

## Nun richten wir Geschwindigkeits - Klassen ein: Zunächst die oberste, parent 
tc class add dev ppp0 parent 1: classid 1:1 htb rate 768kbit ceil 768kbit 

## Klasse fuer kleine Pakete bis 64 Bytes (SYN/ACK, ACK) mit hoher Priorität
tc class add dev ppp0 parent 1:1 classid 1:10 htb rate 68kbit ceil 100kbit prio 0 

## Klasse fuer VPN/SSH 
tc class add dev ppp0 parent 1:1 classid 1:11 htb rate 100kbit ceil 200kbit prio 1 

## Klasse fuer normalen Traffic 
tc class add dev ppp0 parent 1:1 classid 1:12 htb rate 300kbit ceil 700kbit prio 2 

## Klasse fuer Bulk 
tc class add dev ppp0 parent 1:1 classid 1:13 htb rate 300kbit ceil 700kbit prio 3 

## Nun der Filter für SYN/ACK, ACK Pakete 
iptables -A POSTROUTING -t mangle -o ppp0 -p tcp -m length --length :64 -j MARK --set-mark 10 

## Für VPN/IPsec 
iptables -A POSTROUTING -t mangle -o ppp0 -p 50 -j MARK --set-mark 11 

## Für SSH 
iptables -A POSTROUTING -t mangle -o ppp0 -p tcp --dport 22 -j MARK --set-mark 11 

## Für ausgehenden SMTP - Traffic 
iptables -A POSTROUTING -t mangle -o ppp0 -p tcp --dport 25 -j MARK --set-mark 13 

## Für eingehenden SMTP - Traffic
iptables -A PREROUTING -t mangle -i ppp0 -p tcp --sport 25 -j MARK --set-mark 13

## Und nun müssen die Filterregeln an die Klassen gebunden werden
  
tc filter add dev ppp0 parent 1:0 prio 0 protocol ip handle 10 fw flowid 1:10 
tc filter add dev ppp0 parent 1:0 prio 0 protocol ip handle 11 fw flowid 1:11 
tc filter add dev ppp0 parent 1:0 prio 0 protocol ip handle 13 fw flowid 1:13

## Und nun die Anzeige der Kernel - Parameter

tc qdisc show dev ppp0
tc class show dev ppp0
tc filter show dev ppp0
 
Die Ausgabe dieses Skriptes sollte folgendes zeigen:
qdisc htb 1: r2q 10 default 12 direct_packets_stat 0
class htb 1:11 parent 1:1 prio 1 rate 100Kbit ceil 200Kbit burst 1728b
cburst 1856b
class htb 1:1 root rate 768Kbit ceil 768Kbit burst 2582b cburst 2582b
class htb 1:10 parent 1:1 prio 0 rate 68Kbit ceil 100Kbit burst 1686b cburst
1728b
class htb 1:13 parent 1:1 prio 3 rate 300Kbit ceil 700Kbit burst 1983b
cburst 2495b
class htb 1:12 parent 1:1 prio 2 rate 300Kbit ceil 700Kbit burst 1983b
cburst 2495b
filter parent 1: protocol ip pref 49151 fw
filter parent 1: protocol ip pref 49151 fw handle 0xd classid 1:13
filter parent 1: protocol ip pref 49151 fw
filter parent 1: protocol ip pref 49151 fw handle 0xb classid 1:11
filter parent 1: protocol ip pref 49152 fw
filter parent 1: protocol ip pref 49152 fw handle 0xa classid 1:10
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MARK       tcp  --  anywhere             anywhere           length 0:64 MARK
set 0xa
MARK       ipv6-crypt--  anywhere             anywhere           MARK set
0xb
MARK       tcp  --  anywhere             anywhere           tcp dpt:ssh MARK
set 0xb
MARK       tcp  --  anywhere             anywhere           tcp dpt:smtp
MARK set 0xd
Wer detaillierte Statistiken über den Traffic im Linux Kernel benötigt, kann mit tc -s class ls dev ppp0 sich diese ausgeben lassen:
[root]# tc -s class ls dev ppp0
class htb 1:11 parent 1:1 prio 1 rate 100Kbit ceil 200Kbit burst 1728b
cburst 1856b
 Sent 100 bytes 1 pkts (dropped 0, overlimits 0)
 lended: 1 borrowed: 0 giants: 0
 tokens: 104448 ctokens: 56320

class htb 1:1 root rate 768Kbit ceil 768Kbit burst 2582b cburst 2582b
 Sent 179602 bytes 2358 pkts (dropped 0, overlimits 0)
 rate 4bps
 lended: 0 borrowed: 0 giants: 0
 tokens: 21192 ctokens: 21192

class htb 1:10 parent 1:1 prio 0 rate 68Kbit ceil 100Kbit burst 1686b cburst
1728b
 Sent 110724 bytes 2163 pkts (dropped 0, overlimits 0)
 rate 3bps
 lended: 2163 borrowed: 0 giants: 0
 tokens: 155011 ctokens: 108032

class htb 1:13 parent 1:1 prio 3 rate 300Kbit ceil 700Kbit burst 1983b
cburst 2495b
 Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
 lended: 0 borrowed: 0 giants: 0
 tokens: 42324 ctokens: 22820

class htb 1:12 parent 1:1 prio 2 rate 300Kbit ceil 700Kbit burst 1983b
cburst 2495b
 Sent 68778 bytes 194 pkts (dropped 0, overlimits 0)
 lended: 194 borrowed: 0 giants: 0
 tokens: 40960 ctokens: 22236
Abschließend bleibt noch zu sagen, daß mit Hilfe dieser Skripte jede beliebige Firewall nachgerüstet werden kann. Die Firewall - Regeln befinden sich in der Tabelle filter, wo sie mit den Kommandos iptables -t nat -L; iptables -t filter -L angeschaut werden können. Es ist möglich, je nach Distribution, daß beim Verändern der Filterregeln in der Firewall die Regeln in der Tabelle mangle mit verändert. In diesem Falle kommentiert man zu Beginn des Skriptes die Zeile iptables -t mangle -F einfach aus. Und damit diese QoS - Regeln reboot - fest sind, kann man diese entweder komplett in die Datei /etc/rc.local kopieren, oder aus /etc/rc.local ein weiteres Skript aufrufen lassen, welches diese Regeln enthält.

Weitere Dokumentation findet sich unter www.lartc.org und HTB Anleitung.