Jak zrobić tak, by móc i coś intensywnie ściągać (nie koniecznie przez P2P), ale jednocześnie działały inne usługi na zadawalającym poziomie? Oczywiście, trzeba kształtować ruch. Jest wiele opracowań i przykładów na ten temat, więc dorzucę kilka słów od siebie. Ale takich trochę innych...
O kształtowaniu ruchu
Po pierwsze szerokim łukiem obchodzę tc filter. Wygląda to paskudnie... Poza tym skoro obecnie właściwie każdy liczący się firewall musi zapewniać stanowość, to też chcę mieć stanowość w kolejkach. Proste? Kolejna sprawa - chcę rozróżnić połączenia nawiązywane z mojej sieci lokalnej do Internetu i te, które nawiązywane są z Internetu do mojej sieci lokalnej. Chcę także oddzielnie traktować ruch wychodzący z mojego routera/firewalla. Mam na nim zainstalowane kilka usług, między innymi DNS, w związku z czym długie rozwiązywanie nazw w przypadku sporego obciążenia pasma ruchem wychodzącym może mnie irytować, a po co się mam złościć? Na początek więc znakuję połączenia, wykorzystuję do tego CONNMARK. Jak znakuję? W tabeli mangle wystarczy dodać następujące reguły:
-A FORWARD -o ppp0 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1 -A FORWARD -o eth0 -m conntrack --ctstate NEW -j CONNMARK --set-mark 2 -A OUTPUT -o ppp0 -m conntrack --ctstate NEW -j CONNMARK --set-mark 3
Powyższe reguły powodują to, że każde połączenie inicjowane z sieci wewnętrznej na świat, czyli wychodzące przez ppp0 mojego routera otrzymuje mark 1. Połączenia wchodzące, czyli wychodzące przez eth0 mojego routera otrzymują mark 2. Połączenia nawiązywane przez mój router ze światem otrzymją mark 3. Połączenia, które inicjuje mój router do sieci wewnętrznej nie są znakowane. Połączenia razem ze znakami można obejrzeć w /proc/net/ip_conntrack.
Oczywiście, mogę dodatkowo nadawać priorytety niektórym innym połączeniom. Przykładowo:
-A FORWARD -m layer7 --l7proto bittorrent -j CONNMARK --set-mark 4 -A INPUT -i ppp0 -m conntrack --ctstate NEW -p tcp -m tcp -m multiport --dports 22 -j CONNMARK --set-mark 5
Tak, używam dodatkowo l7-filter. I połączenia bittorrent (nie ważne z której strony inicjowane) otrzymują swój własny mark. Połączenia ze świata do mojego routera także otrzymuja swój własny znak. Przy znakowaniu trzeba pamiętać, że do danego pakietu dopasowana może zostać więcej niż jedna reguła. Jeśli tak sie stanie, to połączenie otrzyma mark taki, jak wskazuje ostatnia pasująca reguła.
Dobrze, połączenia są oznakowane, jak je teraz skierować do odpowiednich kolejek... Tutaj można posłużyć się wygodnym targetem CLASSIFY. W tabeli mangle w łańcuchu POSTROUTING wystarczy skierować pakiety z oznakowanych połączeń do odpowiednich kolejek. Wyglądać to może mniej więcej tak:
-A POSTROUTING -m connmark --mark 1 -j CLASSIFY --set-class 1:1 -A POSTROUTING -m connmark --mark 2 -j CLASSIFY --set-class 1:2 -A POSTROUTING -m connmark --mark 3 -j CLASSIFY --set-class 1:3 -A POSTROUTING -m connmark --mark 4 -j CLASSIFY --set-class 1:4 -A POSTROUTING -m connmark --mark 5 -j CLASSIFY --set-class 1:5
Tak naprawdę, to mam nieco bardziej sensownie oznakowane kolejki i marki... Numery kolejek składają się u mnie z trzech cyfr, główna to 100, na pierwszym poziomie zagłębienia kolejki mają numery 110, 120, 130 na kolejnym - 111, 112, 113, 121,122,123... i tak dalej według tego samego wzorca. Jakoś łatwiej mi się w takim schemacie połapać. Jeśli tworzyłbym więcej poziomów kolejek (na razie tak mi nie odbiło), numery byłyby czterocyfrowe...
Właśnie, kolejki... Cały numer teraz polega na tym, by ustawić je tak, by kolejka na ppp0 odpowiadała kolejce na eth0. Odpowiadała w sensie classid. Dlaczego? Dlatego, że lubię kształtować zarówno ruch przychodzący (i robię to kolejkami na eth0), jak i wychodzący (czyli na ppp0). Nawet w obrębie jednego połączenia. Wszystko przez P2P. Gdy przez BitTorrenta ściąga się na przykład kolejną płytkę Gentoo, transfer jest naprawdę spory, w praktyce wypełnia całe pasmo. P2P ma to do siebie, że skoro sam ciągniesz, to inni też ciągną od ciebie. A z kolei łącza, często są asymetryczne, czyli download nie odpowiada uploadowi. Dlatego w obrębie jednego połączenia dla ruchu przychodzącego chcę pozwolić powiedzmy na maksymalnie 600kbit, ale w ruchu wychodzącym tylko 128kbit.
Jakie algorytmy stosuję? Przez pewien czas stosowałem HTB, ale później przesiadłem się na HFSC. Przesiadka na HFSC nastąpiła po mojej przygodzie z OpenBSD. Tam nie mogłem skłonić do zadawalającej współpracy CBQ, pożyczać bestia nie chciała, więc sprawdziłem HFSC i tak mi jakoś zostało...
HFSC wydaje się bardzo skomplikowanym algorytmem. Wystarczy popatrzeć:
Usage: ... hfsc [ [ rt SC ] [ ls SC ] | [ sc SC ] ] [ ul SC ] SC := [ [ m1 BPS ] [ d SEC ] m2 BPS m1 : slope of first segment d : x-coordinate of intersection m2 : slope of second segment Alternative format: SC := [ [ umax BYTE ] dmax SEC ] rate BPS umax : maximum unit of work dmax : maximum delay rate : rate
Oczywiste, prawda? Przydział pasma określany jest przez trzy parametry rt, ls oraz ul. Określony musi być parametr ls, parametry rt oraz ul są opcjonalne. Jeśli ul nie jest określony, kolejka nie pożycza od innych. Do określenia parametrów swoich kolejek wykorzystuję tylko ls (link share) oraz ul (upper limit). ls określa ile "należy się" kolejce, ul określa natomiast ile maksymalnie pasma może wykorzystać kolejka, jeśli to pasmo nie jest wykorzystywane przez innych. To, która kolejka szybciej pożycza zależy od ls. Jeśli będą dwie kolejki, jedna będzie miała ls na poziomie 16, druga natomiast na 32, to ta druga będzie zwykle pożyczała dwa razy więcej niż pierwsza. Oczywiście do tego jeszcze dochodzi ul. No co, przecież mówiłem, że algorytm nie jest prosty :)
Przy pomocy pierwszego formatu opisu kolejki można osiągnąć taki efekt, że przez pewien czas kolejka dostaje więcej pasma (określane przez m1), a następnie po określonym czasie (określane przez d) otrzymuje inną wartość (określane przez m2). Czasami jest to przydatne, ale może nie na początku korzystania z HFSC.
A rozkoszować się dobrze wykonaną robotą można przy pomocy narzędzia bmon.