资讯详情

资讯详情

IEC61850 MMS协议深度解析:从原理到Wireshark抓包实战

干变电站调试这几年我有一半的时间都耗在规约沟通上。早期做Modbus点表双方对着Excel来回核对改一个点要重新下装后来开始接触IEC61850第一次抓MMS报文时对着16进制愣了半天——一个读取遥测的动作封包居然能嵌套五层。等真正把MMS协议跑通、把Wireshark里的字节和标准文档逐条对上我才意识到规约这层窗户纸捅破之后后面的路会顺很多。这篇博文要做的就是把这层窗户纸也帮你捅破。我会用开源库libIEC61850搭一套能真实运行的MMS通信环境用Wireshark从物理字节层面把一次读数据、写数据、主动上报的完整过程拆开看还会把自己调试过程中踩过的一些坑和排查思路整理出来。适合正在做电力自动化、继保装置调试、规约开发的朋友也适合刚开始接触IEC61850、对MMS一脸懵的同学——只要会敲基本命令就能跟着把流程跑起来。1. 把IEC61850里的MMS位置搞清楚三层映射与通信栈1.1 为什么61850偏偏选了MMSIEC61850标准族里通信服务映射部分Part 8-1把MMS定义为应用层协议。很多初学者第一反应是既然都定义了一套完整的信息模型和抽象服务ACSI为什么不直接从零写一套协议非要套用MMS原因在于对于变电站自动化这种对可靠性、实时性、互操作性要求都极高的场景完全不必要重造轮子。MMSManufacturing Message Specification制造报文规范是ISO 9506定义的工业自动化应用层协议早在IEC61850出现之前就已经在工业现场跑了多年对象模型成熟、大量厂商支持、工具链完善。IEC61850真正聪明的地方在于它自己定义了一套抽象通信服务接口ACSI和对象模型然后再通过SCSM特定通信服务映射把这套抽象模型映射到MMS上。换句话说IEC61850负责“讲什么”MMS负责“怎么讲”。这个分层最大的价值在工程实践上无论站控层后台是南瑞的还是国电南自的只要双方都按IEC61850的SCL文件描述模型、用MMS完成映射就能互认。你不用关心对方后台内部怎么存储数据只需要关心MMS链路上报文的规范和语义。这也是为什么验收抓包时测控装置和监控后台联调抓到的往往就是MMS报文。1.2 通信栈的完整路径MMS怎么一层层踩到TCP上再看底层传输MMS并不是直接裸跑在TCP上的。完整的IEC61850-8-1通信栈从下到上依次是TCP传输层端口102TPKTRFC 1006在TCP上承载OSI传输层PDUCOTPISO 8073OSI传输层协议MMSISO 9506应用层协议很多人抓包时看到Wireshark里先是三条TCP握手然后出现一个COTP的Connect Request包再往后才是MMS的Initiate-Request就有点懵。这其实是标准的OSI over TCP封装过程。现场工程中站控层与IED之间的MMS通信几乎都不走完整的OSI七层协议栈而是采用RFC 1006的方式把OSI传输层PDU封装到TCP段里再通过TCP的102端口传输。这个链路有一个非常实用的小知识由于底层是TCPMMS通信天然具备可靠传输、流量控制和拥塞控制。数据不丢包、不乱序应用层可以相对专注于服务语义。但代价是实时性不如GOOSE那种直接映射到以太网链路层的机制所以IEC61850里跳闸类、闭锁类信息走GOOSE遥测、遥信、定值、文件等对实时性要求没那么极端但对完整性要求高的服务走MMS。你要是拿MMS去传跳闸命令响应时间和网络拥塞问题会让你想哭。1.3 IEC61850对象模型到MMS对象的翻译对照真正要上手编码和抓包前必须先建立一张“翻译表”。IEC61850里那些逻辑设备、逻辑节点、数据对象到了MMS世界里全部变成一个个MMS对象。我整理了一份最常用的对照关系IEC61850概念MMS概念说明Logical Device逻辑设备Domain域一个逻辑设备映射为一个MMS Domain通常以LDName命名Logical Node逻辑节点Domain内的一组命名变量逻辑节点实例用LDName/LNName作为前缀数据对象按分层的规则拼接成变量名Data Object / Data AttributeNamed Variable命名变量例如MMXU1.A.phsA.cVal.mag.f对应MMS里的一个浮点变量DataSetNamed Variable List命名变量列表数据集就是预先定义好的变量集合供报告和采样使用Report Control BlockMMS域内的RCB对象 InformationReport服务报告上送使用MMS的InformationReport信息报告Control Block如跳闸控制MMS域内的DO / SBO相关对象遥控操作在MMS侧体现为对控制块内各个属性变量的读、写操作这个翻译规则直接决定了你在Wireshark里看到的MMS对象路径。比如后台读某个间隔的有功功率MMS Read请求里出现的对象名是DistalLD/MMXU1.TotW.mag.f这样的形式本质就是IEC61850对象引用在MMS命名空间里的投影。编码时libIEC61850会在API里直接使用IEC61850风格的对象路径底层自动帮你映射成MMS命名但抓包分析时你要能两头对上。2. 环境搭建没有真实IED也能学的完整模拟方案2.1 硬件与仿真环境的两种选择学习MMS通信最大的门槛其实是设备。一台真实的IED智能电子设备动辄几万块不是人人都能随时摸到。就算有设备你也不方便反复折腾它的通信参数。所以我把实验方案分成两条路方案A真实IED 笔记本直连。这个适合有现场资源的工程师。把笔记本网口和IED接到同一个交换机或直连配好同网段IP然后用Wireshark抓MMS报文。优点是报文完全真实缺点是IED的配置文件生成、下装、重启这一套流程比较繁琐每次修改模型都要重新下装。方案B纯软件仿真。在电脑上跑libIEC61850开源库一台机器既是IED又是客户端甚至可以把IED服务和客户端都跑在同一台电脑上用回环接口抓包。这个方案零成本、可重复、能随时改代码特别适合学习阶段。这篇博文采用方案B因为它能让你在半小时内把完整链路跑起来之后再上真机就只是替换环境的问题。2.2 libIEC61850快速上手libIEC61850是目前用得最多的开源IEC61850实现由慕尼黑工业大学的MZ Automation团队维护支持MMS、GOOSE、SV等核心服务代码是用C语言写的提供了Server和Client两套API。它在GitHub上可以直接下载也提供了Windows和Linux的编译方式。我这里以Linux环境为例。克隆源码后进入examples/server_example_basic目录里面已经有一个现成的模拟IEDgit clone https://github.com/mz-automation/libiec61850.git cd libiec61850 mkdir build cd build cmake .. make -j4编译完成后在examples/server_example_basic目录下会生成一个可执行文件直接运行默认监听TCP 102端口IED的IP是0.0.0.0本机所有网卡模型是simpleIOGenericIO这个逻辑设备。终端打印输出类似IED server started on port 102可以在另一个终端用nmap -p 102 127.0.0.1验证端口已经监听或者用telnet 127.0.0.1 102不输入内容也能看到TCP连接建立。这一步就相当于你已经有了一台“模拟IED”。2.3 Wireshark的MMS协议解析配置Wireshark本身内置了MMS解析器但有个前提条件它需要正确识别底层是COTP/TPKT封装。默认情况下如果是标准的102端口Wireshark会自动识别但如果某些特殊环境里端口不是102或者你在LibIEC61850里改了监听端口就需要手动指定解码规则。具体操作在Wireshark里抓到TCP包后选中一个TCP报文右键 - 解码为Decode As- 传输层/应用层把TCP端口102指定为COTP。设置完成后Wireshark会按COTP协议去解析TCP负载然后自动识别上层MMS。如果你用的是自定义端口也可以在这里选RFC 1006或COTP关键让Wireshark能进入OSI over TCP的解码路径。另外一个很实用的小技巧Wireshark支持按协议名过滤。但在MMS关联建立之前你只能看到TCP和COTP报文。等MMS关联建立后过滤表达式mms就会生效直接只看MMS层。如果你希望过滤某个MMS服务类型比如只看Read请求响应可以用mms.confirmedRequestPDU配合mms.confirmedResponsePDU来过滤。这些表达式在后续抓包分析时高频使用。3. 手把手跑通一个MMS通信实例3.1 从一份最小模型开始把SCL文件里的信息“喂”给服务器先别急着写代码。IEC61850的世界里一切通信内容都基于信息模型而信息模型在工程中用SCLSubstation Configuration Language文件描述。libIEC61850的server_example_basic里已经内置了一份静态生成的模型直接用就可以但为了后面抓包看得明白我建议你动手改一个最小模型试试。这里用一个极简的逻辑设备模型包含一个逻辑节点LLN0作为公共节点再有一个MMXU1作为测量节点// 伪代码示意实际用libIEC61850的模型API创建 LogicalDevice* lDevice LogicalDevice_create(simpleIOGenericIO, model); LogicalNode* lln0 LogicalNode_create(LLN0, lDevice); LogicalNode* mmxu1 LogicalNode_create(MMXU1, lDevice);其中MMXU1下会有A电流、W有功功率等数据对象每个数据对象下面又有phsA、cVal、mag、f这些数据属性。这一层层的嵌套在MMS报文里会变成一个完整的MMS变量路径。你先记住这个规律IEC61850对象引用路径“/”层的点号层级在MMS中会转换为用$分隔的MMS标识符。3.2 服务端实现让模拟IED真正能应答读写libIEC61850的Server API核心就几行创建模型、创建IedServer对象、启动服务。我截取一个关键示例#include iec61850_server.h #include hal_time.h #include stdio.h int main(void) { // 创建IED服务器实例模型使用编译期生成的external model IedServer iedServer IedServer_create(iedModel); // 启动服务器监听所有网卡的102端口 IedServer_start(iedServer, 102); if (!IedServer_isRunning(iedServer)) { printf(Server start failed!\n); return 1; } printf(IED server running on port 102\n); while (1) { Thread_sleep(1000); // 模拟遥测值变化便于测试信息报告上送 IedServer_lockDataModel(iedServer); float value (float) (50.0 (rand() % 10) / 5.0); IedServer_setFloatValue(iedServer, (IedServer_getLogicalNode(iedServer, MMXU1)) ? IED_MODEL_MMXU1_A_phsA_cVal_mag_f : NULL, value); IedServer_unlockDataModel(iedServer); } IedServer_stop(iedServer); IedServer_destroy(iedServer); return 0; }这个代码的核心是服务器内部维护了一个数据模型客户端通过MMS Read来读取当前值通过MMS Write来写入数据。启动以后它就挂在102端口等着被调用。为了后面抓包能看到上送信息我还在循环里改了MMXU1.A.phsA.cVal.mag.f的值这样一旦使能了报告控制块客户端就能收到InformationReport。3.3 客户端实现连接、读遥测、写遥控值客户端用libIEC61850的Client API逻辑也很直白#include iec61850_client.h #include stdio.h int main(void) { IedConnection con IedConnection_create(); // 用TCP连接服务器 IedConnection_connect(con, 127.0.0.1, 102); if (con-state ! IED_CONNECTION_STATE_CONNECTED) { printf(Connection failed\n); IedConnection_destroy(con); return 1; } printf(Connected to server\n); // 读取遥测值MMXU1.A.phsA.cVal.mag.f MmsValue* value IedConnection_readObject(con, simpleIOGenericIO, MMXU1/A.phsA.cVal.mag.f, IEC61850_FC_MX); if (value ! NULL) { printf(A magnitude (f): %f\n, MmsValue_toFloat(value)); MmsValue_delete(value); } // 写入一个开关位置GGIO1.SPCSO1.stVal示例需要模型里有这个点 IedConnection_writeObject(con, simpleIOGenericIO, GGIO1/SPCSO1.stVal, IEC61850_FC_ST, MmsValue_newBoolean(true)); // 断开连接 IedConnection_close(con); IedConnection_destroy(con); return 0; }运行这个客户端终端会打印读到的遥测值。这里有个非常容易误解的地方IedConnection_readObject里传的第二个参数是逻辑设备名第三个参数是LNName.DataObject路径但底层会拼成MMS可解析的完整路径。如果你传错了逻辑设备名或者把路径层级写错MMS返回的就是ObjectAccessDenied或ObjectNonExistent这在现场是非常典型的错误。3.4 验证通信三端同步确认跑起来以后建议开三个终端一个跑server一个跑client一个开Wireshark在loopback接口上抓包。抓包后先别急着看内容直接观察报文数量一次完整的连接读写应该有大约20-30个TCP段其中MMS应用层报文不足10个。这个比例基本展示了MMS协议的“真实体积”底层TCP握手、确认、窗口管理消耗了相当一部分报文。我建议你把抓包保存为pcapng文件后面每一步分析都基于这份现场报文来对照。我在第四章用的就是这类实测报文的结构和顺序。4. Wireshark抓包把MMS报文拆开看4.1 过滤规则先抓到对的那几包抓包时如果server和client都在本机选loopbacklo接口。启动抓包后再运行client就能看到完整交互。为了聚焦MMS报文过滤栏用tcp.port 102先看全部102端口流量。等到MMS关联建立后可以用mms || cotp把MMS和COTP报文一起显示这样能直观看到TCP之上COTP和MMS的层次关系。不要只过滤mms因为会漏掉COTP层的连接建立过程而COTP连接建立恰恰是很多人看不懂的“额外握手”。4.2 一次完整会话的报文时序从TCP握手到InformationReport我截取一次真实的会话过程把关键报文列成一张表序号源-目的协议关键信息1Client - ServerTCPSYN握手开始2Server - ClientTCPSYNACK3Client - ServerTCPACK4Client - ServerCOTPCRConnect RequestTPKT包5Server - ClientCOTPCCConnect Confirm6Client - ServerMMSInitiate-Request携带MMS版本、协商参数7Server - ClientMMSInitiate-Response8Client - ServerMMSConfirmed-RequestRead读取9Server - ClientMMSConfirmed-ResponseRead返回值10Client - ServerMMSConfirmed-RequestWrite写入11Server - ClientMMSConfirmed-ResponseWrite返回成功12Client - ServerMMSConfirmed-RequestGetNameList可选13Client - ServerTCPFIN/ACK连接关闭注意第4、5两步这是OSI传输层的连接建立COTP Connect Request/Confirm它发生在MMS关联之前。很多教程只讲TCP和MMS忽略了COTP导致新手看到COTP包就以为MMS出问题了。其实COTP建立成功后才能承载MMS的Initiate。后面第6、7步才是MMS层的“握手”用来协商MMS协议版本、最大报文长度、支持的服务等参数。第8-12步是真正的业务请求。信息报告InformationReport不会出现在这个会话里因为它是服务端主动推送的必须等报告控制块被使能后才会触发。为了看到它你可以在client连接后先对数据集使能报告RptEna置true然后服务端那个循环改值的代码就会触发InformationReport主动上送。4.3 字节级拆解用一次Read请求看穿MMS的TLV编码Wireshark能把MMS报文解码成结构化的字段但你要真理解协议还是要看原始字节。MMS的PDU编码遵循ASN.1的BERBasic Encoding Rules规则基本单元是TLV三元组Tag类型、Length长度、Value值。Wireshark的“Bytes”面板里能看到完整hex原始数据。我用一个实际Read请求包的关键部分来说明。抓包里某个MMS Confirmed-Request PDU的hex长这样此处为演示略作简化03 00 00 48 02 F0 80 00 01 00 00 00 00 0A A0 25 02 01 01 A1 20 60 1E A0 1C 30 1A 04 0E 73 69 6D 70 6C 65 49 4F 47 65 6E 65 72 69 63 49 4F 04 08 4D 4D 58 55 31 2E 41 2E ...我逐层拆解给你看03 00 00 48TPKT头。03是常量版本号00保留00 48十进72表示整个TPKT包长度为72字节。02 F0 80COTP头。02表示头长度2字节F0是DTDataTPDU类型80是EOT标志。接下来的00 01 00 00 00 00 0A是MMS的关联ID或一部分包装字段具体看实现。A0 25第一个MMS应用层TLVTagA0表示Confirmed-Request PDU上下文标签00x25十进37表示后面Value区占37字节。02 01 01Integer类型TLV值是01这是invokeID调用编号。A1 20上下文标签1表示请求内容。后面跟的60 1E就是ASN.1的ReadRequest结构。这套逐层解析的方法看着繁琐但在现场排查时极其有用。因为Wireshark有时会因为解析器版本问题把字段解析错位你拿原始hex和标准文档核对才能确认到底是设备发了非标报文还是Wireshark误判。4.4 InformationReport设备主动上报是怎么出现的InformationReport是MMS中唯一一个设备不经过请求就能主动发给客户端的服务对应IEC61850里的报告上送机制。线上运行中遥控操作后的遥信变位、模拟量越限等都是通过InformationReport即时推送的。抓包时你会看到服务端发起一个TCP数据段里面MMS PDU的类型是InformationReportTag通常是A6上下文标签6。注意它不需要invokeID也不需要客户端回应确认——实际上MMS协议也不要求对InformationReport做应用层确认可靠性完全依赖底层TCP。这又验证了一件事MMS对TCP的依赖不是可有可无的而是设计上就靠TCP来保证不丢包。如果要定位某个数据集的报告报文可以看InformationReport里携带的数据集名称和对象列表。正常情况下使能报告后服务器按缓存报告里设定的触发条件数据变化、品质变化、数据刷新等在对应时机发出InformationReport。如果收不到就要回头检查报告控制块的触发条件配置和数据集引用是否一致。5. 实战中必然踩的坑从连不上到数据错位的排查链路5.1 第一层排查TCP都连不上先别怪MMS现场联调最常见的现象是客户端报连接失败。第一步不要看MMS先看TCP通不通。命令行里telnet IP 102能通说明TCP没问题如果不通查IP地址、子网掩码、网关和交换机的VLAN配置。Wireshark里如果连TCP三次握手都看不到或只有SYN没有SYNACK那就说明数据包根本没送到对端或者被防火墙拦了别打开MMS解析浪费时间。另外一个隐蔽坑多网卡机器上服务端绑定的是0.0.0.0还能接受任意网卡但如果服务端绑定了具体IP客户端连另一个网卡IP就会失败。现场IED可能有多个网口管理口和过程层口IP不能混用。这类问题你可以在Wireshark里看到TCP重传或RST包。5.2 第二层排查TCP通了但MMS关联失败TCP握手正常COTP连接的CR/CC也正常但MMS Initiate-Request发出去后服务端回了Initiate-Response里的协商结果包含错误或者直接发了Reject这就需要看MMS的错误原因。常见的有版本不匹配、不支持的服务类型、超出最大报文长度限制等。libIEC61850里如果server端模型不支持某个服务它会在MMS层返回ServiceUnsupported的响应。这时候抓包看响应报文中的reject reason字段那个错误码可以直接查MMS标准附录。很多时候是因为server端没有使能对应功能的MMS服务映射比如没有使能GetNameList或者没有开放文件传输。这些在IedServer启动配置里都有开关。5.3 第三层排查路径引用和数据模型不一致这是我遇到最多的一类问题。客户端连上了关联也建立成功但读数据时报ObjectNonExistent或读出来的值是空。根本原因是MMS对象路径和Server实际模型不匹配。IEC61850的对象路径大小写敏感一点都不能错。比如MMXU1.A.phsA.cVal.mag.f和MMXU1/a.phsA.cVal.mag.f注意大小写和斜杠位置在MMS里就是完全不同的变量。排查时有一个高效的办法在客户端调用IedConnection_getNameList把Server里某个Domain下的所有变量列表拉出来看看实际对象名是什么。libIEC61850的客户端例子goose_client和mms_client里都有获取名称列表的调用可以直接参考。对照实际对象名和代码里读的对象名很快就能发现是多了还是少了中间某一段比如把cVal写成了cval。还有一个高频错误把IEC61850的数据集DataSet路径当成普通变量去读。数据集在MMS里是Named Variable List不是单一的Named Variable需要用专门的读数据集操作用IedConnection_readMultipleObjects或者直接读数据集对象的引用。用读单变量的API去读数据集肯定读不出来。5.4 Wireshark显示“看不懂”的常见原因有时你会发现Wireshark明明抓到了102端口的包却显示为Data而不是COTP或MMS。这通常是解码器没识别成功。解决方式前面提过选中报文右键“解码为”手动把TCP端口指定为COTP。如果还不行检查一下抓包长度设置有些网卡默认抓包长度snaplen不够大大报文被截断Wireshark解析NM_MMS的TLV时长度字段超出捕获长度就会放弃解析。在Wireshark的捕获选项里把“限制每个包的长度”设成65535字节或留空基本能解决。还有一类情况MMS报文较长时TCP会分片传输Wireshark默认会做TCP重组但如果抓包时丢了一个分片重组失败整个MMS报文就变成乱码。此时检查Wireshark底部状态栏有没有显示“TCP segment of a reassembled PDU”或“Unreassembled”字样。如果是服务器或者客户端的发送缓冲区设置不合理导致频繁分片可以调整栈的MSS或应用层报文长度配置但这属于网络调优范畴先确认抓包无误再说。5.5 信息报告不触发的典型原因前面提到服务端主动上报依赖报告控制块RCB使能。现场调试时经常遇到客户端已经把RptEna置为true但设备数据变化后就是没有InformationReport。原因是RCB里还有两个关键参数一个是数据集引用DatSet一个是触发条件TrgOp。数据集引用必须指向一个已经定义好的数据集如果数据集不存在或引用错位设备根本不知道该上报什么。触发条件则决定了哪些事件能触发上报常见配置数据变化触发或品质变化触发你只置RptEna但TrgOp是0设备同样不会上报。另外MMS的Report是带缓存机制的。如果服务端数据变化频繁而客户端来不及接收消息会积压在缓存里。调试时建议把触发条件先设为“数据变化”一种频率不要太高确保每条报告能对应到一次明确的数据变化上。我见过很多新手栽在这里RptEna也置true了数据集也对但触发条件没设数据死活不上送抓包抓来抓去都是空。最后分享一个调试习惯从我自己的经历看MMS协议调试最大的陷阱不是协议本身而是“你以为你发的东西和实际发出去的东西是一致的”。所以每次联调我都习惯性地先开Wireshark留底哪怕刚开始不分析等出问题再回看往往比瞎猜快得多。还有一个更具体的习惯把抓到的MMS报文里的对象路径复制到文本编辑器里和SCD文件里的IED模型逐字对照大多数数据读不到的问题都是这个环节抓出来的。另外给刚入门的朋友一个建议如果你已经把libIEC61850的例子跑通下一步不要急着上复杂模型先从一个点比如一个遥测值开始自己写client自己在Wireshark里找到对应的Read和Response报文再去看InformationReport。把这个最小闭环吃透再到现场面对真实IED你会发现除了设备型号变了通信流程几乎完全一样。剩下的就是经验和耐心了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →