Zenoh 專案的 Wireshark dissector 是現代系統程式設計的精彩範例,展現了如何用 Rust 實現安全又強大的網路分析工具,同時善用既有協定實作。本文筆者將探討 zenoh-dissector
插件的架構、實作方式與安裝流程。
zenoh-dissector
是用 Rust 編寫的 Wireshark 插件,能深入剖析 Zenoh 協定封包。它運用 Rust 的記憶體安全特性與既有的 zenoh-protocol
crate,不僅易於維護,也可以高效分析 Zenoh 網路流量,無需自行重寫協定解析邏輯。
注意: 本插件需 Wireshark 4.4 版。若需舊版 Zenoh 協定(0.10.0 之前),可用 legacy Lua 插件。
編譯 dissector 前需具備:
各平台安裝方式:
Linux (Ubuntu):
sudo add-apt-repository -y ppa:wireshark-dev/stable
sudo apt update
sudo apt install -y wireshark-dev wireshark
macOS:
brew install --cask wireshark
# 編譯時需建立符號連結
ln -snf $(find /Applications/Wireshark.app/Contents/Frameworks -name "libwireshark.*.dylib" | tail -n 1) libwireshark.dylib
export WIRESHARK_LIB_DIR=$(pwd)
Windows (Chocolatey):
choco install -y wireshark
編譯插件:
cargo build --release
安裝至 Wireshark:
Linux:
mkdir -p ~/.local/lib/wireshark/plugins/4.4/epan
cp ./target/release/libzenoh_dissector.so ~/.local/lib/wireshark/plugins/4.4/epan/
macOS:
mkdir -p ~/.local/lib/wireshark/plugins/4.4/epan
cp ./target/release/libzenoh_dissector.dylib ~/.local/lib/wireshark/plugins/4.4/epan/libzenoh_dissector.so
Windows:
$epan_dir = "$Env:APPDATA\Wireshark\plugins\4.4\epan"
New-Item -ItemType Directory -Path $epan_dir -Force
cp .\target\release\zenoh_dissector.dll $epan_dir
Help -> About Wireshark -> Plugins
驗證插件安裝情況zenoh
篩選 Zenoh 流量此 dissector 採用典型的 Rust/C interoperability 架構,將功能分散至兩個 crate:
epan-sys
crate 提供 Wireshark Enhanced Packet ANalyzer (EPAN) 函式庫的低階 unsafe
FFI 介面,特色如下:
bindgen
自動由 Wireshark 的 C 標頭構建 Rust 函式簽名unsafe
代碼,最大限度縮小非安全範圍如欲進一步瞭解 Rust/C FFI,請參見前文:Day 14: 在 Zenoh 中橋接 Rust 與 C — 第 1 部分:架構與程式碼生成。
主 crate 包含所有安全、封包剖析邏輯:
zenoh-protocol
與 zenoh-codec
crate這種設計分離確保robustness: 將 unsafe
侷限於 FFI,其餘全部享有 Rust 的安全保證。
註冊流程在 zenoh-dissector/src/lib.rs
透過可供 C 調用的導出函數完成:
#[no_mangle]
#[used]
static plugin_want_major: std::ffi::c_int = 4;
#[no_mangle]
#[used]
static plugin_want_minor: std::ffi::c_int = 4;
plugin_register
提供兩個主要 callback:
register_protoinfo
:註冊協定 ("Zenoh", "zenoh") 及所有顯示欄位register_handoff
:指派解析器指定埠號(TCP/UDP 7447)協定狀態以 thread_local!
靜態變數 PROTOCOL_DATA
管理,全域狀態管理乾淨俐落。
Rust 巨集基礎請見前文:Day 10: Rust Macro 熱身:從
macro_rules!
到 Derive Macro
其中的亮點是 zenoh-dissector/src/macros.rs
裡的巨集,能自動定義協定欄位:
impl_for_struct!
與 impl_for_enum!
巨集自動生成兩個關鍵 trait 實作:
Registration
trait:定義 Wireshark 欄位如何註冊
generate_hf_map()
:建立 Wireshark 欄位註冊表generate_subtree_names()
:產生 UI 分層欄位名AddToTree
trait:定義解析資料如何顯示在 Wireshark dissection tree
add_to_tree()
:遞迴填充 UI tree 的協定資料impl_for_struct!
巨集於 zenoh-dissector/src/macros.rs
具高度彈性,可處理多種欄位型態:
macro_rules! impl_for_struct {
(
struct $struct_name:ident {
// 一般欄位 - 以文字呈現
$($field_name:ident: $field_ty:ty,)*
// 展開欄位 - 創建子樹
$(#[dissect(expand)] $expand_name:ident: $expand_ty:ty,)*
// 向量欄位 - 集合遍歷
$(#[decode(vec)] $vec_name:ident: Vec<$vec_ty:ty>,)*
// 選擇性欄位 - 條件性顯示
$(#[dissect(option)] $skip_name:ident: Option<$option_ty:ty,)*
// 列舉欄位 - 顯示 enum variant
$(#[dissect(enum)] $enum_name:ident: $enum_ty:ty,)*
}
) => { /* macro body */ }
}
產生的 Registration
實作:
impl Registration for $struct_name {
fn generate_hf_map(prefix: &str) -> HeaderFieldMap {
let mut hf_map = HeaderFieldMap::new()
// 每個一般欄位建立文字條目
$(
.add(
format!("{}.{}", prefix, stringify!{$field_name}),
&stringify!{$field_name}.to_case(Case::Title),
FieldKind::Text
)
)*
// 向量欄位建立分支條目
$(
.add(
format!("{}.{}", prefix, stringify!{$vec_name}),
stringify!{$vec_ty},
FieldKind::Branch
)
)*;
// 遞迴擴展巢狀欄位定義
$(
hf_map.extend(<$vec_ty>::generate_hf_map(&format!("{prefix}.{}", stringify!{$vec_name})));
)*
hf_map
}
}
產生的 AddToTree
實作:
impl AddToTree for $struct_name {
fn add_to_tree(&self, prefix: &str, args: &TreeArgs) -> Result<()> {
// 每個一般欄位加進 tree
$(
let hf_index = args.get_hf(&format!("{prefix}.{}", stringify!{$field_name}))?;
unsafe {
epan_sys::proto_tree_add_string(
args.tree,
hf_index,
args.tvb,
args.start as _,
args.length as _,
nul_terminated_str(&format!("{:?}", self.$field_name))?,
);
}
)*
// 向量欄位遍歷,每個元素加進 tree
$(
for item in NetworkMessageIter::new(self.reliability, self.$vec_name.as_slice()) {
item.add_to_tree(&format!("{prefix}.{}", stringify!{$vec_name}), args)?;
}
)*
// 展開欄位則委派給自身實作
$(
self.$expand_name.add_to_tree(
&format!("{prefix}.{}", stringify!{$expand_name}),
args
)?;
)*
Ok(())
}
}
摘自 zenoh-dissector/src/zenoh_impl.rs
:
impl_for_struct! {
struct InitSyn {
version: u8,
whatami: WhatAmI,
zid: ZenohId,
resolution: Resolution,
batch_size: BatchSize,
ext_qos: Option<QoS>,
ext_auth: Option<Auth>,
// ... 更多 extension 欄位
}
}
這樣僅用一行巨集呼叫就能自動生成約 80 行原本需手動寫的重複程式碼。
巨集系統在編譯期就建立 Rust struct 欄位與 Wireshark 顯示元素的映射:
batch_size
轉成 "zenoh.init.batch_size" 給 Wiresharkbatch_size
變成 UI 的 "Batch Size"此方法帶來下列效益:
這種方式:
主要剖析流程於 dissect_main
與 dissect_heur
:
從 Wireshark 的 tvbuff_t
安全複製 raw bytes 至 Rust Vec<u8>
。
能正確處理 TCP 流重組,檢查 can_desegment
並於訊息不完整時要求更多資料。
利用 zenoh_codec::Decode
trait 從 buffer 解析 TransportMessage
,這是架構上的重大勝利,直接用 battle-tested 的實作。
加進 tree 的方法皆用 epan-sys
函式(如 proto_tree_add_string
, proto_item_add_subtree
),構建 Wireshark 可展開樹狀介面。
如 "7447 → 7447 [InitSyn, Frame(DATA)]" 給封包列表用的摘要說明。
現在的 Rust 版本是舊 Lua 插件(v0.7.2-rc 分支)升級而來,這轉型彰顯了現代架構的優勢。
舊 Lua dissector (zenoh.lua
, 約 1,500 行) 傳統 Wireshark plugin 方式:
手動協定解析:
function parse_zint(buf, bsize)
local i = 0
local val = 0
repeat
local tmp = buf(i, 1):uint()
val = bit.bor(val, bit.lshift(bit.band(tmp, 0x7f), i * 7))
i = i + 1
until (bit.band(tmp, 0x80) == 0x00)
return val, i
end
海量手動欄位定義:
-- 幾百行都在定義
proto_zenoh.fields.init_flags = ProtoField.uint8("zenoh.init.flags", "Flags", base.HEX)
proto_zenoh.fields.init_vmaj = ProtoField.uint8("zenoh.init.v_maj", "VMaj", base.u8)
proto_zenoh.fields.data_flags = ProtoField.uint8("zenoh.data.flags", "Flags", base.HEX)
-- ... 一百多個手動欄位定義
硬編碼訊息解讀:
function parse_msgid(tree, buf)
local msgid = bit.band(buf(0, 1):uint(), 0x1f)
if msgid == SESSION_MSGID.DECLARE then
local subtree = tree:add("DECLARE (Zenoh Transport)")
-- 手動子樹處理...
elseif msgid == SESSION_MSGID.DATA then
local subtree = tree:add("DATA (Zenoh Transport)")
-- 更多手動處理...
-- ... if-elseif 連串
end
直接重用協定、無需重寫:
// 用 zenoh-codec 解析
let messages = self.codec.read(&mut reader)?;
for message in messages {
message.add_to_tree(tree_item);
}
巨集自動產生一致性:
impl_for_struct! {
struct InitSyn {
version: u8,
whatami: WhatAmI,
zid: ZenohId,
}
}
自動同步:
zenoh-protocol
crate 同步記憶體安全與效能:
ZSlice
/ZBuf
零拷貝解析面向 | Lua(舊版) | Rust(新版) |
---|---|---|
程式碼量 | ~1,500 行 | ~500 行核心邏輯 |
協定解析 | 手動重寫 | 官方 crate 重用 |
欄位定義 | 100+ 手動定義 | 巨集自動化 |
維護 | 高(需手動同步) | 低(自動同步) |
型別安全 | 執行期錯誤可能 | 編譯期檢查 |
效能 | 直譯執行 | 原生編譯 |
Rust 重寫顯示良好架構能大幅提升維護效率:
zenoh-protocol
這種從 Lua 手刻到 Rust 巨集生成,形同從「解析器即協定複製品」轉為「解析器即資料檢視器」,更可持續。
此實作展現數種關鍵模式:
zenoh-dissector
展現了現代 Rust 如何同時滿足系統程式需求與網路協定分析。透過記憶體安全、協定複用與巨集自動化,實作出高維護性與高效能的 Wireshark 插件,堪稱網路分析工具的新典範。
巨集消除重複程式碼,搭配協定 crate 重用,讓 dissector 既健壯又簡潔。這種設計值得 Rust 生態系內其他協定的解析插件採用。