nix-dissector/nix-packet.lua

334 lines
12 KiB
Lua

local nix_proto = Proto("nix", "Nix Daemon Protocol")
local dst_field = ProtoField.uint64("nix.dst", "Destination FD")
local src_field = ProtoField.uint64("nix.src", "Source FD")
local client_hello_field = ProtoField.bytes("nix.clienthello", "Client Hello")
local client_hello_magic_field = ProtoField.uint64("nix.clienthello.magic", "Client magic number")
local client_hello_version_field = ProtoField.uint64("nix.clienthello.version", "Client version")
local daemon_hello_field = ProtoField.bytes("nix.daemonhello", "Daemon Hello")
local daemon_hello_magic_field = ProtoField.uint64("nix.daemonhello.magic", "Daemon magic number")
local daemon_hello_version_field = ProtoField.uint64("nix.daemonhello.protolversion", "Protocol Version")
local op_name_field = ProtoField.string("nix.opname", "Operation Name")
local op_addtostore = ProtoField.bytes("nix.addtostore", "Add to store operation")
local op_addtostore_name = ProtoField.string("nix.addtostore.name", "Add to store name")
local op_addtostore_camstr = ProtoField.string("nix.addtostore.camstr", "Add to store camstr")
local op_addtostore_nb_references = ProtoField.uint64("nix.addtostore.nbreferences", "Number of References")
local op_addtostore_reference = ProtoField.string("nix.addtostore.reference", "Reference")
local op_addtostore_repairflag = ProtoField.bool("nix.addtostore.repairflag", "Repair Flag")
local op_addtostore_payload = ProtoField.bytes("nix.addtostore.payload", "Request Payload")
local op_addtostore_response = ProtoField.bytes("nix.addtostore.response", "Response")
local op_setoptions = ProtoField.bytes("nix.setoptions", "Set connection options")
local op_setoptions_keepfailed = ProtoField.bool("nix.setoptions.keepfailed", "Keep the failed build directory")
local op_setoptions_keepgoing = ProtoField.bool("nix.setoptions.keepgoing", "Keep on going after a failed build")
local op_setoptions_tryfallback = ProtoField.bool("nix.setoptions.tryfallback", "Fallback")
local op_setoptions_verbosity = ProtoField.uint64("nix.setoptions.verbosity", "Verbosity")
local op_setoptions_maxbuildjobs = ProtoField.uint64("nix.setoptions.maxbuildjobs", "Max build jobs")
local op_setoptions_maxsilenttime = ProtoField.uint64("nix.setoptions.maxsilenttime", "Max Silent Time")
local op_setoptions_verbosebuild = ProtoField.uint64("nix.setoptions.verbosebuild", "Verbosity level")
local op_setoptions_buildcores = ProtoField.uint64("nix.setoptions.buildcores", "NB Build cores")
local op_setoptions_usesubstitutes = ProtoField.bool("nix.setoptions.usesubstitutes", "Use substitutes")
local op_setoptions_nbclientoverrides = ProtoField.uint64("nix.setoptions.nbclientoverrides", "Nb Client Overrides")
local op_setoptions_clientoverridename = ProtoField.uint64("nix.setoptions.clientoverridename", "Client Override key")
local op_setoptions_clientoverridevalue = ProtoField.uint64("nix.setoptions.clientoverridevalue", "Client Override Value")
local op_is_valid_path = ProtoField.string("nix.isvalidpath", "isvalidpath request")
op_addtostore_step = 0
nix_proto.fields = {
dst_field,
src_field,
op_field,
first_byte_field,
client_hello_field,
client_hello_magic_field,
client_hello_version_field,
daemon_hello_field,
daemon_hello_magic_field,
daemon_hello_version_field,
op_name_field,
op_addtostore,
op_addtostore_name,
op_addtostore_camstr,
op_addtostore_nb_references,
op_addtostore_reference,
op_addtostore_repairflag,
op_addtostore_payload,
op_addtostore_response,
op_setoptions,
op_setoptions_keepfailed,
op_setoptions_keepgoing,
op_setoptions_tryfallback,
op_setoptions_verbosity,
op_setoptions_maxbuildjobs,
op_setoptions_maxsilenttime,
op_setoptions_verbosebuild,
op_setoptions_buildcores,
op_setoptions_usesubstitutes,
op_setoptions_nbclientoverrides,
op_setoptions_clientoverridename,
op_setoptions_clientoverridevalue,
op_is_valid_path
}
local op_table = {
[1] = "IsValidPath",
[3] = "HasSubstitutes",
[4] = "QuaryPathHash",
[5] = "QueryReferences",
[6] = "QueryReferrers",
[7] = "AddToStore",
[8] = "AddTextToStore",
[9] = "BuildPaths",
[10] = "EnsurePath",
[11] = "AddTempRoot",
[12] = "AddIndirectRoot",
[13] = "SyncWithGC",
[14] = "FindRoots",
[16] = "ExportPath",
[18] = "QueryDeriver",
[19] = "SetOptions",
[20] = "CollectGarbage",
[21] = "QuerySubstitutablePathInfo",
[22] = "QueryDerivationOutputs",
[23] = "QueryAllValidPaths",
[24] = "QueryFailedPaths",
[25] = "ClearFailedPaths",
[26] = "QueryPathInfo",
[27] = "ImportPaths",
[28] = "QueryDerivationOutputNames",
[29] = "QueryPathFromHashPart",
[30] = "QuerySubstitutablePathInfos",
[31] = "QueryValidPaths",
[32] = "QuerySubstitutablePaths",
[33] = "QueryValidDerivers",
[34] = "OptimiseStore",
[35] = "VerifyStore",
[36] = "BuildDerivation",
[37] = "AddSignatures",
[38] = "NarFromPath",
[39] = "AddToStoreNar",
[40] = "QueryMissing",
[41] = "QueryDerivationOutputMap",
[42] = "RegisterDrvOutput",
[43] = "QueryRealisation",
[44] = "AddMultipleToStore",
[45] = "AddBuildLog",
[46] = "BuildPathsWithResults",
}
function parse_client_hello(tvb, pinfo, tree, offset)
local subtree = tree:add(client_hello_field, tvb:range(offset, 8))
subtree:add_le(client_hello_magic_field, tvb(offset,8))
return offset + 8
end
function parse_daemon_hello(tvb, pinfo, tree, offset)
local subtree = tree:add(daemon_hello_field, tvb:range(offset, 16))
subtree:add_le(daemon_hello_magic_field, tvb(offset,8))
offset = offset + 8
subtree:add_le(daemon_hello_version_field, tvb(offset,8))
return offset + 8
end
-- Reads a Nix daemon string from tvb.
-- The Nix daemon strings are composed of two fields:
-- 1. The size of the string (8 bytes)
-- 2. The string itself, 8-aligned (padding with \0), non null
-- terminated.
function read_string(tvb, pinfo, tree, offset)
local str
-- Read size (u_size)
local size = tvb(offset,4):le_int()
local offset = offset + 8
-- Strings are 8-aligned. We need to discard the potential padding.
if (size % 8) ~= 0 then
-- Parting the string. They are null-padded, so we'll get a the
-- null terminaison wireshark is expecting for free.
str = tvb(offset,size):string()
offset = offset + (size + (8 - (size % 8)))
else
-- The string is already 8-aligned. This is a bit annoying:
-- Wireshark expects the strings to be null terminated. Nix
-- daemon is not null-terminating the strings it sends to the
-- wire.
--
-- We have to extract the string to a new tvb to append a null
-- byte at the end. We can then send this new null-terminated
-- string to wireshark.
--
-- Note: the offset indexes the original tvb, not the
-- temporarily created one. There's no need to take this new
-- null bit into account.
local tvb_clone = tvb:bytes(offset, size)
tvb_clone:set_size(size + 1)
tvb_clone:set_index(size, 0)
str = tvb_clone(0,size+1):tvb():range(0,size+1):string()
offset = offset + size
end
return offset, str
end
function parse_add_to_store(tvb, pinfo, tree, offset)
local initoffset = offset
offsetname, name = read_string(tvb, pinfo, tree, offset)
offset, camstr = read_string(tvb, pinfo, tree, offsetname)
local subtree = tree:add(op_addtostore, tvb(initoffset, offset - initoffset))
subtree:add(op_addtostore_name, tvb(initoffset, offsetname - initoffset), name)
subtree:add(op_addtostore_camstr, tvb(offsetname, offset - offsetname), camstr)
local nb_references = tvb(offset,4):le_int()
subtree:add_le(op_addtostore_nb_references, tvb(offset,8))
offset = offset + 8
for i=1, nb_references do
local reference
local prevoffset = offset
offset, reference = read_string(tvb, pinfo, tree, offset)
subtree:add(op_addtostore_reference, tvb(prevoffset, offset - prevoffset), reference)
end
local repairflag = subtree:add(op_addtostore_repairflag, tvb(offset, 1))
offset = offset + 8
op_addtostore_step = 2
return offset
end
function process_op_addtostore_step(tvb, pinfo, tree, offset)
if op_addtostore_step == 2 then
local subtree = tree:add(op_addtostore, tvb(offset, tvb:len() - offset))
subtree:add(op_addtostore_payload, tvb(offset, tvb:len() - offset))
elseif op_addtostore_step == 1 then
local subtree = tree:add(op_addtostore, tvb(offset, tvb:len() - offset))
subtree:add(op_addtostore_response, tvb(offset, tvb:len() - offset))
end
op_addtostore_step = op_addtostore_step - 1
end
function parse_set_options(tvb, pinfo, tree, offset)
local subtree = tree:add(op_setoptions, tvb(offset, tvb:len() - offset))
subtree:add(op_setoptions_keepfailed, tvb(offset, 1))
offset = offset + 8
subtree:add(op_setoptions_keepgoing, tvb(offset, 1))
offset = offset + 8
subtree:add(op_setoptions_tryfallback, tvb(offset, 1))
offset = offset + 8
subtree:add_le(op_setoptions_verbosity, tvb(offset, 8))
offset = offset + 8
subtree:add_le(op_setoptions_maxbuildjobs, tvb(offset, 8))
offset = offset + 8
subtree:add_le(op_setoptions_maxsilenttime, tvb(offset, 8))
offset = offset + 8
-- Obsolete useBuildHook
offset = offset + 8
subtree:add_le(op_setoptions_verbosebuild, tvb(offset, 8))
offset = offset + 8
-- Obsolete logtype
offset = offset + 8
-- Obsolete printBuildTrace
offset = offset + 8
subtree:add_le(op_setoptions_buildcores, tvb(offset, 8))
offset = offset + 8
subtree:add(op_setoptions_usesubstitutes, tvb(offset, 1))
offset = offset + 8
local nb_client_overrides = tvb(offset, 4):le_int()
subtree:add_le(op_setoptions_nbclientoverrides, tvb(offset, 8))
offset = offset + 8
for i=1, nb_client_overrides do
local initoffset = offset
local nameoffset
local name
local value
nameoffset, name = read_string(tvb, pinfo, tree, offset)
offset, value = read_string(tvb, pinfo, tree, offset)
subtree:add(op_setoptions_clientoverridename, tvb(initoffset, nameoffset - initoffset), name)
subtree:add(op_setoptions_clientoverridevalue, tvb(initoffset, offset - nameoffset), value)
end
return offset
end
function parse_is_valid_path(tvb, pinfo, tree, offset)
local initoffset = offset
local path
offset, path = read_string(tvb, pinfo, tree, offset)
tree:add(op_is_valid_path, tvb(initoffset, offset - initoffset), path)
return offset
end
function parse_op(tvb, pinfo, tree, offset, op)
tree:add(op_name_field, tvb(offset, 8), op_table[op])
offset = offset + 8
if op_table[op] == "AddToStore" then
offset = parse_add_to_store(tvb, pinfo, tree, offset)
elseif op_table[op] == "SetOptions" then
offset = parse_set_options(tvb, pinfo, tree, offset)
elseif op_table[op] == "IsValidPath" then
offset = parse_is_valid_path(tvb, pinfo, tree, offset)
end
return offset
end
function nix_proto.dissector(tvb, pinfo, tree)
local offset = 0
local subtree = tree:add(nix_proto, tvb(), "Nix Daemon Protocol Data")
local dst = subtree:add(dst_field, tvb(offset, 8))
offset = offset + 8
local src = subtree:add(src_field, tvb(offset, 8))
offset = offset + 8
local first_word = tvb(offset, 4):le_uint()
if op_addtostore_step > 0 then
process_op_addtostore_step(tvb, pinfo, subtree, offset)
elseif first_word == 0x6e697863 then
offset = parse_client_hello(tvb, pinfo, subtree, offset)
elseif first_word == 0x6478696f then
offset = parse_daemon_hello(tvb, pinfo, subtree, offset)
elseif op_table[first_word] ~= nil then
offset = parse_op(tvb, pinfo, subtree, offset, first_word)
end
pinfo.cols.protocol = "Nix Daemon"
pinfo.cols.dst = tostring(tvb(0, 8):uint64())
pinfo.cols.src = tostring(tvb(8, 8):uint64())
end
local wtap_encap_table = DissectorTable.get("wtap_encap")
wtap_encap_table:add(wtap.USER0, nix_proto)