前置条件
(1)确认现有设备通过 LinkSDK 实现接入阿里云物联网平台:
假定设备接入物联网平台的 Linux C-SDK 从下面的渠道获得(一般而言设备制造商均会从这个渠道获得 SDK):登录阿里云物联网平台 -- 文档与工具 -- SDK 定制,按照以下选项选择 SDK 定制。
点击“开始生成”并获得下载链接。
解压之后获得的目录结构如下:
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 安全运营中心服务端。
后续步骤:确认安装效果