mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
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:
111
contrib/wireshark/README.md
Normal file
111
contrib/wireshark/README.md
Normal 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
|
||||||
587
contrib/wireshark/memberlist.lua
Normal file
587
contrib/wireshark/memberlist.lua
Normal 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)()
|
||||||
59
contrib/wireshark/moby-networkdb.lua
Normal file
59
contrib/wireshark/moby-networkdb.lua
Normal 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)
|
||||||
@@ -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
|
||||||
|
|||||||
45
libnetwork/networkdb/debug.go
Normal file
45
libnetwork/networkdb/debug.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user