Target MARK w iptables (sumowanie oznaczeń)

Spis treści

Pakiety i połączenia można oznaczać w iptables za pomocą target MARK oraz CONNMARK . Ta właściwość tego filtra jest znana chyba większość użytkowników linux'a. Z reguły nie interesujemy się zbytnio szczegółami oznaczania pakietów/połączeń, bo zwykle jest ono przeprowadzane prawidłowo. Niemniej jednak, są przypadki, w których markery nie będą ustawiane poprawnie. Chodzi o sytuacje, gdzie mamy do czynienia z kilkoma mechanizmami oznaczającymi pakiety. Dla przykładu weźmy sobie kształtowanie ruchu za pomocą narzędzia tc oraz failover czy load balancing łącza w oparciu o różne tablice routingu. W obu tych mechanizmach zwykle używa się markowania pakietów w iptables . Co się jednak dzieje z takim pakietem, gdy przechodzi przez reguły zarówno pierwszego jak i drugiego mechanizmu? To właśnie spróbujemy ustalić w tym wpisie.

Różnica między target MARK i CONNMARK oraz domyślna maska

We wstępie wspomniałem o oznaczaniu pojedynczych pakietów oraz całych połączeń. W iptables wykorzystuje się do tych celów dwa targety: MARK oraz CONNMARK. Target MARK nakłada oznaczenie na pojedynczy pakiet. Używa się go głównie przy dopasowaniu nowych połączeń, tj. pakietów w stanie NEW. Natomiast z targetu CONNMARK korzysta się przy zapisywaniu i odtwarzaniu oznaczeń w przypadku pakietów przypisanych do nawiązanego już połączenia. W ten sposób jesteśmy w stanie odciążyć maszynę i zapobiec ciągłemu przepisywaniu oznaczeń w przypadku połączeń, które już posiadają znacznik. Te markery są widoczne w tablicy conntrack'a pod /proc/net/nf_conntrack lub /proc/net/ip_conntrack , w zależności od wykorzystywanego modułu do śledzenia połączeń. Poniżej przykład takiego wpisu:

ipv4     2 tcp      6 431994 ESTABLISHED src=192.168.1.150 dst=52.11.112.70 sport=54280 dport=443
src=52.11.112.70 dst=192.168.1.150 sport=443 dport=54280 [ASSURED] mark=2 zone=0 use=2

W tym przypadku, mamy połączenie między maszynami o adresach 192.168.1.150 oraz 52.11.112.70 . Widzimy wyżej wyraźnie, że został mu przypisany marker 2 . W jaki sposób oznacza się takie połączenia? Poniżej zostaną rozpisane dwie bazy markujące, które rzucą nam nieco światła na ten mechanizm oznaczania pakietów i połączeń. Na sam początek stwórzmy sobie w iptables kilka łańcuchów w tablicy mangle . Do nich będzie przekierowany ruch:

# iptables -t mangle -N base-1
# iptables -t mangle -N base-2
# iptables -t mangle -N marking-1
# iptables -t mangle -N marking-2

# iptables -t mangle -A PREROUTING -j base-1
# iptables -t mangle -A PREROUTING -j base-2
# iptables -t mangle -A POSTROUTING -j base-1
# iptables -t mangle -A POSTROUTING -j base-2

W zależności od przeznaczenia oznaczeń (kontrola ruchu, routing) trzeba dobrać odpowiednie łańcuchy. Chodzi generalnie o to, że pakiety w iptables przechodzą przez ten filtr w pewien określony sposób. Jest on przedstawiony na poniższej fotce (źródło):

iptables-przeplyw-pakietow-netfilter

Teraz w łańcuchach base-1 oraz base-2 umieszczamy po bazie oznaczającej całe połączenia:

Pierwsza baza markująca:

# iptables -t mangle -A base-1 -j CONNMARK --restore-mark --nfmask 0x0000ff00 --ctmask 0x0000ff00
# iptables -t mangle -A base-1 -m mark ! --mark 0x00000000/0x0000ff00 -j RETURN
# iptables -t mangle -A base-1 -m mark --mark 0x00000000/0x0000ff00 -j marking-1
# iptables -t mangle -A base-1 -j CONNMARK --save-mark --nfmask 0x0000ff00 --ctmask 0x0000ff00

Druga baza markująca:

# iptables -t mangle -A base-2 -j CONNMARK --restore-mark --nfmask 0x000000ff --ctmask 0x000000ff
# iptables -t mangle -A base-2 -m mark ! --mark 0x00000000/0x000000ff -j RETURN
# iptables -t mangle -A base-2 -m mark --mark 0x00000000/0x000000ff -j marking-2
# iptables -t mangle -A base-2 -j CONNMARK --save-mark --nfmask 0x000000ff --ctmask 0x000000ff

Dla lepszego zrozumienia tematu korzystałem z pełnych masek i oznaczeń. Chodzi o to, że target MARK i CONNMARK są w stanie nakładać oznaczenia 32-bitowe. Zapis HEX to 0x00000000 do 0xffffffff. Zarówno maska jak i oznaczenie może przybrać wartość z tego przedziału. Jeśli nie określamy maski, to domyślnie jest ustawiana 0xffffffff . Efektem tego jest przepisywanie oznaczeń. W każdej z tych dwu baz wykorzystujemy inną maskę. W pierwszej mamy 0x0000ff00 , w drugiej zaś 0x000000ff . Skrótowo zawsze można zapisać je z pominięciem poprzedzających zer, tj. 0xff00 oraz 0xff. Tego typu oznaczenia będą zwracane przez iptables podczas listowania reguł.

Tak stworzony mechanizm markujący działa w następujący sposób. Pakiet w stanie NEW nie mający jeszcze nałożonego oznaczenia (0x00000000, 0x0) dociera do tablicy mangle do łańcucha PREROUTING . Tam wpada do pierwszej bazy markującej i przechodzi przez pierwszą regułę. Jako, że ten pakiet ma oznaczenie 0x00000000, to ta pierwsza reguła nic zbytnio z nim nie robi. Pakiet po przejściu przez nią dalej ma oznaczenie 0x00000000 . Druga reguła dopasowuje pakiet w oparciu o moduł -m mark i jeśli pakiet ma inne oznaczenie niż 0x00000000, to zostaje zwrócony do łańcucha wyżej, czyli nie przechodzi już przez pozostałe reguły tej bazy markującej. Trzecia reguła również porównuje ustawione oznaczenie. Jeśli pakiet nie jest oznaczony, tzn. ma 0x00000000, to jest przesyłany do łańcucha marking-1 , w którym będą się znajdować reguły markujące. Te reguły nas na razie nie interesują. Po przejściu przez łańcuch marking-1 , pakiet trafia do ostatniej reguły w pierwszej bazie markującej. To tutaj właśnie jest zapisywane oznaczenie dla całego połączenia, które widzieliśmy wyżej w tablicy conntrack'a. Niemniej jednak, zapisywane są tylko bity określone przez maskę 0x0000ff00. Podobnie sprawa wygląda w przypadku drugiej bazy markującej. W tym przypadku wykorzystujemy maskę 0x000000ff .

Zatem w obu przypadkach maski są inne, czyli inne bity będą brane pod uwagę. W efekcie, pakiet przechodząc przez obie bazy będzie miał ustawione oznaczenie sumaryczne, tj. znacznik ustawiony w drugiej bazie zostanie dodany do tego oznaczenia nałożonego przez pierwszą bazę. W ten sposób mechanizmy operujące na tych bazach mogą oznaczać pakiety niezależnie i operować na tak ustawionych oznaczeniach bez przeszkadzania sobie.

Reguły markujące

Te powyższe bazy markujące są ustawione w taki sposób, by pakiet, który nie został jeszcze oznaczony, przechodził przez reguły markujące. W pierwszej bazie mamy zdefiniowany łańcuch marking-1 , w drugiej zaś marking-2 . W tych łańcuchach musimy teraz umieścić reguły. Dla przykładu dodajmy tam te poniższe:

# iptables -t mangle -A marking-1 -p icmp -m icmp --icmp-type 8 -j MARK --set-xmark 0x00000100/0x0000ff00

# iptables -t mangle -A marking-2 -d 8.8.8.8 -j MARK --set-xmark 0x00000002/0x000000ff
# iptables -t mangle -A marking-2 -d 8.8.4.4 -j MARK --set-xmark 0x00000001/0x000000ff

W pierwszej bazie markującej dodaliśmy regułę, która oznacza pakiety protokołu ICMP echo-request. Będzie im nadawane oznaczenie 0x100, czyli 256 po przeliczeniu na ludzki język. To oznaczenie po przejściu przez tę bazę, zostanie przypisane do całego połączenia. Następnie pakiety trafią do drugiej bazy markującej i przejdą przez dwie ostatnie reguły widoczne powyżej. W zależności od miejsca przeznaczenia ping'u, pakiety zostaną tutaj oznaczone 0x1 lub 0x2. Jako, że korzystamy z innej maski, to te wartości zostaną dodane do tej poprzedniej. Czyli pakiet po przejściu przez drugą bazę markującą będzie miał oznaczenie 0x101 lub 0x102 , czyli 257 lub 258. Poniżej prezentuje się listing tablicy mangle :

tablica-mangle-iptables-mark-oznaczanie-pakietow

Sprawdźmy czy ten mechanizm działa jak należy. Odpalamy zatem terminal i wysyłamy żądania ping do kilku serwisów:

ping-iptables-mark-oznaczanie-pakietow-conntrack

W górnym okienku mamy podgląd tablicy conntrack'a. W pozostałych czterech oknach mamy ping'i. Widzimy, że tablica conntrack'a jest filtrowana i zwracane są wyniki ze znacznikiem innym niż 0. Wszystkie żądania ping zostały oznaczone znacznikiem minimum 0x100, czyli minim 256. Dwa z wpisów conntrack'a mają przypisany mark 0x101 oraz 0x102, czyli 257 i 268 w zapisie dziesiętnym. Są to połączenia, które zostały dopasowane w obu bazach markujących. Pozostałe zaś zostały dopasowane tylko w pierwszej bazie i dlatego ich oznaczenie wskazuje na 0x100, czyli 256. Jeśli podejrzymy jeszcze raz reguły w tablicy mangle , powinniśmy zobaczyć szereg dopasowań:

tablica-mangle-iptables-mark-oznaczanie-pakietow

Mamy cztery sesje ping . W łańcuchu marking-1 mamy cztery dopasowania, zaś w łańcuchu marking-2 mamy dwa dopasowania. W łańcuchach base-1 oraz base-2 w regule drugiej (ta z RETURN) również mamy kilka dopasowań, co wskazuje, że oznaczenie części pakietów różni się od 0x0 na pozycjach bitów określonych przez maski. Ilość dopasowanych pakietów w obu przypadkach się różni. W pierwszej bazie jest dopasowanych 80 pakietów, w drugiej zaś jedynie 40. Dzieje się tak dlatego, że w pierwszej bazie zostały oznaczone wszystkie pakiety ICMP. Natomiast w drugiej, tylko połowa, bo przecie tylko dwie z czterech sesji ping idzie na adresy 8.8.8.8 i 8.8.4.4 .

Jak działa --set-xmark w target MARK

Przy oznaczaniu pakietów za pomocą target MARK, reguły mają postać -j MARK --set-xmark 0x100/0xff00 lub -j MARK --set-xmark 0x2/0xff (pomińmy te poprzedzające zera). By zrozumieć co dokładnie zachodzi przy przetwarzaniu tych reguł, trzeba sobie rozpisać binarnie wszystkie te wartości biorąc pod uwagę przepływ pakietów przez reguły.

Wszystkie pakiety, które trafiają do iptables są oznaczone markiem 0x0. W pierwszej bazie markującej chcemy ustawić oznaczenie 0x100/0xff00. By tego dokonać, trzeba przeprowadzić dwie operacje: AND NOT oraz XOR . Spójrzmy poniżej:

     MARK 0000 0000 0000 0000    bazowe oznaczenie (0x0)
     MASK 1111 1111 0000 0000    nasza maska (0xff00)
  AND NOT -------------------    w MASK trzeba pierw poodwracać bity (0->1, 1->0), po czym dać logiczny AND z MARK
   RESULT 0000 0000 0000 0000    w ten sposób stary MARK zawsze jest przepisywany (0x0)
SET XMARK 0000 0001 0000 0000    ustawiamy nowy mark (0x100)
      XOR -------------------    przeprowadzamy operację XOR (przepisujemy różniące się bity)
   RESULT 0000 0001 0000 0000    otrzymujemy nowy mark (0x100) --> 256

W przypadku bazowego marka 0x0, ten proces nie jest tak klarowny jak być powinien. Na szczęście mamy drugą fazę oznaczania i tu już proces powinien być już bardziej przejrzysty:

     MARK 0000 0001 0000 0000    bazowe oznaczenie (0x100)
     MASK 0000 0000 ffff ffff    nasza maska (0xff)
  AND NOT -------------------    w MASK trzeba pierw poodwracać bity (0->1, 1->0), po czym dać logiczny AND z MARK
   RESULT 0000 0001 0000 0000    w ten sposób stary MARK zawsze jest przepisywany (0x100)
SET XMARK 0000 0000 0000 0001    ustawiamy nowy mark (0x1)
      XOR -------------------    przeprowadzamy operację XOR (przepisujemy różniące się bity)
   RESULT 0000 0001 0000 0001    otrzymujemy nowy mark (0x101) --> 257

Tak właśnie działa dodawanie (sumowanie) oznaczeń w iptables przy pomocy target MARK .

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.