[译]用lua创建一个Wireshark解剖器系列5(模块化)

在这里,我将解释如何将代码分成几个模块。在我的例子中,我将标题和有效负载部分分成单独的文件。

划分代码

我想为Header提供单独的文件,OP_REPLY消息的文件,OP_QUERY消息的文件,帮助函数的文件和main文件。最终的文件结构如下所示:


我创建一个名为mongodb的独立文件夹。这将包含协议的所有模块。主文件仍然直接存在于plugins中。

加载文件

在Lua中,我们通常有两个可以加载文件的函数:dofilerequire。我将使用require,原因如下
Lua提供了一个更高级的函数来加载和运行库,称为require。粗略地说,requiredofile完成相同的工作,但有两个重要的区别。首先,require在路径中搜索文件;第二,require控制是否已运行文件以避免重复工作。由于这些功能,require是Lua中用于加载库的首选功能。
除了require我们必须使用package.prepend_path()package.path是Wireshark查找文件的地方。 prepend_path将为package.path添加一个新路径。在我的特定情况下,工作目录是Wireshark根目录,这意味着我必须将“plugins / mongodb”添加到package.path。如果您使用的是另一个操作系统,那么您可能有另一条路径,或者将文件放在另一个文件夹中(例如用户插件目录而不是全局插件目录)。
要导入模块,我们必须在主文件的开头添加以下内容:

package.prepend_path("plugins/mongodb")
local header = require("header")

如前所述,prepend_path()行将使Wireshark能够在plugins / mongodb目录中查找文件,而require行将导入header.lua中的代码。不应包含文件结尾。正如我们进一步看到的那样,我在header.lua中“导出”一个Table(对象),我们可以在主文件中使用点表示法:local var = header.myFunction()

创建header.lua

我正在将一些与头文件相关的代码从主文件移到header.lua文件中:

function get_opcode_name(opcode)
    local opcode_name = "Unknown"

        if opcode ==    1 then opcode_name = "OP_REPLY"
    elseif opcode == 2001 then opcode_name = "OP_UPDATE"
    elseif opcode == 2002 then opcode_name = "OP_INSERT"
    elseif opcode == 2003 then opcode_name = "RESERVED"
    elseif opcode == 2004 then opcode_name = "OP_QUERY"
    elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
    elseif opcode == 2006 then opcode_name = "OP_DELETE"
    elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
    elseif opcode == 2010 then opcode_name = "OP_COMMAND"
    elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end

    return opcode_name
end

local m = {}

function m.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)
    headerSubtree:add_le(message_length, buffer(0,4))
    headerSubtree:add_le(request_id,     buffer(4,4))
    headerSubtree:add_le(response_to,    buffer(8,4))

    local opcode_number = buffer(12,4):le_uint()
    local opcode_name = get_opcode_name(opcode_number)
    headerSubtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")

    return opcode_name
end

return m

我已经将mongodb.lua中的get_opcode_name()移动到header.lua。我还创建了一个名为m的Table(对象),我创建了一个名为parse()的新函数。 parse()函数包含之前在主文件中的头字段解析逻辑。因为headerSubtree是一个引用类型,所以我不必从函数中返回它:在从parse()返回后它仍然会被修改。但是,我需要在主文件中使用opcode_name,所以我会返回它。表m从文件返回,因此可以在主文件中使用。我们不必将get_opcode_name()添加到m,因为它仅在header.lua中使用。
在取出一些Header内容后,主文件看起来像这样:

package.prepend_path("plugins/mongodb")
local header = require("header")

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length  = ProtoField.int32 ("mongodb.message_length"  , "messageLength"     , base.DEC)
request_id      = ProtoField.int32 ("mongodb.requestid"       , "requestID"         , base.DEC)
response_to     = ProtoField.int32 ("mongodb.responseto"      , "responseTo"        , base.DEC)
opcode          = ProtoField.int32 ("mongodb.opcode"          , "opCode"            , base.DEC)

-- Payload fields
flags           = ProtoField.int32 ("mongodb.flags"           , "flags"             , base.DEC)
full_coll_name  = ProtoField.string("mongodb.full_coll_name"  , "fullCollectionName", base.ASCII)
number_to_skip  = ProtoField.int32 ("mongodb.number_to_skip"  , "numberToSkip"      , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn"    , base.DEC)
query           = ProtoField.none  ("mongodb.query"           , "query"             , base.HEX)

response_flags  = ProtoField.int32 ("mongodb.response_flags"  , "responseFlags"     , base.DEC)
cursor_id       = ProtoField.int64 ("mongodb.cursor_id"       , "cursorId"          , base.DEC)
starting_from   = ProtoField.int32 ("mongodb.starting_from"   , "startingFrom"      , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned"    , base.DEC)
documents       = ProtoField.none  ("mongodb.documents"       , "documents"         , base.HEX)

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,                     -- Header
  flags, full_coll_name, number_to_skip, number_to_return, query,      -- OP_QUERY
  response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
    length = buffer:len()
    if length == 0 then return end

    pinfo.cols.protocol = mongodb_protocol.name

    local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
    local headerSubtree = subtree:add(mongodb_protocol, buffer(), "Header")
    local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")

    -- Header
    local opcode_name = header.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)

    -- Payload
    if opcode_name == "OP_QUERY" then
        local flags_number = buffer(16,4):le_uint()
        local flags_description = get_flag_description(flags_number)
        payloadSubtree:add_le(flags,      buffer(16,4)):append_text(" (" .. flags_description .. ")")

        -- Loop over string
        local string_length
        for i = 20, length - 1, 1 do
            if (buffer(i,1):le_uint() == 0) then
                string_length = i - 20
                break
            end
        end

        payloadSubtree:add_le(full_coll_name,   buffer(20,string_length))
        payloadSubtree:add_le(number_to_skip,   buffer(20+string_length,4))
        payloadSubtree:add_le(number_to_return, buffer(24+string_length,4))
        payloadSubtree:add_le(query,            buffer(28+string_length,length-string_length-28))
    elseif opcode_name == "OP_REPLY" then
        local response_flags_number = buffer(16,4):le_uint()
        local response_flags_description = get_response_flag_description(response_flags_number)

        payloadSubtree:add_le(response_flags,   buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
        payloadSubtree:add_le(cursor_id,        buffer(20,8))
        payloadSubtree:add_le(starting_from,    buffer(28,4))
        payloadSubtree:add_le(number_returned,  buffer(32,4))
        payloadSubtree:add_le(documents,        buffer(36,length-36))
    end
end

function get_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "Reserved"
    elseif flags == 1 then flags_description = "TailableCursor"
    elseif flags == 2 then flags_description = "SlaveOk.Allow"
    elseif flags == 3 then flags_description = "OplogReplay"
    elseif flags == 4 then flags_description = "NoCursorTimeout"
    elseif flags == 5 then flags_description = "AwaitData"
    elseif flags == 6 then flags_description = "Exhaust"
    elseif flags == 7 then flags_description = "Partial"
    elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

function get_response_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "CursorNotFound"
    elseif flags == 1 then flags_description = "QueryFailure"
    elseif flags == 2 then flags_description = "ShardConfigStale"
    elseif flags == 3 then flags_description = "AwaitCapable"
    elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

注意对header.parse()的调用。如上所述,我返回opcode_name,因为我需要在代码中进一步向下。子树headerSubtree也将被修改(添加到它的字段),因为它是一个引用类型,因此在parse()中是可变的。

创建OP_QUERY.lua 和 OP_REPLY.lua

如您所见,主文件中仍有很多Header内容可以移动到Header模块中。稍后我会移动它,但首先我要对OP_QUERYOP_REPLY解析代码执行相同的操作,就像使用Header代码一样
我正在制作OP_QUERY.lua并将get_flag_description()OP_QUERY解析逻辑移动过去:

function get_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "Reserved"
    elseif flags == 1 then flags_description = "TailableCursor"
    elseif flags == 2 then flags_description = "SlaveOk.Allow"
    elseif flags == 3 then flags_description = "OplogReplay"
    elseif flags == 4 then flags_description = "NoCursorTimeout"
    elseif flags == 5 then flags_description = "AwaitData"
    elseif flags == 6 then flags_description = "Exhaust"
    elseif flags == 7 then flags_description = "Partial"
    elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

local m = {}

function m.parse(payloadSubtree, buffer, length, flags, full_coll_name, number_to_skip, number_to_return, query)
    local flags_number = buffer(16,4):le_uint()
    local flags_description = get_flag_description(flags_number)
    payloadSubtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")

    -- Loop over string
    local string_length
    for i = 20, length - 1, 1 do
        if (buffer(i,1):le_uint() == 0) then
            string_length = i - 20
            break
        end
    end

    payloadSubtree:add_le(full_coll_name,   buffer(20,string_length))
    payloadSubtree:add_le(number_to_skip,   buffer(20+string_length,4))
    payloadSubtree:add_le(number_to_return, buffer(24+string_length,4))
    payloadSubtree:add_le(query,            buffer(28+string_length,length-string_length-28))
end

return m

我也在制作OP_REPLY.lua并将get_response_flag_description()OP_REPLY解析逻辑移动过去:

function get_response_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "CursorNotFound"
    elseif flags == 1 then flags_description = "QueryFailure"
    elseif flags == 2 then flags_description = "ShardConfigStale"
    elseif flags == 3 then flags_description = "AwaitCapable"
    elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

local m = {}

function m.parse(payloadSubtree, buffer, response_flags, cursor_id, starting_from, number_returned, documents)
    local response_flags_number = buffer(16,4):le_uint()
    local response_flags_description = get_response_flag_description(response_flags_number)

    payloadSubtree:add_le(response_flags,  buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
    payloadSubtree:add_le(cursor_id,       buffer(20,8))
    payloadSubtree:add_le(starting_from,   buffer(28,4))
    payloadSubtree:add_le(number_returned, buffer(32,4))
    payloadSubtree:add_le(documents,       buffer(36,length-36))
end

return m

主文件如下:

package.prepend_path("plugins/mongodb")
local header   = require("header")
local op_query = require("OP_QUERY")
local op_reply = require("OP_REPLY")

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length  = ProtoField.int32 ("mongodb.message_length"  , "messageLength"     , base.DEC)
request_id      = ProtoField.int32 ("mongodb.requestid"       , "requestID"         , base.DEC)
response_to     = ProtoField.int32 ("mongodb.responseto"      , "responseTo"        , base.DEC)
opcode          = ProtoField.int32 ("mongodb.opcode"          , "opCode"            , base.DEC)

-- Payload fields
flags           = ProtoField.int32 ("mongodb.flags"           , "flags"             , base.DEC)
full_coll_name  = ProtoField.string("mongodb.full_coll_name"  , "fullCollectionName", base.ASCII)
number_to_skip  = ProtoField.int32 ("mongodb.number_to_skip"  , "numberToSkip"      , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn"    , base.DEC)
query           = ProtoField.none  ("mongodb.query"           , "query"             , base.HEX)

response_flags  = ProtoField.int32 ("mongodb.response_flags"  , "responseFlags"     , base.DEC)
cursor_id       = ProtoField.int64 ("mongodb.cursor_id"       , "cursorId"          , base.DEC)
starting_from   = ProtoField.int32 ("mongodb.starting_from"   , "startingFrom"      , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned"    , base.DEC)
documents       = ProtoField.none  ("mongodb.documents"       , "documents"         , base.HEX)

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,                     -- Header
  flags, full_coll_name, number_to_skip, number_to_return, query,      -- OP_QUERY
  response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
    length = buffer:len()
    if length == 0 then return end

    pinfo.cols.protocol = mongodb_protocol.name

    local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
    local headerSubtree = subtree:add(mongodb_protocol, buffer(), "Header")
    local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")

    -- Header
    local opcode_name = header.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)

    -- Payload
    if opcode_name == "OP_QUERY" then
        op_query.parse(payloadSubtree, buffer, length, flags, full_coll_name, number_to_skip, number_to_return, query)
    elseif opcode_name == "OP_REPLY" then
        op_reply.parse(payloadSubtree, buffer, response_flags, cursor_id, starting_from, number_returned, documents)
    end
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

当然,我们必须require这两个新文件。

移动字段创建代码

主文件mongodb.lua现在看起来更干净了。仍有HeaderOP_QUERYOP_REPLY相关逻辑可以移动到各自的文件中。移动字段创建代码后,我们也可以摆脱对三个parse()方法的笨拙调用。它们包含的参数太多,应该从模块中了解到。
让我们在模块中移动字段创建代码。这是header.lua:

function get_opcode_name(opcode)
    local opcode_name = "Unknown"

        if opcode ==    1 then opcode_name = "OP_REPLY"
    elseif opcode == 2001 then opcode_name = "OP_UPDATE"
    elseif opcode == 2002 then opcode_name = "OP_INSERT"
    elseif opcode == 2003 then opcode_name = "RESERVED"
    elseif opcode == 2004 then opcode_name = "OP_QUERY"
    elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
    elseif opcode == 2006 then opcode_name = "OP_DELETE"
    elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
    elseif opcode == 2010 then opcode_name = "OP_COMMAND"
    elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end

    return opcode_name
end

local m = {
    message_length = ProtoField.int32("mongodb.message_length", "messageLength", base.DEC),
    request_id     = ProtoField.int32("mongodb.requestid"     , "requestID"    , base.DEC),
    response_to    = ProtoField.int32("mongodb.responseto"    , "responseTo"   , base.DEC),
    opcode         = ProtoField.int32("mongodb.opcode"        , "opCode"       , base.DEC)
}

function m.get_fields()
    local fields = {
        message_length = m.message_length,
        request_id = m.request_id,
        response_to = m.response_to,
        opcode = m.opcode
    }

    return fields
end

function m.parse(headerSubtree, buffer)
    headerSubtree:add_le(m.message_length, buffer(0,4))
    headerSubtree:add_le(m.request_id,     buffer(4,4))
    headerSubtree:add_le(m.response_to,    buffer(8,4))

    local opcode_number = buffer(12,4):le_uint()
    local opcode_name = get_opcode_name(opcode_number)
    headerSubtree:add_le(m.opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")

    return opcode_name
end

return m

这些字段作为m表的成员存在。 get_fields()函数用于在模块外部访问它们。另请注意,parse()函数通过模块本身访问字段,而不是将它们作为参数传递。
我还将OP_QUERYOP_REPLY的字段移动到各自的模块中。主文件现在看起来像这样:

package.prepend_path("plugins/mongodb")
local helpers  = require("helpers")
local header   = require("header")
local op_query = require("OP_QUERY")
local op_reply = require("OP_REPLY")

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

local   header_fields =   header.get_fields()
local op_query_fields = op_query.get_fields()
local op_reply_fields = op_reply.get_fields()

helpers.merge_tables(  header_fields, mongodb_protocol.fields)
helpers.merge_tables(op_query_fields, mongodb_protocol.fields)
helpers.merge_tables(op_reply_fields, mongodb_protocol.fields)

function mongodb_protocol.dissector(buffer, pinfo, tree)
    length = buffer:len()
    if length == 0 then return end

    pinfo.cols.protocol = mongodb_protocol.name

    local       subtree  =    tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
    local headerSubtree  = subtree:add(mongodb_protocol, buffer(), "Header")
    local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")

    local opcode_name = header.parse(headerSubtree, buffer)

        if opcode_name == "OP_QUERY" then op_query.parse(payloadSubtree, buffer, length)
    elseif opcode_name == "OP_REPLY" then op_reply.parse(payloadSubtree, buffer) end
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

您可以看到所有字段初始化代码都消失了。我们仍然需要在mongodb_protocol.fields中插入字段,这就是我们用get_fields()获取它们的原因。我使用一个名为merge_tables()的辅助函数将三个表合并在一起。我把这个函数放在一个名为helpers.lua的模块中。它看起来像这样:

local m = {}

-- Made by Doug Currie (https://stackoverflow.com/users/33252/doug-currie)
-- on Stack Overflow. https://stackoverflow.com/questions/1283388/lua-merge-tables
function m.merge_tables(from, to)
    for k,v in pairs(from) do to[k] = v end
end

return m

如您所见,我在Stack Overflow上找到了代码。

最后,您可以看到对parse()的调用已缩短为:

local opcode_name = header.parse(headerSubtree, buffer)
    if opcode_name == "OP_QUERY" then op_query.parse(payloadSubtree, buffer, length)
elseif opcode_name == "OP_REPLY" then op_reply.parse(payloadSubtree, buffer) end

我们不必再传递所有字段变量,因为它们已经被放入模块本身。
完整实现
因此,您可以将代码分成几个文件。

  • 使用package.prepend_path()将目录添加到包路径。
  • 使用require()从另一个文件中读取代码。
  • 在模块文件中创建一个名为m的表(对象)或任何你想要的。
  • 向表中添加方法和变量。
  • 返回您创建的表并在主文件中使用它。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容