Add test suite for Lua dissector-related functions
This isn't super-fancy, but it runs a simple protocol dissector and verifies the tshark output matches what it expects. Things like Proto, ProtoField, Field, Tvb, TvbRange, etc., are used in an example dissector script - it dissects DNS... partially. Enough to make sure things aren't fundamentally broken. This provides something to add on top of later as well. Change-Id: Icf3c8e9534944bcf4c4f6150f02a9a43f999cd75 Reviewed-on: https://code.wireshark.org/review/126 Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com> Tested-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
This commit is contained in:
parent
fe0ed97e35
commit
f5a1786ea1
Binary file not shown.
|
@ -0,0 +1,391 @@
|
|||
----------------------------------------
|
||||
-- script-name: dns_dissector.lua
|
||||
-- author: Hadriel Kaplan <hadrielk at yahoo dot com>
|
||||
-- Copyright (c) 2014, Hadriel Kaplan
|
||||
-- This code is in the Public Domain, or the BSD (3 clause) license if Public Domain does not apply
|
||||
-- in your country.
|
||||
--
|
||||
-- BACKGROUND:
|
||||
-- This is an example Lua script for a protocol dissector. The purpose of this script is two-fold:
|
||||
-- * To provide a reference tutorial for others writing Wireshark dissectors in Lua
|
||||
-- * To test various functions being called in various ways, so this script can be used in the test-suites
|
||||
-- I've tried to meet both of those goals, but it wasn't easy. No doubt some folks will wonder why some
|
||||
-- functions are called some way, or differently than previous invocations of the same function. I'm trying to
|
||||
-- to show both that it can be done numerous ways, but also I'm trying to test those numerous ways, and my more
|
||||
-- immediate need is for test coverage rather than tutorial guide. (the Lua API is sorely lacking in test scripts)
|
||||
--
|
||||
-- OVERVIEW:
|
||||
-- This script creates an elementary dissector for DNS. It's neither comprehensive nor error-free with regards
|
||||
-- to the DNS protocol. That's OK. The goal isn't to fully dissect DNS properly - Wireshark already has a good
|
||||
-- DNS dissector built-in. We don't need another one. We also have other example Lua scripts, but I don't think
|
||||
-- they do a good job of explaining things, and the nice thing about this one is getting capture files to
|
||||
-- run it against is trivial. (plus I uploaded one)
|
||||
--
|
||||
-- HOW TO RUN THIS SCRIPT:
|
||||
-- Wireshark and Tshark support multiple ways of loading Lua scripts: through a dofile() call in init.lua,
|
||||
-- through the file being in either the global or personal plugins directories, or via the command line.
|
||||
-- See the Wireshark USer's Guide chapter on Lua (http://www.wireshark.org/docs/wsug_html_chunked/wsluarm.html).
|
||||
-- Once the script is loaded, it creates a new protocol named "MyDNS" (or "MYDNS" in some places). If you have
|
||||
-- a capture file with DNS packets in it, simply select one in the Packet List pane, right-click on it, and
|
||||
-- select "Decode As ...", and then in the dialog box that shows up scroll down the list of protocols to one
|
||||
-- called "MYDNS", select that and click the "ok" or "apply" button. Voila`, your'e now decoding DNS packets
|
||||
-- using the simplistic dissector in this script. Another way is to download the capture file made for
|
||||
-- this script, and open that - since the DNS packets in it use UDP port 65333 (instead of the default 53),
|
||||
-- and since the MyDNS protocol in this script has been set to automatically decode UDP port 65333, it will
|
||||
-- automagically do it without doing "Decode As ...".
|
||||
--
|
||||
----------------------------------------
|
||||
--print("Wireshark version = "..get_version())
|
||||
--print("Lua version = ".._VERSION)
|
||||
|
||||
----------------------------------------
|
||||
-- Unfortunately, the older Wireshark/Tshark versions have bugs, and part of the point
|
||||
-- of this script is to test those bugs are now fixed. So we need to check the version
|
||||
-- end error out if it's too old.
|
||||
local major, minor, micro = get_version():match("(%d+)%.(%d+)%.(%d+)")
|
||||
if major and tonumber(major) <= 1 and ((tonumber(minor) <= 10) or (tonumber(minor) == 11 and tonumber(micro) < 3)) then
|
||||
error( "Sorry, but your Wireshark/Tshark version ("..get_version()..") is too old for this script!\n"..
|
||||
"This script needs Wireshark/Tshark version 1.11.3 or higher.\n" )
|
||||
end
|
||||
----------------------------------------
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- creates a Proto object, but doesn't register it yet
|
||||
local dns = Proto("mydns","MyDNS Protocol")
|
||||
|
||||
----------------------------------------
|
||||
-- multiple ways to do the same thing: create a protocol field (but not register it yet)
|
||||
-- the abbreviation should always have "<myproto>." before the specific abbreviation, to avoid collisions
|
||||
local pf_trasaction_id = ProtoField.new("Transaction ID", "mydns.trans_id", ftypes.UINT16)
|
||||
local pf_flags = ProtoField.new("Flags", "mydns.flags", ftypes.UINT16, nil, base.HEX)
|
||||
local pf_num_questions = ProtoField.uint16("mydns.num_questions", "Number of Questions")
|
||||
local pf_num_answers = ProtoField.uint16("mydns.num_answers", "Number of Answer RRs")
|
||||
local pf_num_authority_rr = ProtoField.uint16("mydns.num_authority_rr", "Number of Authority RRs")
|
||||
local pf_num_additional_rr = ProtoField.uint16("mydns.num_additional_rr", "Number of Additional RRs")
|
||||
|
||||
-- within the flags field, we want to parse/show the bits separately
|
||||
-- note the "base" argument becomes the size of the bitmask'ed field when ftypes.BOOLEAN is used
|
||||
-- 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)
|
||||
-- again the following shows different ways of doing the same thing basically
|
||||
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?")
|
||||
local pf_flag_opcode = ProtoField.new("Opcode", "mydns.flags.opcode", ftypes.UINT16, nil, base.DEC, 0x7800, "operation code")
|
||||
local pf_flag_authoritative = ProtoField.new("Authoritative", "mydns.flags.authoritative", ftypes.BOOLEAN, nil, 16, 0x0400, "is the response authoritative?")
|
||||
local pf_flag_truncated = ProtoField.bool("mydns.flags.truncated", "Truncated", 16, nil, 0x0200, "is the message truncated?")
|
||||
local pf_flag_recursion_desired = ProtoField.bool("mydns.flags.recursion_desired", "Recursion desired", 16, {"yes","no"}, 0x0100, "do the query recursivley?")
|
||||
local pf_flag_recursion_available = ProtoField.bool("mydns.flags.recursion_available", "Recursion available", 16, nil, 0x0080, "does the server support recursion?")
|
||||
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?")
|
||||
local pf_flag_authenticated = ProtoField.bool("mydns.flags.authenticated", "Authenticated", 16, {"yes","no"}, 0x0020, "did the server DNSSEC authenticate?")
|
||||
local pf_flag_checking_disabled = ProtoField.bool("mydns.flags.checking_disabled", "Checking disabled", 16, nil, 0x0010)
|
||||
|
||||
-- no, these aren't all the DNS response codes - this is just an example
|
||||
local rcodes = {
|
||||
[0] = "No Error",
|
||||
[1] = "Format Error",
|
||||
[2] = "Server Failure",
|
||||
[3] = "Non-Existent Domain",
|
||||
[9] = "Server Not Authoritative for zone"
|
||||
}
|
||||
-- the above rcodes table is used in this next ProtoField
|
||||
local pf_flag_rcode = ProtoField.uint16("mydns.flags.rcode", "Response code", base.DEC, rcodes, 0x000F)
|
||||
local pf_query = ProtoField.new("Query", "mydns.query", ftypes.BYTES)
|
||||
local pf_query_name = ProtoField.new("Name", "mydns.query.name", ftypes.STRING)
|
||||
local pf_query_name_len = ProtoField.new("Name Length", "mydns.query.name.len", ftypes.UINT8)
|
||||
local pf_query_label_count = ProtoField.new("Label Count", "mydns.query.label.count", ftypes.UINT8)
|
||||
local rrtypes = { [1] = "A (IPv4 host address)", [2] = "NS (authoritative name server)", [28] = "AAAA (for geeks only)" }
|
||||
local pf_query_type = ProtoField.uint16("mydns.query.type", "Type", base.DEC, rrtypes)
|
||||
-- again, not all class types are listed here
|
||||
local classes = {
|
||||
[0] = "Reserved",
|
||||
[1] = "IN (Internet)",
|
||||
[2] = "The 1%",
|
||||
[5] = "First class",
|
||||
[6] = "Business class",
|
||||
[65535] = "Cattle class"
|
||||
}
|
||||
local pf_query_class = ProtoField.uint16("mydns.query.class", "Class", base.DEC, classes, nil, "keep it classy folks")
|
||||
|
||||
----------------------------------------
|
||||
-- this actually registers the ProtoFields above, into our new Protocol
|
||||
-- in a real script I wouldn't do it this way; I'd build a table of fields programaticaly
|
||||
-- and then set dns.fields to it, so as to avoid forgetting a field
|
||||
dns.fields = { pf_trasaction_id, pf_flags,
|
||||
pf_num_questions, pf_num_answers, pf_num_authority_rr, pf_num_additional_rr,
|
||||
pf_flag_response, pf_flag_opcode, pf_flag_authoritative,
|
||||
pf_flag_truncated, pf_flag_recursion_desired, pf_flag_recursion_available,
|
||||
pf_flag_z, pf_flag_authenticated, pf_flag_checking_disabled, pf_flag_rcode,
|
||||
pf_query, pf_query_name, pf_query_name_len, pf_query_label_count, pf_query_type, pf_query_class }
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- we don't just want to display our protocol's fields, we want to access the value of some of them too!
|
||||
-- There are several ways to do that. One is to just parse the buffer contents in Lua code to find
|
||||
-- the values. But since ProtoFields actually do the parsing for us, and can be retrieved using Field
|
||||
-- objects, it's kinda cool to do it that way. So let's create some Fields to extract the values.
|
||||
-- The following creates the Field objects, but they're not 'registered' until after this script is loaded.
|
||||
-- Also, these lines can't be before the 'dns.fields = ...' line above, because the Field.new() here is
|
||||
-- referencing fields we're creating, and they're not "created" until that line above.
|
||||
-- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function.
|
||||
-- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created).
|
||||
local questions_field = Field.new("mydns.num_questions")
|
||||
local query_type_field = Field.new("mydns.query.type")
|
||||
local query_class_field = Field.new("mydns.query.class")
|
||||
local response_field = Field.new("mydns.flags.response")
|
||||
|
||||
-- here's a little helper function to access the response_field value later.
|
||||
-- Like any Field retrieval, you can't retrieve a field's value until its value has been
|
||||
-- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls.
|
||||
-- So this isResponse() function can't be used until after the pf_flag_response ProtoField
|
||||
-- has been used inside the dissector.
|
||||
-- Note that calling the Field object returns a FieldInfo object, and calling that
|
||||
-- returns the value of the field - in this case a boolean true/false, since we set the
|
||||
-- "mydns.flags.response" ProtoField to ftype.BOOLEAN way earlier when we created the
|
||||
-- pf_flag_response ProtoField. Clear as mud?
|
||||
--
|
||||
-- A shorter version of this function would be:
|
||||
-- local function isResponse() return response_field()() end
|
||||
-- but I though the below is easier to understand.
|
||||
local function isResponse()
|
||||
local response_fieldinfo = response_field()
|
||||
return response_fieldinfo()
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------
|
||||
---- some constants for later use ----
|
||||
-- the DNS header size
|
||||
local DNS_HDR_LEN = 12
|
||||
|
||||
-- the smallest possible DNS query field size
|
||||
-- has to be at least a label length octet, label character, label null terminator, 2-bytes type and 2-bytes class
|
||||
local MIN_QUERY_LEN = 7
|
||||
|
||||
-- the UDP port number we want to associate with our protocol
|
||||
local MYDNS_PROTO_UDP_PORT = 65333
|
||||
|
||||
----------------------------------------
|
||||
-- some forward "declarations" of helper functions we use in the dissector
|
||||
-- I don't usually use this trick, but it'll help reading/grok'ing this script I think
|
||||
-- if we don't focus on them.
|
||||
local byteArray2String, getQueryName
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- The following creates the callback function for the dissector.
|
||||
-- It's the same as doing "dns.dissector = function (tvbuf,pkt,root)"
|
||||
-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
|
||||
-- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
|
||||
-- this function and pass it these arguments for the packet it's dissecting.
|
||||
function dns.dissector(tvbuf,pktinfo,root)
|
||||
|
||||
-- We want to check that the packet size is rational during dissection, so let's get the length of the
|
||||
-- packet buffer (Tvb).
|
||||
-- Because DNS has no additonal payload data other than itself, and it rides on UDP without padding,
|
||||
-- we can use tvb:len() or tvb:reported_len() here; but I prefer tvb:reported_length_remaining() as it's safer.
|
||||
local pktlen = tvbuf:reported_length_remaining()
|
||||
|
||||
-- We start by adding our protocol to the dissection display tree.
|
||||
-- A call to tree:add() returns the child created, so we can add more "under" it using that return value.
|
||||
-- The second argument is how much of the buffer/packet this added tree item covers/represents - in this
|
||||
-- case (DNS protocol) that's the remainder of the packet.
|
||||
local tree = root:add(dns, tvbuf:range(0,pktlen))
|
||||
|
||||
-- now let's check it's not too short
|
||||
if pktlen < DNS_HDR_LEN then
|
||||
-- since we're going to add this protocol to a specific UDP port, we're going to
|
||||
-- assume packets in this port are our protocol, so the packet being too short is an error
|
||||
tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short")
|
||||
return
|
||||
end
|
||||
|
||||
-- Now let's add our transaction id under our dns protocol tree we just created.
|
||||
-- The transaction id starts at offset 0, for 2 bytes length.
|
||||
tree:add(pf_trasaction_id, tvbuf:range(0,2))
|
||||
|
||||
-- We'd like to put the transaction id number in the GUI row for this packet, in its
|
||||
-- INFO column/cell. Firt we need the transaction id value, though. Since we just
|
||||
-- dissected it with the previous code line, we could now get it using a Field's
|
||||
-- FieldInfo extractor, but instead we'll get it directly from the TvbRange just
|
||||
-- to show how to do that. We'll use Field/FieldInfo extractors later on...
|
||||
local transid = tvbuf:range(0,2):uint()
|
||||
pktinfo.cols.info:set("(".. transid ..")")
|
||||
|
||||
-- now let's add the flags, which are all in the packet bytes at offset 2 of length 2
|
||||
-- instead of calling this again and again, let's just use a variable
|
||||
local flagrange = tvbuf:range(2,2)
|
||||
|
||||
-- for our flags field, we want a sub-tree
|
||||
local flag_tree = tree:add(pf_flags, flagrange)
|
||||
-- I'm indenting this for calarity, because it's adding to the flag's child-tree
|
||||
-- let's add the type of message (query vs. response)
|
||||
flag_tree:add(pf_flag_response, flagrange)
|
||||
|
||||
-- we now know if it's a response or query, so let's put that in the
|
||||
-- GUI packet row, in the INFO column cell
|
||||
-- this line of code uses a Lua trick for doing something similar to
|
||||
-- the C/C++ 'test ? true : false' shorthand
|
||||
pktinfo.cols.info:prepend(isResponse() and "Response " or "Query ")
|
||||
|
||||
flag_tree:add(pf_flag_opcode, flagrange)
|
||||
|
||||
if isResponse() then
|
||||
flag_tree:add(pf_flag_authoritative, flagrange)
|
||||
end
|
||||
|
||||
flag_tree:add(pf_flag_truncated, flagrange)
|
||||
|
||||
if isResponse() then
|
||||
flag_tree:add(pf_flag_recursion_available, flagrange)
|
||||
else
|
||||
flag_tree:add(pf_flag_recursion_desired, flagrange)
|
||||
end
|
||||
|
||||
flag_tree:add(pf_flag_z, flagrange)
|
||||
|
||||
if isResponse() then
|
||||
flag_tree:add(pf_flag_authenticated, flagrange)
|
||||
flag_tree:add(pf_flag_rcode, flagrange)
|
||||
end
|
||||
|
||||
flag_tree:add(pf_flag_checking_disabled, flagrange)
|
||||
|
||||
-- now add more to the main mydns tree
|
||||
tree:add(pf_num_questions, tvbuf:range(4,2))
|
||||
tree:add(pf_num_answers, tvbuf:range(6,2))
|
||||
-- another way to get a TvbRange is just to call the Tvb like this
|
||||
tree:add(pf_num_authority_rr, tvbuf(8,2))
|
||||
-- or if we're crazy, we can create a sub-TvbRange, from a sub-TvbRange of the TvbRange
|
||||
tree:add(pf_num_additional_rr, tvbuf:range(10,2):range()())
|
||||
|
||||
local num_queries = questions_field()()
|
||||
local pos = DNS_HDR_LEN
|
||||
|
||||
if num_queries > 0 then
|
||||
-- let's create a sub-tree, using a plain text description (not a field from the packet)
|
||||
local queries_tree = tree:add("Queries")
|
||||
|
||||
local pktlen_remaining = pktlen - pos
|
||||
|
||||
while num_queries > 0 and pktlen_remaining > 0 do
|
||||
if pktlen_remaining < MIN_QUERY_LEN then
|
||||
queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, "query field missing or too short")
|
||||
return
|
||||
end
|
||||
|
||||
-- we don't know how long this query field in total is, so we have to parse it first before
|
||||
-- adding it to the tree, because we want to identify the correct bytes it covers
|
||||
local label_count, name, name_len = getQueryName(tvbuf:range(pos,pktlen_remaining))
|
||||
if not label_count then
|
||||
q_tree:add_expert_info(PI_MALFORMED, PI_ERROR, name)
|
||||
return
|
||||
end
|
||||
|
||||
-- now add the first query to the 'Queries' child tree we just created
|
||||
-- we're going to change the string generated by this later, after we figure out the subsequent fields.
|
||||
-- the whole query field is the query name field length we just got, plus the 20byte type and 2-byte class
|
||||
local q_tree = queries_tree:add(pf_query, tvbuf:range(pos, name_len + 4))
|
||||
|
||||
q_tree:add(pf_query_name, tvbuf:range(pos, name_len), name)
|
||||
pos = pos + name_len
|
||||
|
||||
pktinfo.cols.info:append(" "..name)
|
||||
|
||||
-- the following tree items are generated by us, not encoded in the packet per se, so mark them as such
|
||||
q_tree:add(pf_query_name_len, name_len):set_generated()
|
||||
q_tree:add(pf_query_label_count, label_count):set_generated()
|
||||
|
||||
q_tree:add(pf_query_type, tvbuf:range(pos, 2))
|
||||
q_tree:add(pf_query_class, tvbuf:range(pos + 2, 2))
|
||||
pos = pos + 4
|
||||
|
||||
-- now change the query text
|
||||
q_tree:set_text(name..": type "..query_type_field().display ..", class "..query_class_field().display)
|
||||
|
||||
pktlen_remaining = pktlen_remaining - (name_len + 4)
|
||||
num_queries = num_queries - 1
|
||||
end -- end of while loop
|
||||
|
||||
if num_queries > 0 then
|
||||
-- we didn't process them all
|
||||
queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, num_queries .. " query field(s) missing")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- tell wireshark how much of tvbuff we dissected
|
||||
return pos
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- we want to have our protocol disseciton invoked for a specific UDP port,
|
||||
-- so get the udp dissecotr table and add our protocol to it
|
||||
local udp_encap_table = DissectorTable.get("udp.port")
|
||||
udp_encap_table:add(MYDNS_PROTO_UDP_PORT, dns)
|
||||
|
||||
-- We're done!
|
||||
-- our protocol (Proto) gets automatically registered after this script finishes loading
|
||||
----------------------------------------
|
||||
|
||||
----------------------------------------
|
||||
-- a helper function used later
|
||||
-- note that it doesn't use "local" because it's already been declared as a local
|
||||
-- variable way earlier in this script (as a form of forward declaration)
|
||||
byteArray2String = function (barray, begin, length)
|
||||
local word = {}
|
||||
for i = 1, length do
|
||||
word[i] = string.char(barray:get_index(begin))
|
||||
begin = begin + 1
|
||||
end
|
||||
return table.concat(word)
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- DNS query names are not just null-terminated strings; they're actually a sequence of
|
||||
-- 'labels', with a length octet before each one. So "foobar.com" is actually the
|
||||
-- string "\06foobar\03com\00". We could create a ProtoField for label_length and label_name
|
||||
-- or whatever, but since this is an example script I'll show how to do it in raw code.
|
||||
-- This function is given the TvbRange object from the dissector() function, and needs to
|
||||
-- parse it.
|
||||
-- On success, it returns three things: the number of labels, the name string, and how
|
||||
-- many bytes it covered of the buffer (which is always 2 more than the name length in this case).
|
||||
-- On failure, it returns nil and the error message.
|
||||
getQueryName = function (tvbr)
|
||||
local label_count = 0
|
||||
local name = ""
|
||||
|
||||
local len_remaining = tvbr:len()
|
||||
if len_remaining < 2 then
|
||||
-- it's too short
|
||||
return nil, "invalid name"
|
||||
end
|
||||
|
||||
local barray = tvbr:bytes() -- gets a ByteArray of the TvbRange
|
||||
local pos = 0 -- unlike Lua, ByteArray uses 0-based indexing
|
||||
|
||||
-- get the first octet/label-length
|
||||
local label_len = barray:get_index(pos)
|
||||
if label_len == 0 then
|
||||
return nil, "invalid initial label length of 0"
|
||||
end
|
||||
|
||||
while label_len > 0 do
|
||||
if label_len >= len_remaining then
|
||||
return nil, "invalid label length of "..label_len
|
||||
end
|
||||
pos = pos + 1 -- move past label length octet
|
||||
-- sadly, there's no current way to get a raw Lua string from a ByteArray (nor from Tvb for that matter)
|
||||
-- so we need to do it one character at a a time
|
||||
-- append the label and a dot to name string
|
||||
name = name .. byteArray2String(barray, pos, label_len) .. "."
|
||||
len_remaining = len_remaining - (label_len + 1) -- subtract label and its length octet
|
||||
label_count = label_count + 1
|
||||
pos = pos + label_len -- move past label
|
||||
label_len = barray:get_index(pos)
|
||||
end
|
||||
|
||||
-- we appended an extra dot, so get rid of it
|
||||
name = name:sub(1, -2)
|
||||
|
||||
return label_count, name, name:len() + 2
|
||||
end
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
-- This is a test script for tshark.
|
||||
-- This script runs inside tshark.
|
||||
-- FIRST run tshark with the "dns_dissector.lua" plugin, with the dns_port.pcap file,
|
||||
-- and with full tree output (-V switch). Pipe that to a file named testin.txt.
|
||||
-- This verify script then reads in that testin.txt.
|
||||
--
|
||||
-- tshark -r bogus.cap -X lua_script:<path_to_testdir>/lua/verify_dns_dissector.lua
|
||||
|
||||
local function testing(...)
|
||||
print("---- Testing "..tostring(...).." ----")
|
||||
end
|
||||
|
||||
local lines = {
|
||||
{
|
||||
"MyDNS Protocol",
|
||||
"Transaction ID: 43",
|
||||
"Flags: 0x0100",
|
||||
"0... .... .... .... = Response: this is a query",
|
||||
".000 0... .... .... = Opcode: 0",
|
||||
".... ..0. .... .... = Truncated: False",
|
||||
".... ...1 .... .... = Recursion desired: yes",
|
||||
".... .... .0.. .... = World War Z - Reserved for future use: 0x0000",
|
||||
".... .... ...0 .... = Checking disabled: False",
|
||||
"Number of Questions: 1",
|
||||
"Number of Answer RRs: 0",
|
||||
"Number of Authority RRs: 0",
|
||||
"Number of Additional RRs: 0",
|
||||
"Queries",
|
||||
"us.pool.ntp.org: type A (IPv4 host address) (1), class IN (Internet) (1)",
|
||||
"Name: us.pool.ntp.org",
|
||||
"[Name Length: 17]",
|
||||
"[Label Count: 4]",
|
||||
"Type: A (IPv4 host address) (1)",
|
||||
"Class: IN (Internet) (1)",
|
||||
},
|
||||
|
||||
{
|
||||
"MyDNS Protocol",
|
||||
"Transaction ID: 43",
|
||||
"Flags: 0x8180",
|
||||
"1... .... .... .... = Response: this is a response",
|
||||
".000 0... .... .... = Opcode: 0",
|
||||
".... .0.. .... .... = Authoritative: False",
|
||||
".... ..0. .... .... = Truncated: False",
|
||||
".... .... 1... .... = Recursion available: True",
|
||||
".... .... .0.. .... = World War Z - Reserved for future use: 0x0000",
|
||||
".... .... ..0. .... = Authenticated: no",
|
||||
".... .... .... 0000 = Response code: No Error (0)",
|
||||
".... .... ...0 .... = Checking disabled: False",
|
||||
"Number of Questions: 1",
|
||||
"Number of Answer RRs: 15",
|
||||
"Number of Authority RRs: 6",
|
||||
"Number of Additional RRs: 2",
|
||||
"Queries",
|
||||
"us.pool.ntp.org: type A (IPv4 host address) (1), class IN (Internet) (1)",
|
||||
"Name: us.pool.ntp.org",
|
||||
"[Name Length: 17]",
|
||||
"[Label Count: 4]",
|
||||
"Type: A (IPv4 host address) (1)",
|
||||
"Class: IN (Internet) (1)",
|
||||
}
|
||||
}
|
||||
|
||||
local numtests = #lines[1] + #lines[2]
|
||||
print("going to run "..numtests.." tests")
|
||||
|
||||
-- for an example of what we're reading through to verify, look at end of this file
|
||||
print("opening file testin.txt")
|
||||
local file = io.open("testin.txt", "r")
|
||||
local line = file:read()
|
||||
|
||||
local pktidx = 1
|
||||
local total = 0
|
||||
|
||||
while line do
|
||||
-- eat beginning whitespace
|
||||
line = line:gsub("^%s+","",1)
|
||||
if line:find("^Frame %d+:") then
|
||||
pktidx = line:match("^Frame (%d+):")
|
||||
testing("Frame "..pktidx)
|
||||
pktidx = tonumber(pktidx)
|
||||
line = file:read()
|
||||
elseif line == lines[pktidx][1] then
|
||||
-- we've matched the first line of our section
|
||||
-- now verify the rest is sequential
|
||||
for i, v in ipairs(lines[pktidx]) do
|
||||
io.stdout:write("testing Frame "..pktidx..", line "..i.."...")
|
||||
if not line then
|
||||
-- ended too soon
|
||||
io.stdout:write("failed!\n")
|
||||
error("Ran out of file lines!")
|
||||
return
|
||||
end
|
||||
-- eat beginning whitespace
|
||||
line = line:gsub("^%s+","",1)
|
||||
if line ~= v then
|
||||
io.stdout:write("failed!\n")
|
||||
print("Got this:'"..line.."', expected this:'"..v.."'")
|
||||
error("mismatched lines!")
|
||||
return
|
||||
end
|
||||
io.stdout:write("passed\n")
|
||||
total = total + 1
|
||||
line = file:read()
|
||||
end
|
||||
else
|
||||
line = file:read()
|
||||
end
|
||||
end
|
||||
|
||||
print(total.." of "..numtests.." tests run and passed")
|
||||
|
||||
if total ~= numtests then
|
||||
error("Did not find all our lines to test!")
|
||||
return
|
||||
end
|
||||
|
||||
print("\n-----------------------------\n")
|
||||
-- must print out the following for success (the test shell sciprt looks for this)
|
||||
print("All tests passed!\n\n")
|
||||
|
||||
|
||||
----------------------------------------------------------
|
||||
-- We should see something like this:
|
||||
--[[
|
||||
Frame 1: 75 bytes on wire (600 bits), 75 bytes captured (600 bits)
|
||||
Encapsulation type: Ethernet (1)
|
||||
Arrival Time: Sep 26, 2004 23:18:04.938672000 EDT
|
||||
[Time shift for this packet: 0.000000000 seconds]
|
||||
Epoch Time: 1096255084.938672000 seconds
|
||||
[Time delta from previous captured frame: 0.000000000 seconds]
|
||||
[Time delta from previous displayed frame: 0.000000000 seconds]
|
||||
[Time since reference or first frame: 0.000000000 seconds]
|
||||
Frame Number: 1
|
||||
Frame Length: 75 bytes (600 bits)
|
||||
Capture Length: 75 bytes (600 bits)
|
||||
[Frame is marked: False]
|
||||
[Frame is ignored: False]
|
||||
[Protocols in frame: eth:ethertype:ip:udp:mydns]
|
||||
Ethernet II, Src: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e), Dst: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53)
|
||||
Destination: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53)
|
||||
Address: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53)
|
||||
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
|
||||
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
|
||||
Source: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e)
|
||||
Address: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e)
|
||||
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
|
||||
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
|
||||
Type: IP (0x0800)
|
||||
Internet Protocol Version 4, Src: 192.168.50.50 (192.168.50.50), Dst: 192.168.0.1 (192.168.0.1)
|
||||
Version: 4
|
||||
Header Length: 20 bytes
|
||||
Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00: Not-ECT (Not ECN-Capable Transport))
|
||||
0000 00.. = Differentiated Services Codepoint: Default (0x00)
|
||||
.... ..00 = Explicit Congestion Notification: Not-ECT (Not ECN-Capable Transport) (0x00)
|
||||
Total Length: 61
|
||||
Identification: 0x0a41 (2625)
|
||||
Flags: 0x00
|
||||
0... .... = Reserved bit: Not set
|
||||
.0.. .... = Don't fragment: Not set
|
||||
..0. .... = More fragments: Not set
|
||||
Fragment offset: 0
|
||||
Time to live: 128
|
||||
Protocol: UDP (17)
|
||||
Header checksum: 0x7ceb [correct]
|
||||
[Good: True]
|
||||
[Bad: False]
|
||||
Source: 192.168.50.50 (192.168.50.50)
|
||||
Destination: 192.168.0.1 (192.168.0.1)
|
||||
User Datagram Protocol, Src Port: 65282 (65282), Dst Port: 65333 (65333)
|
||||
Source Port: 65282 (65282)
|
||||
Destination Port: 65333 (65333)
|
||||
Length: 41
|
||||
Checksum: 0x07a9 [validation disabled]
|
||||
[Good Checksum: False]
|
||||
[Bad Checksum: False]
|
||||
[Stream index: 0]
|
||||
MyDNS Protocol
|
||||
Transaction ID: 43
|
||||
Flags: 0x0100
|
||||
0... .... .... .... = Response: this is a query
|
||||
.000 0... .... .... = Opcode: 0
|
||||
.... ..0. .... .... = Truncated: False
|
||||
.... ...1 .... .... = Recursion desired: yes
|
||||
.... .... .0.. .... = World War Z - Reserved for future use: 0x0000
|
||||
.... .... ...0 .... = Checking disabled: False
|
||||
Number of Questions: 1
|
||||
Number of Answer RRs: 0
|
||||
Number of Authority RRs: 0
|
||||
Number of Additional RRs: 0
|
||||
Queries
|
||||
us.pool.ntp.org: type A (IPv4 host address) (1), class IN (Internet) (1)
|
||||
Name: us.pool.ntp.org
|
||||
[Name Length: 17]
|
||||
[Label Count: 4]
|
||||
Type: A (IPv4 host address) (1)
|
||||
Class: IN (Internet) (1)
|
||||
|
||||
Frame 2: 540 bytes on wire (4320 bits), 540 bytes captured (4320 bits)
|
||||
Encapsulation type: Ethernet (1)
|
||||
Arrival Time: Sep 26, 2004 23:18:04.945618000 EDT
|
||||
[Time shift for this packet: 0.000000000 seconds]
|
||||
Epoch Time: 1096255084.945618000 seconds
|
||||
[Time delta from previous captured frame: 0.006946000 seconds]
|
||||
[Time delta from previous displayed frame: 0.006946000 seconds]
|
||||
[Time since reference or first frame: 0.006946000 seconds]
|
||||
Frame Number: 2
|
||||
Frame Length: 540 bytes (4320 bits)
|
||||
Capture Length: 540 bytes (4320 bits)
|
||||
[Frame is marked: False]
|
||||
[Frame is ignored: False]
|
||||
[Protocols in frame: eth:ethertype:ip:udp:mydns]
|
||||
Ethernet II, Src: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53), Dst: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e)
|
||||
Destination: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e)
|
||||
Address: AmbitMic_6c:40:4e (00:d0:59:6c:40:4e)
|
||||
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
|
||||
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
|
||||
Source: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53)
|
||||
Address: Cisco-Li_82:b2:53 (00:0c:41:82:b2:53)
|
||||
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
|
||||
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
|
||||
Type: IP (0x0800)
|
||||
Internet Protocol Version 4, Src: 192.168.0.1 (192.168.0.1), Dst: 192.168.50.50 (192.168.50.50)
|
||||
Version: 4
|
||||
Header Length: 20 bytes
|
||||
Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00: Not-ECT (Not ECN-Capable Transport))
|
||||
0000 00.. = Differentiated Services Codepoint: Default (0x00)
|
||||
.... ..00 = Explicit Congestion Notification: Not-ECT (Not ECN-Capable Transport) (0x00)
|
||||
Total Length: 526
|
||||
Identification: 0x2153 (8531)
|
||||
Flags: 0x00
|
||||
0... .... = Reserved bit: Not set
|
||||
.0.. .... = Don't fragment: Not set
|
||||
..0. .... = More fragments: Not set
|
||||
Fragment offset: 0
|
||||
Time to live: 63
|
||||
Protocol: UDP (17)
|
||||
Header checksum: 0xa508 [correct]
|
||||
[Good: True]
|
||||
[Bad: False]
|
||||
Source: 192.168.0.1 (192.168.0.1)
|
||||
Destination: 192.168.50.50 (192.168.50.50)
|
||||
User Datagram Protocol, Src Port: 65333 (65333), Dst Port: 65282 (65282)
|
||||
Source Port: 65333 (65333)
|
||||
Destination Port: 65282 (65282)
|
||||
Length: 506
|
||||
Checksum: 0xf9d5 [validation disabled]
|
||||
[Good Checksum: False]
|
||||
[Bad Checksum: False]
|
||||
[Stream index: 0]
|
||||
MyDNS Protocol
|
||||
Transaction ID: 43
|
||||
Flags: 0x8180
|
||||
1... .... .... .... = Response: this is a response
|
||||
.000 0... .... .... = Opcode: 0
|
||||
.... .0.. .... .... = Authoritative: False
|
||||
.... ..0. .... .... = Truncated: False
|
||||
.... .... 1... .... = Recursion available: True
|
||||
.... .... .0.. .... = World War Z - Reserved for future use: 0x0000
|
||||
.... .... ..0. .... = Authenticated: no
|
||||
.... .... .... 0000 = Response code: No Error (0)
|
||||
.... .... ...0 .... = Checking disabled: False
|
||||
Number of Questions: 1
|
||||
Number of Answer RRs: 15
|
||||
Number of Authority RRs: 6
|
||||
Number of Additional RRs: 2
|
||||
Queries
|
||||
us.pool.ntp.org: type A (IPv4 host address) (1), class IN (Internet) (1)
|
||||
Name: us.pool.ntp.org
|
||||
[Name Length: 17]
|
||||
[Label Count: 4]
|
||||
Type: A (IPv4 host address) (1)
|
||||
Class: IN (Internet) (1)
|
||||
]]
|
||||
|
|
@ -69,6 +69,34 @@ unittests_step_exntest() {
|
|||
unittests_step_test
|
||||
}
|
||||
|
||||
unittests_step_lua_dissector_test() {
|
||||
if [ $HAVE_LUA -ne 0 ]; then
|
||||
test_step_skipped
|
||||
return
|
||||
fi
|
||||
|
||||
# First run tshark with the dissector script.
|
||||
$TSHARK -r $CAPTURE_DIR/dns_port.pcap -V -X lua_script:$TESTS_DIR/lua/dissector.lua > testin.txt 2>&1
|
||||
RETURNVALUE=$?
|
||||
if [ ! $RETURNVALUE -eq $EXIT_OK ]; then
|
||||
echo
|
||||
cat ./testin_tmp.txt
|
||||
test_step_failed "exit status of $DUT: $RETURNVALUE"
|
||||
return
|
||||
fi
|
||||
|
||||
# then run tshark again with the verification script. (it internally reads in testin.txt)
|
||||
$TSHARK -r $CAPTURE_DIR/dns_port.pcap -X lua_script:$TESTS_DIR/lua/verify_dissector.lua > testout.txt 2>&1
|
||||
if grep -q "All tests passed!" testout.txt; then
|
||||
test_step_ok
|
||||
else
|
||||
echo
|
||||
cat ./testin.txt
|
||||
cat ./testout.txt
|
||||
test_step_failed "didn't find pass marker"
|
||||
fi
|
||||
}
|
||||
|
||||
unittests_step_lua_int64_test() {
|
||||
if [ $HAVE_LUA -ne 0 ]; then
|
||||
test_step_skipped
|
||||
|
@ -80,7 +108,8 @@ unittests_step_lua_int64_test() {
|
|||
if grep -q "All tests passed!" testout.txt; then
|
||||
test_step_ok
|
||||
else
|
||||
cat testout.txt
|
||||
echo
|
||||
cat ./testout.txt
|
||||
test_step_failed "didn't find pass marker"
|
||||
fi
|
||||
}
|
||||
|
@ -111,12 +140,14 @@ unittests_step_wmem_test() {
|
|||
|
||||
unittests_cleanup_step() {
|
||||
rm -f ./testout.txt
|
||||
rm -f ./testin.txt
|
||||
}
|
||||
|
||||
unittests_suite() {
|
||||
test_step_set_pre unittests_cleanup_step
|
||||
test_step_set_post unittests_cleanup_step
|
||||
test_step_add "exntest" unittests_step_exntest
|
||||
test_step_add "lua dissector" unittests_step_lua_dissector_test
|
||||
test_step_add "lua int64" unittests_step_lua_int64_test
|
||||
test_step_add "oids_test" unittests_step_oids_test
|
||||
test_step_add "reassemble_test" unittests_step_reassemble_test
|
||||
|
|
Loading…
Reference in New Issue