Konfiguracja interfejsów IFB w linux'ie

Spis treści

Ten wpis również będzie poświęcony tematyce kontroli i kształtowania ruchu sieciowego w linux'ie, z tym, że ograniczymy się tutaj do konfiguracji interfejsów IFB. Działają one na podobnej zasadzie co interfejsy IMQ. Niewątpliwą zaletą interfejsów IFB jest fakt, że są one natywnie wspierane przez kernel linux'a, przez co ich obsługa jest dziecinnie prosta. Wadą jest z kolei to, że nie do końca damy radę kształtować ruch przychodzący do naszej maszyny. Tak czy inaczej, postaramy się skonfigurować te interfejsy i zobaczymy co z nich idzie wycisnąć.

Konfiguracja modułu ifb

Interfejsy IFB powstały w zamierzeniu jako alternatywa dla istniejących wcześniej interfejsów IMQ. Różnica między tymi dwoma mechanizmami jest taka, że IFB ma problemy z kształtowaniem ruchu docierającego do naszego komputera. Właściwie to nie jest w stanie tego zrobić. Możemy, co prawda, nadać określone limity zostawiając część łącza do dyspozycji innym procesom ale nie jesteśmy póki co w stanie tak kształtować ruchu przychodzącego, jak to się odbywa w przypadku tego wychodzącego. Nie jest jednak aż tak źle jak mogłoby się wydawać, zatem przejdźmy do konfiguracji odpowiednich modułów.

By móc operować na interfejsach IFB i przy ich pomocy rozgraniczyć pakiety wychodzące od przychodzących, musimy załadować kilka modułów. Najlepiej to zrobić w pliku /etc/modules , skąd te moduły będą ładowane automatycznie przy każdym rozruchu komputera. Na nasze potrzeby musimy tam dopisać te poniższe linijki:

ifb
xt_connmark
sch_fq_codel
act_mirred

Ewentualną konfigurację dla tych modułów dodajemy do pliku /etc/modprobe.d/modules.conf . W tym przypadku jest to:

options ifb numifbs=2

Jeśli chcemy załadować jakiś moduł w systemie ręcznie, musimy użyć do tego celu modprobe .

Rozdzielanie ruchu

Mając załadowane potrzebne moduły, możemy przejść do tworzenia kolejek z wykorzystaniem narzędzia tc . Ruch, który przetacza się przez interfejs karty sieciowej w naszym komputerze jest wymieszany. Musimy zatem rozdzielić dwa kanały download i upload oraz przekierować je na osobne wirtualne interfejsy sieciowe ifb0 oraz ifb1 . Robimy to w poniższy sposób:

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

Zarządzanie ruchem wychodzącym (egress)

Mając rozdzielony ruch, możemy teraz założyć kolejki. Najpierw zajmijmy się ruchem wychodzącym:

tc qdisc add dev ifb0 root handle 1: htb default 400 r2q 10
      tc class add dev ifb0 parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit quantum 60000
            tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 980kbit ceil 980kbit
                  tc class add dev ifb0 parent 1:10 classid 1:200 htb rate 480kbit ceil 980kbit prio 0
                  tc class add dev ifb0 parent 1:10 classid 1:300 htb rate 200kbit ceil 980kbit prio 2
                  tc class add dev ifb0 parent 1:10 classid 1:400 htb rate 150kbit ceil 980kbit prio 4
                  tc class add dev ifb0 parent 1:10 classid 1:500 htb rate 150kbit ceil 980kbit prio 5
            tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 999mbit ceil 999mbit quantum 60000
                  tc class add dev ifb0 parent 1:20 classid 1:1000 htb rate 99mbit ceil 999mbit prio 1 quantum 60000

Pakietami wyjściowymi możemy sterować przez iptables z celami -j MARK i -j CLASSIFY oraz przy pomocy cgroups. My tutaj będziemy dla przykładu markować pakiety w iptables . Do tego celu musimy utworzyć kilka regułek w tablicy mangle , przykładowo:

iptables -t mangle -N qos_egress
iptables -t mangle -N qos_ingress

iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A PREROUTING -i bond0 -j qos_ingress
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark

iptables -t mangle -A POSTROUTING -j CONNMARK --restore-mark
iptables -t mangle -A POSTROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A POSTROUTING -o bond0 -j qos_egress
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark

Te powyższe reguły zlikwidują niepotrzebną pracę przy oznaczaniu pakietów, bo marki będą aplikowane na poszczególne połączenia, a nie na pojedyncze pakiety. Musimy jednak dodać kilka dodatkowych reguł, które będą oznaczać pakiety mające mark=0 . Zwykle są to pakiety od nowych połączeń. Poniżej kilka przykładów:

iptables -t mangle -A qos_egress -s 192.168.0.0/16 -d 192.168.0.0/16 -j MARK --set-mark 10
iptables -t mangle -A qos_egress -s 192.168.0.0/16 -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A qos_egress -m owner --gid-owner p2p -j MARK --set-mark 5
iptables -t mangle -A qos_egress -m owner --gid-owner p2p -j RETURN
iptables -t mangle -A qos_egress -m owner --gid-owner morfik -j MARK --set-mark 2
iptables -t mangle -A qos_egress -m owner --gid-owner morfik -j RETURN
iptables -t mangle -A qos_egress -m owner --gid-owner root -j MARK --set-mark 3
iptables -t mangle -A qos_egress -m owner --gid-owner root -j RETURN
iptables -t mangle -A qos_egress -s 192.168.10.0/24 -j MARK --set-mark 4
iptables -t mangle -A qos_egress -s 192.168.10.0/24 -j RETURN

Oznaczanie pakietów robimy w osobnych łańcuchach. W przypadku ruchu wychodzącego jest to łańcuch qos_egress . Chodzi generalnie o to, że po tym jak pakiet trafi w regułę, która nada mu oznaczenie, to nie kończy on przemierzać następnych reguł w danym łańcuchu. Dlatego do markowania wykorzystujemy osobne łańcuchy, które są wywoływane z głównych łańcuchów iptables . Reguły z -j RETURN zwracają pakiety do łańcucha nadrzędnego i tam pakiet sobie leci już dalej. Skracają one w ten sposób drogę pakietu przez filtr, co przekłada się na szybsze jego przetworzenie.

Musimy jeszcze powiązać reguły iptables z filtrem tc . Do tego celu potrzebujemy te poniższe reguły:

tc filter add dev ifb0 protocol ip parent 1:0 prio 5 handle 2 fw classid 1:200
tc filter add dev ifb0 protocol ip parent 1:0 prio 90 handle 3 fw classid 1:300
tc filter add dev ifb0 protocol ip parent 1:0 prio 20 handle 4 fw classid 1:400
tc filter add dev ifb0 protocol ip parent 1:0 prio 10 handle 5 fw classid 1:500
tc filter add dev ifb0 protocol ip parent 1:0 prio 100 handle 10 fw classid 1:1000

I to w zasadzie tyle, przynajmniej jeśli chodzi o ruch wychodzący. Pamiętajmy, by pilnować oznaczeń, tak by 5 fw pasował do -j MARK --set-mark 5 w iptables . Podobnie z classid 1:500 .

Zarządzanie ruchem przychodzącym (ingress)

W przypadku interfejsów IFB mamy nieco ograniczone pole manewru i nie wszystkie mechanizmy oznaczeń pakietów jak i ich kolejkowania działają w przypadku ruchu docierającego do danego hosta w sieci. Podobnie jak w przypadku ruchu wychodzącego, musimy stworzyć kilka kolejek dla pakietów przychodzących, które będą kierowane do wirtualnego interfejsu ifb1 :

tc qdisc add dev ifb1 root handle 2: htb default 400 r2q 100
      tc class add dev ifb1 parent 2:0 classid 2:1 htb rate 1000mbit ceil 1000mbit quantum 60000
            tc class add dev ifb1 parent 2:1 classid 2:10 htb rate 14500kbit ceil 14500kbit
                  tc class add dev ifb1 parent 2:10 classid 2:200 htb rate 4500kbit ceil 14500kbit prio 0
                  tc class add dev ifb1 parent 2:10 classid 2:300 htb rate 3000kbit ceil 14500kbit prio 2
                  tc class add dev ifb1 parent 2:10 classid 2:400 htb rate 4500kbit ceil 14500kbit prio 4
                  tc class add dev ifb1 parent 2:10 classid 2:500 htb rate 2500kbit ceil 14500kbit prio 5
            tc class add dev ifb1 parent 2:1 classid 2:20 htb rate 985mbit ceil 985mbit quantum 60000
                  tc class add dev ifb1 parent 2:20 classid 2:1000 htb rate 85mbit ceil 985mbit prio 1 quantum 60000

Teraz zaczynają się problemy, bo musimy napisać odpowiednie regułki dla tc filter , który przekieruje część ruchu do odpowiednich kolejek. Operowanie na filtrze tc nie należy do prostych rzeczy. W zrozumieniu składni selektora u32 mogą przydatne okazać się te linki: man tc-ematch oraz man tc-u32. Tak czy inaczej załóżmy, że chcemy dać na osobną kolejkę ruch http (port 80) i https (port 443). Piszemy zatem regułki uwzględniające porty źródłowe 80 i 443:

tc filter add dev ifb1 parent 2:0 protocol ip prio 10 u32 match ip sport 80 0xffff classid 2:300
tc filter add dev ifb1 parent 2:0 protocol ip prio 10 u32 match ip sport 443 0xffff classid 2:300

Pamiętajmy też, by odpowiednio ustawić priorytet ( prio ) dla tego filtra. Im niższy numer, tym wyżej zlokalizowany będzie ten filtr w tablicy filtrów tc . Na kilka słów wyjaśnienia zasługuje jeszcze maska 0xffff dla portów. Jest ona 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. Jak widać jest to trochę skomplikowane ale dla naszych potrzeb spokojnie wystarczą te cztery parametry: src , dst , sport , dport i protocol . 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. Z kolei przy protocol możemy wydzielić cały protokół w oparciu o ich numerki.

Teraz odpalmy jakąś przeglądarkę internetową, np. Firefox'a, w celach testowych. Pakiety powinny wędrować do kolejek 1:200 oraz 2:300. Poniżej przykład:

ksztaltowanie-ruchu-interfejsy-ifb-tc

Statystyki ruchu

Wyżej wykorzystaliśmy narzędzie bmon do pokazania w czasie rzeczywistym do jakich kolejek trafiają pakiety. Czasami przy pisaniu reguł mogą pojawić się błędy i pakiety nie będą trafiać tam gdzie byśmy sobie tego życzyli. Dlatego też warto zaznajomić się się nieco ze statystykami, które potrafią pokazać min. to ile trafień ma dana reguła. Poniżej kilka poleceń, które pokazują rozpiskę aktualnej konfiguracji.

Inforamcje o qdisc:

# tc -d -s qdisc show dev ifb1

Informacje o filtrach:

# tc -s -d -p filter show dev ifb1

Informacje na temat klas:

# tc -s -d class show dev ifb1

Jeśli komuś niezbyt odpowiada zapis tekstowy i wolałby zobaczyć rozpiskę w postaci jakiegoś wykresu, to istnieje narzędzie tcviz, które na podstawie tych wszystkich literek i cyferek zrobi nam miły dla oka obrazek.

Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.