EMQX MQTT 5.0 协议合规修复:拒绝 PUBLISH 包中非法的 Subscription-Identifier 属性

发布时间:2026/9/24 11:37:28
EMQX MQTT 5.0 协议合规修复:拒绝 PUBLISH 包中非法的 Subscription-Identifier 属性 后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载导读本文深入解析 EMQX 在 MQTT 5.0 协议处理上的一个关键合规性修复对应 changelog 条目changes/ee/fix-16782.en.md当客户端在 PUBLISH 报文中携带Subscription-Identifier属性时EMQX 现在会将其判定为协议错误Protocol Error并主动断开该客户端连接。读完本文你将理解 MQTT 5.0 中Subscription-Identifier属性的合法使用场景、EMQX 从报文解析到连接断开的完整校验链路以及该修复对服务端安全性与协议互操作性的实际意义。背景Subscription-Identifier 属性在 MQTT 5.0 中的定位Subscription-Identifier订阅标识符是 MQTT 5.0 引入的可变报头属性其属性标识符Property Identifier为0x0B取值为一个可变字节整数范围是 1 到 268,435,4550xFFFFFFF。按照 MQTT 5.0 规范该属性仅允许出现在两类报文中SUBSCRIBE 报文客户端在订阅时附带一个或多个订阅标识符用于把某条订阅与一个业务标识绑定服务端下发的 PUBLISH 报文服务端在向客户端投递消息时把与该消息匹配的订阅所对应的订阅标识符放入 PUBLISH 属性中客户端据此可以快速判断这条消息来自哪条订阅从而在单连接上实现类似多租户的路由分流无需解析主题。关键约束客户端发送给服务端的 PUBLISH 报文不得包含Subscription-Identifier属性。规范明确若出现该情况服务端必须将其视为协议错误Protocol Error并按协议错误流程处理发送带原因码0x82的 DISCONNECT 报文并关闭连接。这个约束背后的逻辑很清晰订阅标识符的生产者只能是服务端它在投递时生成客户端没有资格在发布消息时伪造订阅标识。如果允许客户端任意携带该属性既违背规范语义也可能被用于混淆服务端对消息来源的判断。修复内容概述本次修复changelog 原文Fixed MQTT v5 protocol handling for invalid PUBLISH properties. If a client sends a PUBLISH packet containingSubscription-Identifier, EMQX now treats it as a protocol error and disconnects the client.即此前 EMQX 对客户端 PUBLISH 中携带的非法Subscription-Identifier属性缺乏严格的协议校验修复后一旦检测到该属性出现在客户端 PUBLISH 报文中EMQX 将其按协议错误处理并断开连接。源码级解析从属性解析到连接断开的完整链路要理解这次修复可以沿 EMQX 处理一条入站 PUBLISH 报文的完整路径逐层查看相关实现。第一步帧层属性解析emqx_frame.erlEMQX 的 MQTT 帧解析器位于 apps/emqx/src/emqx_frame.erl。在解析报文属性时属性标识符0x0B被识别为Subscription-Identifier其值按可变字节整数解析并存入属性映射parse_property(16#0B, Bin/binary, Props, StrictMode, MaxUserProps, NUserProps) - {Val, Rest} parse_variable_byte_integer(Bin), parse_property( Rest, put_prop(Subscription-Identifier, Val, Props, StrictMode), StrictMode, MaxUserProps, NUserProps );这段代码位于 emqx_frame.erl。可以看到帧解析层对属性本身是中立的——它只负责把字节流正确地解码成属性键值对至于该属性出现在哪种报文中是否合法交由上层报文校验逻辑判定。帧层还负责属性序列化serialize_property即服务端在生成下行 PUBLISH / CONNACK 时按相同规则编码。第二步报文合法性校验emqx_packet.erl报文级别的语义校验集中在 apps/emqx/src/emqx_packet.erl 的emqx_packet:check/1,2中。本次修复的核心逻辑就在 PUBLISH 属性的专项校验函数check_pub_props/1check_pub_props(#{Topic-Alias : 0}) - {error, ?RC_TOPIC_ALIAS_INVALID}; check_pub_props(#{Subscription-Identifier : _}) - {error, ?RC_PROTOCOL_ERROR}; check_pub_props(#{Response-Topic : ResponseTopic}) - try emqx_topic:validate(name, ResponseTopic) of true - ok catch error:_Error - {error, ?RC_PROTOCOL_ERROR} end; check_pub_props(_Props) - ok.代码见 emqx_packet.erl。要点如下只要 PUBLISH 属性映射中出现Subscription-Identifier键立即返回{error, ?RC_PROTOCOL_ERROR}协议错误原因码其余 PUBLISH 属性按各自规则继续校验例如Topic-Alias不允许为 0、Response-Topic必须是合法主题名该校验属于 PUBLISH 报文整体check/1流程的一部分与 SUBSCRIBE 报文的校验check_subscribe/3相互独立。作为对照emqx_packet.erl 中 SUBSCRIBE 报文的Subscription-Identifier校验只检查取值范围1..0xFFFFFFF超范围才返回?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED——这正体现了同一属性在不同报文中的合法性与语义完全不同这一设计原则。第三步通道层执行断开emqx_channel.erl报文经帧解析后由通道进程emqx_channel处理。在 apps/emqx/src/emqx_channel.erlPUBLISH 报文进入handle_in后先做合法性检查handle_in(?PUBLISH_PACKET(_QoS, _Topic, _PacketId) Packet, Channel) - case emqx_packet:check(Packet) of ok - ?EXT_TRACE_CLIENT_PUBLISH(...), ... {error, ReasonCode} - ?TRACE(MQTT, invalid_publish_packet, #{reason emqx_reason_codes:name(ReasonCode)}), handle_out(disconnect, ReasonCode, Channel) end;当emqx_packet:check(Packet)返回{error, ?RC_PROTOCOL_ERROR}即上述check_pub_props的判定结果时通道层调用handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel)服务端向客户端发送DISCONNECT 报文原因码为协议错误0x82随后关闭该客户端的网络连接。这与 MQTT 5.0 规范对协议错误的处置要求一致服务端应发送 DISCONNECT原因码 0x82并终止连接。handle_out(disconnect, ...)在本文件中多处被用于各类协议错误场景如 emqx_channel.erl是 EMQX 统一的协议错误出口。服务端正常使用侧Subscription-Identifier 的合法路径为了对照理解再看服务端合法使用该属性的两条路径路径一SUBSCRIBE → 存储。客户端在 SUBSCRIBE 中携带订阅标识符时emqx_channel.erl 的enrich_subopts_subid会把它写入对应订阅选项的subid字段随后用于会话/订阅管理enrich_subopts_subid(TopicFilters, #{sub_props : #{Subscription-Identifier : SubId}}) - [{Topic, SubOpts#{subid SubId}} || {Topic, SubOpts} - TopicFilters]; enrich_subopts_subid(TopicFilters, _State) - TopicFilters.路径二CONNACK 能力声明。EMQX 在 CONNACK 中向客户端声明自己支持订阅标识符emqx_channel.erlNAckProps AckProps#{ ... Subscription-Identifier-Available 1, ... },只有服务端在 CONNACK 中声明Subscription-Identifier-Available: 1客户端才被允许在 SUBSCRIBE 中使用订阅标识符而无论服务端是否支持客户端 PUBLISH 都绝不允许携带该属性。本次修复正是堵住了后一个方向的合规漏洞。修复的工程价值从工程角度看本次修复带来三个层面的收益协议合规性EMQX 作为 MQTT Broker其入站报文校验必须与 MQTT 5.0 规范保持一致。对非法 PUBLISH 属性不再宽容放行避免出现规范不允许的报文被静默接受的兼容性偏差。安全与健壮性协议错误的客户端可能是实现有缺陷的 SDK也可能是恶意构造报文的攻击者会在连接建立后立即被识别并断开防止其利用该连接持续发送畸形报文减轻服务端无谓的资源占用。互操作性保障其他严格遵守规范的客户端和服务端可以确信EMQX 对 PUBLISH 属性的处理符合 MQTT 5.0 语义降低跨厂商联调时因属性误用导致的隐性故障。排查与验证建议如果你在升级 EMQX 后观察到某些 MQTT 5.0 客户端频繁被断开可按下述思路排查抓取客户端发出的 PUBLISH 报文检查其属性列表中是否包含Subscription-Identifier属性标识0x0B。合规的客户端 SDK 不应在发布消息时添加该属性在 EMQX 日志中检索invalid_publish_packet跟踪日志其reason字段会给出protocol_error之类的具体原因对应 emqx_channel.erl 的?TRACE输出若是自研客户端请在发布路径上移除Subscription-Identifier属性若使用第三方 SDK可联系 SDK 维护方确认其 MQTT 5.0 属性处理是否符合规范。小结fix-16782是一个典型的小而关键的协议合规修复它在 emqx_packet.erl 中为客户端 PUBLISH 报文增加Subscription-Identifier属性的非法性判定并由 emqx_channel.erl 在报文入口统一执行协议错误即断开的处理策略。整条链路——帧解析emqx_frame.erl→ 语义校验emqx_packet.erl→ 通道处置emqx_channel.erl——清晰展现了 EMQX 对 MQTT 5.0 报文先校验、后处理的设计思想也提醒客户端开发者MQTT 5.0 的属性使用有严格的报文上下文约束任何想当然的属性携带都可能触发服务端的协议错误处理。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX 严格模式strict_mode拒绝重复 MQTT v5 属性行为变更与配置指南EMQX 严格模式strict_mode拒绝重复 MQTT v5 属性行为变更与配置指南 导读 本文围绕 EMQX 5.x 中关于 MQTT v5 报文后端物联网消息队列通信MQTT 5.0全支持EMQX协议兼容性与性能测试报告MQTT 5.0全支持EMQX协议兼容性与性能测试报告 引言MQTT 5.0时代的消息传递挑战 你是否正在为物联网项目选择合适的MQTT代理面对海量设备连后端物联网消息队列通信EMQX 消除 MQTT v5 CONNACK 拒绝连接后的 unclean_terminate 误报警告日志PR 15872 修复解析EMQX 消除 MQTT v5 CONNACK 拒绝连接后的 unclean_terminate 误报警告日志PR 15872 修复解析 导读 本文围绕 E后端物联网消息队列通信创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考