Linux netfilter Hacking HOWTO <author>Rusty Russell, mailing list <tt>netfilter@lists.samba.org</tt> &nl; Fordította Kis-Szabó András<tt><htmlurl name="kisza@sch.bme.hu" url="mailto:kisza@sch.bme.hu"></tt>&nl; <date>v1.11 2001/10/31, fordítás ideje: 2001. december 20. <abstract> Ez a dokumentum bemutatja a netfilter felépítését, hogyan kell hozzá kiegészítéseket írni, és néhány nagyobb, erre az architektúrára épülő rendszer is bemutatásra kerül, mint pl. a csomagszűrés (packet filtering), kapcsolatkövetés (connecion tracking) és címfordítás (Network Address Translation). </abstract> <!-- Table of contents --> <toc> <!-- Begin the document --> <sect>Bevezetés<label id="intro"> <p> Szervusztok! <p>Ez a leírás olyan, mint egy utazás; egyes részei jól ki vannak dolgozva, míg más részeken esetleg egyedül érezhetitek magatokat. A legjobb tanács, amit adhatok Nektek, hogy szerezzetek egy nagy bögre kávét vagy meleg kakaót, üljetek le egy kényelmes székbe, és figyeljétek a tartalomjegyzéket mielőtt belevágtok a hálózat- programozás néha veszélyes világába. <p>A netfilter felületén elhelyezkedő szerkezet jobb megismeréséhez ajánlom a Packet Filtering HOWTO és a NAT HOWTO átolvasását. A kernel programozásához szükséges információkhoz ajánlom a következő leírásokat: Rusty's Unreliable Guide to Kernel Hacking és Rusty's Unreliable Guide to Kernel Locking. <p>(C) 2000 Paul `Rusty' Russell. Licenced under the GNU GPL. <sect1>Mi az a netfilter? <p> A netfilter egy váz a csomagok megváltoztatására, ami a hagyományos Berkeley socket felületen kívül helyezkedik el. Négy része van. Első: minden protokoll definiál hook-okat (IPv4-nél 5 darab van), amik jól definiált pontokat határoznak meg a csomagok protokoll-stack-beli útjuk során. Mindegyik ilyen ponton a protokoll képes meghívni a netfilter vázat a csomaggal és a hook sorszámával. <p> Második: a kernel részei regisztrálni tudják magukat a különböző hook-okhoz minden protokoll esetében. Amikor a csomag megérkezik a netfilter vázhoz, az ellenőrzi, hogy valaki regisztrálta-e magát a megadott protokollhoz és hookhoz. Amennyiben van ilyen rész, akkor mindegyik lehetőséget kap a csomag megvizsgálására (esetleg megváltoztatására), ezután figyelmen kívül hagyhatja a csomagot (<tt>NF_DROP</tt>), átengedheti (<tt>NF_ACCEPT</tt>), kiveheti a netfilterből a csomagot (<tt>NF_STOLEN</tt>), vagy kérheti a netfiltert, hogy állítsa sorba a csomagot az userspace programok számára (<tt>NF_QUEUE</tt>). <p> Harmadik: a sorba állított csomagok (az ip_queue driverrel) a felhasználói programokhoz szabályozottan kerülnek elküldésre. A csomagok kezelése aszinkron. <p> Az utolsó rész a jó forráskód kommentezésből és a csodálatos dokumentációból áll. Ez szükséges bármilyen kísérleti projecthez. A netfilter mottója (szemtelenül lopva Cort Dougan-tól): <tscreen><verb> ``So... how is this better than KDE?'' </verb></tscreen> <p> Ehhez az egyszerű vázhoz számos kiegészítés készült, amik az előző kernelekhez hasonló funkcionalitást adnak a rendszerhez, ide értve a bővíthető NAT rendszert, valamint a bővíthető csomagszűrő rendszert (iptables). <sect1>Mi a baj azzal, amit a 2.0 és 2.2-es verziókban találhatunk? <p> <enum> <item>Nincs kialakított metódusa a csomagok felhasználói térbe való továbbításának <itemize> <item>Kernel programozás nehéz <item>Kernel programozást C/C++ -ban kell végezni <item>Dinamikus szűrési szabályok nem tartozhatnak a kernelhez <item> 2.2 bevezette a csomagok userspace-be küldését netlinken keresztül, de a csomagok ismételt elküldése lassú. Például nm lehetséges a csomag olyan újraküldése, mintha valós interface-ről érkezne. </itemize> <item>Transzparens proxyzás olyan, mint egy cserépedény: <itemize> <item> <bf>Minden</bf> csomagot meg kell néznünk, hogy van-e olyan socket, ami az adott címre van bind-olva <item> A root idegen címekre is bind-olhat <item> Nem lehet a helyileg készült csomagokat átirányítani <item> REDIRECT nem kezeli le az UDP válaszokat: az UDP névszerver csomagok átirányítása a 1153-as portra nem működik, mert egyes kliensek nem szeretik azokat a válaszokat, amik nem az 53-as portról érkeznek. <item> REDIRECT nincs összhangban a tcp/udp port-hozzárendeléssel: a felhasználó kaphat olyan portot, ami egy REDIRECT szabállyal el van fedve. <item>Legalább kétszer hibás volt a 2.1-es sorozat alatt. <item>A program-kód nagyon csúnya, tolakodó. Az #ifdef CONFIG_IP_TRANSPARENT_PROXY a 2.2.1-es kernelben 34-szer fordul elő 11 fileban, szemben a CONFIG_IP_FIREWALL-al, ami 10-szer található meg 5 fileban. </itemize> <item>Interface-től független csomagszűrő szabályok készítése nem lehetséges: <itemize> <item>Muszáj tudni a helyi interface címét, hogy a helyileg induló és végződő csomagokat meg tudjuk különböztetni az áthaladóktól. <item>S mi több: ez nem elég a redirection vagy masquerading esetén. <item>Forward láncnak csak a kimenő interface-en van információja a csomagról, ami azt jelenti, hogy a hálózati felépítés ismeretében magadnak kell kitalálnod a csomag lehetséges eredetét. </itemize> <item>Masquerading össze van fűzve a csomagszűréssel:<p> A masquerading és a szűrés közti kölcsönhatások bonyolulttá teszik a tűzfal kialakítását: <itemize> <item>Bemeneti szűrésnél a válaszcsomag mintha magának az eszköznek szólna <item>Átmenű szűrésnél a visszaalakított (demasquerad) csomagok nem kerültek ellenőrzésre soha többé <item>Kimeneti szűrésnél: mintha maga az eszköz küldte volna a csomagot </itemize> <item>TOS módosítás, átirányítás, ICMP unreachable és mart (ami a port átirányításra, routolásra és a QoS-re van hatással) szintén a csomagszűrési programkódban kerültek megvalósításra. <item>ipchains kód se nem moduláris, se nem bővíthető (pl. MAC cím szűrés, opciók szűrése, stb.). <item>A szükséges alapok hiánya a különböző megoldások bőséges választékához vezetett: <itemize> <item>Masquerading, és protokollonkénti modulok <item>Gyors állandó NAT routolási kóddal (nincs protokoll támogatás) <item>Port forwarding, redirect, automatikus forwarding <item>The Linux NAT és Virtual Server Project. </itemize> <item>A CONFIG_NET_FASTROUTE és a csomagszűrés összeférhetetlensége: <itemize> <item>Továbbított csomagok három láncon is áthaladnak <item>Nincs lehetőség ezeknek a láncoknak a kihagyására </itemize> <item>Routing védelem (pl. forráscím-ellenőrzés) nem lehetséges a csomagok ellenőrzésének hiánya miatt <item>Nincs lehetőség a csomagszűrő szabályok számlálóinak automatikus olvasására. <item>CONFIG_IP_ALWAYS_DEFRAG egy fordítási opció, nehezebbé téve az életet azok számára, akik egységes felépítést szeretnének. </enum> <sect1>Ki vagy? <p> Az egyetlen, aki olyan bolond, hogy ezt csinálja! Mint az ipchains társszerzője és az aktuális Linux Kernel IP Firewall karbantartója látom azokat a problémákat, amelyek az alkalmazás során előjönnek, valamint azokat a feladatokat, amiket egyes emberek megpróbálnak megoldani. <sect1>Miért omlott össze? <p> Woah! Bizonyára az <bf>elmúlt</bf> héten láttad! <p> Azért, mert nem vagyok egy nagy programozó - legalábbis mint szeretném, és természetesen nem tudtam minden esetet kitesztelni időhiány, felszereltség és/vagy inspiráció miatt. Van egy tesztállományunk, aminek a kibővítését bátran felajánlom. <sect>Hol érhetem el az utolsó változatot? <p>Van egy CVS szerver a samba.org-on, ami tartalmazza a legutolsó HOWTO-kat, userspace eszközöket és tesztbázist. Alkalmi tallózásra használhatod a <url url="http://cvs.samba.org/cgi-bin/cvsweb/netfilter/" name="Webes felületet">. Az utolsó változat megszerzéséhez a következőt kell tenned: <enum> <item> Jelentkezz be a SAMBA CVS szerverbe anonymous-ként: <tscreen><verb> cvs -d :pserver:cvs@cvs.samba.org:/cvsroot login </verb></tscreen> <item> Ajelszóhoz gépeld be, hogy `cvs'. <item> A kód kikéréséhez használd a következőt: <tscreen><verb> cvs -d :pserver:cvs@cvs.samba.org:/cvsroot co netfilter </verb></tscreen> <item> Az utolsó verzióhoz a következőt használd: <tscreen><verb> cvs update -d -P </verb></tscreen> </enum> <sect>Netfilter Felépítés <p>Netfilter csupán hook-ok sorozata a protokoll-stack különböző pontjain (jelen állapotban IPv4, IPv6 és DECnetben). Az (idealizált) IPv4 diagramm a következőképpen néz ki: <tscreen><verb> Csomag útja a netfilter rendszerben: --->[1]--->[ROUTE]--->[3]--->[4]---> | ^ | | | [ROUTE] v | [2] [5] | ^ | | v | </verb></tscreen><label id="netfilter-traversal"> A csomag a bal oldalon érkezik be: átesik az alapvető ésszerűségi ellenőrzéseken (pl. nem csonkolt, IP checksum rendben, nem promiscous csomag), átadásra kerül a netfilter keretrendszernek az NF_IP_PRE_ROUTING [1] hookon. <p> Ezután belépnek a rouolási kódba, itt dől el, hogy a csomag egy másik interface felé tart, vagy helyi feldolgozásra kerül. A routolási kód eldobhatja a nem továbbítható csomagokat. <p> Amennyiben az eszköznek érkezett a csomag, a netfilter váz újra meghívódik a NF_IP_LOCAL_IN [2] hookkal, még mielőtt megkapná a program (ha van ilyen). <p> Ha egy másik interface-nek kell továbbítani, akkor a netfilter rendszer meghívja a NF_IP_FORWARD [3] hookot. <p> A csomag ezután az utolsó hookhoz érkezik, a NF_IP_POST_ROUTING [4] hookhoz, mielőtt ismét kikerülne a hálózatra. <p> Az NF_IP_LOCAL_OUT [5] hook a helyileg keletkezett csomagokra hívódik meg. Mint látható, az útvonalválasztás csak a hook után történik meg: abban az esetben, ha a routing kód előbb kerülne meghívásra (a forráscím és pár opció kiszámítására) - ha meg akarod változtatni a routolást, magát az `skb->dst' mezőt kell módosítanod, ahogy az a NAT kódban is történik. <sect1>Netfilter Alapok <p> Van egy példánk az IPv4-es netfilterhez, ahol láthatod, hogy minden hook meghívódik. Ez a lényege a netfilternek. <p> Kernel modulok bármelyik hookra regisztrálhatják magukat. A modulnak, ami regisztrálja magát, prioritást kell rendelnie a funkciójához; ezután amikor a netfilter hook aktivizálódik a belső hálózati részekből, minden bejegyzett modul meghívásra kerül a prioritási sorrend alapján, s szabadon módosíthatja a csomagot. A modul öt dologra kérheti a netfiltert: <enum> <item> NF_ACCEPT: folytassa az útját. <item> NF_DROP: dobja el a csomagot, ne folytassa az útját. <item> NF_STOLEN: átvettem a csomagot, ne folytassa az útját. <item> NF_QUEUE: rakja be a sorba a csomagot (rendszerint userspace-beli feldolgozásra). <item> NF_REPEAT: ismételje meg a hook-ot. </enum> <p> A netfilter egyéb részei (sorba-állított csomagok kezelése, kiegészítések) a kernel részben kerülnek bemutatásra. <p> Ezen az alapon nagyon összetett csomagmanipulációt tudunk kiépíteni, mind azt a következő két szakasz is bemutatja. <sect1>Csomag kiválasztás: IP Tables <p> A csomag-kiválasztási rendszert IP Tablesnek hívják, s a netfilter vázra épült. Közvetlen leszármazottja az ipchains (ami az ipfwadmnak, s ami a BSD ipfw-jének) - csak bővíthetőséggel. Kernel modulok tudnak új táblákat bejegyezni, s arra ítélni egy csomagot, hogy az adott táblán haladjon végig. Ez a kiválasztás használatba kerül a csomagszűrésnél (`filter' tábla), hálózati címfordításnál (NAT) (`nat' tábla) valamint az általános routolás előtti csomagmódosításnál (`mangle' tábla). <p>A következő hookok vannak regisztrálva a netfilterben (abban a sorrendben felsorolva, ahogyan meghívásra kerülnek): <tscreen><verb> --->PRE------>[ROUTE]--->FWD---------->POST------> Conntrack | Filter ^ NAT (Src) Mangle | | Conntrack NAT (Dst) | [ROUTE] (QDisc) v | IN Filter OUT Conntrack | Conntrack ^ Mangle | | NAT (Dst) v | Filter </verb></tscreen> <sect2>Csomagszűrés <p> Ez a tábla, a `filter' sose változtatja meg a csomagot: csak megszűri. <p> Az iptables előnye az ipchains-szel szemben, hogy kicsi és gyors, valamint a netfilterbe az NF_IP_LOCAL_IN, NF_IP_FORWARD és NF_IP_LOCAL_OUT pontokon kapcsolódik. Ez azt jelenti, hogy minden egyes csomag egy (és csak is egy) helyen kerülhet megszűrésre. Ez sokkal könnyebbé teszi a használatát az ipchains-szel szemben. Szintén előny, hogy a netfilter váz biztosítani képes a be és kimenő interface-t is az NF_IP_FORWARD hooknak, ami számos szűrést egyszerűbbé tesz. <p> Megjegyzés: az ipchains és az ipfwadm is portolásra került a netfilter vázra, lehetővé téve a meglevő rendszerek használatát. <sect2>NAT <p> Ez a `nat' tábla birodalma, ami két helyről eszi a csomagokat: a nem helyi csomagokat a NF_IP_PRE_ROUTING és a NF_IP_POST_ROUTING hookokról, amik lehetőséget adnak a cél és forrás megváltoztatására. Ha a CONFIG_IP_NF_NAT_LOCAL definiált, a NF_IP_LOCAL_OUT és NF_IP_LOCAL_IN hookok kerülnek használatba a helyi eredetű csomagok esetében. <p> Ez a tábla kevésben tér el a `filter' táblától: a kapcsolatnak csak az első csomagja halad keresztül a láncon, s az eredmény ezután minden csomagra alkalmazva lesz a kapcsolat alatt. <sect3>Masquerade, Port Forward, Transparent Proxy <p>A NAT-ot két részre bontottam: Source NAT (ahol a csomag forrása változhat) és Destination NAT (ahol az első csomag célja változhat). <p>Masquerading az SNAT egy speciális formája; a port forwarding és a transzparens proxy-zás a DNAT esetei. Ezek mindegyike megvalósítható a NAT keretrendszerrel, ahelyett, hogy különálló rendszerek lennének. <sect2>Csomag megváltoztatás <p>A csomagváltoztató tábla (`mangle' tábla) használható a csomag tartalmának megváltoztatására. Ez a NF_IP_PRE_ROUTING és a NF_IP_LOCAL_OUT pontokon kapcsolódik a netfilterhez. <sect1>Kapcsolatkövetés <p> Kapcsolatkövetés sarkalatos pontja a NAT-nak, de ennek ellenére külön modulként került megvalósításra; ez megengedi, hogy a csomagszűrő egy kiegészítése egyszerűen és tisztán használja a kapcsolatkövetést (a `state' modul). <sect1>Egyéb kiegészítések <p>Az új flexibilitás lehetőséget ad igazán vad dolgokra, de azok számára is nyitott a lehetőség, akik bővítéseket, vagy teljesen helyettesítő részeket szeretnének belevenni a rendszerbe. <sect>Programozói információk <p>Elmondok egy titkot: az én kicsi hörcsögöm végezte a teljes kódolást. Én csak egy csatorna, egy arcvonal vagyok a kis kedvencem nagy tervében. Nos, ne engem hibáztassatok, ha hiba van benne, hanem az aranyos, kis szőröst. <sect1>ip_tables megértése <p>iptables egyszerűen a szabályok egy nevesített tömbjét szolgáltatja (innen a név: ip-táblák), valamint informálnak a beérkező csomagok útjáról. Miután egy tábla bejegyzésre került, a felhasználói programok képesek olvasni és kicserélni a tartalmát a getsockopt() és setsockopt() függvényekkel. <p>iptables nincs bejegyezve egyik netfilter hookhoz sem: arra számít, hogy más modulok megteszik ezt, s a csomagokat helyes sorrendben továbbítják felé; egy modult be kell jegyezni a netfilter hookokra, valamint az ip_tables-be is, valamint lehetőséget kell adni az ip_tables meghívására, ha a hook elérésre került. <sect2> ip_tables adatstruktúra <p>A kényelmesség miatt azonos adatstruktúra társul egy szabályhoz az userspace-ben és a kernelben is, annak ellenére, hogy egyes mezők csak a kernelben kerülnek alkalmazásra. <p>Minden szabály a következő részekből áll: <enum> <item> `struct ipt_entry' <item> Nulla vagy több `struct ipt_entry_match' struktúra, mindegyik változó hosszúságú (0 vagy nagyobb) adatterülettel. <item> `struct ipt_entry_target' struktúra, változó hosszúságú (0 vagy nagyobb) adatterülettel. </enum> A szabály változó természete nagy szabadságot kölcsönöz a bővítményeknek, mint láthatjuk is akár minden match vagy target különböző mennyiségű adatot hordozhat. Ez azonban néhány csapdát hordoz magában: figyelni kell az igazításra, kerekítésre. Ennek során ügyelünk, hogy a `ipt_entry', `ipt_entry_match' és a `ipt_entry_target' struktúrák megfelelő méretűek legyenek, és a rendszeren elérhető legnagyobb igazítási méretre legyenek felkerekítve (IPT_ALIGN() macro). <p> `struct ipt_entry' mezői: <enum> <item> `struct ipt_ip' rész: specifikációkat tartalmaz az IP fejlécre vonatkozóan, amire egyezni fog. <item> `nf_cache' bitmező, ami azt mutatja meg, hogy a csomag melyik mezőit vizsgálja a szabály. <item> `target_offset' mező, ami a szabály az ipt_entry_target struktúra elejétől mért távolságát mutatja. Ez mindit igazított érték (IPT_ALIGN macro). <item> `next_offset' adja meg a szabály teljes méretét, beleértve az egyezéseket és a célokat. (match és target). Ez szintén igazított érték (IPT_ALIGN macro). <item> `comefrom' a kernel által használt változó, a csomag útjának követésére. <item> `struct ipt_counters' mező tartalmazza a csomag és byte számlálókat, amik az adott szabállyal való egyezésre mutatnak. </enum> <p> A `struct ipt_entry_match' és `struct ipt_entry_target' struktúrák nagyon hasonlóak: tartalmazzák a teljes (IPT_ALIGN-olt) hossz mezejét (`match_size' és `target_size'), valamint egy unionban a match vagy target nevét (userspace) és mutatóját (kernel). <p> A szabályszerkezet trükkös természete miatt pár segédfunkció is elérhető: <descrip> <tag>ipt_get_target()</tag> Ez a beépített függvény visszaad egy pointert a szabály targetjére. <tag>IPT_MATCH_ITERATE()</tag> Ez a makró meghívja az adott funkciót minden egyes match-ra az adott szabályban. A függvények első argumentuma egy `struct ipt_match_entry', míg a többi (ha létezik) az, amit az IPT_MATCH_ITERATE() makró kapott. A funkció nullát ad vissza az iteráció folytatásához, nem nulla értéket a megszakításához. <tag>IPT_ENTRY_ITERATE()</tag> Ez a funkció mutatókat vár egy bejegyzésre, a tábla teljes bejegyzéseinek méretére, valamint a meghívandó funkcióra. A funkció első argumentuma egy `struct ipt_entry', és a további argumentumai (ha vannak) megegyeznek az IPT_ENTRY_ITERATE()-ben megadottakkal. A funkció nullát ad vissza az iteráció folytatásához, nem nulla értéket a megszakításához. </descrip> <sect2>ip_tables userspace formája <p>Userspace-nek négy művelete van: olvasni tudja az aktuális táblát, információhoz juthat (hook helye, tábla mérete), kicserélheti a táblát (és megtarthatja a régi számlálókat), és új számlálókat adhat hozzá. <p>Ezzel bármilyen elemi művelet szimulálható userspace-ből: a libiptc könyvtáron keresztül, ami kényelmes "add/delete/replace" szemantikát ad a programokhoz. <p>Amiért ezek a táblák továbbításra kerülnek a kernelbe, az igazítás komoly kérdés olyan rendszerekben, ahol eltérő a méret (pl. Sparc64 kernel 32bites userspace-el). Ezek az esetek az IPT_ALIGN makró felüldefiniálásával vannak megoldva a `libiptc.h'-ban. <sect2> ip_tables használat <p>A kernel azon a helyen kezdi el az értelmezést, ahol az adott hook kívánja. Az a szabály kerül vizsgálatra, aminek a `struct ipt_ip' elemei megegyeznek, minden `struct ipt_entry_match' sorban ellenőrzésre kerül (a match-al összerendelt függvényen keresztül). Ha a match függvény 0-t ad eredményül, akkor megáll az értelmezés. Ha a `hotdrop' paramétert 1-be állítja, a csomag azonnal eldobásra kerül. <p>Ha az iteráció végigér a számlálók növelésre kerülnek, s a `struct ipt_entry_target' kerül megvizsgálásra: ha ez egy alap target, akkor a `verdict' mező kerül olvasásra (negatív jelenti azt, hogy már van döntés, a pozitív pedig egy ugrási eltolást ad meg.) Ha pozitív a válasz, s az offset nem a következő szabályra mutat, a `back' változó beállításra kerül és az előző `back' értéke a szabály `comefrom' mezőjébe kerül. <p>A nem standard targeteknél a target függvény hívódik meg: ez egy döntést ad vissza (nem alap targetek nem tudnak ugrani, ugyanis megsérthetnék a hurokdetektálást). A döntés lehet IPT_CONTINUE a következő szabályon való továbbhaladáshoz. <sect1>Iptables bővítése <p>Amiért ilyen lusta vagyok, az <tt>iptables</tt> meglehetősen jól bővíthető. Ez alapjában egy csalás a munka másra való áthárításával, ami kb. az, amiről az Open Source szól (Free Software - ahogy RMS mondaná - a szabadságról szól, és én egy beszédén ülve írtam ezt.) <p><tt>iptables</tt>i kibővítése potenciálisan két részből áll: a kernel kibővítése egy új modul írásával, és lehetőség szerint az userspace rész <tt>iptables</tt> programjának bővítése egy új shared könyvtár írásával. <sect2>A Kernel <p>Egy kernel modult írni magában egy egyszerű feladat, ahogy azt a példában is láthatod. Amire oda kell figyelned, hogy a kódodnak újra-belépőnek kell lennie: előfordulhat, hogy egy csomag érkezik az userspace-ből, míg egy másik egy megszakításon keresztül. SMP esetében minden CPU esetében lehet csomag a megszakításokon (2.3.4 és fölötte). <p> Azok a funkciók, amikről tudnod kell: <descrip> <tag>init_module()</tag> Ez a modul belépési pontja. Ez egy negatív hibaszámot ad vissza, vagy 0-t, ha sikeresen regisztrálta magát a netfilterben. <tag>cleanup_module()</tag> A modul kilépési pontja; itt veheti ki magát a modul a netfilterből. <tag>ipt_register_match()</tag> Ez egy új match bejegyzésére használható. Egy `struct ipt_match'-al kezelhető, amit rendszerint static-ként deklarálnak. <tag>ipt_register_target()</tag> Ez agy új target bejegyzésére használható. Egy `struct ipt_target'-al kezelhető, amit rendszerint static-ként deklarálnak. <tag>ipt_unregister_target()</tag> A tergetem visszavonására használható. <tag>ipt_unregister_match()</tag> A match-em visszavonására használható. </descrip> <p>Egy figyelmeztetés a trükkös dolgokkal kapcsolatban (mint pl. számlálók nyújtása) az extra helyekben az új match-ben vagy target-ban. SMP eszközön a teljes tábla megduplázódik egy memcpy()-val minden CPU-ra: ha valóban központi információt akarsz tárolni, akkor nézd meg azt, ahogy ez a `limit' match-ben megvalósításra került. <sect3>Új match funkciók <p>Új match funkciók rendszerint különálló modulokként kerülnek megírásra. Ez lehetővé teszi ezeknek a moduloknak a felváltott bővítését, bár ez rendszerint nem szükséges. Egyik lehetőség a netfilter váz `nf_register_sockopt' funkciója a felhasználói kapcsolatteremtésre. Másik lehetőség szimbólumok kiexportálása más modulok felé, ahol regisztrálhatják magukat, azonos módon, mint ahogy a netfilter és az ip_tables csinálja. <p>Az új match funkciód központi része az ipt_match struktúra, ami az `ipt_register_match()'-nak kerül átadásra. A struktúra szerkezete: <descrip> <tag>list</tag> Tetszőleges junk lehet, állítsd `{ NULL, NULL }'-ra. <tag>name</tag> Ez a mező tartalmazza a match funkció nevét, ahogyan az userspace-ből hivatkozunk rá. A név lehetőleg egyezzen meg a modul nevével (pl. ha a név `mac', akkor a modul neve legyen `ipt_mac.o'), hogy az automatikus betöltés működhessen. <tag>match</tag> Ez egy mutató a match funkcióra, ami megkapja az `skb', az `in' és `out' device mutatókat (ami lehet NULL, a hook-tól függően), egy mutatót a match adatra az éppen feldolgozott szabályban (az a struktúra, ami az userspace-ben készült), IP offsetet (nem nulla jelenti hogy nem-fejléc csomag), egy pointert a protokollfejlécre, az adat hosszát (pl. a csomag hossza az IP fejléc méretével csökkentve) és végül egy mutatót a `hotdrop' változóra. Ez nem-nulla értékkel jelzi, ha a csomag egyezett, és a `hotdrop' 1-be állításával ill. 0 visszaadásával dobathatja el azonnal a csomagot. <tag>checkentry</tag> Ez egy mutató, ami egy olyan függvényre mutat, ami ellenőrzi a szabály specifikációját; ha 0-t ad vissza, akkor nem lett elfogadva a szabály. Például: a `tcp' match típus csak TCP csomagokat fog elfogadni, s ha a `struct ipt_ip' része a szabálynak nem tartalmazza, hogy a protokollnak TCP-nek kell lennie, nullát ad vissza. A táblanév argument segít megtalálni, hogy hol van a szabály, míg a `hook_mask' bitmask megadja, hogy melyik hookokból kerülhet meghívásra a szabály. Ha a szabály nem függ a hookoktól, akkor figyelmen kívül lehet hagyni. <tag>destroy</tag> Ez egy mutató egy olyan függvényre, ami akkor hívódik meg, amikor a match-ot tartalmazó szabály törlésre kerül. Ez lehetővé teszi a dinamikus területfoglalást a chechkentry-ben, s a felszabadítást. <tag>me</tag> Ez a mező `THIS_MODULE'-ra van beállítva, egy pointert ad erre a modulra. Egy használatszámlálóhoz van kötve, ami fel- le változik amikor szabály születik vagy törlésre kerül. Ez meggátolja a felhasználót a modul eltávolításában (cleanup_module()) ha szabály tartalmazza. </descrip> <sect3>Új target-ek <p>Az új target-ek rendszerint különálló modulként kerülnek megvalósításra. A tárgyalás módja megegyezik az `Új match funkciók' fejezetben találhatókkal. <p>Az új targeted központi része az ipt_target struktúra, ami az ipt_register_target()-nek kerül átadásra. A struktúra a következő mezőket tartalmazza: <descrip> <tag>list</tag> A mező értéke tetszőleges junk lehet, legyen `{ NULL, NULL }'. <tag>name</tag> A target neve, ahogyan az userspace-ből hivatkoznak rá. A név lehetőleg egyezzen meg a modul nevével (pl. ha a név `REJECT', a modult hívjad `ipt_REJECT.o'-nak), hogy az automatikus betöltés működjön. <tag>target</tag> Pointer a target funkcióra, ami megkapja az skbuff-ok, a hook számok, a be- és kimenő eszközöket (bármelyik lehet NULL), egy pointert a target adataira és a szabály helyét a táblában. A visszatérési érték lehet IPT_CONTINUE(-1), ha a vizsgálat folytatódhat, vagy egy netfilter döntés (NF_DROP, NF_ACCEPT, NF_STOLEN stb.). <tag>checkentry</tag> Egy mutató arra a funkcióra, amely a szabály szerkezetét ellenőrzi. Nullával jelzi, ha a megadott szabály nem elfogadható. <tag>destroy</tag> A target törlésekor meghívandó függvényre egy mutató. Lehetőség van a checkentry-ben lefoglalt területek felszabadítására. <tag>me</tag> A mező értéke `THIS_MODULE', ami egy pointert ad a modulra. Tartalmaz egy számlálót, aminek az értéke nő vagy csökken amikor a targetre hivatkoznak, vagy megszüntetik a hivatkozást. Ez meggátolja a felhasználót a modul eltávolításában (cleanup_module()), ha szabály hivatkozik rá. </descrip> <sect3>Új táblák <p>Tetszőleges célra létrehozhatsz egy táblát, amikor csak akarod. Ehhez a `ipt_register_table()'-t kell meghívnod egy `struct ipt_table' struktúrával, aminek a következő mezői vannak: <descrip> <tag>list</tag> A mező értéke tetszőleges junk, legyen `{ NULL, NULL }'. <tag>name</tag> A táblának a nevét atrtalmazza, ahogyan az userspace-ből hivatkozunk rá. A név lehetőleg egyezzen meg a modul nevével (pl. ha `nat' a tábla neve, akkor a modul legyen `iptable_nat.o'), hogy az autómatikus betöltés működjön. <tag>table</tag> Ez egy teljesen kitöltött `struct ipt_replace', ahogy az userspace-ből a tábla kicserélésére használják. A `counters' mutatót NULL-ra kell állítani. Az adatterültet `__initdata'-nak lehet deklarálni, s így betöltés után eldobható. <tag>valid_hooks</tag> Ez egy bitmaszk az IPv4 netfilter hook-okról, ahol a csomag belelép: a bejegyzés helyességének ellenőrzésére használható, valamint az ipt_match és ipt_target `checkentry()' funkciójának lehetséges hookjainak származtatásához. <tag>lock</tag> Ez egy írható/olvasható zár(lock) az egész táblára; RW_LOCK_UNLOCKED-re kell beállítani. <tag>private</tag> Az ip_tables kód belső használatára fenntartott. </descrip> <sect2>Userspace eszközök <p>Nos, megírtad a szép, csillogó kernelmodulodat, s most használni szeretnéd a funkcióit userspace-ből. Ahelyett, hogy magát az <tt>iptables</tt>-t kellene módosítani minden bővítéshez, egy késő 90-es évek beli technológiát használok: furbikat. Bocsánat, shared library-kre gondoltam. <p>Új táblák rendszerint nem igényelnek bővítést az <tt>iptables</tt>-ben: a felhasználó használhatja a `-t' opciót az új tábla használatához. <p>A könyvtárban jó, ha van egy `_init()' funkció, ami automatikusan meghívódik betöltéskor: egy megfelelője a kernel modulok `init_module()' funkciójának. Ez meghívhatja a `register_match()' vagy a `register_target()' függvényeket, attól függően, hogy a könyvtár match-et vagy target-et tartalmaz. <p>A könyvtárat el kell készítened: ez használható a struktúrák beállítására, vagy további opciók nyújtására. Jelenleg ragaszkodunk a shared library-hoz, még akkor is, ha nem csinál semmit, az olyan problémák csökkentésére, amik a könyvtár hiányára hivatkoznak. <p>Van pár hasznos funkció az `iptables.h' fejlécfileban: <descrip> <tag>check_inverse()</tag> azt ellenőrzi, hogy egy argument `!'-e, ha az, akkor beállítja az `invert' flaget, ha még nem volt beállítva. Ha igazat ad vissza, az optind-t növelned kell, ahogy a példában is látszik. <tag>string_to_number()</tag> egy karaktersort számmá konvertál az adott tartományban, -1-et ad vissza ha hibás, vagy a határon túli a karaktersor. <tag>exit_error()</tag> lehetőleg ezt hívd meg, ha hibát találtál. Rendszerint az első paramétere `PARAMETER_PROBLEM', ami azt jelenti, hogy a felhasználó hibás parancssort adott be. </descrip> <sect3>Új match funkciók <p>A könyvtár _init() funkciója egy `register_match()' hívást tartalmaz egy statikus `struct iptables_match' struktúra-pointerrel, aminek a következő mezői vannak: <descrip> <tag>next</tag> A match-ek láncolt listájának kezelésére használják (pl. a szabályok listája). alapértelmezésben NULL értékre kell állítani. <tag>name</tag> A match funkció neve. Meg kell egyeznie a könyvtár nevével (pl. `tcp' - `libipt_tcp.so'). <tag>version</tag> Rendszerint a IPTABLES_VERSION makróra van állítva: azt biztosítja, hogy az <tt>iptables</tt> nem olvas be hibás könyvtárat. <tag>size</tag> A match adat mérete ehhez a match-hez; lehetőleg használd az IPT_ALIGN() makrót a helyes igazításhoz. <tag>userspacesize</tag> Néhány matchben a kernel módosít pár mezőt. Ez azt jelenti, hogy egy egyszerű `memcmp()' nem elég két szabály összehasonlítására (a delete-matching-rule funkcióhoz elengedhetetlen). Ha ez a helyzet, akkor az állandó mezőket a struktúra elején kell elhelyezni, s a nem módosuló rész méretét kell itt megadni. Rendszerint ez megegyezik a `size' mezővel. <tag>help</tag> Az a funkció, ami az opciók használatát írja ki. <tag>init</tag> Az extra helyek beállítására használható (ha van) az ipt_entry_match struktúrában, s állíthatja az nfcache biteket. Ha valami olyant vizsgálsz, ami nem kifejezhető a `linux/include/netfilter_ipv4.h'-val, egyszerűen OR-old meg az NFC_UNKNOWN bitet. A `parse()' előtt fog meghívódni. <tag>parse</tag> Ez akkor kerül meghívásra, ha egy nem ismert funkciót talál a parancssorban: nem nullát ad vissza, ha valóban a könyvtáradhoz tartozik. `invert' értéke igaz, ha már talált `!'-t. A `flags' kizárólag a match könyvtár által használt, rendszerint bitmaszk tárolására használják, ami a beállított kapcsolókat reprezentálja. Meg kell győződnöd arról, hogy az nfcache mezőt állítod. Szükséged lehet az `ipt_entry_match' méretének növelésére áthelyezéssel, de a méretet az IPT_ALIGN makróval kell megadnod! <tag>final_check</tag> A parancssor értelmezése után hívódik meg, és a `flags' értékét vizsgálja. Lehetőséget ad összeférhetetlenség-vizsgálatra, s az `exit_error()' hívással jelezheted a problémát. <tag>print</tag> A lánclistázó kód használja a (standard kimenetre) való funkciókiíráskor. A numeric flag be van állítva, ha a felhasználó megadta a `-n' kapcsolót. <tag>save</tag> A parse ellentettje: az `iptables-save' használja a szabályt létrehozó opciók visszaállításához. <tag>extra_opts</tag> Ez egy NULL-lezárt listája az extra funkcióknak, amiket a könyvtárad nyújt. Az eddigi opciókkal összedolgozásra kerül, s úgy kerül a getopt_long-hoz (nézd meg a mauálját). A getopt_long visszatérési kódja az első argument lesz (`c') a `parse()' funkcióhoz. </descrip> Van még pár extra funkció a struktúra végén, de azokat az <tt>iptables</tt> használja: nem kell beállítanod őket! <sect3>Új target-ek <p>A könyvtárak _init() funkciója kezeli a `register_target()'-t, s a statikus `struct iptables_target' struktúráját, aminek a felépítése hasonló az iptables_match struktúrájához. <sect2>`libiptc' használata <p><tt>libiptc</tt> a táblakezelő könyvtár, az iptables szabályok listázására és módosítására tervezett könyvtár. Jelenleg csak az iptables program használja, könnyű egyéb programok implementálása. Root jogokkal kell rendelkezned a használatához. <p>A kernel táblák magukban csak egyszerű szabálytáblázatok, valamint belépési pontokat tartalmazó halmazok. A láncok elnevezése ("INPUT", stb.) csak egy, a könyvtárak által szolgáltatott leképezés. Felhasználó által definiált láncok neveit a chain fejléce elé beillesztett hiba-bejegyzés tartalmazza a target extra adat-területén (a beépített chain pozíciók a három tábla belépési pontjainál vannak definiálva. <p>A következő standart target-ek támogatottak: ACCEPT, DROP, QUEUE (amik NF_ACCEPT, NF_DROP és NF_QUEUE -ra vannak fordítva), RETURN (ami a speciális IPT_RETURN-nek felel meg, s az ip_tables kezeli), valamint a JUMP (ami egy eltolási értékre (offset) fordul le). <p>Az `iptc_init()' meghívásakor a tábla - beleértve a számlálókat - kerül beolvasásra. A tábla az `iptc_insert_entry()', `iptc_replace_entry()', `iptc_append_entry()', `iptc_delete_entry()', `iptc_delete_num_entry()', `iptc_flush_entries()', `iptc_zero_entries()', `iptc_create_chain()', `iptc_delete_chain()' és `iptc_set_policy()' függvényekkel módosítható. <p>A változtatások nem kerülnek visszaírásra, csak az `iptc_commit()' meghívása után. Ez azt jelenti, hogy két felhasználó is módosíthatja ugyanazt a táblát, s így versenyhelyzetet kialakítva; szükség lenne lock-olásra, de még nem készült el. <p>A számlálókra nem él a versenyhelyzet: a visszaíráskor az érték korrigálódik az eltelt idő alatti változással. <p>Számos segítő funkciót implementáltak: <descrip> <tag>iptc_first_chain()</tag> Visszaadja az első lánc nevét a táblában. <tag>iptc_next_chain()</tag> A következő chain nevét adja, NULL-al jelzi a lista végét. <tag>iptc_builtin()</tag> Igazat ad vissza, ha az adott láncnév egy beépített chain neve. <tag>iptc_first_rule()</tag> Egy mutatót ad vissza az első szabályra az adott láncon belül. NULL jelzi az üres láncot. <tag>iptc_next_rule()</tag> A következő szabályt adja az adott chain-ben. NULL jelenti a lánc végét. <tag>iptc_get_target()</tag> Az adott szabály target-jét adja vissza. Ha ez egy kiterjesztett target, akkor a nevét adja vissza. Ha egy másik chain-re ugrás, akkor az új chain nevét. Ha egy döntés (pl. DROP), akkor azt tartalmazza, s ha nincs target (accounting szabály), akkor üres sort. <p>Figyelem! Ezt a funkciót célszerű használni az ipt_entry struktúra `verdict' mezeje helyett, mert bővebb információ szerezhető belőle. <tag>iptc_get_policy()</tag> A beépített lánc policy-ét kérdezi le, valamint a `counters' argumentumát kitölti a szabály találati paramétereivel. <tag>iptc_strerror()</tag> Az iptc könyvtár hibajelzéseinek jelentéssel való kibővítését adja. A hibával visszatérő függvény beállítja az errno értékét, s ez a funkció kiírja a hibakódhoz tartozó üzenetet. </descrip> <sect1>A NAT megértése <p>Üdvözöllek a kernel címfordítási részében! Felhívnám arra a figyelmedet, hogy az itt nyújtott infrastruktúra inkább a teljességre, mint a nyers hatásfokra helyezte a hangsúlyt, és a jövő trükkjei jelentősen növelhetik a teljesítőképességet. Jelenleg boldog vagyok, hogy működik. <p>A NAT fel van bontva kapcsolat-követési (connection tracking) (ez nem módosítja a csomagokat) és magára a fordítási kódra. Connection tracking az iptables modulokban való felhasználhatóságra lett tervezve, így olyan állapotok szövevényes rendszer alapján dönthet, amelyek a NAT-ot nem érdeklik. <sect2>Connection Tracking <p>A Connection tracking hookjai nagy prioritási szinttel az NF_IP_LOCAL_OUT és az NF_IP_PRE_ROUTING hookokban találhatók, így a rendszerbe való megérkezésük előtt vizsgálja a csomagokat. <p>Az nfct mező az skb struktúrában egy pointer az ip_conntrack struktúrába, az infos[] tömb egy elemére. Ennélfogva meg tudjuk mondani az skb állapotát az általa mutatott elemen keresztül: ez a mutató tárolja az állapot-struktúrát és az skb - állapot közötti kapcsolatot is. <p>A legjobb eljárás az `nfct' mező kicsomagolására az `ip_conntrack_get()' használata, ami NULL-al jelzi, ha nincs beállítva, vagy visszaadja a kapcsolat-mutatót, valamint kitölti a ctinfo-t, ami leírja a csomag és a kapcsolat viszonyát. Ennek a változónak számos értéke lehet: <descrip> <tag>IP_CT_ESTABLISHED</tag> A csomag egy már létrejött kapcsolathoz tartozik, az eredei irányban. <tag>IP_CT_RELATED</tag> A csomag kapcsolatban van egy connection-nel, és az eredi irányba halad. <tag>IP_CT_NEW</tag> A csomag egy új kapcsolatot próbál kialakítani (természetesen az eredeti irányban). <tag>IP_CT_ESTABLISHED + IP_CT_IS_REPLY</tag> A csomag egy már létrejött kapcsolathoz tartozik, az ellenkező irányban. (Válasz) <tag>IP_CT_RELATED + IP_CT_IS_REPLY</tag> A csomag kapcsolatban van egy connection-nel, és az ellenkező irányba halad. (Válasz) </descrip> Így a válasz csomagra egyszerűen ellenőrizhetünk egy >=IP_CT_IS_REPLY teszttel. <sect1>Connection Tracking/NAT kibővítése <p>Ezek a programvázak tetszőleges protokollhoz és leképezési módhoz való illesztésre lettek tervezve. Néhány ilyen leképezés nagyon speciális is lehet, pl. terheléselosztás vagy tartalékolás. <p>Belsőleg a connection tracking párokba alakítja a csomagot, ami az érdekes részét mutatja a csomagnak, mielőtt függőségekre vagy egyező szabályokra keresne. Ennek a leírónak van egy módosítható és egy nem módosítható része is; "src" és "dst" névvel, ahogy ez a Source NAT-ban az első csomagnál látható (lehetőleg kell lennie egy válasz csomagnak is a Destination NAt világában). Ez a leíró minden csomagra az adott folyamaton belül az adott irány mellett azonos. <p>Például a TCP csomag leírójában a módosítható rész tartalma: forráscím é sport, a nem módosíthatóé: célcím és port. A két résznek nem feltétlenül kell azonos szerkezetűnek lennie: pl. egy ICMP csomagnál a forráscím és az ICMP id a módosítható; míg a célcím és az ICMP típus és kód a nem módosítható rész. <p>Minden leírónak (tuple) van egy inverze, ami a válsz csomagnak a leírója. Például egy ICMP ping csomagnak (icmp id 12345, from 192.168.1.1 to 1.2.3.4) az ellentettje a ping-reply csomag (icmp id 12345, from 1.2.3.4 to 192.168.1.1). <p>Ezek a párok, amiket a `struct ip_conntrack_tuple' testesít meg, széles körben használtak. Valójában azzal a hook-kal, ahonnan a csomag jött (aminek a várható módosításra van hatása) és a beérkezési device adataival a csomaggal kapcsolatos összes információnkat tartalmazza. <p>A legtöbb leyrót a `struct ip_conntrack_tuple_hash' struktúrában találjuk meg, ami egy két-irányba láncolt lista kezeléséhez szükséges kiegészítést és egy segédmutatót (a leíró melyik kapcsolathoz tartozik) rendel még mellé. <p>A kapcsolatot a `struct ip_conntrack'-al reprezentáljuk: két `struct ip_conntrack_tuple_hash' mezője van: egyik az eredeti irányba mutat (tuplehash[IP_CT_DIR_ORIGINAL]), a másik pedig a válasz-csomagokra (tuplehash[IP_CT_DIR_REPLY]). <p>Az első dolog, amit a NAT kód végrehajt az, hogy megnézi, hogy a connection tracking kód kicsomagolta-e a leíróját, s egy meglevő kapcsolathoz tartozónak találta-e az skbuff nfct mezőjének vizsgálatával; ez megmondja, hogy új kapcsolathoz tartozik-e vagy nem, melyik irányba halad. Az utóbbi esetben a szükséges módosítás már meghatározásra került a kapcsolathoz. <p>Ha egy új kapcsolat kezdete, akkor megpróbál szabályt keresni a leíróhoz, az alap iptables keresési rendszerrel a `nat' táblában. Ha egy szabály egyezik rá, akkor felhasználja a mindkét irányba szükséges módosítások meghatározásához; a connection-tracking kód jelezheti, hogy várhatóan a másik irányba is módosítani kell. Ezután a fentiek alapján módosításra kerül. <p>Ha nem talál szabályt, akkor egy `null' kötést készít: ez rendszerint nem kezeli a csomagot, de létezik, hogy biztosítsuk, hogy nem lapolunk be egy új kapcsolatot a régi fölé. Néha azonban nem sikerül elkészíteni a null-kötést, mert egy már meglevő kapcsolattal felülírtuk. Ebben az esetben a protokollonkénti módosítás megpróbálhatja újra felvenni, annak ellenére, hogy ez egy null-kötés. <sect2>Standard NAT Target-ek <p>NAT targetek hasonlítanak a hagyományos iptales kiegészítésekre, kivéve, hogy a `nat' táblában kerülnek csak felhasználásra. Az SNAT és DAT targetek mindegyike kap egy `struct ip_nat_multi_range'-t az extra adataihoz; a kötések elkészítéséhez használható címterületet adja meg. Egy tartományelem, `struct ip_nat_range' tartalmaz egy minimum és egy maximum IP címet és egy protokoll-függő maximum és minimum értéket (pl. TCP portok). Szintén található hely a flageknek, amik megmondhatják, hogy legyen az IP cím beírva (néha csak a protokoll-specifikus részre van szükségünk a leíróból), vagy azt, hogy a protokoll-specifikus része a tartománynak értelmezető. <p>Egy több elemből álló tartomány egy tömb ezekből a `struct ip_nat_range' elemekből. Ez azt jelenti, hogy a tartomány lehet: "1.1.1.1-1.1.1.2 ports 50-55 AND 1.1.1.3 port 80". Minden tartományelem hozzáadásra kerül a tartományhoz (egy union, azoknak, akik ezt szeretik). <sect2>Új protokollok <sect3>A Kernelen belül <p>Új protokoll implementálása a leíró(tuple) a változtatható és a nem változtatható részeinek meghatározásával kezdődik. A leíróban mindenre megvan a lehetőséged, hogy egy folyamot egyértelműen azonosíthass. A változtatható része a leírónak az a rész, amivel a NAT-ot elvégezheted: a TCP-hez ez a forrásport, az ICMP-nek az icmp ID; valami, amit a folyam azonosítására használhatsz. A nem módosítható rész a csomag maradéka, ami egyértelműen meghatározza a hálózati folyamot, de nem szeretnél játszani vele (pl. a TCP célport, ICMP típus). <p>Ha egyszer eldöntöttük, meg lehet írni a bővítést a connection-tracking kódhoz a megfelelő könyvtárban, és elkezdheted az `ip_conntrack_protocol' struktúra kitöltését, ami az `ip_conntrack_register_protocol()' híváshoz szükséges. <p>A `struct ip_conntrack_protocol' szerkezete: <descrip> <tag>list</tag> Legyen `{ NULL, NULL }'; a listakezeléshez használjuk. <tag>proto</tag> A protokoll-sorszáma (`/etc/protocols'). <tag>name</tag> A protokoll neve. Ezt a nevet fogja látni a felhasználó; rendszerint az a legjobb, ha megegyezik az `/etc/protocols'-ban találhatóval. <tag>pkt_to_tuple</tag> A funkció, ami kitölti a protokoll-specifikus részét a leírónak az adott csomagra vonatkozóan. A `datah' pointer a fejléc kezdetére mutat, és a datalen a csomag méretét tartalmazza. Ha a csomag nem elég hosszú ahhoz, hogy benne legyen a teljes fejléc, 0-t ad vissza, noha a datalen mindig legalább 8 byte lesz (a keretrendszer garantálja). <tag>invert_tuple</tag> Ez a funkció egyszerűen megváltoztatja a protokoll-függő részét a leírónak a válasz irányból érkező csomagnak megfelelően. <tag>print_tuple</tag> Ez a funkció jeleníti meg a protokoll-függő részét a leírónak; rendszerint a megadott bufferbe kerül beírásra. A használt karakterek száma a visszatérési érték. A /proc-ban az állapotok megjelenítésére használjuk. <tag>print_conntrack</tag> A conntrack struktúra privát részének megjelenítésére használjuk (ha van ilyen), valamint szintén megjelenik a /proc-bejegyzésben. <tag>packet</tag> Akkor kerül meghívásra, ha egy csomag a kapcsolathoz tartozónak tűnik. Mutatókat kapcs a conntrack struktúrára, az IP fejlécre, a méretre és a ctinfo-ra. Egy döntést kell visszaadnod a csomagra (rendszerint NF_ACCEPT), vagy -1-et, ha a csomag nem tartozik a kapcsolathoz. Törölni is tudod a kapcsolatot, de használnod kell a következő beszúrást a versenyhelyzet elkerülésére (ip_conntrack_proto_icmp.c): <tscreen><verb> if (del_timer(&ct->timeout)) ct->timeout.function((unsigned long)ct); </verb></tscreen> <tag>new</tag> Ha a csomag egy új kapcsolat indít; nincs ctinfo paramétere, mert az első csomag ctinfo-ja definíció szerint IP_CT_NEW. 0-t ad vissza, ha nem sikerült elkészítenie a kapcsolatot, vagy timeout volt. </descrip> Miután megírtuk és leteszteltük, hogy remekül tudjuk követni a protokollunk, itt az idő, hogy megtanítsuk a NAT-ot, hogy hogyan kell átfordítania azt. Ez egy új modul írását jelenti; egy kiterjesztést a NAT kódhoz, valamint az `ip_nat_protocol' struktúra feltöltését az `ip_nat_protocol_register()' funkcióhoz. <descrip> <tag>list</tag> `{ NULL, NULL }' <tag>name</tag> A protokoll neve. Ezt a nevet fogja látni a felhasználó; rendszerint az a legjobb, ha megegyezik az `/etc/protocols'-ban találhatóval. (Főleg az userspace-beli autómatikus betöltés miatt.) <tag>protonum</tag> A protokoll-sorszáma (`/etc/protocols'). <tag>manip_pkt</tag> Ez a másik fele a connection tracking pkt_to_tuple funkciójának: gondold azt, hogy egy "tuple_to_pkt". Azért van néhány különbség: kapsz egy mutatót az IP fejléc kezdetére és a teljes csomagméretre. Ez azért van, mert néhány protokollhoz (UDP, TCP) szükség van a fejlécre. Továbbá az ip_nat_tuple_manip mezejét a leírónak (pl. az "src" mező) a teljes leíró helyett, és a módosítás típusát. <tag>in_range</tag> Megmondja, hogy a módosítható része az adott leírónak a megadott tartományon belül van-e. A funkció egy kicsit trükkös: megadjuk a leíróra alkalmazott módosítás típusát, ami megmondja nekünk, hogyan kell értelmezni a tartományt (a forrás vagy a céltartományra céloztunk...). <p>Ezzel a funkcióval ellenőrizhetjük, hogy egy meglevő megfeleltetés helyes tartományba rakott-e minket, valamint azt is hogy szükséges-e a módosítás. <tag>unique_tuple</tag> Ez a funkció a NAT központja(magja): egy leírót és egy tartományt kap, s itt kerül a protokoll-függő része a leírónak a tartományon belülivé, s válik egyedivé. Ha nem találsz nemhasznált leírót a tartományban, akkor 0-t kell visszaadnod. Szintén kapunk egy mutatót a conntrack struktúrára, ami az ip_nat_used_tuple()-hoz szükséges. <p>A szokásos hozzáállás az, hogy egyszerűen folyamatosan léptetjük végig a protokoll-függő részét a leírónak a tartományon, elvégezve az `ip_nat_used_tuple()' ellenőrzést rajta, amíg hamisat ad vissza. <p>A null-megfeleltetés már tesztelve volt: vagy a tartományon kívül van, vagy már foglalt. <p>Ha az IP_NAT_RANGE_PROTO_SPECIFIED nem lett beállítva, az azt jelenti, hogy a felhasználó NAT-ot csinál, s nem NAPT-ot: valami érzékeny dolgot csinál a tartománnyal. Ha nincs szükség megfeleltetésre (például a TCP-n belül a cél-megfeleltetésnek nem kell megváltoztatnia a TCP portot, kivéve, ha utasítják rá), 0-t adjon vissza. <tag>print</tag> Egy karakter-buffert, egy megegyező leírót és egy maszkot vár, s kiírja a protokoll-függő részeket, s visszaadja a felhasznált bufferméretet. <tag>print_range</tag> Egy karakter-buffert és egy tartományt vár, s kiírja a protokoll-függő részét a tartománynak, s visszaadja a felhasznált bufferméretet. Nem kerül meghívásra, ha az IP_NAT_RANGE_PROTO_SPECIFIED jelzőbit nem volt beállítva a tartományhoz. </descrip> <sect2>Új NAT Target-ek <p>Ez egy valóban érdekes rész. Lehetőséged van új NAT targetek írására, amelyek új leképezést valósítanak meg. Két extra targetet alapból biztosít a csomag: MASQUERADE és REDIRECT. Ezek egyszerű minták, s remekül bemutatják az új NAT targetek életképességét és erejét. <p>Ezek a többi iptables atrgethez hasonlóan vannak megírva, de belül megszakítják a kapcsolatot és meghívják az `ip_nat_setup_info()'-t. <sect2>Protokoll segítők (Helperek) <p>A kapcsolatkövetés protokoll helperei lehetővé teszik a követő kódnak a több kapcsolatot tartalmazó protokollok megértését (pl. FTP), és megjelölik a leszármazott kapcsolatokat, a szülő alá rendelik azokat, rendszerint az adatkapcsolatból kiolvasott cím alapján. <p>A NAT protokoll helperei két dolgot végeznek: lehetővé teszik, hogy a NAT kód módosítsa az adatfolyamot a benne található címek átírásával, valamint elvégezhesse a NAT-ot a kapcsolódó folyamon amikor az beérkezik (az eredeti kapcsolat alapján). <sect2>Connection Tracking Helper Modules <sect3>Leírás <p> A connection tracking module kötelessége, hogy meghatározza, melyik csomagok tartoznak egy már felépült kapcsolathoz. A modul a következőket teszi: <itemize> <item>Jelzi a netfilternek, melyik csomagok fontosak a modulunknak (a legtöbb helper egy megadott porton üzemel). <item>Bejegyzi a funkcióját a netfilterbe. A funkció meghívásra kerül minden egyes csomagra, ami illeszkedik a feltételre. <item>Egy `ip_conntrack_expect_related()' függvényt meghívhat, hogy jelezze a netfilternek, hogy egy kapcsolatra vár. </itemize> <sect3>Elérhető struktúrák és függvények <p>A kernel modulod init funkciójának meg kell hívnia a `ip_conntrack_helper_register()' függvényt egy pointerrel a `struct ip_conntrack_helper'-ra. A struktúra a következő mezőkkel rendelkezik: <descrip> <tag>list</tag>A láncolt lista feje. A netfiletr belsőleg kezeli. Alapbeállításban legyen `{ NULL, NULL }'. <tag>tuple</tag>Egy `struct ip_conntrack_tuple', ami megadja, hogy milyen csomagok érdeklik a helperünket. <tag>mask</tag>Mégegy `struct ip_conntrack_tuple'. A mask megadja, hogy a <tt>tuple</tt>-nek melyik bitjei valósak. <tag>help</tag>A függvény, amit a netfilter meghívhat minden csomagra, ami illeszkedik a tuple+mask-ra. </descrip> <sect3>Egy minta conntrack helper modul <p> <tscreen><code> #define FOO_PORT 111 static int foo_help(const struct iphdr *iph, size_t len, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { /* analyze the data passed on this connection and decide how related packets will look like */ if (there_will_be_new_packets_related_to_this_connection) { t = new_tuple_specifying_related_packets; ip_conntrack_expect_related(ct, &t); /* save information important for NAT in ct->help.ct_foo_info; */ } return NF_ACCEPT; } static struct ip_conntrack_helper foo; static int __init init(void) { memset(&foo, 0, sizeof(struct ip_conntrack_helper); /* we are interested in all TCP packets with destport 111 */ foo.tuple.dst.protonum = IPPROTO_TCP; foo.tuple.dst.u.tcp.port = htons(FOO_PORT); foo.mask.dst.protonum = 0xFFFF; foo.mask.dst.u.tcp.port = 0xFFFF; foo.help = foo_help; return ip_conntrack_helper_register(&foo); } static void __exit fini(void) { ip_conntrack_helper_unregister(&foo); } </code></tscreen> <sect2>NAT helper modulok <sect3>Leírás <p> A NAT-helper modulok alkalmazásfüggő NAT-kezelést tesznek lehetővé. Rendszerint az adatok röptében történő elemzését jelentik: gondolj csak a PORT parancsra az FTP-ben, ahol a kliens megmondja a szervernek, hogy melyik IP/port-párhoz kell kapcsolódnia. Így az FTP-helper modulnak ki kell cserélnie az IP/port-ot a PORT parancs után az FTP parancs-csatornában. <p> Ha elbántunk a TCP-vel, akkor a dolgok kissé összetettebbé válnak. Az ok a lehetséges csomagméret-változás (FTP példa: a PORT utáni IP/port-párt reprezentáló string hossza megváltozik). Ha megváltoztatjuk a csomagméretet, akkor egy syn/ack eltéréshez jutunk a NAT két oldala között. (Ez azt jelenti, hogy ha kiegészítettük a csomagot 4 oktettel, akkor ezután minden csomag TCP sorszámához hozzá kell adnunk). <p> A kapcsolódó csomagok speciális kezelésére is szükség van. Az FTP példához visszatérve: az adatkapcsolat minden egyes csomagját NAT-olni kell a kliens által a parancscsatornán belül a PORT parancsban megadott IP/port-párra, a sima táblázatos keresés helyett. <itemize> <item>callback a csomagra, ami az adott csatornát elindította (foo_help) <item>callback az összes kapcsolódó csomagra (foo_nat_expected) </itemize> <sect3>Elérhető struktúrák és függvények <p>A nat helper modulod `init()' funkciójának meg kell hívnia a `ip_nat_helper_register()' függvényt egy pointerrel a `struct ip_nat_helper'-ra. A struktúra a következő mezőkkel rendelkezik: <descrip> <tag>list</tag>A láncolt lista feje. A netfiletr belsőleg kezeli. Alapbeállításban legyen `{ NULL, NULL }'. <tag>tuple</tag>Egy `struct ip_conntrack_tuple', ami megadja, hogy milyen csomagok érdeklik a NAT helperünket. <tag>mask</tag>Mégegy `struct ip_conntrack_tuple'. A mask megadja, hogy a <tt>tuple</tt>-nek melyik bitjei valósak. <tag>help</tag>A függvény, amit a netfilter meghívhat minden csomagra, ami illeszkedik a tuple+mask-ra. <tag>name</tag>Egy egyedi név, ami a modulunkat azonosítja. </descrip> Ez pontosan megegyezik a connection tracking helper modul írásával. Jelezni tudod, hogy a modulod képes minden várható kapcsolat NAT-olását elvégezni (valószínűleg egy connection tracking modullal került beillesztésre). Ezt a `ip_nat_expect_register()' függvénnyel teheted meg, ami egy `struct ip_nat_expect' struktúrát vár. A struktúra a következő mezőkkel rendelkezik: <descrip> <tag>list</tag>A láncolt lista feje. A netfiletr belsőleg kezeli. Alapbeállításban legyen `{ NULL, NULL }'. <tag>expect</tag>a funkció, ami elvégzi a NAT-olást a várt csomagokra. TRUE-val jelzi, hogy lekezelte a csomagot, különben a következő bejegyzett funkció kerül meghívásra. Ha TRUE-t ad vissza, akkor ki kell töltenie a döntés mezőt! </descrip> <sect3>Egy minta NAT helper modul <p> <tscreen><code> #define FOO_PORT 111 static int foo_nat_expected(struct sk_buff **pksb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info, struct ip_conntrack *master, struct ip_nat_info *masterinfo, unsigned int *verdict) /* called whenever a related packet (as specified in the connection tracking module) arrives params: pksb packet buffer hooknum HOOK the call comes from (POST_ROUTING, PRE_ROUTING) ct information about this (the related) connection info &ct->nat.info master information about the master connection masterinfo &master->nat.info verdict what to do with the packet if we return 1. { /* Check that this was from foo_expect, not ftp_expect, etc */ /* Then just change ip/port of the packet to the masqueraded values (read from master->tuplehash), to map it the same way, call ip_nat_setup_info, set *verdict, return 1. */ } static int foo_help(struct ip_conntrack *ct, struct ip_nat_info *info, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pksb) /* called for the packet causing related packets params: ct information about tracked connection info (STATE: related, new, established, ... ) hooknum HOOK the call comes from (POST_ROUTING, PRE_ROUTING) pksb packet buffer */ { /* extract information about future related packets (you can share information with the connection tracking's foo_help). Exchange address/port with masqueraded values, insert tuple about related packets */ } static struct ip_nat_expect foo_expect = { { NULL, NULL }, foo_nat_expected }; static struct ip_nat_helper hlpr; static int __init(void) { int ret; if ((ret = ip_nat_expect_register(&foo_expect)) == 0) { memset(&hlpr, 0, sizeof(struct ip_nat_helper)); hlpr.list = { NULL, NULL }; hlpr.tuple.dst.protonum = IPPROTO_TCP; hlpr.tuple.dst.u.tcp.port = htons(FOO_PORT); hlpr.mask.dst.protonum = 0xFFFF; hlpr.mask.dst.u.tcp.port = 0xFFFF; hlpr.help = foo_help; ret = ip_nat_helper_register(hlpr); if (ret != 0) ip_nat_expect_unregister(&foo_expect); } return ret; } static void __exit(void) { ip_nat_expect_unregister(&foo_expect); ip_nat_helper_unregister(&hlpr); } </code></tscreen> <sect1>Értsük meg a Netfilter-t! <p>Netfilter nagyon egyszerű, és elég pontosan le van írva az előző fejezetekben. Néha azonban szükséges a NAT és az ip_tables által nyújtott szolgáltatások alá menni, vagy esetleg teljesen ki is cserélhetőek. <p>Egy fontos kitétel a netfilterhez (nos, a jövőben) az elrejtés. Minden skb-nek van egy `nfcache' mezője: egy bitmask, ami megmutatja, hogy milyen mezőket kell a fejlécből megvizsgálni, valamint, hogy megváltozott-e a csomag, vagy nem. A terv az, hogy minden netfilter hook VAGY-al állítja be a fontos bitjeit, így lehetővé válik a későbbiekben egy olyan rendszer kialakítása, ami okos annyira, hogy el tudja dönteni, hogy a csomagot el kell-e küldeni a netfilterben, vagy teljesen kihagyható. <p>A legfontosabb bitek az NFC_ALTERED, ami azt jelenti, hogy a csomag megváltozott-e (ez már használva van az IPv4-es NF_IP_LOCAL_OUT-ban a megváltoztatott csomagok routolására), és NFC_UNKNOWN, ami azt mutatja, hogy a caching-et nem lehet elvégezni, mert néhány olyan jellemző került megvizsgálásra, amit nem lehet kifejezni. Ha kétségeid vannak, akkor egyszerűen állítsd be a NFC_UNKNOWN flaget az skb nfcache mezőjében, a hook-odon belül. <sect1>Új netfilter modulok írása <sect2> Csatlakoztatás a Netfilter hook-okra <p> Kernelen belül a csomagok fogadásához egyszerűen tudsz írni egy modult, ami bejegyez egy "netfilter hook"-ot. Ez alapjában az érdeklődés kifejezésének a módja; az aktuális pontok protokoll-specifikusak lehetnek, és külön headerekben találhatók, mint pl. a "netfilter_ipv4.h". <p> Netfilter hook bejegyzéséhez és eltávolításához használd az `nf_register_hook' és `nf_unregister_hook' függvényeket. Ezek mindegyike vár egy pointert a `struct nf_hook_ops'-ra, ami a következőképpen épül fel: <descrip> <tag>list</tag> `{ NULL, NULL }', láncolt listába illesztéshez használt. <tag>hook</tag> A funkció akkor kerül meghívásra, ha a csomag eléri a hook-ot. A lehetséges visszatérési értékek: NF_ACCEPT, NF_DROP vagy NF_QUEUE. NF_ACCEPT: a következő, erre a pontra csatlakozó hook kerül meghívásra. NF_DROP: a csomag eldobásra kerül. NF_QUEUE: sorbaállításra kerül. Egy mutatót kapsz egy skb pointerre, szóval teljesen le tudod cserélni az skb-t, ha akarod. <tag>flush</tag> Aktuálisan nem hsznált: a cache ürítésekor a találatok kezeléséhez készült. Talán sose lesz implementálva: állítsd NULL-ra! <tag>pf</tag> A protokollcsalád, pl. `PF_INET' IPv4-hez. <tag>hooknum</tag> A hook száma, amiben érdekelt vagy. Pl: `NF_IP_LOCAL_OUT'. </descrip> <sect2> Queued csomagok feldolgozása <p>Ezt a felületet az ip_queue használja; be tudod jegyezni, hogy egy adott protokollhoz tartozó csomagokat lekezelje. Hasonló a felépítése, mintha egy hook-hoz regisztrálnád magad, kivéve, hogy lehetőséged van a scomag feldolgozásának megállítására, valamint csak azokat a csomagokat látod, amelyekre a hook `NF_QUEUE'-t válaszolt. <p>A két, a regisztrációhoz használható függvény: `nf_register_queue_handler()' és `nf_unregister_queue_handler()'. A beregisztrált függvény `void *' pointerrel kerül lekezelésre. <p>Ha senki sem jelentkezett az adott protokoll lekezelésére, akkor az NF_QUEUE megegyezik az NF_DROP visszatérési értékkel. <p>Amennyiben bejegyezted az érdeklődésed a sorbaállított csomagokra, elkezdődik a sorbaállítás. Bármit megtehetsz velük, csak meg kell hívnod az `nf_reinject()'-et miután befejezted a módosítást (ne csak egyszerűen kfree_skb()-d őket). Amikor visszaküldesz egy skb-t, te kezeled az skb-t, a `struct nf_info'-t, ami a kezelődet jelenti, valamint a döntést: NF_DROP eredményezi a csomag eldobását, NF_ACCEPT jelenti a hookokban való továbbküldést, NF_QUEUE jelenti az ismételt sorbaállítást, NF_REPEAT hatására pedig ismét bekerül a hook-ba (figyelt a végtelen ciklusokra!). <p>A `struct nf_info'-ban információt kapsz a csomagról, mint pl. a hozzá tartozó intarface-ek, a hook, amin fennakadt, stb. <sect2> Parancsok értelmezése az userspace-ből <p>Gyakori a netfilter részekben, hogy kommunikálni szeretnének az userspace-el. Az erre használható módszer a setsockopt mechanizmus. ehhez azonban módosítani kell minden protokollt, hogy meghívja a nf_setsockopt()-ot azokra a setsockopt számokra, amiket nem ismer (valamint a nf_getsockopt()-ot a getsockopt-hoz), s nemcsak az IPv4, IPv6 és DECnet protokollokban tegye meg ezt. <p>Egy nem szokványos módszerként bejegyzünk egy `struct nf_sockopt_ops'-ot az nf_register_sockopt() hívással. A struktúra mezői: <descrip> <tag>list</tag> A listába való beillesztéshez használt: `{ NULL, NULL }'. <tag>pf</tag> A kezelt protokollcsalád, pl: PF_INET. <tag>set_optmin</tag> és <tag>set_optmax</tag> Ezek megadják a (kizárólagos) tartományát a kezelt setsockopt számoknak. Ezentúl 0-val jelezheted, hogy nincs setsockopt opciód. <tag>set</tag> Ez a funkció kerül meghívásra, ha a felhasználó meghívja az egyik setsockopt opciódat. Célszerű ellenőrizni, hogy megvan-e a NET_ADMIN capability-e. <tag>get_optmin</tag> és <tag>get_optmax</tag> Ezek megadják a (kizárólagos) tartományát a kezelt getsockopt számoknak. Ezentúl 0-val jelezheted, hogy nincs getsockopt opciód. <tag>set</tag> Ez a funkció kerül meghívásra, ha a felhasználó meghívja az egyik getsockopt opciódat. Célszerű ellenőrizni, hogy megvan-e a NET_ADMIN capability-e. </descrip> <p>Az utolsó két mezőt belsőleg használjuk. <sect1>Csomagkezelés az userspace-ben <p>A libipq könyvtár és a `ip_queue' modul segítségével majdnem mindent megtehetsz az userspace-ben, amit a kernelen belül. Ez azt jelenti - kisebb sebességcsökkenéssel - a programodat teljes egészében fejlesztheted az userspace-ben. Amíg nem akarsz nagy sávszélességet szűrni, használd inkább ezt a lehetőséget a kernelen belüli csomagkezeléssel szemben. <p>A netfilter nagyon korai szakaszában kipróbáltam az iptables egy nagyon korai verziójának userspace-be való portolásával. Netfilter kinyitja a kaput, hogy bárki tetszőleges, egészen hatékony modulokat írjon azon a nyelven, amelyiken csak szeretne. <sect>2.0 és 2.2 csomagszűrő moduljainak átfordítása <p>Nézd meg az ip_fw_compat.c file-t, ahol egy egyszerű layert találhatsz, ami jelentősen leegyszerűsítheti a portolás folyamatát. <sect>Netfilter Hookok a Tunnel-fejlesztőknek <p>A Tunnel (vagy encapsulation) meghajtók íróinak két egyszerű szabályt kell követniük a 2.4-es kernelnél (a kernelben megtalálható meghajtók írásaokr, mit pl. a net/ipv4/ipip.c): <itemize> <item> El kell engedni az skb->nfct -ot, ha a csomagot felismerhetetlenné (pl. ki/becsomagolás) teszed. Ezt nem kell megtenned, ha egy *új* skb struktúrába csomagolod át, de amennyiben ezt helyben végzet, feltétlenül meg kell tenned! <p>Másképpen: a NAT kód a régi kapcsolatkövetési információt fogja használni a csomag megváltoztatásához, aminek nem a várt működés lesz a következménye! <item>Győződj meg arról, hogy a becsomagolt csomagok áthaladnak a LOCAL_OUT hookon, a kicsomagolt csomagok pedig a PRE_ROUTING hook-on (a legtöbb tunnel az ip_rcv() függvényt használja, ami megcsinálja a beállításokat)! <p>Másképpen: a felhaszáló elvárásával ellentétben nem fogja tudni szűrni a tunneleket! </itemize> <p>Az általános eljárás az első problémára a következőhöz hasonló kód beillesztése mielőtt be- vagy kicsomagolnád a csomagot: <tscreen><verb> /* Tell the netfilter framework that this packet is not the same as the one before! */ #ifdef CONFIG_NETFILTER nf_conntrack_put(skb->nfct); skb->nfct = NULL; #ifdef CONFIG_NETFILTER_DEBUG skb->nf_debug = 0; #endif #endif </verb></tscreen> <p>Általában a második pont teljesülése érdekében meg kell találnod azt a pontot, ahol az újonnan előállított csomag belép az "ip_send()"-be, és a következővel lecserélni: <tscreen><verb> /* Send "new" packet from local host */ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, ip_send); </verb></tscreen> <p> ezeket a szabályokat betartva az lesz az eredmény, hogy az a szamély, aki csomagszűrési szabályokat szeretne felvenni a tunnel-állomáson, a következőhöz hasonló csomag-útvonalat fog látni a tunnelezett csomag számára: <enum> <item> FORWARD hook: hagyományos csomag (eth0 -> tunl0) <item> LOCAL_OUT hook: becsomagolt csomag (->eth1). </enum> És a válasz csomag számára: <enum> <item> LOCAL_IN hook: becsomagolt válasz csomag (eth1->) <item> FORWARD hook: becsomagolt csomag (eth1 -> eth0). </enum> <sect>A Test Suite <p>A CVS fán belül lakik a tesztrendszer: amit nyújtani tud, az nagyobb biztonság arra vonatkozóan, hogy a változtatásaid nem rontottak el semmit. Triviális tesztek legalább annyira fotosak, mint a trükkösek: az egyszerű tesztek leegyszerűsítik az összetetteket (legalábbis biztos lehetsz benne, hogy az egyszerűbb dolgok működnek, mielőtt belekezdesz az összetettekbe). <p>A tesztek egyszerűek: csak shell-scriptek a testsuite/ könyvtárban, amiknek sikeresen le kell futniuk. A scriptek ABC-sorrendben futnak, így a `01test' a `02test' előtt hajtódik végre. Jelenleg 5 könyvtár van: <descrip> <tag>00netfilter/</tag> Általános Netfilter tesztek <tag>01iptables/</tag> iptables tesztek <tag>02conntrack/</tag> connection tracking tesztek <tag>03NAT/</tag> NAT tesztek <tag>04ipchains-compat/</tag> ipchains/ipfwadm kompatibilitási tesztek </descrip> A testsuite/ könyvtárban található a `test.sh'. Ez elkészít két dummy interface-t (tap0 és tap1), felkapcsolja a forwardingot, s kitölt minden netfilter modult. Ezután végighalad a fenti könyvtárakon, s elindítja a test.sh scriptjeiket, egészen addig, míg az egyik hibát nem jelez. Két argumentuma van: `-v'-re kiírja az összes tesztet végrehajtáskor, míg egy teszt nevének megadásával addig kihagy minden tesztet, míg meg nem találja a megadottat. <sect1>Tesztek írása <p>Készíts egy új file-t a megfelelő könyvtárban: próbáld meg beszámozni a tesztedet, hogy a helyes időben fusson le. Pl: az ICMP válasz teszteléséhez (02conntrack/02reply.sh) meg kell győződnünk a kérés helyes kezeléséről (02conntrack/01simple.sh). <p>Jobb sok kis tesztet készíteni, ahol mindegyik lefed egy adott területet, mert segíti a probléma helyének pontos meghatározását. <p>Ha hibát észlelsz, egyszerűen lépj ki `exit 1'-el, ami hibajelzést generál; ha valami, amit vizsgálsz hibát kell hogy jelezzen, akkor célszerű egy egyedi üzenet megjelenítése. A teszted `exit 0'-val lépjen ki, ha minden sikeres volt. <bf>Minden</bf> parancs sikerességét ellenőrizni kell, akár a `set -e' használatával, akár egy `|| exit 1' kiegészítéssel minden parancs után. <p>A segédfunkciók (`load_module' és `remove_module') használhatók a modulok kezelésére: sose támaszkodj az automatikus betöltésre, kivéve, ha valójában azt szeretnéd tesztelni. <sect1>Változók és a környezet <p>Két játék interface-ed van: tap0 és tap1. A címük a <tt>$TAP0</tt> és <tt>$TAP1</tt> válozóban található. Mindkettő netmaskja 255.255.255.0; a hálózati címük a $TAP0NET-ben és a $TAP1NET-ben található. <p>Van egy üres ideiglenes file-od a $TMPFILE-ban. Ez törlésre kerül a teszted végén. <p>A tesztjeid a testduite/ könyvtárból fognak futni. Ezentúl ha programokat szeretnél használni, akkor azokat a `../userspace' path-stringgel használd. <p>A scipted bővebb információt is megjeleníthet, ha a $VERBOSE be van állítva (a felhasználó megadta a `-v' kapcsolót). <sect1>Hasznos eszközök <p>Számos hasznos segédprogramot találsz a "tools" könyvtárban: mindegyik nem-nulla visszatérési értékkel jelzi, ha valamilyen problémája akadt. <sect2>gen_ip <p>IP csomagokat tudsz a segítségével előállítani, amelyek a kimeneten fognak megjelenni. A csomagokat bele tudod küldeni a tap0 és tap1 eszközökbe egyszerű kimenet-átirányítással a /dev/tap0 ill. /dev/tap1-re (ezeket a testsuite első futáskor létrehozza, ha nem léteztek korábban). <p>gen_ip egy nagyon egyszerű program, ami igen kényes a paraméter-sorrendjére. Először az általános opciók jönnek: <descrip> <tag>FRAG=offset,length</tag> Elkészíti a csomagot, és darabokra tördeli a megadott hosszal és eltolással. <tag>MF</tag> Beállítja a `More Fragments' bitet a csomagban. <tag>MAC=xx:xx:xx:xx:xx:xx</tag> Beállítja a forrás MAC címet a csomagban. <tag>TOS=tos</tag> Beállítja a TOS mezőt (0-255). </descrip> Ezután jönnek a kötelező paraméterek: <descrip> <tag>source ip</tag> Forrás IP cím. <tag>dest ip</tag> Cél IP cím. <tag>length</tag> Teljes hossz, a fejlécet beleszámolva. <tag>protocol</tag> A protokoll sorszáma, pl. 17 = UDP. </descrip> Az ezután következő paraméterek protokoll-függőek: az UDP(17)-hez a forrás és a cél portszám; az ICMP(1)-hez a típus és a kód: ha a típus 0 vagy 8 (ping-válasz, vagy ping), akkor két további argumentumot vár (az ID és a sequence mezők). A TCP-hez a forrás és célport, és a flagek ("SYN", "SYN/ACK", "ACK", "RST" vagy "FIN"). Itt lehetőség van három további argumentumra is: "OPT=" - vesszővel elválasztott opció-felsorolás, "SYN=" egy sequence number-rel, valamint "ACK=" szintén egy sequence number-rel. Végül egy opcionális argumentum, a "DATA" jelzi a csomag tartalmát, amit a standard inputról tölt fel. <sect2>rcv_ip <p>Az IP csomagokat tudod vele ellenőrizni. Amennyire csak lehetséges, megpróbálja visszaállítani a gen_ip parancssorát (a fragmentek a kivételek). <p>Nagyon hasznos a csomagok feldolgozásában. Két kötelező argumentuma van: <descrip> <tag>wait time</tag> A maximális idő másodpercben, amíg a csomag megérkezésére vár a tandard input-ján. <tag>iterations</tag> Az értelmezendő csomagok száma. </descrip> Egy opcionális argumentuma van: a "DATA". Ennek hatására kinyomtatja a csomag tartalmát a kimenetére a fejléc után. <p>Egy általános felhasználási mód: <verb> # Set up job control, so we can use & in shell scripts. set -m # Wait two seconds for one packet from tap0 ../tools/rcv_ip 2 1 < /dev/tap0 > $TMPFILE & # Make sure that rcv_ip has started running. sleep 1 # Send a ping packet ../tools/gen_ip $TAP1NET.2 $TAP0NET.2 100 1 8 0 55 57 > /dev/tap1 || exit 1 # Wait for rcv_ip, if wait %../tools/rcv_ip; then : else echo rcv_ip failed: cat $TMPFILE exit 1 fi </verb> <sect2>gen_err <p>Egy csomagot vár (pl. a gen_ip-tól) a bemenetén, s ICMP error üzenetre fordítja. <p>Három argumentuma van: a forrás IP cím, típus és kód. A cél IP cím a csomag forráscíme lesz. <sect2>local_ip <p>A bemenetén érkező csomagot elküldi a rendszerbe egy raw-socketen keresztül. Ez biztosítja a helyileg generált csomagot a tesztekhez (elválasztva a hálózati meghajtókba küldött csomagoktól, amik távolról érkezőnek látszanak). <sect1>Tanácsok <p>Minden tool feltételezi, hogy mindet meg tud csinálni egy írással vagy olvasással: ez igaz az ethertap device-okra, de nem biztos, hogy igaz ha valami trükköset csinálsz a pipe-okkal. <p>dd-t lehet használni a csomagok darabolására: dd-nek van egy obs (kimeneti blokkméret) opciója, ami lehetővé teszi a csomagok egy íráson keresztül való megjelenítését. <p>Először a sikerességre tesztelj: pl. teszteld azt, hogy a csomag sikeresen lockolásra került. Első teszt legyen az, hogy a csomag normálisan áthaladt, s <bf>utána</bf> teszteld csak a blokkolást. Különben egy nem kívánt hiba megállíthatja a csomagokat... <p>Próbálj meg precíz teszteket írni, s ne véletlen dolgokkal tesztelj, s figyeld, mi lesz belőle. Ha egy pontos teszt hibát jelez, akkor a hiba helye használható információt adhat. Viszont ha egy véletlen teszt hibázik, akkor nem lehet vele mit kezdeni. <p>Ha egy teszt üzenet nélkül jelez hibát, használhatod a `-x' opciót a legelső sorban (pl: `#! /bin/sh -x'), hogy lásd, hogy melyik programok futnak. <p>Ha a teszt véletlenszerűen áll meg, ellenőrizd a külső behatásokat (próbáld meg leállítani a külső interface-eidet). Például Andrew Tridgell-el közös hálózaton ülve felfigyeltem, hogy Windows broadcast-el gyötörték a gépemet. <sect>Motiváció <p>Ahogy az ipchains-t fejlesztettem, rájöttem, hogy a csomagszűrést rossz helyen valósítottuk meg. Most nem találom, de írtam egy levelet Alan Cox-nak, aki csak annyit mondott, hogy `miért nem fejezed be először amit csinálsz, annak ellenére, hogy talán igazad van'. Röviden a gyakorlatiasság győzött a helyes út felett. <p>Amiután befejeztem az ipchainst - ami egy kis módosításnak indult az ipfwadm kernel részein - s egy nagyobb újraírás felé fordultam, megírtam a HOWTO-t, kifinomult képet kaptam arról, hogy a Linux társadalomnak milyen problémái vannak a csomagszűréssel, maszkolással, port-átirányítással és hasonlókkal. <p>Ez az öröme a saját támogatás nyújtásának: közelről érezheted, hogy a felhasználók mit szeretnének megvalósítani, mivel szenvednek. A szabad program a legnagyobb jutalom a legtöbb felhasználó kezében, s ez azt jelenti, hogy egyszerűnek kell lennie. Az architektúra, s nem a dokumentáció volt a legnagyobb hiba. <p>Nos, megvolt a tapasztalat az ipchains kódjából, s az ötlet az emberek próbálkozásaiból. Csak két problémám volt: <p>Nem szeretnék visszatérni a biztonsági rendszerekbe. Biztonsági konzulensnek lenni egy állandó huzavona a lelkiismereted és a pénztárcád között. Alapszinten: te a biztonság érzését árulod, hadilábon áll az aktuális biztonsággal. Talán katonai területen, ahol értik a biztonságot, más lennék. <p>A második problémám az, hogy nemcsak a kezdő felhasználó okozhat gondot, hanem növekvő számú vállalat és ISP használja ezt a terméket. Ezektől a felhasználóktól megbízható információkra van szükség, ha a jövő otthoni felhasználóira tervezek. <p>Ezek a problémák megoldódtak amikor belefutottam David Bonn-ba, a WatchGuard-tól az Usenix-en 1998 júliusában. Egy Linux kernel programozót kerestek - a végén megegyeztünk, hogy egy hónapra elmegyek a seattle-i irodájukba, hogy hátha sikerül összehozni egy megállapodást, miszerint ők támogatják az új kódot cserébe a támogatásomért. A megállapodás többről szólt, mint szerettem volna. Nem szeretnék külső konzulensként szerepelni - legalábbis mostanában. <p>A WatchGuard-hoz való kötődés lehetővé tette egy nagy ügyfélkör elérését, s mindemellett független maradhattam tőlük, ami lehetővé tette, hogy mindenkit egyformán támogassak. <p>Egyszerűen megírtam a netfiltert, az ipchains-t portoltam a tetjére, s elkészültem vele. Sajnos ez meghagyta a masquerading kódot a kernelben: de a masquerading kód függetlenítése a csomagszűréstől egyike a legnagyobb nyereségeknek, de az megmaradt, hogy a masquerading-ot ki kell emelni a kernelből, s át kell helyezni a netfilter rendszerbe. <p>Az ipfwadm `interface-address' szolgáltatásával (amit kivettem az ipchains-ből) kapcsolatos tapasztalataim megmutatták, hogy nem lesz egyszerű munka a masquerading kód kivágása a kernelből, s nem lehet másra bízni a netfilter-be való portolását. <p>A meglevő kódból a lehető legkevesebbet kellett megtartanom, s inkább új lehetőségekkel kibővíteni azt, felbátorítandó a felhasználókat, hogy használják azokat. Ez a transzparens proxy, masquerading és port forwarding lecserélését jelentette. Más szavakkal: egy teljes NAT layer-t. <p>Bár elhatároztam, hogy portolom a meglevő masquerading réteget a teljesen új NAT rendszer implementálása helyett, de a kódon meglátszott a kora, valamint a karbantartás hiánya. Nem volt felelőse a karbantartásának. Úgy látszik, hogy a komoly felhasználó nem használta a modult, s az otthoni felhasználók közül pedig senki sem vette a fáradságot a követésre. Bátor emberek, mint pl. Juan Ciarlante készítettek javítgatásokat hozzá, de elérte azt az állapotot, hogy szükségessé vált az újraírása. <p>Fel szeretném hívni a figyelmedet, hogy nem én voltam az alkalmas ember a NAT újraírására: soha többé nem használtam a masquerading-et, s nem tanulmányoztam a meglevő kódot. Talán ezért tartott tovább, mint szerettem volna. De az eredmény egészen jó - legalábbis a saját véleményem szerint - s biztos vagyok benne, hogy sokat tanultam belőle. Nincs kétségem afelől, hogy a második verzió sokkal jobb lesz, miután megláttuk, hogy miként használják az emberek. <sect>Köszönetnyilvánítás <p>Köszönet azoknak, akik segítettek, különösen Harald Welte-nek a Protokollsegítők (Protocol Helpers) fejezetért. </article>