Wie man mit tcpdump auf doppelt getagged Pakete prüft
Für einen bpf Filter benötige ich die Möglichkeit mehrfach mit VLANs getaggte Pakete zu erkennen und in deren Inhalt nach protokollspezifischen Werten zu durchsuchen. Natürlich möchte ich die binären Regeln nicht komplett von Hand schreiben.
Der einfache Ansatz ist sich das Regelwerk durch tcpdump selbst erzeugen zu lassen.
# tcpdump -s 0 -p -d 'ip and udp and src port 12345' (000) ldh [12] (001) jeq #0x800 jt 2 jf 10 (002) ldb [23] (003) jeq #0x11 jt 4 jf 10 (004) ldh [20] (005) jset #0x1fff jt 10 jf 6 (006) ldxb 4*([14]&0xf) (007) ldh [x + 14] (008) jeq #0x3039 jt 9 jf 10 (009) ret #262144 (010) ret #0
Dieser Code macht folgendes:
- Im Ethernet-Header (an Position 12) wird der Ethertype ermittelt.
- Ist der IPv4 (0800), so geht's bei 2 weiter, anderenfalls Abbruch zu 10.
- Im IP-Header (an Position 23 aus Sicht des kompletten Frames) steht die Protokollnummer.
- Ist diese UDP (17 = 0x11), geht's weiter.
- Nach dem variabel langen IP-Header (Länge in 32bit Worten an Position 14) folgt der UDP Header.
- Dort steht an (der variablen) Position die Portnummer.
- Entspricht die dem gewünschten Wert, gibt's einen positiven Rückgabewert (der i.d.R. die Länge der zu exahierenden Daten entspricht).
- Anderenfalls gibt es den Fehlercode 0 zurück (oder auch 0 verwertbare Bytes).
Oder direkt binär, so wie ich es brauche:
# tcpdump -s 0 -p -ddd 'ip and udp and src port 12345' 11 40 0 0 12 21 0 8 2048 48 0 0 23 21 0 6 17 40 0 0 20 69 4 0 8191 177 0 0 14 72 0 0 14 21 0 1 12345 6 0 0 262144 6 0 0 0
Dieser BPF Code nimmt an, dass das Paket direkt mit IP beginnt, es gibt keine VLAN Frames (die jeweils 4 Byte kosten).
Ich lönnte jetzt also an allen Stellen, wo auf eine Position im Paket Bezug genommen wird, einen entsprechenden Offset manuell hinzufügen. Dieses manuelle Nachpatchen ist jedoch nicht sonderlich wartungsfreundlich.
Nach einigem Suchen fand ich den undokumentierten Befehl vlan. Wenn man den vor den Ausdruck stellt, passt das tcpdump den BPF-Filter passend an.
# root@a10nsp:~ # tcpdump -s 0 -p -d 'vlan 123 and ip and udp and src port 12345' (000) ldh [12] (001) jeq #0x8100 jt 4 jf 2 (002) jeq #0x88a8 jt 4 jf 3 (003) jeq #0x9100 jt 4 jf 17 (004) ldh [14] (005) and #0xfff (006) jeq #0x7b jt 7 jf 17 (007) ldh [16] (008) jeq #0x800 jt 9 jf 17 (009) ldb [27] (010) jeq #0x11 jt 11 jf 17 (011) ldh [24] (012) jset #0x1fff jt 17 jf 13 (013) ldxb 4*([18]&0xf) (014) ldh [x + 18] (015) jeq #0x3039 jt 16 jf 17 (016) ret #262144 (017) ret #0
Man sieht sehr schön, dass die Offsets für die Paketanalyse schön um vier Bytes verschoben wurden.
Gehen auch zwei VLANs?
# tcpdump -s 0 -p -d 'vlan 123 and vlan 456 and ip and udp and src port 12345' (000) ldh [12] (001) jeq #0x8100 jt 4 jf 2 (002) jeq #0x88a8 jt 4 jf 3 (003) jeq #0x9100 jt 4 jf 24 (004) ldh [14] (005) and #0xfff (006) jeq #0x7b jt 7 jf 24 (007) ldh [16] (008) jeq #0x8100 jt 11 jf 9 (009) jeq #0x88a8 jt 11 jf 10 (010) jeq #0x9100 jt 11 jf 24 (011) ldh [18] (012) and #0xfff (013) jeq #0x1c8 jt 14 jf 24 (014) ldh [20] (015) jeq #0x800 jt 16 jf 24 (016) ldb [31] (017) jeq #0x11 jt 18 jf 24 (018) ldh [28] (019) jset #0x1fff jt 24 jf 20 (020) ldxb 4*([22]&0xf) (021) ldh [x + 22] (022) jeq #0x3039 jt 23 jf 24 (023) ret #262144 (024) ret #0
Hervorragend: Nun werden doppelt getaggte Pakete ausgewertet. Allerdings muss man die VLAN Tags exakt kennen.
Ich möchte aber nur double tagged VLANs mit variablen VLAN-Nummern bearbeiten, kenne diese also nicht. Wie kann man das beschreiben? Vielleicht als Negation? Nicht VLAN 123? Gibt es eine VLAN Nummer die sicher nicht auftreten kann?
ja, die Null. Eine VLAN Header mit der VLAN-ID 0 ist definiert als untagged, gestattet aber QoS Parameter. Untagged will ich aber nicht, das ist also okay.
# tcpdump -s 0 -p -d 'not vlan 0 and not vlan 0 and ip and udp and src port 12345' (000) ldh [12] (001) jeq #0x8100 jt 4 jf 2 (002) jeq #0x88a8 jt 4 jf 3 (003) jeq #0x9100 jt 4 jf 6 (004) ldh [14] (005) jset #0xfff jt 6 jf 22 (006) ldh [16] (007) jeq #0x8100 jt 10 jf 8 (008) jeq #0x88a8 jt 10 jf 9 (009) jeq #0x9100 jt 10 jf 12 (010) ldh [18] (011) jset #0xfff jt 12 jf 22 (012) ldh [20] (013) jeq #0x800 jt 14 jf 22 (014) ldb [31] (015) jeq #0x11 jt 16 jf 22 (016) ldh [28] (017) jset #0x1fff jt 22 jf 18 (018) ldxb 4*([22]&0xf) (019) ldh [x + 22] (020) jeq #0x3039 jt 21 jf 22 (021) ret #262144 (022) ret #0
Und tut!