ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

RIOT 上的 NASA bplib DTN 实战:基于 UDP Convergence Layer 的 BPv7 示例深入解析

2026/9/20 21:33:13 拓冰建站 浏览量
RIOT 上的 NASA bplib DTN 实战:基于 UDP Convergence Layer 的 BPv7 示例深入解析 RIOT 上的 NASA bplib DTN 实战基于 UDP Convergence Layer 的 BPv7 示例深入解析【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT导读本篇文章以 RIOT 操作系统中的bplib_cla_udp示例为核心系统讲解如何在 RIOT 上借助 NASA 的 bplib 库实现延迟/中断容忍网络DTNDelay/Disruption Tolerant Networking的 Bundle Protocol v7BPv7。你将掌握 DTN 的核心理念存储转发、Convergence Layer 抽象、该示例的编译配置方法、基于 UDP 的 CLA 底层实现以及如何在 native 目标上用 tap 设备搭建 13 个 BP 节点的通信与中断恢复实验。本文所有实现细节均以当前仓库源码与配置为事实依据可对照 示例目录 中的文件逐一验证。一、什么是 DTN 与 Bundle Protocol延迟/中断容忍网络DTN是一类面向高通信延迟、频繁连接中断场景的网络架构其起源是航天通信领域——深空链路具有高延迟、可预测但不可靠的连接特征。DTN 的核心协议称为Bundle Protocol束协议bplib 实现的是其BPv7版本。与 TCP/IP 的端到端假设不同DTN不要求通信双方之间存在持续的端到端连接而是依赖存储转发store-and-forward消息以Bundle束为单位在节点间逐跳传递每个节点需要有持久化存储用于暂存当前无法转发没有下一跳的 Bundle当下一跳重新可用时Bundle 会从存储中被取出并继续转发。在 RIOT 仓库中bplib 的 RIOT 移植版本由 pkg/bplib 包提供。根据 pkg/bplib/doc.md 的说明该移植bplib 7.0.2目前不支持以下 BPv7 特性分片Fragmentation、状态报告生成、BIBE 扩展也因此不支持 Custody Transfer、BPSec 扩展并且 EID Scheme 仅支持ipn不支持dtn。二、Convergence Layer 与 CLA 的角色Bundle 协议本身不规定消息如何在物理链路上传输而是通过Convergence Layer汇聚层CL抽象来承载The messages of the protocol (bundles) are sent over some underlying protocol, called aConvergence Layer(CL). The object that translates between the underlying protocol and the bundle processor is called aConvergence Layer Adapter(CLA).CL承载 Bundle 传输的底层协议CLA在底层协议与 Bundle 处理器之间完成转换的对象。本示例使用的是UDPCLBundle 被直接作为 UDP 载荷发送legacy UDPCL 风格参见 pkg/bplib/cla/doc.md 中的说明。RIOT 移植目前实现了两种 CLACLA 模块底层协议说明bplib_cla_udpUDPgnrc_sock_udp将 Bundle 直接作为 UDP 载荷发送即本示例所用bplib_cla_bleBLE L2CAP CoC通过 L2CAP 连接发送 Bundle见 BLE 示例说明示例 README 中链接的 BLE 示例相对路径已转换为仓库根路径 examples/networking/dtn/bplib_cla_ble/README.md。三、示例的编译配置三个核心 Makefile 参数本示例的配置全部由 Makefile 处理它解析三个编译期变量并转换为 C 宏Makefile 变量对应宏默认值作用REMOTEBPLIB_EXAMPLE_IP_REMOTE::1设置发送 Bundle 所用的底层 IPv6 远端地址REMOTE_EID_NODEBPLIB_EXAMPLE_REMOTE_NODE_NO100设置目的端 EID 的节点号node numberLOCAL_EID_NODEBPLIB_LOCAL_EID_NODE_NUM100设置当前设备的节点号如果变量未设置Makefile 会发出$(warning ...)提示并回退到默认值。例如未设置REMOTE时提示REMOTE IPv6 address not set, defaulting to [::1]此外示例中 EID 的服务号service number是静态选择的UDP 端口同样固定。在 main.c 中可以确认#define BPLIB_EXAMPLE_PORT 4556 #define BPLIB_EXAMPLE_REMOTE_SERVICE_NO 123其中 UDP 端口 4556 对应 IANA 为 BP/UDPRFC 9171分配的端口号符合标准约定。3.1 与 CLA 相关的编译期常量Makefile 中还定义了与 bplib 运行相关的关键常量CFLAGS -DBPLIB_MAX_NUM_CONTACTS1 # 联系人contact表大小 CFLAGS -DBPLIB_MAX_NUM_CHANNELS1 # 通道channel表大小 CFLAGS -DCONFIG_BPLIB_STOR_BASE\/nvm0/bp\ # Bundle 存储路径前缀 CFLAGS -DBPLIB_TIME_FILE_NAME\/nvm0/bp_time.dat\ # DTN 时间信息文件 CFLAGS -DFS_NATIVE_DIR\$(CURDIR)/native$(LOCAL_EID_NODE)\ # native 上文件系统挂载目录BPLIB_MAX_NUM_CONTACTS1本示例只有一个联系人远端节点。如果要实现中间节点向两个不同下一跳转发需要将其增大详见三节点场景一节。BPLIB_MAX_NUM_CHANNELS1本示例仅向一个 EID 发送、在一个服务号上接收 Bundle。CONFIG_BPLIB_STOR_BASE存储实现保存 Bundle 的根路径不同存储实现可能在其下创建子目录如node_id/service_id/。BPLIB_TIME_FILE_NAMEbplib 的 TIME 模块用它记录跨启动周期的 DTN 时间参考。若不需要时间追踪可用bplib_no_vfs模块关闭见 pkg/bplib/doc.md 的模块表。FS_NATIVE_DIR在 native 目标上将 VFS 文件系统挂载到示例目录下的native$(LOCAL_EID_NODE)子目录从而按本地节点号隔离存储——这是三节点模拟的关键前提见后文。3.2 启用的模块Makefile 中通过USEPKG/USEMODULE声明依赖USEPKG bplib USEMODULE bplib_stor_vfs_ordered # 有序/分层存储 USEMODULE bplib_cla_udp # UDPCL USEMODULE shell USEMODULE shell_cmd_bplib # bplib shell 命令 USEMODULE shell_cmd_ps USEMODULE shell_cmd_gnrc_netif USEMODULE netdev_default USEMODULE auto_init_gnrc_netif USEMODULE gnrc_ipv6_default USEMODULE gnrc_sock_udp USEMODULE vfs_default其中bplib_stor_vfs_ordered是有序存储实现Bundle 按紧急程度urgency排序后出队这要求 DTN 时间已知vfs 文件系统依赖。UDP CLA 依赖gnrc_sock_udp。四、源码视角Channel 与 Contact 的初始化示例的初始化逻辑全部位于 main.c 的_config_nc()函数中它演示了 bplib 的两大配置对象4.1 Channel通道配置通道定义了向哪个目的 EID 发送、携带哪些块、用什么 CRC等策略。示例将通道 0 配置为BPLib_EID_t dest { .Scheme BPLIB_EID_SCHEME_IPN, .IpnSspFormat BPLIB_EID_IPN_SSP_FORMAT_TWO_DIGIT, .Allocator 0, .Node BPLIB_EXAMPLE_REMOTE_NODE_NO, // 由 REMOTE_EID_NODE 决定 .Service BPLIB_EXAMPLE_REMOTE_SERVICE_NO // 123 }; bplib_channel_set_crc_type(0, BPLib_CRC_Type_CRC16); bplib_channel_set_service_no(0, BPLIB_EXAMPLE_REMOTE_SERVICE_NO); bplib_channel_set_lifetime(0, 3600000); // Bundle 生存期 1 小时ms bplib_channel_set_dest_eid(0, dest);同时为 Bundle 添加了多个扩展块并配置其序号与 CRCBUNDLE_AGE 块块序号 2CRC16HOP_COUNT 块跳数限制块块序号 3无 CRChop limit 为 10——这正是三节点实验中 Bundle 来回转发后在第 10 跳被丢弃的原因PREVIOUS_NODE 块块序号 4无 CRC。上述块可以通过bplib_channel_set_block_include(0, block, false)移除见 main.c 注释。4.2 Contact联系人配置联系人定义了把数据发给谁、从哪收。示例将 contact 0 配置为覆盖节点号 110000、服务号 110000 的 EID 模式并把出/入地址绑定到 UDP 端口 4556BPLib_EID_Pattern_t reachable_eids { .Scheme BPLIB_EID_SCHEME_IPN, .IpnSspFormat ..., .MaxNode 10000, .MinNode 1, .MaxService 10000, .MinService 1 }; bplib_contact_set_destinations(0, 0, reachable_eids); bplib_contact_set_out_addr(0, BPLIB_EXAMPLE_IP_REMOTE, BPLIB_EXAMPLE_PORT); bplib_contact_set_in_addr(0, BPLIB_EXAMPLE_IP_LOCAL, BPLIB_EXAMPLE_PORT);注意由于该 contact 覆盖了节点 110000 的所有EID两台设备之间会转发一切不归属本地的 Bundle——这就是三节点实验中消息在 A、B 之间来回传输的根源。4.3 启动顺序main()的启动顺序也很有讲究main.cbplib_init()初始化 bplib 实例_config_nc()完成 NCNode Config配置bplib_cla_udp_start(cla_udp1, 0)启动 UDP CLA必须先于 bplib 侧的 contact 启动BPLib_PI_AddApplication(0)/BPLib_PI_StartApplication(0)注册并启动应用层 I/OBPLib_CLA_ContactSetup(0)/BPLib_CLA_ContactStart(0)通知 bplib 该 contact 已就绪创建bplib APP IN线程通过BPLib_PI_Egress()轮询消费到达的 Bundle 并打印内容进入 shell 循环。此外源码注释提醒正式产品中应在退出前调用BPLib_CLA_ContactTeardown与BPLib_PI_RemoveApplication以便把尚未发送的排队 Bundle 推回存储main.c。五、UDP CLA 的底层实现UDP CLA 的实现位于 pkg/bplib/cla/udp/bplib_cla_udp.c其头文件在 pkg/bplib/cla/udp/include/bplib_cla_udp.h。从源码结构看一个 CLA 实例内部包含两个线程egress 线程cla_udp_out命名bplib-cla-udp-rx循环调用BPLib_CLA_Egress()从 bplib 取待发送 Bundle再通过sock_udp_send()发给远端。对-EHOSTUNREACH错误做了容忍处理bplib_cla_udp.c。ingress 线程cla_udp_in命名bplib-cla-udp-tx循环调用sock_udp_recv()收 UDP 报文再调用BPLib_CLA_Ingress()送入 bplibbplib_cla_udp.c。启动时bplib_cla_udp_start()会从 NC 表中读取ClaOutAddr/ClaOutPort与ClaInAddr/ClaInPort解析为 IPv6 地址后用sock_udp_create()建立 socket然后创建上述两个线程。线程优先级为THREAD_PRIORITY_MAIN - 2栈大小等于THREAD_STACKSIZE_MEDIUM CONFIG_BPLIB_CLA_UDP_BUFLEN各含一个收发缓冲。重要行为提示UDP CLA只接收来自已配置远端地址的报文来自其他 IP 的 UDP 数据会被静默忽略见 bplib_cla_udp.h 的note以及 bplib_cla_udp.c 中对-EPROTO的容错分支。5.1 UDP CLA 相关配置项定义在头文件中的两个可配置宏bplib_cla_udp.h宏默认值说明CONFIG_BPLIB_CLA_UDP_BUFLEN1024发送与接收缓冲各自独立的大小即 UDP 上的 MTU一般不应超过约 1400CONFIG_BPLIB_CLA_UDP_TIMEOUT10000socket 轮询与 bplib egress 的阻塞超时ms调用bplib_cla_udp_stop()后线程最多经过该超时才会退出六、实验场景一Loopback 回环测试最简单的测试无需任何配置修改——所有标志默认即回环make all term前提是系统存在可用的 tap 设备示例启动时会尝试绑定一个。此时本地节点号与目的节点号均为默认值 100且REMOTE默认为::1使用 shell 命令bplib send 0 [DATA]发送数据时当前节点就是目的节点数据走 bplib 内部投递路径完全不会经过 gnrc/UDPREADME.md 中明确说明。这里的0是channel 编号示例只使用一个通道编号从 0 开始。七、实验场景二两台设备A ↔ B7.1 创建两个 tap 设备并启动按 gnrc 示例的方法创建两个 tap 设备如tap0、tap1分别启动两个实例make all term PORTtap0 make all term PORTtap1随后在两侧 shell 中运行ifconfig查看各自虚拟 tap 设备的 IPv6 地址。假设设备 A 本地址为fe80::4832:95ff:feb7:5161设备 B 本地址为fe80::d8af:c5ff:febc:a4097.2 重新编译指定远端地址拿到地址后重新编译将各自的REMOTE指向对方并给两台设备不同的节点号# 设备 A本地 fe80::4832:95ff:feb7:5161 make all term PORTtap0 REMOTEfe80::d8af:c5ff:febc:a409 LOCAL_EID_NODE100 REMOTE_EID_NODE200 # 设备 B本地 fe80::d8af:c5ff:febc:a409 make all term PORTtap1 REMOTEfe80::4832:95ff:feb7:5161 LOCAL_EID_NODE200 REMOTE_EID_NODE100此时运行bplib send 0 [DATA]Bundle 应投递到另一实例。数据路径为bplib → gnrcUDP 载荷→ 对端 → bplib对端能投递成功是因为其本地节点号正是目的节点号且通道处于激活状态。7.3 中断模拟Disruption SimulationDTN 的价值在于应对中断本示例可以完整体验这一过程在任一节点执行bplib contact 0 stop将 contact 0 置为stopped状态用bplib contact 0查看当前状态示例正常启动时会置为 started处于 stopped 状态时bplib 认为该 contact 当前无法服务其目的节点此时再bplib send 0 [DATA]Bundle不会到达对端而是被放入storage——在 native 目标上即本示例目录下的native/子目录对应FS_NATIVE_DIR的默认节点号 100中断结束后执行bplib contact 0 start重新启用 contact经过一定超时后Bundle 会从存储中被取出并最终送达设备 B。为什么中断检测很难自动化README 明确指出DTN 起源于航天通信那里的 contact 可由静态、可预测的轨道计算得出而 IoT 设备一般不具备这种可预测性因此可靠的中断检测不幸地并不容易。八、实验场景三三个 BP 节点A.1 ↔ B ↔ A.2真正的三设备转发需要更大的代码改动但用两台设备模拟三个 BP 节点只需重新配置。核心技巧是利用FS_NATIVE_DIR按LOCAL_EID_NODE隔离存储以及修改LOCAL_EID_NODE让一台设备在 BP 语义下扮演不同节点。8.1 场景设定设备 A 想发给一个既非 A 也非 B 的目的节点节点号 300# 设备 A注意远端节点号变了 make all term PORTtap0 REMOTEfe80::d8af:c5ff:febc:a409 LOCAL_EID_NODE100 REMOTE_EID_NODE300 # 设备 B make all term PORTtap1 REMOTEfe80::4832:95ff:feb7:5161 LOCAL_EID_NODE200 REMOTE_EID_NODE100A 发送消息后B不会投递它B 的本地节点不是 300但该消息会在两台设备之间被反复转发。原因是两个节点的 contact 都覆盖节点号 110000 的全部 EID见 main.c因此双方都认为所有 110000 的 Bundle 应发给对方。用 Wireshark 可以观察到这种来回转发。转发最终会停止是因为 Bundle 携带了 Hop Count 块跳数达到上限 10 后 Bundle 被删除对应 main.c 中bplib_channel_set_hop_limit(0, 10)。8.2 模拟第三个节点在设备 B 上执行bplib contact 0 stop从 A 发送一个新 Bundle——此时 Wireshark 中只有一个Bundle 被发出且它被存入 B 的存储native200/目录修改设备 A使其看起来像第三个节点本地节点号改为此前目的节点号 300# 设备 A远端节点号再次改变 make all term PORTtap0 REMOTEfe80::d8af:c5ff:febc:a409 LOCAL_EID_NODE300 REMOTE_EID_NODE100在 B 上重新启用 contactbplib contact 0 start此刻 BP 语义下的节点 A节点号 300会收到从 B 存储中取出的 Bundle。这个实验完整演示了 DTN 的核心思想无需直接端到端路径的存储转发传输。8.3 不做重配置、直接在三节点间转发的前提README 明确列出若要在不重新配置的情况下让中间节点转发必须存在另一个 IP 设备此处是另一个 tap 设备中间设备必须有两个 contact即BPLIB_MAX_NUM_CONTACTS 2必须初始化两个分别指向各自下一跳的 UDP CLA。由于本示例 Makefile 将BPLIB_MAX_NUM_CONTACTS固定为 1这正是需要较大改动的原因所在。九、bplib shell 命令速查示例通过USEMODULE shell_cmd_bplib引入bplibshell 命令详见 sys/shell/cmds/bplib.doc.md。其子命令如下子命令说明bplib send channel PAYLOAD将给定字符串载荷作为 Bundle 从指定 channel 注入bplib channel channel [new_state]不带new_state时打印通道状态否则尝试迁移到setup/start/stop/teardown之一bplib contact contact [new_state]不带new_state时打印联系人状态否则尝试迁移到add/start/stop/remove之一参数约束channel取值在[0, BPLIB_MAX_NUM_CHANNELS)contact取值在[0, BPLIB_MAX_NUM_CONTACTS)即本示例中二者都只能是0。该 shell 命令文档还强调它只是现有实现的辅助工具仅有 shell 命令而无 CLA 初始化与 Bundle 消费逻辑的应用无法独立工作。此外NC 的完整配置如块包含策略、CRC 类型、contact 远端地址等当前仍是编译期常量未来计划迁移为可运行时配置见 bplib.doc.md 的 Future efforts 一节。十、bplib 在 RIOT 中的模块与配置全景本示例只触及 bplib 能力的一部分。综合 pkg/bplib/doc.md 的模块清单按需可选的模块包括存储实现bplib_stor_vfs_ordered按紧急程度有序出队本示例所用、bplib_stor_vfs_unordered按vfs_readdir顺序取回速度更快但适合 contact/channel 少的场景、bplib_stor_void丢弃无法立即投递的 Bundle仅适合叶节点与测试。优化模块bplib_include_nc_telemetry默认裁剪遥测表省 3.3 KB、bplib_include_as默认裁剪管理统计表省 7.5 KB、bplib_no_vfs配合bplib_stor_void可完全去掉 vfs 依赖。初始化控制bplib_barebones可关闭默认启用的bplib_init/bplib_nc/bplib_fwp三个模块便于自行替换。常用配置宏均定义于 pkg/bplib/doc.md宏说明CONFIG_BPLIB_CLA_UDP_BUFLENUDP CLA 的 MTU建议不超过约 1400CONFIG_BPLIB_CLA_UDP_TIMEOUTUDP CLA stop 后线程终止的超时msCONFIG_BPLIB_MEMPOOL_LENBundle 发送/投递队列的 mempool 大小越大可排队 Bundle 越多mempool 满时新入站 Bundle 会被丢弃每个 Bundle 至少约 700 字节的BPLib_BBlocks_t加上载荷块CONFIG_BPLIB_STOR_BASEBundle 存储路径前缀默认/nvm0/bpCONFIG_BPLIB_EGRESS_CACHE_LEN每个 channel/contact 的缓存队列中的 Bundle 引用数越大越少搜存储但更耗内存CONFIG_BPLIB_TIME_FILE_NAME时间信息文件路径注意不要放在存储实现会遍历的子目录里否则可能被误认为 Bundle十一、运行环境与限制推荐目标本示例在native目标上配合 gnrc UDP 验证通过也可尝试用 802.15.4 或nimble_netif承载 UDP以在真实硬件上测试。BLE 直连场景请改用 BLE 示例。tap 设备即使只做 loopback也需要可用的 tap 设备因为 CLA 会尝试绑定一个。BPv7 能力边界如前所述RIOT 移植的 bplib 7.0.2 不支持分片、状态报告、BIBE/BPSec且 EID Scheme 仅支持ipn。生产注意事项应定期调用BPLib_STOR_GarbageCollect()防止存储无限增长为保证跨启动周期的单调时间维护至少应在重启/终止时调用BPLib_TIME_MaintenanceActivities()或为应对意外断电而定期调用pkg/bplib/doc.md。结语通过本示例可以在一台 Linux 机器上完整体验 DTN 的三个关键能力基于 UDPCL 的 Bundle 传输、contact 中断下的持久化存储以及多节点模拟下的存储转发。结合 main.c、Makefile 与 UDP CLA 源码 对照阅读即可从会跑示例深入到理解 DTN 协议栈在 RIOT 上的落地方式为在真实硬件或更多收敛层如 BLE L2CAP上扩展 bplib 应用打下基础。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考