Added libpcap parsing example.
[ocaml-bitstring.git] / examples / libpcap.ml
1 (* Print out packets from a tcpdump / libpcap / wireshark capture file.
2  * $Id$
3  *
4  * To test this, capture some data using:
5  *   /usr/sbin/tcpdump -s 1500 -w /tmp/dump
6  * then analyze it using:
7  *   ./libpcap /tmp/dump
8  *
9  * The file format is documented here:
10  *   http://wiki.wireshark.org/Development/LibpcapFileFormat
11  *
12  * libpcap endianness is determined at runtime.  Currently we don't
13  * handle this well - we need to write the match code out twice.
14  * Runtime endianness setting will solve this.  See TODO list, item 11.
15  *)
16
17 open Printf
18
19 let rec main () =
20   if Array.length Sys.argv <= 1 then failwith "libpcap dumpfile";
21   let bits = Bitmatch.bitstring_of_file Sys.argv.(1) in
22   let endian, file_header, bits = libpcap_header bits in
23
24   (* Read the packets and print them out. *)
25   let rec loop bits =
26     let pkt_header, pkt_data, bits = libpcap_packet endian file_header bits in
27     decode_and_print_packet file_header pkt_header pkt_data;
28     loop bits
29   in
30   try loop bits
31   with
32     End_of_file -> ()
33
34 and libpcap_header bits =
35   bitmatch bits with
36   | { 0xd4c3b2a1_l : 32;                (* writer was little endian *)
37       major : 16 : littleendian;        (* version *)
38       minor : 16 : littleendian;
39       timezone : 32 : littleendian;     (* timezone correction (seconds) *)
40       _ : 32 : littleendian;            (* always 0 apparently *)
41       snaplen : 32 : littleendian;      (* max length of captured packets *)
42       network : 32 : littleendian;      (* data link layer type *)
43       rest : -1 : bitstring
44     } ->
45       Bitmatch.LittleEndian, (major, minor, timezone, snaplen, network), rest
46
47   | { 0xa1b2c3d4_l : 32;                (* writer was big endian *)
48       major : 16;                       (* version *)
49       minor : 16;
50       timezone : 32;                    (* timezone correction (seconds) *)
51       _ : 32;                           (* always 0 apparently *)
52       snaplen : 32;                     (* max length of captured packets *)
53       network : 32;                     (* data link layer type *)
54       rest : -1 : bitstring
55     } ->
56       Bitmatch.BigEndian, (major, minor, timezone, snaplen, network), rest
57
58   | { _ } ->
59       failwith "not a libpcap/tcpdump packet capture file"
60
61 and libpcap_packet endian file_header bits =
62   if endian = Bitmatch.LittleEndian then (
63     bitmatch bits with
64     | { ts_sec : 32 : littleendian;     (* packet timestamp seconds *)
65         ts_usec : 32 : littleendian;    (* packet timestamp microseconds *)
66         incl_len : 32 : littleendian;   (* packet length saved in this file *)
67         orig_len : 32 : littleendian;   (* packet length originally on wire *)
68         pkt_data : Int32.to_int incl_len*8 : bitstring;
69         rest : -1 : bitstring
70       } ->
71         (ts_sec, ts_usec, incl_len, orig_len), pkt_data, rest
72
73     | { _ } -> raise End_of_file
74   ) else (
75     bitmatch bits with
76     | { ts_sec : 32;                    (* packet timestamp seconds *)
77         ts_usec : 32;                   (* packet timestamp microseconds *)
78         incl_len : 32;                  (* packet length saved in this file *)
79         orig_len : 32;                  (* packet length originally on wire *)
80         pkt_data : Int32.to_int incl_len*8 : bitstring;
81         rest : -1 : bitstring
82       } ->
83         (ts_sec, ts_usec, incl_len, orig_len), pkt_data, rest
84
85     | { _ } -> raise End_of_file
86   )
87
88 and decode_and_print_packet file_header pkt_header pkt_data =
89   let (ts_sec, ts_usec, _, orig_len) = pkt_header in
90   printf "%ld.%ld %ldB " ts_sec ts_usec orig_len;
91
92   (* Assume an ethernet frame containing an IPv4/6 packet.  We ignore
93    * the ethertype field and determine the IP version from the packet
94    * itself.  If it doesn't match our assumptions, hexdump it.
95    *)
96   (bitmatch pkt_data with
97    | { d0 : 8; d1 : 8; d2 : 8; d3 : 8; d4 : 8; d5 : 8; (* ether dest *)
98        s0 : 8; s1 : 8; s2 : 8; s3 : 8; s4 : 8; s5 : 8; (* ether src *)
99        _ : 16;                                        (* ethertype *)
100        packet : -1 : bitstring                        (* payload *)
101      } ->
102        printf "%x:%x:%x:%x:%x:%x < %x:%x:%x:%x:%x:%x "
103          d0 d1 d2 d3 d4 d5 s0 s1 s2 s3 s4 s5;
104
105        (bitmatch packet with
106         | { 4 : 4;                      (* IPv4 *)
107             hdrlen : 4; tos : 8; length : 16;
108             identification : 16; flags : 3; fragoffset : 13;
109             ttl : 8; protocol : 8; checksum : 16;
110             s0 : 8; s1 : 8; s2 : 8; s3 : 8;
111             d0 : 8; d1 : 8; d2 : 8; d3 : 8;
112             _(*options*) : (hdrlen-5)*32 : bitstring;
113             _(*payload*) : -1 : bitstring } ->
114             printf "IPv4 %d.%d.%d.%d < %d.%d.%d.%d "
115               s0 s1 s2 s3 d0 d1 d2 d3
116
117         | { 6 : 4;                      (* IPv6 *)
118             tclass : 8; flow : 20;
119             length : 16; nexthdr : 8; ttl : 8;
120             _(*source*) : 128 : bitstring;
121             _(*dest*) : 128 : bitstring;
122             _(*payload*) : -1 : bitstring } ->
123             printf "IPv6 ";
124
125         | { _ } ->
126             printf "\n"; Bitmatch.hexdump_bitstring stdout packet
127        )
128
129    | { _ } ->
130        printf "\n"; Bitmatch.hexdump_bitstring stdout pkt_data
131   );
132   printf "\n"
133
134 let () = main ()