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