Toto je pouze lokální záložní kopie odkazovaného článku, doporučuji navštívit původní odkaz. Připravujete se o případnou plodnou diskuzi pod článkem.

HTB - automatizujeme

Lenost, lenost a zase lenost. Každý správný administrátor se vyznačuje právě touto vlastností. Neberte to ale negativně. Právě lenost je totiž hnacím motorem veškerého pokroku a dnes vám ukážu, jak mě právě ta moje lenost donutila vymyslet několik "zlepšováků", které teď hojně využívám při shapování.

Nejdřív ale novinky. Kdo si hraje, nezlobí, a já si od posledního článku zase trochu hrál. Mimo jiné jsem zjistil, a teď nevím, jestli se to týká pouze ADSL, že je skutečně velice důležité shapovat i odchozí provoz. Jediná instance bittorrentu je schopna úplně "zasvinit" odchozí kanál, což má bohužel smrtonosné následky v podobě nulového (!) downloadu. Dále je docela vhodné dělit šířku pásma nejen podle jednotlivých uživatelů, ale poskytnout i jakési rozdělení podle priorit v rámci každého jednouživatelského "krajíce". Jak jsem již psal v některém z předchozích článků, zahazování packetů má ten správný výchovný účinek jen na TCP spojení, takže ničit funkční UDP nebo dokonce ICMP (nebo jiné) packety není to pravé ořechové. Likvidovat TCP packety s příznakem SYN nebo ACK také není příliš vhodné, protože jsou to právě tyto, které dávají odesílateli informaci o úspěšném doručení dat a nenutí ho tak znovu zbytečně vysílat.

Vyřešme to tedy následovně. Vytvořme hlavní HTB qdisc (identicky jako v minulých článcích), který se bude starat pouze o férové rozdělení pásma mezi jednotlivé uživatele (tedy IP adresy). Každou z jeho tříd ale ještě obohatíme o PRIO qdisc, který se postará o to, že nebudeme házet všechna data "do jednoho pytle". Qdisc PRIO je jednoduchá disciplína, která při navázání na nějaký uzel (klidně i uzel "root") vytvoří pevný počet tříd, o něž se pak stará, a to následujícím způsobem. Když nastane čas odeslání (fyzicky přes interface), tento qdisc se podívá do své třídy 1 a všechny packety, které zde najde, odešle. Stejně pokračuje přes třídy 2, 3 atd., dokud nedojde až k té poslední. To se však většinou nestane, protože v našem případě pověsíme qdisc PRIO na některou z tříd qdiscu HTB (taková třída má omezení na množství dat, které propustí), a když dojde k dosažení limitu pro data, HTB se prostě qdiscu PRIO přestane ptát, a ten si může svá neodeslaná data vetknout za klobouk. Pokud pak dokážeme správně zařazovat packety do jednotlivých tříd qdiscu PRIO, máme zaručeno, že např. SSH provoz bude vždy odbaven přednostně, čímž dosáhneme mnohem živějšího dojmu při dálkové administraci. Všechny priority ale budou platit pouze v rámci jednotlivých tříd qdiscu HTB, takže i když bude uživatel A SSHčkovat jako o život, uživateli B poběží prohlížení webu beze změny.

Naznačme si tedy výslednou strukturu (zjednodušeně):

HTB root - omezení 256kbit (celá šířka)[1:0 a 1:1]
  HTB class 1 - uživatel A - omezení (256/3)kbit [1:11 a 11:0]
    PRIO 1 - ICMP provoz [11:1]
    PRIO 2 - SSH provoz [11:2]
    PRIO 3 - ostaní provoz [11:3]
  HTB class 2 - uživatel B - omezení (256/3)kbit [1:12 a 12:0]
    PRIO 1 - ICMP provoz [12:1]
    PRIO 2 - SSH provoz [12:2]
    PRIO 3 - ostaní provoz [12:3]
  HTB class 3 - uživatel C - omezení (256/3)kbit [1:13 a 13:0]
    PRIO 1 - ICMP provoz [13:1]
    PRIO 2 - SSH provoz [13:2]
    PRIO 3 - ostaní provoz [13:3]

Již při prvním pohledu je jasné, že něco takového přece nebudeme dělat "ručně", ale využijeme dvou (pro masochisty více) vnořených cyklů v BASHi (už chápete název článku?). K tomu nám poslouží skriptík "shaping-buildtree". Jeho podrobným popisem se zde zabývat nebudu, neboť mám pocit, že ho pochopí snad každý. Snad jen uvedu, že očekává nadefinovavé parametry $GRPS (počet uživatelů/skupin), $IFACE (interface), $RATE (celková šířka pásma) a $BANDS (počet podtříd, tzv. "bands", pro qdisc PRIO). Po jeho provedení bude na definovaném rozhraní vytvořena stromová struktura tak, jak jsem ji naznačil výše s očíslováním uvedeným v hranatých závorkách. V tomto stromu bude definováno i filtrování podle značky packetu. Když přijde packet se značkou 43 (značkování zařídíme jinde), budeme vědět, že ho máme "šoupnout" do třetího bandu čtvrtého uživatele.

Tím bylo vše důležité řečeno. Rozebírat skripty řádek po řádku je podle mého zbytečné, mám pocit, že jsou dostatečně "čtivé". Tímto je tedy zveřejňuji (shaping, shaping-in, shaping-out, shaping-buildtree) a další studium nechávám na vás.

Tuhle část už číst nemusíte :-). Vážně... Pokud jste přece jenom až zde, není vám ve skriptech něco jasné... Zkusím tedy odhadnout FAQ:

Proč cyklus v shaping-in/out pro značkování packetů probíhá "pozpátku"?

Pozorně se podívejte na seznam IP adres a pak na značkovací pravidla. Díky tomu, že jump na MARK neskončí porovnávání dalších pravidel, je nutné projíždět pravidla od těch nejobecnějších po nejkonkrétnější. Ve stávajíci implementaci se tedy nejdříve všechny packety z IP rozsahu LANu označí podle pravidel, ale později se event. ještě přeznačkují podle (pokud patří ke konkrétnímu IPčku).

Proč jsou ve vnitřním cyklu v shaping-buildtree dva filtry?

Protože je to potřeba. Nějakou dobu mi trvalo na to přijít, ale nakonec jsem zjistil docela zásadní skutečnost, a to tu, že není možné "posílat" packety (pomocí "filter flowid") mezi jednotlivými qdiscy. Je nutné nejdříve takovýto packet dostat na nejkrajnější třídu daného qdiscu. Z této třídy pak packet automaticky "přejde" do qdiscu, který je na tuto třídu navázán, a pokud má takovýto qdisc své vlastní podtřídy, je nutné vytvořit novou sadu filtrů.

Proč se proměnná $GRPS nejmenuje $GROUPS?

Vážně vás to zajímá? :-) Rozumně vám to nevysvětlím, ale jinak by to prostě nefungovalo. $GROUPS se zdá být nějakým rezervovaným slovem shellu, a ať tuto proměnnou plním čímkoliv, nedostanu z ní výsledně nic... :-(

Další dotazy očekávám v diskusi, ale bohužel nemohu zaručit, že si najdu čas na ně odpovědět...