incomplete.io

wireshark taps in lua

This brief article shows how to use the Lua scripting engine extension in Wireshark to automatically process and analyse a PCAP-format packet capture.

As a hopefully-not-too-trite example, let's assume that we have an application on a Windows system that stores its data on another Windows system using the SMB protocol. Let us also assume that this multithreaded application has multiple file descriptors open to the same file and that we're interested (for whatever reason) in the amount of read and write I/O per descriptor.

Our Lua script will create a Listener object (line 15) which will have its packet() method called once for each packet matching the read filter of the Listener object and a draw() method that will be called once after all packets have been processed.

From each of the packets processed, we're interested in a number of SMB2 dissector fields and we declare each of those on lines 9-12. The field names are available in the dissector reference on wireshark.org or displayed in the status bar of your wireshark UI when a given field is selected. We're interested primarily in the SMB2 command, file descriptor and I/O sizes.

The bulk of the work will be done in our case by a packet() method, which is defined on line 21. This method essentially looks (line 36) for SMB2 read commands (command 0x8) and SMB2 write commands (command 0x9), extracts the I/O size and tracks it in a global data structure (line 53).

Our draw() method (line 59) could be much more complex, but for simplicity's sake, we're just using the pretty printer to display our Lua object as Lua-syntax text. Sample output is provided below.

In order to run the tap script, we need to tell Wireshark or tshark to execute it over a particular packet capture. Using the -X lua_script: tshark option allows us to specify our tap as defined in the file script.lua thusly:

 tshark -r packets.cap -q -X lua_script:script.lua 

Some sample output using the code below can be found at the end of this article.

Here's the complete source code example:

01: --
02: -- An example Lua Wireshark tap for giving SMB2 per-FID I/O stats
03: --
04: 
05: -- Pull in the pl.pretty Lua module to lazy-print our collected data
06: local pretty = require 'pl.pretty'
07: 
08: -- Define fields we want to pull from the packets
09: local cmd_f = Field.new("smb2.cmd")
10: local fid_f = Field.new("smb2.fid")
11: local rlen_f = Field.new("smb2.read_length")
12: local wlen_f = Field.new("smb2.write_length")
13: 
14: -- Define a tap to deal with all our packets
15: smb_tap = Listener.new("smb2")
16: 
17: -- A place to hold everything we find and care about
18: local files = {}
19: 
20: -- this is called for each packet
21: function smb_tap.packet(pinfo,tvb,data)
22:     cmd = cmd_f().value
23: 
24:     if fid_f() then
25:         fid = tostring(fid_f())
26:     else
27:         -- no FID in frame -- opened before capture started?
28:         return
29:     end
30: 
31:     -- create a slot in the table for this FID
32:     if not files[fid] then
33:         files[fid] = {}
34:     end
35: 
36:     if cmd == 0x8 or cmd == 0x9 then
37:         local length
38:         if cmd == 0x8 then
39:             -- use pretty labels and keep the read_length
40:             cmdstring = "read"
41:             length = tonumber(rlen_f().value)
42:         else
43:             -- use pretty labels and keep the write length
44:             cmdstring = "write"
45:             length = tonumber(wlen_f().value)
46:         end
47: 
48:         -- initialise our counter if not already
49:         if not files[fid][cmdstring] then
50:             files[fid][cmdstring] = 0
51:         end
52:         -- add the current request's length/size to our per-FID counter
53:         files[fid][cmdstring] = files[fid][cmdstring] + length
54:     end
55: 
56: end
57: 
58: -- this is called when all packets have been processed
59: function smb_tap.draw()
60:     
61:     pretty.dump(files)
62: end
63: 
64: 

And some sample output using the above code and an existing packet capture:

01: {
02:   ["00000039-000b-0000-1500-00000b000000"] = {
03:   },
04:   ["0000001e-000b-0000-4900-00000b000000"] = {
05:     read = 397312
06:   },
07:   ["00000027-000b-0000-0100-00000b000000"] = {
08:   },
09:   ["00000029-000b-0000-0900-00000b000000"] = {
10:     read = 397312
11:   },
12:   ["0000002d-000b-0000-0900-00000b000000"] = {
13:     write = 4096
14:   },
15:   ["00000004-000b-0000-0100-00000b000000"] = {
16:     write = 248,
17:     read = 2048
18:   },
19:   ["00000025-000b-0000-6500-00000b000000"] = {
20:     read = 397312
21:   },
22:   ["00000006-000b-0000-0900-00000b000000"] = {
23:     write = 304,
24:     read = 2048
25:   },
26:   ["00000018-000b-0000-3500-00000b000000"] = {
27:     write = 20480
28:   },
29:   ["00000016-000b-0000-2d00-00000b000000"] = {
30:     read = 401408
31:   },
32:   ["00000033-000b-0000-1d00-00000b000000"] = {
33:     write = 24576
34:   },
35:   ["0000000f-000b-0000-0500-00000b000000"] = {
36:     write = 521766
37:   },
38:   ["0000002b-000b-0000-0100-00000b000000"] = {
39:     write = 4096
40:   },
41:   ["00000031-000b-0000-1500-00000b000000"] = {
42:     read = 401408
43:   },
44:   ["0000002f-000b-0000-0d00-00000b000000"] = {
45:   },
46:   ["00000005-000b-0000-0500-00000b000000"] = {
47:     write = 248,
48:     read = 2048
49:   },
50:   ["00000007-000b-0000-0100-00000b000000"] = {
51:     write = 304,
52:     read = 2048
53:   },
54: }
55: 
Twitter: @IncompleteIO