1----------------------------------------
2-- script-name: proto.lua
3-- This is based on the dissector.lua example script, which is also used for testing.
4-- Unlike that one, this one is purely for testing even more things, notably
5-- the Proto/ProtoField API.
6----------------------------------------
7
8------------- general test helper funcs ------------
9local FRAME = "frame"
10local OTHER = "other"
11
12local total_tests = 0
13local function getTotal()
14    return total_tests
15end
16
17
18local packet_counts = {}
19local function incPktCount(name)
20    if not packet_counts[name] then
21        packet_counts[name] = 1
22    else
23        packet_counts[name] = packet_counts[name] + 1
24    end
25end
26local function getPktCount(name)
27    return packet_counts[name] or 0
28end
29
30local passed = {}
31local function setPassed(name)
32    if not passed[name] then
33        passed[name] = 1
34    else
35        passed[name] = passed[name] + 1
36    end
37    total_tests = total_tests + 1
38end
39
40local fail_count = 0
41local function setFailed(name)
42    fail_count = fail_count + 1
43    total_tests = total_tests + 1
44end
45
46-- expected number of runs per type
47-- note ip only runs 3 times because it gets removed
48-- and dhcp only runs twice because the filter makes it run
49-- once and then it gets replaced with a different one for the second time
50local taptests = { [FRAME]=4, [OTHER]=48 }
51local function getResults()
52    print("\n-----------------------------\n")
53    for k,v in pairs(taptests) do
54        if v ~= 0 and passed[k] ~= v then
55            print("Something didn't run or ran too much... tests failed!")
56            print("Dissector type "..k.." expected: "..v..", but got: "..tostring(passed[k]))
57            return false
58        end
59    end
60    print("All tests passed!\n\n")
61    return true
62end
63
64
65local function testing(type,...)
66    print("---- Testing "..type.." ---- "..tostring(...).." for packet # "..getPktCount(type).." ----")
67end
68
69local function test(type,name, ...)
70    io.stdout:write("test "..type.."-->"..name.."-"..getTotal().."-"..getPktCount(type).."...")
71    if (...) == true then
72        setPassed(type)
73        io.stdout:write("passed\n")
74        return true
75    else
76        setFailed(type)
77        io.stdout:write("failed!\n")
78        error(name.." test failed!")
79    end
80end
81
82---------
83-- the following are so we can use pcall (which needs a function to call)
84local function callFunc(func,...)
85    func(...)
86end
87
88local function callObjFuncGetter(vart,varn,tobj,name,...)
89    vart[varn] = tobj[name](...)
90end
91
92local function setValue(tobj,name,value)
93    tobj[name] = value
94end
95
96local function getValue(tobj,name)
97    local foo = tobj[name]
98end
99
100------------- test script ------------
101
102----------------------------------
103-- modify original test function for now, kinda sorta
104local orig_test = test
105test = function (...)
106    return orig_test(OTHER,...)
107end
108
109----------------------------------------
110-- creates a Proto object, but doesn't register it yet
111testing(OTHER,"Proto creation")
112
113test("Proto.__call", pcall(callFunc,Proto,"foo","Foo Protocol"))
114test("Proto.__call", pcall(callFunc,Proto,"foo1","Foo1 Protocol"))
115test("Proto.__call", not pcall(callFunc,Proto,"","Bar Protocol"))
116test("Proto.__call", not pcall(callFunc,Proto,nil,"Bar Protocol"))
117test("Proto.__call", not pcall(callFunc,Proto,"bar",""))
118test("Proto.__call", not pcall(callFunc,Proto,"bar",nil))
119
120
121local dns = Proto("mydns","MyDNS Protocol")
122
123test("Proto.__tostring", tostring(dns) == "Proto: MYDNS")
124
125----------------------------------------
126-- multiple ways to do the same thing: create a protocol field (but not register it yet)
127-- the abbreviation should always have "<myproto>." before the specific abbreviation, to avoid collisions
128testing(OTHER,"ProtoField creation")
129
130local pfields = {} -- a table to hold fields, so we can pass them back/forth through pcall()
131--- variable                -- what dissector.lua did, so we almost match it
132local pf_trasaction_id     = 1 -- ProtoField.new("Transaction ID", "mydns.trans_id", ftypes.UINT16)
133local pf_flags             = 2 -- ProtoField.new("Flags", "mydns.flags", ftypes.UINT16, nil, base.HEX)
134local pf_num_questions     = 3 -- ProtoField.uint16("mydns.num_questions", "Number of Questions")
135local pf_num_answers       = 4 -- ProtoField.uint16("mydns.num_answers", "Number of Answer RRs")
136local pf_num_authority_rr  = 5 -- ProtoField.uint16("mydns.num_authority_rr", "Number of Authority RRs")
137local pf_num_additional_rr = 6 -- ProtoField.uint16("mydns.num_additional_rr", "Number of Additional RRs")
138
139test("ProtoField.new",pcall(callObjFuncGetter, pfields,pf_trasaction_id, ProtoField,"new", "Transaction ID", "mydns.trans_id", ftypes.INT16,nil,"base.DEC"))
140test("ProtoField.new",pcall(callObjFuncGetter, pfields,pf_flags, ProtoField,"new", "Flags", "mydns.flags", ftypes.UINT16, nil, "base.HEX"))
141
142-- tries to register a field that already exists (from the real dns proto dissector) but with incompatible type
143test("ProtoField.new_duplicate_bad",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Flags", "dns.flags", ftypes.INT16, nil, "base.HEX"))
144test("ProtoField.int16_duplicate_bad",not pcall(callObjFuncGetter, pfields,10, ProtoField,"int16", "dns.id","Transaction ID"))
145-- now compatible (but different type)
146test("ProtoField.new_duplicate_ok",pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Flags", "dns.flags", ftypes.UINT32, nil, "base.HEX"))
147test("ProtoField.uint16_duplicate_ok",pcall(callObjFuncGetter, pfields,10, ProtoField,"uint16", "dns.id","Transaction ID"))
148
149-- invalid valuestring arg
150test("ProtoField.new_invalid_valuestring",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Transaction ID", "mydns.trans_id", ftypes.INT16,"howdy","base.DEC"))
151-- invalid ftype
152test("ProtoField.new_invalid_ftype",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Transaction ID", "mydns.trans_id", 9999))
153-- invalid description
154--test("ProtoField.new_invalid_description",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "", "mydns.trans_id", ftypes.INT16))
155test("ProtoField.new_invalid_description",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", nil, "mydns.trans_id", ftypes.INT16))
156
157test("ProtoField.new_invalid_abbr",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "trans id", "", ftypes.INT16))
158test("ProtoField.new_invalid_abbr",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "trans id", nil, ftypes.INT16))
159
160test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_questions, ProtoField,"int16", "mydns.num_questions", "Number of Questions"))
161test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_answers, ProtoField,"int16", "mydns.num_answers", "Number of Answer RRs",base.DEC))
162test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_authority_rr, ProtoField,"int16", "mydns.num_authority_rr", "Number of Authority RRs",base.DEC))
163test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_additional_rr, ProtoField,"int16", "mydns.num_additional_rr", "Number of Additional RRs"))
164
165-- now undo the table thingy
166pf_trasaction_id = pfields[pf_trasaction_id]
167pf_flags = pfields[pf_flags]
168pf_num_questions = pfields[pf_num_questions]
169pf_num_answers = pfields[pf_num_answers]
170pf_num_authority_rr = pfields[pf_num_authority_rr]
171pf_num_additional_rr = pfields[pf_num_additional_rr]
172
173-- within the flags field, we want to parse/show the bits separately
174-- note the "base" argument becomes the size of the bitmask'ed field when ftypes.BOOLEAN is used
175-- the "mask" argument is which bits we want to use for this field (e.g., base=16 and mask=0x8000 means we want the top bit of a 16-bit field)
176-- again the following shows different ways of doing the same thing basically
177local pf_flag_response              = ProtoField.new("Response", "mydns.flags.response", ftypes.BOOLEAN, {"this is a response","this is a query"}, 16, 0x8000, "is the message a response?")
178local pf_flag_opcode                = ProtoField.new("Opcode", "mydns.flags.opcode", ftypes.UINT16, nil, base.DEC, 0x7800, "operation code")
179local pf_flag_authoritative         = ProtoField.new("Authoritative", "mydns.flags.authoritative", ftypes.BOOLEAN, nil, 16, 0x0400, "is the response authoritative?")
180local pf_flag_truncated             = ProtoField.bool("mydns.flags.truncated", "Truncated", 16, nil, 0x0200, "is the message truncated?")
181local pf_flag_recursion_desired     = ProtoField.bool("mydns.flags.recursion_desired", "Recursion desired", 16, {"yes","no"}, 0x0100, "do the query recursivley?")
182local pf_flag_recursion_available   = ProtoField.bool("mydns.flags.recursion_available", "Recursion available", 16, nil, 0x0080, "does the server support recursion?")
183local pf_flag_z                     = ProtoField.uint16("mydns.flags.z", "World War Z - Reserved for future use", base.HEX, nil, 0x0040, "when is it the future?")
184local pf_flag_authenticated         = ProtoField.bool("mydns.flags.authenticated", "Authenticated", 16, {"yes","no"}, 0x0020, "did the server DNSSEC authenticate?")
185local pf_flag_checking_disabled     = ProtoField.bool("mydns.flags.checking_disabled", "Checking disabled", 16, nil, 0x0010)
186
187-- no, these aren't all the DNS response codes - this is just an example
188local rcodes = {
189        [0] = "No Error",
190        [1] = "Format Error",
191        [2] = "Server Failure",
192        [3] = "Non-Existent Domain",
193        [9] = "Server Not Authoritative for zone"
194}
195-- the above rcodes table is used in this next ProtoField
196local pf_flag_rcode         = ProtoField.uint16("mydns.flags.rcode", "Response code", base.DEC, rcodes, 0x000F)
197local pf_query              = ProtoField.new("Query", "mydns.query", ftypes.BYTES)
198local pf_query_name         = ProtoField.new("Name", "mydns.query.name", ftypes.STRING)
199local pf_query_name_len     = ProtoField.new("Name Length", "mydns.query.name.len", ftypes.UINT8)
200local pf_query_label_count  = ProtoField.new("Label Count", "mydns.query.label.count", ftypes.UINT8)
201local rrtypes = { [1] = "A (IPv4 host address)", [2] = "NS (authoritative name server)", [28] = "AAAA (for geeks only)" }
202local pf_query_type         = ProtoField.uint16("mydns.query.type", "Type", base.DEC, rrtypes)
203-- again, not all class types are listed here
204local classes = {
205        [0] = "Reserved",
206        [1] = "IN (Internet)",
207        [2] = "The 1%",
208        [5] = "First class",
209        [6] = "Business class",
210        [65535] = "Cattle class"
211}
212local pf_query_class        = ProtoField.uint16("mydns.query.class", "Class", base.DEC, classes, nil, "keep it classy folks")
213
214
215testing(OTHER,"Proto functions")
216
217----------------------------------------
218-- this actually registers the ProtoFields above, into our new Protocol
219-- in a real script I wouldn't do it this way; I'd build a table of fields programaticaly
220-- and then set dns.fields to it, so as to avoid forgetting a field
221local myfields = { pf_trasaction_id, pf_flags,
222    pf_num_questions, pf_num_answers, pf_num_authority_rr, pf_num_additional_rr,
223    pf_flag_response, pf_flag_opcode, pf_flag_authoritative,
224    pf_flag_truncated, pf_flag_recursion_desired, pf_flag_recursion_available,
225    pf_flag_z, pf_flag_authenticated, pf_flag_checking_disabled, pf_flag_rcode,
226    pf_query, pf_query_name, pf_query_name_len, pf_query_label_count, pf_query_type, pf_query_class }
227
228--dns.fields = myfields
229test("Proto.fields-set", pcall(setValue,dns,"fields",myfields))
230test("Proto.fields-get", pcall(getValue,dns,"fields"))
231test("Proto.fields-get", #dns.fields == #myfields)
232
233local pf_foo = ProtoField.uint16("myfoo.com", "Fooishly", base.DEC, rcodes, 0x000F)
234
235local foo = Proto("myfoo","MyFOO Protocol")
236local bar = Proto("mybar","MyBAR Protocol")
237
238test("Proto.fields-set", pcall(setValue,foo,"fields",pf_foo))
239test("Proto.fields-get", #foo.fields == 1)
240test("Proto.fields-get", foo.fields[1] == pf_foo)
241
242test("Proto.fields-set", not pcall(setValue,bar,"fields","howdy"))
243test("Proto.fields-set", not pcall(setValue,bar,"fields",nil))
244test("Proto.fields-get", #bar.fields == 0)
245
246test("Proto.name-get", foo.name == "MYFOO")
247test("Proto.name-set", not pcall(setValue,foo,"name","howdy"))
248
249test("Proto.description-get", foo.description == "MyFOO Protocol")
250test("Proto.description-set", not pcall(setValue,foo,"description","howdy"))
251
252test("Proto.prefs-get", typeof(foo.prefs) == "Prefs")
253test("Proto.prefs-set", not pcall(setValue,foo,"prefs","howdy"))
254
255local function dummy()
256    setFailed(OTHER)
257    error("dummy function called!")
258    return
259end
260
261-- can't get this because we haven't set it yet
262test("Proto.dissector-get", not pcall(getValue,foo,"dissector"))
263-- now set it
264test("Proto.dissector-set", pcall(setValue,foo,"dissector",dummy))
265test("Proto.dissector-set", not pcall(setValue,foo,"dissector","howdy"))
266test("Proto.dissector-get", pcall(getValue,foo,"dissector"))
267
268test("Proto.prefs_changed-set", pcall(setValue,foo,"prefs_changed",dummy))
269test("Proto.prefs_changed-get", not pcall(getValue,foo,"prefs_changed"))
270test("Proto.prefs_changed-set", not pcall(setValue,foo,"prefs_changed","howdy"))
271
272local function dummy_init()
273    --orig_test(OTHER,"Proto.init-called",true)
274    return
275end
276
277test("Proto.init-set", pcall(setValue,foo,"init",dummy_init))
278test("Proto.init-set", pcall(setValue,bar,"init",dummy_init))
279
280test("Proto.init-get", not pcall(getValue,foo,"init"))
281test("Proto.init-set", not pcall(setValue,foo,"init","howdy"))
282
283local numinits = 0
284function dns.init()
285    numinits = numinits + 1
286    if numinits == 2 then
287        getResults()
288    end
289end
290
291----------------------------------------
292-- create some expert info fields
293local ef_query     = ProtoExpert.new("mydns.query.expert", "DNS query message",
294                                     expert.group.REQUEST_CODE, expert.severity.CHAT)
295local ef_response  = ProtoExpert.new("mydns.response.expert", "DNS response message",
296                                     expert.group.RESPONSE_CODE, expert.severity.CHAT)
297local ef_ultimate  = ProtoExpert.new("mydns.response.ultimate.expert", "DNS answer to life, the universe, and everything",
298                                     expert.group.COMMENTS_GROUP, expert.severity.NOTE)
299-- some error expert info's
300local ef_too_short = ProtoExpert.new("mydns.too_short.expert", "DNS message too short",
301                                     expert.group.MALFORMED, expert.severity.ERROR)
302local ef_bad_query = ProtoExpert.new("mydns.query.missing.expert", "DNS query missing or malformed",
303                                     expert.group.MALFORMED, expert.severity.WARN)
304
305-- register them
306dns.experts = { ef_query, ef_too_short, ef_bad_query, ef_response, ef_ultimate }
307
308
309----------------------------------------
310-- we don't just want to display our protocol's fields, we want to access the value of some of them too!
311-- There are several ways to do that.  One is to just parse the buffer contents in Lua code to find
312-- the values.  But since ProtoFields actually do the parsing for us, and can be retrieved using Field
313-- objects, it's kinda cool to do it that way. So let's create some Fields to extract the values.
314-- The following creates the Field objects, but they're not 'registered' until after this script is loaded.
315-- Also, these lines can't be before the 'dns.fields = ...' line above, because the Field.new() here is
316-- referencing fields we're creating, and they're not "created" until that line above.
317-- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function.
318-- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created).
319local questions_field       = Field.new("mydns.num_questions")
320local query_type_field      = Field.new("mydns.query.type")
321local query_class_field     = Field.new("mydns.query.class")
322local response_field        = Field.new("mydns.flags.response")
323
324-- here's a little helper function to access the response_field value later.
325-- Like any Field retrieval, you can't retrieve a field's value until its value has been
326-- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls.
327-- So this isResponse() function can't be used until after the pf_flag_response ProtoField
328-- has been used inside the dissector.
329-- Note that calling the Field object returns a FieldInfo object, and calling that
330-- returns the value of the field - in this case a boolean true/false, since we set the
331-- "mydns.flags.response" ProtoField to ftype.BOOLEAN way earlier when we created the
332-- pf_flag_response ProtoField.  Clear as mud?
333--
334-- A shorter version of this function would be:
335-- local function isResponse() return response_field()() end
336-- but I though the below is easier to understand.
337local function isResponse()
338    local response_fieldinfo = response_field()
339    return response_fieldinfo()
340end
341
342
343----------------------------------------
344---- some constants for later use ----
345-- the DNS header size
346local DNS_HDR_LEN = 12
347
348-- the smallest possible DNS query field size
349-- has to be at least a label length octet, label character, label null terminator, 2-bytes type and 2-bytes class
350local MIN_QUERY_LEN = 7
351
352-- the UDP port number we want to associate with our protocol
353local MYDNS_PROTO_UDP_PORT = 65333
354
355----------------------------------------
356-- some forward "declarations" of helper functions we use in the dissector
357-- I don't usually use this trick, but it'll help reading/grok'ing this script I think
358-- if we don't focus on them.
359local byteArray2String, getQueryName
360
361
362----------------------------------------
363-- The following creates the callback function for the dissector.
364-- It's the same as doing "dns.dissector = function (tvbuf,pkt,root)"
365-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
366-- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
367-- this function and pass it these arguments for the packet it's dissecting.
368function dns.dissector(tvbuf,pktinfo,root)
369
370    incPktCount(FRAME)
371
372    -- We want to check that the packet size is rational during dissection, so let's get the length of the
373    -- packet buffer (Tvb).
374    -- Because DNS has no additonal payload data other than itself, and it rides on UDP without padding,
375    -- we can use tvb:len() or tvb:reported_len() here; but I prefer tvb:reported_length_remaining() as it's safer.
376    local pktlen = tvbuf:reported_length_remaining()
377
378    -- We start by adding our protocol to the dissection display tree.
379    -- A call to tree:add() returns the child created, so we can add more "under" it using that return value.
380    -- The second argument is how much of the buffer/packet this added tree item covers/represents - in this
381    -- case (DNS protocol) that's the remainder of the packet.
382    local tree = root:add(dns, tvbuf:range(0,pktlen))
383
384    -- now let's check it's not too short
385    if pktlen < DNS_HDR_LEN then
386        -- since we're going to add this protocol to a specific UDP port, we're going to
387        -- assume packets in this port are our protocol, so the packet being too short is an error
388        tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short")
389        return
390    end
391
392    -- Now let's add our transaction id under our dns protocol tree we just created.
393    -- The transaction id starts at offset 0, for 2 bytes length.
394    tree:add(pf_trasaction_id, tvbuf:range(0,2))
395
396    -- We'd like to put the transaction id number in the GUI row for this packet, in its
397    -- INFO column/cell.  Firt we need the transaction id value, though.  Since we just
398    -- dissected it with the previous code line, we could now get it using a Field's
399    -- FieldInfo extractor, but instead we'll get it directly from the TvbRange just
400    -- to show how to do that.  We'll use Field/FieldInfo extractors later on...
401    local transid = tvbuf:range(0,2):uint()
402    pktinfo.cols.info:set("(".. transid ..")")
403
404    -- now let's add the flags, which are all in the packet bytes at offset 2 of length 2
405    -- instead of calling this again and again, let's just use a variable
406    local flagrange = tvbuf:range(2,2)
407
408    -- for our flags field, we want a sub-tree
409    local flag_tree = tree:add(pf_flags, flagrange)
410        -- I'm indenting this for calarity, because it's adding to the flag's child-tree
411        -- let's add the type of message (query vs. response)
412        local query_flag_tree = flag_tree:add(pf_flag_response, flagrange)
413
414        -- let's also add an expert info about it
415        if isResponse() then
416            query_flag_tree:add_proto_expert_info(ef_response, "It's a response!")
417            if transid == 42 then
418                tree:add_tvb_expert_info(ef_ultimate, tvbuf:range(0,2))
419            end
420        else
421            query_flag_tree:add_proto_expert_info(ef_query)
422        end
423
424        -- we now know if it's a response or query, so let's put that in the
425        -- GUI packet row, in the INFO column cell
426        -- this line of code uses a Lua trick for doing something similar to
427        -- the C/C++ 'test ? true : false' shorthand
428        pktinfo.cols.info:prepend(isResponse() and "Response " or "Query ")
429
430        flag_tree:add(pf_flag_opcode, flagrange)
431
432        if isResponse() then
433            flag_tree:add(pf_flag_authoritative, flagrange)
434        end
435
436        flag_tree:add(pf_flag_truncated, flagrange)
437
438        if isResponse() then
439            flag_tree:add(pf_flag_recursion_available, flagrange)
440        else
441            flag_tree:add(pf_flag_recursion_desired, flagrange)
442        end
443
444        flag_tree:add(pf_flag_z, flagrange)
445
446        if isResponse() then
447            flag_tree:add(pf_flag_authenticated, flagrange)
448            flag_tree:add(pf_flag_rcode, flagrange)
449        end
450
451        flag_tree:add(pf_flag_checking_disabled, flagrange)
452
453    -- now add more to the main mydns tree
454    tree:add(pf_num_questions, tvbuf:range(4,2))
455    tree:add(pf_num_answers, tvbuf:range(6,2))
456    -- another way to get a TvbRange is just to call the Tvb like this
457    tree:add(pf_num_authority_rr, tvbuf(8,2))
458    -- or if we're crazy, we can create a sub-TvbRange, from a sub-TvbRange of the TvbRange
459    tree:add(pf_num_additional_rr, tvbuf:range(10,2):range()())
460
461    local num_queries = questions_field()()
462    local pos = DNS_HDR_LEN
463
464    if num_queries > 0 then
465        -- let's create a sub-tree, using a plain text description (not a field from the packet)
466        local queries_tree = tree:add("Queries")
467
468        local pktlen_remaining = pktlen - pos
469
470        while num_queries > 0 and pktlen_remaining > 0 do
471            if pktlen_remaining < MIN_QUERY_LEN then
472                queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, "query field missing or too short")
473                return
474            end
475
476            -- we don't know how long this query field in total is, so we have to parse it first before
477            -- adding it to the tree, because we want to identify the correct bytes it covers
478            local label_count, name, name_len = getQueryName(tvbuf:range(pos,pktlen_remaining))
479            if not label_count then
480                q_tree:add_expert_info(PI_MALFORMED, PI_ERROR, name)
481                return
482            end
483
484            -- now add the first query to the 'Queries' child tree we just created
485            -- we're going to change the string generated by this later, after we figure out the subsequent fields.
486            -- the whole query field is the query name field length we just got, plus the 20byte type and 2-byte class
487            local q_tree = queries_tree:add(pf_query, tvbuf:range(pos, name_len + 4))
488
489            q_tree:add(pf_query_name, tvbuf:range(pos, name_len), name)
490            pos = pos + name_len
491
492            pktinfo.cols.info:append(" "..name)
493
494            -- the following tree items are generated by us, not encoded in the packet per se, so mark them as such
495            q_tree:add(pf_query_name_len, name_len):set_generated()
496            q_tree:add(pf_query_label_count, label_count):set_generated()
497
498            q_tree:add(pf_query_type, tvbuf:range(pos, 2))
499            q_tree:add(pf_query_class, tvbuf:range(pos + 2, 2))
500            pos = pos + 4
501
502            -- now change the query text
503            q_tree:set_text(name..": type "..query_type_field().display ..", class "..query_class_field().display)
504
505            pktlen_remaining = pktlen_remaining - (name_len + 4)
506            num_queries = num_queries - 1
507        end  -- end of while loop
508
509        if num_queries > 0 then
510            -- we didn't process them all
511            queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, num_queries .. " query field(s) missing")
512            return
513        end
514    end
515
516    setPassed(FRAME)
517
518    -- tell wireshark how much of tvbuff we dissected
519    return pos
520end
521
522----------------------------------------
523-- we want to have our protocol disseciton invoked for a specific UDP port,
524-- so get the udp dissecotr table and add our protocol to it
525local udp_encap_table = DissectorTable.get("udp.port")
526udp_encap_table:add(MYDNS_PROTO_UDP_PORT, dns)
527
528----------------------------------------
529-- we also want to add the heuristic dissector, for any UDP protocol
530-- first we need a heuristic dissection function
531-- this is that function - when wireshark invokes this, it will pass in the same
532-- things it passes in to the "dissector" function, but we only want to actually
533-- dissect it if it's for us, and we need to return true if it's for us, or else false
534-- figuring out if it's for us or not is not easy
535-- we need to try as hard as possible, or else we'll think it's for us when it's
536-- not and block other heuristic dissectors from getting their chanc
537--
538-- in practice, you'd never set a dissector like this to be heuristic, because there
539-- just isn't enough information to safely detect if it's DNS or not
540-- but I'm doing it to show how it would be done
541--
542-- Note: this heuristic stuff is new in 1.11.3
543local function heur_dissect_dns(tvbuf,pktinfo,root)
544
545    if tvbuf:len() < DNS_HDR_LEN then
546        return false
547    end
548
549    local tvbr = tvbuf:range(0,DNS_HDR_LEN)
550
551    -- the first 2 bytes are tansaction id, which can be anything so no point in checking those
552    -- the next 2 bytes contain flags, a couple of which have some values we can check against
553
554    -- the opcode has to be 0, 1, 2, 4 or 5
555    -- the opcode field starts at bit offset 17 (in C-indexing), for 4 bits in length
556    local check = tvbr:bitfield(17,4)
557    if check == 3 or check > 5 then
558        return false
559    end
560
561    -- the rcode has to be 0-10, 16-22 (we're ignoring private use rcodes here)
562    -- the rcode field starts at bit offset 28 (in C-indexing), for 4 bits in length
563    check = tvbr:bitfield(28,4)
564    if check > 22 or (check > 10 and check < 16) then
565        return false
566    end
567
568    -- now let's verify the number of questions/answers are reasonable
569    check = tvbr:range(4,2):uint()  -- num questions
570    if check > 100 then return false end
571    check = tvbr:range(6,2):uint()  -- num answers
572    if check > 100 then return false end
573    check = tvbr:range(8,2):uint()  -- num authority
574    if check > 100 then return false end
575    check = tvbr:range(10,2):uint()  -- num additional
576    if check > 100 then return false end
577
578    -- don't do this line in your script - I'm just doing it so our testsuite can
579    -- verify this script
580    root:add("Heuristic dissector used"):set_generated()
581
582    -- ok, looks like it's ours, so go dissect it
583    -- note: calling the dissector directly like this is new in 1.11.3
584    -- also note that calling a Dissector objkect, as this does, means we don't
585    -- get back the return value of the dissector function we created previously
586    -- so it might be better to just call the function directly instead of doing
587    -- this, but this script is used for testing and this tests the call() function
588    dns.dissector(tvbuf,pktinfo,root)
589
590    -- since this is over a transport protocol, such as UDP, we can set the
591    -- conversation to make it sticky for our dissector, so that all future
592    -- packets to/from the same address:port pair will just call our dissector
593    -- function directly instead of this heuristic function
594    -- this is a new attribute of pinfo in 1.11.3
595    pktinfo.conversation = dns
596
597    return true
598end
599
600-- now register that heuristic dissector into the udp heuristic list
601dns:register_heuristic("udp",heur_dissect_dns)
602
603-- We're done!
604-- our protocol (Proto) gets automatically registered after this script finishes loading
605----------------------------------------
606
607----------------------------------------
608-- a helper function used later
609-- note that it doesn't use "local" because it's already been declared as a local
610-- variable way earlier in this script (as a form of forward declaration)
611byteArray2String = function (barray, begin, length)
612    local word = {}
613    for i = 1, length do
614        word[i] = string.char(barray:get_index(begin))
615        begin = begin + 1
616    end
617    return table.concat(word)
618end
619
620----------------------------------------
621-- DNS query names are not just null-terminated strings; they're actually a sequence of
622-- 'labels', with a length octet before each one.  So "foobar.com" is actually the
623-- string "\06foobar\03com\00".  We could create a ProtoField for label_length and label_name
624-- or whatever, but since this is an example script I'll show how to do it in raw code.
625-- This function is given the TvbRange object from the dissector() function, and needs to
626-- parse it.
627-- On success, it returns three things: the number of labels, the name string, and how
628-- many bytes it covered of the buffer (which is always 2 more than the name length in this case).
629-- On failure, it returns nil and the error message.
630getQueryName = function (tvbr)
631    local label_count = 0
632    local name = ""
633
634    local len_remaining = tvbr:len()
635    if len_remaining < 2 then
636        -- it's too short
637        return nil, "invalid name"
638    end
639
640    local barray = tvbr:bytes() -- gets a ByteArray of the TvbRange
641    local pos = 0 -- unlike Lua, ByteArray uses 0-based indexing
642
643    -- get the first octet/label-length
644    local label_len = barray:get_index(pos)
645    if label_len == 0 then
646        return nil, "invalid initial label length of 0"
647    end
648
649    while label_len > 0 do
650        if label_len >= len_remaining then
651            return nil, "invalid label length of "..label_len
652        end
653        pos = pos + 1  -- move past label length octet
654        -- sadly, there's no current way to get a raw Lua string from a ByteArray (nor from Tvb for that matter)
655        -- so we need to do it one character at a a time
656        -- append the label and a dot to name string
657        name = name .. byteArray2String(barray, pos, label_len) .. "."
658        len_remaining = len_remaining - (label_len + 1) -- subtract label and its length octet
659        label_count = label_count + 1
660        pos = pos + label_len -- move past label
661        label_len = barray:get_index(pos)
662    end
663
664    -- we appended an extra dot, so get rid of it
665    name = name:sub(1, -2)
666
667    return label_count, name, name:len() + 2
668end
669
670