在 Docker 中使用 OpenThread 模拟 Thread 网络

1. 简介

26b7f4f6b3ea0700.png

Google 发布的 OpenThreadThread 网络协议的开源实现。Google Nest 发布了 OpenThread,以便开发者广泛使用 Nest 产品中使用的技术,从而加快智能互联家居产品的开发速度。

Thread 规范定义了一种基于 IPv6 的可靠、安全且低功耗的无线设备到设备通信协议,适用于家庭应用。OpenThread 实现所有 Thread 网络层,包括 IPv6、6LoWPAN、具有 MAC 安全性的 IEEE 802.15.4、网状链路建立和网状路由。

此 Codelab 将引导您使用 Docker 在模拟设备上模拟 Thread 网络。

学习内容

  • 如何设置 OpenThread build 工具链
  • 如何模拟 Thread 网络
  • 如何对 Thread 节点进行身份验证
  • 如何使用 OpenThread Daemon 管理 Thread 网络

所需条件

  • Docker
  • Linux、网络路由基础知识

2. 设置 Docker

此 Codelab 旨在在 Linux、Mac OS X 或 Windows 计算机上使用 Docker。建议使用 Linux 环境。

安装 Docker

在您选择的操作系统上安装 Docker。

拉取 Docker 映像

安装 Docker 后,打开终端窗口并拉取 openthread/environment Docker 映像。此映像预构建了 OpenThread 和 OpenThread Daemon,可直接用于本 Codelab。

$ docker pull openthread/environment:latest

请注意,这可能需要几分钟才能完全下载。

在终端窗口中,从映像启动 Docker 容器并连接到其 bash shell:

$ docker run --name codelab_otsim_ctnr -it --rm \
   --sysctl net.ipv6.conf.all.disable_ipv6=0 \
   --cap-add=net_admin openthread/environment bash

--rm 选项会在您退出容器时删除容器。如果您不希望删除容器,请勿使用此选项。

请注意以下标志,这些标志是此 Codelab 所必需的:

  • --sysctl net.ipv6.conf.all.disable_ipv6=0 - 这会在容器内启用 IPv6
  • --cap-add=net_admin - 启用 NET_ADMIN 功能,使您能够执行与网络相关的操作,例如添加 IP 路由

进入容器后,您应该会看到类似如下所示的提示:

root@c0f3912a74ff:/#

在上面的示例中,c0f3912a74ff 是容器 ID。您的 Docker 容器实例的容器 ID 将与此 Codelab 的提示中显示的 ID 不同。

使用 Docker

此 Codelab 假定您了解 Docker 的基本用法。在整个 Codelab 期间,您应始终处于 Docker 容器中。

3. 模拟 Thread 网络

您将在本 Codelab 中使用的示例应用展示了一个最简单的 OpenThread 应用,该应用通过基本的命令行界面 (CLI) 公开 OpenThread 配置和管理接口。

此练习将引导您完成从一个模拟 Thread 设备向另一个模拟 Thread 设备发送 ping 的最低必要步骤。

下图描述了一个基本的 Thread 网络拓扑。在此练习中,我们将模拟绿色圆圈内的两个节点:Thread Leader 和 Thread Router,它们之间只有一条连接。

6e3aa07675f902dc.png

创建网络

1. 启动节点 1

如果您尚未执行此操作,请在终端窗口中启动 Docker 容器并连接到其 bash shell:

$ docker run --name codelab_otsim_ctnr -it --rm \
   --sysctl net.ipv6.conf.all.disable_ipv6=0 \
   --cap-add=net_admin openthread/environment bash

在 Docker 容器中,使用 ot-cli-ftd 二进制文件为模拟的 Thread 设备生成 CLI 进程。

root@c0f3912a74ff:/# /openthread/build/examples/apps/cli/ot-cli-ftd 1

注意:如果您在运行此命令后没有看到 > 提示,请按 enter

此二进制文件实现了 OpenThread 设备。IEEE 802.15.4 无线电驱动程序是在 UDP 之上实现的(IEEE 802.15.4 帧在 UDP 载荷内传递)。

1 的实参是一个文件描述符,表示模拟设备的“出厂分配”IEEE EUI-64 的最低有效位。此值还用于绑定到 UDP 端口以进行 IEEE 802.15.4 无线电仿真(端口 = 9000 + 文件描述符)。在此 Codelab 中,每个模拟的 Thread 设备实例都将使用不同的文件描述符。

注意:在为模拟设备生成进程时,请仅使用文件描述符 1 或更高值,如本 Codelab 中所述。文件描述符 0 已预留给其他用途。

创建新的运营数据集并将其提交为有效数据集。运行数据集是您要创建的 Thread 网络的配置。

> dataset init new
Done
> dataset
Active Timestamp: 1
Channel: 20
Channel Mask: 07fff800
Ext PAN ID: d6263b6d857647da
Mesh Local Prefix: fd61:2344:9a52:ede0/64
Network Key: e4344ca17d1dca2a33f064992f31f786
Network Name: OpenThread-c169
PAN ID: 0xc169
PSKc: ebb4f2f8a68026fc55bcf3d7be3e6fe4
Security Policy: 0, onrcb
Done

将此数据集提交为有效数据集:

> dataset commit active
Done

启动 IPv6 接口:

> ifconfig up
Done

启动 Thread 协议操作:

> thread start
Done

等待几秒钟,然后验证设备是否已成为 Thread Leader。领导者是负责管理路由器 ID 分配的设备。

> state
leader
Done

查看分配给节点 1 的 Thread 接口的 IPv6 地址(您的输出会有所不同):

> ipaddr
fd61:2344:9a52:ede0:0:ff:fe00:fc00
fd61:2344:9a52:ede0:0:ff:fe00:5000
fd61:2344:9a52:ede0:d041:c5ba:a7bc:5ce6
fe80:0:0:0:94da:92ea:1353:4f3b
Done

请注意以下特定 IPv6 地址类型:

  • fd 开头 = 网状本地
  • fe80 开头 = 链路本地

网状本地地址类型进一步细分为:

  • 包含 ff:fe00 = 路由器定位器 (RLOC)
  • 不包含 ff:fe00 = 端点标识符 (EID)

在控制台输出中找到 EID,并记下以供日后使用。在上面的示例输出中,EID 为:

fd61:2344:9a52:ede0:d041:c5ba:a7bc:5ce6

2. 启动节点 2

打开一个新终端,并在当前正在运行的 Docker 容器中执行 bash shell 以供节点 2 使用。

$ docker exec -it codelab_otsim_ctnr bash

在此新的 bash 提示符下,使用实参 2 派生 CLI 进程。这是您的第二个模拟 Thread 设备:

root@c0f3912a74ff:/# /openthread/build/examples/apps/cli/ot-cli-ftd 2

注意:如果您在运行此命令后没有看到 > 提示,请按 enter

配置 Thread 网络密钥和 PAN ID,使用与节点 1 的运行数据集相同的值:

> dataset networkkey e4344ca17d1dca2a33f064992f31f786
Done
> dataset panid 0xc169
Done

将此数据集提交为有效数据集:

> dataset commit active
Done

启动 IPv6 接口:

> ifconfig up
Done

启动 Thread 协议操作:

> thread start
Done

设备将自行初始化为子设备。Thread 子设备相当于终端设备,是一种仅与父设备传输和接收单播流量的 Thread 设备。

> state
child
Done

在 2 分钟内,您应该会看到状态从 child 变为 router。Thread 路由器能够在 Thread 设备之间路由流量。也称为父级。

> state
router
Done

验证网络

验证网状网络是否正常运行的简单方法是查看路由器表。

1. 检查连接情况

在节点 2 上,获取 RLOC16。RLOC16 是设备 RLOC IPv6 地址的最后 16 位。

> rloc16
5800
Done

在节点 1 上,检查节点 2 的 RLOC16 的路由表。确保节点 2 先切换到路由器状态。

> router table
| ID | RLOC16 | Next Hop | Path Cost | LQ In  | LQ Out  | Age | Extended MAC   |
+----+--------+----------+-----------+--------+-------+---+--------------------+
| 20 | 0x5000 |       63 |         0 |      0 |     0 |   0 | 96da92ea13534f3b |
| 22 | 0x5800 |       63 |         0 |      3 |     3 |  23 | 5a4eb647eb6bc66c |

在表中找到了节点 2 的 RLOC 0x5800,确认它已连接到网状网络。

2. 从节点 2 对节点 1 执行 Ping 操作

验证两个模拟的 Thread 设备之间的连接。在节点 2 中,ping 分配给节点 1 的 EID:

> ping fd61:2344:9a52:ede0:d041:c5ba:a7bc:5ce6
> 16 bytes from fd61:2344:9a52:ede0:d041:c5ba:a7bc:5ce6: icmp_seq=1 hlim=64 time=12ms

enter 即可返回到 > CLI 提示。

测试网络

现在,您已可以成功在两个模拟的 Thread 设备之间执行 ping 操作,接下来请通过将一个节点设为离线来测试网状网络。

返回到节点 1 并停止线程:

> thread stop
Done

切换到节点 2 并检查状态。在两分钟内,节点 2 检测到领导者(节点 1)已离线,您应该会看到节点 2 转换为网络的 leader

> state
router
Done
...
> state
leader
Done

确认后,停止 Thread 并将节点 2 恢复出厂设置,然后退出回到 Docker bash 提示符。执行恢复出厂设置是为了确保我们在本练习中使用的 Thread 网络凭据不会沿用到下一个练习中。

> thread stop
Done
> factoryreset
>
> exit
root@c0f3912a74ff:/#

在执行 factoryreset 命令后,您可能需要按几次 enter 才能重新显示 > 提示。请勿退出 Docker 容器。

同时恢复出厂设置并退出节点 1:

> factoryreset
>
> exit
root@c0f3912a74ff:/#

如需探索所有可用的 CLI 命令,请参阅 OpenThread CLI 参考文档

4. 使用调试对节点进行身份验证

在上一个练习中,您设置了一个包含两个模拟设备的 Thread 网络,并验证了连接。不过,这仅允许未经身份验证的 IPv6 链路本地流量在设备之间传递。为了在它们之间(以及通过 Thread 边界路由器与互联网之间)路由全局 IPv6 流量,节点必须经过身份验证。

为了进行身份验证,一个设备必须充当调试器。专员是当前为新 Thread 设备选定的身份验证服务器,也是提供设备加入网络所需的网络凭据的授权者。

在此练习中,我们将使用与之前相同的双节点拓扑。对于身份验证,Thread Leader 将充当调试器,Thread 路由器将充当加入者。

d6a67e8a0d0b5dcb.png

Docker

在剩余的练习中,对于每个节点(终端窗口),请确保您运行的是包含 OpenThread build 的 Docker 容器。如果您要继续完成上一个练习,则应已在同一个 Docker 容器中打开两个 bash 提示符。如果不是,请参阅 Docker 问题排查步骤,或者直接重新完成模拟 Thread 网络练习。

1. 创建网络

在节点 1 中,生成 CLI 进程:

root@c0f3912a74ff:/# /openthread/build/examples/apps/cli/ot-cli-ftd 1

注意:如果您在运行此命令后没有看到 > 提示,请按 enter

创建新的运行数据集,将其提交为有效数据集,然后启动 Thread:

> dataset init new
Done
> dataset
Active Timestamp: 1
Channel: 12
Channel Mask: 07fff800
Ext PAN ID: e68d05794bf13052
Mesh Local Prefix: fd7d:ddf7:877b:8756/64
Network Key: a77fe1d03b0e8028a4e13213de38080e
Network Name: OpenThread-8f37
PAN ID: 0x8f37
PSKc: f9debbc1532487984b17f92cd55b21fc
Security Policy: 0, onrcb
Done

将此数据集提交为有效数据集:

> dataset commit active
Done

启动 IPv6 接口:

> ifconfig up
Done

启动 Thread 协议操作:

> thread start
Done

等待几秒钟,然后验证设备是否已成为 Thread Leader:

> state
leader
Done

2. 开始担任专员角色

在节点 1 上,启动 Commissioner 角色:

> commissioner start
Done

允许任何具有 J01NME 加入者凭据的加入者(通过使用 * 通配符)委托加入网络。加入者是指由人类管理员添加到已调试的 Thread 网络中的设备。

> commissioner joiner add * J01NME
Done

3. 启动加入者角色

在第二个终端窗口中,在 Docker 容器中生成一个新的 CLI 进程。这是节点 2。

root@c0f3912a74ff:/# /openthread/build/examples/apps/cli/ot-cli-ftd 2

在节点 2 上,使用 J01NME 加入者凭据启用加入者角色。

> ifconfig up
Done
> joiner start J01NME
Done

... 等待几秒钟以进行确认 ...

Join success

作为加入者,设备(节点 2)已成功向专员(节点 1)验证自身身份,并已收到 Thread 网络凭据。

现在,节点 2 已通过身份验证,请启动 Thread:

> thread start
Done

4. 验证网络身份验证

检查节点 2 上的 state,以验证其是否已加入网络。在两分钟内,节点 2 从 child 转换到 router

> state
child
Done
...
> state
router
Done

5. 重置配置

为了准备进行下一个练习,请重置配置。在每个节点上,停止 Thread、恢复出厂设置并退出模拟的 Thread 设备:

> thread stop
Done
> factoryreset
>
> exit
root@c0f3912a74ff:/#

在执行 factoryreset 命令后,您可能需要按几次 enter 才能重新显示 > 提示。

5. 使用 OpenThread Daemon 管理网络

在此练习中,我们将模拟一个 CLI 实例(单个嵌入式 SoC Thread 设备)和一个无线电协处理器 (RCP) 实例。

ot-daemon 是 OpenThread Posix 应用的一种模式,它使用 UNIX 套接字作为输入和输出,以便 OpenThread 核心可以作为服务运行。客户端可以通过使用 OpenThread CLI 作为协议连接到套接字来与此服务通信。

ot-ctlot-daemon 提供的一个 CLI,用于管理和配置 RCP。我们将使用此命令将 RCP 连接到 Thread 设备创建的网络。

Docker

对于本练习中的每个节点(终端窗口),请确保您运行的是包含 OpenThread build 的 Docker 容器。如果继续上一个练习,您应该已经打开了同一个 Docker 容器中的两个 bash 提示符。如果未显示,请参阅 Docker 问题排查步骤。

使用 ot-daemon

本练习将使用三个终端窗口,分别对应于以下内容:

  1. 模拟 Thread 设备 (Node 1) 的 CLI 实例
  2. ot-daemon流程
  3. ot-ctl CLI 实例

1. 启动节点 1

在第一个终端窗口中,为模拟的 Thread 设备生成 CLI 进程:

root@c0f3912a74ff:/# /openthread/build/examples/apps/cli/ot-cli-ftd 1

注意:如果您在运行此命令后没有看到 > 提示,请按 enter

创建新的运行数据集,将其提交为有效数据集,然后启动 Thread:

> dataset init new
Done
> dataset
Active Timestamp: 1
Channel: 13
Channel Mask: 07fff800
Ext PAN ID: 97d584bcd493b824
Mesh Local Prefix: fd55:cf34:dea5:7994/64
Network Key: ba6e886c7af50598df1115fa07658a83
Network Name: OpenThread-34e4
PAN ID: 0x34e4
PSKc: 38d6fd32c866927a4dfcc06d79ae1192
Security Policy: 0, onrcb
Done

将此数据集提交为有效数据集:

> dataset commit active
Done

启动 IPv6 接口:

> ifconfig up
Done

启动 Thread 协议操作:

> thread start
Done

查看分配给节点 1 的 Thread 接口的 IPv6 地址:

> ipaddr
fd55:cf34:dea5:7994:0:ff:fe00:fc00
fd55:cf34:dea5:7994:0:ff:fe00:d000
fd55:cf34:dea5:7994:460:872c:e807:c4ab
fe80:0:0:0:9cd8:aab6:482f:4cdc
Done
>

模拟 Thread 网络步骤中所述,一个地址是链路本地地址 (fe80),三个地址是网状本地地址 (fd)。EID 是不包含 ff:fe00 的网状本地地址。在此示例输出中,EID 为 fd55:cf34:dea5:7994:460:872c:e807:c4ab

ipaddr 输出中确定将用于与节点通信的特定 EID。

2. 启动 ot-daemon

在第二个终端窗口中,创建一个 tun 设备节点并设置读/写权限:

root@c0f3912a74ff:/# mkdir -p /dev/net && mknod /dev/net/tun c 10 200
root@c0f3912a74ff:/# chmod 600 /dev/net/tun

此设备用于虚拟设备中的数据包传输和接收。如果设备已创建,您可能会收到错误消息,这是正常现象,可以忽略。

为 RCP 节点(我们称之为节点 2)启动 ot-daemon。使用 -v 详细标志,以便查看日志输出并确认其正在运行:

root@c0f3912a74ff:/# /openthread/build/posix/src/posix/ot-daemon -v \
'spinel+hdlc+forkpty:///openthread/build/examples/apps/ncp/ot-rcp?forkpty-arg=2'

成功时,详细模式下的 ot-daemon 会生成类似于以下内容的输出:

ot-daemon[31]: Running OPENTHREAD/297a880; POSIX; Feb  1 2022 04:43:39
ot-daemon[31]: Thread version: 3
ot-daemon[31]: Thread interface: wpan0
ot-daemon[31]: RCP version: OPENTHREAD/297a880; SIMULATION; Feb  1 2022 04:42:50

让此终端保持打开状态并在后台运行。您不会在其中输入任何其他命令。

3. 使用 ot-ctl 加入网络

我们尚未将节点 2(即 ot-daemon RCP)委托给任何 Thread 网络。这时,ot-ctl 就派上用场了。ot-ctl 使用与 OpenThread CLI 应用相同的 CLI。因此,您可以像控制其他模拟 Thread 设备一样控制 ot-daemon 节点。

打开第三个终端窗口并执行现有容器:

$ docker exec -it codelab_otsim_ctnr bash

进入容器后,启动 ot-ctl

root@c0f3912a74ff:/# /openthread/build/posix/src/posix/ot-ctl
>

您将在第三个终端窗口中使用 ot-ctl 来管理在第二个终端窗口中使用 ot-daemon 启动的节点 2(RCP 节点)。检查节点 2 的 state

> state
disabled
Done

获取节点 2 的 eui64,以限制加入到特定加入者:

> eui64
18b4300000000001
Done

在节点 1(第一个终端窗口)上,启动 Commissioner 并限制仅允许该 eui64 加入:

> commissioner start
Done
> commissioner joiner add 18b4300000000001 J01NME
Done

在第三个终端窗口中,启动节点 2 的网络接口并加入网络:

> ifconfig up
Done
> joiner start J01NME
Done

... 等待几秒钟以进行确认 ...

Join success

作为加入者,RCP(节点 2)已成功向专员(节点 1)验证自身身份,并收到了 Thread 网络凭据。

现在,将节点 2 加入到 Thread 网络(同样在第三个终端窗口中):

> thread start
Done

4. 验证网络身份验证

在第三个终端中,检查节点 2 上的 state,以验证该节点是否已加入网络。在两分钟内,节点 2 从 child 转换到 router

> state
child
Done
...
> state
router
Done

5. 验证连接

在第三个终端窗口中,使用 Ctrl+Dexit 命令退出 ot-ctl,然后返回到容器的 bash 控制台。在此控制台中,使用 ping6 命令通过节点 1 的 EID 来 ping 节点 1。如果 ot-daemon RCP 实例已成功加入 Thread 网络并与该网络通信,则 ping 会成功:

root@c0f3912a74ff:/# ping6 -c 4 fd55:cf34:dea5:7994:460:872c:e807:c4ab
PING fd55:cf34:dea5:7994:460:872c:e807:c4ab (fd55:cf34:dea5:7994:460:872c:e807:c4ab): 56 data bytes
64 bytes from fd55:cf34:dea5:7994:460:872c:e807:c4ab: icmp_seq=0 ttl=64 time=4.568 ms
64 bytes from fd55:cf34:dea5:7994:460:872c:e807:c4ab: icmp_seq=1 ttl=64 time=6.396 ms
64 bytes from fd55:cf34:dea5:7994:460:872c:e807:c4ab: icmp_seq=2 ttl=64 time=7.594 ms
64 bytes from fd55:cf34:dea5:7994:460:872c:e807:c4ab: icmp_seq=3 ttl=64 time=5.461 ms
--- fd55:cf34:dea5:7994:460:872c:e807:c4ab ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 4.568/6.005/7.594/1.122 ms

6. Docker 问题排查

如果您已退出 Docker 容器

bash 提示,您可能需要检查该服务是否正在运行,并根据需要重启 / 重新输入。您创建的未使用 --rm 选项的任何 Docker 容器都应仍然存在。

如需显示哪些 Docker 容器正在运行,请执行以下操作:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
505fc57ffc72        environment       "bash"              10 minutes ago      Up 10 minutes                           codelab_otsim_ctnr

如需显示所有 Docker 容器(包括正在运行和已停止的容器),请运行以下命令:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
505fc57ffc72        environment       "bash"              10 minutes ago      Up 10 minutes                           codelab_otsim_ctnr

如果您在任一 docker ps 命令的输出中未看到容器 codelab_otsim_ctnr,请再次运行该命令:

$ docker run --name codelab_otsim_ctnr -it --rm \
   --sysctl net.ipv6.conf.all.disable_ipv6=0 \
   --cap-add=net_admin openthread/environment bash

只有在您希望容器在退出时被删除的情况下,才使用 --rm 选项。

如果容器已停止(列在 docker ps -a 中,但未列在 docker ps 中),请重启该容器:

$ docker start -i codelab_otsim_ctnr

如果 Docker 容器已在运行(列在 docker ps 中),请在每个终端中重新连接到该容器:

$ docker exec -it codelab_otsim_ctnr bash

“不允许执行操作”错误

如果您在创建新的 OpenThread 节点(使用 mknod 命令)时遇到 Operation not permitted 错误,请确保您按照本 Codelab 中提供的命令以根用户身份运行 Docker。此 Codelab 不支持以无根模式运行 Docker。

7. 恭喜!

您已使用 OpenThread 成功模拟了第一个 Thread 网络。也很棒!

在此 Codelab 中,你学习了如何:

  • 启动和管理 OpenThread 模拟 Docker 容器
  • 模拟 Thread 网络
  • 对 Thread 节点进行身份验证
  • 使用 OpenThread 守护程序管理 Thread 网络

如需详细了解 Thread 和 OpenThread,请参阅以下参考资料:

或者,尝试在 Docker 容器中使用 OpenThread 边界路由器