ARTICLE DETAIL

资讯详情

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

GOOSE报文解析实战:从Wireshark抓包到Scapy脚本排查变电站通信故障

GOOSE报文解析实战:从Wireshark抓包到Scapy脚本排查变电站通信故障 简介GOOSE报文是IEC 61850智能变电站过程层通信的核心其解析能力对保护、测控装置的调试与运维至关重要。内容基于ISO/IEC 8802-3帧格式从普通报文的目的MAC、源MAC、TPID、TCI、以太网类型、APPID及APDU长度等字段入手明确TPID为0x8100、GOOSE专用以太网类型为0x88B8并区分广播报文目的MAC为FF-FF-FF-FF-FF-FF与普通报文的结构差异同时覆盖ASN.1 BER编码的TLV结构包括Tag的Bit位含义Bit 7/6表示类型、Bit 5表示基本/构造类型、Bit 4-0表示Tag值、Length与Value的解析方法以及BOOL、BIT-String、UTC时间、INT、Unsigned、Visible-String等各类数据类型的标记与解码规则并给出实际抓包报文的逐字节分析示例。压缩包内仅含1个PDF文件大小约121KB内容紧凑适合电力二次系统技术人员、通信协议开发者及电气专业学生作为快速查阅和排错参考。目前已有1037人浏览学习搭配实际报文截图对照可帮助读者在短时间内构建起从字节流到语义信息的完整解析思路。1. GOOSE报文解析难在哪先搞懂这张皮变电站里出了故障最怕的不是保护不动而是保护动了、监控却看不到动作信号。这个信号多半就是GOOSE报文。它让保护、测控装置之间靠一条二层组播以太网帧把跳闸、闭锁、启动这些状态传出去不经过TCP/IP不用三次握手几十毫秒内到位。很多工程师手里有Wireshark、有抓包文件却对GOOSE报文解析一脸茫然——拆不开数据集、看不清StNum、分不清心跳和变位。这篇笔记按我自己的排查习惯从报文结构讲到抓包、从脚本解析讲到五个常见坑让你拿到一个.pcap就能把动作情况看明白。2. 拆开一个GOOSE报文四层结构里藏着动作的全部秘密GOOSE的全称是Generic Object Oriented Substation Event这条报文看起来就是一个普通的以太网帧但它的每一个字节都不是白给的。解析之前我必须先把它从宏到微拆成四层以太网头、优先级标签、应用层PDU、数据集内容。凡是后面脚本解析时漏掉的字段十有八九都在这里。2.1 以太网头和EtherType0x88B8不是随便填的GOOSE报文没有IP头没有TCP/UDP端口。它直接用以太网二层转发靠的就是帧里一个特殊字段EtherType。正常的IPv4报文EtherType是0x0800ARP是0x0806而GOOSE固定在0x88B8。抓包时如果Wireshark没有识别出GOOSE协议先看这个值对不对。以太网头的目的MAC是一个组播地址范围通常是01-0C-CD-01-00-00到01-0C-CD-01-01-FF其中前三个字节01-0C-CD是IEC 61850专用组织唯一标识符OUI。源MAC就是发送装置的物理地址。这里有个实际经验如果目的MAC落在01-0C-CD-01-00-00到01-0C-CD-01-00-3F之间表示这个报文带应用标识APPID落在其他段可能不带APPID。Wireshark的GOOSE解析器会按这个规则判断是否显示APPID字段所以不要一看到没有APPID就认为报文坏了。接着是802.1Q VLAN标签共4字节。前两字节是TPID0x8100后两字节的低13位是VLAN ID高3位是优先级Priority。很多智能站把GOOSE报文划在专用VLAN里优先级设为4或5以保证它比普通MMS报文优先转发。解析时如果只盯着PDU看忽略了VLAN段抓包后在交换机上查不到组播就找不出原因。2.2 应用层PDUAPPID、StNum、SqNum和数据集剥开以太网头之后就是IEC 61850的ASN.1编码的APDU应用层协议数据单元。它长这样按字节顺序看APPID2字节用来区分不同的GOOSE控制块报文的身份标识。Length2字节表示从APPID开始到数据集结束的总长度。GOCBRefGOOSE控制块引用字符串告诉解析者这条报文属于哪个装置、哪个控制块。DatSet数据集引用指定携带的数据来自哪个数据集。ConfRev配置版本号装置重启或配置变更后这个值会变化。StNum状态号State Number这是最重要的字段之一。每一次变位比如开关从分到合StNum就加1。解析变位就是盯它。SqNum序号每发一条报文就加1到了最大值又循环。心跳报文SqNum连续增加而StNum不变。Test测试标志位值为1时表示装置处于检修压板投入状态。Nd需要确认标志一般不太用。AllData数据集内容里面是多个带类型的值比如BOOL、INT32、FLOAT、VISIBLE_STRING。很多人解析GOOSE时只抓AllData却忘了StNum和SqNum是判断报文新旧的关键。我见过有人写脚本只对比数据值结果装置没变位时数据内容完全相同脚本就认为“没变化”实际上链路丢了十几个报文。原因就是没看SqNum连续性和StNum变化。2.3 用Wireshark打开第一个GOOSE报文的完整步骤如果你手里正好有一个包含GOOSE报文的pcap文件按这套步骤能最快把它拆开打开Wireshark直接双击pcap文件先别急着用显示过滤。在显示过滤器框里输入goose回车。如果看到能过滤出报文说明Wireshark自带的解析器已经识别了。点击任意一条GOOSE报文在中间的面板里依次展开GOOSE→PDU。找到state number和sequence number两个字段记下来。再翻开相邻两条报文比较一下你会看到SqNum加1了而StNum通常不变。继续展开allData里面就是数据集里的每个值双击某个值可以在右侧看到它的偏移量和原始字节。实际工程里我一般先在Wireshark里把报文看一遍确认里面的stNum、sqNum和allData都正常再上脚本批量解析。如果Wireshark里显示的是“malformed packet”或者“unsupported type”那就根本没走到解析这一步问题多半在链路层或编码不标准上。3. 用tshark和Scapy把GOOSE报文拆成字段命令行与脚本实战Wireshark能看但没法批量处理几十个pcap。真正要统计一晚上的报文有没有丢、变位了几次要靠命令行和脚本。tshark是Wireshark的命令行版Scapy是Python里的协议解析库这两件套是我日常拆GOOSE报文的标配。3.1 tshark抓包与关键字段过滤命令先演示抓包。直接在服务器或笔记本上用tshark抓取实时报文tshark -i eth0 -f ether proto 0x88B8 -w goose.pcap这条命令在eth0网卡上抓所有EtherType为0x88B8的帧写成pcap文件。-f是抓包过滤器在数据到进入解析器之前就生效适合长时间抓包不占存储。解释一下ether proto 0x88B8不能写成goose或udp port因为GOOSE不是TCP/UDP写错了就什么都抓不到。抓完文件后用tshark直接解析tshark -r goose.pcap -Y goose -T fields \ -e frame.time_epoch \ -e goose.stNum \ -e goose.sqNum \ -e goose.test \ -e goose.dataset-Y是显示过滤在已抓到文件里再做一次过滤。-T fields表示输出字段-e指定要提取的字段名。frame.time_epoch是Unix时间戳goose.stNum对应状态号goose.sqNum对应序号goose.test是测试标志goose.dataset是数据集引用。运行后你会得到类似这样的一行行文本1700000000.123456 3 125 0 LLN0$GOOSE$GOOSE1 1700000000.224565 3 126 0 LLN0$GOOSE$GOOSE1从这组数字里就能看出SqNum从125变到126、StNum一直是3说明装置没有变位只是正常心跳。如果你同时抓到了另一个pcap想对比两个文件里同一个控制块的报文可以把上面的命令重跑两遍再导出到文件里比较。还有一个参数值得加-E separator, 让输出用逗号分隔方便直接贴进Excel。我一般会加一个-e goose.appid用来区分多个控制块特别是不同间隔的装置可能共用一个VLAN靠APPID才能分清。3.2 用Scapy编写一个最小GOOSE解析器tshark适合快速看字段但要做二次处理或实时监控比如“当StNum变化时发告警”就得用Python。这里给出一个用Scapy解析离线pcap的最小脚本# parse_goose.py from scapy.all import rdpcap, Ether def hex2int(hex_str): # Scapy的字节可能是b\x01\x02这里直接用int.from_bytes return int.from_bytes(hex_str, big) for pkt in rdpcap(goose.pcap): if pkt.haslayer(Ether) and pkt[Ether].type 0x88B8: # 原始报文从Ethernet头开始直接获取所有字节 raw bytes(pkt) # 跳过Ethernet头(14字节) 可能的VLAN标签(4字节) # 用Ether中的字段来判断更可靠 if hasattr(pkt[Ether], vlan): start 18 # 14(eth) 4(vlan) else: start 14 # 从start开始取APPID2字节 app_id int.from_bytes(raw[start:start2], big) # 跳过APPID(2)和Length(2)进入APDU的TLV序列 # 为了演示我们直接在末尾找数据集的原始编码 # 实际工程中建议用专业的IEC61850库这里展示最少的剥离 print(fAPPID: 0x{app_id:04x})这段脚本不能直接把所有字段解出来因为ASN.1的BER编码是嵌套TLV手写解析很容易在类型标签上踩坑。我一般会结合scapy.contrib.iec61850或者直接用goose这个第三方库但初学者不要一上来就依赖黑盒库先把pcap里的原始字节和Wireshark里的字段对照一遍理解APPID、StNum在哪个偏移位后面排错才快。参数说明rdpcap(goose.pcap)把整个文件读进内存文件太大时改用PcapReader流式读取。pkt[Ether].type就是以太网类型字段判断是不是0x88B8。vlan属性的判断要小心如果交换机没打标签这个字段不存在如果报文带着优先级标签但没有VLAN IDScapy也可能解析为vlan 0。真正解析APDU时建议用goose的parse_frame函数它已经处理好了BER解码from goose import parse_frame for pkt in rdpcap(goose.pcap): if pkt[Ether].type 0x88B8: parsed parse_frame(bytes(pkt)) print(fStNum{parsed[stnum]} SqNum{parsed[sqnum]} fTest{parsed[test]} Dataset{parsed[datSet]})这句代码看起来简单但它背后做了整个APDU的TLV逐字节解包。实际应用时我建议先抓一条报文用Wireshark和这个脚本的输出做对照确认版本、字段名一致。不同版本的库对布尔值和枚举的表示方式可能不一样不要拿旧脚本直接跑新报文。3.3 三个必须调好的参数StNum、SqNum、Test解析GOOSE时我自己的经验是先把这三个参数从工具里摘出来变成一组可靠的判断逻辑StNum状态号变位信号。StNum加1代表一次状态变化解析器要把“变化前的值”和“变化后的值”都记录这是故障追溯的关键。SqNum序号连续性检测。正常心跳时SqNum递增允许超过VLAN上一个循环。如果SqNum从100突然跳到200中间的100条都没收到要么是抓包软件丢帧要么是交换机端口丢弃组播要么是装置发送异常。Test测试标志现场最容易忽略。检修时装置可能投了检修压板Test1如果解析脚本不判断这个位会把检修报文当成真实动作造成误判断。所以输出结果里必须带Test标志并且建议用不同颜色高亮。距离实际使用还差一步把过滤条件加到脚本里比如if parsed[test] 1: continue先跳过检修报文。一般变电站的GOOSE断链和误动排查有这三个字段加上APPID、时间戳就已经够了不需要把所有数据集值都解出来。等你需要看具体遥信变位时再扩展去解AllData。4. 解析结果不靠谱先去检查这五个常见坑这块内容是血泪经验。我帮人排查的十几个“GOOSE解析不出来”或“解析结果对不上”的问题最后全是这些坑不是协议本身多复杂。4.1 现象Wireshark识别不出GOOSE报文原因往往不是报文坏了而是网卡驱动或抓包软件先把VLAN标签剥掉了。某些Windows网卡默认会去除802.1Q头导致分给抓包进程的帧里没有VLAN段EtherType被移位Wireshark自然认不出0x88B8。解决办法是在抓包前设置网卡的“VLAN tag stripping”关闭或者用专业的硬件镜像口配合支持VLAN透传的工具。还有一个原因是抓包时用了“混杂模式”但没开“显示所有VLAN”在Wireshark里点击“视图”菜单确认“Reload as capture file”以后再有VLAN标记的帧在帧头会显示“802.1Q”一行。4.2 现象收到报文但StNum不变以为装置死机这不是装置死机而是装置没有变位。GOOSE的机制是平时以1~2秒的周期发送心跳SqNum递增StNum保持不变一旦检测到状态变位StNum立刻加1同时在极短时间内通常5毫秒连续重发多条报文。解析脚本如果把“StNum没变”当成“没收到动作”就会漏掉真正的告警。解决是同时监视SqNum的间隔时间如果SqNum在几百毫秒内连续跳了几次说明发生了快速重发这就是所谓“变位爆发”。配合StNum变化才能判断是哪一次变位。4.3 现象VLAN配置不一致导致丢帧智能站里GOOSE往往配置了专用VLAN比如VLAN 100。解析端如果用普通网卡接在交换机上而这个端口没有放行该VLAN抓包就什么都抓不到。反过来如果报文本身没有VLAN标签而交换机的端口配置成access口强制加VLAN会导致收到的帧被剥掉或不落地。排查步骤是先看交换机的端口配置确认该端口是Trunk还是Access再在镜像口上抓包看是否能得到VLAN 100的帧。如果在tshark里看到vlan.id 100的显示过滤能出来才说明链路完整。4.4 现象数据集类型映射错位GOOSE的AllData是ASN.1 BER编码的里面的布尔值可能表示为单字节0x83 0x01 0x01字符串有特定标签。手工解析时如果只取字节不看类型标签很容易把整数当成布尔或者把可见字符串当成位串。现象就是脚本输出的“开关状态”在0和1之间乱跳而Wireshark里的原始值明明是稳定的。解决是用Wireshark的goose解析器展开allData对照一次或者用goose库来解码。不要自己从头写BER解码除非你愿意花一整天啃标准。4.5 现象时间戳乱跳GOOSE本身不带精准的时间戳抓包软件加的帧时间可能来自电脑本地时钟也可能来自Windows系统的低精度时钟。多台装置同时解析时如果时间不同步对比前后两个报文的到达间隔就会失真。特别是用Wireshark在普通PC上抓包系统时钟精度可能只有10毫秒而GOOSE的快速重发间隔是5毫秒时间戳会完全错乱。解决是在抓包机上启用PTP或NTP同步至少保证对时精度达到毫秒级否则只适合看序列不适合算时差。5. 从解析到排障用GOOSE报文定位通信故障的三个路径解析不是终点最终要回答“链路通不通、装置有没有动、动作对不对”。我的习惯是把解析结果分三条路走分别对应三种故障。5.1 用SqNum跳变判断链路质量把tshark导出的SqNum序列导入表格统计相邻报文的序号差。正常情况每两帧之间SqNum差1偶发缺失1-2条可接受但如果差值超过5说明存在链路丢帧或者端口拥塞。具体操作建议抓10分钟的心跳报文用下面的tshark命令提取时间戳和SqNumtshark -r goose.pcap -Y goose goose.stNum 0 -T fields -e frame.time_epoch -e goose.sqNum sqnum.log然后在Python里计算相邻差异找出所有diff 1的位置并打印prev None for line in open(sqnum.log): fields line.strip().split(\t) if len(fields) 2: continue sq int(fields[1]) if prev is not None: diff sq - prev if diff ! 1 and sq ! 0: # 不考虑序号循环到0 print(f跳变: {prev} - {sq} (diff{diff})) prev sq注意SqNum是16位无符号整数最大65535循环时从65535回到0差值会变成负数或很大。在判断前先做if abs(diff) 10或if sq prev处理否则你会被循环回绕坑出幻觉。这个脚本虽简单但在现场帮我抓出过一次交换机单臂路由的端口丢帧问题现象就是每5000条报文丢几条正好是交换机CPU处理其他流量的时候。5.2 用StNum和T位的组合判断装置行为装置正常不动作时StNum不变、Test为0。如果StNum频繁变化而Test始终为1说明检修压板一直投入这些动作都不能并入真实故障记录。如果Test从0变成1同时StNum变化说明装置在运行过程中投了检修压板这是一个非常典型的违规操作需要追溯到操作时间点。解析时要把StNum、Test和变化前后时刻一并记录形成这样的表格时间戳StNumSqNumTest事件10:00:01.10051000心跳10:00:02.20051010心跳10:00:03.050600变位SqNum重新从0重发10:00:03.055610快速重发变位后SqNum会归零重新计数这也是一个特征很多新手解析时看到SqNum变小就以为是报文乱序其实恰恰是变位的标志。判断逻辑是如果StNum变了SqNum从0或小值开始属于正常快速重发行为不是乱序。5.3 验证解析代码正确性的最小测试集不管用Scapy还是goose库解析代码必须在一组手工构造的测试样本上跑通才敢上现场。我会构造以下三类数据第一条是标准心跳报文StNum1SqNum100Test0数据集含两个布尔值第二条是检修变位报文StNum2SqNum0Test1第三条是带VLAN标签的报文检查解析器是否正确处理。用Wireshark自带“编辑”里生成pcap或者用goose库直接构造帧。构造测试帧的核心参数目的MAC强制设成01-0c-cd-01-00-01EtherType设为0x88B8APPID设为0x0001VLAN ID设为100StNum和SqNum按你的预期填写。运行解析脚本后确认输出和预期一致特别要检查布尔值的类型是否被正确识别。如果测试帧都解不对不要急着去现场续抓包先把代码修到位。6. 进阶技巧把GOOSE报文化成自己的调试工具除了看单个报文我更愿意做一整套实时监控脚本让所有GOOSE动作自动落成一个CSV并生成摘要。做法很简单用Scapy的Sniff或tshark的-l输出把实时解析和打印结合起来。下面这段是低频监控思路适合现场站内笔记本使用from goose import parse_frame from scapy.all import sniff, Ether import csv, time csv_writer None def handle_packet(pkt): global csv_writer if pkt[Ether].type ! 0x88B8: return parsed parse_frame(bytes(pkt)) row [time.time(), parsed[appid], parsed[stnum], parsed[sqnum], parsed[test], parsed[allData]] print(f{time.strftime(%H:%M:%S)} APPID{parsed[appid]:04x} fSt{parsed[stnum]} Sq{parsed[sqnum]} fTest{parsed[test]}) if csv_writer: csv_writer.writerow(row) with open(goose_monitor.csv, w, newline) as f: csv_writer csv.writer(f) csv_writer.writerow([time, appid, stnum, sqnum, test, alldata]) sniff(ifaceeth0, prnhandle_packet, storeFalse, timeout60)这段代码看起来平平无奇但我在现场用的是同一套思路只是加上了“stnum变化时写红色标记”和“连续丢失sqnum超过3就告警”。好处是不需要一直开着Wireshark滚动看得眼花缭乱脚本只把有意义的动作打出来。运行之前一定要用前文的小节里的测试帧先跑一遍确认库的字段名没有变化。还有一个少为人知的技巧Wireshark的显示过滤器可以直接用GOOSE字段做组合条件比如goose.stNum 3 goose.test 0在分析几十兆pcap时比肉眼翻快得多。我习惯把它存成一个按钮“变位帧”点击直接过滤出所有非检修的变位报文。这样哪怕来一个新同事也能几秒钟看懂抓包文件里发生了什么。这条经验让我少加了很多班也顺便处理过不少带着一脸茫然来求助的同事。希望帮到你下次拿到GOOSE报文先别急着翻数据先看StNum和SqNum再决定要不要继续挖。本文还有配套的精品资源点击获取
返回列表