基于 LinkSDK 集成安全 Agent 最佳实践

前置条件

(1)确认现有设备通过 LinkSDK 实现接入阿里云物联网平台:

假定设备接入物联网平台的 Linux C-SDK 从下面的渠道获得(一般而言设备制造商均会从这个渠道获得 SDK):登录阿里云物联网平台 -- 文档与工具 -- SDK 定制,按照以下选项选择 SDK 定制。

image.png

点击“开始生成”并获得下载链接。

解压之后获得的目录结构如下:

LinkSDK
+---ChangeLog.md
+---LICENSE
+---Makefile
+---components
+---core
+---demos
+---external
+---portfiles

假设当前的物联网设备固件版本中,是基于上述 LinkSDK C 版本的 demo 源代码编译接入的。可以按照下面的步骤进一步集成 IoT 安全运营中心 Agent,并复用上面 demo 当中提供的 MQTT 链路接入阿里云 IoT 安全运营中心。

(2)需要安装 IoT 安全运营中心 Agent

在调试设备上,下载并安装 IoT 安全运营中心 Agent,请参考安装 Agent文档完成安装。

安装好 IoT 安全运营中心 Agent 之后,请确认调试设备的 /usr/<lib/lib64>/(CPU 架构 32 位是 lib,64 位是 lib64)目录当中存在 libsessionmux.so。这个例子当中正是使用 libsessionmux.so 库来复用 LinkSDK 创建的通道给 IoT 安全运营中心 Agent,供其向服务端收发数据。

1. 改造上云程序并接入安全 Agent

在阿里云物联网平台提供的 LinkSDK C 版本当中有一组 demo 程序,开发者只要参考 demo 程序当中的代码,集成到自己设备的阿里云接入应用当中,并替换其中的 ProductKey,DeviceName,DeviceSecret 等参数即可实现自己的业务上云。

不论设备制造商在开发物联网平台接入时,使用了何种 SDK(包括但不限于阿里云物联网平台 LinkSDK、Paho、libmosquitto 等),这样的改造都遵循一定规律,具有一定的要领,主要有下面 4 点:

  • 需要同时启动安全 Agent 服务程序和物联网平台连接程序。

  • 要在设备自身的物联网平台接入程序源码里,定义一个 IoT 安全 Agent 的上行数据处理回调,当 IoT 安全 Agent 有数据发送请求时,这个回调会被调用,此时利用当前的 MQTT publish 接口帮助发送数据给 IoT 安全运营中心云服务。同时这个回调函数定义当中也需要调用 MQTT subscribe 接口帮助 IoT 安全 Agent 订阅来自 IoT 安全运营中心云服务的下行 topic。

  • 在设备调用 MQTT 客户端接口成功连接到物联网平台之后,要调用 IoT 安全 Agent 提供的连接附着接口,并向接口输入 ProductKey,DeviceName,MQTT 连接句柄,以及上面第 2 条当中定义的回调函数句柄。

  • 在 MQTT 数据接收处理函数中,对下行 topic 进行匹配过滤,如果是发送给 IoT 安全 Agent 的 topic,则调用 IoT 安全 Agent 提供的接口发送给 Agent。

下面的改造步骤将全程贯穿上面 4 个要领,请逐一理解。以 LinkSDK/demos/mqtt_basic_demo.c 为蓝本,讲述如何进一步通过微改造,接入 IoT 安全运营中心 Agent。

(1)将 LinkSDK/demos/mqtt_basic_demo.c 复制为 LinkSDK/demos/mqtt_securtiy_demo.c,并在源码额外包含两个 .h 文件,并定义一个全局变量:

...
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"

/* 接入IoT安全运营中心, 包含下面两个文件, 并定义一个全局会话变量 */
#include "sagent_defs.h"
#include "session_mux.h"

static channel_session_t* sec_session = NULL;

(2)定义一个函数,用于处理 IoT 安全运营中心 Agent 的发送消息事件,直接复制粘贴即可。

/* 接入IoT安全运营中心消息回调 */
ipc_error_t demo_security_message_handler(void* context, pid_t pid, char* message, uint16_t length) {
 channel_session_t* session = (channel_session_t*) context;
 void* mqtt_handle = NULL;
 char topic[MAX_TOPIC_NAME_LEN] = { 0 };

 if (NULL != session) {
 fprintf(stdout, "[SOC] cloud_session found : %s\n", session->session_name);
 mqtt_handle = session->custom_context;
 get_session_pub_topic(session, topic, MAX_TOPIC_NAME_LEN);
 } else {
 fprintf(stderr, "[SOC] channel session is empty\n");
 }

 if (0 == strncmp(message, IPC_DPS_CONNECTED, length)) {
 /* 订阅 MQTT topic */
 char sec_topic_sub[MAX_TOPIC_NAME_LEN] = { 0 };
 get_session_sub_topic(session, sec_topic_sub, MAX_TOPIC_NAME_LEN);
 aiot_mqtt_sub(mqtt_handle, sec_topic_sub, NULL, 0, NULL);
 return 0;
 }

 if (NULL != mqtt_handle) {
 aiot_mqtt_pub(mqtt_handle, topic, (uint8_t *)message, (uint32_t)length, 0);
 } else {
 fprintf(stderr, "[SOC] MQTT client as custom context is empty\n");
 }

 return 0;
}

(3)在主流程(demo 例程中的主函数)当中,MQTT 建立成功之后,插入下面一段代码:

/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
 /* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
 aiot_mqtt_deinit(&mqtt_handle);
 printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
 printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
 return -1;
}
// 到这里连接就建立成功了
...
/* 接入IoT安全运营中心, 在MQTT连接成功之后, 调用下面的函数
 * 参数分别为:product_key, device_name, 定义的消息回调, mqtt_handler句柄 */
sec_session = attach_to_security_service(product_key, // 传入 ProductKey
 device_name, // 传入 DeviceName
 demo_security_message_handler, // 传入第 2 步当中定义的回调函数指针
 mqtt_handle, // 传入 MQTT handler
 "aliot");

/* 主循环进入休眠 (这个是 demo 原来的代码)*/
while (1) {
 sleep(1);
}

(4)在 LinkSDK 消息处理回调收到 AIOT_MQTTRECV_PUB 事件时,当中添加一个处理分支,对来自 IoT 安全运营中心服务端的下行 topic 进行过滤并转发给安全 Agent。

void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
 switch (packet->type) {
 case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
 printf("heartbeat response\n");
 /* TODO: 处理服务器对心跳的回应, 一般不处理 */
 }
 break;

 case AIOT_MQTTRECV_SUB_ACK: {
 printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
 -packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
 /* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
 }
 break;

 case AIOT_MQTTRECV_PUB: {
 printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
 printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
 /* TODO: 处理服务器下发的业务报文 */

 /* 接入IoT安全运营中心, 处理相关的报文 */
 if (NULL != sec_session) {
 char topic[MAX_TOPIC_NAME_LEN] = { 0 };
 get_session_sub_topic(sec_session, topic, MAX_TOPIC_NAME_LEN);
 if (0 == strncmp(packet->data.pub.topic, topic, packet->data.pub.topic_len)) {
 send_to_session(sec_session, packet->data.pub.payload, packet->data.pub.payload_len);
 }
 }
 }
 break;

 case AIOT_MQTTRECV_PUB_ACK: {
 printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
 /* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
 }
 break;

 default: {

 }
 }
}

2. 编译和测试

将 IoT 安全运营中心 Agent 相应架构的 libsessionmux.so 拷贝至交叉编译环境 $sysroot/usr/local/lib 下,并将对应的 include 目录中的头文件拷贝至 LinkSDK/demos/ 目录,然后开始编译。

在 LinkSDK 给出的示范 demo 当中,采用了 Makefile 对 mqtt_basic_demo 进行编译,需要略微修改 Makefile,加入对 libsessionmux.so 的依赖:

...
# 追加安全 Agent 依赖的 SEC_AGENT_LIB
LIBSEC = -lsessionmux
LIBSECPATH = -L$sysroot/usr/local/lib
...

并在构建 PROG_TARGET 时加入这个 flag:

...
# 在编译 demo 时加入安全 Agent 依赖的 LIBSEC
$(PROG_TARGET): $(NOPOLL_lib) $(AIOT_LIB)
	$(Q)echo "+ Linking $(OUT_DIR)/$(notdir $@) ..."
	$(Q)mkdir -p $(dir $@)
	$(Q)if [ $(notdir $@) = "remote-access-basic-demo" ] \
		|| [ $(notdir $@) = "tunnel-basic-demo" ] ;then \
		$(CC) -o $@ \
			$(patsubst $(OUT_DIR)/%,%,$(addsuffix .c,$(subst $(notdir $@),$(subst -,_,$(notdir $@)),$@))) \
			$(BLD_CFLAGS) $(DYN_LIB_LDFLAGS) $(BLD_LDFLAGS) $(LIBSECPATH) $(LIBSEC); \
	else \
		$(CC) -o $@ \
			$(patsubst $(OUT_DIR)/%,%,$(addsuffix .c,$(subst $(notdir $@),$(subst -,_,$(notdir $@)),$@))) \
			$(BLD_CFLAGS) $(STA_LIB_LDFLAGS) $(BLD_LDFLAGS) $(LIBSECPATH) $(LIBSEC); \
	fi
...

其它 GNU 构建工具,例如 scons 或者 CMake 均仅需加入对 $sysroot/usr/local/lib/libsessionmux.so 和对它的两个头文件位于 $sysroot/usr/local/include 的查找依赖即可,本文不再一一举例。

Makefile 改造完成之后运行 make 可构建出参考程序,将程序拷贝到目标设备,并确保目标设备已安装好 IoT 安全运营中心 Linux Lite Agent,安全服务启动状态下(sudo systemctl start dpsd_lite.serviec 启动)运行参考程序,即可连接 IoT 安全运营中心,并在安全运营中心控制台资产列表中看到上线设备。

# 首先确认 IoT 安全 Agent 服务已启动,并且没有 sagent 这个进程:
root@bloodlessvm:~/project/LinkSDK# systemctl status dpsd_lite.service
● dpsd_lite.service - DPS Lite daemon
 Loaded: loaded (/etc/systemd/system/dpsd_lite.service; enabled; vendor preset: disabled)
 Active: active (running) since Sat 2023-08-05 14:34:27 CST; 2s ago
 Main PID: 3393 (dpsd_lite)
 CGroup: /system.slice/dpsd_lite.service
 └─3393 /system/dps/bin/dpsd_lite

Jun 14 08:45:17 bloodlessvm systemd[1]: Started DPS daemon.

# 编译 LinkSDK 接入安全服务的 demo
root@bloodlessvm:~/project/LinkSDK# make
+ Linking output/lib/libaiot.so ...
+ Linking output/lib/libaiot.a ...
+ Linking output/mqtt-security-demo ...

# 执行 Demo,传入参数 iot_instance_id, product_key, device_name, device_secret
root@bloodlessvm:~/project/LinkSDK# cd output/
root@bloodlessvm:~/project/LinkSDK/output# 
root@bloodlessvm:~/project/LinkSDK/output# ./mqtt-security-demo iot_instance_id product_key device_name device_secret

3. 固件升级和生产参考

通过上面的改造,在目标设备上已经形成了阿里云物联网平台接入+安全 Agent 的协同工作。如果您的设备固件是通过 rootfs 烧录方式在产线上进行生产,或者是采用 rootfs 进行全量或者差分 FOTA,请注意为固件更新添加以下一些文件系统路径:

  • /system/dps

  • /etc/systemd/system/dpsd_lite.service 或者 /etc/init/dpsd_lite.conf

  • /usr/lib/libsessionmux.so

  • /usr/lib64/libsessionmux.so // 64 位架构,指向 /usr/lib/libsessionmux.so 的软链接

4. 示例代码

可以通过阅读和编译运行下面源代码,进行快速体验,并跑通一个 Link SDK C 版本 + 安全 Agent 的例子。

源文件和 Makefile:下载

实践步骤:

解压缩附件 demo 包,将 Makefile 当中的安全 Agent 依赖项(主要是 libsessionmux 依赖)放置到 LinkSDK Makefile 文档指定的路径,将所需头文件复制到 LinkSDK/demo 目录,构建并运行即可。

上面的示例代码可以直接贡献到 LinkSDK,并在 LinkSDK 下载时提供安全 feature,用户接入之后,此时如果没有 IoT 安全运营中心 Agent,sessionmux client 则处于潜伏状态,在后台定时重连 sessionmux server(不耗 CPU)。

一旦通过后装或者 FOTA 安装了 IoT 安全运营中心 Agent,这个 sessionmux client 即从潜伏状态自动激活,连接到 Agent 并接入 IoT 安全运营中心服务端。

后续步骤:确认安装效果