/ Linux Reviews / Networking / Kształtowanie Ruchu i Zaawansowany Routing HOWTO - en - pl


9.5. Dyscypliny kolejkowania z klasami

qdisc z klasami przydatne są jeśli masz ruch różnego rodzaju, który powinien być w różny sposób traktowany. Jedną z qdisc z klasami jest `CBQ' - `Kolejkowanie oparte o klasy' (ang. "Class Based Queueing") i jest tak popularna, że ludzie identyfikują całe zagadnienie kolejkowania jako właśnie CBQ, ale nie jest to do końca poprawne.

CBQ jest jedynie najstarszą zabawką - a jednocześnie najbardziej skomplikowaną. Nie zawsze może robić to co chciałeś. Może to być szok szczególnie dla ludzi podatnych na `efekt sendmaila' - który uczy nas, że skomplikowane technologie dostarczane bez dokumentacji muszą być najlepszymi dostępnymi.

Więcej o CBQ i jego alternatywach już niedługo.

9.5.1. Przepływ w qdisc z klasami

Ruch docierający do qdisc z klasami, musi zostać rozesłany do klas podrzędnych, czyli `sklasyfikowany'. By określić co zrobić z konkretnym pakietem używa się filtrów. Ważne jest, by zauważyć że to filtry wywoływane są w qdisc a nie odwrotnie!

Filtry dołączone do określonych qdisc odpowiadają decyzją i kolejka używa jej do skolejkowania pakietu w jedną z klas. Każda podklasa może sprawdzić swoje filtry by ewentualnie dopasować się do jeszcze innych założeń czy instrukcji. Jeśli tego nie robi, klasa kolejkuje pakiet do qdisc, ją zawierającą.

Poza zawieraniem w sobie innych kolejek, większość qdisc z klasami wykonuje również kształtowanie. Dzięki temu można zarówno planować pakiety (np. przy użyciu SFQ) i ograniczać częstotliwość z jaką będą wysyłane. Potrzebujesz takiej możliwości gdy masz szybki interfejs (na przykład, ethernet) i wolne urządzenie sieciowe (np. modem kablowy).

Gdybyśmy uruchomili tylko SFQ nic się nie stanie, ponieważ pakiety docierają i opuszczają twój router bez żadnej zwłoki: interfejs wyjściowy jest dużo szybszy niż faktyczna przepustowość łącza. Nie ma więc kolejki, którą możnaby kontrolować.

9.5.2. Rodzina kolejek z klasami: korzenie, uchwyty, rodzeństwo i rodzice

Każdy interfejs ma jedną wychodzącą kolejkę-korzeń, domyślnie jest to wspomniana wcześniej bezklasowa `pfifo_fast'. Każdej z qdisc można przydzielić uchwyt, który będzie potem używany przez polecenia konfigurujące by odwołać się do niej. Poza kolejkami dla ruchu wychodzącego, interfejs może mieć również przypisaną politykę dla ruchu przychodzącego.

Uchwyt kolejki składa się z dwóch części: starszego i młodszego numeru. Charakterystyczną nazwą dla kolejki-korzenia jest `1:', które równe jest `1:0'. Młodszy numer kolejki z dyscypliną zawsze równy jest 0.

Klasy muszą mieć ten sam starszy numer, określający ich rodzica. Musi być on unikalny w danej konfiguracji qdisc dla ruchu wychodzącego lub przychodzącego. Młodszy numer musi być unikalny w obrębie qdisc i jej dzieci.

9.5.2.1. Jak filtry używane są do klasyfikowania ruchu

Reasumując, typowa hierarchia może wyglądać tak:

                     1:   root qdisc
                      |
                     1:1    klasa-dziecko
                   /  |  \
                  /   |   \
                 /    |    \
                 /    |    \
              1:10  1:11  1:12   klasy-dzieci
               |      |     | 
               |     11:    |    klasa-liść
               |            | 
               10:         12:   qdisc
              /   \       /   \
           10:1  10:2   12:1  12:2   klasy-liście

Ale niech to drzewko cię nie zmyli! Nie powinieneś wyobrażać sobie kernela w wierzchołku tego drzewa z siecią poniżej, to po prostu nie tak. Pakiety są kolejkowane i usuwane z kolejki w korzeniu, który jest jedyną kolejką z którą rozmawia kernel.

Pakiet może być sklasyfikowany w następującym łańcuchu:

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

Pakiet znajduje się w kolejkce należącej do qdisc dołączonej do klasy 12:2. W tym przykładzie, filtr został dołączony do każdego `węzła' w drzewie, gdzie decyduje do której gałęzi pakiet zostanie skierowany dalej. Może to mieć sens. Jednak możliwe jest również:

1: -> 12:2

W tym przypadku, filtr dołączony do korzenia zdecydował, by wysłać pakiet od razu do 12:2.

9.5.2.2. Jak pakiety są zdejmowane z kolejki przez sprzęt

Kiedy kernel zdecyduje, że musi zdjąć pakiety z kolejki i wysłać je interfejsem, kolejka-korzeń 1: odbiera żądanie rozkolejkowania, które przesyłane jest do `1:1', następnie do `10:', `11:' i `12:' - z których każde odpytuje swoje rodzeństwo i próbuje wykonać na nich funkcję dequeue(). W tym przypadku, kernel musi przejść całe drzewo, ponieważ tylko 12:2 zawiera pakiet.

Krótko mówiąc, zagnieżdżone klasy rozmawiają TYLKO ze swoimi rodzicami, nigdy z interfejsami. Tylko kolejka-korzeń jest rozkolejkowywana przez kernel!

Wynikiem tego jest fakt, że klasy nigdy nie są rozkolejkowywane szybciej niż na to pozwalają ich rodzice. I to jest dokładnie to czego chcemy - możemy dzięki temu mieć kolejkowanie SFQ w klasie przychodzącej, która nie wykonuje kształtowania a jedynie planowanie; możemy również kształtować ruch w kolejkach wychodzących, które wykonują kształtowanie.

9.5.3. qdisc PRIO

Kolejka PRIO nie zajmuje się tak naprawdę kształtowaniem ruchu, dzieli jedynie ruch na podstawie tego, jak skonfigurowałeś filtry. Możesz traktować ją jak `pfifo_fast' na sterydach - zamiast pasma jest osobna klasa zamiast prostej kolejki FIFO.

Kiedy pakiet zostaje skolejkowany w qdisc PRIO, na podstawie komend filtrujących które podałeś wcześniej, wybierana jest klasa. Domyślnie, stworzone są trzy i zawierają czyste kolejki FIFO bez żadnej struktury wewnętrznej, ale możesz zastąpić je dowolnymi dostępnymi qdisc.

Zawsze gdy pakiet musi zostać wyjęty z kolejki, sprawdza się klasę :1. Wyższe klasy są sprawdzane tylko wtedy, gdy poprzednie nie zwróciły pakietu.

Kolejka PRIO jest bardzo użyteczna, gdy chcesz prioretyzować określone rodzaje ruchu nie tylko na podstawie flag ToS, ale całej potęgi, którą zapewniają filtry tc. Może również zawierać inne kolejki do predefiniowanych 3, podczas gdy `pfifo_fast' jest ograniczona do prostych kolejek FIFO.

Ponieważ kolejka ta nie zajmuje się kształtowaniem ruchu, obowiązuje to samo ostrzeżenie co do SFQ: używaj jej tylko jeśli fizyczne łącze jest naprawdę zapchane, lub wsadź ją do qdisc z klasami, która nie zajmuje się kształtowaniem ruchu. To ostatnie oznacza praktycznie wszystkie modemy kablowe i urządzenia DSL.

Formalnie rzecz biorąc, kolejka PRIO jest planującą kolejką bezstratną.

9.5.3.1. Parametry PRIO i ich użycie

Następujące parametry rozpoznawane są przez tc:

bands - pasma

Ilość pasm do utworzenia. Każde pasmo to tak naprawdę klasa. Jeśli zmienisz ten numer, musisz zmienić również:

priomap - mapa priorytetów

Jeśli nie dostarczysz filtrów tc do klasyfikowania ruchu, kolejka PRIO będzie sprawdzać priorytety TC_PRIO by stwierdzić, jak kolejkować ruch.

Działa to tak jak kolejka `pfifo_fast' wspomniana wcześniej.

Pasma są klasami i domyślnie nazywane są od `numer_starszy:1' do `numer_starszy:3', więc jeśli twoja kolejka PRIO nazywa się 12:, użyj tc by przefiltrować ruch na 12:1 dla podniesienia jej priorytetu.

Powtarzam, że pasmo 0 trafia na młodszy numer 1! A pasmo 1 na młodszy numer 2 i tak dalej!

9.5.3.2. Przykładowa konfiguracja

Stworzymy takie drzewko:

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

`Ciężki' ruch pójdzie do 30:, ruch interaktywny do 20: lub 10:.

Polecenia do wpisania:

# tc qdisc add dev eth0 root handle 1: prio 
## To *od razu* tworzy klasy 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                                

Teraz sprawdźmy co stworzyliśmy:

# 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) 
Jak widzisz, pasmo 0 dostało już trochę ruchu i jeden pakiet został wysłany w czasie wykonywania tego polecenia!

Użyjemy teraz trochę transferu danych narzędziem, które poprawnie ustawia flagi ToS i spojrzymy jeszcze raz:

# 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) 
Widać, że cały ruch poszedł do uchwytu 30:, który identyfikuje pasmo z najmniejszym priorytetem, tak jak chcieliśmy. Żeby sprawdzić, czy ruch interaktywny faktycznie trafia do wyższych pasm, wygenerujemy trochę takiego ruchu:

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

Zadziałało - cały dodatkowy ruch trafił do 10:, która jest naszą kolejką o najwyższym priorytecie. Żaden ruch nie trafił do niższych priorytetów, które otrzymały przed chwilą cały ruch wygenerowany przez `scp'.

9.5.4. Sławna qdisc CBQ

Tak jak wspomniano wcześniej, kolejka CBQ jest najbardziej skomplikowaną, tą o której jest najwięcej szumu a jednocześnie najmniej ludzi rozumie co robi i jak skonfigurować ją dokładnie tak jak chcemy. Nie dzieje się tak dlatego, że autorzy byli złośliwi lub niekompetentni - przeciwnie, po prostu algorytm CBQ nie jest zbyt precyzyjny i niezbyt pasuje do sposobu w jaki działa Linuks.

Poza byciem kolejką z klasami, CBQ zajmuje się również kształtowaniem ruchu i to właśnie tego nie robi zbyt dobrze. Powinna pracować w ten sposób: jeśli chcesz ograniczyć ruch 10mbit/s do 1mbit/s, łącze powinno być przez 90% bezczynne. Jeśli nie jest, musimy je dławić by BYŁO w 90% czasu faktycznie bezczynne.

Jest to raczej trudne do zmierzenia, więc CBQ pobiera czas bezczynności z ilości mikrosekund pomiędzy wywołaniami sprzętowymi o więcej danych. Po połączeniu tych informacji, CBQ aproksymuje jak bardzo łącze jest zajęte.

Nie jest to raczej ostrożne i nie zawsze prowadzi do poprawnych rezultatów. Na przykład, jaka jest prędkość łącza na interfejsie, który nie potrafi wysłać pełnych 100mbit/s danych - być może z powodu źle zaimplementowanego sterownika? Karty sieciowe PCMCIA również nigdy nie osiągają 100mbit/s ponieważ nie projektowano do tego tej szyny i ponownie - jak skalkulować czas bezczynności?

Robi się jeszcze gorzej, jeśli rozważymy trochę nietypowe urządzenia sieciowe, takie jak PPP ponad Ethernetem czy PPTP ponad TCP/IP. Efektywna przepustowość w tym przypadku to tak naprawdę miara tego jak dobrze działają `rurki' (ang. "pipe") do przestrzeni użytkownika - a działają bardzo wydajnie.

Ludzie, którzy prowadzili pomiary stwierdzili, że CBQ nie zawsze jest bardzo dokładne a czasami w ogóle podaje nieadekwatne rezultaty.

W większości wypadków, działa jednak dobrze. Z dokumentacją, którą masz w ręku, powinieneś poradzić sobie ze skonfigurowaniem jej tak, by działała dobrze.

9.5.4.1. Szczegóły kształtowania ruchu przez CBQ

Tak jak wcześniej napisano, CBQ dba cały czas o to, by łącze było przez odpowiedni czas bezczynne i dzięki temu prawdziwa przepustowość spadła do skonfigurowanej wielkości. Żeby to robić kalkuluje czas, który powinien upłynąć pomiędzy kolejnymi pakietami.

W czasie pracy, efektywny czas bezczynności mierzony jest za pomocą `wykładnika ważonego średniego przesyłu' (ang. "exponential weighted moving average", EWMA), który traktuje świeże pakiety jako wykładniczo ważniejsze niż starsze. Tak samo kalkulowany jest poziom obciążenia stacji uniksowych.

Wyliczony czas bezczyności odejmowany jest od wartości wyliczonej przez EWMA i wynik nazywa się średnim czasem bezczynności - `avgidle'. Dokładnie załadowane łącze ma ten czas równy zero: pakiety docierają dokładnie co jeden wyliczony interwał czasowy.

Przeładowane łącze ma ujemny średni czas bezczynności, a jeśli wartość ta jest duża, CBQ zamyka je na chwilę i oznacza to jako `przekroczenie limitu' (ang. "overlimit").

Odpowiednio, łącze puste ma duży średni czas bezczynności, który po paru godzinach ciszy umożliwiłby zajęcie nieskończonej przepustowości. By temu zapobiec, wartość średniego czasu bezczynności ogranicza się odgórnie parametrem `maxidle'.

Jeśli dojdzie do przekroczenia limitu, CBQ zdławi się na dokładnie tyle czasu ile zostało wyliczone pomiędzy pakietami a następnie przepuści jeden pakiet i zdławi się znowu. Sprawdź znaczenie parametru `minburst'.

Poniższe parametry można podać by skonfigurować kształtowanie ruchu:

avpkt

Średnia wielkość pakietu, mierzona w bajtach. Potrzebna do wyliczania `maxidle', który jest wyliczany z `maxburst', który z kolei podawany jest w pakietach.

bandwidth - pasmo

Fizyczne pasmo twojego urządzenia, potrzebne do wyliczania czasu bezczynności.

cell - komórka

Czas który zajmuje pakietowi przesłanie przez urządzenie może rosnąć krokowo, na podstawie rozmiaru pakietu. Pakiety o wielkościach 800 i 806 bajtów mogą być wysyłane dokładnie tyle samo czasu - parametr ten kontroluje ziarnistość. Zwykle ustawiany na `8'. Musi być całkowitą potęgą dwójki.

maxburst

Ta ilość pakietów służy do wyliczania `maxidle' tak, by gdy `avgidle' jest równe `maxidle', ta ilość pakietów mogła zostać wysłana dodatkowo zanim `avgidle' spadnie do 0. Ustaw tą wartość wyżej by być bardziej tolerancyjnym dla dodatkowych serii ( ang. "bursts"). Nie możesz ustawić bezpośrednio `maxidle' - możesz to zrobić tylko przez ten parametr.

minburst

Jak wspomniano wcześniej, CBQ musi dławić ruch w przypadku przekroczenia limitu. Idealnym rozwiązaniem byłoby robienie tego na dokładnie wyliczony czas a następnie wysłać 1 pakiet. Kernele uniksa generalnie mają problemy z planowaniem zadań krótszych niż 10ms, więc lepiej jest dławić ruch na dłuższy okres, następnie przepuszczać ilość pakietów określoną przez parametr `minburst' w jednym ruchu a potem zasypiać na `minburst'-razy dłużej.

Czas czekania nazywa się `czasem wolnym' (ang. "offtime"). Wyższe wartości `minburst' prowadzą do dokładniejszego kształtowania ruchu na dłuższą metę, ale jednocześnie do większych serii pakietów w skali milisekundowej.

minidle

Jeśli `avgidle' ma wartość poniżej 0, oznacza to stan przekroczenia limitu i trzeba czekać dopóki `avgidle' będzie na tyle duże, by wysłać jeden pakiet. By zapobiec nagłemu wypuszczeniu serii przy zamykaniu połączenia na określony okres czasu, `avgidle' ustawiany jest na `minidle' jeśli wartość `avgidle' spadnie za nisko.

Wartość `minidle' podaje się w ujemnych mikrosekundach, więc 10 oznacza że `avgidle' wynosi -10 mikrosekund.

mpu

`Minimalny rozmiar pakietu' (ang. "Minimum packet size") - wymagany, ponieważ nawet pakiety zawierające zero danych wyrównywane są do 64 bajtów w ethernecie i w związku z tym zajmują określony czas i pasmo podczas transmisji. CBQ musi wiedzieć ile wynosi rozmiar pakietu by poprawnie wyliczać wartość czasu bezczynności.

rate - częstotliwość

Wymagana częstotliwość ruchu opuszczającego qdisc - to właśnie jest regulator szybkości!

Wewnętrznie, CBQ ma masę parametrów konfigurujących. Na przykład, klasy o tórych wiadomo, że nie zbierają danych do kolejkowania nie są o takie dane odpytywane. Klasy zajmujące się ruchem nadmiarowym (ponad ustalonym limitem) ograniczane są dodatkowo przez zmniejszenie ich efektywnego priorytetu. Wszystko bardzo mądre i skomplikowane.

9.5.4.2. Zachowanie CBQ z klasami

Poza kształowaniem ruchu, za pomocą wspomnianych wyliczeń czasu bezczynności, CBQ zachowuje się również jak kolejka PRIO w tym sensie, że klasy mogą mieć różne priorytety i te o niższych priorytetach będą odpytywane przed tymi o wysokich priorytetach.

Za każdym razem gdy warstwa sprzętowa zażąda pakietu do wysłania w sieć, zaczyna się `ważony proces round-robin' (ang. "weighted round robin process", WRR) rozpoczynający się od klas z najniższymi priorytetami.

Są one grupowane i odpytywane czy mają jakieś dane do wysłania. Jeśli tak, dane zwracane są do pytającego. Po tym, jak klasa otrzyma zgodę na zdjęcie z kolejki określonej liczby bajtów, sprawdzana jest następna klasa z tym samym priorytetem.

Poniższe parametry kontrolują proces WRR:

allot

Kiedy CBQ proszone jest o pakiet do wysłania przez interfejs, sprawdzi wszystkie kolejki wewnętrzne (w klasie) w kolejności parametru `priorytet'. Za każdym razem gdy kolejna klasa dostaje swoją kolej, może wysłać tylko ograniczoną ilość danych. Parametr `allot' jest podstawową jednostką tej ilości. Zajrzyj od opisu parametru `weight' po więcej informacji.

prio

Kolejka CBQ może zachowywać się jak qdisc PRIO. Wewnętrzne klasy z niższymi priorytetami sprawdzane są pierwsze (jeśli mają jakiś ruch), inne klasy nie są odpytywane o ruch.

weight

`Waga' wspomaga proces WRR. Każda klasa otrzymuje szansę wysłania danych gdy przyjdzie jego kolej. Jeśli posiadasz klasy z wyraźnie większą przepustowością niż inne istnieje powód, by pozwolić im wysłać więcej danych w jednym ruchu niż innym klasom.

CBQ dodaje wszystkie wagi w klasach podrzędnych i normalizuje je, więc widzisz wartości arbitralne: tylko `współczynniki' (ang. "ratio") są ważne. Generalnie używa się wzoru `częstotliwość/10' i zdaje się to sprawdzać dobrze. Obliczona waga jest mnożona przez parametr `allot' by sprawdzić, jak dużo danych można wysłać w jednym ruchu.

Zwróć uwagę na to, że wszystkie klasy w obrębie hierarchii CBQ współdzielą ten sam starszy numer!

9.5.4.3. Parametry CBQ określające możliwości pożyczania i współdzielenia łącza

Poza czystym ograniczaniem określonych rodzajów ruchu możliwe jest również określanie, które klasy mogą pożyczać pasma z innych klas lub współdzielić.

Isolated/sharing

Klasa skonfigurowana z parametrem `wyizolowana' (ang. "isolated") nie pożycza przepustowości rodzeństwu. Użyj go jeśli masz konkurujące lub wzajemnie nieprzyjazne działy (lub użytkowników, aplikacje itp.) na jednym łączu.

Program tc umożliwa również ustawienie parametru `współdzieląca' (ang. "sharing"), co jest odwrotnością wyizolowanej.

bounded/borrow

Klasa może być `ograniczona' (ang. "bounded"), co oznacza, że nie będzie próbowała pożyczać od rodzeństwa. Możliwy jest również parametr `pożyczająca' (ang. "borrow"), który jest dokładnie odwrotnością tego zachowania.

Typową sytuacją jest, gdy obie jednostki organizacyjne są zarówno `wyizolowane' jak i `ograniczone', co oznacza, że są ograniczone do przydzielonej częstotliwości i nie będą próbowały nic pożyczać.

W obrębie takiej klasy pojedyńczej jednostki, mogą znaleźć się klasy, którym zezwolimy na wymianę przepustowości.

9.5.4.4. Przykładowa konfiguracja


               1:           root qdisc
               |
              1:1           klasy-dzieci
             /   \
            /     \
          1:3     1:4       klasy-liście
           |       |
          30:     40:       qdiscs
         (sfq)   (sfq)

Konfiguracja ogranicza ruch serwera WWW do 5mbit, ruch SMTP do 3mbit. Razem, nie mogą zająć więcej niż 6mbit. Mamy 100mbit'ową kartę sieciową oraz klasy, które mogą pożyczać od siebie pasmo.

# 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
Ta część konfiguruje klasę-korzeń i odpowiednią klasę 1:0. Klasa 1:1 jest ograniczona, więc ogólnie przepustowość nie może przekroczyć progu 6mbit.

Jak już wcześniej wspomniano, CBQ wymaga wielu regulacji. Wszystkie parametry wyjaśniono wyżej. Odpowiadająca konfiguracja HTB jest o wiele prostsza.

# 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

Widać nasze dwie klasy. Zauważ, że skalujemy wagę ze skonfigurowaną częstotliwością. Obie klasy nie są ograniczone, ale połączone z klasą 1:1, która jest ograniczona. Oznacza to, że suma obu klas nigdy nie wykroczy poza 6mbitów. Numery identyfikacyjne muszą znajdować się w obrębie jednego numeru starszego, równego nadrzędnej kolejce CBQ!

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

Obie klasy mają domyślnie kolejki FIFO. Zamieniliśmy je na SFQ, więc każdy przepływ danych jest traktowany równo.

# 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

Powyższe komendy, dołączone bezpośrednio do korzenia rozsyłają ruch do odpowiednich kolejek.

Zauważ, że użyliśmy komendy tc class add by STWORZYĆ klasy w obrębie qdisc, ale by je dodać użyliśmy komendy tc qdisc add.

Możesz zastanawiać się co dzieje się z ruchem, który nie podlega klasyfikacji przez żadną z dwóch reguł. W tym wypadku, dane będą przetwarzane w obrębie klasy 1:0 i w związku z tym nie będą podlegać ograniczeniom.

Jeśli łącznie ruch smtp i WWW spróbują przekroczyć ustalony limit 6mbit/s, przepustowość zostanie podzielona zgodnie z wagą, to jest 5/8 dla serwera WWW i 3/8 dla serwera poczty.

Przy okazji można powiedzieć, że przy tych ustawieniach, serwer WWW zawsze otrzyma przynajmniej 5/8 * 6mbit = 3.75mbita.

9.5.4.5. Inne parametry CBQ: split & defmap

Jak już napisałem, kolejka z dyscypliną potrzebuje wywoływać filtry by określić do której klasy zostanie skolejkowany pakiet.

Poza wywołaniem filtra, CBQ oferuje inne opcje - defmap i split. Jest to trochę trudne do zrozumienia, ale nie konieczne. Ale ponieważ jest to jedyne miejsce, w którym w ogóle wyjaśnia się takie terminy, postaram się zrobić co w mojej mocy.

Ponieważ zwykle będziesz filtrował tylko na podstawie pola ToS, dostępna jest specjalna składnia. Kiedy CBQ potrzebuje określić gdzie skolejkować pakiet, sprawdza czy ten węzeł to `węzeł rozdzielczy' (ang. "split node"). Jeśli tak, jedna z pod-kolejek określiła, że chciałaby otrzymywać wszystkie pakiety z określonym, skonfigurowanym priorytetem - który może pochodzić z pola ToS, lub opcji gniazd ustawionych przez aplikację.

Bity priorytetów pakietów są poddawane logicznej operacji AND z polem defmap by sprawdzić czy istnieje pasujący odpowiednik. Innymi słowy, jest to krótszy sposób na stworzenie bardzo szybkiego filtru, który pasuje tylko do określonych priorytetów. Mapa defmap równa 0xFF (heksdecymalnie) będzie pasowała do wszystkiego, a 0 do niczego. Przykładowa konfiguracja, by rozjaśnić to tłumaczenie:

# 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
Standardowa preambuła CBQ. Nigdy nie przywyknę do ilości numerków, które należy podać!

defmap odwołuje się do bitów TC_PRIO, które zdefiniowane są jak następuje:

TC_PRIO..          Numer  Odpowiada w ToS
-------------------------------------------------
BESTEFFORT         0      Maksymalna niezawodność        
FILLER             1      Minimalny koszt              
BULK               2      Maksymalna przepustowość (0x8)  
INTERACTIVE_BULK   4                               
INTERACTIVE        6      Minimalna zwłoka (0x10)      
CONTROL            7                               

Numer TC_PRIO odpowiada bitom, liczonym od prawej. Zajrzyj do sekcji opisującej `pfifo_fast' po więcej informacji w jaki sposób bity ToS mapowane są na priorytety.

A teraz klasy interaktywna i dla reszty ruchu:

# 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

`Kolejką rozdzielającą' jest 1:0 i w niej dokonywany jest wybór. c0 to binarnie 11000000, 3F to 00111111, więc obie łącznie będą obejmować cały ruch. Pierwsza klasa pasuje jeśli ustawione są bity 6 i 7 odpowiadając tym samym ruchowi `interaktywnemu' i `kontrolnemu'. Druga klasa odpowiada reszcie.

Węzeł 1:0 ma teraz tabelę taką jak ta:

priorytet  wyślij do
0            1:3
1            1:3
2            1:3
3            1:3
4            1:3
5            1:3
6            1:2
7            1:2

Co więcej, możesz również wydać polecenie `zmiany maski', które określa dokładnie które priorytety chciałbyś zmienić. Potrzebujesz tego tylko gdy używasz tc class change. Na przykład, by dodać ruch typu `best effort' do kolejki 1:2, możemy napisać tak:

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

Mapa priorytetów nad 1:0 wygląda teraz tak:

priorytet  wyślij do
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: nie testowałem tc class change, przeglądałem tylko źródła.

9.5.5. Hierarchiczne Wiadro Żetonów (ang. "Hierarchical Token Bucket")

Martin Devera (<devik>) słusznie zauważył, że CBQ jest skomplikowane i nie wydaje się zoptymalizowane do zastosowania w wielu typowych sytuacjach. Jego hierarchiczne podejście jest bardzo dobrze dostosowane do konfiguracji, w których masz określone pasmo sieciowe do podzielenia wśród różnych zastosowań. Przy okazji wiesz ile każde powinno dostać i mniej więcej ile mogą od siebie pożyczać.

HTB działa tak jak CBQ, ale nie wykonuje przeliczania czasu bezczynności. Zamiast tego, funkcjonuje jako TBF z klasami - stąd jej nazwa. Ma tylko parę parametrów, opisanych dokładniej pod tym adresem.

Rozbudowa konfiguracji HTB doskonale się skaluje przy wzroście skomplikowania. Jeśli chodzi o CBQ, to wiadomo, że jest już skomplikowana na początku! HTB3 (zajrzyj pod ten adres aby sprawdzić informacje o wersjach HTB) jest obecnie częścią oficjalnych źródeł jądra (od 2.4.20-pre1 i 2.5.31). Prawdopodobnie jednak będziesz musiał nadal pobrać załataną wersję tc do obsługi HTB3: wersje oprogramowania w jądrze i w przestrzeni użytkownika muszą być w tej samej wersji lub tc po prostu nie zadziała.

Jeśli akurat masz nowy kernel, lub jesteś w stanie go odpowiednio załatać, powinieneś poważnie rozważyć użycie HTB.

9.5.5.1. Przykładowa konfiguracja

Funkcjonalnie, praktycznie identyczny przykład z tym dla CBQ z sekcji powyżej:

# 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

Autor rekomenduje następnie użycie SFQ poniżej tych klas:

# 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

Dodajmy filtry, które skierują ruch do odpowiednich klas:

# 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
I to wszystko - bez niewyjaśnionych numerków i nieudokumentowanych parametrów.

HTB z pewnością wygląda pięknie - jeśli 10: i 20: obie mają swoją gwarantowaną przepustowość i zostało coś do podzielenia, pożyczają w stosunku 5:3, tak jakbyś mógł się spodziewać.

Ruch niesklasyfikowany do żadnej klasy kierowany jest do 30:, która ma tylko trochę własnej przepustowości ale może pożyczać wszystko co aktualnie jest niewykorzystane. Ponieważ używamy w środku SFQ, mamy zapewniony sprawiedliwy dostęp za darmo!


/ Linux Reviews / Networking / Kształtowanie Ruchu i Zaawansowany Routing HOWTO