Ask Your Question

Revision history [back]

Proper way to reassemble QUIC with custom dissector

Goal: View QUIC -streamed structured data (data spans QUIC frames)

I've implemented a basic LUA dissector to parse through the QUIC stream (using decrypted quic.stream_data) using the chained dissector model ( This works fine with the first data header (obviously so) but since the streamed structured data is larger than the QUIC frame (100kb) I need to reassemble the rest of the data from subsequent frames.

I've tried following some references that show reassembly of PDUs over TCP (FPM example code), but it doesn't seem to work over QUIC. After browsing the QUIC dissector I have seen that there is capability for reassembling data in streams. I am not seeing any accumulation through the tvb. Hopefully there isn't something basic I'm missing - any thoughts?

Current code:

local data = Dissector.get("data")
local raw_quic = Dissector.get("quic")

local f_stream_data ="quic.stream_data")

-- create a function to "postdissect" each frame
function custom_proto.dissector(tvbuf, pktinfo, root)

    dprint("custom.dissector called")
    -- reset the save Tvbs
    tvbs = {}

    local can_desegment_saved = pktinfo.can_desegment
    if pktinfo.can_desegment > 0 then
        pktinfo.can_desegment = 2

local bytes_consumed = 0

raw_quic:call(tvbuf, pktinfo, root)

-- get the length of the decrypted data
local pktlen = f_stream_data().len

-- we do this in a while loop, because there could be multiple FPM messages
-- inside a single TCP segment, and thus in the same tvbuf - but our
-- fpm_proto.dissector() will only be called once per TCP segment, so we
-- need to do this loop to dissect each FPM message in it
while bytes_consumed < pktlen do

    -- We're going to call our "dissect()" function, which is defined
    -- later in this script file. The dissect() function returns the
    -- length of the message it dissected as a positive number, or if
    -- it's a negative number then it's the number of additional bytes it
    -- needs if the Tvb doesn't have them all. If it returns a 0, it's a
    -- dissection error.
    local result = dissectCustom(f_stream_data().tvb(), pktinfo, root, bytes_consumed)

    print("Dissect result ", result)

    if result > 0 then
        -- we successfully processed a message, of 'result' length
        bytes_consumed = bytes_consumed + result
        -- go again on another while loop
    elseif result == 0 then
        -- If the result is 0, then it means we hit an error of some kind,
        -- so return 0. Returning 0 tells Wireshark this packet is not for
        -- us, and it will try heuristic dissectors or the plain "data"
        -- one, which is what should happen in this case.
        return 0
        -- we need more bytes, so set the desegment_offset to what we
        -- already consumed, and the desegment_len to how many more
        -- are needed
        pktinfo.desegment_offset = bytes_consumed

        -- invert the negative result so it's a positive number
        result = -result

        pktinfo.desegment_len = result

        -- even though we need more bytes, this packet is for us, so we
        -- tell wireshark all of its bytes are for us by returning the
        -- number of Tvb bytes we "successfully processed", namely the
        -- length of the Tvb
        return pktlen

pktinfo.can_desegment = can_desegment_saved

-- In a TCP dissector, you can either return nothing, or return the number of
-- bytes of the tvbuf that belong to this protocol, which is what we do here.
-- Do NOT return the number 0, or else Wireshark will interpret that to mean
-- this packet did not belong to your protocol, and will try to dissect it
-- with other protocol dissectors (such as heuristic ones)
return bytes_consumed