2012-03-21

Egymásba ágyazott ismétlődések kezelése TShark field print opciókkal

Tegyük fel, hogy szkriptet kell írnunk, ami valamiféle statisztikát készít mondjuk IPIP vagy GRE tunnelezett forgalomról. Mintának egyből használhatjuk a packetlife.net-ről a GRE.cap-ot. A tunnel ebben az esetben a 10.0.0.1 és a 10.0.0.2 közt van kihúzva, a tunnelen belül pedig az 1.1.1.1 és a 2.2.2.2 kommunikál egymással:


Ha mindezt CLI-ben szeretnénk feldolgozni, akkor persze TSharkra lesz szükség, és egy kicsit állítgatni kell az alapértelmezett outputon. Egyszerűsítsük le a végletekig a dolgot, csak a forrás, illetve a cél IP-ket jelenítsük meg:
karsair@betazed:~$ tshark -r GRE.cap -T fields -e ip.src -e ip.dst -E separator=\;
1.1.1.1;2.2.2.2
2.2.2.2;1.1.1.1
1.1.1.1;2.2.2.2
2.2.2.2;1.1.1.1
1.1.1.1;2.2.2.2
2.2.2.2;1.1.1.1
1.1.1.1;2.2.2.2
2.2.2.2;1.1.1.1
1.1.1.1;2.2.2.2
2.2.2.2;1.1.1.1
Nos, ez nem egészen az, amire szükség van, hiszen itt csak a GRE payload IP-k látszanak, azaz az ip.src és az ip.dst mezők utolsó előfordulásai minden egyes keretben. Ettől többet sajnos nem is várhatunk egészen a Wireshark 1.6-os verziójáig. Az új stabil Wireshark ágban azonban már gondoltak a rekurzivitás barátaira, új opciók vannak ugyanis a hasonló esetek kezelésére. Egyrészt megváltozott az alapértelmezett viselkedés a TShark-ban, -e után megadott mező összes előfordulását kilistázza a program vesszővel elválasztva:
karsair@betazed:~$ /opt/wireshark-1.6.5/bin/tshark -r GRE.cap -T fields -e ip.src \
-e ip.dst -E separator=\;
10.0.0.1,1.1.1.1;10.0.0.2,2.2.2.2
10.0.0.2,2.2.2.2;10.0.0.1,1.1.1.1
10.0.0.1,1.1.1.1;10.0.0.2,2.2.2.2
10.0.0.2,2.2.2.2;10.0.0.1,1.1.1.1
10.0.0.1,1.1.1.1;10.0.0.2,2.2.2.2
10.0.0.2,2.2.2.2;10.0.0.1,1.1.1.1
10.0.0.1,1.1.1.1;10.0.0.2,2.2.2.2
10.0.0.2,2.2.2.2;10.0.0.1,1.1.1.1
10.0.0.1,1.1.1.1;10.0.0.2,2.2.2.2
10.0.0.2,2.2.2.2;10.0.0.1,1.1.1.1
Másrészt vadonatúj field print opcióként itt az "occurrence". Az eddigi field print opciókkal be lehetett állítani, hogy legyen-e az outputban fejléc (-E header=y), a fenti példákban már bemutatott separator opcióval megadhattunk mezőelválasztó karaktert (-E separator=\;), illetve kérhettünk idézőjeleket a mező tartalma köré (pl. -E quote=d). Most viszont azt is megadhatjuk az occurrence opcióval, hogy az adott nevű mező első (f), utolsó (l) vagy összes (a) előfordulását szeretnénk látni, így akár imitálhatjuk az 1.6-os verziók előtti viselkedést (-E occurrence=l):
karsair@betazed:~$ /opt/wireshark-1.6.5/bin/tshark -r GRE.cap -T fields -e ip.src \
-e ip.dst -E separator=\; -E header=y -E quote=d -E occurrence=l
ip.src;ip.dst
"1.1.1.1";"2.2.2.2"
"2.2.2.2";"1.1.1.1"
"1.1.1.1";"2.2.2.2"
"2.2.2.2";"1.1.1.1"
"1.1.1.1";"2.2.2.2"
"2.2.2.2";"1.1.1.1"
"1.1.1.1";"2.2.2.2"
"2.2.2.2";"1.1.1.1"
"1.1.1.1";"2.2.2.2"
"2.2.2.2";"1.1.1.1"
Ami még ide tartozik, mégis kimaradt az eddigi példákból, az az aggregator field print opció, ami az -e után megadott nevű mező(k)nek az adott keretben való előfordulásait elválasztó karaktert definiálja, az alábbi példában ez a kettőspont:
karsair@betazed:~$ /opt/wireshark-1.6.5/bin/tshark -r GRE.cap -T fields -e ip.src \
-e ip.dst -E separator=\; -E quote=d -E occurrence=a -E aggregator=:
"10.0.0.1:1.1.1.1";"10.0.0.2:2.2.2.2"
"10.0.0.2:2.2.2.2";"10.0.0.1:1.1.1.1"
"10.0.0.1:1.1.1.1";"10.0.0.2:2.2.2.2"
"10.0.0.2:2.2.2.2";"10.0.0.1:1.1.1.1"
"10.0.0.1:1.1.1.1";"10.0.0.2:2.2.2.2"
"10.0.0.2:2.2.2.2";"10.0.0.1:1.1.1.1"
"10.0.0.1:1.1.1.1";"10.0.0.2:2.2.2.2"
"10.0.0.2:2.2.2.2";"10.0.0.1:1.1.1.1"
"10.0.0.1:1.1.1.1";"10.0.0.2:2.2.2.2"
"10.0.0.2:2.2.2.2";"10.0.0.1:1.1.1.1"
Eljutottunk tehát odáig, hogy a TShark egy jól konfigurálható kimentet ad a poszt elején vázolt szkript számára, az IP-ket pakolgathatjuk tömbökbe, ide-oda, számolgathatjuk, hogy melyik hányszor fordult elő ilyen külső IP, amolyan belső IP mellett, de az már egy másik történet.

2012-03-14

NAT connection tracking naplózás Linuxon

Gyakran felmerülő probléma, hogy miképp lehet azonosítani linuxos SRC NAT mögötti gépeket megbízhatóan. Az ugye viszonylag egyértelmű, hogy címfordítást végző rendszeren valamit kezdenünk kell a kernel által vezetett conntrack táblával, amihez van egy sztenderd interfész minden Linuxon: a /proc/net/ip_conntrack-ből mindig ki lehet olvasni az éppen aktuális bejegyzéseket. Ez azonban igencsak behatárolt lehetőség. Tegyük fel, hogy percenként mentjük a táblázatot! Még így is igen könnyen lehetnek olyan NAT-olt kapcsolatok, amelyek az előző lekérdezés óta épültek ki, de azóta már kikerültek a conntrack táblázatból, ezért a percenkénti logjainkban semmilyen nyomuk nem marad, arról nem is beszélve, hogy rengeteg felesleges információt is eltárolunk, mert a conntrack bejegyzések jelentős része - a megnyitott, kiépített TCP sessionök - tipikusan több percen át élnek, és ezeket mindig újra és újra tárolgatjuk a percenkénti logokban.

Jobb lenne az eseményalapú megközelítés, csak akkor naplózni a conntrack bejegyzéseket, ha új kapcsolat épül ki, vagy ha az adott conntrack bejegyzés kikerül a conntrack táblázatból. Szerencsére ilyesmi létezik Linuxra, a szoftvercsomag neve conntrack-tools, a benne lévő eszközök pedig kiváló, dinamikus, userspace interfészt biztosítanak a kernel conntrack táblájához. A csomag két fontos eleme a conntrack és a conntrackd nevű program. Az előbbivel listázni, naplózni, létrehozni, módosítani, törölni lehet conntrack bejegyzéseket, az utóbbi (a démon) pedig redundáns NAT rendszerek közt képes a conntrack információk szinkronizálására. A conntrack naplózás tesztjéhez az alábbi topológiát használtam:



A "denobula" beállításait nem vittem túlzásba, egy teljesen alap Ubuntu 10.04 LTS telepítést kell elképzelni, ahol két paranccsal beállítható a 10.1.1.0/24 subnetből érkező csomagok forráscímeinek NAT-olása az eth0-ra aggatott külső IP-re, feltételezve, hogy az alapértelmezett ACCEPT iptables policy-ket senki és semmi nem állította át:
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -o eth0 -s 10.1.1.0/24 -j MASQUERADE
Az interfészeket kell még beállítani, az /etc/network/interfaces fájlba kerüljenek a következők:
auto lo eth0 eth1

iface lo inet loopback

iface eth0 inet static
address 10.0.2.15
netmask 255.255.255.0
gateway 10.0.2.2

iface eth1 inet static
address 10.1.1.1
netmask 255.255.255.0
Majd állítsuk be a DNS-t, végül húzzuk fel az interfészeket (újraindítás után ez már az init alatt megtörténik az előbbi auto bejegyzés miatt):
echo "nameserver 8.8.8.8" > /etc/resolv.conf
ifup eth0
ifup eth1
Következik a lényeg, a conntrack-tools telepítése (valójában csak a conntrack csomag szükséges ahhoz, amiről ebben a posztban szó lesz):
apt-get update
apt-get install conntrack conntrackd
A "denobula" beállítása ezzel meg is van, a tesztkörnyezetből még hiányzik a NAT mögötti kliens, az "andoria", ez bármi lehet, Live CD-s Linux, akármi, a lényeg, hogy konfiguráljunk rajta egy 10.1.1.2/24-es IP-t, majd a 10.1.1.1-et gatewaynek, valamint egy DNS szervert (pl. 8.8.8.8). Ezek után nincs más hátra, mint beröffenteni a NAT-os gépen a connttrack programot, a sok opció közül most a -E lesz az, amire szükségünk lesz, további opcióként megadtam a -o id,timestamp-et, így az egyes bejegyzések pontos időbélyege és az adott NAT-olt kacsolat belső ID-je is látszani fog a kimenetben:
root@denobula:~# conntrack -E -o id,timestamp,extended
[1331710267.511683]        [NEW] ipv4     2 tcp      6 120 SYN_SENT src=10.1.1.2 dst=74.125.232.247 sport=39059 dport=80 [UNREPLIED] src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39059 id=3607679456
[1331710267.513939]        [NEW] ipv4     2 tcp      6 120 SYN_SENT src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 [UNREPLIED] src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 id=3607679216
[1331710267.513992]     [UPDATE] ipv4     2 tcp      6 60 SYN_RECV src=10.1.1.2 dst=74.125.232.247 sport=39059 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39059 id=3607679456
[1331710267.514021]     [UPDATE] ipv4     2 tcp      6 60 SYN_RECV src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 id=3607679216
[1331710267.514047]     [UPDATE] ipv4     2 tcp      6 432000 ESTABLISHED src=10.1.1.2 dst=74.125.232.247 sport=39059 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39059 [ASSURED] id=3607679456
[1331710267.514076]     [UPDATE] ipv4     2 tcp      6 432000 ESTABLISHED src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 [ASSURED] id=3607679216
[1331710272.506759]     [UPDATE] ipv4     2 tcp      6 120 FIN_WAIT src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 [ASSURED] id=3607679216
[1331710272.507065]     [UPDATE] ipv4     2 tcp      6 60 CLOSE_WAIT src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 [ASSURED] id=3607679216
[1331710272.507135]     [UPDATE] ipv4     2 tcp      6 30 LAST_ACK src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 [ASSURED] id=3607679216
[1331710272.507208]     [UPDATE] ipv4     2 tcp      6 120 TIME_WAIT src=10.1.1.2 dst=74.125.232.247 sport=39060 dport=80 src=74.125.232.247 dst=10.0.2.15 sport=80 dport=39060 [ASSURED] id=3607679216
A kimenetben megtalálhatók a kért paraméterek, az első mező a timestamp, az utolsó mező a conntrack ID, a sorok egyébként elég beszédesek, minden sorban a második mező a conntrack esemény, amiből összesen három létezik, a NEW értelemszerűen egy új conntrack bejegyzés létrejöttekor generálódik, az UPDATE egy már létező bejegyzésben valamiféle változást jelez, például az UPDATE bejegyzésekben a TCP állapotátmenet-változások kiválóan követhetők. A harmadik, DESTROY nevű esemény pedig azt jelzi (a fenti példában épp nincs ilyen), hogy egy bejegyzés kikerül a conntrack táblából.

Ez a fajta eseményvezérelt kimenet már egészen közel van ahhoz, hogy használható legyen, két apróságra térnék még ki. Az első, hogy az STDOUT-ra küldött log nem tekinthető valódi log-nak, valahova át kell irányítanunk, a következő példában a tee-t használom, amivel így, ahogy van, ott lehet hagyni egy ötös vagy hatos szöveges terminálon, úgysem nyúl hozzá soha senki ;). A másik dolog, hogy általában az UPDATE eseményekre nincs szükség, hiszen az ilyesfajta logoknak az a lényege, hogy ki lehessen hámozni belőlük, hogy egy SRC NAT mögötti gép mettől meddig, milyen külső IP-vel, hová kommunikált, ehhez pedig elég a bejegyzés létrejöttét (NEW) és a bejegyzés végét (DESTROY) naplózni, ami közben történt a bejegyzéssel, nem érdekes. A "végső" NAT naplózó parancs tehát, amit természetesen ezer módon tovább lehet még fejlesztgetni, így néz ki:
conntrack -E -o id,timestamp,extended -e NEW,DESTROY | tee /var/log/ctrack.log