HTB (Hierarchical Token Bucket)
L'HTB è più comprensibile, intuitivo e veloce del metodo di accodamento CBQ. Entrambi i metodi CBQ e HTB aiutano nel controllo della banda in uscita su di un link. Entrambi consentono l'uso di un unico link fisico simulando dei link virtuali più lenti in cui inviare il traffico dati classificato. In entrambi i casi bisogna specificare come fare questa suddivisione e quali pacchetti metterci dentro. Solo che la HTB lo fa in maniera più intuitiva.
Nota : kbps significa kilobytes per secondo e kbit significa kilobits
Condivisione della connessione
Problema: Abbiamo 2 clineti, A e B, entrambi connessi ad internet via eth0. Noi vogliamo allocare 60 kbps a B e 40 kbps ad A. Inoltre vogliamo suddividere la larghezza di banda di A in modo che 30kbps siano per il traffico WWW e 10kbps per tutto il resto. La banda inutilizzata da una classe può essere usata dalle altre che ne hanno bisogno (in proporzione alle loro bande impostate in fase di configurazione).
L'HTB assicura che il servizio riservato per ogni classe di traffico sia garantito per un valore minimo. Se il traffico fosse non impegnasse tutta la banda assegnatagli, la banda rimansta (in eccesso) è distribuita alle altre classi che en fanno richiesta.

I differenti tipi di traffico nella HTB sono rappresentati da classi, vedi il graficoo di sopra.
Vediamo che comandi usare:
tc qdisc add dev eth0 root handle 1: htb default 12
Questo comando attacca all'interfacia eth0 un metodo di accodamento HTB assegnandogli come identificativo 1: . Questo è solo un nome con il quale ci riferiremo più tardi a questo nodo. Il "default 12" significa che qualsiasi traffico non classificato verrà assegnato alla classe 1:12.
Nota: In generale (non solo per l' HTB ma anche per tutte le qdisc e le classi nel controllo del traffico), gli handles o identificativi sono scritti nella forma x:y dove x è un intero che identifica la qdisc e y è un intero che identifica una classe discendente dalla qdisc. L'identificativo per una qdisc deve avere zero come valore in y, mentre per una classe deve essere diverso da zero. Il valore "1:" scritto sopra è come se fosse "1:0" dove però lo zero è sottinteso.
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps
La prima linea crea la classe "root", 1:1 discendente dalla qdisc "root" 1: .
Una classe è di tipo "root" se è discendente dalla qdisc htb. Una classe root, come altre classi sotto una HTB, consente ai propri "figli" di prendere in prestito banda tra loro, ma una classe root non può scambiare banda con un altra classe root.
Ad esempio, noi potremo avere anche altre classi collegate alla qdisc root, ma leccesso di banda di una non potrebbe essere sfruttata dalle altre. Nel caso noi volessimo che tra le classi ci fosse uno scambio di banda, allora dovremo creare un'altra classe superiore, che comprenda quelle tre, la quale diventerebbe la classe di root, mentre le altre , diventate classi figlie potrebbero prestarsi banda.
Questo è quello che abbiamo definito con gli ultimi tre comandi, cioè una classe root e tre classi figlie(1:10,1:20 e 1:30) ad essa collegate.
Il parametro ceil è spiegato dopo.
Nota: Qualcuno potrebbe chiedersi come mai in tutti i comandi viene ripetuta la parte "dev eth0" dato che è già stato fornito l'identificativo del nodo genitore?
La ragione è semplice, gli identificativi sono unici all'interno di una interfaccia, per esempio eth0 ed eth1 potrebbero avere entrambe al loro interno una classe chiamata 1:1 e se non specifichiamo a quale interfaccia ci stiamo riferendo la corrispondenza tra identificativo e nodo non sarebbe biunivoca.
Se all'utente A corrispondesse l'IP 1.2.3.4 e volessimo stabilire, secondo le specifiche, quali pacchetti inviare nelle classi, allora useremo i seguenti comandi:
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 1.2.3.4 flowid 1:11
Questo non è specifico per questo tipo di accodamento, ma vale per la classificazione del traffico in generale, quindi per approfondimenti rimandiamo alla sezione relativa ai filtri.
In pratica abbiamo collegato alla qdisc root due filtri, i quali cercando una corrispondenza tra i pacchetti e le regole definite al loro interno, instradano il traffico nelle varie classi.
In teoria ce ne sarebbe un terzo, ma è il famoso comportamento di default che abbiamo definito alla creazione della qdisc, infatti, tutti i pacchetti che non corrispondono a nessun filtro (qualsiasi pacchetto che non ha come sorgente 1.2.3.4) vengono di default inviati alla classe figlia 1:12.
Eventualmente possiamo anche attaccare alle classi definite in precedenza delle qdisc diverse da quella di default (pfifo):
tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 5
tc qdisc add dev eth0 parent 1:11 handle 30: pfifo limit 5
tc qdisc add dev eth0 parent 1:12 handle 40: sfq perturb 10
questo è tutto ciò di cui abbiamo bisogno per usare una HTB.
Vediamo cosa accade quando cerchiamo di inviare pacchetti in ogni classe ad una velocià di 90kbps per ognuna e dopo interrompere questo flusso una classe per volta:
- Quando su tutte viene riversato un flusso di 90kbps, molto maggiore del loro rate, ognuna regolarizza il flusso a seconda della velocità che gli era stata imposta: la classe: 1:10 a 30kbps, la 1:11 a 10kbps e la 1:12 a 60kbps.
- Quando la classe A 1:10 smette di riceve traffico WWW, si libera la sua banda di 30kbps, la quale viene ridistribuita con un rapporto 1/6 (10kbps/60kbps) tra le altre 2, quindi la B 1:12 avrà ora una velocità di 30*5/6+60=85kbps e l'altra 30/6+10=15kbps.
- Se ora fermassimo questi 2 flussi (1:11 e 1:12) e facessimo riprendere il traffico WWW della classe A, allora la banda disponibile per quest'ultima sarebbe la sua velocità base 30kbps più le altre cioè 10kbps e 60kbps, cioè avrebbe un rate di 100kbps, che non viene raggiunto in uscita perché il traffico in ingresso e di solo 90kbps.
Vediamo ora di parlare del concetto di quantums. Quando più classi vogliono prestarsi banda ad ognuna viene dato solo un certo numero di bytes prima che venga servita un'altra classe in competizione. Questo numero è chiamato quantum. Quindi quando alcune classi entrano in competizione per avere la banda di un altra, questa gli viene assegnata in proporzione al loro quantum. E' importante capire che per operazioni "precise" è necessario che questo quantum sia il più piccolo possibile e allo stesso tempo maggiore del MTU.
Normalmente non si ha necessità di impostare manualmente questo valore dato che la HTB assegna ad ogni classe dei valori precalcolati. Infatti essa calcola che il quantum di una classe (quando viene creata o modificata) in relazione alla sua velocità (rate) e ad altri parametri.
condivisione gerarchica
Ritornando all'esempio di prima A e B sono due clienti separati, il primo che paga per 40k e il secondo per 60k. Quando la banda di A sul suo traffico WWW si libera, probabilmente A vorrebbe che fosse dedicata al suo traffico non classificato, piuttosto che andare a B.

Il problema è risolto con una gerarchia di classi come mostrato nella figura qui sopra. Il cliente A è adesso espicitamente rappresentato da una sua propria classe, e quindi tramite questa potrà richiedere tutta la sua banda (40kbps) e ridistribuirla come meglio crede tra le sue sottoclassi. Al primo livello avremo la classe root, poi le clssi che chiameremo "interne" e infine le classi foglia.
Assegneremo 40kbps al cliente A, che a sua volta avrà 2 sotto classi una per il traffico WWW da 30kbps ed un'altra da 10kbps per il restante traffico:
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:2 htb rate 40kbps ceil 100kbps
tc class add dev eth0 parent 1:2 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:2 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps
Adesso quando il traffico WWW del cliente A si ferma, la sua banda è assegnata al restante traffico di A che passerà da 10kbps a 40kbps.
Se A avesse comunque un traffico inferiore a 40kbps la sua banda in eccesso verrebbe passata a B.
Rate ceiling (Limite di velocità)
Il parametro ceil rappresenta la massima banda che una classe può usare. Questo limite, come la larghezza di banda riservata alla classe, può essere preso in prestito. Il tetto massimo di default è la stessa larghezza di banda, infatti, questo è il motivo per cui bisogna specificarlo (nell'esempio precedente altrimenti non avremo potuto effettuare scambi di banda).
Rate = Banda garantita
Ceil = Limite massimo di banda
Se nella configurazione di prima cambiassimo per la classe 1:2 (A - WWW) e la 1:11 (A - altro) il parametro ceil da 100kbps ai valori ripettivamente di 60kbps e 20kbps, avremo alcune differenze di comportamento:
- Al fermarsi del traffico WWW su A il restante traffico (1:11) non guadagnerebbe la banda liberata dalla 1:2 perché la 1:11 è limitata dal suo parametro di ceil a 20kbps. La sua banda invece verrebbe prestata a B.
- Nel caso B non avesse più traffico, tutta la sua banda dovrebbe essere trasferita ad A, ma dato che ora per A ci sono dei limiti di banda, può usarne al massimo solo 60kbps, il resto rimarrebbe inutilizzato.
Questa caratteristica potrebbe essere utile ad un service provider, dato che potrebbero voler limitare la connessione per un servizio ad un loro utente indipendentemente dal fatto che gli altri utenti utilizzino o meno lo stesso servizio.
Notare che le classi root non possono prendere in prestito della banda, quindi non si può specificare il parametro ceil per loro, dato che questo coincide sempre con la banda assegnatagli.
Il parametro ceil deve essere sempre maggiore del rate assegnato alla classe e deve anche essere maggiore del ceil assegnato alle sue classi figlie.
Burst
La scheda di rete può spedire solo un pacchetto alla volta e solo alla velocità legata al suo hardware. I software di link sharing possono simulare link multipli, magari con differenti velocità, ma sempre più basse di quella imposta dall'hardware utilizzato.
Per questo motivo la velocità e il limite di velocità di una classe non sono realmente istantanee, ma sono limitazioni softare che entrano in funzione dopo un certo periodo di tempo e dopo un certo numero di pacchetti, dopo i quali l'algoritmo di accodamento, adattandosi al nuovo scenario, va a impostare le varie velocità dei flussi.
Quello che accade è che il traffico da una classe viene in parte spedito alla massima velocità, dopo di che tocca alle altre classi inviare i loro pacchetti, sempre alla massima velocità e sempre per un limitato periodo di tempo. I parametri burst e cburst controllano la quantità di dati che possono essere inviati alla massima velocità (hardware) prima che la classe sucessiva venga servita.
burst [bytes]
Quantità di bytes che può essere inviato alla velocità massima (ceil), la quale può essere maggiore della velocità garantita (rate). Il burst di una classe dovrebbe essere almeno maggiore del burst delle sue sottoclassi.
cburst [bytes]
Quantità di bytes che possono essere inviati alla velocità "infinita" (limite hardware), cioè alla massima velocità che l'interfaccia fisica può inviarli. Il cburst di una classe dovrebbe essere almeno maggiore del più grande cburst delle sue sottoclassi.
Quando nella limitazione della banda entra in gioco il rate della classe allora viene preso in considerazione il burst, quando invece la limitazione viene imposta dal parametro ceil allora si usa il cburst.
Il burst è la quantità di bytes che una classe può inviare alla velocità massima (ceil). E' importante ricordare che il limite di velocità ceil è espresso in bytes al secondo mentre il burst si misura in bytes.
Per usare una metafora potete immaginare il burst come la dimensione di un contenitore, il parametro ceil come la velocità massima con cui fuoriescono i token e il rate la velocità con la quale si riempie di token il contenitore. Così nel caso avessimo un burst da 5000 bytes, un rate di 1000 bytes al secondo e un ceil da 2500 bytes al secondo, allora potremo sostenere un traffico di 1000 bytes/sec , se in un qualsiasi istante fosse disponibile un "burst" di dati allora sarebeb possibile inviare questi ultimi alla velocità di 2500 bytes/sec, ma per quanto tempo? Bene il tempo sarà dato da 5000/2500 = 2 sec , cioè la grandezza del contenitore(burst) diviso la velocità con la quale si svuota ci da il tempo di svuotamento. Dopo di che il contenitore è di nuovo vuoto e riinizia riempirsi alla velocità di 1000 bytes/sec.
Per il cburst si applica la stessa metafora solo che adesso al posto della velocità di ceil ci starà la velocità massima che l'interfaccia hardware può sopportare.
Priorizzazione della condivisione di banda
Con il parametro prio è possibile impostare delle priorità con le quali al momento di dare in prestito della banda, alcune classi siano preferite alla altre.
Fino ad ora questa preferenza è stata gestita dal rapporto dei rate assegnati alle varie classi, ma se ad una classe con un rate piccolo vlessimo comunque garantire un trattamento di favore allora dovremo utilizzare le priorità esplicite.
Facendo riferimento ad un esempio di configurazione precedente:

Se assegniamo a tutte le classi la priorità 1, e solo alla classe SMTP il valore 0 (valore di priorità più elevato), allora la regola sarà che tutte le classi potranno scambiare la banda, ma alla classe con la priorità più alta verranno offerti questi eccessi prima che alle altre, ma rimangono sempre valide le regole che garantiscono la velocità (rate) e il limite massimo di velocità (ceil).
Nell'esempio precedente la cosa sarebbe evidente nel caso B cedesse parte della banda, mentre prima questa sarebbe stata distribuita 3/4 e 1/4, ora la banda va prima di tutto alla classe SMPT, se ne dovesse avanzare, magari dopo aver raggiunto il ceil della SMTP, anche a quella WWW.
Capire le statistiche col comando tc
Il comando tc ci consente di ottenere delle informazioni e delle statistiche sulle discipline di accodamento sotto Linux.
Vediamo un poco la statistica sulla configurazione del paragrafo relativa alla classificazione gerarchica.
Informazioni sulle qdisc:
# tc -s -d qdisc show dev eth0
qdisc pfifo 22: limit 5p
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc pfifo 21: limit 5p
Sent 2891500 bytes 5783 pkts (dropped 820, overlimits 0)
qdisc pfifo 20: limit 5p
Sent 1760000 bytes 3520 pkts (dropped 3320, overlimits 0)
qdisc htb 1: r2q 10 default 1 direct_packets_stat 0
Sent 4651500 bytes 9303 pkts (dropped 4140, overlimits 34251)
Le prime tre sono le qdisc figlie (PFIFO) della HTB (ultima qdisc):
- dropped ci dice quanti pacchetti sono stati scartati dalla qdisc.
- overlimits ci dice quante volte la qdisc ha dovuto ritardare un pacchetto.
- direct_packets_stat ci dice quanti pacchetti sono stati inviati direttamente attraverso la coda.
Il valore r2q (rate to quantum) , che di default è 10, rappresenta il fattore di conversione usato per calcolare il quantum usando la velocità della coda (rate).
Informazioni sulle classi:
tc -s -d class show dev eth0
class htb 1:1 root prio 0 rate 800Kbit ceil 800Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 10240 level 3
Sent 5914000 bytes 11828 pkts (dropped 0, overlimits 0)
rate 70196bps 141pps
lended: 6872 borrowed: 0 giànts: 0
class htb 1:2 parent 1:1 prio 0 rate 320Kbit ceil 4000Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 4096 level 2
Sent 5914000 bytes 11828 pkts (dropped 0, overlimits 0)
rate 70196bps 141pps
lended: 1017 borrowed: 6872 giànts: 0
class htb 1:10 parent 1:2 leaf 20: prio 1 rate 224Kbit ceil 800Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 2867 level 0
Sent 2269000 bytes 4538 pkts (dropped 4400, overlimits 36358)
rate 14635bps 29pps
lended: 2939 borrowed: 1599 giànts: 0
Non ho inserito le classi 1:11 e 1:12 per brevità. Come si può vedere ci sono i parametri che abbiamo impostato in fase di configurazione. Gli altri parametri riguardano i livelli e le informazioni sui quantum del DRR.
Overlimits mostra quante volte la classe ha chiesto di inviare un pacchetto ma non ha potuto per via dei limiti di banda (rate/ceil).
- rate, pps ci dice l'attuale (media su 10 sec) velocità per attraversare la classe.
- lended è il numerodi pacchetti prestato da questa classe (alla sua velocità)
- borrowed è il numero di pacchetti presi in prestito.
- giànts è il numero dei pacchetti più grandi della mtu impostata col comando tc (default 1600). HTB lavorerà con questi ma la velocità non sarà del tutto accurata.
I prestiti sono sempre calcolati in modo transitivo pe le classi, se per esempio 1:10 prendesse in prestito dei pacchetti da 1:2 e questa a sua volta li prendesse in prestito dalla 1:1, allora, anche se alla fine i pacchetti vanno dalla 1:1 alla 1:10, tutti i contatori sia delal 1:2 che della 1:10 vengono incrementati allo stesso modo.
Quantum
Il quantum descrive come la banda è divisa tra le varie qdisc, e funziona così:
- Tutti i quantum per tutte le qdisc sono sommate insieme e la somma viene memorizzata.
- Ogni qdisc ha una priorità relativamente alla funzione : priorità=quantum/somma.
Questa priorità viene utilizzata quando due qdiscs hanno gli stessi rate e ceil, ma gli si vuol dare una differente priorità, questa volta la priorità non riguarda il prendere o dare in prestito la banda, ma l'invio di pacchetti.
In una qdisc di tipo HTB non si può impostare direttamente come parametro, ma dalle informazione possiamo dedurre come le varie classi interagiscono in termini di priorità, riassumendo maggiore sarà il quantum della classe e maggiore sarà la sua priorità.





