Fehlersuche in Datenströmen mittels Wireshark

Datenströme sind wie Blutgefäße in einem Organismus, ohne sie läuft kein Softwaresystem. Und so ist klar, auch hier gehört die Fehlersuche zum Berufsalltag. Bei Binärstreams scheint das erst einmal nicht so einfach zu sein. An dieser Stelle mag ich sehr Wireshark. Viele kennen das Tool zur Untersuchung von Datenströmen im Netzwerkbetrieb oder über USB. Wireshark ist ein Analysetool, welches auf pcap aufsetzt. Der Ansatz von pcap ist jedoch so allgemeingültig, dass man ihn längst nicht nur mehr für Netzwerke nutzt. Daraus resultiert dann auch das einfache und vor allem universelle Libpcap File Format [1], welches zur Speicherung von Mitschnitten von pcap (packet capture) dient.

Eine pcap Datei startet mit einem Global Header, danach folgen beliebig viele mitgeschnittene Binärpakete. Jedes Paket startet mit einem Packet Header, danach folgt das Binärpaket selbst, bezeichnet als Packet Data. Dies noch einmal zum besseren Verständnis

  • Global Header
  • Packet Header (Paket 1)
  • Packet Data (Paket 1)
  • Packet Header (Paket 2)
  • Packet Data (Paket 2)
  • Packet Header (Paket 3)
  • Packet Data (Paket 3)
  • usw …

Der Global Header ist wie folgt aufgebaut:

typedef struct pcap_hdr_s {
        guint32 magic_number;   /* magic number */
        guint16 version_major;  /* major version number */
        guint16 version_minor;  /* minor version number */
        gint32  thiszone;       /* GMT to local correction */
        guint32 sigfigs;        /* accuracy of timestamps */
        guint32 snaplen;        /* max length of captured packets, in octets */
        guint32 network;        /* data link type */
} pcap_hdr_t;

Die magic_number ist der bekannte Weg, einen Wechsel der Byte Order zwischen dem die Datei schreibenden und dem die Datei lesenden Gerät zu erkennen. Beim Schreiben der Datei wird hier 0xa1b2c3d4 geschrieben. Liest nun die Auswertesoftware hier ebenfalls 0xa1b2c3d4, so stimmt die Byte Order überein. Wird dagegen 0xd4c3b2a1 gelesen, so müssen die folgenden Integer Werte geswappt werden. Bedeutet für uns in der Praxis, wir müssen uns beim Schreiben der Datei keine Sorgen um die Byte Order machen. Das Feld network sollte, wenn nicht wirklich ein bereits vorhandener Link Type beim Mitschnitt verwendet wird, einen der Werte DLT_USER0 bis DLT_USER15 (Dezimalwerte 147 bis 162) für die private Nutzung erhalten. Das Feld snaplen enthält die maximal gespeicherte Länge für Pakete. Dieses Feld ist vor allem dann wichtig, wenn Pakete nur gekürzt gespeichert werden. Für die restlichen Felder verweise ich wieder auf [1].

Der Packet Header ist wie folgt aufgebaut:

typedef struct pcaprec_hdr_s {
        guint32 ts_sec;         /* timestamp seconds */
        guint32 ts_usec;        /* timestamp microseconds */
        guint32 incl_len;       /* number of octets of packet saved in file */
        guint32 orig_len;       /* actual length of packet */
} pcaprec_hdr_t;

Mit ts_sec und ts_usec treffen wir auf unseren alten Bekannten timeval aus der C-Welt zur Angabe der Capture Time. Hier kann im einfachsten Fall gettimeofday() genutzt werden. Die Felder incl_len und orig_len dürften selbsterklärend sein. WICHTIG: was logischerweise nicht Bestandteil des Packet Headers ist, ist die Kennung des Senders des Paketes. Wir erinnern uns, pcap dient ursprünglich dem Netzwerkmitschnitt, Sender und Empfänger sind dort in den Paketen selbst angegeben. Implementieren wir bi- oder multidirektionale Mitschnitte, so müssen wir uns selbst um die Sender- und Empfängerkennung im Paket kümmern, im einfachsten Fall durch einen Header, den wir dem Paket voran setzen (natürlich in der Länge berücksichtigen!).

Und das war es auch schon. Auf diese Weise kann man nun Mitschnitte erstellen, z.B. für UART, SPI oder was auch immer. Aber UART z.B. ist doch nicht paketorientiert? Richtig, aber genau das ist wieder etwas, was man ja in dem Fall eigentlich haben möchte, das Wissen um das Timing des Datenstroms. Heißt, ich setze beim Lesen der UART meinen Empfangspuffer ausreichend groß und speichere als Paket alles, was ein read mir an Oktetts (Bytes) bei einem Aufruf zurückgibt.

Diese Methode macht sich nicht nur bei der Fehlersuche und Kontrolle in eigenen Protokollen nützlich, sondern gerade auch in der Fehlersuche bei Protokollen von Sensoren und Komponenten von Zulieferern. Gerade hier trifft man leider sehr oft auf die bitteren Diskrepanzen zwischen Dokumentationen und Realität. Mitschnitte mit genauen Zeitstempeln sind hier nicht nur für die eigene Fehlersuche ein Segen, sondern auch zur Dokumentation und Argumentation gegenüber dem Zulieferer. Nicht selten muss man hier mit harten Bandagen gegen Mauern und Leugnen ankämpfen, unleugbar über Mitschnitte. Über pcap-Dateien hat man so eine gute Möglichkeit, diese auch für ihn direkt auswertbar zu übermitteln. Wireshark ist für jeden verfügbar …