十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GOOSE报文解析实战:从十六进制到GoosePdu的TLV编码拆解

GOOSE报文解析实战:从十六进制到GoosePdu的TLV编码拆解 简介GOOSE报文解析PDF文档专注于智能变电站IEC 61850通信中GOOSE报文的帧结构与编码解析适合电力系统自动化、网络通信及协议分析领域的初学者和工程技术人员。资源为单个PDF文件大小约121KB内容紧凑已有1035人学习参考。文档基于ISO/IEC 8802-3帧格式详细说明目的MAC、源MAC、TPID、以太网类型0x88B8、APPID、APDU长度等字段并结合ASN.1的BER编码形式逐项解析Tag、Length、Value的含义与Tag标志位。针对BOOL型、BIT-String型、UTC时间型、INT型、Unsigned型、Visible-String型等常见数据类型文档均给出编码示例。同时通过两个报文分析实例对十六进制抓包数据逐字节解读帮助读者快速理解APDU Head、广播报文、allData数据集合等结构。无论用于入门学习还是日常报文排查都能提供清晰实用的指导。1. GOOSE报文解析从一帧十六进制样本到完整的GoosePdu做智能变电站调试的同行应该都有过这种经历抓包软件里看到一串 88 B8 开头的报文明明知道这就是 GOOSE但对着十六进制就是拆不出 gocbRef、stNum、allData 到底在哪一段。GOOSE报文解析的难点不在于协议多深而在于它把 ASN.1 BER 的 TLV 编码直接压在以太网帧里没有任何换行和分隔错一个字节后面全乱。这份资料最实在的地方是给了三组真实抓包的完整十六进制数据从带 VLAN 的普通报文到广播报文再到嵌套型 allData一步一步推给你看。适合刚接触 IEC 61850 报文分析的调试工程师也适合写过简单解析脚本但没系统看过 TLV 编码规则的人照着样本拆一遍比翻十页协议标准都管用。2. 二层帧头拆解目的MAC、TPID/TCI与0x88B8的定位规则2.1 普通报文 vs 广播报文TPID/TCI 到底该不该存在GOOSE 报文直接承载在 ISO/IEC 8802-3 的以太网帧上和 TCP/IP 那套完全没关系。普通报文的帧头结构是目的 MAC(6) 源 MAC(6) TPID(2) TCI(2) 以太网类型(2) APPID(2) 长度(2) 保留位(2) APDU。注意 TPID 固定是 0x8100这是 802.1Q VLAN 标记的入口TCI 里装的才是用户优先级、CFI 和 VID。但广播报文的结构会少一段目的 MAC 源 MAC 以太网类型 APPID 长度 保留位 APDU目的 MAC 是全 FF而且没有 TPID/TCI。这意味着判断一个抓包是不是 GOOSE不能上来就从第 12 字节找 0x88B8得先看第 12 字节是 TPID(0x8100) 还是直接就撞上以太网类型。我在现场就吃过这个亏拿到一组不带 VLAN 的 GOOSE 报文按带 VLAN 的偏移去数APPID 读出来全是错的。判断顺序应该是先看目的 MAC 是否以 01 开头组播再看第 12 字节。如果是 0x8100说明带 VLAN 标记以太网类型在第 15 字节如果不是 0x8100那第 12 字节本身就是以太网类型。资料里第二组 Comgoose 报文就是典型的不带 VLAN 样本目的 MAC 01 0c cd 01 00 04第 12 字节直接就是 88 B8。2.2 以太网类型、APPID、长度字段与保留位的分工0x88B8 是 GOOSE 报文的专属以太网类型看到这个值就能确定帧的类型。它后面紧跟 APPID两个字节用于区分同一个网络上的不同 GOOSE 控制块。调试多套保护装置时APPID 是快速定位报文来源的第一线索资料里三组样本的 APPID 分别是 0x0007、0x0004、0x0000各不相同。APPID 后面是长度字段资料里明确写了APDU数据的长度(m8)。这个 m8 很容易被误解——它不是 APDU 的长度而是 APDU 长度加上 8 字节开销。这 8 字节拆开看就是APPID 2 字节、长度字段自身 2 字节、保留位 2 字节、APDU 前缀的 2 字节。样本里长度字段后面连续出现 4 个 00 00前两个是保留位后两个其实是 APDU 的起始前缀连在一起最容易看混。实际解析时别按偏移硬吃要以 61 开头的 APDU Head 作为锚点。APDU 前缀再往后才是 61 81那是下一章要说的 ASN.1 部分。2.3 三组样本的帧头对照把资料里三组真实样本的帧头信息整理成一张表方便对照理解项目第一组普通报文第二组Comgoose第三组Goose3目的 MAC01 00 00 00 00 0701 0c cd 01 00 0401 0c cd 01 01 ff源 MAC08 00 06 86 48 4201 0c cd 01 10 1000 0d 60 9f 07 a6TPID/TCI81 00 / 40 03无81 00 / 80 00以太网类型88 b888 b888 b8APPID00 0700 0400 00长度字段00 90 (144)00 94 (148)01 79 (377)APDU 实际长度136140369注意第二组报文没有 TPID/TCI目的 MAC 和源 MAC 紧挨着就是 88 B8这就是 2.1 节说的判断顺序差异。第三组报文的长度字段是 01 79跨了字节解析时要把两个字节合并成 0x0179 再换算成十进制 377不是简单的 01 和 79 分开读。提示长度字段是 m8样本里 0x0090 对应的 APDU 实际长度是 136不是 144。APDU Head 里的 61 81 85 其中 0x85133正好是 136 减去头部的 3 字节交叉验证能确认前面帧头没有拆错。3. APDU Head 与 ASN.1 BER61 81、80 25 这类 Tag 的编码逻辑3.1 BER-TLV 的位级规则APPID 和长度字段之后整个报文的核心就是 APDU。APDU 采用 ASN.1 的 BER 编码全部是 TLV 结构Tag Length Value。这套编码规则是 GOOSE报文解析绕不开的基础Tag 是一个字节但它的含义分散在三个位段里。Tag 字节的第 7、6 位表示类型种类00 是 universal通用类型01 是 application应用类型10 是 context-specific上下文相关11 是 private私有。第 5 位表示构造方式0 是 primitive基本类型1 是 constructed构造类型这一点直接决定 Value 里面是普通数据还是嵌套了更多 TLV。第 4 到 0 位是具体的 Tag 号。以资料里的实际字节为例0x80 的二进制是 1000 0000第 7、6 位是 10context-specific第 5 位是 0primitiveTag 号是 0对应 gocbRef。0xAB 的二进制是 1010 1011第 7、6 位是 10第 5 位是 1constructedTag 号是 11对应 allData所以 allData 的 Value 是一个嵌套的数据集。0x61 的二进制是 0110 0001第 7、6 位是 01application第 5 位是 1constructedTag 号是 1这就是 APDU Head 的 SEQUENCE 标记。3.2 Length 的长格式与短格式BER 的 Length 字段有两种表示方式。单字节小于等于 0x7F 时直接表示长度这是短格式。如果首字节最高位是 1说明是长格式低 7 位表示后面还有几个字节用来存放长度值。资料里 61 81 85 这个序列61 是 Tag81 表示长度值占 1 字节85 表示实际长度是 0x85 等于 133。第三组 Goose3 报文里的 APDU Head 是 61 82 01 6d82 表示长度值占 2 字节01 6d 合并读是 0x016D 等于 365。这里有个特点GOOSE 报文里 APDU Head 的长度是从 80 开始算起的也就是说长度值不包括 APDU Head 自己的三个字节而是从 gocbRef 的 Tag 一直数到整个 GoosAPDU 结束。理解这一点在写解析脚本时就不用跳过头部再去数长度了直接从 80 那个 Tag 开始取够长度就是完整的 GoosAPDU 内容。Length 长格式在解析时最容易出的问题是不考虑跨字节。85 这种单字节还好82 01 6d 这种就必须把两个字节按大端序拼成 0x016D不能分开读。3.3 Tag 类型速查表资料里列出了 GOOSE 报文里最常见的几种数据类型 Tag整理成表Tag类型字段含义长度说明80Visible-StringgocbRefGOOSE 控制块引用可变ASCII 编码81UnsignedtimeAllowedtoLive最长存活时间典型 2 字节82Visible-StringdatSet数据集引用可变ASCII 编码83Visible-StringgoIDGOOSE 标识可变ASCII 编码84UTC 时间t时标8 字节85UnsignedstNum状态序号1-3 字节86UnsignedsqNum序列序号1-3 字节87BOOLEANtest测试标志1 字节00/0188UnsignedconfRev配置版本号典型 4 字节89BOOLEANndsCom数据属性是否需要描述1 字节00/018AUnsignednumDatSetEntries数据集条目数1 字节AB构造类型allData数据集内容可变嵌套 TLVA2构造类型嵌套数据集Goose3 样本可变内部再套 TLV这张表建议收藏第一次手拆报文时对照使用。类型判断记住一个原则80-8A 基本是带固定长度或自身就能判断长度的字段AB 和 A2 是构造类型必须递归进去拆。91 这个 Tag 在第三组样本里出现8 字节长度对应时间戳字段。4. 逐字段手撕 GoosePdugocbRef、stNum 与 allData 的 ASCII 解码和数值还原4.1 字符串字段gocbRef、datSet、goID 的 ASCII 解码三组样本里gocbRef 和 datSet 都是最长也最容易看花眼的字段因为它们的内容是 ASCII 码。以第一组样本为例APDU Head 之后是 80 2580 是 gocbRef 的 Tag25 是十六进制的 37表示后面 37 个字节是数据。从 50 开始往后数 37 个字节50 32 41 31 4A 31 51 36 50 72 6F 74 65 63 74 69 6F 6E 对应 ASCII 码的 P 2 A 1 J 1 Q 6 P r o t e c t i o n后面的斜杠和 LLN0$GSEprotection 同理拼出来整个 gocbRef 是 P2A1J1Q6Protection/LLN0$GSEprotection。datSet 字段在 gocbRef 后面82 25 开头82 是 Tag25 是长度恰好和 gocbRef 等长内容也一样只是语义不同。goID 短一些83 01 3783 是 Tag01 是长度37 是十六进制查 ASCII 表对应字符 7。这里要强调的是ASCII 解码必须按字节逐个翻译不能把 50 32 当成一个十六进制数去算。写脚本时用 bytes.decode(ascii) 直接处理一整段数据比手算可靠得多。这三组样本里 goID 分别对应 7、X7212_GOOSE_TX_ID、LD0_Goose_ST长度差异很大解析时永远以 Length 字段为准不要假设固定长度。4.2 数值字段timeAllowedtoLive、stNum、sqNum 的十六进制还原字符串字段后面跟着一串数值字段每个字段都是 TLV 结构但 Value 需要按大端序还原。第一组样本里 81 02 05 0081 是 timeAllowedtoLive02 是长度05 00 合并成 0x0500 等于 1280单位是毫秒。第二组样本里 81 02 27 100x2710 等于 10000。同样是 timeAllowedtoLive不同装置配的不同解析脚本里直接按 2 字节大端读就行。stNum 和 sqNum 这对序号是状态变化的晴雨表。第一组样本里 85 01 01stNum 是 186 03 02 70 A1sqNum 是 0x0270A1 等于 159905。stNum 只有 1sqNum 已经到 159905说明装置当前处于稳态状态没变过但报文一直在按心跳周期重发。第二组样本里 stNum1sqNum13第三组样本里 stNum1sqNum0。调试时如果发现 stNum 持续增大说明保护装置的状态在频繁翻转这时候去看 allData 里的布尔值和位串基本能找到是哪个开入量在抖。数值字段没有固定长度第 4 组分析时不能假设 sqNum 一定是 3 字节必须按 Length 字段取值。第三组样本里 confRev 是 88 01 200x20 等于 32表示配置版本号是 32。confRev 不一致是 GOOSE 对点最常见的报错原因两侧装置 confRev 对不上订阅方会一直报配置不匹配。4.3 allData从 AB 到布尔值与位串的组合解析allData 是整个 GoosAPDU 里信息量最大的字段Tag 是 AB后面跟的可能是短格式长度也可能是长格式长度。第一组样本里 AB 1010 是十六进制的 16表示后面 16 个字节是数据。这 16 个字节内部又拆成四组 TLV83 01 00boolean FALSE、84 03 02 00 00bit-string、83 01 00boolean FALSE、84 03 02 00 00bit-string对应 numDatSetEntries4四条数据链。布尔值好理解87 01 00 这类结构里 00 是 FALSE01 是 TRUE。位串需要特别留意84 03 02 00 00 里 84 是 BIT STRING 的 Tag03 是存储长度3 字节02 00 00 里面第一个字节 02 表示最后一个字节有 2 个未使用位。也就是说实际有效数据是去掉 2 个填充位之后的位串资料里解析出来的结果是 0000000000000014 个 0对应双点位置信息的中间状态。位串的第一个字节是未使用位数这个规则不熟悉 ASN.1 的人第一次拆必踩后面避坑章还会重点说。第二组样本里 allData 是 8 条数据链长度 0x18 等于 24 字节内部是 83 01 00 和 84 01 00 交替出现四次。84 01 00 的位串只有 1 字节存储第一个值字节 00 表示无未使用位数据就是 00。第三组样本的 allData 结构完全不同AB 82 01 10 表示长度占 2 字节0x0110 等于 272 字节里面塞的是 8 个 A2 嵌套结构每个结构 0x20 等于 32 字节这种嵌套型 allData 在带品质描述的位置报文里很常见。4.4 嵌套型 allDataA2 结构与位串的递归解析第三组样本Goose3的 allData 是嵌套结构和第一、二组的扁平结构不一样。它的一个 A2 嵌套结构内部包含85 01 00stVal 相关、89 00品质 quality、86 01 00另一个数值、84 02 06 40位串双点位置、84 03 03 00 00位串、91 08 加 8 字节时间戳、83 01 00布尔。每个 A2 嵌套结构代表一条完整的数据链numDatSetEntries8 对应 8 个 A2 结构。拆嵌套结构的核心方法是递归遇到 AB 或 A2 这种 constructed 类型的 Tag就进入它的 Value继续按 TLV 规则拆直到拆出来的 Tag 是 primitive 类型。资料里给的解析结果是 allData 为空但 numDatSetEntries 明确是 8报文字节数也对得上所以这不是空数据而是嵌套结构里的字段都是 0 值解析工具没有把嵌套层的内容展开显示。现场用 Wireshark 看这类报文时如果发现 allData 展开后是空的先检查是不是嵌套结构没有被识别。5. 避坑手拆 GOOSE 报文最常见的四个翻车点5.1 把长度字段当成 APDU 实际长度拆到一半对不上现象用帧头里的长度字段 0x0090 直接当 APDU 长度去截数据结果 gocbRef 后面全是乱码字段边界全乱了。原因长度字段表示的是 m8不是 m。0x0090 等于 144但实际上 APDU 是 136 字节多出来的 8 字节是 APPID、长度字段、保留位和 APDU 前缀。直接用 144 截取会把下一个字段的起始位置算错 8 个字节。解决先用长度字段减 8 得到 m再用 APDU Head 的 61 81 85 交叉验证。0x85 等于 133加上 APDU Head 自身 3 字节正好是 136两个数字能对上说明帧头拆对了。从那以后我每次拆帧都会用这个方式做一次交叉验证确认 m 和 GOOSEPDU 长度一致再继续往下拆。5.2 把 t 字段当普通整数读得到一堆莫名其妙的大数现象84 08 后面跟 8 个字节直接按大端整数解析得到一个天文数字完全不知道什么意思。原因84 是 UTC 时间类型的 Tag8 字节的 Value 不是普通整数而是 BER 编码的 UTC 时间。第一组样本里 84 08 后面全是 00解析结果是 1970-01-01 00:00:00.000000这是典型的装置没对时或者解析工具不支持这个类型。如果按整数读0x0000000000000000 碰巧是 0换成实际运行中的时间戳读出来就是毫无意义的数字。解决遇到 84 开头的字段先确认长度是 8再按 UTC 时间的解析规则处理。Wireshark 里可以直接看到格式化后的时间自己写脚本的话需要用专门处理 BER 时间类型的库或者按位拆出年、月、日、时、分、秒再拼。看到 1970-01-01 这个结果不要慌先检查装置的对时状态。5.3 用目的 MAC 全 FF判断广播报文把组播报文漏掉现象抓包过滤器只匹配 FFFF FFFF FFFF结果 GOOSE 报文一条都抓不到或者抓到了但把带 01 开头目的 MAC 的报文全当普通报文处理。原因GOOSE 报文的目的 MAC 通常是 01 0c cd 01 00 01 这类组播地址不是广播地址。资料里三组样本的目的 MAC 分别是 01 00 00 00 00 07、01 0c cd 01 00 04、01 0c cd 01 01 ff全是 01 开头。广播报文只是 GOOSE 的一种特殊形式现场最常见的反而是组播。解决抓包过滤用以太网类型 0x88B8 或者目的 MAC 前三个字节 01 0c cd 来判断。如果只想看某一组 GOOSE再叠加 APPID 条件。判断报文类型时先看目的 MAC 首字节是不是 01是组播再看帧头第 12 字节是 0x8100 还是 0x88B8判断有没有 VLAN 标记。5.4 位串 BIT STRING 首字节当数据读双点位置全反了现象allData 里 84 03 02 00 00 这个位串直接把 02 当成第一个数据字节算出来的位串值和实际完全对不上。原因BER 编码的 BIT STRINGValue 的第一个字节表示最后一个字节的未使用位数。84 03 02 00 00 里03 是存储长度02 00 00 里第一个字节 02 表示最后一个字节的高 2 位是填充实际有效数据只有 14 位。直接按 02 00 00 的二进制读会把填充位也当有效位。解决解析位串时先取 Length 字段得到存储字节数再取第一个值字节得到未使用位数最后从数据里去掉填充位。资料里 84 02 06 40 这种双点位置位串06 40 按二进制展开后要去掉末尾 6 个填充位再解析。写脚本时把 BIT STRING 的解析单独封装成一个函数不要和其他整数类型混在一起。6. 把解析固化成脚本字节游标遍历加 Wireshark 对照验证手拆几组报文之后下一步就是把这套 TLV 解析逻辑固化到脚本里。GOOSE 报文结构规整用 Python 写一个字节游标解析器逻辑很直接def parse_tlv(data, offset): tag data[offset] offset 1 first data[offset] offset 1 if first 0x80: n first 0x7F length int.from_bytes(data[offset:offset n], big) offset n else: length first value data[offset:offset length] return tag, value, offset length def parse_all_data(value): items [] offset 0 while offset len(value): tag, sub, new_offset parse_tlv(value, offset) items.append((tag, sub)) offset new_offset return itemsparse_tlv 函数是整套解析的核心它先读 Tag再处理 Length 的短格式和长格式。长格式判断是关键首字节最高位是 1 时低 7 位表示后续长度字节数像 82 01 6d 这种就要先读 2 字节再合并成大端整数。parse_all_data 则是针对 allData 字段的循环解析每次取一个 TLV更新游标位置直到整个 Value 被拆完。脚本验证有个笨但可靠的方法拿资料里三组样本的十六进制逐字节跑一遍输出的 gocbRef、stNum、sqNum、allData 数量必须和人工拆解结果完全一致。然后打开 Wireshark 加载同一个 pcap 文件展开 IED 61850 GOOSE 那一层对比 gocbRef 字符串和 allData 的条目数。两边对上了脚本才可以拿去做批量离线解析或者实时抓包联动。从那以后我每次改完解析逻辑都强制走一遍这个流程先用样本验证再和 Wireshark 对照确认字段长度和嵌套层数完全一致才收工。希望帮到你。本文还有配套的精品资源点击获取
返回列表