LINUX QOS USING HTB

Détails

Le but de ce howto est de mettre en place un mécanisme de qualité de service sur votre passerelle Linux. Cela permettra de classifier les paquets qui traversent votre passerelle et de limiter ou de mettre des priorités à certain des flux. Il y a plusieurs type de classement et queues utilisable sous Linux mais je n'aborde ici que HTB.
Autre point important bien que la qualité de service peut être appliqué sur un trafic sortant ou entrant, la modulation du trafic se fait uniquement sur l'interface sortante, du coup pas forcément évident de le mettre en oeuvre mais il existe un patch pour le kernel Linux qui rajoute des interfaces intermédiaires. Le fonctionnement n'est pas à 100% efficace car la bande passante pour une ligne DSL par exemple sera déjà consommée lorsque notre passerelle sera traversée. La méthode pourra en revanche ralentir et controller les flux sur des connexions avec controle de type TCP, mais n'influera pas les flux de type UDP par exemple. Mais globalement ça marche plutôt bien :)

Versions :

- iproute2-2.6.11-050330 / Custom Build ou Debian Package
- Patch IMQ

QOS

Avant de commencer la mise en place, il est important de connaitre la bande passante exacte dont vous disposez. Dans mon cas, aprés des tests j'obtenais environ 11000 Kbits/s en download, mais pour que la QOS fonctionne correctement avec HTB il faut se laisser une marge de "gestion" qui dans la pratique s'élève environ a 10 ou 15%. Donc pour ma ligne j'ai décidé d'arrondir à 10000 kbits/s, c'est cette valeur qui determinera ma bande passante maximal dans HTB.
Une fois cela fait il vous aussi connaitre votre trafic (web, mail, etc...), savoir la part du gateau que vous voulez attribuer à chaque type de trafic et comment selon les cas la bande passante doit être répartie. C'est à partir de cette réflexion que l'on construira l'arbre HTB. (voir schèma)

Mais passons à la pratique pour mieux appréhender tout ça. Nous utiliserons iptables et Netfilter pour marquer les paquets. Je ne rentrerais pas dans les détails du fonctionnement d'iptables ici mais un exemple de règles que j'utilise devrait suffire. Comme traditionnellement il va falloir vous assurez que votre noyau gère la QOS, HTB, Netfilter et les différentes options dont nous aurons besoin. Pour informations voici les options que j'utilise, elles ne sont pas forcéments toutes indispensables ici mais pour tout vous avouer je suis trop faignant pour faire un tri spécifique pour ce howto :)

#
# IP: Netfilter Configuration
#
CONFIG_IP_NF_CONNTRACK=y
CONFIG_IP_NF_CONNTRACK_MARK=y
CONFIG_IP_NF_FTP=y
CONFIG_IP_NF_TFTP=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP_NF_MATCH_LIMIT=y
CONFIG_IP_NF_MATCH_IPRANGE=y
CONFIG_IP_NF_MATCH_MAC=y
CONFIG_IP_NF_MATCH_PKTTYPE=y
CONFIG_IP_NF_MATCH_MARK=y
CONFIG_IP_NF_MATCH_MULTIPORT=y
CONFIG_IP_NF_MATCH_HELPER=y
CONFIG_IP_NF_MATCH_STATE=y
CONFIG_IP_NF_MATCH_CONNTRACK=y
CONFIG_IP_NF_MATCH_CONNMARK=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_TARGET_REJECT=y
CONFIG_IP_NF_TARGET_IMQ=y
CONFIG_IP_NF_TARGET_LOG=y
CONFIG_IP_NF_TARGET_ULOG=y
CONFIG_IP_NF_NAT=y
CONFIG_IP_NF_NAT_NEEDED=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_IP_NF_TARGET_REDIRECT=y
CONFIG_IP_NF_TARGET_NETMAP=y
CONFIG_IP_NF_NAT_FTP=y
CONFIG_IP_NF_NAT_TFTP=y
CONFIG_IP_NF_MANGLE=y
CONFIG_IP_NF_TARGET_MARK=y
CONFIG_IP_NF_TARGET_CLASSIFY=y
CONFIG_IP_NF_TARGET_CONNMARK=y
 
#
# QoS and/or fair queueing
#
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_CLK_JIFFIES=y

#
# Queueing/Scheduling
#
CONFIG_NET_SCH_HTB=y
CONFIG_NET_SCH_SFQ=y

#
# Classification
#
CONFIG_NET_CLS=y
CONFIG_NET_CLS_TCINDEX=y
CONFIG_NET_CLS_ROUTE4=y
CONFIG_NET_CLS_ROUTE=y
CONFIG_NET_CLS_FW=y
CONFIG_NET_ESTIMATOR=y

Vous aurez aussi besoin si vous voulez controller le trafic entrant du patch IMQ qui crée des interfaces virtuelles que vous alimenterez via des règles Netfilter, il sert en plus par exemple si vous avez plusieurs sous réseaux internes possedant chacun son interface (voir la page pour la mise en oeuvre). Vous pouvez ainsi regrouper le trafic entrant dans l'imq pour faire de la qos groupé pour toutes les interfaces sortantes sur vos LAN.
Pour faciliter les modifications ultérieures il est préférable de mettre toutes les informations (interfaces sur lesquelles on va appliquer la qos, identifiants de classes...)dans des variables auxquelles nous feront référence par la suite. Voilà à quoi peut ressembler un tel fichier :

SCRIPTS=/etc/network/scripts
IPTABLES=/usr/local/bin/iptables
IP=/usr/local/sbin/ip
TC=/usr/local/sbin/tc
 
IF_QOSIN=imq1
 
#HTB CLASS
CLASSPROD=10
CLASSPOSTPROD=20
CLASSHIGHPRIO=30
 
HIGHPRIO=0
MAIL=1
HTTP=2
FTP=3
P2P=4

Bien entendu les informations ci-dessus sont spécifiques à mon systeme, à vous de les adapter à vos besoins. On peut maintenant commencer à écrire notre script de gestion de la qos. On declare d'abord que l'on desire une queue différente sur l'interface où le trafic doit être modulé, on specifie aussi que tout ce qui ne sera pas classifié ira dans la classe par défaut d'id 1000. On crée ensuite une classe racine avec notre valeur de bande passante à 10000 kbits/s :

. /etc/network/scripts/common
 
$TC qdisc add dev $IF_QOSIN root handle 1:0 htb default 1000
$TC class add dev $IF_QOSIN parent 1:0 classid 1:1 htb rate 10000kbit

On continue ensuite par déclarer notre classe Prod et ses filles. On rajoute donc une classe d'id 1:10 avec une bande passante normale de 3000 kbits/s et qui peut monter jusqu'au maximum (10000 kbits/s) si personne d'autre ne l'utilise (directive ceil).
Puis vient le tour des sous classes correspondantes à nos type de flux qui elles sont des terminaisons. Nous avons le mail, le ftp et le web, chacun peut avoir 1000 kbits/s et jusqu'a la totalité de la bande passante si elle est disponible. Je leur attribue aussi une priorité de 1 à toute (directive prio).
Enfin pour plus d'égalité je change le type de queue de ces terminaisons en SFQ au lieu de la pfifo_fast classique de Linux. Quelques explications, dans ma classe mails par exemple, il peut y avoir une multitude de connections d'utilisateurs différents, SFQ va assurer une répartition "équitable" de la bande passante de cette classe sur toutes les connections de la queue.

$TC class add dev $IF_QOSIN parent 1:1 classid 1:$CLASSPROD htb rate 3000kbit ceil 10000kbit
 
$TC class add dev $IF_QOSIN parent 1:$CLASSPROD classid 1:$CLASSPROD$MAIL htb rate 1000kbit \
ceil 10000kbit prio 1
$TC qdisc add dev $IF_QOSIN parent 1:$CLASSPROD$MAIL handle $CLASSPROD$MAIL: sfq perturb 10
 
$TC class add dev $IF_QOSIN parent 1:$CLASSPROD classid 1:$CLASSPROD$HTTP htb rate 1000kbit \
ceil 10000kbit prio 1
$TC qdisc add dev $IF_QOSIN parent 1:$CLASSPROD$HTTP handle $CLASSPROD$HTTP: sfq perturb 10
 
$TC class add dev $IF_QOSIN parent 1:$CLASSPROD classid 1:$CLASSPROD$FTP htb rate 1000kbit \ 
ceil 10000kbit prio 1
$TC qdisc add dev $IF_QOSIN parent 1:$CLASSPROD$FTP handle $CLASSPROD$FTP: sfq perturb 10

Les instructions qui suivent sont des filtres qui permettent de diriger les paquets dans les bonnes classes selon leur marquage par Netfilter. Par exemple pour le mail si le paquet est marqué 101 alors il sera traité par la classe d'id 1:101 et ainsi de suite :

$TC filter add dev $IF_QOSIN parent 1:0 protocol ip handle $CLASSPROD$MAIL fw flowid 1:$CLASSPROD$MAIL
$TC filter add dev $IF_QOSIN parent 1:0 protocol ip handle $CLASSPROD$HTTP fw flowid 1:$CLASSPROD$HTTP
$TC filter add dev $IF_QOSIN parent 1:0 protocol ip handle $CLASSPROD$FTP fw flowid 1:$CLASSPROD$FTP

Vous avez compris le principe on refait donc la même chose pour la classe PostProd et ses filles.
Passons à la classe haute priorité qui est un peu différente, déjà on lui attribue une classe spécifique au même niveau que Prod et Postprod pour qu'elle puisse emprunter directement de la bande passante à la classe racine et on positionne en plus à chaque fille une priorité de 0.
Tous les paquets qui arriveront dans les classes filles de haute priorité seront donc envoyés en premiers, il faut donc faire attention aux types de flux que l'on va classifier de la sorte car cela peut interdire l'accés à la bande passante des autres flux. Mais c'est plutôt pratique pour les vidéos conférences du patron par exemple...

$TC class add dev $IF_QOSIN parent 1:1 classid 1:$CLASSHIGHPRIO htb rate 3000kbit ceil 10000kbit
 
$TC class add dev $IF_QOSIN parent 1:$CLASSHIGHPRIO classid 1:$CLASSPROD$HIGHPRIO htb rate 1500kbit \
ceil 10000kbit prio 0
$TC qdisc add dev $IF_QOSIN parent 1:$CLASSPROD$HIGHPRIO handle $CLASSPROD$HIGHPRIO: sfq perturb 10
 
$TC class add dev $IF_QOSIN parent 1:$CLASSHIGHPRIO classid 1:$CLASSPOSTPROD$HIGHPRIO htb rate 1500kbit \
ceil 10000kbit prio 0
$TC qdisc add dev $IF_QOSIN parent 1:$CLASSPOSTPROD$HIGHPRIO handle $CLASSPOSTPROD$HIGHPRIO: sfq perturb 10
 
$TC filter add dev $IF_QOSIN parent 1:0 protocol ip handle $CLASSPROD$HIGHPRIO fw flowid 1:$CLASSPROD$HIGHPRIO
$TC filter add dev $IF_QOSIN parent 1:0 protocol ip handle $CLASSPOSTPROD$HIGHPRIO \ 
fw flowid 1:$CLASSPOSTPROD$HIGHPRIO

Enfin on declare la classe par défaut d'id 1000 avec une priorité 2 (la plus basse dans notre exemple). Ainsi tout ce qui n'est pas marqué par iptables se retrouvera dans cette classe de basse priorité et de bande passante limitée.

$TC class add dev $IF_QOSIN_FREE parent 1:1 classid 1:1000 htb rate 1000kbit ceil 10000kbit prio 2
$TC qdisc add dev $IF_QOSIN_FREE parent 1:1000 handle 1000: sfq perturb 10

Notre arbre est donc fini, il ne reste maintenant plus qu'a classifier le trafic avec iptables, comme promis voici un petit exemple qui devrait se suffire à lui même...

#Creation d'une chaine qui recoit tout le trafic de l'exterieur vers la prod
$IPTABLES -t mangle -N QOS-OUT-PROD
$IPTABLES -t mangle -A FORWARD -i $IF_OUT -d 192.168.0.0/24 -j QOS-OUT-PROD
 
#ICMP
$IPTABLES -t mangle -A QOS-OUT-PROD -p icmp -j MARK --set-mark $CLASSPROD$HIGHPRIO
 
#DNS
$IPTABLES -t mangle -A QOS-OUT-PROD -p udp --sport 53 -j MARK --set-mark $CLASSPROD$HIGHPRIO 
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp --sport 53 -j MARK --set-mark $CLASSPROD$HIGHPRIO
 
#MESSENGER
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp -m multiport --sports 1863,5190,5222 -j MARK \
--set-mark $CLASSPROD$HIGHPRIO
 
#MAIL POP3,IMAP & SMTP
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp -m multiport --sports 110,143,25 -j MARK --set-mark $CLASSPROD$MAIL
 
#HTTP,HTTPS
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp -m multiport --sports 80,443 -j MARK --set-mark $CLASSPROD$HTTP 
 
#FTP 
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp --sport 21 -j MARK --set-mark $CLASSPROD$FTP 
$IPTABLES -t mangle -A QOS-OUT-PROD -p tcp -m helper --helper ftp -j MARK --set-mark $CLASSPROD$FTP 
 
$IPTABLES -t mangle -A QOS-OUT-PROD -j ACCEPT 

Voilà tout est prêt, il n'y à plus qu'a tester tout ça, vous pouvez utilisez la commande tc pour afficher ce qui se passe dans les classes et les queues. Le mieux est de se faire quelques graphiques d'utilisation à partir des classes que l'on a mis en place, mais cela fera surement l'objet d'un autre howto. Une dernière info, si vous voulez classifier le peer to peer par exemple aller voir du côté de ipp2P il est remis à jour assez régulièrement et fonctionne trés bien.