13 KiB
bcap 开发设计备忘
1. 这次重构的前提
当前已确认,直接使用 bcap 主包的范围很小,主要只有:
apps/tcpapps/b612中的tcmapps/b612中的tcpkill
因此这次讨论 bcap 新架构时,可以明确采用以下前提:
- 可以摒弃旧接口,不必为了兼容历史调用持续背负旧模型。
bcap子包暂时不动,尤其是libpcap、nfq这类输入适配层先保持现状。- 重点只讨论
bcap主包的新职责、新模型和新接口。
2. 当前主包存在的问题
当前 bcap 主包中,Packets、PacketInfo、StateDescript 这一套模型混合了承担以下几类职责:
- 报文事实解码
- TCP 状态推断
- 连接状态缓存
- 统计汇总
- 工具侧临时状态寄存
- 文本格式化辅助
这几个职责混在一起,直接导致了以下问题:
2.1 PacketInfo 同时承载“事实”和“推断”
例如:
SrcIP、DstIP、SrcMac、DstMac属于原始事实TcpSeq、TcpAck、TcpWindow虽然也是事实,但只适用于 TCPStateDescript则是推断结果Comment又是工具业务的临时状态
这会让上层调用方无法区分:哪些字段是包里本来就有的,哪些字段是状态机推出来的,哪些字段甚至只是上层工具临时借位存进去的。
2.2 主包过于 TCP 化
当前主模型的核心字段和访问器明显偏向 TCP:
TcpSeq()TcpAck()TcpWindow()TcpPayloads()StateDescript()
虽然已经开始支持 UDP、ICMP、ICMPv6,但整体接口风格仍然默认“TCP 才是主角,其他协议只是附带”。如果目标是做“通用 transport/protocol hint”,这个建模方式会持续妨碍扩展。
2.3 Packets 同时是解析器、状态仓库和工具 scratchpad
当前 Packets 既负责:
- 解析 gopacket.Packet
- 保存连接状态
- 维护统计
- 暴露
Key()查询 - 暴露
SetComment()给上层写入工具状态
这实际上把“协议解析层”和“工具业务状态层”耦死了。
2.4 字符串 key 被当成核心接口
当前以 tcp://a:b-c:d 这类字符串 key 作为主要流标识,这虽然方便快速实现,但不适合作为长期公共接口。
问题包括:
- 协议语义不够结构化
- 上层反复自行拼 key
- 不利于未来支持可逆 flow、单向 packet、无端口协议、ARP 等场景
2.5 文本格式化与 JSON 导出混入主包
像 GetStateDescription、PrintStats、ExportConnectionsToJSON 这种能力,不属于主包核心解析模型,应该降级为上层工具职责,或者至少转到辅助层,而不是继续成为主接口的一部分。
3. 新架构的总体目标
bcap 主包应收敛成:
- 一个统一的报文事实解码层
- 一个统一的协议 hint 推断层
- 一个以 TCP 为重点的轻量状态跟踪层
同时明确边界:
3.1 bcap 应负责的事
- 识别链路层、网络层、传输层协议
- 提取结构化元数据
- 输出通用 protocol hint
- 对 TCP 做轻状态推断
- 对 UDP/ICMP/ARP 做协议识别和元数据抽取
- 对未知协议输出“尽量完整的基础事实”
3.2 bcap 不应负责的事
- 诊断报告拼装
- 干预动作编排
- CLI/TUI 展示文案
- 工具侧临时状态寄存
- 业务策略倒计时
- 面向最终用户的统计打印
也就是说,bcap 的定位应是“提供事实和提示”,而不是“替工具做决策”。
4. 新架构的核心分层
建议主包收敛成 3 个核心对象:
DecoderTrackerAnalyzer
4.1 Decoder
Decoder 是无状态组件,只负责把 gopacket.Packet 解码成结构化“事实”。
职责:
- 识别 L2/L3/L4 协议
- 抽取 MAC、IP、端口、TTL、flags、payload 长度等事实
- 不做跨包状态推断
- 不维护连接表
- 不暴露工具业务状态
4.2 Tracker
Tracker 是有状态组件,只负责基于连续观测结果做轻量 hint 推断。
职责:
- 维护按 flow 组织的轻状态
- 针对 TCP 输出握手、挥手、重传、keepalive、RST 等 hint
- 针对 ICMP/ARP 等协议补充轻量语义标签
- 不负责最终展示
- 不负责业务动作控制
4.3 Analyzer
Analyzer 是便捷组合层,内部持有 Decoder + Tracker,给上层一个简单入口。
典型用途:
tcm/tcml这种一边收包一边展示的工具diag这种离线分析工具show这种需要 packet facts + hints 的浏览工具
这样设计后:
- 只关心事实解码的调用方可直接用
Decoder - 需要 hint 的调用方可用
Analyzer - 需要更细粒度控制的调用方可分别持有
Decoder和Tracker
5. 新的数据模型
新模型建议明确拆成“事实”和“推断”两层。
5.1 第一层:Packet Facts
建议定义统一的结构化事实模型,例如:
type Packet struct {
Meta Meta
Link LinkFacts
Network NetworkFacts
Transport TransportFacts
Raw RawFacts
}
其中:
Meta:时间戳、捕获长度、原始长度、相对时间等LinkFacts:链路层类型、源/目标 MAC、链路层协议等NetworkFacts:IPv4/IPv6/ARP 等信息,包含地址、TTL/HopLimit、fragment、protocol number 等TransportFacts:TCP/UDP/ICMP/unknown 等结构化内容RawFacts:payload 长度、原始 packet 引用等
这层全部表示“包里本来就有”的事实,不包含推断结论。
5.2 第二层:Observation
建议把“经过跟踪器分析后的结果”定义为单独对象:
type Observation struct {
Packet Packet
Flow FlowRef
Hints HintSet
}
这里:
Packet是原始事实Flow是结构化流引用Hints是推断结果
5.3 FlowKey / FlowRef
建议引入结构化 flow 标识,而不是继续把字符串 key 作为主接口。
type FlowKey struct {
Family NetworkFamily
Protocol TransportKind
Src Endpoint
Dst Endpoint
}
type FlowRef struct {
Forward FlowKey
Reverse FlowKey
Stable string
}
其中:
Forward/Reverse提供方向化 keyStable可以保留字符串形式,方便日志、map key、兼容旧调试习惯Endpoint应允许“只有地址没有端口”的协议
5.4 HintSet
建议将所有推断信息挂在 HintSet 上:
type HintSet struct {
Summary SummaryHint
Tags []Tag
TCP *TCPHint
UDP *UDPHint
ICMP *ICMPHint
ARP *ARPHint
}
这样做的好处是:
Summary提供给tcm/tcml这类工具快速展示一句话Tags提供统一筛选、着色、统计能力- 协议专属 hint 由独立结构体承载,不再污染通用顶层对象
6. 协议专属 hint 的建议形态
6.1 TCPHint
TCPHint 是新架构里的重点能力,建议承载:
- seq / ack / window
- flags
- payload length
- options 摘要
- handshake / teardown 阶段
- retransmission 判定
- keepalive 判定
- keepalive response 判定
- rst / ece / cwr 等特殊标记
- 是否疑似 out-of-order
同时建议把原来的 StateDescript uint8 弃用,改成更结构化的字段或 tag,例如:
Phase: TCPPhaseEvent: TCPEventTags: []Tag
6.2 UDPHint
UDP 先做轻支持即可,建议包括:
- payload length
- checksum presence / checksum value
- zero-length payload
- fragment 上下文关联信息(如果网络层提供)
不建议在主包内直接引入 DNS、QUIC 等应用层推断,除非后续确认收益足够大。
6.3 ICMPHint
建议至少包括:
- family: v4 / v6
- type / code
- id / seq
- 是否 echo request / echo reply
- 是否 destination unreachable / time exceeded
这层对 show 和 tcm/tcml 的“多协议展示”已经足够有价值。
6.4 ARPHint
如果目标是支持“展示其他协议”,ARP 必须成为一等公民。
建议至少包括:
- operation
- sender MAC / sender IP
- target MAC / target IP
- request / reply tag
同时 Decoder 对 ARP 不应再因为没有 NetworkLayer() 而直接报错。
7. 标签体系建议
建议在主包内定义统一 tag 体系,供所有上层工具复用。
例如:
tcp.handshake.syntcp.handshake.synacktcp.handshake.acktcp.teardown.fintcp.keepalivetcp.keepalive.responsetcp.retransmittcp.rsttcp.ecetcp.cwrudp.packeticmp.echo-requesticmp.echo-replyicmp.unreachablearp.requestarp.replytransport.unknown
这比继续依赖一个不断膨胀的 StateDescript 枚举更稳。
8. 状态与并发语义
这是新架构里必须提前定死的部分。
8.1 Decoder 的并发语义
Decoder 无状态,应明确支持并发使用。
8.2 Tracker 的并发语义
Tracker 有状态,应明确:
- 同一 flow 的观测结果必须按顺序输入
- 允许多 flow 并发,但内部状态更新语义以 flow 顺序为准
- 如果调用方无法保证顺序,则 hint 结果可能退化
这点非常重要。当前的 shardedMap 只能提供 map 级线程安全,不能自动提供时序正确性。TCP hint 尤其依赖时序,因此新接口必须把这一点写进设计,而不是默认调用方自己猜。
8.3 工具侧临时状态必须外移
例如 tcml 的:
- delay countdown
- block / allow 状态
- 业务关键字命中后的动作状态
都不应再借用 Comment/SetComment 写进 bcap 状态仓库。
新架构里,Tracker 只维护协议分析所需状态,不维护工具策略状态。
9. 错误模型建议
建议错误也按层次整理:
- 解码失败
- 支持范围外
- 协议字段损坏
- 状态推断降级
同时要允许“部分可用”的结果。
例如:
- 能识别到 IPv4,但 transport 未识别
- 能识别到 ARP,但字段不完整
- 能拿到 TCP flags,但因为缺少上下文无法判定 retransmit
这种情况下应尽量返回事实层结果,而不是简单整体失败。
10. 主包不再保留的旧概念
建议在新架构中明确移除或降级以下旧概念:
PacketInfoPacketsStateDescript()Comment()/SetComment()GetStateDescription()PrintStats()ExportConnectionsToJSON()- 各类以字符串 key 作为核心接口的模型
原因是:
PacketInfo混杂事实、推断和业务临时状态Packets角色过多,不适合作为长期主对象StateDescript无法优雅承载多协议 hintComment/SetComment明显越界- 格式化和导出不应继续作为主包核心能力
11. 建议的新接口形态
建议主包至少提供两层使用方式。
11.1 低层接口
decoder := bcap.NewDecoder(opts)
pkt, err := decoder.Decode(gpkt)
tracker := bcap.NewTracker(opts)
obs, err := tracker.Observe(pkt)
适合:
- 想分别控制解码与跟踪的调用方
- 想单测某一层的调用方
- 想把事实层和 hint 层分开使用的工具
11.2 便捷接口
analyzer := bcap.NewAnalyzer(opts)
obs, err := analyzer.ObservePacket(gpkt)
适合:
tcmtcmldiagshow
这类工具大多要的是“输入 gopacket.Packet,得到事实 + hint”。
12. 对现有调用方的迁移影响
12.1 tcpkill
迁移成本最低。
它主要只需要:
- TCP 四元组
- seq / ack / window
- MAC
因此它可以优先迁移到:
Decoder- 或
Analyzer中的Observation.Packet + Observation.Hints.TCP
12.2 tcm / tcml
迁移重点在展示层。
它们当前主要依赖:
StateDescriptTcpSeq/TcpAck/TcpWindow/TcpPayloadsKey/ReverseKeyComment/SetComment
迁移后应改成:
- 用
Observation.Hints.Summary或Tags做展示语义 - 用结构化
FlowRef替代字符串 key 拼接 - 将
tcml的动作状态迁出bcap
12.3 diag
diag 会受益最大。
它本质上就是一个消费“事实 + hint”的离线分析器。等 bcap 能稳定输出结构化 TCP/ICMP/ARP/UDP hint 后,diag 内部很多启发式逻辑会更容易表达,也更少依赖旧的 TCP 枚举状态。
13. 与子包的边界
本次先不动子包,但应明确子包定位:
libpcap:抓包输入适配层nfq:NFQUEUE 输入适配层
它们不属于主模型核心,只是 gopacket.Packet 的来源适配。
换句话说:
bcap主包定义“如何理解 packet”- 子包定义“packet 从哪里来”
这个边界应该长期保持稳定。
14. 当前结论
如果重做 bcap 主包,最合理的方向是:
- 主包已经从
Packets/PacketInfo旧模型切换到Decoder/Tracker/Analyzer新模型。 - 明确分离“报文事实”和“推断 hint”。
- 引入结构化
FlowKey/FlowRef,不再把字符串 key 作为主接口。 - 用
HintSet + Tags + protocol-specific hints取代StateDescript。 - 删除
Comment/SetComment这类工具业务越界能力。 - 让
bcap成为“轻量协议事实 + hint 提供层”,而不是“工具状态仓库”。
15. 推荐的实施顺序
建议真正开始改实现时,按下面顺序推进:
- 先定义新的公共类型草图:
Packet、Observation、FlowKey、HintSet、TCPHint、ICMPHint、ARPHint。 - 再实现无状态
Decoder,保证多协议事实提取完整。 - 再实现有状态
Tracker,先把 TCP hint 迁进去。 - 再提供
Analyzer便捷入口。 - 最后再让
apps/tcp和apps/b612的调用方迁到新接口,并删除旧兼容 facade。
在这个顺序下,主架构会先稳定下来,后续迁移也更可控。