news 2026/9/23 22:35:10

EMQX CoAP 网关连接模式(Connection Mode)加固解析:DTLS 断连后会话保活与 clientid+token 重连接管

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EMQX CoAP 网关连接模式(Connection Mode)加固解析:DTLS 断连后会话保活与 clientid+token 重连接管
  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

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

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

EMQX 的 CoAP 网关在connection_required = true的连接模式下,过去一旦底层 DTLS socket 关闭(sock_closed),对应的连接上下文与会话便会随之销毁,导致使用 CoAP over DTLS 的受限设备在网络抖动后必须重新走完整的建连认证流程。本篇文章基于仓库变更记录 changes/ee/fix-16996.en.md 展开,结合 emqx_coap_channel.erl 的通道状态机与 emqx_coap_dtls_connection_SUITE.erl 的测试用例,深入讲解修复后的两个核心行为:连接态下收到sock_closed后保留会话,以及使用相同clientid与有效token在新 DTLS 会话上完成重连接管(takeover)。读完本文,你将掌握该模式的配置方式、token 的签发与校验规则、断线重连的完整流程,以及如何通过仓库中的测试用例验证这些行为。

修复背景:UDP/DTLS 场景下的会话连续性痛点

CoAP 基于 UDP,本身是无连接的;当叠加 DTLS 提供传输安全时,底层 socket 的存续期与业务会话的存续期是解耦的。对于低功耗、弱网设备,DTLS 连接随时可能因网络波动、NAT 超时或设备休眠而中断。在修复之前,CoAP 通道在任意时刻收到sock_closed都会直接触发shutdown,导致:

  • 已创建的连接上下文(clientid、认证信息)丢失;
  • 后续设备重连时必须重新执行POST /mqtt/connection完整建连流程;
  • 期间到达的订阅消息、观察(Observe)状态等会话数据无法衔接。

本次变更(对应仓库变更记录 fix-16996.en.md)的目标非常明确:

修复 CoAP DTLS 连接模式,使sock_closed之后会话仍然可用,并支持使用相同clientid与有效token的重连接管。

理解连接模式:connection_required 开关

CoAP 网关存在两种工作模式,由配置项gateway.coap.connection_required控制(默认false),其语义在 rel/i18n/emqx_coap_schema.hocon 中有官方说明:

  • false(默认):无连接上下文(stateless),每个请求独立认证处理,适合纯发布/订阅的简单设备;
  • true:客户端必须创建连接上下文,经过认证并保持心跳存活,适合依赖连接状态的设备流程。

配置项定义位于 emqx_coap_schema.erl,同文件还定义了heartbeat(默认30s)、notify_typesubscribe_qospublish_qosblockwise等参数。其中heartbeat仅在connection_required = true时生效,用于判定客户端是否离线(rel/i18n/emqx_coap_schema.hocon)。

在通道初始化时,该开关被读入通道记录并初始化为conn_state = idle(emqx_coap_channel.erl),此后整个生命周期都围绕idle / connected状态机运转。

会话建立:POST /mqtt/connection 与 token 签发

在连接模式下,客户端首先通过 CoAPPOST请求coaps://host/mqtt/connection建立连接。请求的 URI Query 携带认证与身份信息,仓库内部使用?QUERY_PARAMS_MAPPING定义短参数映射(emqx_coap.hrl):

短参数完整参数用途
cclientid客户端标识
ttoken会话令牌
uusername用户名
ppassword密码
qqos服务质量
rretain保留消息标志

建连请求的识别逻辑在 emqx_coap_channel.erl:URI Path 必须等于[<<"mqtt">>, <<"connection">>]且方法为POSTDELETE同一路径则用于主动断开连接。

建连成功后,服务端签发会话令牌,核心逻辑在process_connect/5(emqx_coap_channel.erl):

  1. 调用emqx_gateway_ctx:open_session/6打开会话;
  2. 通过rand:uniform(?TOKEN_MAXIMUM)生成随机数并转为二进制作为token?TOKEN_MAXIMUM定义为4294967295,见 emqx_coap_channel.erl);
  3. token写入通道记录,并以 CoAP2.01 Created响应返回给客户端,响应 payload 即该 token。

客户端必须妥善保存该 token——它是后续所有请求的通行凭证,也是断线重连接管的关键。

心跳保活与主动断开

连接建立后,客户端需要周期性发送心跳。从测试用例可以看到,心跳请求同样是PUT /mqtt/connection?clientid=xxx&token=xxx(emqx_coap_dtls_connection_SUITE.erl)。通道侧由emqx_keepalive机制驱动(emqx_coap_channel.erl):心跳超时会触发{shutdown, timeout, ensure_disconnected(keepalive_timeout, Channel)},即判定离线。

主动断开则通过DELETE /mqtt/connection完成:当通道 token 为空且收到删除连接请求时,返回2.02 Deleted并正常关闭通道(emqx_coap_channel.erl)。

核心修复一:sock_closed 后的会话保留

本次修复的关键代码位于handle_info/2(emqx_coap_channel.erl):

handle_info( {sock_closed, _Reason}, #channel{connection_required = true, conn_state = connected} = Channel ) -> {ok, Channel}; handle_info({sock_closed, Reason}, Channel) -> shutdown(Reason, Channel);

行为可以拆解为两个分支:

  • 连接模式(connection_required = true)且通道已处于connected状态:收到sock_closed时直接返回{ok, Channel},即不销毁通道、不终止会话、不注销客户端注册。DTLS socket 关闭只代表传输层断开,业务会话继续保留在网关侧,等待客户端重连接管;
  • 其他情况(连接模式但尚未 connected,或非连接模式):仍按原逻辑执行shutdown,避免无连接上下文残留。

这一条件分支被单元测试 t_channel_connection_mode_sock_closed 精确覆盖:测试构造connection_required = true的通道,在conn_state = connected且已设置 token 的情况下向handle_info发送{sock_closed, ssl_closed},断言返回{ok, _}(会话保留);而在非 connected 状态或connection_required = false时,断言返回{shutdown, ssl_closed, _}(正常关闭)。

同时,集成测试 t_token_takeover_across_dtls_sessions 验证了更完整的链路:客户端关闭 DTLS socket 后,emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)仍然返回非空——说明连接上下文与通道注册在 socket 关闭后依然存活。

核心修复二:相同 clientid + 有效 token 的重连接管

sock_closed后会话保留只是前提,真正的价值在于客户端重连后能够快速恢复。重连接管流程由check_token/2try_takeover_with_token/4协同完成。

1. 连接模式下强制校验 token

连接模式下,任何非建连请求都会先经过check_auth_state/2(emqx_coap_channel.erl):

  • 若是POST /mqtt/connection建连请求,走正常的会话创建流程;
  • 否则必须从 URI Query 中解析出token;缺失 token 的请求直接返回4.00 Bad Request(错误信息Missing token or clientid in connection mode,见 emqx_coap_channel.erl)。

2. token 与 clientid 的匹配校验

check_token/2(emqx_coap_channel.erl)进一步比对请求参数与通道记录:

  • 请求clientidtoken均与通道一致:正常放行处理;
  • 相同clientid但 token 不匹配:直接拒绝,返回4.01 UnauthorizedInvalid token or clientid in connection mode,见 emqx_coap_channel.erl);
  • clientid与当前通道不同(重连接管场景):进入try_takeover_with_token/4

3. 跨 DTLS 会话的接管

try_takeover_with_token/4(emqx_coap_channel.erl)通过网关连接管理器查询旧连接:

emqx_gateway_cm:call(coap, ReqClientId, {check_token_and_get_clientinfo, ReqToken})

旧通道在handle_call({check_token_and_get_clientinfo, ReqToken}, ...)(emqx_coap_channel.erl)中完成 token 比对,返回脱敏后的 clientinfo(明确移除了password字段,避免通过接管探测接口传播敏感凭据)。token 校验失败或找不到旧通道时返回undefined/false,统一以4.01 Unauthorized拒绝。

校验通过后进入takeover_and_handle_request/5(emqx_coap_channel.erl):

  1. 合并旧连接的 clientinfo:将usernameis_superuserauth_expire_atmountpointenable_authn等认证与挂载信息从旧通道恢复(merge_takeover_clientinfo/3,emqx_coap_channel.erl);
  2. 调用emqx_gateway_ctx:open_session/6打开会话,present = true表示会话已存在(被接管);
  3. 将新请求的 token 写入新通道并置为connected状态,随后正常处理该请求。

接管完成后,同一clientid在注册表中只保留一个通道——集成测试 t_token_takeover_across_dtls_sessions 通过?assertEqual(1, length(emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)))验证了这一点。

4. 安全边界:错误 token 与错误 clientid 均被拒绝

接管机制必须杜绝身份冒用。测试套件对此做了充分验证:

  • t_invalid_token_rejected:携带错误 token 重连,请求返回4.01 Unauthorized
  • t_wrong_clientid_with_valid_token_rejected:token 有效但clientid与签发 token 的客户端不一致,同样被拒绝;
  • t_partial_token_params_rejected:只带clientid或缺省clientid的请求返回4.00 Bad Request

完整配置示例

以下配置同时覆盖 UDP(5683)与 DTLS(5684)监听器,并开启连接模式:

gateway.coap { ## 连接模式开关:true 表示客户端必须建立连接上下文 connection_required = true ## 心跳间隔,仅 connection_required = true 时生效,默认 30s heartbeat = "30s" ## 观察主题消息的 CoAP 消息类型:non | con | qos(qos 按 MQTT QoS 自动映射) notify_type = qos ## 订阅/发布缺省 QoS:qos0 | qos1 | qos2 | coap subscribe_qos = qos1 publish_qos = qos1 ## 主题挂载点 mountpoint = "coap/" listeners.udp.default { bind = "5683" max_connections = 1024000 max_conn_rate = 1000 } listeners.dtls.default { bind = "5684" enable_authn = false dtls_options { verify = verify_none } } }

该配置与 DTLS 测试套件的初始化配置基本一致(emqx_coap_dtls_connection_SUITE.erl)。测试中 DTLS 客户端通过er_coap_dtls_socket:connect({127,0,0,1}, 5684, [{verify, verify_none}])建连,随后访问coaps://127.0.0.1/mqtt/connection完成建连(emqx_coap_dtls_connection_SUITE.erl)。注意:通过emqx.conf配置网关为单节点生效,通过 Dashboard 或 HTTP API 配置则集群生效(见 CoAP 网关 README)。

端到端流程回顾

综合源码与测试,连接模式下的一次断线重连完整链路如下:

  1. 设备 DTLS 连接建立,POST coaps://host/mqtt/connection?clientid=...&username=...&password=...完成认证,服务端签发 token(2.01 Created);
  2. 设备周期性发送PUT /mqtt/connection?clientid=...&token=...心跳保活;
  3. 网络波动导致 DTLS socket 关闭,通道收到{sock_closed, _}——因处于connected状态且连接模式开启,会话与注册保留
  4. 设备重新建立 DTLS 连接,直接携带相同clientid与 token 发送业务请求;
  5. 网关校验 token,向旧通道发起接管探测,恢复认证上下文并打开既有会话,请求得到正常处理;
  6. 若 token 无效、clientid 不匹配或参数缺失,分别以4.01 Unauthorized/4.00 Bad Request拒绝,会话不被冒用。

CoAP 网关整体的消息处理分层(通道 → 认证检查 → 会话 → 传输管理器 → pubsub/mqtt handler)可参考仓库中的流程图 apps/emqx_gateway_coap/doc/flow.png:认证检查在通道入口完成,业务逻辑按消息类型分发给 pubsub handler 与 mqtt handler,正是本修复涉及的check_auth_statecheck_token分支所处的位置。

EMQX CoAP 网关消息处理流程

总结

本次变更从两个层面解决了 CoAP DTLS 连接模式下的会话连续性问题:断开保留sock_closed不再销毁 connected 态会话)与安全接管(仅相同clientid+ 有效token可跨 DTLS 会话恢复连接上下文)。这既降低了弱网设备重连的成本,又通过 token 强校验避免了身份冒用。若需在生产环境启用,请务必:为设备提供可靠的 token 持久化能力、按设备实际心跳节奏合理设置heartbeat,并保持connection_required = true下的请求参数完整性(clientidtoken缺一不可)。

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

【免费下载链接】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/23 22:33:57

C/C++宏定义:原理、技巧与最佳实践

1. 宏定义基础与核心概念在C/C开发者的日常工作中&#xff0c;宏定义&#xff08;#define&#xff09;就像瑞士军刀中的万能工具&#xff0c;看似简单却蕴含巨大能量。作为预处理器指令&#xff0c;它会在编译器看到代码之前完成文本替换工作。这种机制虽然原始&#xff0c;却为…

作者头像 李华
网站建设 2026/9/23 22:32:59

日语动词活用规则全解析:分类、变形与音便规律一次学透

日语学到动词活用&#xff0c;很多人的心态会从"我好像能看懂日语"瞬间变成"我怎么一个词都不认识了"。五十音图背得滚瓜烂熟&#xff0c;结果课文里同一个动词&#xff0c;一会儿是書かない&#xff0c;一会儿是書いて&#xff0c;一会儿是書けば&#xf…

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

EN1175-2020工业车辆电气安全设计实战指南

简介&#xff1a;本资源为欧洲标准EN 1175:2020《工业卡车的安全——电气/电子要求》中文版全文PDF&#xff0c;面向工业车辆制造商、安全工程师、设备认证人员及特种作业监管从业者&#xff0c;解决工业搬运车辆在电气设计、控制接口、能量连接、EMC防护及合规验证等关键环节的…

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

三种聚类算法在鸢尾花数据集上的对比与调参指南

简介&#xff1a;一套基于鸢尾花数据集的三种聚类算法 Python 代码包&#xff0c;面向机器学习初学者与数据分析人员&#xff0c;用于掌握无监督学习中的 K-Means、合并聚类和 DBSCAN&#xff0c;并通过同一份数据直观对比不同算法的聚类效果。资源既包含三种算法的核心实现代码…

作者头像 李华