Quand je l'ai découvert, cela m'a vraiment "soufflé". Linux 2.2 contient toutes les fonctionnalités pour la gestion de la bande passante, de manière comparable à un système dédié de haut niveau.
Linux dépasse même ce que l'ATM et le Frame peuvent fournir.
Les deux unités de base du Contrôle de Trafic sont les filtres et les files d'attente. Les filtres placent le trafic dans des files d'attente, qui recueillent ainsi le trafic et décident ce qu'il faut envoyer en premier, envoyer plus tard, ou éliminer. Il existe plusieurs variantes de filtres et de files d'attente.
Les filtres les plus communs sont fwmark et u32. Le premier vous permet d'utiliser le code Netfilter de Linux pour sélectionner le trafic, et le second vous permet de sélectionner ce trafic à partir de N'IMPORTE QUEL en-tête. La file d'attente la plus connue est la Class Based Queue (File d'attente basée sur des classes). CBQ est une super-file d'attente, qui peut contenir d'autres files d'attente (même d'autres CBQ).
Il n'est peut-être pas évident de voir ce que la mise en file d'attente peut avoir à faire avec la gestion de bande passante, mais ça marche vraiment.
Pour cadre de référence de cette section, j'ai pris l'exemple d'un fournisseur d'accès chez qui j'ai appris les ficelles du métier, pour ne pas le citer : Casema Internet en Hollande. Casema, qui est actuellement un câble-opérateur, a des besoins internet pour ses clients et pour son propre compte. La plupart des ordinateurs de la société ont un accès Internet. En réalité, ils ont beaucoup d'argent à dépenser et n'utilisent pas Linux pour la gestion de la bande passante.
Nous étudierons comment notre fournisseur d'accès aurait pu utiliser Linux pour gérer sa bande passante.
Avec la mise en file d'attente, nous déterminons l'ordre dans lequel les données sont envoyées. Il est important de le comprendre, nous ne pouvons que mettre en forme les données que nous transmettons. Comment ce changement de prioriré détermine-t-il la vitesse de transmission ?
Imaginez une caisse enregistreuse capable de traiter 3 clients par minute. Les personnes souhaitant payer vont attendre en file indienne en bout de queue. C'est une "file d'attente fifo". (NdT : fifo = First In, First Out, premier entré, premier sorti) Supposons maintenant que vous laissiez toujours certaines personnes s'insérer au milieu de la queue, et non en fin. Ces personnes attendront moins de temps et donc pourront faire leurs courses plus rapidement.
Avec la manière dont Internet travaille, nous n'avons pas de contrôle direct de ce que les personnes nous envoient. C'est un peu comme votre boîte aux lettres (physique !) chez vous. Il n'y a pas de façon d'influencer le nombre de lettres que vous recevez, à moins de contacter tout le monde.
Cependant, l'Internet est principalement basé sur TCP/IP qui possède quelques fonctionnalités qui vont pouvoir nous aider. TCP/IP n'a pas d'aptitude à connaître les performances d'un réseau entre deux hôtes. Il commence à envoyer des paquets, de plus en plus rapidement ('slow start') et quand des paquets commencent à se perdre, il ralentit.
C'est comme si vous ne lisiez que la moitié de votre courrier en espérant que vos correspondants arrêteront de vous en envoyer. À la différence du courrier, ça marche sur l'Internet :-)
FIXME: expliquer comment, normalement, les acquittements (ACKs) sont utilisés pour déterminer la vitesse.
[L'Internet] ---<E3, T3, peu importe>--- [Routeur Linux] --- [Bureau+FAI]
eth1 eth0
Maintenant, notre routeur Linux a deux interfaces que je vais appeler eth0 et eth1. Eth1 est connecté à notre routeur qui transmet les paquets venant de ou allant vers notre fibre optique.
Eth0 est connecté à un sous-réseau qui contient à la fois le pare-feu de la société et notre équipement de tête, à travers lequel nos clients peuvent se connecter.
Dans la mesure où nous ne pouvons limiter que ce que nous envoyons, nous avons besoin de deux ensembles de règles séparés, mais très similaires. En modifiant la mise en file d'attente sur eth0, nous déterminons à quelle vitesse les données reçues sont envoyées à nos clients. On détermine ainsi la bande passante descendante disponible vers eux, en résumé, leur "vitesse de téléchargement".
Sur eth1, nous déterminons à quelle vitesse nous envoyons les données sur Internet, à quelle vitesse nos utilisateurs, à la fois ceux de la société et les clients, peuvent émettre les données.
CBQ nous permet de créer plusieurs classes, et même des classes incluses dans d'autres classes. Les divisions les plus larges peuvent être appelées des agences. Au sein de ces classes, se trouveraient des classes comme "traitement par lot" et "traitement interactif".
Par exemple, nous pourrions avoir une connexion Internet de 10 Mbits devant être partagée par nos clients et les besoins de la société. Nous ne devons pas permettre à quelques personnes de la société de monopoliser un gros volume de bande passante, qui devrait être vendue à nos clients.
D'un autre côté, nos clients ne doivent pas pouvoir grignoter le trafic réservé à nos bureaux.
Avant, une manière de résoudre cela était d'utiliser le Frame relay/ATM et de créer des circuits virtuels. Cela fonctionne, mais les réseaux Frame Relay ne sont pas suffisament maillés et ATM est terriblement inefficace dans le transport du trafic IP. Ni l'un ni l'autre ne possède de moyens standardisés pour distribuer le trafic sur différents circuits virtuels (VC).
Cependant, si vous utilisez ATM, Linux peut habilement réaliser des tâches de classification de trafic pour vous. Une autre manière de procéder consiste à commander plusieurs connexions séparées mais ce n'est ni pratique ni élégant et ça ne résoudra pas tous vos problèmes.
CBQ à la rescousse !
Clairement, nous avons ici deux classes principales, "ISP" et "Office". Au départ, on ne se préoccupe pas vraiment de ce que les divisions font de leur bande passante, et donc, nous ne subdiviserons pas plus loin leurs classes.
Nous décidons que les clients devront toujours avoir 8 Mbits de trafic descendant garanti, et nos bureaux 2 Mbits.
La configuration du contrôle de trafic est faite avec l'outil tc
,
du paquet iproute2.
# tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 1000
Bon, beaucoup de chiffres ici. Qu'avons-nous fait ? Nous avons configuré la gestion de la mise en file d'attente (queuing discipline) de eth0. Par "root", nous signifions que cette file d'attente est la racine. Nous lui avons donné la référence (handle) "10:". Nous voulons faire du "CBQ", nous le mentionnons donc sur la ligne de commande. Nous indiquons au noyau qu'il peut allouer 10 Mbits et que la taille moyenne d'un paquet est aux alentours de 1000 octets.
FIXME: Re-vérifier avec Alexey que le calcul de la cellule intégrée est suffisant.
FIXME: Avec un MTU de 1500, la cellule par défaut est calculée de la même façon que dans l'ancien exemple.
FIXME: J'ai vérifié les sources (espaces utilisateur et noyau), on doit pouvoir l'omettre sans problème.
Maintenant, nous devons générer notre classe racine, à partir de laquelle toutes les autres descendront :
# tc class add dev eth0 parent 10:0 classid 10:1 cbq bandwidth 10Mbit rate \
10Mbit allot 1514 weight 1Mbit prio 8 maxburst 20 avpkt 1000
Encore des nombres à gérer - l'implémentation CBQ dans Linux est très générique. Avec "parent 10:0", nous indiquons que cette classe descend de la file d'attente racine générée plus tôt, et référencée par "10:". Avec "classid 10:1", nous baptisons cette nouvelle classe.
Nous ne disons vraiment rien de plus au noyau. Nous générons simplement une classe qui englobe toute la bande passante disponible sur l'interface. Nous avons aussi spécifié que le MTU (plus l'en-tête) est de 1514 octets. Nous appliquons une pondération à cette classe avec un paramètre de réglage de 1 Mbit.
Maintenant, nous créons notre classe ISP :
# tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mbit rate \
8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
Nous allouons 8 Mbits et nous indiquons que cette classe ne doit pas dépasser cette limite par l'ajout du paramètre "bounded". Autrement, elle aurait commencé à emprunter de la bande passante aux autres classes. De plus, cette classe pourra prêter sa bande passante à d'autres classes. Nous en discuterons plus tard.
Pour finir, nous générons la classe "Office" (bureau) :
# tc class add dev eth0 parent 10:1 classid 10:200 cbq bandwidth 10Mbit rate \
2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
Pour rendre les choses un peu plus claires, voici un diagramme qui montre nos classes :
+-------------[10: 10Mbit]----------------------+
|+-------------[10:1 root 10Mbit]--------------+|
|| ||
|| +-[10:100 8Mbit]-+ +--[10:200 2Mbit]-----+ ||
|| | | | | ||
|| | ISP | | Office | ||
|| | | | | ||
|| +----------------+ +---------------------+ ||
|| ||
|+---------------------------------------------+|
+-----------------------------------------------+
Nous avons maintenant indiqué au noyau quelles sont nos classes, mais pas encore comment gérer les files d'attente. Nous le faisons à présent, d'un seul coup pour les deux classes.
# tc qdisc add dev eth0 parent 10:100 sfq quantum 1514b perturb 15
# tc qdisc add dev eth0 parent 10:200 sfq quantum 1514b perturb 15
Dans ce cas, nous installons le gestionnaire de mise en file d'attente Stochastic Fairness Queueing (approx. "statistiquement équitable") ou sfq, qui n'est pas vraiment le plus équitable, mais qui marche bien pour les grandes bandes passantes, sans utiliser trop de temps CPU. Il existe d'autres gestionnaires de file d'attente qui sont meilleurs, mais qui nécessitent plus de temps CPU. Le gestionnaire de mise en file d'attente Token Bucket Filter (filtre à seau de jetons) est souvent utilisé.
Maintenant, la seule chose qui reste à faire est de dire au noyau quels sont les paquets qui appartiennent à une classe. Nous le ferons tout d'abord nativement avec iproute2, mais des applications plus intéressantes sont possibles en combinaison avec netfilter.
# tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip dst \
150.151.23.24 flowid 10:200
# tc filter add dev eth0 parent 10:0 protocol ip prio 25 u32 match ip dst \
150.151.0.0/16 flowid 10:100
Ici, on suppose que notre société (classe Office) est cachée derrière un pare-feu avec l'adresse IP 150.151.23.24 et que toutes nos autres adresses IP devront être considérées comme faisant partie de la classe ISP.
La classificateur u32 est un modèle très simple ; des règles de
classification plus sophistiquées sont possibles lorsque l'on utilise
netfilter pour marquer nos paquets. On peut ensuite utiliser ce
marquage avec tc
.
Maintenant, nous avons divisé équitablement la bande passante descendante, et nous avons besoin de faire la même chose avec le flux montant. Pour abréger, voici toutes les commandes en bloc :
# tc qdisc add dev eth1 root handle 20: cbq bandwidth 10Mbit avpkt 1000
# tc class add dev eth1 parent 20:0 classid 20:1 cbq bandwidth 10Mbit rate \
10Mbit allot 1514 weight 1Mbit prio 8 maxburst 20 avpkt 1000
# tc class add dev eth1 parent 20:1 classid 20:100 cbq bandwidth 10Mbit rate \
8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
# tc class add dev eth1 parent 20:1 classid 20:200 cbq bandwidth 10Mbit rate \
2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
# tc qdisc add dev eth1 parent 20:100 sfq quantum 1514b perturb 15
# tc qdisc add dev eth1 parent 20:200 sfq quantum 1514b perturb 15
# tc filter add dev eth1 parent 20:0 protocol ip prio 100 u32 match ip src \
150.151.23.24 flowid 20:200
# tc filter add dev eth1 parent 20:0 protocol ip prio 25 u32 match ip src \
150.151.0.0/16 flowid 20:100
Dans notre exemple, il se trouve que même si les clients du fournisseur d'accès ne sont pas en majorité connectés (disons 8 heures du matin), nos bureaux n'ont toujours que 2 Mbits, ce qui est un peu du gaspillage.
En enlevant le paramètre "bounded", les classes pourront se prêter de la bande passante les unes aux autres.
Il se peut que des classes ne souhaitent pas prêter leur bande passante à d'autres. Deux fournisseurs d'accès rivaux sur un même lien peuvent ne pas vouloir s'offrir mutuellement de la bande passante pour des prunes. Dans ce cas, vous pouvez ajouter le mot-clé "isolated" à la fin de vos lignes "tc class add".
FIXME: suppositions qui n'ont pas du tout été testées ! Les essayer !
Nous pouvons aller plus loin. Supposons que tous les employés décident de lancer leur client "napster", il est toujours possible que notre base de données de routage dépasse la capacité de bande passante. Pour ce cas de figure, nous créons deux sous-classes, "Human" et "Database".
Notre base de données a toujours besoin de 500 Kbits, il nous reste donc 1,5 Mbits pour la consommation de la classe "Human".
Nous devons donc créer deux nouvelles classes à l'intérieur de la classe Office :
# tc class add dev eth0 parent 10:200 classid 10:250 cbq bandwidth 10Mbit rate \
500Kbit allot 1514 weight 50Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
# tc class add dev eth0 parent 10:200 classid 10:251 cbq bandwidth 10Mbit rate \
1500Kbit allot 1514 weight 150Kbit prio 5 maxburst 20 avpkt 1000 \
bounded
FIXME: Finir cet exemple!
FIXME: document TEQL