新闻详情

新闻详情

首页 / 资讯中心 / 详情

GOOSE报文解析实战:从以太网裸帧到ASN.1编码的逐字节拆解

发布时间:2026/9/30 1:14:00来源:尧图网络
GOOSE报文解析实战:从以太网裸帧到ASN.1编码的逐字节拆解
简介《GOOSE报文解析》是一份面向智能变电站与电力自动化技术人员的PDF资料围绕ISO/IEC 8802-3帧格式梳理普通报文与广播报文的字段组成并从ASN.1 BER编码角度讲解Tag、Length、Value的解析方法。内容结合真实抓包示例逐步演示APDU Head、gocbRef、timeAllowtoLive、datSet、goID、stNum、sqNum、allData等信息的解码过程同时归纳BOOL、BIT-String、UTC时间、INT、Unsigned、Visible-String等类型的标记特征便于对照原始数据排错。资源为单个PDF文件压缩包约121KB轻量易读可作现场随手参考。已有1036人学习说明它常被用于协议入门与调试。这份PDF能帮助读者快速掌握GOOSE报文的结构要点与关键字段含义提升IEC 61850过程层通信的解析效率。1. GOOSE 报文解析从以太网裸帧里读出开关量、时间戳和状态序号搞变电站调试的人应该都有过这种经历抓包软件里拉出一长串十六进制前面是 MAC 地址、后面跟着 88 B8再往后全是看起来毫无规律的字节。如果没有把 GOOSE 报文的帧结构和 ASN.1 编码规则吃透这串数据就是一堆乱码。这份「GOOSE报文解析」资料恰好把最硬核的部分讲清楚了——从 ISO/IEC 8802-3 的以太网帧格式出发一路拆到 APDU 里的布尔量、位串、时间戳甚至还有三组完整报文的逐字节走读。它不仅适合刚接触 IEC 61850 的保护调试人员也适合要做协议解析工具或报文离线分析的二次开发工程师。本文把这套方法拆开讲透读完你就能对着抓包文件复现同样的解析过程。2. 以太网链路层与 GOOSE 帧结构先把 88 B8 外的每个字节安放到位2.1 普通报文与广播报文差在目的 MAC 和 VLAN TagGOOSE 报文直接承载在以太网帧上这和常见的 TCP/UDP 报文完全不同。它没有 IP 层也没有传输层链路层之上直接就是应用层数据。所以解析的第一步永远是先把以太网头剥干净。普通报文的结构是目的 MAC6 字节 源 MAC6 字节 TPID2 字节 TCI2 字节 以太网类型2 字节 APPID2 字节 APDU 长度2 字节 保留位4 字节 APDUm 字节。这里 TPID 固定是 0x8100表示后面跟着 VLAN Tag以太网类型是 0x88B8专门标记 GOOSE 报文。广播报文则简单一些目的 MAC 源 MAC 以太网类型 APPID APDU 长度 保留位 APDU。目的 MAC 直接写成 FF FF FF FF FF FF而且没有 TPID 和 TCI。原因在于广播报文不需要 VLAN 隔离直接在整个二层网络里扩散。提示实际工程里大量 GOOSE 报文用的是组播 MAC01 0C CD 01不是广播 MAC。组播报文的帧结构和普通报文一致同样可以带 VLAN Tag区别只在目的 MAC 的第一个字节是 01。识别一个报文是不是 GOOSE最快的方法是看以太网类型字段0x88B8 就是 GOOSE0x88B9 是 GSE 管理报文别混。很多抓包工具会直接把 0x88B8 翻译成 GOOSE但如果你在写底层解析器必须自己判断这个字段。2.2 从目的 MAC 到 APDU Head逐字段确认边界以资料里的普通报文为例前 18 个字节可以完整拆开01 00 00 00 00 07目的 MAC这不是广播而是组播地址01 开头说明是组播帧08 00 06 86 42 81源 MAC81 00TPID即 0x810040 03TCI高 3 位是用户优先级这里 0x4003 折算下来优先级为 4VID 是 388 B8以太网类型00 07APPID用于区分同一个网络里的不同 GOOSE 控制块00 90APDU 长度从保留位后面开始算0x0090 144 字节00 00 00 00保留位发送方固定填 0接收方一般忽略拆完这 18 个字节后面就是 APDU Head61 81 85 80 25 50。这里的 61 是 ASN.1 的 Application 类型标记0x81 在 BER 编码里表示「后续长度用 1 个字节表示」0x85 表示从下一个字节开始到报文结束一共 133 字节。80 则是 GOOSE PDU 的 Tag25 是它的长度50 开始才是真正的内容。我一般会在解析器里先算一遍「APDU 长度字段的数值 8 是否等于实际 APDU 字节数」。比如上面 0x0090 8 152从保留位往后数 152 个字节正好是报文的结尾。如果对不上不是长度字段写错就是抓包的时候报文被截断了。2.3 长度字段的两个关键点m8 的由来和保留位APDU 长度的计算是新手最容易卡住的地方。资料里写的是「APDU 数据的长度m8」这个 m 指的是 61 81 之后 GOOSE PDU 的长度8 则是从 80gocbRef 的 Tag到 APDU 内容结束的总长度偏移。换句话说你在报文里看到的长度字段是从保留位之后开始算的不是从 61 开始算。保留位一共 4 个字节有些协议文档里叫 Reserved1 和 Reserved2各占 2 字节。IEC 61850-8-1 里规定它们必须为 0但实际抓包中偶尔能看到非 0 值——多数是厂商固件的私有扩展。解析时不要因为保留位非 0 就报错跳过即可。3. ASN.1 BER 编码与 Tag 解析TLV 三段式是读懂 GOOSE 的钥匙3.1 TLV 结构Tag Length Value 怎么拼出嵌套数据GOOSE PDU 里的所有数据都遵循 ASN.1 BER 编码的 TLV 规则Tag 告诉你这个字段是什么类型Length 告诉你 Value 有多长Value 是实际数据。看起来简单但嵌套结构让很多人翻了车。拿 gocbRef 来举例80 25 32 41 31 4A 31 51 36 50 72 6F 74 65 63 74 69 6F 6E 2F 4C 4C 4E 30 24 47 53 45 70 72 6F 74 65 63 74 69 6F 6E。80 是 Tag25 是长度37 字节后面 37 个字节是 Visible-String 类型的数据。32 41 31 4A 31 51 36 按 ASCII 码翻译过来就是 2A1J1Q6。嵌套结构就更考验耐心。Goose3 报文里的 allData 是 A2 开头A2 本身是一个 Constructed 类型意味着它的 Value 里还包含多个子 TLV。A2 20 表示后面有 32 字节的数据这 32 字节里又拆出 8 组结构每组以 A2 05 或 A2 20 开头。解析这类嵌套时必须一层一层剥先按外层 Length 切出完整子块再递归解析子块内部的 TLV。3.2 Tag 字节的位解析Bit7/6/5 各管什么Tag 的解析不能只看十六进制值要拆到二进制位。Bit 7 和 Bit 6 组合表示 Tag 的类别00 是 Universal01 是 Application10 是 Context-Specific11 是 Private。GOOSE 报文里大量使用 Context-Specific 的 Tag比如 80、81、82、83。Bit 5 是 Primitive 还是 Constructed 的标志。0 表示 PrimitiveValue 是原子数据1 表示 ConstructedValue 是嵌套的 TLV 集合。A2 的二进制是 1010 0010Bit5 1所以它是 Constructed。而 83 的二进制是 1000 0011Bit5 0它是 Primitive 的 BOOL 型。Bit 4 到 Bit 0 是 Tag 的实际编号。80 的编号是 081 是 182 是 2以此类推。在 GOOSE 规范里这些编号有固定含义0 是 gocbRef1 是 timeAllowedtoLive2 是 datSet3 是 goID4 是 t5 是 stNum6 是 sqNum7 是 test8 是 confRev9 是 ndsCom10 是 numDatSetEntries11 是 allData。3.3 数据类型 Tag 对照表从 BOOL 到 UTC 时间的编码约定解析时只要看到对应 Tag就能直接确定数据类型不用猜。以下是 GOOSE 报文里出现频率最高的几个Tag 值数据类型说明0x83BOOL1 字节0x00 为 FALSE0x01 为 TRUE0x84BIT-String长度不固定首字节表示末尾无效位数0x85Int有符号整数0x86Unsigned无符号整数0x8AVisible-StringASCII 字符串0x91UTC 时间8 字节从 1970-01-01 00:00:00 起算BIT-String 是最容易读错的类型。比如 84 03 02 00 00Tag 是 84Length 是 3Value 是 02 00 00。这里的第一个字节 02 表示后面 2 个字节里有 2 位是无效位有效数据其实是 16 位减 2 位。换算出来是 14 个 0跟资料里的 00000000000000B 对得上。UTC 时间类型的解析也有讲究。91 08 后面跟 8 个字节按 BER 的 GeneralizedTime 编码前 4 字节是秒数的高位后 4 字节是低位合起来是一个 64 位整数。资料里 00 00 00 00 00 00 00 00 表示 1970 年 1 月 1 日零点这在真实工程里几乎不会出现——正常应该是自 1970 年以来的秒数。碰到全 0 时间戳先别慌大概率是测试报文或者设备没对时。4. 三组报文实例逐字节走读从裸帧一路读到 allData 展开4.1 普通报文实例ACSI 模型映射与 TLV 展开资料里第一组报文是典型的普通单播 GOOSE完整字段如下gocbRef80 25 开头内容是 P2A1J1Q6Protection/LLN0$GSEprotection对应 IEC 61850 里的 GOOSE 控制块引用路径timeAllowedtoLive81 02 05 00值 0x0500 1280ms表示接收方在这个时间内没收到下一帧就判定链路中断datSet82 25 开头数据集的引用路径goID83 01 370x37 按 ASCII 码是 7这是应用标识t84 08 后面 8 字节时间戳全 0stNum85 01 01状态序号为 1说明这是状态变化后的第一帧sqNum86 03 02 70 A10x0270A1 159905这是稳定状态下的序号test87 01 00FALSEconfRev88 01 01配置版本号 1ndsCom89 01 00FALSEnumDatSetEntries8A 01 04数据集里有 4 个数据项allDataAB 10 开头1 字节长表示后面 16 字节是数据集合allData 内部的 4 个数据项依次是83 01 00boolean FALSE、84 03 02 00 00bit-string 00000000000000B、83 01 00boolean FALSE、84 03 02 00 00bit-string 00000000000000B。两层嵌套的 TLV 结构在代码里可以用递归处理判断 Tag 是否为 ABallData或 A2Constructed 的 IEC 61850 数据类型是则进入子循环否则直接按 Primitive 解析。写解析器时核心逻辑就是「根据 Tag 决定是否递归根据类型表决定 Value 怎么转」。4.2 Comgoose 组播报文VLAN 缺席与十六进制转 ASCII第二组报文来自 Comgoose 工具目的 MAC 是 01 0C CD 01 00 04这是标准的 GOOSE 组播地址。帧结构里没有 TPID 和 TCI开头直接是源 MAC 01 0C CD 01 10 10紧接着 88 B8 以太网类型。这说明发送方没有启用 VLAN组播报文直接裸奔在二层网络里。APPID 是 00 04APDU 长度是 00 94148 字节。gocbRef 的内容是 58 37 32 31 32 5F 32 48 42 50 52 4F 54 2F 4C 4C 4E 30 24 47 4F 24 67 6F 63 62 54 78转成 ASCII 就是 X7212_2HBPROT/LLN0$GO$gocbTx。这里有个值得注意的差异普通报文的 gocbRef 用 $GSE 作为数据集引用分隔符而这组报文用的是 $GO$。这两种写法在 IEC 61850 模型里都存在前者通常指向 GSE 控制块本身后者指向 GOOSE 输出数据集。解析时不要把分隔符写死按 $ 拆分段即可。allData 里有 8 个数据链每两个一组轮流出现 boolean 和 bit-string。numDatSetEntries 的值是 08和实际展开的数据项数量一致。如果这两个数字对不上基本可以判定报文在传输中被篡改或者发送端配置有误。4.3 带嵌套结构的 allDataA2 标记的逐层拆解第三组报文最复杂allData 不是直接装 Primitive 数据而是装了一组 A2 开头的嵌套结构。外层 AB 82 01 10AB 是 allData 的 Tag82 表示长度用 2 字节表示01 10 就是 272 字节。这 272 字节里装了 8 组结构每组结构都是 A2 20 或 A2 05 开头。以第一组为例A2 20 A2 05 85 01 00 89 00 86 01 00 84 02 06 40 84 03 03 00 00 91 08 45 65 09 C2 7F FF FF FF 18 83 01 00。A2 20 表示这组结构占 32 字节内部第一个子结构 A2 05 占 5 字节内容是 85 01 00Int 类型值为 0接着 89 00 是 ndsCom86 01 00 是 sqNum84 02 06 40 是 BIT-String……这类带嵌套的报文在断路器位置上报、遥信变位场景里非常常见。每个 A2 结构对应一个数据对象内部包含品质、状态、时间戳等多个属性。如果拿着普通报文的解析逻辑去套很容易把 A2 当成未知 Tag 直接跳过导致关键数据全部丢失。正确的做法是先按 Length 切块再把每个块单独做一轮 TLV 解析。5. GOOSE 解析避坑指南长度错位、嵌套漏拆和时间戳全零5.1 长度算错导致整个 APDU 解析错位现象按资料里的规则拆完 gocbRef 后后面的 timeAllowtoLive 怎么也对不上字段整体偏移。原因APDU 长度字段有两种计法——从保留位开始算和从 61 开始算。如果把 0x0090 当成从 61 开始算的长度解析器会多读 8 个字节。同理GOOSE PDU 的长度 0x85 是从 80 之后才开始算的80 本身不算在内。解决在解析器里做一次自校验。取到 APDU 长度字段 L 后验证「从保留位之后数 L 个字节是否正好到报文结尾」。如果报文尾对齐了说明长度解析正确如果还对不齐继续检查保留位是否被当成数据吞掉了。5.2 BIT-String 的无效位被当成有效数据现象84 03 02 00 00 解析出来的位串比实际多出 2 位导致布尔位错位。原因BER 编码的 BIT-String 第一个字节表示最后一个字节的无效位数。02 表示最后 2 位不算数真实有效位是 16 - 2 14 位。直接将整个位串拿去用会把末尾 2 个无效位也算进业务逻辑。解决解析 BIT-String 时先读首字节记为 unusedBits有效长度 Length * 8 - unusedBits。在代码里把无效位置 0再映射到布尔数组。16 位位串里每一位可能代表一个遥信点错一位就是另一个间隔的开关状态这种错误在现场排查起来极其耗时。5.3 A2 嵌套结构被误判为未知 Tag现象Goose3 报文的 allData 展开后全是乱码A2 20 后面的数据被当成独立字段解析。原因解析器只处理了 ABallData的 Tag没处理 AB 内部嵌套的 A2Constructed 数据对象标记。看到 A2 就当成未知 tag导致子结构里的 85、86、84 全部被跳过。解决把 A2 纳入已知 Tag 列表并在解析循环里实现递归。判断 Tag 的 Bit5 是否为 1Constructed是则先读取 Length再对 Length 范围内的字节递归调用同一解析函数。在 Python 里可以维护一个缓冲区下标每解析完一个完整 TLV 就偏移 Length 个单位。5.4 t 字段时间戳全 0设备尚未对时现象多台装置抓到的 GOOSE 报文t 字段的 8 个字节全部是 0换算时间是 1970 年。原因装置没有接入对时系统或者 IRIG-B 对时未生效。GOOSE 报文里的 t 是状态变化时刻全 0 会让站控层无法判断变位时间也影响 SOE 事件的先后排序。解决解析时把全 0 时间戳单独标记为「未对时」不要直接丢弃报文。同时检查装置的对时状态指示灯和网线连接。如果是实验室测试环境可以用支持 IEEE 1588 的交换机给装置打时间。5.5 stNum 与 sqNum 的状态机关系未验证现象sqNum 突然不连续或者 stNum 变化后 sqNum 没有归零。原因GOOSE 重发机制里 stNum 是状态序号状态变化时加 1sqNum 是报文序号每次重发加 1。一些装置在 stNum 变化后 sqNum 从 1 开始而另一些装置从 0 开始不同厂商实现有差异。解决解析器里把 stNum、sqNum 和 t 三个字段联动起来。stNum 变化时记录新状态并重置 sqNum 基线sqNum 连续但 stNum 不变说明是同一状态的重发此时不更新状态只刷新链路存活计时。发现 stNum 跳变超过 1 时先怀疑丢包再查发送端的重启记录。6. 手工解析工具的落地技巧用 Python 逐字节定位并校验结果自己写一个 GOOSE 报文解析脚本并不难核心就是几层循环拆 TLV。关键在解析结构设计上要支持递归嵌套同时把解析结果和原始十六进制对齐输出方便对照资料里的报文实例验证。import struct GOOSE_ETH_TYPE 0x88B8 def parse_tlv(data, offset0): tag data[offset] offset 1 # 读取长度BER 编码里 0x81 表示后跟 1 字节长度0x82 表示后跟 2 字节 length data[offset] offset 1 if length 0x81: length data[offset] offset 1 elif length 0x82: length struct.unpack(!H, data[offset:offset2])[0] offset 2 value data[offset:offsetlength] return tag, length, value, offset length def parse_goose(frame): # 以太网头固定 14 字节VLAN 标签后以太网类型才是 0x88B8 eth_type struct.unpack(!H, frame[12:14])[0] if eth_type 0x8100: eth_type struct.unpack(!H, frame[16:18])[0] # 跳过 TPID TCIAPPID 在 18-19 字节 appid struct.unpack(!H, frame[18:20])[0] apdu_len struct.unpack(!H, frame[20:22])[0] # APDU 从 24 字节开始前面有 4 字节保留位 apdu_start 24 else: appid struct.unpack(!H, frame[14:16])[0] apdu_len struct.unpack(!H, frame[16:18])[0] apdu_start 20 if eth_type ! GOOSE_ETH_TYPE: return None # 自校验长度APDU 实际字节数应等于长度字段 8 actual_apdu len(frame) - apdu_start if actual_apdu ! apdu_len 8: print(f长度校验失败: 期望 {apdu_len 8}实际 {actual_apdu}) results {} offset apdu_start while offset len(frame): tag, length, value, next_offset parse_tlv(frame, offset) results[tag] value offset next_offset return results这段代码里有两个关键点VLAN Tag 判断和长度自校验。TPID 是 0x8100 时以太网类型字段要往后移 4 字节APDU 长度字段的值需要加 8 才是实际字节数。如果抓包文件里有多帧报文建议循环遍历全部帧统计长度校验失败的比例一旦有帧异常就先检查是不是抓包工具截断而不是改解析逻辑。参数层面的注意项是长度字段的 BER 编码。单字节小于 0x80 时直接作为长度值大于等于 0x80 时表示后续有多个长度字节。资料里的 61 81 85 就是这么解析的0x81 是长度扩展标记后面紧跟 0x85 才是真实长度。很多 C 语言写的解析器忘了处理 0x81/0x82 分支遇到超过 127 字节的 GOOSE PDU 就会多读或少读长度解析结果全乱。写完后用资料里的三组报文做对照测试。第一组重点看 gocbRef 的 ASCII 转换结果是否完全一致第二组确认没有 VLAN 的帧头的偏移量是否处理正确第三组检查 A2 嵌套结构是否被正确递归展开。三组报文全部还原成 GoosPdu 的结构化输出解析器就算过关了。从那以后我每次拿到新的抓包文件都会强制走一遍这套流程先按以太网类型筛出 GOOSE 帧再做长度自校验然后逐层拆 TLV。最后一个习惯是拿到手先看 stNum 和 sqNum 是不是连续这两值能快速告诉我链路是否有丢包、装置是否重启过。这份解析思路能帮你少走很多弯路希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

NestJS GraphQL Federation Schema-First 实战:users-application 子服务完整实现解析 2026/9/30 2:01:30

NestJS GraphQL Federation Schema-First 实战:users-application 子服务完整实现解析

后端Web框架 【免费下载链接】nest A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀 项目地址: https://gitcode.com/GitHub_Trending/ne/nest 点击查…

阅读更多 →
旧Mac免费升级指南:用OpenCore Legacy Patcher装上新款macOS 2026/9/30 2:01:30

旧Mac免费升级指南:用OpenCore Legacy Patcher装上新款macOS

旧Mac免费升级指南:用OpenCore Legacy Patcher装上新款macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 打开"软件更新"却找不到任…

阅读更多 →
函数 2026/9/30 2:01:29

函数

一、引入 存在的问题: 1. 代码冗余,代码量太大 2. 维护性差,复制容易,修改难 如何解决此问题???? 1. 对反复的代码只写一次,并对它起个名字 2. 想使用次功能代码时&#…

阅读更多 →
AI生成可综合RTL:GPIO IP全生命周期设计实践 2026/9/30 2:01:23

AI生成可综合RTL:GPIO IP全生命周期设计实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Vue 2 渐进式框架技术全景:从仓库源码到构建生态的完整指南 2026/9/30 2:01:23

Vue 2 渐进式框架技术全景:从仓库源码到构建生态的完整指南

前端Web框架 【免费下载链接】vue This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core 项目地址: https://gitcode.com/gh_mirrors/vu/vue 点击查看 免费下载 Vue 2 是 Vue 团队维护的经典渐进式前端框架,本仓库(…

阅读更多 →
3ds Max到WebGL的三维交付链:gltf、TypeScript与性能实战 2026/9/30 2:01:22

3ds Max到WebGL的三维交付链:gltf、TypeScript与性能实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉