1 ----------------------------------------
2 -- script-name: dns_dissector.lua
4 -- author: Hadriel Kaplan <hadrielk at yahoo dot com>
5 -- Copyright (c) 2014, Hadriel Kaplan
6 -- This code is in the Public Domain, or the BSD (3 clause) license if Public Domain does not apply
12 -- * fixed a bug with default settings
13 -- * added ability for command-line to overide defaults
16 -- * made it use the new ProtoExpert class model for expert info
17 -- * add a protocol column with the proto name
18 -- * added heuristic dissector support
19 -- * added preferences settings
20 -- * removed byteArray2String(), and uses the new ByteArray:raw() method instead
23 -- This is an example Lua script for a protocol dissector. The purpose of this script is two-fold:
24 -- * To provide a reference tutorial for others writing Wireshark dissectors in Lua
25 -- * To test various functions being called in various ways, so this script can be used in the test-suites
26 -- I've tried to meet both of those goals, but it wasn't easy. No doubt some folks will wonder why some
27 -- functions are called some way, or differently than previous invocations of the same function. I'm trying to
28 -- to show both that it can be done numerous ways, but also I'm trying to test those numerous ways, and my more
29 -- immediate need is for test coverage rather than tutorial guide. (the Lua API is sorely lacking in test scripts)
32 -- This script creates an elementary dissector for DNS. It's neither comprehensive nor error-free with regards
33 -- to the DNS protocol. That's OK. The goal isn't to fully dissect DNS properly - Wireshark already has a good
34 -- DNS dissector built-in. We don't need another one. We also have other example Lua scripts, but I don't think
35 -- they do a good job of explaining things, and the nice thing about this one is getting capture files to
36 -- run it against is trivial. (plus I uploaded one)
38 -- HOW TO RUN THIS SCRIPT:
39 -- Wireshark and Tshark support multiple ways of loading Lua scripts: through a dofile() call in init.lua,
40 -- through the file being in either the global or personal plugins directories, or via the command line.
41 -- See the Wireshark User's Guide chapter on Lua (https://www.wireshark.org/docs/wsdg_html_chunked/wsluarm_modules.html).
42 -- Once the script is loaded, it creates a new protocol named "MyDNS" (or "MYDNS" in some places). If you have
43 -- a capture file with DNS packets in it, simply select one in the Packet List pane, right-click on it, and
44 -- select "Decode As ...", and then in the dialog box that shows up scroll down the list of protocols to one
45 -- called "MYDNS", select that and click the "ok" or "apply" button. Voila`, you're now decoding DNS packets
46 -- using the simplistic dissector in this script. Another way is to download the capture file made for
47 -- this script, and open that - since the DNS packets in it use UDP port 65333 (instead of the default 53),
48 -- and since the MyDNS protocol in this script has been set to automatically decode UDP port 65333, it will
49 -- automagically do it without doing "Decode As ...".
51 ----------------------------------------
52 -- do not modify this table
59 -- set this DEBUG to debug_level.LEVEL_1 to enable printing debug_level info
60 -- set it to debug_level.LEVEL_2 to enable really verbose printing
61 -- note: this will be overridden by user's preference settings
62 local DEBUG
= debug_level
.LEVEL_1
64 local default_settings
=
72 -- for testing purposes, we want to be able to pass in changes to the defaults
73 -- from the command line; because you can't set lua preferences from the command
74 -- line using the '-o' switch (the preferences don't exist until this script is
75 -- loaded, so the command line thinks they're invalid preferences being set)
76 -- so we pass them in as command arguments insetad, and handle it here:
77 local args
={...} -- get passed-in args
78 if args
and #args
> 0 then
79 for _
, arg
in ipairs(args
) do
80 local name
, value
= arg
:match("(.+)=(.+)")
81 if name
and value
then
82 if tonumber(value
) then
83 value
= tonumber(value
)
84 elseif value
== "true" or value
== "TRUE" then
86 elseif value
== "false" or value
== "FALSE" then
88 elseif value
== "DISABLED" then
89 value
= debug_level
.DISABLED
90 elseif value
== "LEVEL_1" then
91 value
= debug_level
.LEVEL_1
92 elseif value
== "LEVEL_2" then
93 value
= debug_level
.LEVEL_2
95 error("invalid commandline argument value")
98 error("invalid commandline argument syntax")
101 default_settings
[name
] = value
105 local dprint
= function() end
106 local dprint2
= function() end
107 local function reset_debug_level()
108 if default_settings
.debug_level
> debug_level
.DISABLED
then
109 dprint
= function(...)
110 print(table.concat({"Lua:", ...}," "))
113 if default_settings
.debug_level
> debug_level
.LEVEL_1
then
121 dprint2("Wireshark version = ", get_version())
122 dprint2("Lua version = ", _VERSION
)
124 ----------------------------------------
125 -- Unfortunately, the older Wireshark/Tshark versions have bugs, and part of the point
126 -- of this script is to test those bugs are now fixed. So we need to check the version
127 -- end error out if it's too old.
128 local major
, minor
, micro
= get_version():match("(%d+)%.(%d+)%.(%d+)")
129 if major
and tonumber(major
) <= 1 and ((tonumber(minor
) <= 10) or (tonumber(minor
) == 11 and tonumber(micro
) < 3)) then
130 error( "Sorry, but your Wireshark/Tshark version ("..get_version()..") is too old for this script!\n"..
131 "This script needs Wireshark/Tshark version 1.11.3 or higher.\n" )
134 -- more sanity checking
135 -- verify we have the ProtoExpert class in wireshark, as that's the newest thing this file uses
136 assert(ProtoExpert
.new
, "Wireshark does not have the ProtoExpert class, so it's too old - get the latest 1.11.3 or higher")
138 ----------------------------------------
141 ----------------------------------------
142 -- creates a Proto object, but doesn't register it yet
143 local dns
= Proto("mydns","MyDNS Protocol")
145 ----------------------------------------
146 -- multiple ways to do the same thing: create a protocol field (but not register it yet)
147 -- the abbreviation should always have "<myproto>." before the specific abbreviation, to avoid collisions
148 local pf_trasaction_id
= ProtoField
.new ("Transaction ID", "mydns.trans_id", ftypes
.UINT16
)
149 local pf_flags
= ProtoField
.new ("Flags", "mydns.flags", ftypes
.UINT16
, nil, base
.HEX
)
150 local pf_num_questions
= ProtoField
.uint16("mydns.num_questions", "Number of Questions")
151 local pf_num_answers
= ProtoField
.uint16("mydns.num_answers", "Number of Answer RRs")
152 local pf_num_authority_rr
= ProtoField
.uint16("mydns.num_authority_rr", "Number of Authority RRs")
153 local pf_num_additional_rr
= ProtoField
.uint16("mydns.num_additional_rr", "Number of Additional RRs")
155 -- within the flags field, we want to parse/show the bits separately
156 -- note the "base" argument becomes the size of the bitmask'ed field when ftypes.BOOLEAN is used
157 -- 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)
158 -- again the following shows different ways of doing the same thing basically
159 local 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?")
160 local pf_flag_opcode
= ProtoField
.new ("Opcode", "mydns.flags.opcode", ftypes
.UINT16
, nil, base
.DEC
, 0x7800, "operation code")
161 local pf_flag_authoritative
= ProtoField
.new ("Authoritative", "mydns.flags.authoritative", ftypes
.BOOLEAN
, nil, 16, 0x0400, "is the response authoritative?")
162 local pf_flag_truncated
= ProtoField
.bool ("mydns.flags.truncated", "Truncated", 16, nil, 0x0200, "is the message truncated?")
163 local pf_flag_recursion_desired
= ProtoField
.bool ("mydns.flags.recursion_desired", "Recursion desired", 16, {"yes","no"}, 0x0100, "do the query recursivley?")
164 local pf_flag_recursion_available
= ProtoField
.bool ("mydns.flags.recursion_available", "Recursion available", 16, nil, 0x0080, "does the server support recursion?")
165 local pf_flag_z
= ProtoField
.uint16("mydns.flags.z", "World War Z - Reserved for future use", base
.HEX
, nil, 0x0040, "when is it the future?")
166 local pf_flag_authenticated
= ProtoField
.bool ("mydns.flags.authenticated", "Authenticated", 16, {"yes","no"}, 0x0020, "did the server DNSSEC authenticate?")
167 local pf_flag_checking_disabled
= ProtoField
.bool ("mydns.flags.checking_disabled", "Checking disabled", 16, nil, 0x0010)
169 -- no, these aren't all the DNS response codes - this is just an example
172 [1] = "Format Error",
173 [2] = "Server Failure",
174 [3] = "Non-Existent Domain",
175 [9] = "Server Not Authoritative for zone"
177 -- the above rcodes table is used in this next ProtoField
178 local pf_flag_rcode
= ProtoField
.uint16("mydns.flags.rcode", "Response code", base
.DEC
, rcodes
, 0x000F)
179 local pf_query
= ProtoField
.new("Query", "mydns.query", ftypes
.BYTES
)
180 local pf_query_name
= ProtoField
.new("Name", "mydns.query.name", ftypes
.STRING
)
181 local pf_query_name_len
= ProtoField
.new("Name Length", "mydns.query.name.len", ftypes
.UINT8
)
182 local pf_query_label_count
= ProtoField
.new("Label Count", "mydns.query.label.count", ftypes
.UINT8
)
183 local rrtypes
= { [1] = "A (IPv4 host address)", [2] = "NS (authoritative name server)", [28] = "AAAA (for geeks only)" }
184 local pf_query_type
= ProtoField
.uint16("mydns.query.type", "Type", base
.DEC
, rrtypes
)
185 -- again, not all class types are listed here
188 [1] = "IN (Internet)",
191 [6] = "Business class",
192 [65535] = "Cattle class"
194 local pf_query_class
= ProtoField
.uint16("mydns.query.class", "Class", base
.DEC
, classes
, nil, "keep it classy folks")
196 ----------------------------------------
197 -- this actually registers the ProtoFields above, into our new Protocol
198 -- in a real script I wouldn't do it this way; I'd build a table of fields programmatically
199 -- and then set dns.fields to it, so as to avoid forgetting a field
200 dns
.fields
= { pf_trasaction_id
, pf_flags
,
201 pf_num_questions
, pf_num_answers
, pf_num_authority_rr
, pf_num_additional_rr
,
202 pf_flag_response
, pf_flag_opcode
, pf_flag_authoritative
,
203 pf_flag_truncated
, pf_flag_recursion_desired
, pf_flag_recursion_available
,
204 pf_flag_z
, pf_flag_authenticated
, pf_flag_checking_disabled
, pf_flag_rcode
,
205 pf_query
, pf_query_name
, pf_query_name_len
, pf_query_label_count
, pf_query_type
, pf_query_class
}
207 ----------------------------------------
208 -- create some expert info fields (this is new functionality in 1.11.3)
209 -- Expert info fields are very similar to proto fields: they're tied to our protocol,
210 -- they're created in a similar way, and registered by setting a 'experts' field to
211 -- a table of them just as proto fields were put into the 'dns.fields' above
212 -- The old way of creating expert info was to just add it to the tree, but that
213 -- didn't let the expert info be filterable in wireshark, whereas this way does
214 local ef_query
= ProtoExpert
.new("mydns.query.expert", "DNS query message",
215 expert
.group
.REQUEST_CODE
, expert
.severity
.CHAT
)
216 local ef_response
= ProtoExpert
.new("mydns.response.expert", "DNS response message",
217 expert
.group
.RESPONSE_CODE
, expert
.severity
.CHAT
)
218 local ef_ultimate
= ProtoExpert
.new("mydns.response.ultimate.expert", "DNS answer to life, the universe, and everything",
219 expert
.group
.COMMENTS_GROUP
, expert
.severity
.NOTE
)
220 -- some error expert info's
221 local ef_too_short
= ProtoExpert
.new("mydns.too_short.expert", "DNS message too short",
222 expert
.group
.MALFORMED
, expert
.severity
.ERROR
)
223 local ef_bad_query
= ProtoExpert
.new("mydns.query.missing.expert", "DNS query missing or malformed",
224 expert
.group
.MALFORMED
, expert
.severity
.WARN
)
227 dns
.experts
= { ef_query
, ef_too_short
, ef_bad_query
, ef_response
, ef_ultimate
}
229 ----------------------------------------
230 -- we don't just want to display our protocol's fields, we want to access the value of some of them too!
231 -- There are several ways to do that. One is to just parse the buffer contents in Lua code to find
232 -- the values. But since ProtoFields actually do the parsing for us, and can be retrieved using Field
233 -- objects, it's kinda cool to do it that way. So let's create some Fields to extract the values.
234 -- The following creates the Field objects, but they're not 'registered' until after this script is loaded.
235 -- Also, these lines can't be before the 'dns.fields = ...' line above, because the Field.new() here is
236 -- referencing fields we're creating, and they're not "created" until that line above.
237 -- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function.
238 -- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created).
239 local questions_field
= Field
.new("mydns.num_questions")
240 local query_type_field
= Field
.new("mydns.query.type")
241 local query_class_field
= Field
.new("mydns.query.class")
242 local response_field
= Field
.new("mydns.flags.response")
244 -- here's a little helper function to access the response_field value later.
245 -- Like any Field retrieval, you can't retrieve a field's value until its value has been
246 -- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls.
247 -- So this isResponse() function can't be used until after the pf_flag_response ProtoField
248 -- has been used inside the dissector.
249 -- Note that calling the Field object returns a FieldInfo object, and calling that
250 -- returns the value of the field - in this case a boolean true/false, since we set the
251 -- "mydns.flags.response" ProtoField to ftype.BOOLEAN way earlier when we created the
252 -- pf_flag_response ProtoField. Clear as mud?
254 -- A shorter version of this function would be:
255 -- local function isResponse() return response_field()() end
256 -- but I though the below is easier to understand.
257 local function isResponse()
258 local response_fieldinfo
= response_field()
259 return response_fieldinfo()
262 --------------------------------------------------------------------------------
263 -- preferences handling stuff
264 --------------------------------------------------------------------------------
266 -- a "enum" table for our enum pref, as required by Pref.enum()
267 -- having the "index" number makes ZERO sense, and is completely illogical
268 -- but it's what the code has expected it to be for a long time. Ugh.
269 local debug_pref_enum
= {
270 { 1, "Disabled", debug_level
.DISABLED
},
271 { 2, "Level 1", debug_level
.LEVEL_1
},
272 { 3, "Level 2", debug_level
.LEVEL_2
},
275 dns
.prefs
.debug
= Pref
.enum("Debug", default_settings
.debug_level
,
276 "The debug printing level", debug_pref_enum
)
278 dns
.prefs
.port
= Pref
.uint("Port number", default_settings
.port
,
279 "The UDP port number for MyDNS")
281 dns
.prefs
.heur
= Pref
.bool("Heuristic enabled", default_settings
.heur_enabled
,
282 "Whether heuristic dissection is enabled or not")
284 ----------------------------------------
285 -- a function for handling prefs being changed
286 function dns
.prefs_changed()
287 dprint2("prefs_changed called")
289 default_settings
.debug_level
= dns
.prefs
.debug
292 default_settings
.heur_enabled
= dns
.prefs
.heur
294 if default_settings
.port
~= dns
.prefs
.port
then
295 -- remove old one, if not 0
296 if default_settings
.port
~= 0 then
297 dprint2("removing MyDNS from port",default_settings
.port
)
298 DissectorTable
.get("udp.port"):remove(default_settings
.port
, dns
)
300 -- set our new default
301 default_settings
.port
= dns
.prefs
.port
302 -- add new one, if not 0
303 if default_settings
.port
~= 0 then
304 dprint2("adding MyDNS to port",default_settings
.port
)
305 DissectorTable
.get("udp.port"):add(default_settings
.port
, dns
)
311 dprint2("MyDNS Prefs registered")
314 ----------------------------------------
315 ---- some constants for later use ----
316 -- the DNS header size
317 local DNS_HDR_LEN
= 12
319 -- the smallest possible DNS query field size
320 -- has to be at least a label null terminator, 2-bytes type and 2-bytes class
321 local MIN_QUERY_LEN
= 5
323 ----------------------------------------
324 -- some forward "declarations" of helper functions we use in the dissector
325 -- I don't usually use this trick, but it'll help reading/grok'ing this script I think
326 -- if we don't focus on them.
330 ----------------------------------------
331 -- The following creates the callback function for the dissector.
332 -- It's the same as doing "dns.dissector = function (tvbuf,pkt,root)"
333 -- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
334 -- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
335 -- this function and pass it these arguments for the packet it's dissecting.
336 function dns
.dissector(tvbuf
,pktinfo
,root
)
337 dprint2("dns.dissector called")
339 -- set the protocol column to show our protocol name
340 pktinfo
.cols
.protocol
:set("MYDNS")
342 -- We want to check that the packet size is rational during dissection, so let's get the length of the
343 -- packet buffer (Tvb).
344 -- Because DNS has no additional payload data other than itself, and it rides on UDP without padding,
345 -- we can use tvb:len() or tvb:reported_len() here; but I prefer tvb:reported_length_remaining() as it's safer.
346 local pktlen
= tvbuf
:reported_length_remaining()
348 -- We start by adding our protocol to the dissection display tree.
349 -- A call to tree:add() returns the child created, so we can add more "under" it using that return value.
350 -- The second argument is how much of the buffer/packet this added tree item covers/represents - in this
351 -- case (DNS protocol) that's the remainder of the packet.
352 local tree
= root
:add(dns
, tvbuf
:range(0,pktlen
))
354 -- now let's check it's not too short
355 if pktlen
< DNS_HDR_LEN
then
356 -- since we're going to add this protocol to a specific UDP port, we're going to
357 -- assume packets in this port are our protocol, so the packet being too short is an error
358 -- the old way: tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short")
359 -- the correct way now:
360 tree
:add_proto_expert_info(ef_too_short
)
361 dprint("packet length",pktlen
,"too short")
365 -- Now let's add our transaction id under our dns protocol tree we just created.
366 -- The transaction id starts at offset 0, for 2 bytes length.
367 tree
:add(pf_trasaction_id
, tvbuf
:range(0,2))
369 -- We'd like to put the transaction id number in the GUI row for this packet, in its
370 -- INFO column/cell. First we need the transaction id value, though. Since we just
371 -- dissected it with the previous code line, we could now get it using a Field's
372 -- FieldInfo extractor, but instead we'll get it directly from the TvbRange just
373 -- to show how to do that. We'll use Field/FieldInfo extractors later on...
374 local transid
= tvbuf
:range(0,2):uint()
375 pktinfo
.cols
.info
:set("(".. transid
..")")
377 -- now let's add the flags, which are all in the packet bytes at offset 2 of length 2
378 -- instead of calling this again and again, let's just use a variable
379 local flagrange
= tvbuf
:range(2,2)
381 -- for our flags field, we want a sub-tree
382 local flag_tree
= tree
:add(pf_flags
, flagrange
)
383 -- I'm indenting this for clarity, because it's adding to the flag's child-tree
385 -- let's add the type of message (query vs. response)
386 local query_flag_tree
= flag_tree
:add(pf_flag_response
, flagrange
)
388 -- let's also add an expert info about it
390 query_flag_tree
:add_proto_expert_info(ef_response
, "It's a response!")
391 if transid
== 42 then
392 tree
:add_tvb_expert_info(ef_ultimate
, tvbuf
:range(0,2))
395 query_flag_tree
:add_proto_expert_info(ef_query
)
398 -- we now know if it's a response or query, so let's put that in the
399 -- GUI packet row, in the INFO column cell
400 -- this line of code uses a Lua trick for doing something similar to
401 -- the C/C++ 'test ? true : false' shorthand
402 pktinfo
.cols
.info
:prepend(isResponse() and "Response " or "Query ")
404 flag_tree
:add(pf_flag_opcode
, flagrange
)
407 flag_tree
:add(pf_flag_authoritative
, flagrange
)
410 flag_tree
:add(pf_flag_truncated
, flagrange
)
413 flag_tree
:add(pf_flag_recursion_available
, flagrange
)
415 flag_tree
:add(pf_flag_recursion_desired
, flagrange
)
418 flag_tree
:add(pf_flag_z
, flagrange
)
421 flag_tree
:add(pf_flag_authenticated
, flagrange
)
422 flag_tree
:add(pf_flag_rcode
, flagrange
)
425 flag_tree
:add(pf_flag_checking_disabled
, flagrange
)
427 -- now add more to the main mydns tree
428 tree
:add(pf_num_questions
, tvbuf
:range(4,2))
429 tree
:add(pf_num_answers
, tvbuf
:range(6,2))
430 -- another way to get a TvbRange is just to call the Tvb like this
431 tree
:add(pf_num_authority_rr
, tvbuf(8,2))
432 -- or if we're crazy, we can create a sub-TvbRange, from a sub-TvbRange of the TvbRange
433 tree
:add(pf_num_additional_rr
, tvbuf
:range(10,2):range()())
435 local num_queries
= questions_field()()
436 local pos
= DNS_HDR_LEN
438 if num_queries
> 0 then
439 -- let's create a sub-tree, using a plain text description (not a field from the packet)
440 local queries_tree
= tree
:add("Queries")
442 local pktlen_remaining
= pktlen
- pos
444 -- multiple questions in one query hasn't been used for a long time, but just in case, let's loop
445 while num_queries
> 0 and pktlen_remaining
> 0 do
446 if pktlen_remaining
< MIN_QUERY_LEN
then
447 -- old way: queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, "query field missing or too short")
448 queries_tree
:add_proto_expert_info(ef_bad_query
)
452 -- we don't know how long this query field in total is, so we have to parse it first before
453 -- adding it to the tree, because we want to identify the correct bytes it covers
454 local label_count
, name
, name_len
= getQueryName(tvbuf
:range(pos
,pktlen_remaining
))
455 if not label_count
then
456 queries_tree
:add_expert_info(PI_MALFORMED
, PI_ERROR
, name
)
460 -- now add the first query to the 'Queries' child tree we just created
461 -- we're going to change the string generated by this later, after we figure out the subsequent fields.
462 -- the whole query field is the query name field length we just got, plus 2-byte type and 2-byte class.
463 local q_tree
= queries_tree
:add(pf_query
, tvbuf
:range(pos
, name_len
+ 4))
465 q_tree
:add(pf_query_name
, tvbuf
:range(pos
, name_len
), name
)
468 pktinfo
.cols
.info
:append(" "..name
)
470 -- the following tree items are generated by us, not encoded in the packet per se, so mark them as such
471 q_tree
:add(pf_query_name_len
, name_len
):set_generated()
472 q_tree
:add(pf_query_label_count
, label_count
):set_generated()
474 q_tree
:add(pf_query_type
, tvbuf
:range(pos
, 2))
475 q_tree
:add(pf_query_class
, tvbuf
:range(pos
+ 2, 2))
478 -- now change the query text
479 -- calling a Field returns a multival of one FieldInfo object for
480 -- each value, so we select() only the most recent one
481 q_tree
:set_text(name
..": type "..select(-1, query_type_field()).display
482 ..", class "..select(-1, query_class_field()).display
)
484 pktlen_remaining
= pktlen_remaining
- (name_len
+ 4)
485 num_queries
= num_queries
- 1
486 end -- end of while loop
488 if num_queries
> 0 then
489 -- we didn't process them all
490 queries_tree
:add_expert_info(PI_MALFORMED
, PI_ERROR
, num_queries
.. " query field(s) missing")
495 -- parsing answers, authority RRs, and additional RRs is up to you!
497 dprint2("dns.dissector returning",pos
)
499 -- tell wireshark how much of tvbuff we dissected
503 ----------------------------------------
504 -- we want to have our protocol dissection invoked for a specific UDP port,
505 -- so get the udp dissector table and add our protocol to it
506 DissectorTable
.get("udp.port"):add(default_settings
.port
, dns
)
508 ----------------------------------------
509 -- we also want to add the heuristic dissector, for any UDP protocol
510 -- first we need a heuristic dissection function
511 -- this is that function - when wireshark invokes this, it will pass in the same
512 -- things it passes in to the "dissector" function, but we only want to actually
513 -- dissect it if it's for us, and we need to return true if it's for us, or else false
514 -- figuring out if it's for us or not is not easy
515 -- we need to try as hard as possible, or else we'll think it's for us when it's
516 -- not and block other heuristic dissectors from getting their chance
518 -- in practice, you'd never set a dissector like this to be heuristic, because there
519 -- just isn't enough information to safely detect if it's DNS or not
520 -- but I'm doing it to show how it would be done
522 -- Note: this heuristic stuff is new in 1.11.3
523 local function heur_dissect_dns(tvbuf
,pktinfo
,root
)
524 dprint2("heur_dissect_dns called")
526 -- if our preferences tell us not to do this, return false
527 if not default_settings
.heur_enabled
then
531 if tvbuf
:len() < DNS_HDR_LEN
then
532 dprint("heur_dissect_dns: tvb shorter than DNS_HDR_LEN of:",DNS_HDR_LEN
)
536 local tvbr
= tvbuf
:range(0,DNS_HDR_LEN
)
538 -- the first 2 bytes are transaction id, which can be anything so no point in checking those
539 -- the next 2 bytes contain flags, a couple of which have some values we can check against
541 -- the opcode has to be 0, 1, 2, 4 or 5
542 -- the opcode field starts at bit offset 17 (in C-indexing), for 4 bits in length
543 local check
= tvbr
:bitfield(17,4)
544 if check
== 3 or check
> 5 then
545 dprint("heur_dissect_dns: invalid opcode:",check
)
549 -- the rcode has to be 0-10, 16-22 (we're ignoring private use rcodes here)
550 -- the rcode field starts at bit offset 28 (in C-indexing), for 4 bits in length
551 check
= tvbr
:bitfield(28,4)
552 if check
> 22 or (check
> 10 and check
< 16) then
553 dprint("heur_dissect_dns: invalid rcode:",check
)
557 dprint2("heur_dissect_dns checking questions/answers")
559 -- now let's verify the number of questions/answers are reasonable
560 check
= tvbr
:range(4,2):uint() -- num questions
561 if check
> 100 then return false end
562 check
= tvbr
:range(6,2):uint() -- num answers
563 if check
> 100 then return false end
564 check
= tvbr
:range(8,2):uint() -- num authority
565 if check
> 100 then return false end
566 check
= tvbr
:range(10,2):uint() -- num additional
567 if check
> 100 then return false end
569 dprint2("heur_dissect_dns: everything looks good calling the real dissector")
571 -- don't do this line in your script - I'm just doing it so our test-suite can
572 -- verify this script
573 root
:add("Heuristic dissector used"):set_generated()
575 -- ok, looks like it's ours, so go dissect it
576 -- note: calling the dissector directly like this is new in 1.11.3
577 -- also note that calling a Dissector object, as this does, means we don't
578 -- get back the return value of the dissector function we created previously
579 -- so it might be better to just call the function directly instead of doing
580 -- this, but this script is used for testing and this tests the call() function
581 dns
.dissector(tvbuf
,pktinfo
,root
)
583 -- since this is over a transport protocol, such as UDP, we can set the
584 -- conversation to make it sticky for our dissector, so that all future
585 -- packets to/from the same address:port pair will just call our dissector
586 -- function directly instead of this heuristic function
587 -- this is a new attribute of pinfo in 1.11.3
588 pktinfo
.conversation
= dns
593 -- now register that heuristic dissector into the udp heuristic list
594 if default_settings
.heur_regmode
== 1 then
595 -- this is the "normal" way to register a heuristic: using a lua function
596 dns
:register_heuristic("udp",heur_dissect_dns
)
597 elseif default_settings
.heur_regmode
== 2 then
598 -- this is to test the fix for bug 10695:
599 dns
:register_heuristic("udp",dns
.dissector
)
600 elseif default_settings
.heur_regmode
== 3 then
601 -- and this too is to test the fix for bug 10695:
602 dns
:register_heuristic("udp", function (...) return dns
.dissector(...); end )
606 -- our protocol (Proto) gets automatically registered after this script finishes loading
607 ----------------------------------------
609 ----------------------------------------
610 -- DNS query names are not just null-terminated strings; they're actually a sequence of
611 -- 'labels', with a length octet before each one. So "foobar.com" is actually the
612 -- string "\06foobar\03com\00". We could create a ProtoField for label_length and label_name
613 -- or whatever, but since this is an example script I'll show how to do it in raw code.
614 -- This function is given the TvbRange object from the dissector() function, and needs to
616 -- On success, it returns three things: the number of labels, the name string, and how
617 -- many bytes it covered of the buffer.
618 -- On failure, it returns nil and the error message.
619 getQueryName
= function (tvbr
)
620 local label_count
= 0
624 local len_remaining
= tvbr
:len()
625 if len_remaining
< 2 then
627 return nil, "invalid name"
630 local barray
= tvbr
:bytes() -- gets a ByteArray of the TvbRange
631 local pos
= 0 -- unlike Lua, ByteArray uses 0-based indexing
634 local label_len
= barray
:get_index(pos
)
635 if label_len
>= len_remaining
then
636 return nil, "invalid label length of "..label_len
638 pos
= pos
+ 1 -- move past label length octet
639 if label_len
> 0 then
640 -- append the label and a dot to name string
641 -- note: this uses the new method of ByteArray:raw(), added in 1.11.3
642 name
= name
.. barray
:raw(pos
, label_len
) .. "."
643 label_count
= label_count
+ 1
644 pos
= pos
+ label_len
-- move past label
646 name_len
= name_len
+ label_len
+ 1
647 len_remaining
= len_remaining
- (label_len
+ 1) -- subtract label and its length octet
650 -- we appended an extra dot, so get rid of it
651 name
= name
:sub(1, -2)
654 -- this is the root zone (.)
658 return label_count
, name
, name_len