Netzwerk-Tests mit bats, netcat und xxd
IPsec-Tester ist ein Nebenprojekt, dass ich im Umfeld meines letzen Buches angefangen habe. Es dient mir dazu, das IKE-Protokoll - genau genommen Version 2 davon - besser zu verstehen. Momentan kann es IKE_SA_INIT-Anfragen verstehen und beantwortet sie mit NO_PROPOSAL_CHOSEN.
Ich komme nur selten dazu, an dem Projekt weiter zu arbeiten, darum will ich den erreichten Stand mit Tests dokumentieren. Außerdem sollen die Tests mich darauf aufmerksam machen, wenn ich etwas kaputt gemacht habe.
Netzwerkprotokolle testen
Viele Netzwerkprotokolle
- wie zum Beispiel HTTP, IMAP, SMTP -
arbeiten mit reinem Text
und können mit telnet oder Netcat sehr einfach getestet werden.
Für die mit TLS verschlüsselten Varianten
leistet openssl s_client
ähnliches.
IKE gehört leider nicht zu diesen Protokollen. Es verwendet UDP und tauscht Daten in einem binären Format aus. Immerhin arbeitet IKE mit paarweise ausgetauschten Nachrichten, so dass man das Protokoll mit je einem Paar von gesendetem und empfangenem Datagramm simulieren kann.
Für etliche binäre Formate gibt es Compiler beziehungsweise Interpreter, die die Form von Text in das binäre Format und zurück umwandeln können. Das wäre auch ein lohnendes Teilziel für IPsec-Tester, aber im Moment ist das Projekt nicht so weit.
Binärdaten in Text umwandeln
Hier kommt xxd
ins Spiel, Teil des vim-Projekts.
Dieses Programm kann binäre Daten
in eine Textdarstellung als Hexdump umwandeln
und einen Hexdump zurück in binäre Daten.
Die binären Daten kann ich mit Netcat versenden
und die eintreffende Antwort mit xxd
als Hexdump ausgeben.
Es bleibt die Frage,
wie ich an die Hexdumps für die Anfrage komme.
Eine Möglichkeit wäre, diese Daten Byte für Byte selbst aufzuschreiben und sozusagen einen IKE-Request von Hand zusammenzubauen. Das ist im Prizip möglich, die RFĆs enthalten alle dafür nötigen Informationen. So weit will ich aber doch nicht gehen.
Eine einfachere Möglichkeit ist, einen IKE-Austausch im Netzwerk mitzuschneiden, die Datagramme als Hexdump ausgeben zu lassen und damit weiter zu arbeiten.
Bei tcpdump
bekomme ich einen Hexdump der Daten
mit der Option -X
.
Dabei muss ich beachten,
dass dieser Hexdump
außer der IKE-Nachricht
auch den UDP-, den IP- und möglicherweise den Ethernet-Header enthält.
Das heißt,
beim Bearbeiten des Hexdump
muss ich den Teil vor der IKE-Nachricht entfernen,
weil später Netcat die Daten versendet
und nur die UDP-Nutzdaten braucht.
12:23:16.799419 IP 192.168.57.4.500 > 192.168.57.1.500: isakmp: parent_sa ikev2_init[I]
0x0000: 4500 0218 e00d 0000 4011 a571 c0a8 3904 E.......@..q..9.
0x0010: c0a8 3901 01f4 01f4 0204 f56b 7a57 b1e8 ..9........kzW..
0x0020: 37ff 0200 0000 0000 0000 0000 2120 2208 7...........!.".
0x0030: 0000 0000 0000 01fc 2200 005c 0200 002c ........"..\...,
...
0x0200: 2900 0010 0000 402f 0002 0003 0004 0005 ).....@/........
0x0210: 0000 0008 0000 4016 ......@.
Dieses Beispiel aus einem Hexdump von tcpdump enthält den IPv4- und den UDP-Header gefolgt von den Daten des IKE-Requests. Für den Test benötige ich nur die Daten ab Position 0x01c, das heißt, zunächst muss ich die ersten 28 Bytes entfernen, um den Dump für einen Test benutzen zu können.
Etwas einfacher komme ich mit Wireshark an die Daten. Ich wähle das Request-Datagramm aus, gehe in den Dissektor für UDP und markiere die Nutzdaten. Mit Rechtsklick öffne ich das Kontextmenü und wähle mit Packetbytes exportieren eine Datei aus, in die ich die Binärdaten der Anfrage abspeichere.
Diese Datei kann ich direkt verwenden
oder mit xxd
vor der Weiterverarbeitung
in einen Hexdump umwandeln.
Nun kann ich den Test mit folgendem Befehl ausführen:
xxd -r request.dump \
| nc -u -W1 $addr 500 \
| xxd > response.dump
Die Daten in response.dump sollten so aussehen
wie die IKE-Daten im Response
bei Wireshark beziehungsweise tcpdump -X
.
Um den Test reproduzierbar zu machen,
kopiere ich response.dump nach expected.dump
und kann beim Wiederholen des Tests
die beiden Dateien mit cmp
vergleichen.
Ich muss nicht mal mehr einen Blick
auf die Daten des Antwortdatagramms werfen.
Testautomatisierung mit bats-core
Um diesen oder später auch weitere Tests automatisiert ablaufen zu lassen, benötigt es etwas mehr. Automatisiserte Testsuiten lassen sich idealerweise mit einem einzigen Befehl aufrufen und kümmern sich um die Vorbereitung der Tests und das Aufräumen hinterher.
Bei diesen Tests arbeite ich mit der Shell und ein paar zusätzlichen Programmen. Dafür gibt es mit bats (dem Bash Automated Testing System) ein Framework, dass genau das Gewünschte kann und zusätzlich die Ergebnisse mit dem Test Anything Protocol (TAP) zurück geben kann.
Bei bats kann ich mit der Shellfunktion setup()
vor jedem einzelnen Test die Umgebung vorbereiten
oder mit setup_file()
einmalig für alle Tests in einem Skript.
Analog dazu kann ich mit teardown()
nach jedem einzelnen Test aufräumen
oder mit teardown_file()
einmalig nach dem letzten Test.
Für meine Zwecke reicht es,
zu Beginn der Tests das Program itip
im Hintergrund zu starten
und nach dem letzten Test zu beenden.
Dementsprechend verwende ich die folgende Funktion für die Vorbereitung:
setup_file() {
( local pid=$(exec sh -c 'echo "$PPID"')
echo $pid > itip.pid
exec ./itip > itip.out 2>&1
) &
sleep 1
}
Nach dem letzten Test beende ich
itip
mit der folgenden Funktion:
teardown_file() {
kill $(cat itip.pid)
wait $(cat itip.pid) 2>/dev/null || true
}
Der Test für IKE_SA_INIT benutzt eine Funktion für den eigentlichen Ablauf und sieht so aus:
ike_sa_init_01() {
xxd -r t/ike_sa_init-01-req.dump \
| nc -u -W1 127.0.0.1 500 \
| xxd > t/ike_sa_init-01-res.dump
cmp t/ike_sa_init-01-{exp,res}.dump
}
@test "IKE_SA_INIT 01" {
run ike_sa_init_01
[ "$status" -eq 0 ]
}
Alle Testskripte lege ich im Unterverzeichnis t/ ab und rufe sie mit dem folgenden Befehl auf:
$ bats t
✓ IKE_SA_INIT 01
1 test, 0 failures
Möchte ich lieber die Ausgabe im Test Anything Protocol, wie von Perl-Tests gewohnt, lautet der Aufruf:
$ bats --tap t
1..1
ok 1 IKE_SA_INIT 01
Posted 2021-02-09