CoAP 本地控制可靠性:发现、会话与重启恢复
CoAP 本地控制可靠性:发现、会话与重启恢复
摘要
本文链路中的 CoAP 运行在 UDP 之上,常见失败表象是:控制端已经发出请求,设备却没有回复。根因未必在网络,也可能是双方对设备身份、Session 或消息序列的理解已经不一致。
本文以 OpenHarmony iot_management 的本地控制链路为背景,梳理双端口发现、设备信息合并、baseUrl、PUUID、Session、sequence 与重启恢复之间的关系,并给出一套从报文到业务状态的排障方法。
关键字
CoAP、设备发现、5683、5686、PUUID、Session、sequence、状态一致性
1. 可靠性不是“能发包”
本工程采用 CoAP over UDP,不会替应用维护“对端是否仍保存旧会话”这类业务状态。本地控制的可靠性也不只是重传,而是网络、设备身份和安全会话三类状态同时收敛:

任何一层不一致,应用都可能看到“请求已发送但没有回复”。因此必须同时确认报文到达、设备校验条件、响应去向和业务状态变化。
2. 本文工程中的 5683 与 5686:两类协议阶段
本文工程将首次接入与认证后控制分在两个端口上:
| 端口 | 主要职责 | 典型能力 |
|---|---|---|
| 5683 | 接入与配置平面 | CloudSetup 发现、PIN/SPEKE、CloudSetup、主动事件 |
| 5686 | 认证后控制平面 | 本地控制发现、Session 管理、加密控制 |
5683 让“已联网但未身份化”的设备具备安全接入条件;5686 服务于已具备身份材料后的本地控制。端口不同不表示两台设备,而是同一设备在不同协议阶段暴露不同能力。
RFC 7252 定义 UDP 5683 为 CoAP 默认端口;5686、上述 URI 及能力分工均是本文工程的协议约定,并非 CoAP 的通用端口语义。RFC 7252 对 IP 组播请求要求使用 NON 消息;IPv4 广播是否可用、设备是否响应,则还受网络与工程实现约束。

这里有一个常见误解:在本文工程的发现实现中,控制端的扫描请求可以发往广播或组播地址,设备响应会按请求源地址和临时 UDP 源端口单播返回。RFC 7252 也规定,服务器若响应组播请求,应使用单播响应。抓包若只过滤 udp.dstport == 5683 || udp.dstport == 5686,往往只能看到请求,遗漏返回到临时端口的响应。
3. 发现结果:合并而非叠加
5683 和 5686 的响应到达顺序与字段完整度都可能不同:前者常提供网络地址和基础信息,后者补齐 devId、authCodeId 与 sessId。控制端应把它们归并为同一逻辑设备,而不是把每条响应都展示成新设备。
推荐的匹配优先级如下:
物理 UDID
↓
MAC + SN / prodId + SN
↓
CoAP devId
↓
baseUrl(最后兜底,不能作为长期唯一身份)

合并的核心原则是“非空补充,不用空字段覆盖有效字段”。否则,较晚到达但信息不完整的发现响应,可能把已经获得的 authCodeId 或 devId 清空,导致设备看似在线却无法继续连接。
4. 地址、PUUID 与请求关联
本节的 baseUrl、PUUID、CloudSetup、/.sys/sessMngr 与 e2eCtrl 都是本文工程定义的字段或资源,不属于 RFC 7252 规定的标准资源或 CoAP Option。
4.1 baseUrl 只表示设备主机地址
发现结果中的 baseUrl 应是设备可达地址,例如 10.194.20.68,不应混入 scheme、端口或 URI。客户端根据业务统一构造目标:
SPEKE coap://<baseUrl>:5683/SPEKE/v2
CloudSetup coap://<baseUrl>:5683/CloudSetup/v2
Session coap://<baseUrl>:5686/.sys/sessMngr
控制命令 coap://<baseUrl>:5686/e2eCtrl
设备端 IP 字节序处理错误时,发现响应仍可能到达控制端,但后续请求会被发往错误地址。因此,baseUrl 必须同时通过日志、抓包和实际网段核对。
4.2 PUUID 标识控制端,不等同于密钥
PUUID 是控制端在本地控制协议中使用的稳定标识,通常作为自定义 CoAP Option 携带。它的目标是让设备区分控制端实例,而不是代替 authCode 或会话密钥。
PUUID 应具备以下特性:
- 首次生成后在本地稳定保存;
- 不依赖某次 DHCP 地址或单台设备信息;
- 不以完整明文输出到普通日志;
- 生成或读取失败时明确报错,而不是发送空 Option。
若设备端要求 PUUID 非空,控制端遗漏该 Option 时,设备可能主动丢弃请求。此时“抓包没有设备回复”不等于广播没有到达设备,而是协议校验在设备端提前结束。
4.3 Token 关联请求与响应
CoAP 的 Token 用于把响应分派回对应请求;Message ID 用于确认和重复检测;二者都不能替代业务 Session ID。Message ID 也不能代替 Token 做通用请求—响应关联,尤其在分离响应场景中两者承担的职责不同。
| 字段 | 解决的问题 |
|---|---|
| Token | 响应属于哪一次客户端请求 |
| Message ID | CoAP 消息确认与重复检测 |
| sessId | 当前业务安全会话是谁 |
| sequence | 当前安全会话中的消息顺序 |
因此,设备响应 Token 不匹配时,客户端即使收到了 UDP 包,也可能无法命中原请求回调,最终表现为超时。
5. Session:先验证,再复用
身份材料校验通过后,控制端通过 /.sys/sessMngr 创建本地控制 Session。拿到 sessId 只是开始;响应字段、密钥派生输入、sequence 和加密模式均通过校验后,Session 才可使用。

以下情况不应继续使用该记录:
- sessId、认证材料、密钥输入或 sequence 不完整;
- 本地记录过期,或本地时钟异常;
- 5686 发现返回的 sessId 为空;
- 5686 发现返回的 sessId 与本地不同;
- 安全管理器无法初始化加密/解密上下文。
可复用的 Session 必须同时存在于“本地持久化记录”和“设备当前运行状态”中。本地 KV 有旧记录,不能证明设备重启后仍持有同一个 Session。
6. sequence:允许跳号,禁止回退
sessId 解决“使用哪个会话”,sequence 解决“会话内第几条消息”。在本文工程中,它用于抵抗重复和重放,基本约束是同一 Session 内单调递增;其字段格式、持久化顺序和是否允许跳号均是本地控制协议约定,不是 CoAP 标准字段。
读取当前 sequence
↓
分配下一个 sequence
↓
构造加密报文与 CoAP Option
↓
持久化已分配的值
↓
提交网络发送
若先发送、后持久化,进程在发送后崩溃可能导致重启后重复使用旧 sequence;若已持久化但发送失败,sequence 会出现跳号。对多数单调递增校验而言,跳号通常可接受,回退才会带来重放风险。
Session ID 也要注意编码边界:日志或 JSON 中的 32 个十六进制字符,若协议 Option 定义为 16 字节二进制值,写入 Option 前必须先解码。把文本直接当作 32 字节 option 发送,会造成长度与内容不匹配。
7. 设备重启:让状态重新收敛
设备端通常只在内存中保存运行时 Session,而控制端可能把 Session 写入 KV。设备重启后会出现典型的状态分裂:
控制端:仍保存旧 Session
设备端:运行时 Session 已丢失
正确策略不是让设备“接受未知 Session”,而是让控制端依据新的发现结果失效旧记录并重新建会话:

控制命令超时或设备明确拒绝时,也应标记本地 Session 失效并提示上层重新连接;业务命令不宜无条件自动重放,避免设备已执行而响应丢失时重复执行。
8. 主动事件:绑定真实接口
普通控制响应回到控制端临时 UDP 端口;设备主动事件则需要知道控制端当前可达的本地地址。在本文工程中,主动事件使用控制端实际接口 IP 的 5683 监听端口。
控制响应:设备 5686 → 控制端临时 UDP 端口
主动事件:设备 → 控制端实际 IP:5683
监听 0.0.0.0 虽然方便,但会把多个接口的数据混在一起,也无法明确告诉设备事件应该发回哪个地址。更稳妥的做法是绑定当前 WLAN 或 SoftAP 的实际 IP;当网络切换、DHCP 地址变化或 Wi-Fi 重连时,停止旧监听并重新绑定。
9. 从网络到业务的排障
| 层级 | 典型现象 | 优先检查 |
|---|---|---|
| 网络层 | 请求发出但无任何响应 | IP、路由、AP 组播策略、设备端口 |
| 发现层 | 只有基础信息或身份字段为空 | 5686 响应、PUUID、临时响应端口 |
| 合并层 | 同一设备显示多条记录 | UDID、MAC/SN、devId 合并条件 |
| 身份层 | authCodeId 为空 | CloudSetup 是否成功、身份是否持久化 |
| Session 层 | 重启后无法控制 | 远端 sessId、本地 KV、重建分支 |
| 加密层 | 设备收到但拒绝/解密失败 | Session ID 编码、派生参数、sequence |
| 业务层 | CoAP 成功但状态未变 | serviceName、payload、设备业务处理 |
抓包时优先按设备 IP 观察双向流量:
ip.addr == <device-ip>
仅用于确认发现请求时,可补充:
udp.dstport == 5683 || udp.dstport == 5686
第二条过滤条件只覆盖“发往设备服务端口”的方向,不能替代按设备 IP 的双向观察。
参考
更多推荐
所有评论(0)