aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorHadriel Kaplan <hadrielk@yahoo.com>2014-03-10 01:54:51 -0400
committerAnders Broman <a.broman58@gmail.com>2014-03-14 07:29:15 +0000
commit04c39bb0972bac1f95eb9394b5ca1086f19c0d93 (patch)
tree62171e4584b86bb746d6a73181eb7627a15b9e44 /test
parenta59ac1bd10d29d05ca5cd657b7c64ab13a08670d (diff)
Add Lua heuristic dissector support
This adds the ability for Lua scripts to register heuristic dissectors for any protocol that has registered a heuristic dissector list, such as UDP, TCP, and ~50 others. The Lua function can also establish a conversation tied to its Proto dissector, to avoid having to check the heuristics for the same flow. The example dissector in the testsuite has also been enhanced to include a heuristic dissector, to verify the functionality and provide an example implementation. Change-Id: Ie232602779f43d3418fe8db09c61d5fc0b59597a Reviewed-on: https://code.wireshark.org/review/576 Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'test')
-rw-r--r--test/captures/dns_port.pcapbin671 -> 1318 bytes
-rw-r--r--test/lua/dissector.lua117
-rw-r--r--test/lua/proto.lua77
-rw-r--r--test/lua/verify_dissector.lua18
-rwxr-xr-xtest/suite-wslua.sh20
5 files changed, 215 insertions, 17 deletions
diff --git a/test/captures/dns_port.pcap b/test/captures/dns_port.pcap
index 73bff58172..c19a79c8c9 100644
--- a/test/captures/dns_port.pcap
+++ b/test/captures/dns_port.pcap
Binary files differ
diff --git a/test/lua/dissector.lua b/test/lua/dissector.lua
index 635cdb5d49..632ae87f9b 100644
--- a/test/lua/dissector.lua
+++ b/test/lua/dissector.lua
@@ -35,8 +35,24 @@
-- automagically do it without doing "Decode As ...".
--
----------------------------------------
---print("Wireshark version = "..get_version())
---print("Lua version = ".._VERSION)
+-- debug printer, set DEBUG to true to enable printing debug info
+-- set DEBUG2 to true to enable really verbose printing
+local DEBUG, DEBUG2 = false, false
+
+local dprint = function() end
+local dprint2 = function() end
+if DEBUG or DEBUG2 then
+ dprint = function(...)
+ print(table.concat({"Lua:", ...}," "))
+ end
+
+ if DEBUG2 then
+ dprint2 = dprint
+ end
+end
+
+dprint2("Wireshark version = ", get_version())
+dprint2("Lua version = ", _VERSION)
----------------------------------------
-- Unfortunately, the older Wireshark/Tshark versions have bugs, and part of the point
@@ -177,6 +193,10 @@ local getQueryName
-- 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)
+ dprint2("dns.dissector called")
+
+ -- set the protocol column to show our protocol name
+ pktinfo.cols.protocol:set("MYDNS")
-- We want to check that the packet size is rational during dissection, so let's get the length of the
-- packet buffer (Tvb).
@@ -195,6 +215,7 @@ function dns.dissector(tvbuf,pktinfo,root)
-- 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")
+ dprint("packet length",pktlen,"too short")
return
end
@@ -216,7 +237,7 @@ function dns.dissector(tvbuf,pktinfo,root)
-- 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
+ -- I'm indenting this for clarity, 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)
@@ -312,16 +333,101 @@ function dns.dissector(tvbuf,pktinfo,root)
end
end
+ dprint2("dns.dissector returning",pos)
+
-- 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
+-- we want to have our protocol dissection invoked for a specific UDP port,
+-- so get the udp dissector 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 also want to add the heuristic dissector, for any UDP protocol
+-- first we need a heuristic dissection function
+-- this is that function - when wireshark invokes this, it will pass in the same
+-- things it passes in to the "dissector" function, but we only want to actually
+-- dissect it if it's for us, and we need to return true if it's for us, or else false
+-- figuring out if it's for us or not is not easy
+-- we need to try as hard as possible, or else we'll think it's for us when it's
+-- not and block other heuristic dissectors from getting their chanc
+--
+-- in practice, you'd never set a dissector like this to be heuristic, because there
+-- just isn't enough information to safely detect if it's DNS or not
+-- but I'm doing it to show how it would be done
+--
+-- Note: this heuristic stuff is new in 1.11.3
+local function heur_dissect_dns(tvbuf,pktinfo,root)
+ dprint2("heur_dissect_dns called")
+
+ if tvbuf:len() < DNS_HDR_LEN then
+ dprint("heur_dissect_dns: tvb shorter than DNS_HDR_LEN of:",DNS_HDR_LEN)
+ return false
+ end
+
+ local tvbr = tvbuf:range(0,DNS_HDR_LEN)
+
+ -- the first 2 bytes are tansaction id, which can be anything so no point in checking those
+ -- the next 2 bytes contain flags, a couple of which have some values we can check against
+
+ -- the opcode has to be 0, 1, 2, 4 or 5
+ -- the opcode field starts at bit offset 17 (in C-indexing), for 4 bits in length
+ local check = tvbr:bitfield(17,4)
+ if check == 3 or check > 5 then
+ dprint("heur_dissect_dns: invalid opcode:",check)
+ return false
+ end
+
+ -- the rcode has to be 0-10, 16-22 (we're ignoring private use rcodes here)
+ -- the rcode field starts at bit offset 28 (in C-indexing), for 4 bits in length
+ check = tvbr:bitfield(28,4)
+ if check > 22 or (check > 10 and check < 16) then
+ dprint("heur_dissect_dns: invalid rcode:",check)
+ return false
+ end
+
+ dprint2("heur_dissect_dns checking questions/answers")
+
+ -- now let's verify the number of questions/answers are reasonable
+ check = tvbr:range(4,2):uint() -- num questions
+ if check > 100 then return false end
+ check = tvbr:range(6,2):uint() -- num answers
+ if check > 100 then return false end
+ check = tvbr:range(8,2):uint() -- num authority
+ if check > 100 then return false end
+ check = tvbr:range(10,2):uint() -- num additional
+ if check > 100 then return false end
+
+ dprint2("heur_dissect_dns: everything looks good calling the real dissector")
+
+ -- don't do this line in your script - I'm just doing it so our testsuite can
+ -- verify this script
+ root:add("Heuristic dissector used"):set_generated()
+
+ -- ok, looks like it's ours, so go dissect it
+ -- note: calling the dissector directly like this is new in 1.11.3
+ -- also note that calling a Dissector objkect, as this does, means we don't
+ -- get back the return value of the dissector function we created previously
+ -- so it might be better to just call the function directly instead of doing
+ -- this, but this script is used for testing and this tests the call() function
+ dns.dissector(tvbuf,pktinfo,root)
+
+ -- since this is over a transport protocol, such as UDP, we can set the
+ -- conversation to make it sticky for our dissector, so that all future
+ -- packets to/from the same address:port pair will just call our dissector
+ -- function directly instead of this heuristic function
+ -- this is a new attribute of pinfo in 1.11.3
+ pktinfo.conversation = dns
+
+ return true
+end
+
+-- now register that heuristic dissector into the udp heuristic list
+dns:register_heuristic("udp",heur_dissect_dns)
+
-- We're done!
-- our protocol (Proto) gets automatically registered after this script finishes loading
----------------------------------------
@@ -361,6 +467,7 @@ getQueryName = function (tvbr)
end
pos = pos + 1 -- move past label length octet
-- append the label and a dot to name string
+ -- note: this uses the new method of ByteArray:raw(), added in 1.11.3
name = name .. barray:raw(pos, label_len) .. "."
len_remaining = len_remaining - (label_len + 1) -- subtract label and its length octet
label_count = label_count + 1
diff --git a/test/lua/proto.lua b/test/lua/proto.lua
index 1ade48bfe9..9b159e65a3 100644
--- a/test/lua/proto.lua
+++ b/test/lua/proto.lua
@@ -47,7 +47,7 @@ end
-- note ip only runs 3 times because it gets removed
-- and bootp only runs twice because the filter makes it run
-- once and then it gets replaced with a different one for the second time
-local taptests = { [FRAME]=2, [OTHER]=48 }
+local taptests = { [FRAME]=4, [OTHER]=48 }
local function getResults()
print("\n-----------------------------\n")
for k,v in pairs(taptests) do
@@ -497,6 +497,81 @@ end
local udp_encap_table = DissectorTable.get("udp.port")
udp_encap_table:add(MYDNS_PROTO_UDP_PORT, dns)
+----------------------------------------
+-- we also want to add the heuristic dissector, for any UDP protocol
+-- first we need a heuristic dissection function
+-- this is that function - when wireshark invokes this, it will pass in the same
+-- things it passes in to the "dissector" function, but we only want to actually
+-- dissect it if it's for us, and we need to return true if it's for us, or else false
+-- figuring out if it's for us or not is not easy
+-- we need to try as hard as possible, or else we'll think it's for us when it's
+-- not and block other heuristic dissectors from getting their chanc
+--
+-- in practice, you'd never set a dissector like this to be heuristic, because there
+-- just isn't enough information to safely detect if it's DNS or not
+-- but I'm doing it to show how it would be done
+--
+-- Note: this heuristic stuff is new in 1.11.3
+local function heur_dissect_dns(tvbuf,pktinfo,root)
+
+ if tvbuf:len() < DNS_HDR_LEN then
+ return false
+ end
+
+ local tvbr = tvbuf:range(0,DNS_HDR_LEN)
+
+ -- the first 2 bytes are tansaction id, which can be anything so no point in checking those
+ -- the next 2 bytes contain flags, a couple of which have some values we can check against
+
+ -- the opcode has to be 0, 1, 2, 4 or 5
+ -- the opcode field starts at bit offset 17 (in C-indexing), for 4 bits in length
+ local check = tvbr:bitfield(17,4)
+ if check == 3 or check > 5 then
+ return false
+ end
+
+ -- the rcode has to be 0-10, 16-22 (we're ignoring private use rcodes here)
+ -- the rcode field starts at bit offset 28 (in C-indexing), for 4 bits in length
+ check = tvbr:bitfield(28,4)
+ if check > 22 or (check > 10 and check < 16) then
+ return false
+ end
+
+ -- now let's verify the number of questions/answers are reasonable
+ check = tvbr:range(4,2):uint() -- num questions
+ if check > 100 then return false end
+ check = tvbr:range(6,2):uint() -- num answers
+ if check > 100 then return false end
+ check = tvbr:range(8,2):uint() -- num authority
+ if check > 100 then return false end
+ check = tvbr:range(10,2):uint() -- num additional
+ if check > 100 then return false end
+
+ -- don't do this line in your script - I'm just doing it so our testsuite can
+ -- verify this script
+ root:add("Heuristic dissector used"):set_generated()
+
+ -- ok, looks like it's ours, so go dissect it
+ -- note: calling the dissector directly like this is new in 1.11.3
+ -- also note that calling a Dissector objkect, as this does, means we don't
+ -- get back the return value of the dissector function we created previously
+ -- so it might be better to just call the function directly instead of doing
+ -- this, but this script is used for testing and this tests the call() function
+ dns.dissector(tvbuf,pktinfo,root)
+
+ -- since this is over a transport protocol, such as UDP, we can set the
+ -- conversation to make it sticky for our dissector, so that all future
+ -- packets to/from the same address:port pair will just call our dissector
+ -- function directly instead of this heuristic function
+ -- this is a new attribute of pinfo in 1.11.3
+ pktinfo.conversation = dns
+
+ return true
+end
+
+-- now register that heuristic dissector into the udp heuristic list
+dns:register_heuristic("udp",heur_dissect_dns)
+
-- We're done!
-- our protocol (Proto) gets automatically registered after this script finishes loading
----------------------------------------
diff --git a/test/lua/verify_dissector.lua b/test/lua/verify_dissector.lua
index 0494f22f0a..f65d1ece72 100644
--- a/test/lua/verify_dissector.lua
+++ b/test/lua/verify_dissector.lua
@@ -61,7 +61,10 @@ local lines = {
}
}
-local numtests = #lines[1] + #lines[2]
+-- we're going to see those two sets of output twice: both by the normal
+-- dissector, then the first one by the heuristic, then the second one by
+-- a conversation match
+local numtests = 1 + (2 * (#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
@@ -71,6 +74,7 @@ local line = file:read()
local pktidx = 1
local total = 0
+local found = false
while line do
-- eat beginning whitespace
@@ -79,6 +83,18 @@ while line do
pktidx = line:match("^Frame (%d+):")
testing("Frame "..pktidx)
pktidx = tonumber(pktidx)
+ if pktidx > 2 then pktidx = pktidx - 2 end
+ line = file:read()
+ elseif line:find("%[Heuristic dissector used%]") then
+ -- start again, because it now repeats
+ -- but we should not see this [Heuristic dissector used] line again
+ -- or it's an error in setting the conversation
+ if found then
+ error("Heuristic dissector ran twice - conversation setting not working?")
+ return
+ end
+ found = true
+ total = total + 1
line = file:read()
elseif line == lines[pktidx][1] then
-- we've matched the first line of our section
diff --git a/test/suite-wslua.sh b/test/suite-wslua.sh
index f734f41aea..fa682b08d3 100755
--- a/test/suite-wslua.sh
+++ b/test/suite-wslua.sh
@@ -39,7 +39,7 @@ wslua_step_dissector_test() {
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
+ $TSHARK -r $CAPTURE_DIR/empty.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
@@ -121,7 +121,7 @@ wslua_step_proto_test() {
return
fi
- # First run tshark with the dissector script.
+ # First run tshark with the proto script.
$TSHARK -r $CAPTURE_DIR/dns_port.pcap -V -X lua_script:$TESTS_DIR/lua/proto.lua > testin.txt 2>&1
grep -q "All tests passed!" testin.txt
if [ $? -ne 0 ]; then
@@ -130,7 +130,7 @@ wslua_step_proto_test() {
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
+ $TSHARK -r $CAPTURE_DIR/empty.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
@@ -148,7 +148,7 @@ wslua_step_int64_test() {
fi
# Tshark catches lua script failures, so we have to parse the output.
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/int64.lua > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/int64.lua > testout.txt 2>&1
if grep -q "All tests passed!" testout.txt; then
test_step_ok
else
@@ -165,30 +165,30 @@ wslua_step_args_test() {
fi
# Tshark catches lua script failures, so we have to parse the output.
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:1 > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:1 > testout.txt 2>&1
grep -q "All tests passed!" testout.txt
if [ $? -ne 0 ]; then
cat testout.txt
test_step_failed "lua_args_test test 1 failed"
fi
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 -X lua_script1:foo -X lua_script1:bar > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 -X lua_script1:foo -X lua_script1:bar > testout.txt 2>&1
grep -q "All tests passed!" testout.txt
if [ $? -ne 0 ]; then
cat testout.txt
test_step_failed "lua_args_test test 2 failed"
fi
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 -X lua_script2:1 -X lua_script1:foo -X lua_script1:bar > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 -X lua_script2:1 -X lua_script1:foo -X lua_script1:bar > testout.txt 2>&1
grep -q "All tests passed!" testout.txt
if [ $? -ne 0 ]; then
cat testout.txt
test_step_failed "lua_args_test test 3 failed"
fi
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua > testout.txt 2>&1
if grep -q "All tests passed!" testout.txt; then
cat testout.txt
test_step_failed "lua_args_test negative test 4 failed"
fi
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/script_args.lua -X lua_script1:3 > testout.txt 2>&1
if grep -q "All tests passed!" testout.txt; then
cat testout.txt
test_step_failed "lua_args_test negative test 5 failed"
@@ -241,7 +241,7 @@ wslua_step_struct_test() {
fi
# Tshark catches lua script failures, so we have to parse the output.
- $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/struct.lua > testout.txt 2>&1
+ $TSHARK -r $CAPTURE_DIR/empty.pcap -X lua_script:$TESTS_DIR/lua/struct.lua > testout.txt 2>&1
if grep -q "All tests passed!" testout.txt; then
test_step_ok
else