/james/notes/computers

Making Wireshark Link Layer Dissectors

Overview

While Wireshark is typically used for ethernet traffic, it can also be used to decode any arbitrary protocol. All PCAP files specify which link layer is used in the PCAP global header. Ethernet has a link-layer value of 1, but there are over 100 other standardised types defined. Sixteen link layer values are reserved for user use; these are named DLT_USER0 (147) through to DLT_USER15 (162). A full list of all supported link layers can be found here.

Lua Scripts

The functionality of Wireshark can be easily extended by using the Lua scripting language to create protocol dissectors. Typically these are written to dissect custom Ethernet or IP packets, but they can just as easily be used to dissect data at the link layer too.

An excellent guide to creating Wireshark protocol dissectors in Lua can be found here.

Lua scrips saved in ~/.local/lib/wireshark/plugins will be automatically loaded by Wireshark. Alternatively the -X lua_script:my_lua_script.lua option can be used to temporarily load a script when running Wireshark from a terminal.

User DLT Table

When a PCAP file has a DTL_USER* link layer specified, Wireshark looks up in its User DLT Table to identify which protocol dissector to use.

To set up the User DTL Table, first make sure any custom Lua dissectors are loaded in Wireshark, then go to Edit -> Preferences -> Protocols -> DLT_USER -> Encapsulations Table (Edit). Select +, pick the desired DLT from the drop down box, enter the name of the protocol dissector that understands the link layer, and finally press Ok.

Alternatively, if you just want to temporarily load a dissector script and link it to a DLT user link layer, then you can use the following command:

wireshark -X lua_script:<name_of_script>.lua -o "uat:user_dlts:\"User 0 (DLT=147)\",\"<name_of_link_layer_dissector>\",\"\",\"\",\"\",\"\""
# E.g. If my_script.lua had a dissector for "myprotocol", and you wanted to use DLT_USER3,
# then you would use the following command:
wireshark -X lua_script:my_script.lua -o "uat:user_dlts:\"User 3 (DLT=150)\",\"myprotocol\",\"\",\"\",\"\",\"\""

Example

For this example I have made up an demo protocol with the following specification:

source: NULL terminated string
destination: NULL terminated string
type: UINT16, Big Endian
data: 32 bytes of data

Generating a PCAP file with a custom link layer

The following Python script will generate a PCAP file containing data structured as specified in the protocol defined above.

#!/usr/bin/env python3

# import module
import struct
import time
import random
import os

# PCAP file settings
# https://wiki.wireshark.org/Development/LibpcapFileFormat
MAGIC_NUMBER = 0xA1B2C3D4 
VERSION_MAJOR = 2
VERSION_MINOR = 4
THISZONE = 0 # GMT to local correction
SIGFIGS = 0 # Accuracy of timestamps
SNAPLEN = 65535 # Max packet length
NETWORK = 147 # Data link type. 147 = DLT_USER0, 148 = DLT_USER1, etc.
PCAP_GLOBAL_HEADER_FORMAT = '@ I H H i I I I' # @ = System Endian
PCAP_PACKET_HEADER_FORMAT = '@ I I I I'

class Pcap:

    def __init__(self, filename,):
        self.pcap_file = open(filename, 'wb')
        self.pcap_file.write(
            struct.pack(PCAP_GLOBAL_HEADER_FORMAT, MAGIC_NUMBER, VERSION_MAJOR,
                        VERSION_MINOR, THISZONE, SIGFIGS, SNAPLEN, NETWORK))

    def writelist(self, data=[]):
        for i in data:
            self.write(i)
        return

    def write(self, data):
        ts_sec, ts_usec = self.getTime()
        length = len(data)
        self.pcap_file.write(struct.pack(PCAP_PACKET_HEADER_FORMAT, ts_sec, ts_usec, length, length))
        self.pcap_file.write(data)

    def close(self):
        self.pcap_file.close()

    def getTime(self):
        time_now = time.time()
        time_seconds = int(time_now)
        time_microseconds = int((time_now - time_seconds) * 1e6)
        return time_seconds, time_microseconds

# This is just an example protocol. Data could be anything
def get_example_frame(source, destination, type):
    output = bytes()
    output += bytes(source + "\0", "UTF-8")
    output += bytes(destination + "\0", "UTF-8")
    output += struct.pack("> H", type) # Big endian ushort
    output += os.urandom(32)
    return output

if __name__ == "__main__":
    # Create PCAP file with 10 random packets in it
    pcap = Pcap("test.pcap")
    for _ in range(10):
        pcap.write(get_example_frame("Device" + str(random.randint(0,4)),
                                     "Device" + str(random.randint(0,4)),
                                     random.randint(0,10)))
        time.sleep(0.01)
    pcap.close()

Lua script to dissect the protocol

-- "mylink" is the name of the protocol; this is what is specified in the User DLT Table
user_link_layer = Proto("mylink", "My link layer")

SRC = ProtoField.string("MYLINK.SRC","SRC", base.ASCII)
DST = ProtoField.string("MYLINK.DST","DST", base.ASCII)
TYPE = ProtoField.uint16("MYLINK.TYPE","TYPE", base.HEX)

user_link_layer.fields = {SRC, DST, TYPE}

function user_link_layer.dissector(buffer, pinfo, tree)
    pinfo.cols.protocol = user_link_layer.name
    packet_length = buffer:len()
    local subtree = tree:add(user_link_layer, buffer(), "My Frame")

    -- Get source string
    local offset = 0
    local src_string_len = get_string(buffer,offset,packet_length)
    subtree:add(SRC, buffer(offset, src_string_len))
    -- Write the source to the main wireshark table view as well
    pinfo.cols.src = buffer(offset, src_string_len):string()

    -- Get destination string
    offset = offset + src_string_len
    local dst_string_len = get_string(buffer,offset,packet_length)
    subtree:add(DST, buffer(offset,dst_string_len))
    -- Write the destination to the main wireshark table view as well
    pinfo.cols.dst = buffer(offset, dst_string_len):string()

    -- Get type UINT16
    offset = offset + dst_string_len
    subtree:add(TYPE, buffer(offset,2))
    offset = offset + 2

    -- Handover rest of data to generic data dissector
    local data_buffer = buffer:range(offset, packet_length-offset):tvb()
    Dissector.get("data"):call(data_buffer,pinfo,tree)
end

function get_string(buffer, start, max)
    local string_length = 0
    for i = start, max - 1, 1 do
        if (buffer(i,1):le_uint() == 0) then
            -- +1 to include the \0
            string_length = i - start + 1            
            break
        end
    end
    return string_length
end 

NB. There is no obligation to have source and destination defined in the protocol, but since most protocols have these fields Wireshark shows columns for them in the GUI. These will just be blank if not used.

Results

Once the Lua script is loaded, and the User DLT Table is set up, loading the test.pcap file causes the link layer protocol dissector to be invoked, and the protocol is correctly dissected in the Wireshark window.

A screenshot of the result is shown below: