Fix bug 6357: Lua all_field_infos() broken within tap/listener

The current API for Lua provides a global function
"all_field_infos()" which returns all the populated field_info nodes
in the current proto_tree.

By default all_field_infos() "works", in the literal sense: it returns
exactly the fields the previous dissectors of the packet have
populated at that instant of time.  But of course dissectors don't
populate all the applicable fields most of the time, because of the
TRY_TO_FAKE_THIS_ITEM optimization where they don't fill in things
that aren't needed at the time by a display, color, or tap's dfilter.

So this commit offers a way to force the dissectors to populate
all the applicable field_infos in the tree, by setting the proto_tree
to be visible.  Obviously that is going to impact performance, since
it basically bypasses the TRY_TO_FAKE_THIS_ITEM optimization; so the
patch only does this if the Lua script author told it to explicitly,
by adding an argument to Listener.new() and register_postdissector().

Change-Id: I11d3559fbe8c14fbadf1b51415a3701dc1200b7b
Reviewed-on: https://code.wireshark.org/review/286
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
This commit is contained in:
Hadriel Kaplan 2014-02-21 01:11:41 -05:00 committed by Alexis La Goutte
parent 860747e1e7
commit bd36fe1bcb
6 changed files with 119 additions and 8 deletions

View File

@ -229,6 +229,21 @@ epan_circuit_cleanup(void)
circuit_cleanup();
}
/* Overrides proto_tree_visible i epan_dissect_init to make all fields visible.
* This is > 0 if a Lua script wanted to see all fields all the time.
* This is ref-counted, so clearing it won't override other taps/scripts wanting it.
*/
static gint always_visible_refcount = 0;
void
epan_set_always_visible(gboolean force)
{
if (force)
always_visible_refcount++;
else if (always_visible_refcount > 0)
always_visible_refcount--;
}
epan_dissect_t*
epan_dissect_init(epan_dissect_t *edt, epan_t *session, const gboolean create_proto_tree, const gboolean proto_tree_visible)
{
@ -247,7 +262,7 @@ epan_dissect_init(epan_dissect_t *edt, epan_t *session, const gboolean create_pr
if (create_proto_tree) {
edt->tree = proto_tree_create_root(&edt->pi);
proto_tree_set_visible(edt->tree, proto_tree_visible);
proto_tree_set_visible(edt->tree, (always_visible_refcount > 0) ? TRUE : proto_tree_visible);
}
else {
edt->tree = NULL;

View File

@ -145,6 +145,18 @@ WS_DLL_PUBLIC void epan_free(epan_t *session);
WS_DLL_PUBLIC const gchar*
epan_get_version(void);
/**
* Set/unset the tree to always be visible when epan_dissect_init() is called.
* This state change sticks until cleared, rather than being done per function call.
* This is currently used when Lua scripts request all fields be generated.
* By default it only becomes visible if epan_dissect_init() makes it so, usually
* only when a packet is selected.
* Setting this overrides that so it's always visible, although it will still not be
* created if create_proto_tree is false in the call to epan_dissect_init().
* Clearing this reverts the decision to epan_dissect_init() and proto_tree_visible.
*/
void epan_set_always_visible(gboolean force);
/** initialize an existing single packet dissection */
WS_DLL_PUBLIC
epan_dissect_t*

View File

@ -199,6 +199,7 @@ struct _wslua_tap {
int packet_ref;
int draw_ref;
int reset_ref;
gboolean all_fields;
};
# define DIRECTORY_T GDir

View File

@ -192,9 +192,11 @@ WSLUA_CONSTRUCTOR Listener_new(lua_State* L) {
/* Creates a new Listener listener */
#define WSLUA_OPTARG_Listener_new_TAP 1 /* The name of this tap */
#define WSLUA_OPTARG_Listener_new_FILTER 2 /* A filter that when matches the tap.packet function gets called (use nil to be called for every packet) */
#define WSLUA_OPTARG_Listener_new_ALLFIELDS 3 /* Whether to generate all fields. Note: this impacts performance (default=false) */
const gchar* tap_type = luaL_optstring(L,WSLUA_OPTARG_Listener_new_TAP,"frame");
const gchar* filter = luaL_optstring(L,WSLUA_OPTARG_Listener_new_FILTER,NULL);
const gboolean all_fields = wslua_optbool(L, WSLUA_OPTARG_Listener_new_ALLFIELDS, FALSE);
Listener tap;
GString* error;
@ -207,6 +209,7 @@ WSLUA_CONSTRUCTOR Listener_new(lua_State* L) {
tap->packet_ref = LUA_NOREF;
tap->draw_ref = LUA_NOREF;
tap->reset_ref = LUA_NOREF;
tap->all_fields = all_fields;
/*
* XXX - do all Lua taps require the protocol tree? If not, it might
@ -226,6 +229,10 @@ WSLUA_CONSTRUCTOR Listener_new(lua_State* L) {
g_string_free(error,TRUE); /* XXX LEAK? */
}
if (all_fields) {
epan_set_always_visible(TRUE);
}
pushListener(L,tap);
WSLUA_RETURN(1); /* The newly created Listener listener object */
}
@ -263,6 +270,11 @@ WSLUA_METHOD Listener_remove(lua_State* L) {
/* Removes a tap listener */
Listener tap = checkListener(L,1);
if (tap->all_fields) {
epan_set_always_visible(FALSE);
tap->all_fields = FALSE;
}
remove_tap_listener(tap);
return 0;

View File

@ -1403,8 +1403,6 @@ static int Proto__tostring(lua_State* L) {
Proto proto = checkProto(L,1);
gchar* s;
if (!proto) return 0;
s = ep_strdup_printf("Proto: %s",proto->name);
lua_pushstring(L,s);
@ -1414,8 +1412,10 @@ static int Proto__tostring(lua_State* L) {
WSLUA_FUNCTION wslua_register_postdissector(lua_State* L) {
/* Make a protocol (with a dissector) a postdissector. It will be called for every frame after dissection */
#define WSLUA_ARG_register_postdissector_PROTO 1 /* the protocol to be used as postdissector */
#define WSLUA_OPTARG_register_postdissector_ALLFIELDS 2 /* Whether to generate all fields. Note: this impacts performance (default=false) */
Proto proto = checkProto(L,WSLUA_ARG_register_postdissector_PROTO);
if (!proto) return 0;
const gboolean all_fields = wslua_optbool(L, WSLUA_OPTARG_register_postdissector_ALLFIELDS, FALSE);
if(!proto->is_postdissector) {
if (! proto->handle) {
@ -1427,6 +1427,10 @@ WSLUA_FUNCTION wslua_register_postdissector(lua_State* L) {
luaL_argerror(L,1,"this protocol is already registered as postdissector");
}
if (all_fields) {
epan_set_always_visible(TRUE);
}
return 0;
}

View File

@ -8,6 +8,7 @@ local ETH = "eth"
local IP = "ip"
local BOOTP = "bootp"
local OTHER = "other"
local PDISS = "postdissector"
local packet_counts = {}
local function incPktCount(name)
@ -34,7 +35,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]=4, [ETH]=4, [IP]=3, [BOOTP]=2, [OTHER]=15 }
local taptests = { [FRAME]=4, [ETH]=4, [IP]=3, [BOOTP]=2, [OTHER]=16 }
local function getResults()
print("\n-----------------------------\n")
for k,v in pairs(taptests) do
@ -64,6 +65,49 @@ local function test(type,name, ...)
end
end
local pkt_fields = { [FRAME] = {}, [PDISS] = {} }
local function getAllFieldInfos(type)
local fields = { all_field_infos() }
local fieldnames = {}
for i,v in ipairs(fields) do
fieldnames[i] = v.name
end
local pktnum = getPktCount(type)
pkt_fields[type][pktnum] = { ["num"] = #fields, ["fields"] = fieldnames }
end
local function dumpAllFieldInfos()
for i,v in ipairs(pkt_fields[FRAME]) do
print("In frame tap for packet ".. i ..":")
print(" number of fields = ".. v.num)
for _,name in ipairs(v.fields) do
print(" field = ".. name)
end
local w = pkt_fields[PDISS][i]
print("In postdissector for packet ".. i ..":")
print(" number of fields = ".. w.num)
for _,name in ipairs(w.fields) do
print(" field = ".. name)
end
end
end
local function checkAllFieldInfos()
for i,v in ipairs(pkt_fields[FRAME]) do
local numfields = v.num
if numfields ~= pkt_fields[PDISS][i].num then
print("Tap and postdissector do not have same number of fields!")
return false
end
if numfields < 100 then
print("Too few fields!")
return false
end
end
return true
end
---------
-- the following are so we can use pcall (which needs a function to call)
local function makeListener(...)
@ -124,7 +168,7 @@ local f_ip_dst = Field.new("ip.dst")
local f_bootp_hw = Field.new("bootp.hw.mac_addr")
local f_bootp_opt = Field.new("bootp.option.type")
local tap_frame = Listener.new()
local tap_frame = Listener.new(nil,nil,true)
local tap_eth = Listener.new("eth")
local tap_ip = Listener.new("ip","bootp")
local tap_bootp = Listener.new("bootp","bootp.option.dhcp == 1")
@ -146,6 +190,8 @@ function tap_frame.packet(pinfo,tvb,frame)
local eth_src2 = tostring(tvb:range(6,6))
test(FRAME,"FieldInfo.range-1", eth_src1 == eth_src2)
getAllFieldInfos(FRAME)
setPassed(FRAME)
end
@ -221,13 +267,34 @@ tap_bootp.packet = bootp_packet
function tap_frame.reset()
-- reset never gets called in tshark (sadly)
error("reset called!!")
if not GUI_ENABLED then
error("reset called!!")
end
end
function tap_frame.draw()
test(OTHER,"all_field_infos", checkAllFieldInfos())
setPassed(OTHER)
getResults()
end
-- max_gap.lua
-- create a gap.max field containing the maximum gap between two packets between two ip nodes
-- we create a "protocol" for our tree
local max_gap_p = Proto("gap","Gap in IP conversations")
-- we create our fields
local max_gap_field = ProtoField.float("gap.max")
-- we add our fields to the protocol
max_gap_p.fields = { max_gap_field }
-- then we register max_gap_p as a postdissector
register_postdissector(max_gap_p,true)
function max_gap_p.dissector(tvb,pinfo,tree)
incPktCount(PDISS)
getAllFieldInfos(PDISS)
end