[譯]用lua創(chuàng)建一個(gè)Wireshark解剖器系列5(模塊化)

在這里,我將解釋如何將代碼分成幾個(gè)模塊。在我的例子中,我將標(biāo)題和有效負(fù)載部分分成單獨(dú)的文件。

劃分代碼

我想為Header提供單獨(dú)的文件,OP_REPLY消息的文件,OP_QUERY消息的文件,幫助函數(shù)的文件和main文件。最終的文件結(jié)構(gòu)如下所示:


我創(chuàng)建一個(gè)名為mongodb的獨(dú)立文件夾。這將包含協(xié)議的所有模塊。主文件仍然直接存在于plugins中。

加載文件

在Lua中,我們通常有兩個(gè)可以加載文件的函數(shù):dofilerequire。我將使用require,原因如下
Lua提供了一個(gè)更高級(jí)的函數(shù)來加載和運(yùn)行庫(kù),稱為require。粗略地說,requiredofile完成相同的工作,但有兩個(gè)重要的區(qū)別。首先,require在路徑中搜索文件;第二,require控制是否已運(yùn)行文件以避免重復(fù)工作。由于這些功能,require是Lua中用于加載庫(kù)的首選功能。
除了require我們必須使用package.prepend_path()。package.path是Wireshark查找文件的地方。 prepend_path將為package.path添加一個(gè)新路徑。在我的特定情況下,工作目錄是Wireshark根目錄,這意味著我必須將“plugins / mongodb”添加到package.path。如果您使用的是另一個(gè)操作系統(tǒng),那么您可能有另一條路徑,或者將文件放在另一個(gè)文件夾中(例如用戶插件目錄而不是全局插件目錄)。
要導(dǎo)入模塊,我們必須在主文件的開頭添加以下內(nèi)容:

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

如前所述,prepend_path()行將使Wireshark能夠在plugins / mongodb目錄中查找文件,而require行將導(dǎo)入header.lua中的代碼。不應(yīng)包含文件結(jié)尾。正如我們進(jìn)一步看到的那樣,我在header.lua中“導(dǎo)出”一個(gè)Table(對(duì)象),我們可以在主文件中使用點(diǎn)表示法:local var = header.myFunction()。

創(chuàng)建header.lua

我正在將一些與頭文件相關(guān)的代碼從主文件移到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

我已經(jīng)將mongodb.lua中的get_opcode_name()移動(dòng)到header.lua。我還創(chuàng)建了一個(gè)名為m的Table(對(duì)象),我創(chuàng)建了一個(gè)名為parse()的新函數(shù)。 parse()函數(shù)包含之前在主文件中的頭字段解析邏輯。因?yàn)?code>headerSubtree是一個(gè)引用類型,所以我不必從函數(shù)中返回它:在從parse()返回后它仍然會(huì)被修改。但是,我需要在主文件中使用opcode_name,所以我會(huì)返回它。表m從文件返回,因此可以在主文件中使用。我們不必將get_opcode_name()添加到m,因?yàn)樗鼉H在header.lua中使用。
在取出一些Header內(nèi)容后,主文件看起來像這樣:

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)

注意對(duì)header.parse()的調(diào)用。如上所述,我返回opcode_name,因?yàn)槲倚枰诖a中進(jìn)一步向下。子樹headerSubtree也將被修改(添加到它的字段),因?yàn)樗且粋€(gè)引用類型,因此在parse()中是可變的。

創(chuàng)建OP_QUERY.lua 和 OP_REPLY.lua

如您所見,主文件中仍有很多Header內(nèi)容可以移動(dòng)到Header模塊中。稍后我會(huì)移動(dòng)它,但首先我要對(duì)OP_QUERYOP_REPLY解析代碼執(zhí)行相同的操作,就像使用Header代碼一樣
我正在制作OP_QUERY.lua并將get_flag_description()OP_QUERY解析邏輯移動(dòng)過去:

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解析邏輯移動(dòng)過去:

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)

當(dāng)然,我們必須require這兩個(gè)新文件。

移動(dòng)字段創(chuàng)建代碼

主文件mongodb.lua現(xiàn)在看起來更干凈了。仍有HeaderOP_QUERYOP_REPLY相關(guān)邏輯可以移動(dòng)到各自的文件中。移動(dòng)字段創(chuàng)建代碼后,我們也可以擺脫對(duì)三個(gè)parse()方法的笨拙調(diào)用。它們包含的參數(shù)太多,應(yīng)該從模塊中了解到。
讓我們?cè)谀K中移動(dòng)字段創(chuàng)建代碼。這是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()函數(shù)用于在模塊外部訪問它們。另請(qǐng)注意,parse()函數(shù)通過模塊本身訪問字段,而不是將它們作為參數(shù)傳遞。
我還將OP_QUERYOP_REPLY的字段移動(dòng)到各自的模塊中。主文件現(xiàn)在看起來像這樣:

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)

您可以看到所有字段初始化代碼都消失了。我們?nèi)匀恍枰趍ongodb_protocol.fields中插入字段,這就是我們用get_fields()獲取它們的原因。我使用一個(gè)名為merge_tables()的輔助函數(shù)將三個(gè)表合并在一起。我把這個(gè)函數(shù)放在一個(gè)名為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上找到了代碼。

最后,您可以看到對(duì)parse()的調(diào)用已縮短為:

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

我們不必再傳遞所有字段變量,因?yàn)樗鼈円呀?jīng)被放入模塊本身。
完整實(shí)現(xiàn)
因此,您可以將代碼分成幾個(gè)文件。

  • 使用package.prepend_path()將目錄添加到包路徑。
  • 使用require()從另一個(gè)文件中讀取代碼。
  • 在模塊文件中創(chuàng)建一個(gè)名為m的表(對(duì)象)或任何你想要的。
  • 向表中添加方法和變量。
  • 返回您創(chuàng)建的表并在主文件中使用它。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容