Wireshark 自定义协议解析器与多字段时序图 - 设计文档

Wireshark 自定义协议解析器与多字段时序图 - 设计文档

1. 项目概述

1.1 目标

创建一个完整的 Wireshark 自定义协议解析器,支持:

  • 自定义二进制协议解析(Lua)
  • I/O Graph 聚合绘图(AVG+小间隔)
  • 多字段独立坐标系时序图(Lua Tap + Chart.js HTML)
  • 按需数据提取(紧凑列式存储,JS 端按需生成图表数据)

1.2 技术栈

组件 版本 用途
Wireshark 3.4.x / 4.6.x 协议分析平台
Lua 5.2 / 5.3 解析器和绘图插件
GCC (MinGW64) 8.1.0+ pcap 生成器编译
Chart.js 4.x 前端图表渲染

2. 架构设计

2.1 系统架构

复制代码
┌──────────────────────────────────────────────────────────┐
│                       用户入口                           │
├──────────┬──────────────┬───────────────────────────────┤
│ Wireshark│  I/O Graph   │  Tools > MYPROTO >            │
│ GUI      │  (聚合绘图)   │  逐包原始值绘图                │
├──────────┴──────────────┴───────────────────────────────┤
│                                                          │
│  ┌──────────────────────────────────────────────────┐    │
│  │              HTML5 + Chart.js 页面               │    │
│  │  ┌──────┐ ┌──────┐ ┌──────┐ (最多5个独立坐标系) │    │
│  │  │图表1 │ │图表2 │ │图表3 │  每个有独立Y轴      │    │
│  │  │下拉选│ │下拉选│ │下拉选│  共享X轴(时间对齐)   │    │
│  │  └──────┘ └──────┘ └──────┘                      │    │
│  │  + 十字线贯穿所有图表 + 信息面板 + 缩放/平移     │    │
│  └──────────────────────────────────────────────────┘    │
│                         ▲                                │
│                    pktTimes + fieldData                  │
│                   (紧凑列式数据,按需生成图表)            │
│                         ▲                                │
│  ┌──────────────────────────────────────────────────┐    │
│  │  myproto_plotter.lua                              │    │
│  │  Listener.new() → tap.packet() → 紧凑列式序列化  │    │
│  └──────────────────────────────────────────────────┘    │
│                         ▲                                │
│  ┌──────────────────────────────────────────────────┐    │
│  │  myproto_dissector.lua                            │    │
│  │  Proto + ProtoField → dissector() → 字段注册     │    │
│  └──────────────────────────────────────────────────┘    │
│                         ▲                                │
│                    UDP Port 5555                         │
│  ┌──────────────────────────────────────────────────┐    │
│  │  sensor_data.pcap (gen_pcap.exe 生成)            │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘

2.2 文件清单

文件 说明
myproto_dissector.lua 协议解析器(11个字段,注册到 UDP 5555)
myproto_plotter.lua 多字段时序图插件(Listener + HTML 生成)
gen_pcap.c C 程序,生成测试 pcap
gen_plot.py Python 辅助脚本(tshark 输出转 HTML)
build.bat 一键编译+生成 pcap
plot.bat 一键提取+生成 HTML 图表

3. 协议定义

3.1 MYPROTO 协议结构(22字节)

偏移 字段 类型 字节 I/O Graph 绘图字段
0 magic uint16 2
2 version uint8 1
3 msg_type uint8 1 ✅ MsgType
4 sequence uint32 4 ✅ Sequence
8 timestamp_ms uint32 4 ✅ Timestamp(ms)
12 temperature int16 2 ✅ Temp©
14 humidity uint16 2 ✅ Humidity(%)
16 pressure uint16 2 ✅ Pressure(hPa)
18 voltage int16 2 ✅ Voltage(V)
20 status uint8 1 ✅ Status
21 reserved uint8 1

I/O Graph 可绘制字段 :必须是数值类型(uint*/int*/float/double)
绘图插件字段:8个(含非 I/O Graph 字段如 MsgType、Status)


4. 解析器设计

4.1 字段注册规则

lua 复制代码
-- ✅ 数值字段 - I/O Graph 和绘图插件均可用
ProtoField.uint8 / .uint16 / .uint24 / .uint32
ProtoField.int8 / .int16 / .int24 / .int32
ProtoField.float / .double

-- ❌ 非数值字段 - 两种绘图均不可用
ProtoField.string / .bytes / .bool

4.2 Wireshark 4.x Lua API 变更速查

3.4.x 语法 4.6.x 状态 4.6.x 替代方案
Listener.new("proto") ❌ Tap not found Listener.new() 无参
Field.new() in callback ❌ Error 必须模块顶层调用
pinfo.relative_time ❌ No such method Field.new("frame.time_relative")
pinfo.cols.protocol == "X" ❌ userdata #{ field_f() } > 0
tap:reset() ❌ No such method 自定义 reset_data()
NSTime 直接运算 ❌ type error tonumber(tostring(value))
string.gsub(s, "%", d) ❌ invalid % gsub(pat, function() return d end)
string.format("...%)..") ❌ invalid conversion %% 转义

5. 多字段时序图设计

5.1 需求与方案映射

需求 方案
最多5个字段独立坐标系,时序对齐 5个 Chart.js 实例,共享 pktTimes 数组
下拉列表选择字段,默认1个,添加按钮 addChart()/removeChart() 动态管理
鼠标虚线贯穿所有图表 + 交点坐标 crosshairPlugin + findNearestIdx() 二分查找
放大/缩小/还原 按钮控制 + 滚轮缩放 + 拖拽平移,X轴联动
数据按需提取 紧凑列式存储,JS端 makeChartData() 按需生成
中文界面 按钮/下拉/提示全部中文

5.2 数据格式设计

核心设计原则:数据按需组织,不事先生成

紧凑列式存储(嵌入 HTML)
javascript 复制代码
// 共享时间数组(仅一份)
var pktTimes = [0.000, 0.998, 1.980, 2.992, ...];

// 每个字段一个值数组(无重复时间)
var fieldData = {
  "Temp(C)":      [25.00, 25.79, 26.58, ...],
  "Humidity(%)":  [66.82, 67.54, 68.17, ...],
  "Pressure(hPa)":[103.11, 103.09, 103.06, ...],
  "Voltage(V)":   [3.395, 3.421, 3.443, ...],
  "Sequence":     [0, 1, 2, ...],
  "Timestamp(ms)":[783080489, 783081571, ...],
  "MsgType":      [0, 1, 2, ...],
  "Status":       [0, 1, 2, ...]
};

对比

指标 旧方案(per-field times) 新方案(列式共享)
时间存储 每字段重复一份 共享一份
HTML大小(200包8字段) ~42KB ~27KB (-36%)
图表数据 嵌入时全部预生成 JS按需 makeChartData()
按需图表数据生成
javascript 复制代码
function makeChartData(fieldName) {
  if (!fieldName || !fieldData[fieldName]) return [];
  var vals = fieldData[fieldName];
  var data = [];
  for (var i = 0; i < pktTimes.length; i++) {
    data.push({x: pktTimes[i], y: vals[i]});
  }
  return data;
}

仅在用户从下拉框选择字段时调用,将列式数据转换为 Chart.js scatter 格式。

5.3 Lua 数据序列化

lua 复制代码
local pkt_times = ser(times)   -- 共享时间数组

local field_data = string.format(
    '{"Temp(C)":%s,"Humidity(%%)":%s,"Pressure(hPa)":%s,"Voltage(V)":%s,' ..
    '"Sequence":%s,"Timestamp(ms)":%s,"MsgType":%s,"Status":%s}',
    ser(temp_vals), ser(hum_vals), ser(press_vals), ser(volt_vals),
    ser_int(seq_vals), ser_int(ts_vals), ser_int(msg_vals), ser_int(stat_vals)
)

-- 注:Humidity(%%) 中 %% 是 string.format 转义,输出为 Humidity(%)
-- 注:整数用 ser_int() 避免小数点

5.4 HTML 模板注入

lua 复制代码
local html = plot_html_template
html = html:gsub("__PKT_TIMES__", function() return pkt_times end)
html = html:gsub("__FIELD_DATA__", function() return field_data end)

使用 function() return data end 包裹 gsub 替换值,避免值中含 % 导致报错。


6. 交互设计

6.1 图表管理

复制代码
初始状态:1个图表(Temp(C))
          ┌─────────────────────────────┐
          │ [选择字段 ▼ Temp(C)] [✕]    │
          │ ─────────────────────────── │
          │ ~~~波形~~~              │
          └─────────────────────────────┘

点击"添加图表"后:2个图表
          ┌─────────────────────────────┐
          │ [选择字段 ▼ Temp(C)] [✕]    │
          │ ~~~波形~~~              │
          ├─────────────────────────────┤
          │ [选择字段 ▼ Humidity(%)] [✕] │
          │ ~~~波形~~~              │
          └─────────────────────────────┘

最多5个图表,添加按钮自动禁用
仅1个图表时,删除按钮隐藏

6.2 十字线交互

复制代码
鼠标移动 → findNearestIdx(timeVal) 二分查找 → crosshairIdx
         → 所有图表 afterDraw 画虚线 + 交点圆点 + 数值标签
         → infoPanel 显示已选字段的值(不是全部字段)

┌──────────────────────────────────────────┐
│ t = 5.980s | Temp(C): 26.58 | Humidity(%): 68.17 │
└──────────────────────────────────────────┘

关键优化

  • findNearestIdx() 使用二分查找(O(log n)),而非线性扫描
  • crosshairIdx 基于索引而非时间值,infoPanel 直接 fieldData[name][idx] 取值
  • scheduleRedraw() 通过 requestAnimationFrame 节流,每帧最多重绘一次
  • crosshairIdx 变化时才触发重绘

6.3 缩放/平移

操作 实现
按钮:放大/缩小/还原 doZoom(factor) / doReset()
鼠标滚轮缩放 以鼠标位置为中心,1.2x 倍率
拖拽平移 mousedown/mousemove/mouseup + applyXRange()
X轴联动 任何图表缩放/平移后,applyXRange() 同步所有图表

关键细节

  • mousedown 检查 e.target.tagName,跳过 SELECT/OPTION/BUTTON,避免拦截下拉框
  • wheel 事件 {passive: false} + e.preventDefault() 阻止页面滚动
  • 拖拽期间 isPanning 标志阻止十字线更新

6.4 下拉框不拦截修复

问题 :canvas getBoundingClientRect() 覆盖整个图表行(含下拉框区域),mousedown 事件被拦截为拖拽。

修复

javascript 复制代码
cc.addEventListener('mousedown', function(e) {
    if (e.button !== 0) return;
    var tag = e.target.tagName;
    if (tag === 'SELECT' || tag === 'OPTION' || tag === 'BUTTON') return;
    // ... 开始拖拽
});

6.5 infoPanel 仅显示已选字段

问题:默认显示全部8个字段值,信息过多。

修复:遍历已添加图表的 dropdown,只显示选中字段:

javascript 复制代码
function updateInfoPanel() {
  var shown = {};
  for (var i = 0; i < chartIds.length; i++) {
    var fn = document.getElementById('sel_' + chartIds[i]).value;
    if (fn && fieldData[fn] && !shown[fn]) {
      shown[fn] = true;
      html += fn + ': ' + fieldData[fn][crosshairIdx].toFixed(2);
    }
  }
}

7. Lua Tap 插件设计

7.1 架构

复制代码
用户点击 "Retap & Plot"
        │
        ▼
  reset_data()          清空历史数据
        │
        ▼
  retap_packets()       重新扫描所有包
        │
        ▼
  tap.packet()          逐包回调
   ├─ temp_f() 提取温度  ─→ temp_vals
   ├─ hum_f()  提取湿度  ─→ hum_vals
   ├─ press_f()提取气压  ─→ press_vals
   ├─ volt_f() 提取电压  ─→ volt_vals
   ├─ seq_f()  提取序列号 ─→ seq_vals
   ├─ ts_f()   提取时间戳 ─→ ts_vals
   ├─ msg_f()  提取消息类型→ msg_vals
   └─ stat_f() 提取状态   ─→ stat_vals
        │
        ▼
  do_plot()             生成 HTML
   ├─ ser() 序列化浮点数组
   ├─ ser_int() 序列化整数数组
   ├─ string.format() 组装 fieldData
   ├─ gsub(fn) 安全替换到模板
   ├─ io.open() 写入 HTML
   └─ os.execute() 打开浏览器

7.2 Field.new() 规则

lua 复制代码
-- ✅ 必须在模块顶层调用
local temp_f = Field.new("myproto.temperature")
local time_f = Field.new("frame.time_relative")

-- ❌ 不能在菜单回调或 dissector 内调用
win:add_button("Plot", function()
    local f = Field.new("xxx")  -- ERROR!
end)

7.3 协议判断

lua 复制代码
function tap.packet(pinfo, tvb)
    -- ❌ 4.x 中 pinfo.cols.protocol 是 userdata,不能字符串比较
    -- if pinfo.cols.protocol == "MYPROTO" then ... end

    -- ✅ 用 Field 提取判断
    local temp_info = { temp_f() }
    if #temp_info == 0 then return end  -- 非myproto包跳过
end

7.4 NSTime 转换

lua 复制代码
-- ❌ 4.x 中 frame.time_relative 返回 NSTime,不能直接运算
-- local t = time_info[1].value  -- type error

-- ✅ 转换为 number
local t = tonumber(tostring(time_info[1].value))

7.5 string.format 中的 % 转义

lua 复制代码
-- ❌ "Humidity(%)" 中的 % 被解释为格式转义
-- string.format("... Humidity(%) ...", ...)  -- invalid conversion '%)

-- ✅ %% 转义,输出为 %
string.format('..."Humidity(%%)"...', ...)

7.6 gsub 安全替换

lua 复制代码
-- ❌ 如果 series_data 含 %,gsub 会报错
-- html:gsub("__DATA__", series_data)

-- ✅ 用 function 包裹
html:gsub("__PKT_TIMES__", function() return pkt_times end)
html:gsub("__FIELD_DATA__", function() return field_data end)

8. 已修复的全部问题

# 错误 修复方案 文件
1 Tap myproto not found Listener.new() 无参 plotter
2 Field_new before Taps Field.new() 移到模块顶层 plotter
3 No such method 'reset' 自定义 reset_data() plotter
4 No MYPROTO data found Field 提取判断非 pinfo.cols.protocol plotter
5 No such method 'relative_time' Field.new("frame.time_relative") plotter
6 number expected, got NSTime tonumber(tostring(value)) plotter
7 invalid use of '%' in replacement gsub(pat, function() return data end) plotter
8 invalid conversion '%)' to 'format' Humidity(%%) 转义 plotter
9 下拉框不能下拉/选择 mousedown 检查 e.target.tagName plotter + gen_plot
10 infoPanel 显示全部字段 只遍历已选图表的 dropdown plotter + gen_plot
11 英文界面 按钮/下拉/提示改中文 plotter + gen_plot

9. 前端 JS 架构

9.1 模块划分

复制代码
├── 数据层
│   ├── pktTimes[]         共享时间数组
│   ├── fieldData{}        列式字段数据
│   └── makeChartData(fn)  按需生成 Chart.js scatter 数据
│
├── 图表管理
│   ├── addChart(field)    添加图表(默认1个,最多5个)
│   ├── removeChart(id)    删除图表(至少保留1个)
│   ├── changeField(id)    切换字段(销毁+重建 Chart 实例)
│   └── updateAddBtn()     更新按钮状态(禁用/显示删除)
│
├── 十字线
│   ├── crosshairPlugin    Chart.js afterDraw 插件
│   ├── findNearestIdx(t)  二分查找最近包索引
│   └── updateInfoPanel()  仅显示已选字段值
│
├── 缩放/平移
│   ├── doZoom(factor)     按钮缩放
│   ├── doReset()          还原
│   ├── applyXRange(mn,mx) X轴联动
│   └── 事件:wheel/mousedown/mousemove/mouseup
│
└── 性能优化
    ├── scheduleRedraw()   requestAnimationFrame 节流
    ├── crosshairIdx       索引复用(非时间值查找)
    └── 'none' animation   Chart.js 跳过动画

9.2 关键函数签名

javascript 复制代码
makeChartData(fieldName) → [{x: time, y: value}, ...]
addChart(fieldSel)       → void (创建 DOM + Chart 实例)
removeChart(id)          → void (销毁 Chart + 移除 DOM)
findNearestIdx(timeVal)  → number (二分查找,O(log n))
applyXRange(min, max)    → void (所有图表联动)
scheduleRedraw()         → void (RAF 节流)

10. 数据生成器设计

10.1 gen_pcap.c 要点

  • 网络字节序put16()/put32() 大端写入
  • IP 校验和ip_checksum() 计算
  • 传感器模拟:正弦波 + 随机抖动

10.2 数据范围

字段 最小值 最大值 单位换算
温度 17.00°C 32.99°C 值/100
湿度 30.00% 70.00% 值/100
气压 99.30 hPa 103.29 hPa 值/100
电压 3.100V 3.499V 值/1000

11. 构建与部署

11.1 编译

bash 复制代码
gcc -O2 -o gen_pcap.exe gen_pcap.c -lm
gen_pcap.exe sensor_data.pcap 200

11.2 安装插件

bash 复制代码
copy myproto_dissector.lua %APPDATA%\Wireshark\plugins\
copy myproto_plotter.lua    %APPDATA%\Wireshark\plugins\

11.3 版本兼容性

Wireshark Lua 解析器 I/O Graph Lua Tap io,stat
3.4.x 5.2 ✅ AVG ⚠️ 部分API ⚠️ 返回0
4.6.x 5.3 ✅ AVG ✅ 全部修复 ✅ 正确语法

文档版本 : 3.0
更新日期 : 2026-05-17
兼容版本: Wireshark 3.4.x / 4.6.x

相关推荐
humcomm3 小时前
AI编程对前端架构师技能的具体要求有哪些变化
前端·系统架构·ai编程
程序员果子3 小时前
LangGraph :构建复杂有状态智能体的核心框架
人工智能·python·架构·langchain·prompt·ai编程·langgraph
_大学牲3 小时前
从零实现自己的agent第六期:Agent Team团队协作
github·agent·ai编程
o_insist3 小时前
everything-claude-code 在 Codex 的应用:不要照搬全家桶,而是做一套更聪明的增强层
人工智能·ai编程·vibecoding
_大学牲3 小时前
从零实现自己的agent第一期:什么是agent
github·agent·ai编程
_大学牲3 小时前
从零实现自己的agent第二期: 百行代码从零手搓agent
github·agent·ai编程
正旺单片机3 小时前
claude code 笔记
人工智能·ai编程
码码哈哈0.03 小时前
20260517 最新官网版本Codex 下载
ai编程