Created
March 13, 2021 21:20
-
-
Save adamnew123456/e717eac8976e0e2bb1f7ed2b849635be to your computer and use it in GitHub Desktop.
A Wireshark dissector for the SANE network protocol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <https://www.gnu.org/licenses/>. | |
--]] | |
--[[ | |
SANE Network Protocol Dissector | |
------------------------------- | |
This is a protocol dissector for Wireshark which is capable of dissecting | |
the network traffic of the SANE daemon. The network protocol is documented | |
by the SANE project itself, but note that you may want to have a copy of | |
the libsane header files handy for reference in case you encounter any | |
structs that are not included in the protocol docs: | |
http://www.sane-project.org/html/doc015.html | |
https://github.com/chadjoan/libsane/blob/master/c/sane.h | |
Currently only the control protocol is supported. The image protocol is | |
primarily just blobs of pixel data, so decoding it is less interesting | |
unless you want to capture images. | |
--]] | |
-- The types of messages the client can send to the server. All communication is | |
-- initiated by the client in the SANE protocol. | |
local rpc_enum = { | |
[0] = "SANE_NET_INIT", | |
[1] = "SANE_NET_GET_DEVICES", | |
[2] = "SANE_NET_OPEN", | |
[3] = "SANE_NET_CLOSE", | |
[4] = "SANE_NET_GET_OPTION_DESCRIPTORS", | |
[5] = "SANE_NET_CONTROL_OPTION", | |
[6] = "SANE_NET_GET_PARAMETERS", | |
[7] = "SANE_NET_START", | |
[8] = "SANE_NET_CANCEL", | |
[9] = "SANE_NET_AUTHORIZE", | |
[10] = "SANE_NET_EXIT", | |
} | |
-- Status codes returned by the server | |
local status_enum = { | |
[0] = "SANE_STATUS_GOOD", | |
[1] = "SANE_STATUS_UNSUPPORTED", | |
[2] = "SANE_STATUS_CANCELLED", | |
[3] = "SANE_STATUS_DEVICE_BUSY", | |
[4] = "SANE_STATUS_INVAL", | |
[5] = "SANE_STATUS_EOF", | |
[6] = "SANE_STATUS_JAMMED", | |
[7] = "SANE_STATUS_NO_DOCS", | |
[8] = "SANE_STATUS_COVER_OPEN", | |
[9] = "SANE_STATUS_IO_ERROR", | |
[10] = "SANE_STATUS_NO_MEM", | |
[11] = "SANE_STATUS_ACCESS_DENIED", | |
} | |
-- The types used for property values | |
local value_type_enum = { | |
[0] = "SANE_TYPE_BOOL", | |
[1] = "SANE_TYPE_INT", | |
[2] = "SANE_TYPE_FIXED", | |
[3] = "SANE_TYPE_STRING", | |
[4] = "SANE_TYPE_BUTTON", | |
[5] = "SANE_TYPE_GROUP", | |
} | |
-- The units supported for property values | |
local value_unit_enum = { | |
[0] = "SANE_UNIT_NONE", | |
[1] = "SANE_UNIT_PIXEL", | |
[2] = "SANE_UNIT_BIT", | |
[3] = "SANE_UNIT_MM", | |
[4] = "SANE_UNIT_DPI", | |
[5] = "SANE_UNIT_PERCENT", | |
[6] = "SANE_UNIT_MICROSECOND", | |
} | |
-- Constraints that determine the values that a property is allowed to have | |
local value_constraint_enum = { | |
[0] = "SANE_CONSTRAINT_NONE", | |
[1] = "SANE_CONSTRAINT_RANGE", | |
[2] = "SANE_CONSTRAINT_WORD_LIST", | |
[3] = "SANE_CONSTRAINT_STRING_LIST", | |
} | |
-- Flag used to determine the preferred endianness of the server with respect | |
-- to pixel data | |
local endian_enum = { | |
[0x1234] = "Little-Endian", | |
[0x4321] = "Big-Endian", | |
} | |
-- The layout of data used when retrieving scanned images | |
local format_enum = { | |
[0] = "SANE_FRAME_GRAY", | |
[1] = "SANE_FRAME_RGB", | |
[2] = "SANE_FRAME_RED", | |
[3] = "SANE_FRAME_GREEN", | |
[4] = "SANE_FRAME_BLUE", | |
} | |
-- What the client is requesting the server do to a property | |
local action_enum = { | |
[0] = "SANE_ACTION_GET_VALUE", | |
[1] = "SANE_ACTION_SET_VALUE", | |
[2] = "SANE_ACTION_SET_AUTO", | |
} | |
local proto_saned = Proto("saned", "SANE Daemon"); | |
local rpc_base = ProtoField.uint8("saned.rpc", "RPC Codes", base.DEC, rpc_enum) | |
local resource = ProtoField.stringz("saned.resource", "Scanner Resource", base.ASCII) | |
local version_code = ProtoField.uint32("saned.version", "Version Code", base.DEC) | |
local handle = ProtoField.uint32("saned.handle", "Handle", base.DEC) | |
local value = ProtoField.uint32("saned.value.type", "Option Value Type", base.DEC, value_type_enum) | |
local value_bool = ProtoField.uint32("saned.value.bool", "Boolean Option Value", base.DEC) | |
local value_int = ProtoField.uint32("saned.value.int", "Integer Option Value", base.DEC) | |
local value_fixed = ProtoField.uint32("saned.value.fixed", "Fixed Decimal Option Value", base.DEC) | |
local value_string = ProtoField.stringz("saned.value.string", "String Option Value", base.ASCII) | |
local req_username = ProtoField.stringz("saned.username", "Username", base.ASCII) | |
local req_password = ProtoField.stringz("saned.password", "Password", base.ASCII) | |
local req_device_name = ProtoField.stringz("saned.device_name", "Device Name", base.ASCII) | |
local req_option = ProtoField.uint32("saned.option", "Option Number", base.DEC) | |
local req_action = ProtoField.uint32("saned.action", "Action", base.DEC, action_enum) | |
local resp_status = ProtoField.uint32("saned.status", "Status", base.DEC, status) | |
local resp_dummy = ProtoField.uint32("saned.dummy", "Dummy Protocol Synchronization Value", base.DEC) | |
local resp_device_name = ProtoField.stringz("saned.device.name", "Device Name", base.ASCII) | |
local resp_device_vendor = ProtoField.stringz("saned.device.vendor", "Device Vendor", base.ASCII) | |
local resp_device_model = ProtoField.stringz("saned.device.model", "Device Model", base.ASCII) | |
local resp_device_type = ProtoField.stringz("saned.device.type", "Device Type", base.ASCII) | |
local resp_opt_name = ProtoField.stringz("saned.opt.name", "Option Name", base.ASCII) | |
local resp_opt_title = ProtoField.stringz("saned.opt.title", "Option Title", base.ASCII) | |
local resp_opt_desc = ProtoField.stringz("saned.opt.desc", "Option Description", base.ASCII) | |
local resp_opt_valuetype = ProtoField.uint32("saned.opt.valuetype", "Option Value Type", base.DEC, value_type_enum) | |
local resp_opt_valueunit = ProtoField.uint32("saned.opt.valueunit", "Option Value Unit", base.DEC, value_unit_enum) | |
local resp_opt_valuesize = ProtoField.uint32("saned.opt.valuesize", "Option Value Size", base.DEC) | |
local resp_opt_valuecap = ProtoField.uint32("saned.opt.cap", "Option Capability Flags", base.DEC) | |
local resp_opt_cap_soft = ProtoField.uint32("saned.opt.cap.soft", "Software Selectable", base.DEC, {}, 1) | |
local resp_opt_cap_hard = ProtoField.uint32("saned.opt.cap.hard", "Hardware Selectable", base.DEC, {}, 2) | |
local resp_opt_cap_detect = ProtoField.uint32("saned.opt.cap.detect", "Software Detectable", base.DEC, {}, 4) | |
local resp_opt_cap_emulated = ProtoField.uint32("saned.opt.cap.emulated", "Emulated By SANE", base.DEC, {}, 8) | |
local resp_opt_cap_automatic = ProtoField.uint32("saned.opt.cap.automatic", "Device Can Pick Value", base.DEC, {}, 16) | |
local resp_opt_cap_inactive = ProtoField.uint32("saned.opt.cap.inactive", "Not Active", base.DEC, {}, 32) | |
local resp_opt_cap_advanced = ProtoField.uint32("saned.opt.cap.advanced", "Advanced", base.DEC, {}, 64) | |
local resp_opt_valuecons = ProtoField.uint32("saned.opt.cons", "Option Constraint Flags", base.DEC, value_constraint_enum) | |
local resp_opt_cons_strings = ProtoField.stringz("saned.opt.cons.strings", "Text Constraint Value", base.STRING) | |
local resp_opt_cons_words = ProtoField.uint32("saned.opt.cons.words", "Integer Constraint Value", base.DEC) | |
local resp_opt_cons_rangemin = ProtoField.uint32("saned.opt.cons.range.min", "Range Constraint Min", base.DEC) | |
local resp_opt_cons_rangemax = ProtoField.uint32("saned.opt.cons.range.max", "Range Constraint Max", base.DEC) | |
local resp_opt_cons_rangequant = ProtoField.uint32("saned.opt.cons.range.quant", "Range Quantization", base.DEC) | |
local resp_param_format = ProtoField.uint32("saned.param.format", "Frame Format", base.DEC, format_enum) | |
local resp_param_last_frame = ProtoField.uint32("saned.param.is_last_frame", "Range Quantization", base.DEC) | |
local resp_param_bytes_per_line = ProtoField.uint32("saned.param.bytes_per_line", "Bytes Per Line", base.DEC) | |
local resp_param_pixels_per_line = ProtoField.uint32("saned.param.pixels_per_line", "Pixels Per Line", base.DEC) | |
local resp_param_lines = ProtoField.uint32("saned.param.lines", "Total Lines", base.DEC) | |
local resp_param_depth = ProtoField.uint32("saned.param.depth", "Depth", base.DEC) | |
local resp_port = ProtoField.uint32("saned.port", "Data Port", base.DEC) | |
local resp_byte_order = ProtoField.uint32("saned.byte_order", "Byte Order", base.DEC, endian_enum) | |
local resp_info = ProtoField.uint32("saned.info", "Info Flags", base.DEC) | |
local resp_info_inexact = ProtoField.uint32("saned.info.inexact", "Rounded Value", base.DEC, {}, 1) | |
local resp_info_reload_opt = ProtoField.uint32("saned.info.reload_opt", "Options changed", base.DEC, {}, 2) | |
local resp_info_reload_par = ProtoField.uint32("saned.info.reload_par", "Parameters changed", base.DEC, {}, 4) | |
proto_saned.fields = { | |
rpc_base, | |
resource, | |
version_code, | |
handle, | |
value, | |
value_bool, | |
value_int, | |
value_fixed, | |
value_string, | |
req_username, | |
req_password, | |
req_device_name, | |
req_option, | |
req_action, | |
resp_status, | |
resp_dummy, | |
resp_device_name, | |
resp_device_vendor, | |
resp_device_model, | |
resp_device_type, | |
resp_opt_name, | |
resp_opt_title, | |
resp_opt_desc, | |
resp_opt_valuetype, | |
resp_opt_valueunit, | |
resp_opt_valuesize, | |
resp_opt_valuecap, | |
resp_opt_cap_soft, | |
resp_opt_cap_hard, | |
resp_opt_cap_detect, | |
resp_opt_cap_emulated, | |
resp_opt_cap_automatic, | |
resp_opt_cap_inactive, | |
resp_opt_cap_advanced, | |
resp_opt_valuecons, | |
resp_opt_cons_strings, | |
resp_opt_cons_words, | |
resp_opt_cons_rangemin, | |
resp_opt_cons_rangemax, | |
resp_opt_cons_rangequant, | |
resp_param_format, | |
resp_param_last_frame, | |
resp_param_bytes_per_line, | |
resp_param_pixels_per_line, | |
resp_param_lines, | |
resp_param_depth, | |
resp_port, | |
resp_byte_order, | |
resp_info, | |
resp_info_inexact, | |
resp_info_reload_opt, | |
resp_info_reload_par, | |
} | |
-- Captures a slice of the buffer representing a string value. String values are | |
-- prefixed by their length encoded as a 4-byte word | |
function read_string(buf, offset) | |
local length = buf(offset, 4):uint() | |
if length == 0 then | |
return nil, offset + 4 | |
else | |
return buf(offset + 4, length), offset + 4 + length | |
end | |
end | |
-- Reads the value of a 4-byte word | |
function read_uint(buf, offset) | |
return buf(offset, 4):uint(), offset + 4 | |
end | |
function dissect_request(rpc, buf, pkg, subtree) | |
local str -- Used by various readers | |
local offset = 4 | |
if rpc == 0 then -- SANE_NET_INIT | |
subtree:add(version_code, buf(offset, 4)) | |
offset = offset + 4 | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(req_username, str) end | |
elseif rpc == 1 then -- SANE_NET_GET_DEVICES | |
-- Void request. These seem to contain some dummy value but it's always zero | |
elseif rpc == 2 then -- SANE_NET_OPEN | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(req_device_name, str) end | |
elseif rpc == 3 or rpc == 4 or rpc == 6 or rpc == 7 or rpc == 8 then | |
-- SANE_NET_CLOSE | |
-- SANE_NET_GET_OPTION_DESCRIPTORS | |
-- SANE_NET_GET_PARAMETERS | |
-- SANE_NET_START | |
-- SANE_NET_CANCEL | |
subtree:add(handle, buf(offset, 4)) | |
offset = offset + 4 | |
elseif rpc == 5 then -- SANE_NET_CONTROL_OPTION | |
subtree:add(handle, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(req_option, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(req_action, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(value, buf(offset, 4)) | |
local value_type | |
value_type, offset = read_uint(buf, offset) | |
local value_size | |
value_size, offset = read_uint(buf, offset) | |
-- All of these are encoded as pointers to values, except for strings | |
-- which are not wrapped in this way since they're already pointers | |
local value_null | |
if value_type == 0 then -- SANE_TYPE_BOOL | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_bool, buf(offset, 4)) end | |
elseif value_type == 1 then -- SANE_TYPE_INT | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_int, buf(offset, 4)) end | |
elseif value_type == 2 then -- SANE_TYPE_FIXED | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_fixed, buf(offset, 4)) end | |
elseif value_type == 3 then -- SANE_TYPE_STRING | |
str = read_string(buf, offset) | |
if str ~= nil then subtree:add(value_string, str) end | |
else | |
offset = offset + 8 -- Include both null toggle and empty value | |
end | |
elseif rpc == 9 then -- SANE_NET_AUTHORIZE | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resource, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(req_username, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(req_password, str) end | |
end | |
end | |
function dissect_response(rpc, buf, pkg, subtree) | |
local str -- Used by various readers | |
local status | |
local offset = 0 | |
if rpc == 0 then -- SANE_NET_INIT | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
subtree:add(version_code, buf(offset, 4)) | |
end | |
elseif rpc == 1 then -- SANE_NET_GET_DEVICES | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
offset = offset + 4 -- We don't care about the device array length, it's NULL terminated | |
while true do | |
local device_null | |
device_null, offset = read_uint(buf, offset) | |
if device_null == 1 then | |
break | |
end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_device_name, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_device_vendor, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_device_model, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_device_type, str) end | |
end | |
end | |
elseif rpc == 2 then -- SANE_NET_OPEN | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
subtree:add(handle, buf(offset, 4)) | |
offset = offset + 4 | |
str = read_string(buf, offset) | |
if str ~= nil then subtree:add(resource, str) end | |
end | |
elseif rpc == 3 or rpc == 8 or rpc == 9 then | |
-- SANE_NET_CLOSE | |
-- SANE_NET_CANCEL | |
-- SANE_NET_AUTHORIZE | |
subtree:add(resp_dummy, buf(offset, 4)) | |
elseif rpc == 4 then -- SANE_NET_OPTION_GET_DESCRIPTORS | |
local desc_count | |
desc_count, offset = read_uint(buf, offset) | |
while desc_count > 0 do | |
local desc_null | |
desc_null, offset = read_uint(buf, offset) | |
if desc_null == 0 then | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_opt_name, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_opt_title, str) end | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_opt_desc, str) end | |
subtree:add(resp_opt_valuetype, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_opt_valueunit, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_opt_valuesize, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_opt_valuecap, buf(offset, 4)) | |
subtree:add(resp_opt_cap_soft, buf(offset, 4)) | |
subtree:add(resp_opt_cap_hard, buf(offset, 4)) | |
subtree:add(resp_opt_cap_detect, buf(offset, 4)) | |
subtree:add(resp_opt_cap_emulated, buf(offset, 4)) | |
subtree:add(resp_opt_cap_automatic, buf(offset, 4)) | |
subtree:add(resp_opt_cap_inactive, buf(offset, 4)) | |
subtree:add(resp_opt_cap_advanced, buf(offset, 4)) | |
offset = offset + 4 | |
local constraint_type | |
subtree:add(resp_opt_valuecons, buf(offset, 4)) | |
constraint_type, offset = read_uint(buf, offset) | |
if constraint_type == 1 then -- SANE_CONSTRAINT_RANGE | |
offset = offset + 4 -- Skip redundant NULL tag, these always have values | |
subtree:add(resp_opt_cons_rangemin, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_opt_cons_rangemax, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_opt_cons_rangequant, buf(offset, 4)) | |
offset = offset + 4 | |
elseif constraint_type == 2 then -- SANE_CONSTRAINT_WORD_LIST | |
local word_count | |
word_count, offset = read_uint(buf, offset) | |
word_count = word_count - 1 | |
offset = offset + 4 -- Skip length value included in the data | |
while word_count > 0 do | |
subtree:add(resp_opt_cons_words, buf(offset, 4)) | |
offset = offset + 4 | |
word_count = word_count - 1 | |
end | |
elseif constraint_type == 3 then -- SANE_CONSTRAINT_STRING_LIST | |
local str_count | |
str_count, offset = read_uint(buf, offset) | |
while str_count > 0 do | |
str, offset = read_string(buf, offset) | |
if str ~= nil then subtree:add(resp_opt_cons_strings, str) end | |
str_count = str_count - 1 | |
end | |
end | |
end | |
desc_count = desc_count - 1 | |
end | |
elseif rpc == 5 then -- SANE_NET_CONTROL_OPTION | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
subtree:add(resp_info, buf(offset, 4)) | |
subtree:add(resp_info_inexact, buf(offset, 4)) | |
subtree:add(resp_info_reload_opt, buf(offset, 4)) | |
subtree:add(resp_info_reload_par, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(value, buf(offset, 4)) | |
local value_type | |
value_type, offset = read_uint(buf, offset) | |
local value_size | |
value_size, offset = read_uint(buf, offset) | |
-- All of these are encoded as pointers to values, except for strings | |
-- which are not wrapped in this way since they're already pointers | |
local value_null | |
if value_type == 0 then -- SANE_TYPE_BOOL | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_bool, buf(offset, 4)) end | |
elseif value_type == 1 then -- SANE_TYPE_INT | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_int, buf(offset, 4)) end | |
elseif value_type == 2 then -- SANE_TYPE_FIXED | |
value_null, offset = read_uint(buf, offset) | |
if value_null == 1 then subtree:add(value_fixed, buf(offset, 4)) end | |
elseif value_type == 3 then -- SANE_TYPE_STRING | |
str = read_string(buf, offset) | |
if str ~= nil then subtree:add(value_string, str) end | |
else | |
offset = offset + 8 -- Include both null toggle and empty value | |
end | |
local resource_null | |
resource_null, offset = read_uint(buf, offset) | |
if resource_null == 0 then | |
str = read_string(buf, offset) | |
if str ~= nil then subtree:add(resource, str) end | |
end | |
end | |
elseif rpc == 6 then -- SANE_NET_GET_PARAMETERS | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
subtree:add(resp_param_format, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_param_last_frame, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_param_bytes_per_line, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_param_pixels_per_line, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_param_lines, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_param_depth, buf(offset, 4)) | |
offset = offset + 4 | |
end | |
elseif rpc == 7 then -- SANE_NET_START | |
subtree:add(resp_status, buf(offset, 4)) | |
status, offset = read_uint(buf, offset) | |
if status == 0 then -- SANE_STATUS_GOOD | |
subtree:add(resp_port, buf(offset, 4)) | |
offset = offset + 4 | |
subtree:add(resp_byte_order, buf(offset, 4)) | |
offset = offset + 4 | |
str = read_string(buf, offset) | |
if str ~= nil then subtree:add(resource, str) end | |
end | |
end | |
end | |
--[[ | |
HACK! | |
Wireshark seems to do an initial pass over the packet data with the dissector, | |
and then re-invokes it later when it wants to display the packet data. During | |
the initial pass we have to associate each response with its request since the | |
response packets don't include an RPC tag. | |
This *will* break if you try it on a network where more than one machine is | |
accessing a SANE server. You could store the last RPC value in its own table | |
indexed by (src_ip, dest_ip, src_port, dest_port), I just didn't here. | |
--]] | |
local rpc_cache = {} | |
last_rpc = nil | |
function proto_saned.dissector(buf, pkt, tree) | |
local subtree = tree:add(proto_saned, buf) | |
if pkt.dst_port == 6566 then | |
subtree:add(rpc_base, buf(0, 4)) | |
proto_type = buf(0, 4):uint() | |
last_rpc = proto_type | |
dissect_request(proto_type, buf, pkt, subtree) | |
elseif not pkt.visited then | |
-- Initial pass saves the discovered RPC value so that we can run the | |
-- dissector out of order later | |
rpc_cache[pkt.number] = last_rpc | |
dissect_response(proto_type, buf, pkt, subtree) | |
else | |
-- The user clicked on a packet | |
dissect_response(rpc_cache[pkt.number], buf, pkt, subtree) | |
end | |
end | |
-- ? | |
local wtap_encap_table = DissectorTable.get("wtap_encap") | |
wtap_encap_table:add(wtap.USER0, proto_saned) | |
-- Register the protocol to a specific port | |
local tcp_encap_table = DissectorTable.get("tcp.port") | |
tcp_encap_table:add(6566, proto_saned) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment