BAD[SECTOR].PL

polski think tank IT Security

Nftables to projekt, który ma docelowo zastąpić znane z sys­temu Linux mechanizmy fil­trowania pakietów sieciowych, takie jak iptables, ip6tables czy arp­tables. Rów­nież korzysta z mechanizmów pod­sys­temu Net­fil­ter, jed­nak zamiast stałych struk­tur i szeregu specjalizowanych modułów znaj­dziemy w nim maszynę wir­tualną, która uruchamia pseudokod wyrażający reguły klasyfikacji.

Zapora sieciowa zwana też z rzadka ścianą prze­ciw­ogniową (ang. firewall) to aktywny element sieciowy, którego zadaniem jest fil­trowanie ruchu, czyli zarządzanie wzajemną widocz­no­ścią stacji sieciowych, co pozwala realizować ochronę infrastruk­tury lub sys­temu przed intruzami. Ist­nieją sprzętowe firewalle, ale mamy też do czynienia z zaporami software’owymi, wbudowanymi w sys­temy operacyjne. Te ostat­nie nie muszą być wcale mniej wydajne niż sprzętowe, a często są znacz­nie bar­dziej elastyczne w obsłudze i posiadają więcej nowator­skich funk­cji. Problem z nimi polega na awaryj­no­ści urządzeń, które składają się z wielu kom­ponen­tów (kom­putery), a także na tym, że sys­tem może być obciążony przez inne realizowane zadania.

Wprowadzenie

Zapora może działać w róż­nych war­stwach modelu ISO/​OSI. Spo­tkamy więc firewalle kon­tro­lujące ruch na poziomie ramek sieciowych (w war­stwie łącza danych), na poziomie IP (w war­stwie sieciowej), a także na poziomie obsługi protokołów TCPUDP (w war­stwie trans­por­towej). Bar­dziej zaawan­sowane zapory, tzw. firewalle aplikacyjne, dokonują inspek­cji zawar­to­ści pakietów i zaj­mują się ochroną na poziomie war­stwy aplikacyj­nej (np. protokołu HTTP czy SMTP). Co do zasady wszyst­kie firewalle nie róż­nią się jed­nak – ich zadaniem jest sprawowanie kon­troli nad tym kto, z kim i w jaki spo­sób może się komunikować.

W dużym skrócie i uprosz­czeniu war­stwa łącza odpowiada za komunikację jed­nej stacji sieciowej z drugą w obrębie tego samego nośnika (np. seg­mentu ether­netowego), war­stwa sieciowa pozwala wymieniać infor­macje między sys­temami operacyj­nymi iden­tyfikowanymi w spo­sób nie­zależny od medium (np. adresami IP), a war­stwa trans­por­towa pozwala wskazywać kon­kretne uruchomione programy działające w komunikujących sys­temach (np. z użyciem numeru portu).

War­stwa łącza przy­pomina samo­chód dostaw­czy, war­stwa sieciowa naczepę zawierającą towary wysyłane pod kon­kretny adres, a war­stwa trans­por­towa paczki oznaczone numerami miesz­kań, które mają trafić do odpowied­nich lokatorów. Zawar­tość paczki to już war­stwa kon­kret­nego zastosowania, czyli aplikacyjna.

Proces umiesz­czania zawar­to­ści należącej do warstw wyż­szych w kon­tenerach warstw niż­szych nosi nazwę kap­suł­kowania (ang. encap­sulation), a proces odwrotny odkap­suł­kowania (ang. deencapsulation).

W powyż­szej analogii z samo­chodem zapora przy­pomina osobę, która go zatrzymuje i dokonuje inspek­cji. W zależ­no­ści od kom­peten­cji i upraw­nień może to być tylko spraw­dzenie dokąd kierowca się udaje (war­stwa łącza), zbadanie trasy oraz kon­dycji naczepy (war­stwa sieciowa) czy kon­kret­nych paczek (war­stwa trans­por­towa), albo nawet rewidowanie zawar­to­ści prze­syłek (war­stwa aplikacyjna).

Historia

Przy­po­mnijmy sobie jak wyglądało fil­trowanie pakietów w ker­nelu Linux na prze­strzeni lat.

Ipfwadm

Linux już w linii 2.0 był wyposażony w prosty mechanizm fil­trowania pakietów sieciowych, który nazywał się ipfwadm (IP Firewall Administration). W owym czasie jądro sys­temowe nie zawierało jesz­cze kodu odpowiedzial­nego za kon­tro­lowanie ruchu sieciowego, więc korzystanie z tej zapory wiązało się z włączeniem odpowied­nich poprawek.

Dzięki ipfwadm i poleceniu o tej samej nazwie administrator mógł ustanawiać reguły w jed­nym z kilku z góry zdefiniowanych zestawów:

  • input (pakiety przychodzące),
  • output (pakiety wychodzące z systemu),
  • forwarding (pakiety prze­kazywane między inter­fej­sami sieciowymi),
  • accounting (wszyst­kie pakiety pod­dawane zliczaniu).

Zapora działała w ten spo­sób, że każdy pakiet sieciowy był dopasowywany do kolej­nych reguł odpowied­niego zestawu (w zależ­no­ści od tego czy emitowany lokal­nie, prze­kazywany, czy może kierowany do sys­temu). Filtr roz­poznawał protokoły IP, TCP, UDP oraz ICMP. Umoż­liwiał też zastosowanie jed­nej ze strategii ich obsługi:

  • przyj­mowanie pakietu (accept);
  • odrzucanie pakietu (reject);
  • ignorowanie pakietu (deny).

Ipfwadm pozwalał rów­nież na wprowadzenie prostej trans­lacji adresów źródłowych w trybie jeden do jed­nego lub wiele do jed­nego (tzw. maskarada adresu lub podsieci).

Spójrzmy na przy­kład użycia polecenia, które do zestawu reguł wej­ściowych dodaje blokowanie pakietów TCP kierowanych do portu o numerze 80 ze stacji o adresie IP 192.168.0.2.

ipfwadm -I -a deny -P tcp -S 192.168.0.2 -D 0.0.0.0/0 80

Opcja -a z war­to­ścią deny określa tzw. cel reguły (ang. rule tar­get), natomiast specyfikacje protokołu, adresów i numeru portu to kryteria dopasowywania (ang. match criteria). Z tymi lub podob­nymi ter­minami możemy się spo­tkać we wszyst­kich firewalach.

Kryteriami reguły tej zapory mogły być adresy, numery por­tów, protokoły, nazwy inter­fej­sów sieciowych i nie­które flagi nagłów­ków pakietów. Jeżeli administrator chciał uruchomić prze­kazywanie między por­tami (ang. port for­war­ding), musiał korzystać z narzędzia ipautofw działającego w prze­strzeni użyt­kow­nika lub narzędzia ipportfw korzystającego z mechanizmów kernela.

Ipchains

Wraz z nadej­ściem ker­neli z linii 2.2 pojawił się uspraw­niony mechanizm kon­troli ruchu sieciowego nazwany ipchains (Linux IP Firewal­ling Chains). Bazował on na kodzie ipfwadm, w którym dokonano uspraw­nień dotyczących mak­symal­nych war­to­ści licz­ników pakietowych czy fil­trowania uszkodzonych i szkodliwych nagłów­ków. Wprowadzono rów­nież obsługę więk­szej liczby protokołów sieciowych oraz moż­liwość odwróconego dopasowywania reguł. Podział na zestawy pozostał taki sam, jed­nak nazwano je łań­cuchami (ang. chains).

Nowo­ścią było wprowadzenie drzewiastej struk­tury reguł. W ipfwadm mieliśmy do czynienia z kil­koma zestawami, do których mogliśmy dodawać reguły fil­trujące, natomiast w ipchains pojawiła się moż­liwość tworzenia własnych. Stąd właśnie zmiana nazwy. Dzięki łań­cuchom powtarzalne zestawy fil­trów można było umiesz­czać w osob­nych prze­strzeniach (łań­cuchach użyt­kow­nika), a następ­nie do łań­cuchów wbudowanych (tj. input, output czy forward) dodawać reguły, które część ruchu prze­każą do jed­nego z tych pierw­szych w celu dal­szego przetwarzania.

W związku z moż­liwo­ścią dodawania nowych łań­cuchów reguł wprowadzono też opcję korzystania z ich iden­tyfikatorów jako celów – zamiast accept czy reject, celem reguły mogło być prze­kazanie pakietu do łań­cucha utworzonego przez administratora. Dodano też cel o nazwie return, którego użycie sprawia, że następuje powrót do łań­cucha nad­rzęd­nego (z którego prze­kazano kon­trolę nad pasującym do danej reguły pakietem). Aby obsługiwać maskaradę IP (ang. IP Masquerade) i prze­zroczystego pośred­nika, dodano też cele masqredirect.

Warto wspo­mnieć, że w samym narzędziu ipchains dodano rów­nież opcję replace, dzięki której można było zastępować reguły bez koniecz­no­ści ich wcześniej­szego kasowania i ponow­nego dodawania.

Net­fil­ter i iptables

W ker­nelach z serii 2.4 mieliśmy do czynienia z pew­nego rodzaju prze­łomem w kwestii obsługi sieci. Pojawił się wbudowany w jądro pod­sys­tem Net­fil­ter – pierw­szy mocno zin­tegrowany z innymi kom­ponen­tami sieciowymi klasyfikator z ujed­noliconym inter­fej­sem służącym do sterowania mechanizmami kon­troli ruchu. Dzięki bliskiej integracji z pozostałymi kom­ponen­tami sterowania prze­pływem moż­liwe stało się stosowanie znacz­nie bar­dziej elastycz­nych i precyzyj­nych strategii kontroli.

Gdy Net­fil­ter był gotowy, pojawiło się też – użyt­kowane do dziś – narzędzie iptables (IP Tables) służące do sterowania ustawieniami fil­tra pakietowego i mechanizmów trans­lacji adresów sieciowych (ang. Network Address Trans­lation, skr. NAT). Do popraw­nej pracy zapory sterowanej tym oprogramowaniem wymagane są odpowied­nie moduły jądra (np. x_tablesip_tables). Z kolei aby sterować fil­trami, których obsługę dodano w ker­nelu póź­niej, używa się dodat­kowych programów: ip6tables (obsługa IPv6), arptables (fil­trowanie ruchu ARP) czy ebtables (fil­trowanie na poziomie most­ków sieciowych).

Net­fil­ter wprowadza następujące ulep­szenia w stosunku do poprzed­niego kodu filtrującego:

  • inspek­cja stanu (ang. stateful inspec­tion) na poziomie protokołów (IPv4 i IPv6) oraz aplikacji (IPv4);
  • port for­war­ding nie wymagający usług działających w prze­strzeni użytkownika;
  • pełna obsługa NAT (1 do 1 oraz 1 do wielu);
  • integracja z pod­sys­temem jako­ści usługi (ang. Quality of Service, skr. QoS);
  • integracja z IP sets (bazy adresów IP w wydaj­nych strukturach).

W samym pod­sys­temie iptables poza wbudowanymi łań­cuchami reguł (input, outputforward) pojawiły się dodat­kowo prerouting oraz postrouting. Pierw­szy łań­cuch jest spraw­dzany zanim zapad­nie decyzja o wyborze trasy pakietu (lub uznania go za prze­znaczony dla lokal­nego procesu), a drugi wtedy, gdy decyzja taka już zapadła i pakiet ma opu­ścić sys­tem. Było to moż­liwe dzięki mechanizmowi tzw. pod­pięć (ang. hooks). Pozwalają one, aby inne moduły jądra (np. x_tablesip_tables czy właśnie nf_tables) rejestrowały w Net­fil­terze własne funk­cje, do których na róż­nych etapach prze­twarzania pakietów i ramek będą kierowane datagramy.

Tabele iptables

W iptables łań­cuchy reguł nie są naj­wyż­szym poziomem abs­trak­cji, lecz przynależą do tzw. tabel (ang. tables). Te ostat­nie są zbiorami zawierającymi łań­cuchy, które sterują róż­nymi funk­cjami firewalla – np. osobna tabela odpowiada za trans­lację adresów (NAT), a osobna za filtrowanie.

Tabele nie pochodzą z Net­fil­tera, lecz są spo­sobem organizowania reguł w iptables, który odzwier­ciedla odpowied­nie etapy „podróży” pakietu przez stos obsługi sieci. Każda tabela iptables zawiera pewne wbudowane łań­cuchy, do których trafiają pakiety pochodzące z zarejestrowanych wcześniej pod­pięć. Nie należy wyobrażać sobie tabel jako pojedyn­czych punk­tów tran­zytowych – wiele z nich jest „odwiedzanych” przez dany pakiet czę­ściej niż raz, chociaż w kolej­nych momen­tach trafi on do do róż­nych wbudowanych w daną tabelę łańcuchów.

Ścieżki prze­pływu

Chociaż tabele i zawarte w nich łań­cuchy reguł nie są ściśle związane z pod­sys­temem Net­fil­ter, to pozwalają łatwiej zro­zumieć jak dokład­nie wygląda klasyfikowanie pakietów. Taki przy­kład schematu ilustrującego drogę pakietu przez kolejne elementy mechanizmu obsługi sieci można znaleźć w zasobach Wikimedia Com­mons lub klikając obrazek umiesz­czony poniżej.

Źródło:
CC BY­‑SA 3.0 PL
Ilustracja ścieżek prze­pływu w pod­sys­temie Net­fil­ter ker­nela Linux

Przy­po­mnijmy sobie funk­cje poszczegól­nych tabel iptables i przy­pisane do nich wbudowane łań­cuchy, do których z użyciem pod­pięć Net­fil­tera kierowane są pakiety sieciowe:

  • tabela raw:
    • łań­cuchy: prerouting, output;
    • prze­znaczenie: fil­trowanie tuż przy inter­fej­sach sieciowych;
  • tabela filter:
    • łań­cuchy: input, forward, output;
    • prze­znaczenie: fil­trowanie ruchu sieciowego;
  • tabela nat:
    • łań­cuchy: prerouting, output, postrouting;
    • prze­znaczenie: trans­lacja adresów (NAT);
  • tabela mangle:
    • łań­cuchy: prerouting, input, forward, output, postrouting;
    • prze­znaczenie: wprowadzanie zmian w pakietach;
  • tabela security:
    • łań­cuchy: input, forward, output;
    • prze­znaczenie: współ­praca z mechanizmami obowiąz­kowej kon­troli dostępu (ang. Man­datory Access Con­trol, skr. MAC), takimi jak np. SELinux.

Śledzenie i inspek­cja stanu

Ponie­waż Net­fil­ter wyposażony jest w moduł śledzenia połączeń (ang. con­nec­tion trac­king), więc każdy badany pakiet będzie oznaczony jed­nym z abs­trak­cyj­nych stanów w ramach przy­porząd­kowania do wykrytej (lub nie) sesji komunikacyjnej:

  • new – pakiet nawiązujący połączenie;
  • established – pakiet należący do ustanowionego połączenia;
  • related – pakiet związany z ist­niejącym połączeniem;
  • invalid – pakiet niepoprawny;
  • untracked – pakiet nieśledzony.

Dzięki śledzeniu połączeń i znakowaniu pakietów prze­chodzących przez zestawy reguł, administrator może korzystać z kryteriów decyzyj­nych bazujących na stanach.

Oznaczanie pakietów stanami odbywa się zaraz po etapie prze­twarzania w łań­cuchu prerouting przy­pisanego do tabeli raw. Jest to też miej­sce, gdzie z użyciem specjal­nych reguł możemy wpływać na to, jakie moduły śledzące zostaną wykorzystane w kon­kret­nych przy­pad­kach. W prak­tyce śledzenie połączeń polega nie tylko na uzyskiwaniu infor­macji z nagłów­ków (np. pakietów TCP), ale rów­nież z prze­noszonych w pakietach danych aplikacyjnych.

Przy­kładem, gdy nie wystar­cza samo śledzenie nagłów­ków war­stwy trans­por­towej może być protokół FTP. W jego sesjach korzysta się z dwóch połączeń TCP: jed­nego do wydawania poleceń, a drugiego do prze­syłania plików. Nawiązanie połączenia z por­tem o numerze 21 i prze­słanie żądania pobrania pliku sprawia, że ser­wer instruuje klienta do jakiego portu musi wykonać połączenie, aby pobrać dane. Aby śledzić tak nawiązywane, nowe połączenie, a jego pakietom nadać stan related, zapora sieciowa musi w jakiś spo­sób pozyskać komunikat war­stwy aplikacyj­nej (dialogu klienta z ser­werem) i na pod­stawie wykrytego łań­cucha tek­stowego zawierającego numer portu utworzyć wpis w tabeli śledzenia połączeń. Umoż­liwia to odpowiedni moduł, który bada zawar­tość pakietów, jeśli komunikacja odbywa się na danym porcie.

Cele

W czyniącym użytek z Net­fil­tera narzędziu iptables możemy mieć do czynienia z celami reguł, których liczba zwięk­szyła się w stosunku do poprzed­ników. Pod­stawowy zestaw wygląda tak:

  • accept – przyjęcie pakietu;
  • reject – odrzucenie pakietu z poin­for­mowaniem drugiej strony;
  • drop – zablokowanie pakietu (w poprzed­nich: deny);
  • queue – prze­kazanie pakietu do obsługi przez proces użytkownika;
  • return – powrót do nad­rzęd­nego łańcucha.

Poza wypisanymi powyżej ist­nieją też cele specyficzne dla kon­kret­nych tabel i łań­cuchów (np. log, mark czy mirror), jed­nak nie będziemy ich teraz omawiać.

Nftables

Nftables (Net­fil­ter Tables) to mechanizm zarządzania fil­trami sieciowymi ker­nela Linux, który ma zastąpić iptables. Projekt został zapocząt­kowany w roku 2008 przez Patricka McHardy’ego. Jak sama nazwa wskazuje, nftables rów­nież korzysta z pod­sys­temu Net­fil­ter i rów­nież mamy do czynienia z tabelami. Kod zapory został stworzony od pod­staw i nie bazuje na iptables, chociaż zachowano spo­sób organizowania reguł w łań­cuchy i tabele.

Nftables składa się z kilku komponentów:

  • pod­programów obsługi w jądrze;
  • biblioteki libmnl (do obsługi komunikacji przez gniazda Netlink);
  • biblioteki libn­ftnl (do obsługi API nftables za pośred­nic­twem libmnl);
  • oprogramowania użyt­kowego (polecenie nft), które zastępuje iptables.

Administratorzy sys­temów z ker­nelami Linux w wer­sji 3.13 i wydaniami póź­niej­szymi powinni mieć już moż­liwość korzystać z nftables, jeśli tylko zain­stalują potrzebne biblioteki i narzędzie nft. Nie­które dys­trybucje GNU/​Linuksa mają to oprogramowanie w repozytoriach, w innych konieczna jest samodzielna kom­pilacja ze źródeł.

Czym w skrócie różni się nftables od iptables? Uprosz­czono ker­nelowy inter­fejs binarny aplikacji (ang. Application Binary Inter­face, skr. ABI), „odchudzono” kod (w iptables nie­które frag­menty były po prostu powielane), ulep­szono obsługę rapor­towania błędów, a w końcu wprowadzono cał­kiem nowy spo­sób wyrażania i prze­twarzania reguł. Zamiast narzędzi iptables, ebtables, arptablesip6tables mamy jedno polecenie, które steruje fil­trami, a kod w ker­nelu jest nie­zależny od protokołów i warstw.

Podob­nie jak w iptables wciąż mamy do czynienia z łań­cuchami reguł, które są umiesz­czane w tabelach, jed­nak nie ist­nieją łań­cuchy wbudowane, na stałe pod­pięte do Net­fil­tera. Zamiast tego administrator zapory bazującej na nftables może tworzyć dowolne tabele, a następ­nie umiesz­czać w nich dowolne łań­cuchy. Te ostat­nie mogą być (ale wcale nie muszą) kojarzone z wybranymi punk­tami ścieżek przepływu.

Generyczne kryteria

Nftables wprowadza do ker­nela maszynę wir­tualną, która odpowiedzialna jest za uruchamianie pseudokodu pochodzącego z prze­strzeni użyt­kow­nika. Pseudokod ten jest wcześniej wysyłany przez gniazdo typu Netlink z narzędzia nft po dokonaniu inter­pretacji i kom­pilacji wprowadzonych reguł. Dzięki temu dodawanie nowych funk­cji i strategii fil­trowania ruchu jest ułatwione – dodat­kowa war­stwa abs­trak­cji pozwala wyeliminować sztywne struk­tury prze­chowujące reguły.

Zastosowanie maszyny wir­tual­nej eliminuje koniecz­ność tworzenia osob­nych modułów obsługi dla nowych protokołów czy selek­torów kryteriów. Kod w ker­nelu, który jest odpowiedzialny za badanie kryteriów dopasowywania pakietów, został uprosz­czony w stosunku do poprzed­nich mechanizmów (takich jak iptables czy ipchains). Potrafi on odczytywać zawar­tość (ładunek) pakietu, infor­macje nagłów­kowe i skojarzone z pakietem metadane (np. inter­fejs wej­ściowy i wej­ściowy czy stan w kon­tek­ście śledzenia połączeń), a na bazie tych infor­macji i zestawu operatorów (aryt­metycz­nych, bitowych i porów­nywania) mogą być kon­struowane bar­dziej wyszukane filtry.

Poza tym w przy­padku roz­wijającego się stosu TCP/​IP i coraz licz­niej­szych miejsc klasyfikacji pakietów w jądrze fil­try bazujące na wykorzystaniu stałych struk­tur z czasem będą pracowały coraz mniej wydaj­nie. Wynika to z koniecz­no­ści prze­szukiwania wszyst­kich miejsc, w których poten­cjal­nie mogą znaj­dować się klasyfikatory dla każ­dego prze­twarzanego pakietu. Problem ten znika, gdy liczba punk­tów decyzyj­nych zależy od fak­tycz­nie użytych reguł, a właśnie z taką sytuacją mamy do czynienia, gdy reguły są pseudoprogramami.

Zastosowanie maszyny wir­tual­nej oszczędza czas programistów, daje elastycz­ność administratorowi i sprawia, że w prze­st­zreni jądra sys­temowego załadowanych jest jed­nocześnie mniej modułów z roz­szerzeniami, które mogą poten­cjal­nie zawierać usterki. Inspiracją dla takiego podej­ścia był znany z sys­temów typu BSD filtr Berekeley Pac­ket Fil­ter (skr. BPF). Nie­którzy zarzucają autorom nftables, że mogli skorzystać z jego kodu lub z wykorzystywanej prze­zeń biblioteki libp­cap zamiast ponow­nie wymyślać koło. W reak­cji na tego typu sugestie pod­noszone jest na przy­kład, że BPF nie obsługuje przy­rostowych aktualizacji i ma ograniczony do 64 kilobaj­tów roz­miar fil­trów. Problemów takich nie mają podobno naj­now­sze wer­sje sil­nika BPF++, jed­nak trzeba przy­znać, że BPF nie obsługuje tak specyficz­nych struk­tur danych, jakie używane są w nftables (np. omówionych w dal­szej czę­ści zbiorów).

Przy­kład

Spójrzmy na nieco prze­robiony diagram ilustrujący ścieżki prze­pływu ruchu sieciowego:

Źródło:
CC BY­‑SA 3.0 PL
Diagram obrazujący ścieżki prze­pływu w pod­sys­temie Net­fil­ter z dodanymi nazwami pod­pięć i typów łańcuchów

W stosunku do oryginal­nej ilustracji zmienione zostało znaczenie prostokąt­nych figur, które wcześniej sym­bolizowały tabele i łań­cuchy. Zamiast nazw tabel w gór­nych czę­ściach kom­ponen­tów klasyfikujących znaj­dziemy typy łań­cuchów, a zamiast nazw łań­cuchów wbudowanych tak zwane nazwy pod­pięć.

W nftables tabele nie są powiązane z etapami prze­twarzania i grupują łań­cuchy odnoszące się do danej rodziny protokołów (np. IPv4 czy ARP). Z kolei łań­cuchy zawsze tworzy się samodziel­nie, chociaż można ustawiać ich typy. W kilku miej­scach widać też dopisek prio i liczbę. Jest to adnotacja o koniecz­no­ści zdefiniowania odpowied­niego priorytetu dodawanego łań­cucha, aby działał zgod­nie z oczekiwaniami. W ilustracji pokazano tylko ścieżki dla IPv4 i obsługi most­ków sieciowych należące do więk­szego zestawu rodzin protokołów obsługiwanych przez nftables.

Spróbujmy, patrząc na schemat, prze­śledzić los hipotetycz­nego pakietu sieciowego. Załóżmy, że dla niego nasz sys­tem będzie tylko miej­scem tran­zytowym, tzn. routerem, zaś celem ser­wer WWW w pod­sieci usługowej. Załóżmy też, że ser­wery w tej pod­sieci posługują się adresami IP z prywat­nych pul, a nasz firewall musi dokonywać trans­lacji adresów docelowych. Inter­fej­sem wej­ściowym (publicz­nym) będzie eth0, a wyj­ściowym (połączonym z usługami) eth1.

Przy­kład ten jest w miarę uniwer­salny i ułatwia zro­zumienie w jaki spo­sób działają zapory sieciowe korzystające z pod­sys­temu Netfilter:

  1. Na inter­fej­sie eth0 pojawia się ramka zaadresowana do karty sieciowej, a w niej znaj­duje się pakiet IP prze­noszący dane TCP z żądaniem nawiązania połączenia (ustawiona flaga SYN).

  2. Pod­program obsługi war­stwy łącza danych rezer­wuje odpowied­nią struk­turę na obsługę ramki.

  3. Ramka (a naprawdę reprezen­tująca ją struk­tura) jest prze­kazywana do pod­sys­temu QoS (kolejka ingress klasy qdisc).

  4. Następ­nie sys­tem decyduje czy należy ramkę prze­kazać do inter­fejsu most­kującego (ang. bridge) celem prze­słania do innego seg­mentu. Ponie­waż nie mamy do czynienia z taką sytuacją, koń­czy się obsługa ramki i jej zawar­tość zostaje odkap­suł­kowana. Wewnątrz znaj­duje się pakiet IP, który zostaje prze­kazany do pod­programu obsługi war­stwy sieciowej.

  5. Pod­program obsługi war­stwy sieciowej rezer­wuje odpowied­nią struk­turę i umiesz­cza w niej pakiet IP.

  6. Dane pakietu dopasowywane są do reguł łań­cucha typu filter obsługiwanych przez niskopriorytetowe pod­pięcie prerouting (odpowied­nik tabeli raw w iptables). W tym miej­scu administrator może ustanowić regułę, która wyłączy inspek­cję stanu dla pakietów o pew­nych wła­ściwo­ściach, może też zdecydować o specjal­nym potrak­towaniu pakietów w tym zakresie (np. pakietów TCP kierowanych do portu 2111 tak, jakby prze­nosiły dane protokołu FTP).

  7. Jeśli pakiet nie został odrzucony lub zignorowany, trafia do pod­sys­temu śledzenia połączeń (conntrack). Tam na pod­stawie analizy jego nagłówka i danych, a także porów­nania z sys­temową tabelą połączeń, zostaje odpowied­nio oznaczony, a w sys­temowej tabeli pojawia się lub zostaje zak­tualizowany wpis o połączeniu. Wykorzystywane są tu nie­które znacz­niki sterujące śledzeniem, jeśli były wcześniej ustawione.

  8. Pakiet trafia do łań­cucha typu route przez pod­pięcie prerouting (odpowied­nik tabeli mangle z iptables), gdzie może być zmieniony, jeśli określono to regułami.

  9. Pakiet trafia do łań­cucha typu nat przez pod­pięcie prerouting, gdzie może być zmieniony jego adres docelowy. Znaj­dzie się tu reguła trans­lacji sieciowych adresów docelowych (ang. Destination Netword Address Trans­lation, skr. DNAT), która mówi, żeby pod­mienić adres IP pakietu z publicz­nego na prywatny, przy­pisany do ser­wera WWW w chronionej pod­sieci. W tym przy­padku rów­nież utrzymywana jest w sys­temie odpowied­nia baza, aby mechanizmy obsługi NAT wiedziały jak modyfikować pakiety stanowiące odpowiedzi na przekształcone.

  10. Następuje pod­jęcie decyzji o routingu na pod­stawie tabel trasowania (ang. routing tables). Pod­program obsługi wykrywa, że pakiet nie jest prze­znaczony dla lokal­nej stacji i że należy prze­słać go dalej innym inter­fej­sem sieciowym.

  11. Pakiet trafia do łań­cucha typu route przez pod­pięcie forward, gdzie może być zmieniony, jeśli określono to regułami.

  12. Pakiet trafia do łań­cucha typu filter przez pod­pięcie forward, gdzie znaj­dują się reguły ochronne.

  13. Pakiet trafia do łań­cucha typu route przez pod­pięcie postrouting, gdzie może być zmieniony, jeśli określono to regułami.

  14. Pakiet trafia do łań­cucha typu nat przez pod­pięcie postrouting, gdzie może być dokonana trans­lacja sieciowego adresu źródłowego (ang. Source Network Address Trans­lation, skr. SNAT). Nie trafia na żadną pasującą regułę, więc nie zostaje zastosowany NAT.

  15. Pakiet trafia do pod­programu obsługi reguł XFRM, odpowiedzial­nego za prze­kształ­canie datagramów. Tu dokonywane są np. zmiany zawar­to­ści i nagłów­ków, jeśli wykorzystywany jest zbiór protokołów ochron­nych IP Security (ang. Inter­net Protocol Security, skr. IPSec). Ponie­waż w sys­temie nie ma określonych zasad XFRM, więc pakiet jest prze­kazywany dalej w nie­zmienionej formie.

  16. Pakiet jest kap­suł­kowany w ramkę zaadresowaną do stacji sieciowej będącej ser­werem WWW.

  17. Ramka trafia do kolejki egress klasy qdisc pod­sys­temu QoS.

  18. Ramka trafia do inter­fejsu sieciowego eth1, gdzie jest trans­mitowana w medium do docelowej stacji sieciowej.

Architek­tura

Naj­ogól­niej­szymi (zawierającymi pozostałe) struk­turami nftables są tabele. Służą do prze­chowywania łań­cuchów regułzbiorów. O zbiorach powiemy w dal­szej czę­ści, z kolei łań­cuchy reguł nie róż­nią się pod względem prze­znaczenia od tych, które znamy z iptables czy ipchains – pozwalają grupować reguły.

Pakiety sieciowe z pod­sys­temu Net­fil­ter dzięki pod­pięciom trafiają do wybranych przez administratora łań­cuchów, a w nich są prze­twarzane przez każdą kolejną regułę, aż do momentu, gdy w którejś doj­dzie do decyzji, że nie powinny być już analizowane.

Funk­cją reguł jest badanie, czy pakiety można dopasować do kryteriów zawar­tych w wyrażeniach dopasowujących. W przy­padku pozytyw­nego dopasowania wykonywane są umiesz­czone w regułach akcje, które wyrażane są odpowied­nimi deklaracjami (ang. statements). Ist­nieje kilka rodzajów akcji, a naj­popular­niej­sze dotyczą podej­mowania decyzji w sprawie dal­szego losu pakietu. Decyzje nazywane są też wer­dyk­tami, a ich sym­boliczny zapis deklaracjami decyzyj­nymi.

 

Źródło:
CC BY­‑NC­‑SA 3.0 PL
Schemat ilustrujący architek­turę nftables w kon­tek­ście ścieżek prze­pływu i pod­pięć mechanizmu Netfilter

Tabele

Tabela jest kon­tenerem zawierającym łań­cuchy regułzbiory. Ma nazwę i przy­pisaną jej rodzinę protokołu (ang. protocol family). Roz­róż­nienie pod względem rodzin protokołów jest konieczne, ponie­waż mamy do czynienia z odmien­nymi spo­sobami adresowania, moż­liwymi do zastosowania testami i róż­nymi pod­pięciami pod­sys­temu Netfilter.

Moż­liwe rodziny protokołów to:

  • ipprotokół inter­netowy w wer­sji 4 (ang. Inter­net Protocol ver­sion 4, skr. IPv4);
  • ip6protokół inter­netowy w wer­sji 6 (ang. Inter­net Protocol ver­sion 6, skr. IPv6);
  • inetprotokół inter­netowy (hybryda łącząca IPv4 i IPv6);
  • arpprotokół odwzorowywania adresów (ang. Address Resolution Protocol, skr. ARP);
  • bridge – protokoły związane z most­kami sieciowymi (ang. network bridges).

Łań­cuchy reguł

Łań­cuch reguł to struk­tura zawierająca uszeregowane reguły, których zadaniem jest analiza prze­pływających pakietów. Ist­nieją dwa rodzaje łań­cuchów: łań­cuchy zwykłe (ang. regular chains) i łań­cuchy bazowe (ang. base chains). Te pierw­sze mogą być używane w celu bar­dziej przej­rzystej organizacji reguł – można wysyłać do nich pakiety korzystając z akcji decyzyj­nej reguł o nazwie jump.

Łań­cuchy bazowe służą do prze­chwytywania pakietów ze wskazanych miejsc ścieżki prze­pływu Net­fil­tera. Tworząc je należy podać:

  • typ łań­cucha, którym może być:
    • filter – oznaczający łań­cuch fil­trujący pakiety;
    • nat – oznaczający łań­cuch służący trans­lacji adresów (NAT);
    • route – oznaczający łań­cuch do zmiany tras pakietów (odpowied­nik mangle w iptables);
  • pod­pięcie – określające miej­sce mechanizmu Net­fil­ter, w które łań­cuch ma być włączony:
    • prerouting – pakiety przed pod­jęciem decyzji o trasowaniu (wszyst­kie pakiety wchodzące do systemu);
    • input – pakiety kierowane do tego systemu;
    • forward – pakiety dla innego sys­temu, dla których ten jest routerem;
    • output – pakiety pochodzące z tego systemu;
    • postrouting – pakiety po pod­jęciu decyzji o routingu (wszyst­kie pakiety opusz­czające system);
  • priorytet – będący liczbą cał­kowitą decydującą o punk­cie pod­pięcia do pod­sys­temu Net­fil­ter (domyśl­nie 0).

W przy­padku tabel obsługujących protokół ARP możemy używać tylko pod­pięć inputoutput, a w przy­padku rodziny bridge wyłącz­nie input, outputforward.

Typy danych

Pod­czas wyrażania parametrów reguł umiesz­czonych w łań­cuchach korzysta się z określonych i zro­zumiałych przez nftables typów danych (ang. data types). Ich sym­boliczna reprezen­tacja zostanie w procesie kom­pilacji zmieniona w odpowied­nie struk­tury pseudokodu.

Pamięciowe obiekty więk­szo­ści typów mają stały roz­miar, ale zdarzają się takie, dla których obszar pamięci jest przy­dzielany dynamicz­nie (np. typy złożone czy łań­cuchy tekstowe).

Typy pod­stawowe

  • numeryczne:
    • bitmask – maska bitowa, np. 0x00000002, 0x01, established;
    • integer – liczba cał­kowita, np. 123;
    • mark – znacz­nik pakietu, np. 1024;
  • tek­stowe:
    • string – łań­cuch tek­stowy, np. lancuch lub "lancuch";
  • związane z datą i czasem:
    • time – czas;
  • adresowe i iden­tyfikacyjne:
    • ipv4_addr – adres IPv4, np. 192.168.0.1 – moż­liwa jest też notacja dziesiętna, ósem­kowa i hek­sadecymalna (rów­nież z krop­kami), jak i podanie nazwy DNS stacji, która zostanie zamieniona na adres przez zapytanie wysłane do resolvera;
    • ipv6_addr – adres IPv6, np. fe80::122:fbff:facd:e48e;
    • ll_addr – adres war­stwy łącza danych, np. 10:aa:be:ce:de:ef;
    • inet_service – usługa inter­netowa (numer portu), np. 22, ssh;
  • protokolarne:
    • inet_proto – protokół inter­netowy war­stwy trans­por­towej, np. tcp;
    • nf_proto – protokół war­stwy sieciowej;
    • pkt_type – typ pakietu;
  • specyficzne dla TCP:
    • tcp_flag – flaga TCP, np. syn;
  • specyficzne dla ICMP:
    • icmp_code – kod komunikatu ICMP, np. net-unreach;
    • icmp_type – typ komunikatu ICMP, np. echo-request;
    • icmpx_code – generyczny kod komunikatu ICMP, np. no-route;
  • specyficzne dla Ether­netu:
    • ether_type – typ protokołu (lub roz­miar, gdy <= 1500);
    • ether_addr – adres MAC, np. 10:aa:be:ce:de:ef;
  • specyficzne dla śledzenia połączeń:
    • ct_dir – kierunek połączenia;
    • ct_label – etykieta połączenia;
    • ct_state – stan połączenia;
    • ct_status – status połączenia;
  • specyficzne dla trasowania:
    • realm – dziedzina trasowania (32­‑bitowa liczba lub nazwa z /etc/iproute2/rt_realms);
  • specyficzne dla pod­sys­temu Traf­fic Con­trol:
    • tc_handle – uchwyt pod­sys­temu kon­troli przepływu;
  • decyzyjne:
    • verdict – decyzja, np. jump.
  • specyficzne dla IPv6:
    • icmpv6_type – typ komun­katu ICMP dla IPv6, np. packet-too-big;
    • mh_type – typ nagłówka mobilnościowego;
  • specyficzne dla inter­fej­sów sieciowych:
    • iface_index – numer kolejny inter­fejsu (liczba 32­‑bitowa lub nazwa);
    • iface_type – typ inter­fejsu (16­‑bitowa liczba);
    • ifname – nazwa inter­fejsu (16­‑bajtowy łań­cuch tekstowy);
    • devgroup – grupa urządzeń;
  • specyficzne dla ARP:
    • arphrd – znacz­nik typu urządzenia ARP;
    • arp_op – typ operacji ARP;
  • sys­temowe:
    • uid – iden­tyfikator użyt­kow­nika (32­‑bitowa liczba lub nazwa);
    • gid – iden­tyfikator grupy (32­‑bitowa liczba lub nazwa);
  • specyficzne dla DCCP:
    • dccp_pkttype – typ pakietu DCCP.

Typy można też podzielić ze względu na obszary, w których znaj­dują zastosowanie (w nawiasach podano nazwę pliku z kodem źródłowym, w którym znaj­dują się definicje):

  • ogólny (datatype.c):
    invalid, verdict, nf_proto, bitmask, integer, string, ll_addr, ipv4_addr, ipv6_addr, inet_proto, inet_service, mark, icmp_code, icmpv6_code, icmpx_code, time;

  • obsługa śledzenia połączeń (ct.c):
    ct_state, ct_dir, ct_status, ct_label;

  • metadane (meta.c):
    realm, tc_handle, iface_index, iface_type, uid, gid, pkt_type, devgroup;

  • obsługa protokołów (proto.c):
    icmp_type, tcp_flag, dccp_pkttype, icmpv6_type, arp_op, ether_addr, ether_type;

  • obsługa nagłów­ków roz­szerzeń (exthdr.c):
    mh_type.

Typy złożone

Nftables wyposażono w wydajne struk­tury służące do prze­chowywania danych: słow­niki, zbiorymapy danych. Są to typy złożone, w których struk­turach prze­chowywać można dane innych typów. Pod­czas ich tworzenia konieczne jest określanie jakie kon­kret­nie typy danych będą wchodziły w ich skład.

Zbiory

Zbiory (ang. sets) to kolek­cje elemen­tów, które pozwalają prze­chowywać i wyszukiwać infor­macje, na przy­kład adresy, numery por­tów, nazwy inter­fej­sów i inne dane znanych typów. Danych tych można następ­nie używać jako kryteriów dopasowania lub parametrów akcji.

W zależ­no­ści od rodzaju danych zbiory mogą wewnętrz­nie korzystać z drzew czerwono­‑czarnych (ang. red­‑black trees) lub tablic mieszających (ang. hash tables), żeby zapew­niać szybki dostęp do elemen­tów i dokonywać automatycz­nej kom­presji więk­szych zestawów. Jeśli na przy­kład prze­chowywane i dopasowywane mają być infor­macje dotyczące prze­strzeni adresowych z uwzględ­nieniem zakresów, można wykorzystać struk­turę drzewiastą, a gdy między elemen­tami nie ma hierar­chicz­nej relacji tablicę mieszającą.

Zbiory mogą być anonimowe (ang. anonymous sets) lub nazwane (ang. named sets). Te pierw­sze są osadzone w regułach, a drugie mogą być w nich użyt­kowane, lecz są zdefiniowane osobno i iden­tyfikowane podaną nazwą.

Jeżeli usuwana jest reguła, w której osadzono anonimowy zbiór, to prze­staje on ist­nieć. Poza tym nie można zmieniać zawar­to­ści takich zbiorów po ich utworzeniu, chociaż można się do nich odwoływać z użyciem przy­dzielonych przez ker­nel identyfikatorów.

Przy­kład nazwanego zbioru używanego do dopasowywania:

set zbior {
    type ipv4_address
    elements = { 127.0.0.1, 172.0.0.1 }
}

Obsługiwane typy elemen­tów, które mogą wchodzić w skład zbiorów to:

  • ipv4_addr – adresy IPv4;
  • ipv6_addr – adresy IPv6;
  • ether_addr – adresy ethernetowe;
  • inet_proto – rodzaje protokołów internetowych;
  • inet_service – usługi inter­netowe (np. numery portów);
  • mark – znacz­niki pakietowe.
Mapy

Mapy (ang. maps) – zwane też mapami danych (ang. data maps), gdy nie zawierają danych decyzyj­nych – służą się do prze­chowywania war­to­ści (ang. values) przy­porząd­kowanych do podanych kluczy (ang. keys). Mapy wewnętrz­nie korzystają ze zbiorów, a programi­ści obiek­towi mogą wyobrażać sobie mapę jako klasę pochodną zbioru. Mapy, podob­nie jak zbiory, mogą być anonimowe (ang. anonymous maps) lub nazwane (ang. named maps).

Przy­kład mapy pomoc­nej w wyrażeniu spo­sobu trans­lacji adresów z prze­kierowaniem portów:

map kierownik {
    type inet_service : ipv4_addr;
        80 : 192.168.0.2,
    8888 : 172.30.1.8
}

Możemy zauważyć, że deklaracja typu zawiera dwie nazwy oddzielone dwukrop­kiem. Pierw­sza określa typ kluczy, a druga typ kojarzonych z nimi wartości.

Słow­niki

Słow­niki (ang. dic­tionaries), zwane też mapami decyzyj­nymi (ang. ver­dict maps, skr. vmaps), pozwalają przy­porząd­kowywać akcje do kluczy. Wewnętrz­nie słow­niki korzystają ze zbiorów do struk­turalizowania danych i są specyficz­nym rodzajem map. Programi­ści obiek­towi mogą wyobrażać sobie słow­nik jako egzem­plarz mapy, w którym typem war­to­ści jest verdict (decyzja). Słow­niki mogą być anonimowe (ang. anonymous dic­tionaries) lub nazwane (ang. named dictionaries).

Przy­kład anonimowego słow­nika używanego do warun­kowania pod­jęcia akcji:

map {
    type inet_proto : verdict;
    elements = { tcp : jump obsluga-tcp, udp : jump obsluga-udp }
}
Mapy decyzyjne a mapy danych

Mapa decyzyjna (słow­nik) jest rów­nież mapą, jed­nak przed­stawiona została jako osobna struk­tura z tej przy­czyny, że bywa często stosowana do wyrażenia pew­nych decyzji przez użycie specyfikatora vmap umiesz­czanego w wyrażeniach. Zamiast osob­nej (w sen­sie skła­dniowym) deklaracji mamy wtedy do czynienia z decyzjami zawar­tymi bez­pośred­nio w mapie.

Inter­wały (zakresy)

Inter­wały (ang. intervals) pozwalają określać zakresy (ang. ran­ges), czyli zbiory war­to­ści z pew­nych prze­działów. Wyrażane są zapisem: wartość-wartość i mogą pojawiać się zarówno w miej­scach, gdy oczekiwane jest podanie pew­nych danych, jak i jako elementy zbiorów czy map. Naj­częst­szym ich zastosowaniem jest używanie ich do oznaczania zakresów adresacji i numerów portów.

Przy­kłady zakresów:

192.168.0.1-192.168.0.100
1-1024

Przy­kład zakresu w mapie decyzyjnej:

 map {
    type ipv4_addr : verdict;
    elements = {
      192.168.0.1-192.168.0.100 : jump serwery,
      192.168.0.101-192.168.0.255 : drop
    }
}

Uwaga: Gdy inter­wały o nakładających się zakresach są kluczami mapy, to pod­czas prze­szukiwania wybrany zostanie ten element, którego zakres jest bar­dziej precyzyjny (węż­szy). Jeżeli oba nakładające się zakresy są takiej samej długo­ści, a przy­pisane akcje róż­nią się, zwrócony zostanie błąd.

Reguły

Reguły w nftables składają się z wyrażeńdeklaracji. Jeżeli badany pakiet charak­teryzuje się wła­ściwo­ściami pasującymi do przed­stawionych wyrażeniem kryteriów, to zostaje wykonana zadeklarowana akcja lub akcje. Deklaracja jest elemen­tem składniowo­‑gramatycznym, natomiast akcja czyn­no­ścią, która zostanie powzięta.

Wyrażenia

Wyrażenia (ang. expres­sions), jak sama nazwa wskazuje, pozwalają wyrażać pewne war­to­ści. War­to­ści te mogą być stałe (np. adresy sieciowe czy numery por­tów), albo pozyskane dynamicz­nie (np. dane powstałe wcześniej w toku badania zawar­to­ści pakietu czy infor­macje ker­nela dotyczące trasowania bądź śledzenia połączeń). Każda stała war­tość wyrażenia ma jakiś znany typ danych, podob­nie jak każda war­tość wyrażenia, które zostało już obliczone.

Wyrażeń używa się przede wszyst­kim w regułach, aby kon­struować kryteria dopasowywania (ang. match criteria) pakietów, jed­nak znaj­dują one rów­nież zastosowanie w dookreślaniu akcji reguł, np. pod­czas parametryzowania trans­lacji adresów.

Wyrażenia mogą składać się z innych wyrażeń, a ich spoiwem są wtedy odpowied­nie operatory. Powstają wtedy wyrażenia połączone (ang. com­bined expressions).

Możemy wyróż­nić kilka rodzajów wyrażeń:

  • wyrażenia pod­stawowe (ang. primary expressions):
    • wyrażenia stałe (ang. con­stant expressions);
    • wyrażenia nie­stałe (ang. non­‑constant expressions):
      • wyrażenia metadanowe (ang. meta data expressions);
      • wyrażenia ładun­kowe (ang. payload expressions);
      • wyrażenia śledzenia połączeń (ang. con­n­track expressions);
  • wyrażenia połączone (ang. com­bined expressions):
    • wyrażenia bitowe (ang. bitwise expressions);
    • wyrażenia przed­rosk­towe (ang. prefix expressions);
    • wyrażenia listowe (ang. list expressions);
    • wyrażenia zakresowe (ang. range expressions);
    • wyrażenia złączeniowe (ang. con­cat expressins);
    • wyrażenia wieloznaczne (ang. wild­card expressions);
  • wyrażenia relacyjne (ang. relational expressions),
    • pod­stawowe wyrażenia relacyjne (ang. basic relational expressions);
    • porów­nania flag (ang. flag comparisions).

Wyrażenia pod­stawowe

Wyrażenia pod­stawowe to takie wyrażenia, które opisują pojedyn­czą por­cję danych. Mogą być stałe lub dynamiczne (nie­stałe). Te pierw­sze są podawane na etapie kon­struowania wyrażeń, a drugie pozyskiwane w trak­cie pracy.

Wyrażenia stałe

Wyrażenia stałe to przed­stawione z użyciem typów danych nie­zmienne war­to­ści, np. wyrażone numerycz­nie adresy IP czy flagi TCP. Wyrażenia tego typu są obliczane w prze­strzeni użyt­kow­nika (w oprogramowaniu odpowiedzial­nym za prze­twarzanie i kom­pilację reguł).

Nie­które ze stałych wyrażeń mają sym­boliczne odpowied­niki, aby moż­liwe było bar­dziej przej­rzyste reprezen­towanie pew­nych ustalonych war­to­ści, np. usług inter­netowych z użyciem nazw a nie znanych numerów por­tów. Poniż­sza tabela przed­stawia stałe sym­boliczne wyrażenia z podziałem na rodziny zastosowań:

Protokół /​ sek­cjaTyp danychSym­bole stałych
ARParp_oprequest, reply, rrequest, rreply, inrequest, inreply, nak
iface_typeether, ppp , ipip, ipip6, loopback, sit, ipgre
DCCPdccp_pkttyperequest, response, data, ack, dataack, closereq, close, reset, sync, syncack
Ether­netether_typeip, arp, ip6, vlan
CTct_diroriginal, reply
ct_stateinvalid, new, established, related, untracked
ct_statusexpected, seen-reply, assured, confirmed, snat, dnat, dying
ICMPv4icmp_typeecho_reply, destination-unreachable, source-quench, redirect, echo-request, time-exceeded, parameter-problem, timestamp-request, timestamp-reply, info-request, info-reply, address-mask-request, address-mask-reply
icmp_codenet-unreachable, host-unreachable, prot-unreachable, port-unreachable, net-prohibited, host-prohibited, admin-prohibited
ICMPv6no-route, admin-prohibited, addr-unreachable, port-unreachable
icmpv6_typedestination-unreachable, packet-too-big, time-exceeded, param-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering
ICMPicmpx_typeport-unreachable, admin-prohibited, no-route, host-unreachable
IPnf_protoipv4, ipv6
pkt_typeunicast, broadcast, multicast, ipgre
IPv6mh_typebinding-refresh-request, home-test-init, careof-test-init, home-test, careof-test, binding-update, binding-acknowledgement, binding-error, fast-binding-update, fast-binding-acknowledgement, fast-binding-advertisement, experimental-mobility-header, home-agent-switch-message
TCPtcp_flagfin, syn, rst, psh, ack, urg, ecn, cwr
Wyrażenia metadanowe

Wyrażenia metadanowe pozwalają odczytywać metadane badanego pakietu. Infor­macje te nie pochodzą bez­pośred­nio z datagramu, ale mogą być pochodną zawar­tych tam danych. Każde wyrażenie metadanowe składa się ze słowa kluczowego meta, po którym następuje słowo kluczowe (ang. keyword) określające kon­kretny parametr.

ParametrTyp danychOpis
lengthinteger (32 bity)długość pakietu
prioritypriorytet
protocolether_typeprze­noszony protokół
markmarkznacz­nik pakietowy
iififace_indexnumer kolejny inter­fejsu wejściowego
iiftypeiface_typesprzętowy typ inter­fejsu wejściowego
oififace_indexnumer kolejny inter­fejsu wyjściowego
oifnamestringnazwa inter­fejsu wyjściowego
oiftypeiface_typesprzętowy typ inter­fejsu wyjściowego
skuiduidsys­temowy iden­tyfikator użyt­kow­nika dla gniazda
skgidgidsys­temowy iden­tyfikator grupy dla gniazda
rtclassidrealmdziedzina trasowania

Wyrażenia metadanowe mogą być doprecyzowane (ang. qualified) lub nie­do­precyzowane (ang. unqualified). Te pierw­sze korzystają ze składni zawierającej słowo kluczowe meta, a w przy­padku tych drugich narzędzie nft dedukuje, że chodzi o takie wyrażenie, jeśli podano po prostu należący do niego unikatowy klucz.

Wyrażenia ładun­kowe

Dzięki wyrażeniom ładun­kowym możemy dokonywać testów zawar­to­ści danych trans­mitowanych w pakiecie. Warto zauważyć, że powszechną definicją ładunku (ang. payload) pakietu jest ta jego część, w której trans­mitowane są wła­ściwe dane (z pominięciem metadanych czy danych nagłów­kowych). Ter­minologia zastosowana przez autorów nftables na pierw­szy rzut oka różni się od powszech­nie przyjętej, bo w wyrażeniach widzimy odniesienia właśnie do danych w nagłów­kach. Warto jed­nak pamiętać, że w przy­padku generycz­nych metod pozyskiwania danych z pakietów i ramek mamy do czynienia z operowaniem na naj­niż­szym poziomie i dostępem do ładun­ków, którymi są protokoły wyż­szych warstw (włączając ich nagłówki).

Wyrażenia ładun­kowe róż­nią się ze względu na rodzaj obsługiwanego protokołu wyż­szej war­stwy i kon­struując je należy podać dwa słowa kluczowe – pierw­sze określające protokół prze­noszonych danych, a drugie nazwę kon­kret­nego parametru, którego war­tość ma być pobrana.

ProtokółParametrTyp danychOpis
arp
(ARP)
htypeinteger (16 bitów)typ sprzętu
ptypeether_typetyp protokołu
hleninteger (8 bitów)długość adresu sprzętowego
pleninteger (8 bitów)długość adresu protokolarnego
oparp_opoperacja
dccp
(DCCP)
sportinet_serviceport źródłowy
dportport docelowy
ether
(Ether­net)
saddrether_addradres źródłowy
daddradres docelowy
typeether_typetyp prze­noszonego protokołu
ip
(IPv4)
versioninteger (4 bity)wer­sja nagłówka IP
hdrlengthinteger (16 bitów)długość nagłówka IP
lengthcał­kowita długość pakietu
ididen­tyfikator pakietu IP
frag-offprze­sunięcie fragmentu
checksumsuma kon­tro­lna nagłówka IP
ttlinteger (8 bitów)czas trwania pakietu (ang. Time to live, skr. TTL)
tosinteger (6 bitów)pole typu usługi (ang. Type of Service, skr. ToS)
protocolinet_protoprotokół wyż­szej warstwy
saddripv4_addradres źródłowy
daddradres docelowy
ip6
(IPv6)
versioninteger (4 bity)wer­sja nagłówka IP
prioritypriorytet
flowlabelinteger (20 bitów)etykieta prze­pływu
lengthinteger (16 bitów)długość ładunku
nexthdrinet_protoprotokół następ­nego nagłówka (zwykle kap­suł­kowanego pakietu)
hoplimitinteger (8 bitów)mak­symalna liczba przeskoków
saddripv6_addradres źródłowy
daddradres docelowy
ah
(AH)
nexthdrinet_protoprotokół następ­nego nagłówka (zwykle kap­suł­kowanego pakietu)
hdrlengthinteger (8 bitów)długość nagłówka AH
reservedinteger (16 bitów)prze­strzeń zarezerwowana
spiinteger (32 bity)indeks parametrów bez­pieczeń­stwa (ang. Security Parameter Index, skr. SPI)
sequencenumer sekwen­cyjny
esp
(ESP)
spiinteger (32 bity)indeks parametrów bez­pieczeń­stwa (ang. Security Parameter Index, skr. SPI)
sequencenumer sekwen­cyjny
ipcomp
(IPcomp)
nexthdrinet_protoprotokół następ­nego nagłówka (zwykle kap­suł­kowanego pakietu)
flagsinteger (8 bitów)flagi
cfiinteger (16 bitów)indeks parametrów kom­presji (ang. Com­pres­sion Parameter Index, skr. CFI)
tcp
(TCP)
sportinet_serviceport źródłowy
dportport docelowy
sequenceinteger (32 bity)numer sekwen­cyjny
ackseqnumeru potwier­dzenia
doffinteger (4 bity)prze­sunięcie danych
reservedinteger (6 bitów)prze­strzeń zarezerwowana
flagstcp_flagsflagi
windowinteger (16 bitów)szerokość okna
checksumsuma kon­tro­lna
urgptrwskaź­nik danych priorytetowych
sctp
(SCTP)
sportinet_serviceport źródłowy
dportport docelowy
vtaginteger (32 bity)znacz­nik weryfikacyjny
checksumsuma kon­tro­lna
udp
(UDP)
sportinet_serviceport źródłowy
dportport docelowy
lengthinteger (16 bitów)długość cał­kowita pakietu
checksumsuma kon­tro­lna
udplite
(UDP­‑lite)
sportinet_serviceport źródłowy
dportport docelowy
cscovinteger (16 bitów)pokrycie sumy kontrolnej
checksumsuma kon­tro­lna
vlan
(VLAN)
idinteger (12 bitów)iden­tyfikator (VID)
cfiinteger (1 bit)wskaź­nik for­matu kanonicz­nego (ang. Canonical For­mat Indicator, skr. CFI)
pcpinteger (3 bity)punkt kodowy priorytetu (ang. Priority Code Point, skr. PCP)
typeether_typetyp prze­noszonego protokołu
Wyrażenia śledzenia połączeń

Wyrażenia związane z pod­sys­temem śledzenia połączeń pozwalają na dostęp do metadanych, dzięki którym można określić stany pakietów w kon­tek­ście ich przy­porząd­kowania do sesji komunikacyj­nych. Składają się ze słowa kluczowego ct i kolej­nego słowa kluczowego, które określa kon­kretny parametr.

ParametrTyp danychOpis
statect_statestan połączenia
directionct_dirkierunek połączenia
statusct_statusstatus połączenia
markmarkznacz­nik pakietowy
expirationtimeczas waż­no­ści połączenia
helperstringpomoc­niczy moduł obsługi połączenia
l3protonf_protoprotokół war­stwy sieciowej
saddripv4_addr
ipv6_addr
adres źródłowy
daddradres docelowy
protocolinet_protoprotokół war­stwy transportowej
proto-srcinet_serviceiden­tyfikacja źródła war­stwy transportowej
proto-dstiden­tyfikacja celu war­stwy transportowej

Wyrażenia połączone

Wyrażenia połączone, jak sama nazwa mówi, składają się z innych wyrażeń połączonych w logiczny spo­sób, np. operacją wykonywaną na war­to­ściach tych wyrażeń.

Wyrażenia operacji bitowych

Wyrażenia operacji bitowych pozwalają prze­prowadzać działania na pojedyn­czych bitach war­to­ści podanych wyrażeń. Składają się z operatora i dwóch operan­dów, które powinny być war­to­ściami o typach numerycznych:

OperatorZnaczenie
&koniunk­cja bitowa
(iloczyn logiczny, operacja AND)
|alter­natywa bitowa
(suma logiczna, operacja OR)
^bitowa róż­nica symetryczna
(logiczna alter­natywa wykluczająca, operacja XOR)
Wyrażenia przed­rost­kowe

Dzięki wyrażeniom przed­rost­kowym można zapisywać na przy­kład prefiksy sieci. Składają się z dwóch argumen­tów oddzielonych znakiem ukośnika (/). War­tość pierw­szego argumentu powinna być adresem lub typem numerycz­nym, a drugiego liczbą cał­kowitą określającą liczbę bitów pierw­szego, które są istotne (np. powinny być pozostawione pod­czas wyliczania czę­ści adresu IP wskazującego na sieć).

Wyrażenia zakresowe

Wyrażenia zakresowe obsługiwane są przez inter­wały omówione przy okazji przed­stawiania typów złożonych. Składają się z dwóch argumen­tów typu numerycz­nego (wyrażeń war­to­ściowanych do typu numerycz­nego) oddzielonych dywizem (-).

Wyrażenia listowe

Wyrażenia listowe, zwane także listami wyrażeń są listami kolej­nych wyrażeń oddzielonych znakami prze­cinka (,). Używane są na przy­kład do zakomunikowania wielu flag, które muszą być jed­nocześnie ustawione (w tzw. dopasowaniach flag, które opiszemy w dal­szej części).

Wyrażenia złączeniowe

Dzięki wyrażeniom złączeniowym moż­liwe jest tworzenie wyrażeń, których war­to­ści zostaną połączone w jeden łań­cuch. Może być to przy­datne na przy­kład pod­czas tworzenia kluczy dla zbiorów odzwier­ciedlających wielowymiarowe struk­tury lub pod­czas dopasowywania sąsiadujących pól nagłówków.

Wyrażenie złączeniowe można tworzyć na bazie dwóch lub więcej wyrażeń, między którymi znaj­duje się znak kropki (,).

Wyrażenia wieloznaczne

Wyrażenia wieloznaczne pozwalają określać dane domyślne pod­czas korzystania ze słow­ników. Używa się w nich sym­bolu gwiazdki (*), który oznacza, że dany rekord pasuje do dowol­nych danych wej­ściowych (jeśli żaden inny nie został odnaleziony w strukturze).

Wyrażenia relacyjne

Wyrażenia relacyjne, zwane też wyrażeniami dopasowującymi (ang. match expres­sions) pozwalają na spraw­dzanie zależ­no­ści i związ­ków między war­to­ściami. Dzięki nim moż­liwe jest warun­kowanie wykonywanie pew­nych czyn­no­ści, np. jeśli badany pakiet spełni określone kryteria.

Ist­nieją dwa rodzaje wyrażeń dopasowujących: proste wyrażenia relacyjne (ang. basic relational expres­sions) i porów­nania flag (ang. flag comparisions).

Proste wyrażenia relacyjne

Proste wyrażenia relacyjne polegają na użyciu operatora (ang. operator) komunikującego relację między wyrażeniami umiesz­czonymi po jego lewej i prawej stronie:

OperatorZnaczenie
==równe
!=różne
<mniej­sze
<=mniej­sze lub równe
>więk­sze
>=więk­sze lub równe

Jeśli nie podano operatora, to domniemywa się, że chodzi o ==.

W przy­padku, gdy wyrażeniem po prawej stronie jest zbiór, dokonywane jest jego prze­szukanie pod kątem zawierania określonego elementu, podob­nie w przy­padku gdy jest to zakres (interwał).

Dopasowania flag

Drugim rodzajem wyrażeń dopasowujących są dopasowania flag. Flagi to jed­nobitowe elementy, które mogą wyrażać logiczną prawdę lub fałsz. W struk­turach pakietów i ramek roz­maite flagi łączy się w jedno– lub nawet wielobaj­towe łań­cuchy. Powstają wtedy struk­tury, które chociaż dają się reprezen­tować numerycz­nie (jako liczby cał­kowite), są w istocie zestawami parametrów, a znaczenie każ­dego z nich zależy od umiej­scowienia w sekwen­cji. Aby zbadać czy dane flagi z wyrażonego numerycz­nie zestawu są ustawione, należy wykonać operację iloczynu bitowego war­to­ści i przy­gotowanego wcześniej wzorca z zapalonymi bitami w spo­dziewanych miej­scach. Jeśli w rezul­tacie otrzymamy taką samą war­tość jak wzorzec, oznaczało to będzie, że wszyst­kie testowane flagi są aktywne.

Aby nie trzeba było korzystać z bitowej aryt­metyki, można użyć właśnie wyrażeń dopasowujących flagi, a zamiast podawać liczby użyć reprezen­tujących kon­kretne flagi wyrażeń stałych (np. established zamiast 0x00000002 przy określaniu oczekiwanego stanu połączenia).

Wyrażenie dopasowujące flagi składa się z podanego na początku innego wyrażenia (np. wyrażenia ładun­kowego pobierającego z nagłówka pakietu zestaw flag) i listy oddzielonych prze­cin­kami wyrażeń stałych typu bitmask.

Deklaracje i akcje

Dzięki temu, że zamiast z regułami o ustalonej struk­turze mamy do czynienia z małymi pseudoprogramami, możemy bez tworzenia dodat­kowych łań­cuchów czy osob­nych reguł sprawiać, aby w stosunku do pakietu speł­niającego określone kryteria była podej­mowana więcej niż jedna akcja (ang. action) na pod­stawie podanej deklaracji (ang. statement).

Akcjami mogą być róż­norakie operacje, włączając analizy polegające np. na wczytaniu ładunku z pakietu w celu porów­nania pew­nego frag­mentu z wzor­cem. Tak naprawdę znika wyraźny podział na kryteria i cele. Znane z iptables cele, będące efek­tem pracy reguły, stały się właśnie akcjami. Nie­które z zadeklarowanych akcji decydują  o losie pakietu – nazywamy je wtedy decyzjami lub wer­dyk­tami (ang. ver­dicts), a wyrażające je kon­struk­cje deklaracjami decyzyj­nymi (ang. ver­dict statements).

Oto moż­liwe akcje, które mogą być pod­jęte, gdy kryteria dopasowywania reguły zostaną spełnione:

  • decyzje (werdykty):
    • accept – przyjęcie pakietu;
    • reject – odrzucenie pakietu z poin­for­mowaniem nadawcy;
    • drop – zablokowanie pakietu;
    • return – zwrócenie pakietu do łań­cucha nadrzędnego;
    • jump – skok do podanego łańcucha;
    • goto – bez­pow­rotny skok do podanego łańcucha;
  • trans­lacje adresów:
    • snat – trans­lacja adresu źródłowego pakietu (SNAT);
    • dnat – trans­lacja adresu docelowego pakietu (DNAT);
  • modyfikacje:
    • meta mark – ustawianie znacz­nika pakietu;
    • meta priority – ustawianie priorytetu (klasyfikacja QoS);
    • meta nftrace – włączanie śledzenia ścieżki w obrębie nf_tables;
  • ograniczanie pręd­ko­ści:
    • limit rate – wykonywanie akcji tylko dla podanej liczby pakietów w czasie;
  • rapor­towanie:
    • log – zgłoszenie pakietu do pod­sys­temu rapor­towania zdarzeń;
  • zliczanie:
    • counter – zliczanie pakietów (w nftables opcjonalne).

Akcje mogą więc decydować o losie pakietu, ale mogą też dokonywać dal­szej analizy. Życiowym przy­kładem może tu być koniecz­ność rapor­towania i jed­noczesnego oznaczania pakietów – w nftables robi się to z użyciem jed­nego polecenia.

Nie­które akcje reguł nftables można parametryzować, a parametry mogą być ustalane dynamicz­nie, na pod­stawie danych uzyskanych w toku analizy pakietu lub przez odczyt struk­tur utrzymywanych przez jądro (np. tabel śledzenia połączeń). W prak­tyce pozwala to na zarządzanie ruchem sieciowym w spo­sób nie­zwykle elastyczny.

Przy­kłady

Prak­tyczne zastosowania i przy­kłady z wyjaśnieniami przed­stawimy w drugiej czę­ści artykułu, jed­nak dla ciekaw­skich umiesz­czamy kilka przy­kładów stanowiących wstęp do zain­teresowania się nftables.

Tworzenie tabeli nasza (rodzina IPv4), a w niej łań­cucha reguł wejscie, do którego trafiały będą pakiety kierowane do lokal­nej stacji sieciowej:

nft add table ip nasza
nft add chain ip nasza wejscie { type filter hook input priority 0 \; }

Dodanie do tabeli nasza zbioru dobre-adresy zawierającego zaufane adresy IP:

nft add set ip nasza dobre-adresy { type ipv4_addr \; }
nft add element ip nasza dobre-adresy { 192.168.0.1 , 172.30.0.5 }

Dodanie do łań­cucha reguł wejscie reguły, która dla każ­dego z adresów innych niż podany zakres dokonuje zliczania nawiązywanych sesji:

nft add rule ip nasza wejscie ct state new \
                              ip saddr != 192.168.0.1-192.168.0.254 \
                              counter

Dodanie reguły, która dla każ­dego „dobrego adresu” oznacza pakiet mar­kerem 31337:

nft add rule ip nasza wejscie ip saddr @dobre-adresy mark 31337

Wyświetlenie tabeli z zawartością:

nft list table ip nasza

Rezul­tat wykonania powyż­szej komendy:

table ip nasza {
    set dobre-adresy { 
         type ipv4_addr
         elements = { 192.168.0.1, 172.30.0.5 }
    }

    chain wejscie {
        type filter hook input priority 0; policy accept;
        ct state new ip saddr < 192.168.0.1 ip saddr > 192.168.0.254 counter
                                                           packets 0 bytes 0 
        ip saddr @dobre-adresy mark 0x00007a69 
    }
}

Zobacz także

RED.
PW