在RTOS環(huán)境中集成基礎(chǔ)版SOC
SOC是一個端到端的服務(wù),為RTOS、Linux、Android操作系統(tǒng)的物聯(lián)網(wǎng)終端提供安全審計(jì)。本文介紹了如何在RTOS操作系統(tǒng)中集成基礎(chǔ)版SOC SDK。
背景信息
基礎(chǔ)版SOC SDK是SOC在終端的代理,可以定期檢查設(shè)備系統(tǒng)的完整性,實(shí)時上報(bào)運(yùn)行狀態(tài)。讓管理者隨時了解物聯(lián)網(wǎng)終端的安全狀況,及時發(fā)現(xiàn)安全問題,進(jìn)行排查和修復(fù)。
基礎(chǔ)版SOC SDK通過Core、Platform、Service三個層次,提供了應(yīng)用集成,平臺移植和服務(wù)擴(kuò)展功能,旨在通過SOC服務(wù)一站式地保護(hù)物聯(lián)網(wǎng)終端安全。具體架構(gòu)如下圖所示:
前提條件
已通過IoT安全中心獲取安全SDK,并根據(jù)目標(biāo)系統(tǒng)的開發(fā)要求將SDK解壓到特定位置。
集成基礎(chǔ)版SOC后,調(diào)用接口int32_t aiot_das_stepping
會上報(bào)MQTT消息。您可以通過控制調(diào)用接口的頻率來控制設(shè)備端基礎(chǔ)版SOC發(fā)送的消息量。
平臺適配
步驟一:設(shè)置日志開關(guān)
編譯時傳入DAS_DEBUG,即可開啟基礎(chǔ)版SOC SDK日志?;A(chǔ)版SOC SDK代碼中打印日志接口為DAS_LOG(...)定義在das/include/das/platform.h
。
步驟二:設(shè)置設(shè)備身份信息
請適配物聯(lián)網(wǎng)終端唯一ID,協(xié)助基礎(chǔ)版SOC SDK識別物聯(lián)網(wǎng)終端,ID可以是MEID、CPU ID、MAC地址等等,形式為字符串即可。
以移遠(yuǎn)ec600s開發(fā)板為例,函數(shù)原型如下:
適配接口在das/src/board/ec600s/das_board.c
size_t das_hal_device_id(char *buf, size_t size);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 buf 用來存放物聯(lián)網(wǎng)終端唯一ID的緩存。 size buf的大小,目前為96字節(jié),您可以自行修改DEVICE_ID_MAX_LEN,但由于物聯(lián)網(wǎng)終端唯一ID會上報(bào)到云端,請配合您云端通道(如MQTT)的單筆消息頻寬設(shè)定。 返回結(jié)果如下: 請求結(jié)果 描述 成功 返回實(shí)際寫入buf的ID大小,以字節(jié)為單位。 失敗 請返回0。
步驟三:適配ROM掃描范圍
如果您有text或者ROM code,您希望確保完整性,可以適配此接口,基礎(chǔ)版SOC SDK將定期幫您掃描計(jì)算完整性。
以移遠(yuǎn)ec600s開發(fā)板為例,函數(shù)原型如下:
適配接口在das/src/board/ec600s/das_board.c
。
int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER]);
入?yún)⑿畔⑷缦拢?/p>
參數(shù) | 描述 |
buf | 用來存放物聯(lián)網(wǎng)終端唯一ID的緩存。 |
返回結(jié)果如下:
請求結(jié)果 | 描述 |
成功 | 請返回DAS_ROM_BANK_NUMBER。 |
失敗 | 請返回0。 |
可以參考如下案例:
您有兩段代碼段想要掃描,且您認(rèn)為這兩段代碼段正常情況下不該被修改(除非返廠重?zé)鐃ext、code等等,那么您可以可在das_hal_rom_info設(shè)置它。代碼段的位置可根據(jù)您的layout file(如scatter file)所編寫的symbol位置去設(shè)定。
針對設(shè)定的代碼段,您的任務(wù)必須有可讀權(quán)限,以免發(fā)生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;
}
步驟四:互斥鎖適配
請?jiān)?code data-tag="code" class="code">das/src/service/service_sys.c和das/src/service/service_lwip_nfi.c
文檔中適配互斥鎖,例如POSIX線程pthread或者AliOS-Things互斥鎖aos_mutex。
步驟五:網(wǎng)絡(luò)流量采集適配
根據(jù)不同的網(wǎng)絡(luò)環(huán)境,選擇對應(yīng)的開發(fā)方式:
如果您的網(wǎng)路棧是基于LWIP,且可以修改到LWIP協(xié)議棧,請參考基于LWIP的網(wǎng)路流采集篇章做網(wǎng)路監(jiān)控掛載。
如果您使用其他網(wǎng)絡(luò),可以采用自定義網(wǎng)路流采集,根據(jù)您的網(wǎng)路棧做掛載,或者掛載在AT命令負(fù)責(zé)派送網(wǎng)路的出入口。
您掛載的任務(wù)可能和您的MQTT任務(wù)不同,請將das/src/core/das_attest.c
和將被掛載的任務(wù)一起編譯。das_attest.c會將數(shù)據(jù)寫到一個全局緩存,基礎(chǔ)版SOC SDK將從此全局緩存讀取數(shù)據(jù)。
基于LWIP的網(wǎng)路流采集
如果您的網(wǎng)路棧是基于LWIP,且您可以修改到LWIP協(xié)議棧,您可采用此網(wǎng)路流采集方式。常見RTOS如FreeRTOS和AliOS-Things上,都是采用LWIP協(xié)議棧?;A(chǔ)版SOC SDK提供das_attest API對網(wǎng)絡(luò)行為進(jìn)行監(jiān)控。主要原理是在系統(tǒng)的LWIP協(xié)議棧中,插入das_attest審計(jì)代碼,從而記錄網(wǎng)絡(luò)流的進(jìn)程。
TCP網(wǎng)絡(luò)流量進(jìn)入
在tcp_in.c文件開頭引入相關(guān)頭文件。
#include "lwip/ip.h" #include <das.h>
在tcp_in.c文件的tcp_input函數(shù)中插入das_attest監(jiān)控代碼。
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; ... }
UDP協(xié)議流量進(jìn)入
在udp.c文件開頭引入相關(guān)頭文件。
#include "lwip/ip.h" #include <das.h>
在udp.c文件的udp_input函數(shù)中插入das_attest監(jiān)控代碼。
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))); ... }
網(wǎng)絡(luò)流量出方向
在ip.c文件開頭引入相關(guān)頭文件。
#include <das.h>
在ip.c文件的ip_output_if_opt函數(shù)中插入das_attest監(jiān)控代碼。
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); }
自定義網(wǎng)路流采集
如果您的網(wǎng)路棧不是基于LWIP,或者您無法修改LWIP協(xié)議棧,您可采用自定義網(wǎng)路流采集方式。您需要提供您每筆網(wǎng)路流的源IP、源端口、目標(biāo)IP、目標(biāo)端口、協(xié)議。您可根據(jù)您的網(wǎng)路棧做掛載,或者掛載在AT命令負(fù)責(zé)派送網(wǎng)路的出入口等等也行。
掛載TCP網(wǎng)絡(luò)流量進(jìn)方向
#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 轉(zhuǎn)換后格式,uint32_t。 local_addr.s_addr, // 本機(jī) IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 remote_port, // 外部端口,uint32_t。 local_port, // 本機(jī)端口,uint32_t。 packet_len, // 包長度,uint32_t。 6 // 填寫 6,代表 TCP,uint32_t。 ); // ...
掛載TCP網(wǎng)絡(luò)流量出方向
#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, // 反過來,本機(jī) IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 remote_addr.s_addr, // 反過來,外部 IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 local_port, // 反過來,本機(jī)端口,uint32_t。 remote_port, // 反過來,外部端口,uint32_t。 packet_len, // 包長度,uint32_t。 6 // 填寫 6,代表 TCP,uint32_t。 ); // ...
掛載UDP網(wǎng)絡(luò)流量進(jìn)方向
#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 轉(zhuǎn)換后格式,uint32_t。 local_addr.s_addr, // 本機(jī) IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 remote_port, // 外部端口,uint32_t。 local_port, // 本機(jī)端口,uint32_t。 packet_len, // 包長度,uint32_t。 17 // 填寫 17,代表 UDP,uint32_t。 ); // ...
掛載UDP網(wǎng)絡(luò)流量出方向
#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, // 反過來,本機(jī) IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 remote_addr.s_addr, // 反過來,外部 IP 位置,需要是 inet_aton 轉(zhuǎn)換后格式,uint32_t。 local_port, // 反過來,本機(jī)端口,uint32_t。 remote_port, // 反過來,外部端口,uint32_t。 packet_len, // 包長度,uint32_t。 17 // 填寫 17,代表 UDP,uint32_t。 ); // ...
應(yīng)用集成
在物聯(lián)網(wǎng)終端的實(shí)際開發(fā)中,由于資源受限,每個物聯(lián)網(wǎng)終端網(wǎng)絡(luò)連接的數(shù)目是受限的?;A(chǔ)版SOC SDK在核心層只定義了相關(guān)數(shù)據(jù)的訂閱和分發(fā)接口,物聯(lián)網(wǎng)終端可以根據(jù)實(shí)際的網(wǎng)絡(luò)會話上對接基礎(chǔ)版SOC服務(wù)。
步驟一:初始化核心服務(wù)
請參考如下函數(shù)原型:
void* das_init(const char *product_name, const char *device_name);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 product_name 產(chǎn)品名稱。如果使用阿里云IoT平臺上云,參數(shù)可以在阿里云IoT平臺上申請獲得;如果自己實(shí)現(xiàn)上云通道,參數(shù)自定義即可。 device_name 物聯(lián)網(wǎng)終端名稱。如果使用阿里云IoT平臺上云,參數(shù)可以在阿里云IoT平臺上申請獲得;如果自己實(shí)現(xiàn)上云通道,參數(shù)自定義即可。 返回結(jié)果如下: 請求結(jié)果 描述 成功 初始化成功,返回服務(wù)的session指針,用于后續(xù)安全服務(wù)相關(guān)函數(shù)調(diào)用。 失敗 返回NULL。
步驟二:設(shè)置固件版本號
請參考如下函數(shù)原型:
int das_set_firmware_version(char *ver);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 ver 固件版本號字串,例如:lemo-1.0.0-20191009.1111。 返回結(jié)果如下: 請求結(jié)果 描述 成功 返回0。 失敗 返回-1。
步驟三:設(shè)置上下行消息主題
服務(wù)器端和客戶端根據(jù)消息的topic類型來確認(rèn)是否是自己需要處理的數(shù)據(jù)。
請參考如下函數(shù)原型:
設(shè)置上行消息主題
const char* das_pub_topic(void *session, const char *topic);
設(shè)置下行消息主題
const char* das_sub_topic(void *session, const char *topic);
入?yún)⑿畔⑷缦拢?/p>
參數(shù)
描述
session
由das_init函數(shù)返回的服務(wù)實(shí)例。
topic
自定義的上下行消息topic字符串;如果想使用內(nèi)置的缺省topic,則此值為NULL。
返回結(jié)果如下:
請求結(jié)果
描述
成功
返回默認(rèn)或自定義的topic字符串。
說明如果topic參數(shù)為NULL,返回內(nèi)置的缺省topic字符串,原型如下:
/sys/$(product_name)/$(device_name)/security/up(down)stream
。自定義topic字符串長度不能超過64字節(jié),否則設(shè)置會失敗。
失敗
返回NULL。有可能是參數(shù)錯誤或者topic字符串超過64字節(jié)。
步驟四:配置網(wǎng)絡(luò)連接
基礎(chǔ)版SOC SDK采集的數(shù)據(jù)需要通過業(yè)務(wù)已有的網(wǎng)絡(luò)通道進(jìn)行上報(bào)。
請參考如下函數(shù)原型:
void das_connection(void *session,
publish_handle_t publish_handle,
void *channel);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 session 由das_init函數(shù)返回的實(shí)例。 publish_handle 需要用戶自己實(shí)現(xiàn),用來發(fā)送數(shù)據(jù)的函數(shù)指針。 channel 業(yè)務(wù)創(chuàng)建,用于數(shù)據(jù)上報(bào)的網(wǎng)絡(luò)通道實(shí)例。 本函數(shù)無返回值。
步驟五:消息發(fā)送回調(diào)
用來發(fā)送數(shù)據(jù)的回調(diào)函數(shù)。如果基礎(chǔ)版SOC SDK有數(shù)據(jù)需要上報(bào),那么該回調(diào)由安全服務(wù)內(nèi)部觸發(fā)。請將此回調(diào)通過das_connection注冊。
請參考如下函數(shù)原型:
typedef int (*publish_handle_t)(const char *topic,
const uint8_t *message, size_t msg_size,
void *channel);
該接口需要用戶實(shí)現(xiàn)。
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 topic 數(shù)據(jù)上行的topic類型。 message 需要發(fā)送的數(shù)據(jù)。 size 需要發(fā)送的數(shù)據(jù)大小。 channnel 業(yè)務(wù)創(chuàng)建,用于數(shù)據(jù)上報(bào)的網(wǎng)絡(luò)通道實(shí)例。 返回結(jié)果如下: 請求結(jié)果 描述 成功 返回0。 失敗 返回負(fù)數(shù)。
步驟六:更新網(wǎng)絡(luò)連接狀態(tài)
當(dāng)業(yè)務(wù)的網(wǎng)絡(luò)狀態(tài)發(fā)生變化時,需要通知基礎(chǔ)版SOC SDK。
請參考如下函數(shù)原型:
業(yè)務(wù)網(wǎng)絡(luò)已連接
void das_on_connected(void *session);
業(yè)務(wù)網(wǎng)絡(luò)已斷開
void das_on_connected(void *session);
入?yún)⑿畔⑷缦拢?/p>
參數(shù)
描述
session
由das_init函數(shù)返回的實(shí)例。
本函數(shù)無返回值。
步驟七:處理下行數(shù)據(jù)
當(dāng)業(yè)務(wù)收到服務(wù)器下發(fā)的指令或數(shù)據(jù)的時候,可以通過topic來區(qū)分是否是SOC的。如果是,則需要通知基礎(chǔ)版SOC SDK來處理。
請參考如下函數(shù)原型:
void das_on_message(void *session, const uint8_t *message, size_t msg_size);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 session 由das_init函數(shù)返回的實(shí)例。 message 由服務(wù)器端下發(fā)的數(shù)據(jù)。 size 下發(fā)數(shù)據(jù)的字節(jié)數(shù)。 函數(shù)無返回值。
步驟八:步進(jìn)驅(qū)動取證服務(wù)
基礎(chǔ)版SOC SDK不會主動執(zhí)行取證操作,而是需要業(yè)務(wù)定時來驅(qū)動執(zhí)行。
請參考如下函數(shù)原型:
das_result_t das_stepping(void *session, uint64_t now);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 session 由das_init函數(shù)返回的實(shí)例。 now 當(dāng)前系統(tǒng)時間,以毫秒為單位。 返回結(jié)果如下: 請求結(jié)果 描述 成功 返回0,int(das_result_t)類型。 失敗 返回負(fù)數(shù),int(das_result_t)類型。
步驟九:終止核心服務(wù)
請參考如下函數(shù)原型:
void das_final(void *session);
入?yún)⑿畔⑷缦拢?/p> 參數(shù) 描述 session 由das_init函數(shù)返回的實(shí)例。 函數(shù)無返回值。
示例代碼
完整示例代碼請參看$(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;
}