简介:在电力远动通信系统中,规约报文是设备间数据传输的载体,工程师常需通过抓包定位异常。IEC104规约作为运行于TCP之上的应用层协议,其报文由APCI和ASDU构成:APCI负责会话控制与序号管理,ASDU承载遥测、遥信等业务数据。掌握APDU边界识别、I帧序号计算、类型标识与传送原因的含义,是解析报文的基础。借助Wireshark等工具,可快速完成U帧握手确认、总召唤流程跟踪及数据完整性校验,从而在远动通道调试中高效排查握手失败、序号跳变等典型故障。本文面向远动通信与电力自动化运维场景,结合抓包实践讲解IEC104报文结构、常用字段及故障定位思路,适合需要开展点表映射、规约调试与网络分析的工程师参考。
1. IEC104规约报文分析:看得懂十六进制,对不上业务才是真门槛
凌晨两点调度电话把值班工程师叫醒:主站侧看着TCP连接是通的,变电站侧就是不上数据。抓包出来几十条重复的STARTDT激活帧,通道参数、IP地址、端口查过一遍,最后发现是站端装置应用层没起来。这是IEC104规约报文分析里非常典型的一个坎:104规约跑在TCP上,控制信息在APCI里,业务数据在ASDU里,和串口时代的101规约完全不同。看懂IEC104报文,关键不是逐字节翻译十六进制,而是把U帧握手、I帧序号和ASDU里的类型标识、传送原因、信息对象地址对应到远动点表上。下面按这条线拆开讲,覆盖APCI与ASDU结构、常用类型标识、用Wireshark从抓包到解析的流程,以及现场常用的排障技巧,适合刚接手远动通道调试、准备写点表映射脚本的工程师。
2. IEC104规约报文先分层:端口2404、APCI控制域和帧类型
2.1 一条104报文由APCI和ASDU组成
IEC104规约在IEC 60870-5-104标准里定义,应用层直接跑在TCP之上,默认端口2404。一个TCP连接可以双向传输报文,报文的基本单位叫APDU(应用规约数据单元),结构分两段:前6字节是APCI(应用规约控制信息),后面是ASDU(应用服务数据单元)。APCI包含启动字符0x68、APDU长度、4字节控制域;ASDU承载遥测、遥信、遥控等具体业务。
这里有个容易误解的地方:APDU和TCP报文段不是一回事。APDU的边界由第2个字节的长度字段确定,比如0x68 0x10表示后面还有16字节,整条APDU共18字节。不管TCP层怎么分片、粘包,解析器都靠这个长度字段把APDU一条条切出来,而不是靠回车换行或固定超时。一条APDU最长253字节,其中ASDU最多249字节,比串口101规约的单帧容量大很多,这也是104能批量上送几十个点的主要原因。
平时说的“104报文分析”,绝大多数场景是在抓包里把APDU边界划对,再看控制域和ASDU。APDU长度对不上时,Wireshark会显示“malformed packet”,先查抓包是不是超过MTU后被拆开,或者TCP流重组没打开。
2.2 四字节控制域:I帧、S帧、U帧的区别与报文值
控制域的4字节是整个会话状态机的核心。前2比特标识帧类型:I帧是信息帧,低2位为00;S帧是监视帧,低2位为01;U帧是控制帧,低2位为11。I帧带双向序号,S帧只带接收序号,U帧不带序号。
| 帧类型 | 判定位 | 携带序号 | 典型作用 | 常见报文示例 |
|---|---|---|---|---|
| I帧 | bit0=0,bit1=0 | N(S)和N(R) | 携带ASDU业务数据 | 68 10 08 00 08 00 ... |
| S帧 | bit0=1,bit1=0 | 仅N(R) | 纯确认,不携带ASDU | 68 04 01 00 02 00 |
| U帧 | bit0=1,bit1=1 | 无 | 启动/停止数据传输、测试链路 | 68 04 07 00 00 00 |
I帧的发送序号N(S)和接收序号N(R)各占15位,从0递增,到32767后回绕。序号从字节里取出来时要注意位偏移:N(S)是第1字节bit1到bit7拼接第2字节共15位,N(R)是第3字节bit1到bit7拼接第4字节共15位。换算公式为:
N(S) = ((b0 >> 1) & 0x7F) | (b1 << 7) N(R) = ((b2 >> 1) & 0x7F) | (b3 << 7)其中b0到b3是控制域4字节。这个位运算很多解析脚本里都会出现,写错会导致看到的序号跳变,进而误判丢包。
U帧没有序号,它的4字节控制域只有第1字节有意义,第2到第4字节为0。现场最常遇到的U帧报文是这几个:STARTDT act(主站请求数据传输)为68 04 07 00 00 00,STARTDT con(从站确认)为68 04 0B 00 00 00;STOPDT act为68 04 13 00 00 00,STOPDT con为68 04 23 00 00 00;TESTFR act为68 04 43 00 00 00,TESTFR con为68 04 83 00 00 00。记不住位定义没关系,记这几个十六进制值就能应付绝大多数抓包场景。
TCP连接建立后,主站必须先发STARTDT act,从站回STARTDT con,此时才允许传I帧。如果抓包里只有TCP三次握手和不断重复的STARTDT act,基本可以断定从站应用层没有正常启动或网关配置错误,而不是链路问题。
2.3 与101规约、61850通讯规约的边界
很多刚接触的人会把104规约和101规约混在一起。101规约走串口或低速网络,帧格式是FT1.2,有起始字符、控制域、地址域、用户数据、校验和,一帧最多255字节;104规约是101规约在TCP/IP网络上的改造,应用层的ASDU沿用了101的定义,但链路层换成APCI加TCP。也就是说,ASDU字段两边基本通用,APCI和帧格式完全不通用。
站内通信则更多遇到61850通讯规约。61850用于变电站站控层和间隔层之间的MMS、GOOSE、SV报文,解决的是站内设备互操作;104规约解决的是变电站与调度主站之间的远动信息传输,两者在链路层面没有交集,但调试时经常同时出现:同一个测控装置既上送104到主站,又在站内跑61850,点表对不上时要从两端分别找原因。
另外还要注意,698规约和DL/T 645-1997是用电信息采集方向的内容,面向电能表计费数据,报文格式跟远动的104完全不同,别拿104的解析思路往那上面套。
3. ASDU字段拆解:类型标识、传送原因和公共地址决定怎么解析
3.1 ASDU头部字段逐个看
ASDU从类型标识开始,一个完整的ASDU由固定头和信息对象组成。固定头依次是:类型标识1字节、可变结构限定词(VSQ)1字节、传送原因1字节、公共地址2字节。信息对象部分根据类型标识的不同而变化,可能包含多个信息对象,每个对象有信息对象地址(IOA)和对应的数据体。
类型标识告诉解析器后面跟的是什么数据:是单点遥信、双点遥信、短浮点遥测、还是遥控命令。这个字节不认对,后面全是错的。可变结构限定词的bit7是连续标志,低7位是信息对象个数;如果连续标志为1,表示这组信息对象的地址是连续的,报文里只写第一个地址,后面依次加1;如果为0,每个对象都带完整地址,报文会明显变长。
传送原因占1字节,bit6是测试标志(S/E位),低6位是原因值。现场看到0x43、0x47这类值,先转成二进制,bit6为1表示这是测试链路时发的报文,不是真实数据。公共地址占2字节,小端存储,用于区分不同厂站,一个TCP连接上一般固定为一个值,跨厂站共用通道时尤其要注意。
3.2 现场最常用的类型标识与传送原因
IEC104规约的类型标识沿用了101规约的定义,真正天天用的就几个。以下是调试远动通道时最常遇到的类型:
| 类型标识 | 十六进制 | 名称 | 信息对象内容 |
|---|---|---|---|
| 1 | 0x01 | 单点遥信 M_SP_NA-1 | 1字节,bit0为分合状态 |
| 3 | 0x03 | 双点遥信 M_DP_NA-1 | 1字节,bit0/bit1组合 |
| 9 | 0x09 | 带时标的单点遥信 M_SP_TB-1 | 状态1字节加7字节时标 |
| 13 | 0x0D | 短浮点遥测 M_ME_NC-1 | 4字节IEEE754浮点加1字节品质 |
| 45 | 0x2D | 单点遥控 C_SC_NA-1 | 1字节命令,含选择执行标志 |
| 46 | 0x2E | 双点遥控 C_DC_NA-1 | 1字节命令 |
| 100 | 0x64 | 总召唤 C_IC_NA-1 | 信息对象地址一般为0 |
| 103 | 0x67 | 时钟同步 C_CS_NA-1 | 7字节CP56Time2a时标 |
传送原因最常用的是这几个:1表示周期上送,2表示背景扫描,3表示突发上送,5表示请求,6表示激活,7表示激活确认,8表示停止激活,10表示激活终止,20表示响应站召唤。排查“丢数据”问题时,先看原因:周期上送的数据丢了可能是扫描周期配置问题,突发上送的丢了则要查事件缓存和确认机制。
3.3 手工拆一条短浮点遥测报文
拿一条实际报文来看完整拆解过程。假设抓包得到如下帧:
68 11 08 00 08 00 0D 01 03 01 00 00 00 00 00 00 C8 42 00第1字节0x68是启动字符,第2字节0x11表示APDU长度17,后面跟着4字节控制域和13字节ASDU。控制域08 00 08 00是I帧,N(S)=4,N(R)=4,说明双方序号正常连续。ASDU部分逐字节看:
- 0x0D,类型标识13,短浮点遥测;
- 0x01,VSQ,bit7为0,1个信息对象;
- 0x03,传送原因,突发上送;
- 0x01 0x00,公共地址小端为1;
- 0x00 0x00 0x00,信息对象地址为0;
- 0x00 0x00 0xC8 0x42,IEEE754短浮点,低字节在前,实际值是0x42C80000;
- 0x00,品质描述字节,正常值。
0x42C80000按IEEE754转换,得到100.0。这条报文表达的是:1号厂站第0点遥测值100.0,突变上送,数据有效。如果点表里第0点是有功功率,单位MW,那这条报文就是“有功100MW变位上送”。
3.4 信息对象地址与CP56Time2a时标的坑
信息对象地址在104规约里是3字节小端,范围最大到0xFFFFFF,厂站点表通常按这个值映射。调试时最常见的坑是:主站点表和厂站点表对不上,主站看IOA=256,厂站侧却从1开始编号。抓包只能看到IOA原始值,对点必须拿厂站点表来核对,不能按报文里的数字猜含义。
带时标的信息对象,时标固定7字节,格式是CP56Time2a:前2字节是毫秒(低字节在前),后面依次是分、时、日、月、年各1字节。比如某条带时标遥信的时标字段为00 00 05 0D 19 02 0B,表示毫秒0、分钟5、小时13、日25、月2、年11,即2011年2月25日13时5分0秒。注意年份是偏移表示,0对应2000年,所以0x0B要按2011年来读。写解析脚本时,年、月、日这几个字段都要加偏移和校验,否则排序会乱。
4. 用Wireshark分析104报文:从U帧握手到总召唤数据上送
4.1 抓包过滤与解码视图
Wireshark从2.x版本起内置了IEC 60870-5-104解码器,不需要额外装插件。抓包时建议在采集点用主机镜像端口或TAP,避免在应用层设备上抓导致报文已经被改写。打开抓包文件后,先过滤:
tshark -r capture.pcapng -Y "tcp.port == 2404" -c 20这条命令用tshark从抓包文件里筛出前20条与2404端口相关的报文。参数-r指定输入文件,-Y是显示过滤器,-c限制输出条数。在Wireshark图形界面里,显示过滤器输入tcp.port == 2404,可以排除其他端口干扰;如果想只看已被识别为104的帧,可以输入iec60870_5_104,但要注意只有解码器成功识别APDU时才会带上这个协议名,TCP握手包不会出现。
4.2 一次完整总召唤的报文序列
新站接入调试时,第一个要看的就是总召唤流程。正常顺序如下:
- TCP三次握手建立连接;
- 主站发U帧STARTDT act,从站回STARTDT con;
- 主站发I帧总召唤,传送原因6(激活);
- 从站回总召唤确认,传送原因7(激活确认);
- 从站批量上送全数据,包括遥信、遥测;
- 全部上送完,从站发总召唤结束,传送原因10(激活终止)。
对应抓包里的报文轮廓是:
| 方向 | 报文 | 含义 |
|---|---|---|
| 主站 → 从站 | 68 04 07 00 00 00 | STARTDT act |
| 从站 → 主站 | 68 04 0B 00 00 00 | STARTDT con |
| 主站 → 从站 | 68 0C 00 00 00 00 64 01 06 01 00 00 00 00 | 总召唤,原因6 |
| 从站 → 主站 | 68 0C 00 00 02 00 64 01 07 01 00 00 00 00 | 总召唤确认,原因7 |
| 从站 → 主站 | 68 11 02 00 02 00 0D 01 03 01 00 00 00 00 00 00 C8 42 00 | 遥测上送,值100.0 |
| 从站 → 主站 | 68 0C 04 00 02 00 64 01 0A 01 00 00 00 00 | 总召唤结束,原因10 |
注意控制域里序号的连续性:主站的总召唤N(S)=0,从站的确认N(S)=0且N(R)=1表示已收到主站第0帧;后续数据帧N(S)逐步加1。如果中间某帧序号跳了,说明有I帧丢失,重传机制会触发,但总召唤流程会变慢。
4.3 在Wireshark里核对一条遥测I帧
选中一条I帧,在Packet Details面板展开“IEC 60870-5-104 APDU”,能看到APCI和ASDU两个子树。APCI里显示帧类型、发送序号、接收序号;ASDU里显示类型标识、可变结构限定词、传送原因、公共地址、信息对象地址和数值。以4.2中那条68 11开头的报文为例,树形展开后应显示:Type Identification=13,VSQ=1个不连续对象,Cause=3,Common Address=1,IOA=0,State=100.0。
这里建议把列显示调一下:右键控制域里的“Send Sequence Number”,选择“Apply as Column”,把所有I帧序号按列排列,方便快速扫一遍序号是否连续。总召唤后的数据量大时,用这个方式排查漏帧比逐帧点开高效得多。
4.4 TCP分片、乱序对104解码的影响
104报文最长253字节,远小于TCP的MSS(通常1460字节),正常不会因为长度产生分片。但抓包时如果开了巨型帧,或者中间有TCP透明代理改了报文,反而会出现一条APDU被拆到多个TCP段的情况。Wireshark默认会重组TCP流,但如果抓包不完整,重组失败就可能把后续报文全部标成malformed。
遇到乱序或重传时,不要直接在104解码视图里下结论。先切换到Follow TCP Stream看原始数据,再对照长度字段手工切分APDU。重传的TCP段会带“TCP Retransmission”提示,对应的104报文是重复的I帧,这时要检查是网络丢包还是对端确认超时,而不是数据本身出错。
5. 104故障定位实践:握不上、序号跳变和一段解析脚本
5.1 握手失败先分清TCP和应用层
主站收不到数据时,第一抓包看TCP握手是否完成。如果SYN都发不出去,问题在路由、防火墙或端口策略,典型原因是2404端口未放通。TCP握手正常但看不到STARTDT act,主站侧应用可能没启动。看到STARTDT act反复发、始终没有STARTDT con,问题在从站侧应用层:装置未运行、规约版本不匹配、或者从站收到act后认为配置未就绪不回应。此时再查从站的运行日志而不是继续抓主站。
5.2 序号连续性检查
序号跳变是丢包最直接的信号。用Wireshark把I帧N(S)排成列后,逐行检查差值是否等于1。如果出现跳号,接着看跳号前是否有TCP重传或乱序标记;如果没有,说明报文在中间设备上被丢弃,需要检查交换机端口丢包统计和防火墙会话表老化时间。还有一种情况是序号回绕,15位计数器到32767后归零,这是正常现象,不要当跳变处理。
5.3 Python小脚本:自动核对I帧序号和字段
现场报文量大时,手工核对不现实。把抓包导出的十六进制逐条喂给下面这个脚本,它能自动输出I帧序号和关键字段:
import sys def parse_apdu(line: str): data = bytes.fromhex(line.strip()) if len(data) < 6 or data[0] != 0x68: return None apdu_len = data[1] body = data[2:2 + apdu_len] if len(body) < 6: return None ctrl = body[:4] # 低2位为00表示I帧 if (ctrl[0] & 0x03) != 0: return {"frame": "U或S帧"} ns = ((ctrl[0] >> 1) & 0x7F) | (ctrl[1] << 7) nr = ((ctrl[2] >> 1) & 0x7F) | (ctrl[3] << 7) return { "ns": ns, "nr": nr, "type_id": body[4], "is_continuous": bool(body[5] >> 7), "count": body[5] & 0x7F, "cause": body[6] & 0x3F, "common_addr": int.from_bytes(body[7:9], "little"), } for line in sys.stdin: line = line.strip() if not line: continue p = parse_apdu(line) if p and "ns" in p: print(f"ns={p['ns']:5d} nr={p['nr']:5d} " f"type=0x{p['type_id']:02x} cause={p['cause']:3d} " f"addr={p['common_addr']} count={p['count']}")脚本先校验启动字符和长度字段,然后只处理I帧,U帧和S帧跳过。ns和nr按APCI的15位序号格式从字节里拼出来,type_id是类型标识,cause是传送原因。count是可变结构限定词里的对象个数。把抓包工具导出的十六进制文本重定向到脚本输入,输出里如果ns不再连续递增,就是丢包位置;如果type_id里混进预想之外的类型,再去抓包里定位具体那条帧。把跳变帧前后各20条报文再导出来比对发送间隔,就能定位是网络丢包还是对端重传。
本文还有配套的精品资源,点击获取