CoAP 本地控制可靠性:发现、会话与重启恢复

摘要

本文链路中的 CoAP 运行在 UDP 之上,常见失败表象是:控制端已经发出请求,设备却没有回复。根因未必在网络,也可能是双方对设备身份、Session 或消息序列的理解已经不一致。

本文以 OpenHarmony iot_management 的本地控制链路为背景,梳理双端口发现、设备信息合并、baseUrl、PUUID、Session、sequence 与重启恢复之间的关系,并给出一套从报文到业务状态的排障方法。

关键字

CoAP、设备发现、5683、5686、PUUID、Session、sequence、状态一致性

1. 可靠性不是“能发包”

本工程采用 CoAP over UDP,不会替应用维护“对端是否仍保存旧会话”这类业务状态。本地控制的可靠性也不只是重传,而是网络、设备身份和安全会话三类状态同时收敛:

img

任何一层不一致,应用都可能看到“请求已发送但没有回复”。因此必须同时确认报文到达、设备校验条件、响应去向和业务状态变化。

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 广播是否可用、设备是否响应,则还受网络与工程实现约束。

img

这里有一个常见误解:在本文工程的发现实现中,控制端的扫描请求可以发往广播或组播地址,设备响应会按请求源地址和临时 UDP 源端口单播返回。RFC 7252 也规定,服务器若响应组播请求,应使用单播响应。抓包若只过滤 udp.dstport == 5683 || udp.dstport == 5686,往往只能看到请求,遗漏返回到临时端口的响应。

3. 发现结果:合并而非叠加

5683 和 5686 的响应到达顺序与字段完整度都可能不同:前者常提供网络地址和基础信息,后者补齐 devId、authCodeId 与 sessId。控制端应把它们归并为同一逻辑设备,而不是把每条响应都展示成新设备。

推荐的匹配优先级如下:

物理 UDID
  ↓
MAC + SN / prodId + SNCoAP devId
  ↓
baseUrl(最后兜底,不能作为长期唯一身份)

img

合并的核心原则是“非空补充,不用空字段覆盖有效字段”。否则,较晚到达但信息不完整的发现响应,可能把已经获得的 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 才可使用。

img

以下情况不应继续使用该记录:

  1. sessId、认证材料、密钥输入或 sequence 不完整;
  2. 本地记录过期,或本地时钟异常;
  3. 5686 发现返回的 sessId 为空;
  4. 5686 发现返回的 sessId 与本地不同;
  5. 安全管理器无法初始化加密/解密上下文。

可复用的 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”,而是让控制端依据新的发现结果失效旧记录并重新建会话:

img

控制命令超时或设备明确拒绝时,也应标记本地 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 的双向观察。

参考

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐