Traffic control, czyli jak limitować sieć p2p bez uszczerbku dla niej samej
Kategoria: Artykuły, etykiety: traffic control, torrent, tc, qbittorrent, p2p, kontrola ruchu, iptables, imq, ifb, cgroups
Dodany: 2014-02-11 22:25
(zmodyfikowany: 2014-02-11 22:33)
Przez: morfik
Wyświetleń: 18194
- 1. Interfejsy IMQ
- 2. Jeśli nie IMQ, to co w takim razie?
- 3. Interfejsy IFB
- 4. Statystyki ruchu
- 5. Jak odpalać qbittorrenta?
Od wielu miesięcy chodziło mi po głowie zrealizowanie pewnego projektu polegającego na wydzieleniu określonego pasma sieciowego pod qbittorrenta, tak by on sobie działał w tle non stop i konsumował możliwie dużo łącza (download/upload), przy czym bez strat dla pozostałych usług sieciowych. Każdy chyba wie, że gdy odpalimy torrenta na full, to pingi nam zaraz skaczą na paręset ms, czy nawet parę sekund. Dodatkowo jeśli coś pobieramy na torrencie i zachodzi potrzeba pobrania czegoś via firefox, to transfer w najlepszym wypadku rozłoży się w proporcjach 50:50 -- pół łącza przydzielone zostanie qbittorrentowi, a drugi pół firefoxowi. To samo tyczy się uploadu, gdy chcemy coś wysłać np. na dropboxa, wtedy nie ma innego wyjścia jak albo przykręcić kurek qbittorrentowi bezpośrednio w kliencie, albo wyłączyć qbittorrenta zupełnie na czas wysyłania danych do dropboxa. Teoretycznie niby UTP ma łagodzić skutki korzystania z sieci p2p ale coś to nie bardzo u mnie działa. Qbittorrent, co prawda, ma też wbudowany scheduler i można z niego korzystać, np ustawiając transfer wyższy w określonych godzinach, tych, w których wiemy, że nic na kompie nie będziemy robić, czyli w nocy. Ja korzystałem z tego typu rozwiązania przez pewien czas ale ciągle musiałem te godziny przestawiać, w końcu to wyłączyłem i przeszedłem na manualny system zmiany prędkości, przez klikanie tego miernika w qbittorencie, co mnie doprowadzało do szaleństwa.
W każdym razie to przeszłość, od paru dni walczyłem z zaimplementowaniem traffic control, czyli kontroli ruchu sieciowego, bezpośrednio w kernelu, coś co umożliwia dynamiczny przydział łącza w zależności od obciążenia sieci. Oczywiście to jaki wynik chcemy osiągnąć zależy w dużej mierze od konfiguracji ale udało mi się stworzyć setup, który przydziela qbittorrentowi tyle łącza ile jest aktualnie wolnego i nie trzeba przy tym sobie głowy zawracać.
Jest kilka różnych sposobów na osiągnięcie zadowalających nas efektów, ale nie w każdym z nich idzie zrealizować pewne rzeczy. Największe problemy stwarza ruch przychodzący i jeśli chcemy go również kształtować, będziemy musieli się trochę napracować. Oczywiście nic z czym byśmy sobie nie poradzili ale w przypadku qbittorrenta jest to trochę uciążliwe, bo ten lata po portach i adresach ip jak szalony i nie da się stworzyć żadnej prostej reguły by złapać ten ruch. W każdym innym przypadku, kształtowanie ruchu przychodzącego nie będzie raczej nastręczać większych problemów ale my się nie zajmiemy "każdym innym przypadkiem", my tu rozpracujemy jak ogarnąć ruch p2p, tak by człowiek nie bał się odpalić qbittorrenta, bo ten mu uniemożliwi przeglądanie pornusów czy fejsa...
Jak zatem kształtować ruch? Do dyspozycji mamy kilka narzędzi -- podstawowe to tc z pakietu iproute2 . To za jego pomocą wyznaczymy kolejki na interfejsach sieciowych, do których będą napływać pakiety. To tu również będą ustawione gwarantowane limity łącza, oraz jak dużo pasma może zapożyczyć sobie dana kolejka. Jednak samo stworzenie kolejek nic nam nie da, pakiety trzeba jakoś do nich przekierować i tu, w zależności od tego co tak naprawdę chcemy osiągnąć, możemy skorzystać z filtra tc albo też zaciągnąć do pomocy iptables albo nawet i cgroups. Należy pamiętać przy tym, że cgroups może oznaczać tylko (chyba) pakiety wychodzące i nie da rady go użyć na ruchu skierowanym do naszej maszyny.
1. Interfejsy IMQ
Na sam początek walimy z grubej rury, czyli spróbujemy kształtować ruch zarówno wychodzący jak i przychodzący. Najprościej to zrobić przy pomocy interfejsów IMQ ale kernel debianowy (jak i każdy inny) domyślnie nie obsługuje ich. Podobnie też jest z iptables, gdyż nie ma jak przekierować ruchu na te interfejsy -- no nieźle. Bez rekompilacji kernela i iptables się niestety nie obejdzie. Musimy założyć patche IMQ, dostępne pod tym linkiem http://www.linuximq.net/patches.html. Musimy pobrać patch dla kernel 3.12.4+ oraz patch dla iptables 1.4.13.x . Musimy także skołować źródła kernela oraz iptables. Źródła iptables można pobrać z repo debiana:
# apt-get -t testing source iptables
Reading package lists... Done
Building dependency tree
Reading state information... Done
Selected version '1.4.21-1' (testing) for iptables
Need to get 609 kB of source archives.
Get:1 http://ftp.pl.debian.org/debian/ testing/main iptables 1.4.21-1 (dsc) [1,290 B]
Get:2 http://ftp.pl.debian.org/debian/ testing/main iptables 1.4.21-1 (tar) [547 kB]
Get:3 http://ftp.pl.debian.org/debian/ testing/main iptables 1.4.21-1 (diff) [60.6 kB]
Fetched 609 kB in 0s (644 kB/s)
dpkg-source: info: extracting iptables in iptables-1.4.21
dpkg-source: info: unpacking iptables_1.4.21.orig.tar.bz2
dpkg-source: info: unpacking iptables_1.4.21-1.debian.tar.gz
dpkg-source: info: applying 0101-changelog.patch
dpkg-source: info: applying 0102-add_manpages.patch
dpkg-source: info: applying 0103-lintian_allows_to.patch
dpkg-source: info: applying 0104-lintian_hyphens.patch
dpkg-source: info: applying 0105-lintian_spelling.patch
dpkg-source: info: applying 0201-660748-iptables_apply_man.patch
dpkg-source: info: applying 0202-725413-sctp_man_description.patch
dpkg-source: info: applying 0301-install_iptables_apply.patch
dpkg-source: info: applying 0401-580941-iptables_apply_update.patch
Przy czym trzeba dodać deb-src do /etc/apt/sources.list
deb http://ftp.pl.debian.org/debian/ testing main non-free contrib
deb-src http://ftp.pl.debian.org/debian/ testing main non-free contrib
Mając odpowiednią łatę na iptables, nakładamy ją na źródła:
# cd iptables-1.4.21/
# patch -p1 < ../iptables-1.4.13-IMQ-test1.diff
patching file extensions/libxt_IMQ.c
patching file extensions/libxt_IMQ.man
patching file include/linux/netfilter/xt_IMQ.h
I budujemy paczuszkę deb:
# aptitude build-dep iptables
# dpkg-buildpackage -uc -us -b
Ze źródłami kernela jest trochę gorzej, bo te debianowe mają zaaplikowane własne patche, co trochę uniemożliwia bezstresowe założenie patcha IMQ. Niby na stronie IMQ jest tam wzmianka o tym problemie ale nie mam zielonego pojęcia co powinno zostać zmienione, by ten patch wszedł bez problemu. W każdym razie, jeśli nie debianowy kernel, to trzeba pobrać źródła z oficjalnej strony kernela, https://www.kernel.org/ i tym przypadku jest to 3.12.9 .
Po pobraniu źródeł kernela sprawdzamy podpis:
# xz -cd linux-3.12.9.tar.xz | gpg --verify linux-3.12.9.tar.sign -
gpg: Signature made Sat 25 Jan 2014 06:22:11 PM CET using RSA key ID 6092693E
gpg: Good signature from "Greg Kroah-Hartman (Linux kernel stable release signing key) greg@kroah.com"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E
Jeśli by były jakieś problemy z weryfikacją podpisu, na stronie kernela jest dokładna rozpiska co i jak.
Łatamy kernela:
# tar xfp linux-3.12.9.tar.xz
# xz -d linux-imqmq-3.12.4+.patch.xz
# cd linux-3.12.9/
# patch -p1 < ../linux-imqmq-3.12.4+.patch
patching file drivers/net/Kconfig
patching file drivers/net/Makefile
patching file drivers/net/imq.c
patching file include/linux/imq.h
patching file include/linux/netfilter/xt_IMQ.h
patching file include/linux/netfilter_ipv4/ipt_IMQ.h
patching file include/linux/netfilter_ipv6/ip6t_IMQ.h
patching file include/linux/skbuff.h
Hunk #6 succeeded at 2658 (offset 5 lines).
patching file include/net/netfilter/nf_queue.h
patching file include/uapi/linux/netfilter.h
patching file net/core/dev.c
patching file net/core/skbuff.c
patching file net/ipv6/ip6_output.c
patching file net/netfilter/Kconfig
patching file net/netfilter/Makefile
patching file net/netfilter/core.c
patching file net/netfilter/nf_internals.h
patching file net/netfilter/nf_queue.c
patching file net/netfilter/xt_IMQ.c
Kopiujemy starą konfigurację kernela i odpalamy menuconfig:
# cp /boot/config-3.12-1-amd64 ./.config
# make menuconfig
-> Device Drivers --->
-> Network device support --->
-> Network core driver support --->
<M> IMQ (intermediate queueing device) support
-> Networking support --->
-> Networking options --->
-> Network packet filtering framework (Netfilter) --->
-> Core Netfilter Configuration --->
<M> "IMQ" target support
Zapisujemy konfigurację, kompilujemy i robimy paczuszkę deb:
# aptitude build-dep linux-image-`uname -r`
# make-kpkg -j2 --initrd kernel-image kernel-headers
Po kompilacji powinniśmy mieć 4 paczki, instalujemy je:
# dpkg -i iptables_1.4.21-1_amd64.deb libxtables10_1.4.21-1_amd64.deb linux-image-3.12.9-morfik-imq_amd64.deb linux-headers-3.12.9-morfik-imq_amd64.deb
Musimy jeszcze załadować odpowiednie moduły na starcie systemu:
# cat /etc/modules
...
imq numdevs=2
ipt_IMQ
...
Jeśli wszystko przebiegło bez problemów, możemy zresetować maszynę.
1.1. Konfiguracja przepływu
Powinniśmy mieć już w systemie interfejsy IMQ, sprawdźmy zatem czy tak faktycznie jest:
# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 00:e0:4c:75:03:09 brd ff:ff:ff:ff:ff:ff
3: imq0: <NOARP> mtu 16000 qdisc htb state DOWN mode DEFAULT group default qlen 11000
link/void
4: imq1: <NOARP> mtu 16000 qdisc htb state DOWN mode DEFAULT group default qlen 11000
link/void
Jak widać, nie są jeszcze aktywowane, podnieśmy je:
# ip link set imq0 up
# ip link set imq1 u
W tej chwili możemy już kierować ruch do nich, potrzebujemy jeszcze odpowiednich kolejek i konfiguracji iptables. Kolejki tworzymy przy pomocy narzędzia tc:
# tc qdisc add dev imq0 root handle 1:0 htb default 4
# tc class add dev imq0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
# tc class add dev imq0 parent 1:1 classid 1:2 htb rate 250kbit ceil 950kbit prio 5
# tc class add dev imq0 parent 1:1 classid 1:3 htb rate 450kbit ceil 950kbit prio 0
# tc class add dev imq0 parent 1:1 classid 1:4 htb rate 250kbit ceil 950kbit prio 2
# tc qdisc add dev imq1 root handle 2:0 htb default 4
# tc class add dev imq1 parent 2:0 classid 2:1 htb rate 9500kbit ceil 9500kbit
# tc class add dev imq1 parent 2:1 classid 2:2 htb rate 2500kbit ceil 9500kbit prio 5
# tc class add dev imq1 parent 2:1 classid 2:3 htb rate 4500kbit ceil 9500kbit prio 0
# tc class add dev imq1 parent 2:1 classid 2:4 htb rate 2500kbit ceil 9500kbit prio 2
Powyższe linijki stworzą nam 3 klasy dla każdego interfejsu. Do kolejek 1:2 i 2:2 będzie szedł ruch z qbittorrenta, przez 1:3 i 2:3 będzie przepuszczony ruch użytkownika root oraz morfik, a na klasy 1:4 oraz 2:4 będzie szło wszystko co się nie załapie do powyższych. W tym przypadku korzystam z algorytmu HTB ale jest też kilka innych, a wszystkie można podzielić na dwie kategorie -- bezklasowe (pfifo, bfifo, pfifo_fast, red, sfq, tbf) i klasowe (CBQ, HTB, PRIO). Informacje na temat każdego z powyższych algorytmów można znaleźć w manie tc. Jest też całkiem przyzwoity opis każdego z algorytmów, dostępny pod tym linkiem.
Składnia HTB jest prosta i tam gdzie nie precyzujemy opcji, są one ustawiane na wartości domyślne. W powyższym przykładzie tworzymy qdisc na interfejsie imq0 i określamy 4 jako domyślą kolejkę, do której będzie trafiał ruch. Jeśli nie określimy domyślnej kolejki, ruch który nie trafi do żadnej z kolejek, nie będzie ulegał kształtowaniu. Zakładamy główną kolejkę (1:1) i ustawiamy jej limit 950kbitów. Mam, co prawda, 1mbit uploadu ale zaleca się by ten limit był troszeczkę mniejszy niż maksymalna przepustowość łącza, bo musimy mieć najwęższe ogniwo w tym całym procesie przesyłania informacji, by móc kształtować ruch. Następnie tworzymy 3 kolejki wewnątrz wyznaczonego pasma, odpowiednio nadając im parametr rate i ceil. Parametr rate odpowiada za gwarantowaną przepustowość jaką otrzyma kolejka. W przypadku gdy łącze będzie obciążone na full, kolejka 1:2 będzie miała do dyspozycji 250kbitów i nikt jej tego nie pozbawi. Podobnie z pozostałymi kolejkami. Parametr ceil z kolei ustawia górny limit, czyli ile kolejka może pożyczyć łącza od innych kolejek ale tylko w przypadku gdy te nie wykorzystują swojego pasma w pełni. Przykładowo, qbittorrent ma przeznaczone 250kbitów uploadu, gdy osiągnie ten limit sprawdzi czy może sobie coś pożyczyć i jeśli łącze nie będzie obciążone, to zwiększy sobie dostępne zasoby do 950kbitów. Jak tylko jakiś proces, który jest przypisany do pozostałych dwóch kolejek będzie chciał skorzystać z internetu, natychmiast zostanie przykręcony kurek qbittorrentowi. Jeśli proces zakończy buszowanie po internecie i zwolni zasoby, qbittorrent automatycznie zacznie wykorzystywać łącze w pełni ponownie i tak dalej. Podobnie z pobieraniem danych z internetu. Ważne jest by ustawić jeszcze odpowiedni priorytet. Jeśli kolejka ma wyższy priorytet (mniejszy numer), będzie miała dostęp do niewykorzystanych zasobów jako pierwsza, tj. jeśli kolejka 1:2 i 1:3 będą zjadać 100% swojego przydziału, zostanie im rywalizacja o pozostałe 250kbitów dostępnych w kolejce 1:4. Jeśli grupa 1:3 będzie miała wyższy priorytet i będzie chciała dodatkowe 250kbitów, będzie mogła skorzystać z dostępnego pasma 1:4 w pełni, a qbittorrent na linii 1:2 będzie musiał ograniczyć zużycie do 100% swojego pasma. Dopiero w przypadku gdy kolejka 1:3 nie będzie w pełni wykorzystywać zasobów kolejki 1:4, qbittorrent (1:2) będzie mógł skorzystać z dodatkowych zasobów kolejki 1:4. I o takie zachowanie nam chodzi.
Mamy zatem zrobionych parę kolejek. Trzeba jeszcze jakoś przekierować tam ruch. Najprościej to zrobić przy pomocy iptables:
# iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 1
# iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
# iptables -t mangle -N IMQ-OUT
# iptables -t mangle -A POSTROUTING -o eth0 -j IMQ-OUT
# iptables -t mangle -A IMQ-OUT -m owner --gid-owner p2p -j MARK --set-mark 2
# iptables -t mangle -A IMQ-OUT -m owner --gid-owner p2p -j RETURN
# iptables -t mangle -A IMQ-OUT -m owner --uid-owner morfik -j MARK --set-mark 3
# iptables -t mangle -A IMQ-OUT -m owner --uid-owner morfik -j RETURN
# iptables -t mangle -A IMQ-OUT -m owner --uid-owner root -j MARK --set-mark 3
# iptables -t mangle -A IMQ-OUT -m owner --uid-owner root -j RETURN
# iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark
# iptables -t mangle -A POSTROUTING -o eth0 -j IMQ --todev 0
Ten patch co założyliśmy na iptables daje nam możliwość zastosowania celu -j IMQ, a ten przyjmuje tylko argument --todev i numer interfejsu. Zatem przekierowaniem ruchu na interfejsy zajmują się te dwie linijki z -j IMQ. W zależności czy jest to ruch wychodzący czy przychodzący, pakiety biegną odpowiednio do interfejsów imq0 i imq1. Reguły z -j CONNMARK zapiszą oznaczenia w /proc/net/ip_conntrack , a samo oznaczenie dokonuje się przez -j MARK --set-mark. Trzeba jednak uważać by czasem nie nadpisać marków, bo jeśli pakiet przechodzić przez pierwszą pozycję z -j MARK , to nie kończy jego podróży w łańcuchu, dlatego właśnie został zrobiony osobny łańcuch do markowania pakietów i tam skorzystaliśmy z -j RETURN -- jak tylko pakiet zostanie przypasowany, zostaje zwrócony do łańcucha wyżej, w tym przypadku POSTROUTING i biegnie sobie dalej przez reguły tego łańcucha. Bez -j RETURN, pakiet by przeszedł przez wszystkie reguły w łańcuchu IMQ-OUT, co w pewnych sytuacjach ustawi złego marka. Mamy dodatkowo większe opóźnienie spowodowane dłuższą trasą pakietu. W powyższym przykładzie został wykorzystany moduł -m owner jako baza przy oznaczaniu pakietów i pakiety konkretnych użytkowników/grup zostaną oznaczone. Można oczywiście w dowolny sposób oznaczać pakiety, ale za bardzo nie widzę innej opcji by wyłapać ruch qbittorrenta.
Potrzebujemy jeszcze odpowiednich filtrów tc by przekierować konkretne pakiety do przeznaczonych dla nich kolejek:
# tc filter add dev imq0 protocol ip parent 1:0 prio 1 handle 2 fw classid 1:2
# tc filter add dev imq0 protocol ip parent 1:0 prio 5 handle 3 fw classid 1:3
# tc filter add dev imq1 protocol ip parent 2:0 prio 3 handle 2 fw classid 2:2
# tc filter add dev imq1 protocol ip parent 2:0 prio 7 handle 3 fw classid 2:3
Przypisujemy filter do określonego interfejsu (tc filter add dev imq0 protocol ip) nadajemy mu priorytet (prio 1) i ustawiamy znacznik (handle 2), na podstawie którego pakiet zostanie przekierowany do odpowiedniej kolejki (classid 1:2). Liczba w handle musi odpowiada tej w --set-mark .
Powinno działać. Przy pomocy narzędzia bmon możemy zaobserwować cośmy uczynili. Poniżej prezentuje się rozpiska przy oglądaniu przez morfika czegoś na yt:
Interfaces | RX bps pps %| TX bps pps %
lo | 294B 2 | 294B 2
eth0 | 1.13MiB 871 | 76.13KiB 545
qdisc none (pfifo_fast) | 0 0 | 74.37KiB 545
imq0 | 66.84KiB 545 | 67.30KiB 547
qdisc 1: (htb) | 0 0 | 67.30KiB 547
cls :2 (fw) | 0 0 | 0 0
cls none (fw) | 0 0 | 0 0
cls :3 (fw) | 0 0 | 0 0
class 1:1 (htb) | 0 0 | 67.30KiB 547 58%
-> class 1:2 (htb) | 0 0 | 55.18KiB 245 181%
class 1:3 (htb) | 0 0 | 11.70KiB 299 21%
class 1:4 (htb) | 0 0 | 431B 3 1%
imq1 | 1.12MiB 866 | 1.13MiB 878
qdisc 2: (htb) | 0 0 | 1.13MiB 878
cls :2 (fw) | 0 0 | 0 0
cls none (fw) | 0 0 | 0 0
cls :3 (fw) | 0 0 | 0 0
class 2:1 (htb) | 0 0 | 1.13MiB 878 100%
class 2:2 (htb) | 0 0 | 278.65KiB 275 91%
class 2:3 (htb) | 0 0 | 882.11KiB 602 161%
class 2:4 (htb) | 0 0 | 62B 0 0%
Jak można zaobserwować, stan downloadu/uploadu wynosi 100%/58%. Klasa 2:3 konsumuje 161% przewidzianych zasobów dla tej kolejki -- zjada wolny przydział klasy 2:4 i trochę transferu 2:2, bo qbittorrent nie wykorzystuje swojego pasma w pełni. Podobnie jest z uploadem. Kolejka morfika ma wykorzystane nieco ponad 20% przewidzianych środków, dlatego qbittorrent sobie nieco zapożyczył z innych kolejek. I to tak sobie będzie się automatycznie regulować.
W przypadku gdyby łącze nie było obciążone, staty będą się prezentować następująco:
Interfaces | RX bps pps %| TX bps pps %
->lo | 196B 2 | 196B 2
eth0 | 14.70KiB 115 | 117.72KiB 129
qdisc none (pfifo_fast) | 0 0 | 117.71KiB 129
imq0 | 116.28KiB 128 | 116.03KiB 129
qdisc 1: (htb) | 0 0 | 116.03KiB 129
cls :2 (fw) | 0 0 | 0 0
cls none (fw) | 0 0 | 0 0
cls :3 (fw) | 0 0 | 0 0
class 1:1 (htb) | 0 0 | 116.03KiB 129 100%
class 1:2 (htb) | 0 0 | 115.76KiB 126 379%
class 1:3 (htb) | 0 0 | 164B 2 0%
class 1:4 (htb) | 0 0 | 109B 0 0%
imq1 | 12.99KiB 111 | 12.99KiB 111
qdisc 2: (htb) | 0 0 | 12.99KiB 111
cls :2 (fw) | 0 0 | 0 0
cls none (fw) | 0 0 | 0 0
cls :3 (fw) | 0 0 | 0 0
class 2:1 (htb) | 0 0 | 12.99KiB 111 1%
class 2:2 (htb) | 0 0 | 7.58KiB 107 2%
class 2:3 (htb) | 0 0 | 5.34KiB 3 1%
class 2:4 (htb) | 0 0 | 62B 0 0%
I jak widzimy, qbittorrent zjada całe pasmo uploadu, 279% ponad swój przydział. Sprawdźmy zatem jak wyglądają opóźnienia w takiej sytuacji:
$ ping wp.pl -c 10
PING wp.pl (212.77.100.101) 56(84) bytes of data.
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=1 ttl=247 time=22.0 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=2 ttl=247 time=28.4 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=3 ttl=247 time=24.1 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=4 ttl=247 time=25.2 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=5 ttl=247 time=20.3 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=6 ttl=247 time=22.2 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=7 ttl=247 time=23.9 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=8 ttl=247 time=34.3 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=9 ttl=247 time=21.9 ms
64 bytes from www.wp.pl (212.77.100.101): icmp_seq=10 ttl=247 time=23.6 ms
--- wp.pl ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9004ms
rtt min/avg/max/mdev = 20.371/24.638/34.344/3.859 ms
Całkiem nieźle jak na zapchane łącze przez p2p. Taka mała uwaga jeszcze, może i to fajnie działa ale u mnie przy pobieraniu czegoś przez qbittorrenta, download w nim trochę ssie, a właściwie nie bardzo chce ssać. Trzeba nieco upload przykręcić, tak 5-7% powinno wystarczyć, bo jak nie patrzeć przy pobieraniu plików, pakiety również trzeba wysłać. Trochę to dziwne, że qbittorrent tego nie reguluje automatycznie, a może to ja nie potrafię tego skonfigurować.
Gdzieś na necie znalazłem sposób na wyłapanie tych pakietów i nadanie im wyższego priorytetu. U mnie podziałało, także jeśli nie chce nam się ograniczać uploadu w qbittorrencie, możemy dopisać poniższe regułki w iptables i wstawić je na pierwsze miejsce w łańcuchu IMQ-OUT :
# iptables -t mangle -A IMQ-OUT -o eth0 -m length --length 40:68 -j MARK --set-mark 3
# iptables -t mangle -A IMQ-OUT -o eth0 -m length --length 40:68 -j RETURN
Można oczywiście stworzyć osobną kolejkę dla tych pakietów, tak by przy pobieraniu czegoś, nie przydusiły one pozostałych procesów korzystających z internetu.
Przydałoby się jeszcze zrobić skrypt startowy, co by nam te kolejki konfigurował na starcie systemu. W sumie to można poskładać powyższą konfigurację tc i iptables i wrzucić do jednego skryptu, a ten umieścić na odpowiedniej pozycji w autostarcie systemu. Mój skrypt wygląda tak:
### BEGIN INIT INFO
# Provides: imq
# Required-Start: mountkernfs $local_fs
# Required-Stop: $local_fs
# Should-Start:
# Should-Stop:
# Default-Start: S
# Default-Stop:
# X-Start-Before:
# X-Stop-After:
# Short-Description: tc
# Description: tc configuration for imq interfaces
### END INIT INFO
SCRIPTNAME="imq"
load_tc()
{
echo -n "Loading tc configuration... "
tc qdisc add dev imq0 root handle 1:0 htb default 4
tc class add dev imq0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
tc class add dev imq0 parent 1:1 classid 1:2 htb rate 250kbit ceil 950kbit prio 5
tc class add dev imq0 parent 1:1 classid 1:3 htb rate 450kbit ceil 950kbit prio 0
tc class add dev imq0 parent 1:1 classid 1:4 htb rate 250kbit ceil 950kbit prio 2
tc qdisc add dev imq1 root handle 2:0 htb default 4
tc class add dev imq1 parent 2:0 classid 2:1 htb rate 9500kbit ceil 9500kbit
tc class add dev imq1 parent 2:1 classid 2:2 htb rate 2500kbit ceil 9500kbit prio 5
tc class add dev imq1 parent 2:1 classid 2:3 htb rate 4500kbit ceil 9500kbit prio 0
tc class add dev imq1 parent 2:1 classid 2:4 htb rate 2500kbit ceil 9500kbit prio 2
tc filter add dev imq0 protocol ip parent 1:0 prio 1 handle 2 fw classid 1:2
tc filter add dev imq0 protocol ip parent 1:0 prio 5 handle 3 fw classid 1:3
tc filter add dev imq1 protocol ip parent 2:0 prio 3 handle 2 fw classid 2:2
tc filter add dev imq1 protocol ip parent 2:0 prio 7 handle 3 fw classid 2:3
echo "done!"
}
load_iptables()
{
echo -n "Loading iptables configuration... "
iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 1
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -N IMQ-OUT
iptables -t mangle -A POSTROUTING -o eth0 -j IMQ-OUT
iptables -t mangle -A IMQ-OUT -o eth0 -m length --length 40:68 -j MARK --set-mark 3
iptables -t mangle -A IMQ-OUT -o eth0 -m length --length 40:68 -j RETURN
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --gid-owner p2p -j MARK --set-mark 2
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --gid-owner p2p -j RETURN
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --uid-owner morfik -j MARK --set-mark 3
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --uid-owner morfik -j RETURN
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --uid-owner root -j MARK --set-mark 3
iptables -t mangle -A IMQ-OUT -o eth0 -m owner --uid-owner root -j RETURN
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark
iptables -t mangle -A POSTROUTING -o eth0 -j IMQ --todev 0
echo "done!"
}
interfaces_up()
{
ip link set imq0 up
ip link set imq1 up
}
flush_tc()
{
echo -n "Removing tc qdiscs... "
tc qdisc del dev eth0 root 2> /dev/null
tc qdisc del dev eth0 ingress 2> /dev/null
tc qdisc del dev imq0 root 2> /dev/null
tc qdisc del dev imq1 root 2> /dev/null
echo "done!"
}
flush_iptables()
{
echo -n "Flushing mangle table... "
iptables -t mangle -F 2> /dev/null
iptables -t mangle -X IMQ-OUT 2> /dev/null
echo "done!"
}
interfaces_down()
{
ip link set imq0 down
ip link set imq1 down
}
case "$1" in
start)
interfaces_up && load_tc && load_iptables || exit 1
;;
stop)
flush_tc && flush_iptables && interfaces_down || exit 1
;;
force-reload|restart)
flush_tc && flush_iptables && interfaces_down && sleep 1
interfaces_up && load_tc && load_iptables
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart}"
exit 1
;;
esac
exit 0
Nie jest to szczyt techniki ale działa, a to najważniejsze. Dodatkowo ten skrypt ma się odpalić przed skryptem sieciowym -- trzeba wyedytować nagłówek skryptu /etc/init.d/networking ale pierw usuńmy ten skrypt z autostartu:
# update-rc.d networking remove
Dopisujemy teraz imq w odpowiednich sekcjach:
### BEGIN INIT INFO
# Provides: networking ifupdown
# Required-Start: mountkernfs $local_fs urandom
# Required-Stop: $local_fs
# Should-Start: peerblock iptables-persistent imq
# Should-Stop: peerblock iptables-persistent imq
# Default-Start: S
# Default-Stop: 0 6
# Short-Description: Raise network interfaces.
# Description: Prepare /run/network directory, ifstate file and raise network interfaces, or take them down.
### END INIT INFO
I dodajemy oba skrypty do autostartu:
# update-rc.d networking defaults
# update-rc.d imq defaults
Reboot maszyny i powinno działać.
2. Jeśli nie IMQ, to co w takim razie?
Jeśli nie odpowiada nam za bardzo zabawa z kompilacją kernela i iptables, bo jak nie patrzeć jest to trochę upierdliwe, istnieje inne rozwiązanie, przy wdrażaniu którego za bardzo nie trzeba się wysilać. Umożliwia ono, co prawda, kontrolę tylko ruchu wychodzącego ale nie ma się większego wpływu na ruch przychodzący. Jednak ten sposób również potrafi sprawić, że nie zauważymy większej różnicy w użytkowaniu internetu przy odpalonym na full qbittorrencie, dlatego trzeba o nim parę słów napisać.
2.1. Kształtowanie ruchu wychodzącego przy pomocy -j CLASSIFY
Do kształtowania tego ruchu możemy wykorzystać iptables z celami -j MARK oraz -j CLASSIFY, mamy też możliwość skorzystania z cgroups, oraz z tc filter. Nie będę tutaj opisywał celu -j MARK, bo to już zostało zrobione przy okazji zabawy z interfejsami IMQ i zasadniczo reguły tam zastosowane nie różnią się zbytnio od tych, które by zostały użyte w tym przypadku, jedyne co, to trzeba by było pozmieniać interfejsy. W każdym razie w tym przypadku jest to wielce nieporęczne i nie ma sobie co tym głowy zawracać.
Cel -j CLASSIFY daje nam możliwość przekierowania ruchu do odpowiednich kolejek bez potrzeby zaprzęgania do tego tc filter -- od razu po przypasowaniu reguły w iptables, pakiet trafia do odpowiedniej kolejki. Poniżej jest przedstawiony prosty setup na ograniczenie ruchu wychodzącego z wykorzystaniem tego celu:
# tc qdisc del dev eth0 root
# iptables -t mangle -F 2> /dev/null
# tc qdisc add dev eth0 root handle 1: htb default 4
# tc class add dev eth0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
# tc class add dev eth0 parent 1:1 classid 1:2 htb rate 450kbit ceil 950kbit prio 5
# tc class add dev eth0 parent 1:1 classid 1:3 htb rate 250kbit ceil 950kbit prio 0
# tc class add dev eth0 parent 1:1 classid 1:4 htb rate 250kbit ceil 950kbit prio 2
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner p2p -j CLASSIFY --set-class 1:2
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner p2p -j ACCEPT
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner morfik -j CLASSIFY --set-class 1:3
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner morfik -j ACCEPT
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner root -j CLASSIFY --set-class 1:3
# iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner root -j ACCEPT
Jakież to proste. xD Większość parametrów użytych w powyższym skrypcie zostały już opisane wcześniej. To co zasługuje na uwagę, to --set-class . Wartość tego parametru musi pasować do wartości classid w tc.
2.2. Kształtowanie ruchu wychodzącego przy pomocy cgroups
Jeśli używamy cgroups, możemy przy jego pomocy skierować ruch do odpowiednich kolejek. Samą instalację (i trochę konfiguracji) cgroups opisałem tutaj. By kontrolować pakiety jakieś aplikacji, musimy dopisać odpowiednią linijkę z uwzględnieniem net_cls w /etc/cgrules.conf . Mój wpis od qbittorrenta wygląda następująco:
*:qbittorrent cpu,net_cls users/qbittorrent/
*:qbittorrent* cpu,net_cls users/qbittorrent/
*:qbittorrent-nox cpu,net_cls users/qbittorrent/
*:qbittorrent-nox* cpu,net_cls users/qbittorrent/
Uzupełniamy skrypt /etc/init.d/cgconfig o poniższe linijki:
mkdir -p $CGDIR/net_cls/users/qbittorrent
echo '1' > $CGDIR/net_cls/users/qbittorrent/cgroup.clone_children
echo '0x00010002' > $CGDIR/net_cls/users/qbittorrent/net_cls.classid
Ten numerek 0x00010002 , to są dwie liczby hexalne, składające się na określenie grupy. Ta ma numer w postaci 1:2. Cztery pierwsze cyfry odpowiadają liczbie przed ":" , cztery kolejne, cyfrze po ":". Tak więc mamy dwie liczby 0001 oraz 0002, co daje 1:2. Każda z pozycji może przyjąć 16 wartości, w końcu to zapisz szesnastkowy.
Ładujemy ponownie skrypt od konfiguracji oraz restartujemy demona cgrulesengd:
# /etc/init.d/cgrulesengd stop
# /etc/init.d/cgconfig restart
# /etc/init.d/cgrulesengd start
Tworzymy kolejki:
# tc qdisc del dev eth0 root 2> /dev/null
# iptables -t mangle -F 2> /dev/null
# tc qdisc add dev eth0 root handle 1: htb default 4
# tc class add dev eth0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
# tc class add dev eth0 parent 1:1 classid 1:2 htb rate 450kbit ceil 950kbit prio 5
# tc class add dev eth0 parent 1:1 classid 1:3 htb rate 250kbit ceil 950kbit prio 0
# tc class add dev eth0 parent 1:1 classid 1:4 htb rate 250kbit ceil 950kbit prio 2
Potrzebujemy jeszcze odpowiedniego przekierowania pakietów do klas w tc. Posłuży nam do tego celu tc filter:
# tc filter add dev eth0 protocol ip parent 1:0 prio 10 handle 1 cgroup
Nie potrzebujemy żadnych wpisów w iptables, wszystko jest zarządzane przez cgroups w w połączeniu z tc. W bmon wygląda to tak:
Interfaces | RX bps pps %| TX bps pps %
lo | 271B 1 | 271B 1
->eth0 | 5.99KiB 62 | 117.03KiB 119
qdisc 1: (htb) | 0 0 | 117.03KiB 119
class 1:1 (htb) | 0 0 | 116.99KiB 118 101%
class 1:2 (htb) | 0 0 | 116.92KiB 117 213%
class 1:3 (htb) | 0 0 | 0 0 0%
class 1:4 (htb) | 0 0 | 63B 0 0%
cls :1 (cgroup) | 0 0 | 0 0
U mnie się pojawiały dziwne problemy przy wykorzystaniu cgroups -- albo nie brało pakietów do odpowiednich grup, mimo, że id kolejki był sprecyzowany w pliku net_cls.classid, albo po pewnym czasie ruch się rozdzielał na kolejkę, do której iść powinien i kolejkę domyślną. W przypadku gdy ruch w ogóle nie szedł do przeznaczonej kolejki, można było przy pomocy echo jeszcze raz zapisać plik net_cls.classid. Nie wiem czemu tak się dzieje, może to tylko u mnie. W każdym razie cele -j MARK i -j CLASSIFY działają bez zarzutu, także jeśli mamy jakieś problemy z cgroups, zawsze możemy z tych targetów skorzystać i przekierować ruch przy pomocy iptables.
3. Interfejsy IFB
Jeśli jednak interesuje nas kontrola ruchu w obie strony ale nie mamy zamiaru przy tym kompilować kernela i iptables z łatami IMQ, możemy skorzystać z natywnego rozwiązania oferowanego przez kernel, czyli interfejsów IFB. Działają na podobnej zasadzie co interfejsy IMQ, również będą dwa, z których jeden będzie łapał i kształtował ruch wychodzący, a drugi ruch skierowany do naszej maszyny. W tym przypadku nie da rady kształtować ruchu przychodzącego przy pomocy iptables. Trzeba do tego celu użyć tc filter. Nie wiem czy da radę nim złapać ruch na qbittorrencie, bo składnia tc filter jest trochę skompilowana ale bez problemu idzie wyłapać wszystko inne.
By móc operować na interfejsach IFB i przy ich pomocy rozgraniczyć pakiety wychodzące od przychodzących, musimy załadować kilka modułów. Dopisujemy zatem do pliku /etc/modules poniższe linijki:
ifb numifbs=2
sch_fq_codel
act_mirred
Moduły z /etc/modules są ładowane na starcie systemu. Jeśli chcemy załadować jakiś moduł w systemie ręcznie, musimy użyć do tego celu modprobe .
Tworzymy kolejki:
# tc qdisc del dev eth0 root
# tc qdisc del dev eth0 ingress
# tc qdisc del dev ifb0 root
# tc qdisc del dev ifb1 root
# iptables -t mangle -F 2> /dev/null
# iptables -t mangle -X IFB-OUT 2> /dev/null
# ip link set ifb0 up
# ip link set ifb1 up
# tc qdisc add dev eth0 parent root handle 1:0 htb
# tc filter add dev eth0 parent 1:0 protocol ip prio 10 u32 match ip dst 0.0.0.0/0 flowid 1:1 action mirred egress redirect dev ifb0
# tc qdisc add dev eth0 handle ffff: ingress
# tc filter add dev eth0 parent ffff: protocol ip prio 10 u32 match ip src 0.0.0.0/0 flowid 2:1 action mirred egress redirect dev ifb1
# tc qdisc add dev ifb0 root handle 1: htb default 4
# tc class add dev ifb0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
# tc class add dev ifb0 parent 1:1 classid 1:2 htb rate 250kbit ceil 950kbit prio 5
# tc class add dev ifb0 parent 1:1 classid 1:3 htb rate 450kbit ceil 950kbit prio 0
# tc class add dev ifb0 parent 1:1 classid 1:4 htb rate 250kbit ceil 950kbit prio 2
# tc qdisc add dev ifb1 root handle 2: htb default 4
# tc class add dev ifb1 parent 2:0 classid 2:1 htb rate 9500kbit ceil 9500kbit
# tc class add dev ifb1 parent 2:1 classid 2:2 htb rate 2500kbit ceil 9500kbit prio 2
# tc class add dev ifb1 parent 2:1 classid 2:3 htb rate 4500kbit ceil 9500kbit prio 0
# tc class add dev ifb1 parent 2:1 classid 2:4 htb rate 2500kbit ceil 9500kbit prio 5
Pakietami wyjściowymi możemy sterować tak jak w poprzednich przypadkach, czyli przez iptables z celami -j MARK oraz -j CLASSIFY i cgroups. By kontrolować ruch wychodzący w tym przypadku, stworzyłem sobie kilka regułek z targetem -j CLASSIFY :
# iptables -t mangle -A POSTROUTING -m owner --gid-owner p2p -j CLASSIFY --set-class 1:2
# iptables -t mangle -A POSTROUTING -m owner --gid-owner p2p -j ACCEPT
# iptables -t mangle -A POSTROUTING -m owner --uid-owner morfik -j CLASSIFY --set-class 1:3
# iptables -t mangle -A POSTROUTING -m owner --uid-owner morfik -j ACCEPT
# iptables -t mangle -A POSTROUTING -m owner --uid-owner root -j CLASSIFY --set-class 1:3
# iptables -t mangle -A POSTROUTING -m owner --uid-owner root -j ACCEPT
I to w zasadzie działa, przynajmniej jeśli chodzi o ruch wychodzący.
Teraz kolej na ruch skierowany do naszej maszyny. Jeśli chcemy dać wyższy priorytet dla stron www, to musimy napisać odpowiednie regułki dla tc filter uwzględniające porty źródłowe 80 i 443, po czym przekierować je do kolejki z niższym numerem prio. Najprościej to można zrobić tak:
# tc filter add dev ifb1 parent 2: protocol ip prio 10 u32 match ip sport 443 0xffff classid 2:3
# tc filter add dev ifb1 parent 2: protocol ip prio 10 u32 match ip sport 80 0xffff classid 2:3
Teraz odpalmy firefoxa i wchodzimy na jakąś stronę www. U mnie bmon pokazuje poniższy wynik:
Interfaces | RX bps pps %| TX bps pps %
->lo | 683B 10 | 683B 10
eth0 | 82.31KiB 140 | 74.95KiB 153
qdisc 1: (htb) | 0 0 | 74.94KiB 153
cls none (u32) | 0 0 | 0 0
cls 8000: (u32) | 0 0 | 0 0
cls 8000:800 (u32) | 0 0 | 0 0
qdisc ffff: (ingress) | 0 0 | 80.39KiB 140
cls none (u32) | 0 0 | 0 0
cls 8000: (u32) | 0 0 | 0 0
cls 8000:800 (u32) | 0 0 | 0 0
ifb0 | 74.94KiB 153 | 74.94KiB 153
qdisc 1: (htb) | 0 0 | 74.94KiB 153
class 1:1 (htb) | 0 0 | 74.89KiB 153 65%
class 1:2 (htb) | 0 0 | 67.92KiB 98 223%
class 1:3 (htb) | 0 0 | 6.81KiB 51 12%
class 1:4 (htb) | 0 0 | 165B 2 1%
ifb1 | 239.97KiB 241 | 239.97KiB 241
qdisc 2: (htb) | 0 0 | 239.97KiB 241
class 2:1 (htb) | 0 0 | 241.08KiB 242 21%
class 2:2 (htb) | 0 0 | 0 0 0%
class 2:3 (htb) | 0 0 | 236.88KiB 185 43%
class 2:4 (htb) | 0 0 | 4.20KiB 56 1%
cls none (u32) | 0 0 | 0 0
cls 8000: (u32) | 0 0 | 0 0
cls 8000:800 (u32) | 0 0 | 0 0
cls 8000:801 (u32) | 0 0 | 0 0
Ten 0xffff jest to maska dla portów, podobna do tej dla adresów ip. W przypadku ustawienia ffff, oznacza to tylko jeden port. Można oczywiście ustawić zakres portów przez zmianę tej maski ale jest to trochę upierdliwe i ja tego raczej w pamięci nie policzę. W zrozumieniu składni u32 mogą przydatne okazać się te linki: man ematch oraz man u32 filter.
Jak widać jest to trochę skomplikowane ale dla naszych potrzeb spokojnie wystarczą te cztery parametry: src, dst, sport i dport. By sprecyzować adres ip zamiast portu, wstawiamy dst 222.28.1.40/32 w miejsce sport 80 0xffff. Reguła będzie odnosić się do adresu docelowego 222.28.1.40. Bu ustawić zakres adresów, wystarczy zmienić maskę, podobnie jak w przypadku portów.
Jeśli jednak chcielibyśmy się pobawić w coś bardziej zaawansowanego z wykorzystaniem selektora u32 (i innych) warto nauczyć się przeliczać liczy hex, binarne i decymalne. Poniżej jest kilka przykładów.
Zamiana hex na dec:
$ echo "obase=10;ibase=16;C0A80100" | bc
3232235776
Zamiana hex na bi
$ echo "obase=2;ibase=16;C0A80100" | bc
11000000101010000000000100000000
Zamiana bi na dec:
$ echo "obase=10;ibase=2;11000000101010000000000100000000" | bc
3232235776
I tak dalej. Kluczowe to odpowiednio dobrać obase oraz ibase, które definiują format liczb -- ibase to format wejściowy, a obase wyjściowy.
Warto też się zaznajomić z wyglądem nagłówków tcp i ip.
Podobnie jak w przypadku interfejsów IMQ, możemy sobie zrobić skrypt startowy z użytymi powyżej regułami. Mój skrypt prezentuje się tak:
### BEGIN INIT INFO
# Provides: ifb
# Required-Start: mountkernfs $local_fs
# Required-Stop: $local_fs
# Should-Start:
# Should-Stop:
# Default-Start: S
# Default-Stop:
# X-Start-Before:
# X-Stop-After:
# Short-Description: tc
# Description: tc configuration for ifb interfaces
### END INIT INFO
SCRIPTNAME="ifb"
load_tc()
{
echo -n "Loading tc configuration... "
tc qdisc add dev eth0 parent root handle 1:0 htb
tc filter add dev eth0 parent 1:0 protocol ip prio 10 u32 match ip dst 0.0.0.0/0 flowid 1:1 action mirred egress redirect dev ifb0
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip prio 10 u32 match ip src 0.0.0.0/0 flowid 2:1 action mirred egress redirect dev ifb1
tc qdisc add dev ifb0 root handle 1: htb default 5
tc class add dev ifb0 parent 1:0 classid 1:1 htb rate 950kbit ceil 950kbit
tc class add dev ifb0 parent 1:1 classid 1:2 htb rate 150kbit ceil 950kbit prio 5
tc class add dev ifb0 parent 1:1 classid 1:3 htb rate 200kbit ceil 950kbit prio 4
tc class add dev ifb0 parent 1:1 classid 1:4 htb rate 450kbit ceil 950kbit prio 0
tc class add dev ifb0 parent 1:1 classid 1:5 htb rate 150kbit ceil 950kbit prio 2
tc qdisc add dev ifb1 root handle 2: htb default 5
tc class add dev ifb1 parent 2:0 classid 2:1 htb rate 9500kbit ceil 9500kbit
tc class add dev ifb1 parent 2:1 classid 2:2 htb rate 1500kbit ceil 9500kbit prio 2
tc class add dev ifb1 parent 2:1 classid 2:3 htb rate 2000kbit ceil 9500kbit prio 4
tc class add dev ifb1 parent 2:1 classid 2:4 htb rate 4500kbit ceil 9500kbit prio 0
tc class add dev ifb1 parent 2:1 classid 2:5 htb rate 1500kbit ceil 9500kbit prio 5
tc filter add dev ifb1 parent 2: protocol ip prio 7 u32 match ip sport 80 0xffff classid 2:3
tc filter add dev ifb1 parent 2: protocol ip prio 8 u32 match ip sport 443 0xffff classid 2:3
tc filter add dev ifb1 parent 2: protocol ip prio 20 u32 match ip sport 993 0xffff classid 2:2
tc filter add dev ifb1 parent 2: protocol ip prio 21 u32 match ip sport 143 0xffff classid 2:2
tc filter add dev ifb1 parent 2: protocol ip prio 22 u32 match ip sport 465 0xffff classid 2:2
tc filter add dev ifb1 parent 2: protocol ip prio 35 u32 match ip sport 5222 0xffff classid 2:2
tc filter add dev ifb1 parent 2: protocol ip prio 36 u32 match ip sport 5223 0xffff classid 2:2
echo "done!"
}
load_iptables()
{
echo -n "Loading iptables configuration... "
iptables -t mangle -A POSTROUTING -o eth0 -m length --length 40:68 -j CLASSIFY --set-class 1:3
iptables -t mangle -A POSTROUTING -o eth0 -m length --length 40:68 -j ACCEPT
iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner p2p -j CLASSIFY --set-class 1:2
iptables -t mangle -A POSTROUTING -o eth0 -m owner --gid-owner p2p -j ACCEPT
iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner morfik -j CLASSIFY --set-class 1:4
iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner morfik -j ACCEPT
iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner root -j CLASSIFY --set-class 1:4
iptables -t mangle -A POSTROUTING -o eth0 -m owner --uid-owner root -j ACCEPT
echo "done!"
}
interfaces_up()
{
ip link set ifb0 up
ip link set ifb1 up
}
flush_tc()
{
echo -n "Removing tc qdiscs... "
tc qdisc del dev eth0 root
tc qdisc del dev eth0 ingress
tc qdisc del dev ifb0 root
tc qdisc del dev ifb1 root
echo "done!"
}
flush_iptables()
{
echo -n "Flushing mangle table... "
iptables -t mangle -F 2> /dev/null
echo "done!"
}
interfaces_down()
{
ip link set ifb0 down
ip link set ifb1 down
}
case "$1" in
start)
interfaces_up && load_tc && load_iptables || exit 1
;;
stop)
flush_tc && flush_iptables && interfaces_down || exit 1
;;
force-reload|restart)
flush_tc && flush_iptables && interfaces_down && sleep 1
interfaces_up && load_tc && load_iptables
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart}"
exit 1
;;
esac
exit 0
Edytujemy jeszcze nagłówki skryptu /etc/init.d/networking ale pierw musimy go usunąć z autostartu:
# update-rc.d networking remove
Teraz dodajemy w odpowiednie sekcje ifb:
### BEGIN INIT INFO
# Provides: networking ifupdown
# Required-Start: mountkernfs $local_fs urandom
# Required-Stop: $local_fs
# Should-Start: peerblock iptables-persistent ifb
# Should-Stop: peerblock iptables-persistent ifb
# Default-Start: S
# Default-Stop: 0 6
# Short-Description: Raise network interfaces.
# Description: Prepare /run/network directory, ifstate file and raise network interfaces, or take them down.
### END INIT INFO
Dodajemy oba skrypty do autostartu:
# update-rc.d networking defaults
# update-rc.d ifb defaults
4. Statystyki ruchu
Istnieje kilka narzędzi, które pokazują rozpiskę aktualnej konfiguracji traffic control.
Inforamcje o qdisc:
# tc -d -s qdisc show dev eth0
qdisc htb 1: root refcnt 2 r2q 10 default 0 direct_packets_stat 1044925 ver 3.17 direct_qlen 1000
Sent 161806806 bytes 1065008 pkt (dropped 11997, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
qdisc ingress ffff: parent ffff:fff1 ----------------
Sent 1280866443 bytes 1058357 pkt (dropped 7425, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
# tc -d -s qdisc show dev ifb0
qdisc htb 1: root refcnt 2 r2q 10 default 5 direct_packets_stat 0 ver 3.17 direct_qlen 32
Sent 163118498 bytes 1074893 pkt (dropped 12069, overlimits 2223331 requeues 0)
backlog 0b 28p requeues 0
# tc -d -s qdisc show dev ifb1
qdisc htb 2: root refcnt 2 r2q 10 default 5 direct_packets_stat 0 ver 3.17 direct_qlen 32
Sent 1302094010 bytes 1057023 pkt (dropped 7561, overlimits 1957564 requeues 0)
backlog 0b 29p requeues 0
Informacje o filtrach:
tc -s -d -p filter show dev ifb1
filter parent 2: protocol ip pref 7 u32
filter parent 2: protocol ip pref 7 u32 fh 800: ht divisor 1
filter parent 2: protocol ip pref 7 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 2:3 (rule hit 1388968 success 6829)
match sport 80 (success 6829 )
filter parent 2: protocol ip pref 8 u32
filter parent 2: protocol ip pref 8 u32 fh 801: ht divisor 1
filter parent 2: protocol ip pref 8 u32 fh 801::800 order 2048 key ht 801 bkt 0 flowid 2:3 (rule hit 1382139 success 5130)
match sport 443 (success 5130 )
filter parent 2: protocol ip pref 20 u32
filter parent 2: protocol ip pref 20 u32 fh 802: ht divisor 1
filter parent 2: protocol ip pref 20 u32 fh 802::800 order 2048 key ht 802 bkt 0 flowid 2:2 (rule hit 1377009 success 1940)
match sport 993 (success 1940 )
filter parent 2: protocol ip pref 21 u32
filter parent 2: protocol ip pref 21 u32 fh 803: ht divisor 1
filter parent 2: protocol ip pref 21 u32 fh 803::800 order 2048 key ht 803 bkt 0 flowid 2:2 (rule hit 1375069 success 105)
match sport 143 (success 105 )
filter parent 2: protocol ip pref 22 u32
filter parent 2: protocol ip pref 22 u32 fh 804: ht divisor 1
filter parent 2: protocol ip pref 22 u32 fh 804::800 order 2048 key ht 804 bkt 0 flowid 2:2 (rule hit 1374964 success 0)
match sport 465 (success 0 )
filter parent 2: protocol ip pref 35 u32
filter parent 2: protocol ip pref 35 u32 fh 805: ht divisor 1
filter parent 2: protocol ip pref 35 u32 fh 805::800 order 2048 key ht 805 bkt 0 flowid 2:2 (rule hit 1374964 success 111)
match sport 5222 (success 111 )
filter parent 2: protocol ip pref 36 u32
filter parent 2: protocol ip pref 36 u32 fh 806: ht divisor 1
filter parent 2: protocol ip pref 36 u32 fh 806::800 order 2048 key ht 806 bkt 0 flowid 2:2 (rule hit 1374853 success 113)
match sport 5223 (success 113 )
root:~# tc -s -d -p filter show dev eth0
filter parent 1: protocol ip pref 10 u32
filter parent 1: protocol ip pref 10 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 10 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 (rule hit 2787825 success 2787825)
match IP dst 0.0.0.0/0 (success 2787825 )
action order 1: mirred (Egress Redirect to device ifb0) stolen
index 1 ref 1 bind 1 installed 1852 sec
Action statistics:
Sent 212247685 bytes 1425206 pkt (dropped 0, overlimits 14476 requeues 0)
backlog 0b 0p requeues 0
Jeśli komuś niezbyt odpowiada zapis tekstowy i wolałby zobaczyć rozpiskę w postaci jakiegoś wykresu czy coś, to istnieje narzędzie, które na podstawie tych literek i cyferek zrobi nam miły dla oka obrazek. Sam programik znalazłem na tym blogu, a jego kod jest dostępny pod tym adresem.
Program wywołujemy przez:
$ ./tcviz.py eth0 | dot -Tpng > tc.png
5. Jak odpalać qbittorrenta?
Przez parę dni zastanawiałem się jak ogarnąć uruchamianie qbittorrenta, bo pakiety trzeba przecie łapać albo po uid albo po gid. Propozycji było kilka. Pierwsza z nich zakładała wykorzystanie sudo ale w tym przypadku jest trochę za dużo roboty -- trzeba pilnować uprawnień plików, trzeba przenosić konfigurację i tworzyć nowego użytkownika, bawić się aclem. Niby nic wielkiego ale istnieje inny, dużo prostszy sposób, który wykorzystuje grupy. Normalnie proces jest odpalany z prawami użytkownika, który ten proces uruchamia. Dodatkowo grupa główna tego użytkownika jest brana pod uwagę przy uruchamianiu procesu, w efekcie czego proces jest uruchomiony jako użytkownik morfik i grupa również morfik. Ale przecie grupy nie zostały stworzone po to by używać cały czas tylko jednej z nich, a biorąc pod uwagę fakt, że pakiety możemy filtrować również po grupie, uruchomimy proces z inną grupą.
Tworzymy zatem nową grupę p2p i dodajemy swojego użytkownika do niej:
# groupadd -g 5004 p2p
# adduser morfik p2p
By móc bez hasła używać id innej grupy musimy być podpięci pod tę grupę, można to sprawdzić wklepując do terminala id.
By odpalić proces z inną grupą, wydajemy poniższe polecenie:
$ nice -n 19 ionice -c 2 -n 7 sg p2p -c qbittorrent
Kluczowe w powyższej linijce jest sg. To za jego pomocą ustawiamy procesowi odpowiednią grupę. Dodatkowo, jest jeszcze zaprzęgnięty do pracy nice oraz ionice -- mają na celu odciążenie procesora i dysku w przypadku gdyby inne procesy dość gwałtownie wykorzystywały zasoby maszyny.
QBittorrent posiada klienta nox, czyli coś co działa bez potrzeby odpalania X-ów, a zarządzać nim można przez www. Zjada 2-3 krotnie mniej zasobów niż jego graficzny odpowiednik, co powinno ucieszyć ludzi, którzy... mają mniej RAMu niż ja. xD Posiada on także skrypt startowy, choć domyślnie nie jest dołączony w pakiecie. Wykorzystuje on do uruchomienia qbittorrenta start-stop-daemon -- standardowy mechanizm odpalania procesów startowych w debianie i można przy jego pomocy także sprecyzować grupę. Skrypt prezentuje się tak:
#! /bin/sh
### BEGIN INIT INFO
# Provides: qbittorrent-nox
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Should-Start: iptables-persistent pgl
# Should-Stop: iptables-persistent pgl
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Starts QBittorrent
# Description: Start qbittorrent-nox on start. Change USER= before running
### END INIT INFO
# Author: Jesper Smith
#
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="QBittorrent"
NAME=qbittorrent-nox
DAEMON=/usr/bin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/qbittorrent-nox
USER=morfik:p2p
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
if [ "$START" = "no" ]; then
echo "Autostart wyłączony -- zobacz /etc/default/$NAME ."
exit 0
fi
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon -c $USER -b -t --start --quiet --exec $DAEMON \
|| return 1
start-stop-daemon -c $USER -b --start --quiet --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
sleep 1
}
#
# Function that stops the daemon/service
#
do_stop()
{
start-stop-daemon -c $USER --quiet --stop --exec $DAEMON
sleep 2
return "$?"
}
VERBOSE="yes"
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
Jedyne co musimy dostosować to linijkę z USER=morfik:p2p. Dodajemy skrypt do autostartu przy pomocy:
# update-rc.d qbittorrent-nox defaults
I będzie sobie działał w tle cały czas od chwili załadowania się systemu.
Więcej informacji można znaleźć w poniższych linkach:
https://wiki.gentoo.org/wiki/Traffic_shaping
https://wiki.archlinux.org/index.php/Advanced_Traffic_Control
http://lartc.org/lartc.html#LARTC.QDISC
http://bromirski.net/docs/translations/lartc-pl.html#LARTC.QDISC (PL)
http://wampir.mroczna-zaloga.org/archives/21-o-ksztaltowaniu-ruchu.html
http://robert.nowotniak.com/en/security/htb/
https://www.kernel.org/doc/Documentation/cgroups/net_cls.txt
http://edseek.com/~jasonb/articles/traffic_shaping/index.html
http://manpages.debian.net/cgi-bin/man.cgi?query=tc&sektion=8&apropos=0&manpath=Debian+7.0+wheezy&locale=
http://www.tamos.net/~rhay/wp/overhead/overhead.htm
http://linux-ip.net/articles/Traffic-Control-HOWTO/intro.html