在RTOS环境中集成基础版SOC

SOC是一个端到端的服务,为RTOS、Linux、Android操作系统的物联网终端提供安全审计。本文介绍了如何在RTOS操作系统中集成基础版SOC SDK。

背景信息

基础版SOC SDK是SOC在终端的代理,可以定期检查设备系统的完整性,实时上报运行状态。让管理者随时了解物联网终端的安全状况,及时发现安全问题,进行排查和修复。

基础版SOC SDK通过Core、Platform、Service三个层次,提供了应用集成,平台移植和服务扩展功能,旨在通过SOC服务一站式地保护物联网终端安全。具体架构如下图所示:

SOC基础版SDK简介

前提条件

已通过IoT安全中心获取安全SDK,并根据目标系统的开发要求将SDK解压到特定位置。

注意

集成基础版SOC后,调用接口int32_t aiot_das_stepping会上报MQTT消息。您可以通过控制调用接口的频率来控制设备端基础版SOC发送的消息量。

平台适配

步骤一:设置日志开关

编译时传入DAS_DEBUG,即可开启基础版SOC SDK日志。基础版SOC SDK代码中打印日志接口为DAS_LOG(...)定义在das/include/das/platform.h

步骤二:设置设备身份信息

请适配物联网终端唯一ID,协助基础版SOC SDK识别物联网终端,ID可以是MEID、CPU ID、MAC地址等等,形式为字符串即可。

以移远ec600s开发板为例,函数原型如下:

适配接口在das/src/board/ec600s/das_board.c

size_t das_hal_device_id(char *buf, size_t size);

入参信息如下:

参数

描述

buf

用来存放物联网终端唯一ID的缓存。

size

buf的大小,目前为96字节,您可以自行修改DEVICE_ID_MAX_LEN,但由于物联网终端唯一ID会上报到云端,请配合您云端通道(如MQTT)的单笔消息频宽设定。

返回结果如下:

请求结果

描述

成功

返回实际写入buf的ID大小,以字节为单位。

失败

请返回0。

步骤三:适配ROM扫描范围

如果您有text或者ROM code,您希望确保完整性,可以适配此接口,基础版SOC SDK将定期帮您扫描计算完整性。

以移远ec600s开发板为例,函数原型如下:

适配接口在das/src/board/ec600s/das_board.c

int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER]);

入参信息如下:

参数

描述

buf

用来存放物联网终端唯一ID的缓存。

返回结果如下:

请求结果

描述

成功

请返回DAS_ROM_BANK_NUMBER

失败

请返回0。

可以参考如下案例:

您有两段代码段想要扫描,且您认为这两段代码段正常情况下不该被修改(除非返厂重烧),如text、code等等,那么您可以可在das_hal_rom_info设置它。代码段的位置可根据您的layout file(如scatter file)所编写的symbol位置去设定。

注意

针对设定的代码段,您的任务必须有可读权限,以免发生CPU异常。

示例
extern uint32_t __flash_text_start__;
extern uint32_t __flash_text_end__;

extern uint32_t __ram_image2_text_start__;
extern uint32_t __ram_image2_text_end__;

int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER])
{
    banks[0].address = &__flash_text_start__;
    banks[0].size = &__flash_text_end__ - &__flash_text_start__;

    banks[1].address = &__ram_image2_text_start__;
    banks[1].size = &__ram_image2_text_end__ - &__ram_image2_text_start__;

    return 2;
}

步骤四:互斥锁适配

请在das/src/service/service_sys.cdas/src/service/service_lwip_nfi.c文档中适配互斥锁,例如POSIX线程pthread或者AliOS-Things互斥锁aos_mutex

步骤五:网络流量采集适配

根据不同的网络环境,选择对应的开发方式:

  • 如果您的网路栈是基于LWIP,且可以修改到LWIP协议栈,请参考基于LWIP的网路流采集篇章做网路监控挂载。

  • 如果您使用其他网络,可以采用自定义网路流采集,根据您的网路栈做挂载,或者挂载在AT命令负责派送网路的出入口。

说明

您挂载的任务可能和您的MQTT任务不同,请将das/src/core/das_attest.c和将被挂载的任务一起编译。das_attest.c会将数据写到一个全局缓存,基础版SOC SDK将从此全局缓存读取数据。

  1. 基于LWIP的网路流采集

    如果您的网路栈是基于LWIP,且您可以修改到LWIP协议栈,您可采用此网路流采集方式。常见RTOS如FreeRTOS和AliOS-Things上,都是采用LWIP协议栈。基础版SOC SDK提供das_attest API对网络行为进行监控。主要原理是在系统的LWIP协议栈中,插入das_attest审计代码,从而记录网络流的进程。

    1. TCP网络流量进入

      在tcp_in.c文件开头引入相关头文件。

      #include "lwip/ip.h"
      #include <das.h>

      在tcp_in.c文件的tcp_input函数中插入das_attest监控代码。

      void
      tcp_input(struct pbuf *p, struct netif *inp)
      {
        ....
            
        /* Convert fields in TCP header to host byte order. */
        tcphdr->src = ntohs(tcphdr->src);
        tcphdr->dest = ntohs(tcphdr->dest);
        seqno = tcphdr->seqno = ntohl(tcphdr->seqno);
        ackno = tcphdr->ackno = ntohl(tcphdr->ackno);
        tcphdr->wnd = ntohs(tcphdr->wnd);
      
        // 插入如下代码 {
        das_attest("NFI_INPUT", 
          ip_current_src_addr()->addr, 
          ip_current_dest_addr()->addr, 
          tcphdr->src,
          tcphdr->dest,
          p->tot_len, 
          ip_current_header_proto()
        );
        // }
         
        flags = TCPH_FLAGS(tcphdr);
        tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);
      
        /* Demultiplex an incoming segment. First, we check if it is destined
           for an active connection. */
        prev = NULL;
      
        ...
      }
    2. UDP协议流量进入

      在udp.c文件开头引入相关头文件。

      #include "lwip/ip.h"
      #include <das.h>

      在udp.c文件的udp_input函数中插入das_attest监控代码。

      void
      udp_input(struct pbuf *p, struct netif *inp)
      {
        ...
      
        LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len));
      
        /* convert src and dest ports to host byte order */
        src = ntohs(udphdr->src);
        dest = ntohs(udphdr->dest);
      
        udp_debug_print(udphdr);
          
        // 插入如下代码 {
        das_attest("NFI_INPUT", 
          ip_current_src_addr()->addr, 
          ip_current_dest_addr()->addr, 
          src,
          dest,
          p->tot_len, 
          ip_current_header_proto()
        );
        // }
        
        /* print the UDP source and destination */
        LWIP_DEBUGF(UDP_DEBUG,
                    ("udp (%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F") <-- "
                     "(%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F")\n",
                     ip4_addr1_16(&iphdr->dest), ip4_addr2_16(&iphdr->dest),
                     ip4_addr3_16(&iphdr->dest), ip4_addr4_16(&iphdr->dest), ntohs(udphdr->dest),
                     ip4_addr1_16(&iphdr->src), ip4_addr2_16(&iphdr->src),
                     ip4_addr3_16(&iphdr->src), ip4_addr4_16(&iphdr->src), ntohs(udphdr->src)));
      
        ...
      }
    3. 网络流量出方向

      在ip.c文件开头引入相关头文件。

      #include <das.h>

      在ip.c文件的ip_output_if_opt函数中插入das_attest监控代码。

      err_t ip_output_if_opt(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
             u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
             u16_t optlen)
      {
        ...
      
        LWIP_DEBUGF(IP_DEBUG, ("netif->output()"));
      
        // 插入如下代码 {
        {
          u16_t sport = 0, dport = 0;
          u16_t len = 0;
          u16_t iphdr_hlen = IPH_HL(iphdr);
      
          iphdr_hlen *= 4;
      
          if (proto == IP_PROTO_UDP || IPH_PROTO(iphdr) == IP_PROTO_UDP) {
            struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)iphdr + iphdr_hlen);
            if (udphdr) {
              sport = lwip_ntohs(udphdr->src);
              dport = lwip_ntohs(udphdr->dest);
              len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len;
            }
          }
          else if (proto == IP_PROTO_TCP || IPH_PROTO(iphdr) == IP_PROTO_TCP) {
            struct tcp_hdr *tcphdr = (struct tcp_hdr *)((u8_t *)iphdr + iphdr_hlen);
            if (tcphdr) {
              sport = lwip_ntohs(tcphdr->src);
              dport = lwip_ntohs(tcphdr->dest);
              len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len;
            }
          }
      
          if (dport) {
            das_attest("NFI_OUTPUT", 
                src->addr,
                dest->addr, 
                sport,
                dport, 
                len,
                proto
                );
          }
        }
        // }
          
        return netif->output(netif, p, dest);
      }
  2. 自定义网路流采集

    如果您的网路栈不是基于LWIP,或者您无法修改LWIP协议栈,您可采用自定义网路流采集方式。您需要提供您每笔网路流的源IP、源端口、目标IP、目标端口、协议。您可根据您的网路栈做挂载,或者挂载在AT命令负责派送网路的出入口等等也行。

    1. 挂载TCP网络流量进方向

      #include "das.h"
      #include "inet.h"
      
      // ...
      
      struct in_addr remote_addr;
      struct in_addr local_addr;
      inet_aton(remote_ip, &remote_addr);
      inet_aton(local_ip, &local_addr);
      
      das_attest("NFI_INPUT", 
          remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          local_addr.s_addr,  // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          remote_port,        // 外部端口,uint32_t。
          local_port,         // 本机端口,uint32_t。
          packet_len,         // 包长度,uint32_t。
          6                   // 填写 6,代表 TCP,uint32_t。
      );
      
      // ...
    2. 挂载TCP网络流量出方向

      #include "das.h"
      #include "inet.h"
      
      // ...
      
      struct in_addr remote_addr;
      struct in_addr local_addr;
      inet_aton(remote_ip, &remote_addr);
      inet_aton(local_ip, &local_addr);
      
      das_attest("NFI_OUTPUT", 
          local_addr.s_addr,  // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          local_port,         // 反过来,本机端口,uint32_t。
          remote_port,        // 反过来,外部端口,uint32_t。
          packet_len,         // 包长度,uint32_t。
          6                   // 填写 6,代表 TCP,uint32_t。
      );
      
      // ...
    3. 挂载UDP网络流量进方向

      #include "das.h"
      #include "inet.h"
      
      // ...
      
      struct in_addr remote_addr;
      struct in_addr local_addr;
      inet_aton(remote_ip, &remote_addr);
      inet_aton(local_ip, &local_addr);
      
      das_attest("NFI_INPUT", 
          remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          local_addr.s_addr,  // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          remote_port,        // 外部端口,uint32_t。
          local_port,         // 本机端口,uint32_t。
          packet_len,         // 包长度,uint32_t。
          17                  // 填写 17,代表 UDP,uint32_t。
      );
      
      // ...
    4. 挂载UDP网络流量出方向

      #include "das.h"
      #include "inet.h"
      
      // ...
      
      struct in_addr remote_addr;
      struct in_addr local_addr;
      inet_aton(remote_ip, &remote_addr);
      inet_aton(local_ip, &local_addr);
      
      das_attest("NFI_OUTPUT", 
          local_addr.s_addr,  // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
          local_port,         // 反过来,本机端口,uint32_t。
          remote_port,        // 反过来,外部端口,uint32_t。
          packet_len,         // 包长度,uint32_t。
          17                  // 填写 17,代表 UDP,uint32_t。
      );
      
      // ...

应用集成

在物联网终端的实际开发中,由于资源受限,每个物联网终端网络连接的数目是受限的。基础版SOC SDK在核心层只定义了相关数据的订阅和分发接口,物联网终端可以根据实际的网络会话上对接基础版SOC服务。

步骤一:初始化核心服务

请参考如下函数原型:

void* das_init(const char *product_name, const char *device_name);

入参信息如下:

参数

描述

product_name

产品名称。如果使用阿里云IoT平台上云,参数可以在阿里云IoT平台上申请获得;如果自己实现上云通道,参数自定义即可。

device_name

物联网终端名称。如果使用阿里云IoT平台上云,参数可以在阿里云IoT平台上申请获得;如果自己实现上云通道,参数自定义即可。

返回结果如下:

请求结果

描述

成功

初始化成功,返回服务的session指针,用于后续安全服务相关函数调用。

失败

返回NULL。

步骤二:设置固件版本号

请参考如下函数原型:

int das_set_firmware_version(char *ver);

入参信息如下:

参数

描述

ver

固件版本号字串,例如:lemo-1.0.0-20191009.1111。

返回结果如下:

请求结果

描述

成功

返回0。

失败

返回-1。

步骤三:设置上下行消息主题

服务器端和客户端根据消息的topic类型来确认是否是自己需要处理的数据。

请参考如下函数原型:

  • 设置上行消息主题

    const char* das_pub_topic(void *session, const char *topic);
  • 设置下行消息主题

    const char* das_sub_topic(void *session, const char *topic);

    入参信息如下:

    参数

    描述

    session

    由das_init函数返回的服务实例。

    topic

    自定义的上下行消息topic字符串;如果想使用内置的缺省topic,则此值为NULL。

    返回结果如下:

    请求结果

    描述

    成功

    返回默认或自定义的topic字符串。

    说明

    • 如果topic参数为NULL,返回内置的缺省topic字符串,原型如下:/sys/$(product_name)/$(device_name)/security/up(down)stream

    • 自定义topic字符串长度不能超过64字节,否则设置会失败。

    失败

    返回NULL。有可能是参数错误或者topic字符串超过64字节。

步骤四:配置网络连接

基础版SOC SDK采集的数据需要通过业务已有的网络通道进行上报。

请参考如下函数原型:

void das_connection(void *session,
                    publish_handle_t publish_handle,
                    void *channel);

入参信息如下:

参数

描述

session

由das_init函数返回的实例。

publish_handle

需要用户自己实现,用来发送数据的函数指针。

channel

业务创建,用于数据上报的网络通道实例。

本函数无返回值。

步骤五:消息发送回调

用来发送数据的回调函数。如果基础版SOC SDK有数据需要上报,那么该回调由安全服务内部触发。请将此回调通过das_connection注册。

请参考如下函数原型:

typedef int (*publish_handle_t)(const char *topic,
                                const uint8_t *message, size_t msg_size,
                                void *channel);
注意

该接口需要用户实现。

入参信息如下:

参数

描述

topic

数据上行的topic类型。

message

需要发送的数据。

size

需要发送的数据大小。

channnel

业务创建,用于数据上报的网络通道实例。

返回结果如下:

请求结果

描述

成功

返回0。

失败

返回负数。

步骤六:更新网络连接状态

当业务的网络状态发生变化时,需要通知基础版SOC SDK。

请参考如下函数原型:

  • 业务网络已连接

    void das_on_connected(void *session);
  • 业务网络已断开

    void das_on_connected(void *session);

    入参信息如下:

    参数

    描述

    session

    由das_init函数返回的实例。

    本函数无返回值。

步骤七:处理下行数据

当业务收到服务器下发的指令或数据的时候,可以通过topic来区分是否是SOC的。如果是,则需要通知基础版SOC SDK来处理。

请参考如下函数原型:

void das_on_message(void *session, const uint8_t *message, size_t msg_size);

入参信息如下:

参数

描述

session

由das_init函数返回的实例。

message

由服务器端下发的数据。

size

下发数据的字节数。

函数无返回值。

步骤八:步进驱动取证服务

基础版SOC SDK不会主动执行取证操作,而是需要业务定时来驱动执行。

请参考如下函数原型:

das_result_t das_stepping(void *session, uint64_t now);

入参信息如下:

参数

描述

session

由das_init函数返回的实例。

now

当前系统时间,以毫秒为单位。

返回结果如下:

请求结果

描述

成功

返回0,int(das_result_t)类型。

失败

返回负数,int(das_result_t)类型。

步骤九:终止核心服务

请参考如下函数原型:

void das_final(void *session);

入参信息如下:

参数

描述

session

由das_init函数返回的实例。

函数无返回值。

示例代码

完整示例代码请参看$(das_sdk)/example/lv-example/mqtt_das_example.c

#include "iot_import.h"

#define PRODUCT_KEY     "demo_das_product"
#define DEVICE_NAME     "demo_das_device_1"

static void *session = null;

static int _on_publish(const char *topic, uint8_t *msg, uint32_t size, void *mqtt)
{
    iotx_mqtt_topic_info_t topic_msg;
    topic_msg.qos = IOTX_MQTT_QOS1;
    topic_msg.payload = (void *)message;
    topic_msg.payload_len = length;
    return IOT_MQTT_Publish(mqtt, topic, &topic_msg);
}

static void on_message(void *handle, void *pclient, iotx_mqtt_event_msg_pt msg)
{
    das_on_message(session, msg.payload, msg.payload_len);  
}

int main(int argc, const char argv[][])
{
  const char *sub_topic;

  mqtt = IOT_MQTT_Construct(&mqtt_params);
  
  session = das_init(PRODUCT_KEY, DEVICE_NAME);
  
  das_set_firmware_version("lemo-1.0.0-20191009.1111");
    
  sub_topic = das_sub_topic(session, NULL);
    
  das_connection(session, _on_publish, mqtt);
  
  das_on_connected(session);
  
  IOT_MQTT_Subscribe(mqtt,
      sub_topic, IOTX_MQTT_QOS1, on_message, session);

  while (IOT_MQTT_Yield(mqtt, 200) != IOT_MQTT_DISCONNECTED) {
      ...
      das_stepping(session, time(NULL));
      ...
  }

  das_on_disconnected(session);
  das_final(session);
  
  return 0;
}