contrib: add Wireshark plugins for NetworkDB

Contribute a Wireshark plugin for decrypting and dissecting
hashicorp/memberlist messages. And contribue a plugin for dissecting the
NetworkDB messages transported as memberlist User messages.

Add a feature to NetworkDB to log the encryption keys to a file for the
Wireshark memberlist plugin to consume, configured using an environment
variable.

Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
Cory Snider
2025-07-02 16:16:30 -04:00
parent 3b50d0dbdc
commit ebfafa1561
5 changed files with 804 additions and 0 deletions

111
contrib/wireshark/README.md Normal file
View File

@@ -0,0 +1,111 @@
Wireshark Dissectors for NetworkDB
==================================
`memberlist.lua` is a [Wireshark][] plugin
which registers a `memberlist` protocol
that can dissect the [memberlist][] TCP and UDP protocols.
The `memberlist` protocol can be configured to dissect user data
as the protocol named in the `memberlist.userdata_dissector` preference.
`moby-networkdb.lua` is a Wireshark plugin which registers
a protocol named `networkdbgossip`
that dissects NetworkDB gossip messages.
As node-to-node communications for NetworkDB
are transported as memberlist user messages,
the memberlist protocol dissector must be configured
to invoke the networkdbgossip protocol as a sub-dissector.
(Read: set the preference `memberlist.userdata_dissector:networkdbgossip`)
Installation
------------
### Install Wireshark 4.5 or newer.
Wireshark 4.4 has an incomplete msgpack protocol dissector
that is unable to properly decode memberlist messages.
As of 2025-06-30 Wireshark 4.5 has yet to be released.
A [nightly build][ws.dl] may be required.
### Install the plugins
Configure Wireshark/Tshark to load `memberlist.lua` and `moby-networkdb.lua`.
Refer to [the Wireshark Lua manual][ws.lua.intro] for further instruction.
### Configure the ProtoBuf protocol
NetworkDB messages are serialized to protobuf,
which is not self-describing.
The ProtoBuf Wireshark protocol needs to be supplied with
the protobuf IDL definitions of the messages
in order to dissect them.
1. Clone [moby/moby][] for the NetworkDB IDL definitions.
2. Clone [protocolbuffers/protobuf][] for the protobuf "standard library" IDL.
3. Configure the ProtoBuf protocol (Preferences -> Protocols -> ProtoBuf)
as follows:
- ✅ Load .proto files on startup. (`protobuf.reload_protos`)
- ✅ Dissect Protobuf fields as Wireshark fields. (`protobuf.pbf_as_hf`)
- Add entries to the Protobuf Search Paths table (`uat:protobuf.search_paths`):
- path/to/protocolbuffers/protobuf/src
- path/to/moby/moby/vendor
- path/to/moby/moby
- path/to/moby/moby/libnetwork/networkdb (✅ Load all files)
- path/to/moby/moby/libnetwork/drivers/overlay (✅ Load all files)
Note that it is not sufficient to just grab the .proto files from the repos.
The directory structure is necessary for the definitions to load properly.
### Configure the Memberlist protocol
Configure memberlist to dissect user data messages as NetworkDB gossip.
- In Preferences -> Protocols -> MEMBERLIST,
set the User Data Dissector to `networkdbgossip`.
(`memberlist.userdata_dissector`)
- Optional: set Memberlist TCP+UDP port(s) (`memberlist.ports`) as needed.
E.g. a value such as `7946,10000-10999` would be useful
for analyzing packet captures from NetworkDB unit tests.
Usage Notes
-----------
### Encryption
The memberlist protocol dissector supports decryption
of encrypted memberlist messages
when provided with a file containing the encryption keys used.
In Preferences -> Protocols -> MEMBERLIST,
set the Encryption Key Logfile Path
(`memberlist.keylog`)
to a file containing the encryption keys.
The logfile should list the encryption keys
as hexadecimal strings, delimited by newlines.
dockerd may be configured to write the NetworkDB encryption keys to a logfile
by setting the environment variable `NETWORKDBKEYLOGFILE`
to the path where the file should reside.
### Known Issues
The NetworkDB protocol may fail to load with an error when Wireshark is first started:
moby-networkdb.lua:4: bad argument #1 to 'new'
(Field_new: a field with this name must exist)
This is due to [a known issue in Wireshark.](https://gitlab.com/wireshark/wireshark/-/issues/20161)
Workaround: reload Lua plugins after Wireshark has been initialized.
- Menu: Analyze -> Reload Lua Plugins, or
- Keyboard shortcut: Ctrl-Shift-L (Windows/Linux), ⇧⌘L (macOS)
### Limitations
- Only memberlist encryption version 1 (AES-GCM 128, no padding) is supported,
not version 0 (AES-GCM 128 using PKCS#7 padding).
- Labelled messages cannot currently be decrypted.
[memberlist]: https://github.com/hashicorp/memberlist
[Wireshark]: https://wireshark.org
[ws.dl]: https://www.wireshark.org/download/automated/
[ws.lua.intro]: https://www.wireshark.org/docs/wsdg_html_chunked/wsluarm.html#wsluarm_intro
[moby/moby]: https://github.com/moby/moby
[protocolbuffers/protobuf]: https://github.com/protocolbuffers/protobuf

View File

@@ -0,0 +1,587 @@
local msgpack, lzw, crc32 -- Forward declarations for library code inlined at the end of this chunk.
local memberlist_protocol = Proto("Memberlist", "Memberlist Protocol")
local message_type_enum = {
[0] = "Ping",
[1] = "IndirectPing",
[2] = "ACKResp",
[3] = "Suspect",
[4] = "Alive",
[5] = "Dead",
[6] = "PushPull",
[7] = "Compound",
[8] = "User",
[9] = "Compress",
[10] = "Encrypt",
[11] = "NACKResp",
[12] = "HasCRC",
[13] = "Err",
[244] = "HasLabel",
}
local message_type = ProtoField.uint8("memberlist.message_type", "Message Type", base.DEC, message_type_enum)
local crc = ProtoField.uint32("memberlist.crc", "CRC", base.HEX)
local label_size = ProtoField.uint8("memberlist.label_size", "Label Size", base.DEC)
local label = ProtoField.string("memberlist.label", "Label")
local encryption_version = ProtoField.uint8("memberlist.encryption.version", "Encryption Version", base.DEC)
local encrypted_nonce = ProtoField.bytes("memberlist.encryption.nonce", "AES-GCM Nonce", base.NONE)
local encrypted_ciphertext = ProtoField.bytes("memberlist.encryption.ciphertext", "Ciphertext", base.NONE)
local encrypted_tag = ProtoField.bytes("memberlist.encryption.tag", "AEAD tag", base.NONE)
local ciphertext_length = ProtoField.uint32("memberlist.encryption.length", "Ciphertext length", base.DEC)
local decompressed_data = ProtoField.bytes("memberlist.decompressed_data", "Decompressed Data")
local compound_parts = ProtoField.uint8("memberlist.compound.parts", "Compound Parts", base.DEC)
local compound_part_length = ProtoField.uint16("memberlist.compound.part_length", "Compound Part Length", base.DEC)
local userdata = ProtoField.bytes("memberlist.userdata", "User Data", base.NONE)
memberlist_protocol.fields = {
message_type,
crc,
label_size,
label,
encryption_version,
encrypted_nonce,
encrypted_ciphertext,
encrypted_tag,
ciphertext_length,
decompressed_data,
compound_parts,
compound_part_length,
userdata,
}
local default_settings = { ports = 7946 }
memberlist_protocol.prefs.userdata_dissector = Pref.string("User Data Dissector", "", "Dissector to apply to User message bodies")
memberlist_protocol.prefs.ports = Pref.range("Memberlist TCP+UDP Port(s)", default_settings.ports, "", 65535)
memberlist_protocol.prefs.keylog = Pref.string("Encryption Key Logfile Path", "", [[
Relative or absolute path to a file containing the symmetric keys for decrypting ciphered memberlist messages.
The file should contain the keys, encoded as hex, one per line.]])
local msgpack_string_field = Field.new("msgpack.string")
local function dissect_userdata(buffer, pinfo, tree)
local dissector = memberlist_protocol.prefs.userdata_dissector
if dissector ~= "" then
local d = Dissector.get(dissector)
if d == nil then
tree:add(userdata, buffer())
return
end
local success, err = pcall(Dissector.call, d, buffer, pinfo, tree)
if success then return
else
tree:add_expert_info(PI_DISSECTOR_BUG, PI_ERROR, "Dissector " .. dissector .. " failed: " .. tostring(err))
tree:add(userdata, buffer())
end
else
tree:add(userdata, buffer())
end
end
local function try_decrypt(buffer, tree, aead)
local path = memberlist_protocol.prefs.keylog
if path == "" then return buffer end
local cryptoversion = buffer(0, 1)
if cryptoversion:uint() ~= 1 then return buffer end
local nonce = buffer(1, 12)
buffer = buffer(13):tvb()
local tagSize = 16
local ciphertext = buffer(0, buffer:len()-tagSize)
local tag = buffer(buffer:len()-tagSize)
local cipher = GcryptCipher.open(GCRY_CIPHER_AES, GCRY_CIPHER_MODE_GCM, 0)
-- Don't bother caching the keys in-process.
-- The filesystem cache is performant enough,
-- and the OS is better equipped to invalidate the cache.
for hexkey in io.lines(path) do
cipher:ctl(GCRYCTL_RESET, ByteArray.new())
cipher:setkey(ByteArray.new(hexkey))
cipher:setiv(nonce:bytes())
if aead then cipher:authenticate(aead) end
local decrypted = cipher:decrypt(NULL, ciphertext:bytes())
local result, err = cipher:checktag(tag:bytes())
if result == 0 then
local subtree = tree:add(memberlist_protocol, buffer(), "Encrypted Memberlist")
subtree:add(encryption_version, cryptoversion)
subtree:add(encrypted_nonce, nonce)
subtree:add(encrypted_ciphertext, ciphertext)
subtree:add(encrypted_tag, tag)
return decrypted:tvb("Decrypted")
end
end
return buffer
end
function memberlist_protocol.dissector(buffer, pinfo, tree)
local length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = memberlist_protocol.name
if pinfo.port_type ~= 2 then -- UDP
buffer = try_decrypt(buffer, tree)
end
local opcode = buffer(0, 1):uint()
local subtree = tree:add(memberlist_protocol, buffer(), "Memberlist Protocol, Type: " .. (message_type_enum[opcode] or tostring(opcode)))
local msgtype_tree = subtree:add(message_type, buffer(0, 1))
local buffer = buffer(1):tvb()
if opcode == 244 and buffer:len() > 0 then -- HasLabel
local label_length = buffer(0, 1):uint()
subtree:add(label_size, buffer(0, 1))
subtree:add(label, buffer(1, label_length))
memberlist_protocol.dissector(buffer(1 + label_length):tvb(), pinfo, subtree)
return
elseif opcode == 12 and buffer:len() > 4 then -- HasCRC
subtree:add(crc, buffer(0, 4))
local expected = buffer(0, 4):uint()
buffer = buffer(4):tvb()
local actual = crc32(buffer():raw())
if expected ~= actual then
subtree:add_expert_info(PI_MALFORMED, PI_ERROR, "Actual CRC32 is " .. tostring(actual))
end
memberlist_protocol.dissector(buffer, pinfo, subtree)
return
elseif opcode == 6 then -- PushPull
local msglen = Dissector.get("msgpack"):call(buffer, pinfo, subtree) -- pushPullHeader
local header = msgpack.decode(buffer(0, msglen):raw())
buffer = buffer(msglen):tvb()
for _ = 1, header.Nodes do
buffer = buffer(Dissector.get("msgpack"):call(buffer, pinfo, subtree)):tvb()
end
if header.UserStateLen > 0 then
dissect_userdata(buffer(0, header.UserStateLen):tvb(), pinfo, tree)
buffer = buffer(header.UserStateLen):tvb()
end
elseif opcode == 7 and buffer:len() >= 1 then -- Compound
local nparts = buffer(0, 1)
msgtype_tree:add(compound_parts, nparts)
nparts = nparts:uint()
msgtype_tree:append_text(" (" .. nparts .. " parts)")
buffer = buffer(1):tvb()
local partlengths = {}
for i = 0, nparts-1 do
local partlen = buffer(0, 2)
msgtype_tree:add(compound_part_length, partlen)
table.insert(partlengths, partlen:uint())
buffer = buffer(2):tvb()
end
for _, part_length in ipairs(partlengths) do
if buffer:len() < part_length then
subtree:add_expert_info(PI_MALFORMED, PI_ERROR, "Truncated part in Compound message")
return
end
local part = buffer(0, part_length):tvb()
memberlist_protocol.dissector(part, pinfo, subtree)
buffer = buffer(part_length):tvb()
end
return
elseif opcode == 8 then -- User
if pinfo.port_type == 2 then -- TCP
local headerlen = Dissector.get("msgpack"):call(buffer, pinfo, subtree)
local header = msgpack.decode(buffer(0, headerlen):raw())
buffer = buffer(headerlen):tvb()
if header.UserMsgLen ~= nil then
dissect_userdata(buffer(0, header.UserMsgLen):tvb(), pinfo, tree)
else
subtree:add(userdata, buffer())
end
else
dissect_userdata(buffer, pinfo, tree)
end
return
elseif opcode == 10 then -- Encrypt
local ciphertextlen = buffer(0, 4)
buffer = buffer(4):tvb()
msgtype_tree:add(ciphertext_length, ciphertextlen)
local aead = ByteArray.new("0a") .. ciphertextlen:bytes()
memberlist_protocol.dissector(try_decrypt(buffer(0, ciphertextlen:uint()), msgtype_tree, aead), pinfo, tree)
return
end
if buffer:len() > 0 then
Dissector.get("msgpack"):call(buffer, pinfo, subtree)
if opcode == 9 then -- Compress
local fields = { msgpack_string_field() }
for k,v in ipairs(fields) do
if v.value == "Buf" then
local compressed_data = fields[k+3].range
local rawdata = compressed_data:raw()
local decompressed, err = lzw.decompress(compressed_data:raw())
if err ~= nil then
subtree:add_expert_info(PI_MALFORMED, PI_ERROR, "Decompression failed: " .. err)
return
end
local dtree = subtree:add(decompressed_data, compressed_data, decompressed)
memberlist_protocol.dissector(ByteArray.new(decompressed, true):tvb("Decompressed"), pinfo, tree)
return
end
end
end
end
end
local udp_port = DissectorTable.get("udp.port")
local tcp_port = DissectorTable.get("tcp.port")
function memberlist_protocol.prefs_changed()
if default_settings.ports ~= memberlist_protocol.prefs.ports then
udp_port:remove(default_settings.ports, memberlist_protocol)
tcp_port:remove(default_settings.ports, memberlist_protocol)
default_settings.ports = memberlist_protocol.prefs.ports
udp_port:add(default_settings.ports, memberlist_protocol)
tcp_port:add(default_settings.ports, memberlist_protocol)
end
end
udp_port:add(default_settings.ports, memberlist_protocol)
tcp_port:add(default_settings.ports, memberlist_protocol)
-------------------------
-- Library definitions --
-------------------------
crc32 = (function()
local crc32_lut = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
}
local function crc32(s, crc)
if crc == nil then crc = 0 end
crc = crc ~ 0xffffffff
for i = 1,#s do
crc = (crc >> 4) ~ crc32_lut[((crc ~ string.byte(s, i) ) & 0xf) + 1]
crc = (crc >> 4) ~ crc32_lut[((crc ~ (string.byte(s, i) >> 4)) & 0xf) + 1]
end
return crc ~ 0xffffffff
end
return crc32
end)()
lzw = (function()
--[[
Port of "compress/lzw".Reader from the Go standard library.
Copyright 2011 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
]]--
local maxWidth = 12
local decoderInvalidCode = 0xffff
local flushBuffer = 1 << maxWidth
local function decompress(compressed)
local bits, nBits, litWidth = 0, 0, 8
local width = litWidth + 1
local clear = 1 << litWidth
local eof, hi = clear+1, clear+1
local overflow = 1 << width
local last = decoderInvalidCode
local suffix, prefix = {}, {}
local i = 1
local output = {}
local code
while i <= string.len(compressed) do
::continue::
while nBits < width do
local x = string.byte(compressed, i)
i = i+1
bits = bits | (x << nBits)
nBits = nBits+8
end
code = bits & ((1 << width) - 1)
bits = bits >> width
nBits = nBits - width
if code < clear then -- literal
table.insert(output, string.char(code))
if last ~= decoderInvalidCode then
suffix[hi] = string.char(code)
prefix[hi] = last
end
elseif code == clear then -- clear code
width = litWidth+1
hi = eof
overflow = 1 << width
last = decoderInvalidCode
goto continue
elseif code == eof then -- end of file
return table.concat(output)
elseif code <= hi then -- code in dictionary
local c, tmp = code, {}
if code == hi and last ~= decoderInvalidCode then
c = last
while c >= clear do
c = prefix[c]
end
table.insert(tmp, string.char(c))
c = last
end
while c >= clear do
table.insert(tmp, suffix[c])
c = prefix[c]
end
table.insert(tmp, string.char(c))
for i = #tmp, 1, -1 do
table.insert(output, tmp[i])
end
if last ~= decoderInvalidCode then
suffix[hi] = string.char(c)
prefix[hi] = last
end
else
return nil, "Invalid code: " .. code .. " at position " .. i
end
last, hi = code, hi + 1
if hi >= overflow then
if hi > overflow then
return nil, "unreachable"
end
if width == maxWidth then
last = decoderInvalidCode
hi = hi - 1
else
width = width + 1
overflow = 1 << width
end
end
end
return nil, "Unexpected end of input: "..code
end
return { decompress = decompress }
end)()
msgpack = (function()
--[[----------------------------------------------------------------------------
MessagePack encoder / decoder written in pure Lua 5.3 / Lua 5.4
written by Sebastian Steinhauer <s.steinhauer@yahoo.de>
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
--]]----------------------------------------------------------------------------
local pack, unpack = string.pack, string.unpack
local mtype, utf8len = math.type, utf8.len
local tconcat, tunpack = table.concat, table.unpack
local ssub = string.sub
local type, pcall, pairs, select = type, pcall, pairs, select
--[[----------------------------------------------------------------------------
Decoder
--]]----------------------------------------------------------------------------
local decode_value -- forward declaration
local function decode_array(data, position, length)
local elements, value = {}
for i = 1, length do
value, position = decode_value(data, position)
elements[i] = value
end
return elements, position
end
local function decode_map(data, position, length)
local elements, key, value = {}
for i = 1, length do
key, position = decode_value(data, position)
value, position = decode_value(data, position)
elements[key] = value
end
return elements, position
end
local decoder_functions = {
[0xc0] = function(data, position)
return nil, position
end,
[0xc2] = function(data, position)
return false, position
end,
[0xc3] = function(data, position)
return true, position
end,
[0xc4] = function(data, position)
return unpack('>s1', data, position)
end,
[0xc5] = function(data, position)
return unpack('>s2', data, position)
end,
[0xc6] = function(data, position)
return unpack('>s4', data, position)
end,
[0xca] = function(data, position)
return unpack('>f', data, position)
end,
[0xcb] = function(data, position)
return unpack('>d', data, position)
end,
[0xcc] = function(data, position)
return unpack('>B', data, position)
end,
[0xcd] = function(data, position)
return unpack('>I2', data, position)
end,
[0xce] = function(data, position)
return unpack('>I4', data, position)
end,
[0xcf] = function(data, position)
return unpack('>I8', data, position)
end,
[0xd0] = function(data, position)
return unpack('>b', data, position)
end,
[0xd1] = function(data, position)
return unpack('>i2', data, position)
end,
[0xd2] = function(data, position)
return unpack('>i4', data, position)
end,
[0xd3] = function(data, position)
return unpack('>i8', data, position)
end,
[0xd9] = function(data, position)
return unpack('>s1', data, position)
end,
[0xda] = function(data, position)
return unpack('>s2', data, position)
end,
[0xdb] = function(data, position)
return unpack('>s4', data, position)
end,
[0xdc] = function(data, position)
local length
length, position = unpack('>I2', data, position)
return decode_array(data, position, length)
end,
[0xdd] = function(data, position)
local length
length, position = unpack('>I4', data, position)
return decode_array(data, position, length)
end,
[0xde] = function(data, position)
local length
length, position = unpack('>I2', data, position)
return decode_map(data, position, length)
end,
[0xdf] = function(data, position)
local length
length, position = unpack('>I4', data, position)
return decode_map(data, position, length)
end,
}
-- add fix-array, fix-map, fix-string, fix-int stuff
for i = 0x00, 0x7f do
decoder_functions[i] = function(data, position)
return i, position
end
end
for i = 0x80, 0x8f do
decoder_functions[i] = function(data, position)
return decode_map(data, position, i - 0x80)
end
end
for i = 0x90, 0x9f do
decoder_functions[i] = function(data, position)
return decode_array(data, position, i - 0x90)
end
end
for i = 0xa0, 0xbf do
decoder_functions[i] = function(data, position)
local length = i - 0xa0
return ssub(data, position, position + length - 1), position + length
end
end
for i = 0xe0, 0xff do
decoder_functions[i] = function(data, position)
return -32 + (i - 0xe0), position
end
end
decode_value = function(data, position)
local byte, value
byte, position = unpack('B', data, position)
value, position = decoder_functions[byte](data, position)
return value, position
end
--[[----------------------------------------------------------------------------
Interface
--]]----------------------------------------------------------------------------
return {
_AUTHOR = 'Sebastian Steinhauer <s.steinhauer@yahoo.de>',
_VERSION = '0.6.1',
-- primary decode function
decode = function(data, position)
local values, value, ok = {}
position = position or 1
while position <= #data do
ok, value, position = pcall(decode_value, data, position)
if ok then
values[#values + 1] = value
else
return nil, 'cannot decode MessagePack'
end
end
return tunpack(values)
end,
-- decode just one value
decode_one = function(data, position)
local value, ok
ok, value, position = pcall(decode_value, data, position or 1)
if ok then
return value, position
else
return nil, 'cannot decode MessagePack'
end
end,
}
--[[----------------------------------------------------------------------------
--]]----------------------------------------------------------------------------
end)()

View File

@@ -0,0 +1,59 @@
-- https://wiki.wireshark.org/Protobuf
local protobuf_dissector = Dissector.get("protobuf")
local msgtype_field = Field.new("pbf.networkdb.GossipMessage.type")
local msgdata_field = Field.new("pbf.networkdb.GossipMessage.data")
local msgtype_map = {
[1] = "networkdb.NetworkEvent",
[2] = "networkdb.TableEvent",
[3] = "networkdb.NetworkPushPull",
[4] = "networkdb.BulkSyncMessage",
[5] = "networkdb.CompoundMessage",
[6] = "networkdb.NodeEvent",
}
local function last_fieldinfo(field)
local fieldset = { field() }
return fieldset[#fieldset]
end
local gossip_proto = Proto("networkdbgossip", "Moby NetworkDB Gossip")
function gossip_proto.dissector(tvb, pinfo, tree)
pinfo.private["pb_msg_type"] = "message,networkdb.GossipMessage"
pcall(Dissector.call, protobuf_dissector, tvb, pinfo, tree)
local msgtype_fieldinfo = last_fieldinfo(msgtype_field)
if not msgtype_fieldinfo then return end
local msgdata_fieldinfo = last_fieldinfo(msgdata_field)
if not msgdata_fieldinfo then return end
if msgdata_fieldinfo.offset < msgtype_fieldinfo.offset + #msgtype_fieldinfo then return end
local decodeas = msgtype_map[msgtype_fieldinfo()]
if not decodeas then return end
pinfo.private["pb_msg_type"] = "message," .. decodeas
local ok, err = pcall(Dissector.call, protobuf_dissector, msgdata_fieldinfo.range:tvb(), pinfo, tree)
if not ok then tree:add_expert_info(PI_DISSECTOR_BUG, PI_ERROR, "Dissector for " .. decodeas .. " failed: " .. tostring(err)) end
end
local tableevent_msgtype_map = {
["overlay_peer_table"] = "overlay.PeerRecord",
["endpoint_table"] = "libnetwork.EndpointRecord",
}
local tableevent_name_field = Field.new("pbf.networkdb.TableEvent.table_name")
local tableevent_proto = Proto("networkdb-tableevent", "NetworkDB Table Event")
function tableevent_proto.dissector(tvb, pinfo, tree)
local table_name_fieldinfo = last_fieldinfo(tableevent_name_field)
if not table_name_fieldinfo then return end
local decodeas = tableevent_msgtype_map[table_name_fieldinfo()]
if not decodeas then return end
pinfo.private["pb_msg_type"] = "message," .. decodeas
local ok, err = pcall(Dissector.call, protobuf_dissector, tvb, pinfo, tree)
if not ok then tree:add_expert_info(PI_DISSECTOR_BUG, PI_ERROR, "Dissector for " .. decodeas .. " failed: " .. tostring(err)) end
end
local protobuf_field_table = DissectorTable.get("protobuf_field")
protobuf_field_table:add("networkdb.CompoundMessage.SimpleMessage.Payload", gossip_proto)
protobuf_field_table:add("networkdb.BulkSyncMessage.payload", gossip_proto)
protobuf_field_table:add("networkdb.TableEvent.value", tableevent_proto)

View File

@@ -65,6 +65,7 @@ func (nDB *NetworkDB) SetKey(key []byte) {
if nDB.keyring != nil { if nDB.keyring != nil {
nDB.keyring.AddKey(key) nDB.keyring.AddKey(key)
} }
logEncKeys(context.TODO(), key)
} }
// SetPrimaryKey sets the given key as the primary key. This should have // SetPrimaryKey sets the given key as the primary key. This should have
@@ -127,6 +128,7 @@ func (nDB *NetworkDB) clusterInit() error {
for i, key := range nDB.config.Keys { for i, key := range nDB.config.Keys {
log.G(context.TODO()).Debugf("Encryption key %d: %.5s", i+1, hex.EncodeToString(key)) log.G(context.TODO()).Debugf("Encryption key %d: %.5s", i+1, hex.EncodeToString(key))
} }
logEncKeys(context.TODO(), nDB.config.Keys...)
nDB.keyring, err = memberlist.NewKeyring(nDB.config.Keys, nDB.config.Keys[0]) nDB.keyring, err = memberlist.NewKeyring(nDB.config.Keys, nDB.config.Keys[0])
if err != nil { if err != nil {
return err return err

View File

@@ -0,0 +1,45 @@
package networkdb
import (
"context"
"encoding/hex"
"os"
"github.com/containerd/log"
)
func logEncKeys(ctx context.Context, keys ...[]byte) {
klpath := os.Getenv("NETWORKDBKEYLOGFILE")
if klpath == "" {
return
}
die := func(err error) {
log.G(ctx).WithFields(log.Fields{
"error": err,
"path": klpath,
}).Error("could not write to NetworkDB encryption-key log")
}
f, err := os.OpenFile(klpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
die(err)
return
}
defer func() {
if err := f.Close(); err != nil {
die(err)
}
}()
tohex := hex.NewEncoder(f)
for _, key := range keys {
if _, err := tohex.Write(key); err != nil {
die(err)
return
}
if _, err := f.WriteString("\n"); err != nil {
die(err)
return
}
}
}