news 2026/9/24 11:37:03

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EMQX MQTT 5.0 协议合规修复:拒绝 PUBLISH 包中非法的 Subscription-Identifier 属性
  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

The 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,455(0xFFFFFFF)。

按照 MQTT 5.0 规范,该属性仅允许出现在两类报文中:

  1. SUBSCRIBE 报文:客户端在订阅时附带一个或多个订阅标识符,用于把某条订阅与一个业务标识绑定;
  2. 服务端下发的 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.erl)

EMQX 的 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/1

check_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.erl,PUBLISH 报文进入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.erl):

NAckProps = AckProps#{ ... 'Subscription-Identifier-Available' => 1, ... },

只有服务端在 CONNACK 中声明Subscription-Identifier-Available: 1,客户端才被允许在 SUBSCRIBE 中使用订阅标识符;而无论服务端是否支持,客户端 PUBLISH 都绝不允许携带该属性。本次修复正是堵住了后一个方向的合规漏洞。

修复的工程价值

从工程角度看,本次修复带来三个层面的收益:

  1. 协议合规性:EMQX 作为 MQTT Broker,其入站报文校验必须与 MQTT 5.0 规范保持一致。对非法 PUBLISH 属性不再"宽容放行",避免出现规范不允许的报文被静默接受的兼容性偏差。
  2. 安全与健壮性:协议错误的客户端(可能是实现有缺陷的 SDK,也可能是恶意构造报文的攻击者)会在连接建立后立即被识别并断开,防止其利用该连接持续发送畸形报文,减轻服务端无谓的资源占用。
  3. 互操作性保障:其他严格遵守规范的客户端和服务端可以确信,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 的属性使用有严格的报文上下文约束,任何"想当然"的属性携带都可能触发服务端的协议错误处理。

  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles

项目地址:https://gitcode.com/gh_mirrors/em/emqx
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 11:36:28

Pixhawk 2.4.8差速无人车从零下地:参数配置与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 11:35:32

博科光纤交换机初始化与Zoning配置实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 11:34:26

28 nm PMOS中SiGe外延应力调控四维优化方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 11:29:11

ESP32驱动I2S D类功放:从接线到ESP-IDF实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 11:23:12

NXP LPC18Sxx:180MHz安全MCU的实时控制与硬件防护实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 11:21:23

八千里工业设计的品牌故事:三千个产品设计的远方

八千里工业设计的品牌故事&#xff1a;三千个产品设计的远方 2013年&#xff0c;深圳。制造业正热的年月&#xff0c;一群在深圳做工业设计、相信"好设计能改变产品"的人聚到了一起&#xff0c;成立了八千里工业设计。也是那一年&#xff0c;我们在阿里巴巴1688开了店…

作者头像 李华