Zephyrのセキュア通信を試す
Wind River
コラム
Zephyrとは
Zephyrは、もとはウインドリバー・システムズによって開発されたIoT向けのRTOS(リアルタイムオペレーティングシステム)です。
現在はLinux Foundationによってオープンソースで開発が進められています。
軽量で、x86、arm、RISC-Vなどのさまざまなアーキテクチャに対応し、リアルタイム機能を持ち、さまざまなプロトコルスタックを実装し、セキュリティを重視していることなどを特長とします。
本コラムでは、ZYNQ-7000 ZC702評価ボードにZephyrを移植し、セキュアなプロトコル(TLS)を使って外部に接続するまでの手順を説明します。
開発環境
システム構成要素
|
詳細
|
ホストOS
|
Ubuntu 23.10
|
ターゲットOS
|
Zephyr v4.1.0-4540-gf77e258cb9c2
|
ターゲットボード
|
ZYNQ-7000 EPP ZC702 Evalution Kit REV 1.1
|
Zephyrのビルド
公式のガイドを参考にしながら、ZC702向けのZephyrをビルドします。
- https://docs.zephyrproject.org/latest/develop/getting_started/index.html
- https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html
ホストOSのアップデート
以下のコマンドでホストOSをアップデートします。
$ sudo apt update -y
$ sudo apt upgrade -y
依存関係のインストール
以下のコマンドで必要なパッケージをホストOSにインストールします。
$ sudo apt install --no-install-recommends -y git cmake ninja-build gperf \
ccache dfu-util device-tree-compiler wget \
python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \
make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1
$ cmake --version
$ python3 --version
$ dtc --version
Zephyrの入手とPython依存関係のインストール
以下のコマンドでZephyrを入手し、必要なPythonパッケージをインストールします。
$ cd ~
$ mkdir zephyrproject
$ cd zephyrproject
$ sudo apt install python3-venv
$ python3 -m venv ~/zephyrproject/.venv
$ source ~/zephyrproject/.venv/bin/activate
$ pip install west
$ west init ~/zephyrproject
$ west update
$ west zephyr-export
$ west packages pip --install
Zephyr SDKのインストール
以下のコマンドでZephyr SDKをインストールします。
Zephyr SDKには、Zephyrのビルドに必要なコンパイラ、アセンブラ、リンカーなどが含まれています。
$ cd ~/zephyrproject/zephyr
$ west sdk install
ボードディレクトリの作成
ZCU702向けのボードディレクトリを作成します。
ボードディレクトリとは、各開発ボード向けの設定ファイルをまとめたディレクトリのことです。
今回は、ZCU702と同じSoCを搭載したDigilent Zyboのボードディレクトリをベースにします。
$ mkdir -p boards/xilinx
$ cp -r boards/digilent/zybo boards/xilinx/zc702
$ cd boards/xilinx/zc702
$ mv Kconfig.zybo Kconfig.zc702
$ mv zybo.dts zc702.dts
$ mv zybo.yaml zc702.yaml
$ mv zybo_defconfig zc702_defconfig
$ mv zybo-pinctrl.dtsi zc702-pinctrl.dtsi
$ find . -type f -exec sed -i 's/digilent/xilinx/g' {} +
$ find . -type f -exec sed -i 's/Digilent/Xilinx/g' {} +
$ find . -type f -exec sed -i 's/DIGILENT/XILINX/g' {} +
$ find . -type f -exec sed -i 's/zybo/zc702/g' {} +
$ find . -type f -exec sed -i 's/Zybo/ZC702/g' {} +
$ find . -type f -exec sed -i 's/ZYBO/ZC702/g' {} +
DTSの変更
zc702.dtsは、ボードのハードウェア構成を定義したファイルです。
テキストエディタでLEDとUARTの設定をZC702向けに変更します。
変更前 :
leds {
compatible = "gpio-leds";
ld_mio: led_mio {
gpios = <&psgpio_bank0 7 GPIO_ACTIVE_HIGH>;
label = "LD_MIO";
};
};
&uart1 {
status = "okay";
current-speed = <115200>;
clock-frequency = <100000000>;
pinctrl-0 = <&pinctrl_uart1_default>;
pinctrl-names = "default";
};
変更後 :
leds {
compatible = "gpio-leds";
ld_mio: led_mio {
gpios = <&psgpio_bank0 10 GPIO_ACTIVE_HIGH>; /* <- ここを変えた */
label = "LD_MIO";
};
};
&uart1 {
status = "okay";
current-speed = <115200>;
clock-frequency = <50000000>; /* <- ここを変えた */
pinctrl-0 = <&pinctrl_uart1_default>;
pinctrl-names = "default";
};
サンプルのビルド
以下のコマンドでサンプルアプリケーションをビルドします。
$ cd ~/zephyrproject/zephyr
$ west build -p always -b zc702 samples/hello_world
~/zephyrproject/zephyr/build/zephyr/以下にzephyr.binが作成されたことを確認します。
これがZephyrアプリケーションのバイナリになります。
$ ls ~/zephyrproject/zephyr/build/zephyr/
U-Bootの準備
Zynq-7000ではZephyrアプリケーションを実行する前にSoCの初期化をする必要があります。
ですので、SoCの初期化を行うプログラムを準備します。
今回は以下のXilinxのWikiにあるビルド済みのU-bootを使います。
$ cd ~
$ wget https://xilinx-wiki.atlassian.net/wiki/download/attachments/18841732/zynq-ubuntu-core-12.10-core-armhf-boot.tar.xz
$ tar Jxfv zynq-ubuntu-core-12.10-core-armhf-boot.tar.xz
展開したファイルにboot.binがあることを確認します。
これが最初にロードされるバイナリになります。
$ ls boot
SDカードの準備
SDカードをFAT32形式でフォーマットし、以下の準備したファイルを書き込みます。
boot.bin
zephyr.bin
Zephyrの起動
ボードのスイッチを操作してSDブートするようにし、ボードにSDカードをセットし、ボードの電源を入れます。
U-bootが起動したらオートブートを中断して、以下のコマンドを入力してZephyrアプリケーションのロードと実行を行います。
zynq-uboot> fatload mmc 0 0x0 zephyr.bin
zynq-uboot> go 0x0
Zephyrアプリケーションが以下のメッセージを出力することを確認します。
*** Booting Zephyr OS build v4.1.0-5075-gcf6170cbe605 ***
Hello World! zc702/xc7z010
ZC702にZephyrを移植できました。
Ethernet
続いてEthernetを有効にしていきます。
以下のZYNQのデータシートなどを参考にしながらプロジェクトに変更を加えていきます。
GEMのドライバ (eth_xlnx_gem.c)
Zephyr v4.1.0のGEM(Gigabit Ethernet MAC)のドライバ(eth_xlnx_gem.c)は、そのままではZC702では動かないのでソースコードを変更します。
(1) eth_xlnx_gem_configure_clocks() の分周比計算処理を変更します。
div0 = 8;
div1 = 5;
(2) eth_xlnx_gem_configure_clocks() のレジスタ書込の処理を変更します。
// CLK_CTRL READ
{
mem_addr_t phys_addr = 0xF8000140;
mem_addr_t addr = 0;
device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);
clk_ctrl_reg = sys_read32(addr);
}
clk_ctrl_reg &= ~((ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK <<
ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) |
(ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK <<
ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT));
clk_ctrl_reg |= ((div0 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) <<
ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) |
((div1 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) <<
ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT);
// SLCR UNLOCK
{
mem_addr_t phys_addr = 0xF8000008;
mem_addr_t addr = 0;
device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);
sys_write32(0xDF0D, addr);
}
// CLK_CTRL WRITE
{
mem_addr_t phys_addr = 0xF8000140;
mem_addr_t addr = 0;
device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);
sys_write32(clk_ctrl_reg, addr);
}
// SLCR LOCK
{
mem_addr_t phys_addr = 0xF8000004;
mem_addr_t addr = 0;
device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);
sys_write32(0x767B, addr);
}
PHYのドライバ (phy_xlnx_gem.c)
Zephyr v4.1.0には、ZC702に搭載されたPHY(Marvell Alaska 88E1116R)のドライバがありません。
既存のPHYドライバのソースコード(phy_xlnx_gem.c)に88E1116R固有の処理を追加することでこれを補います。
(1) phy_xlnx_gem_supported_devs配列に88E1116Rの要素を追加します。
{
.phy_id = 0x01410e40,
.phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK,
.api = &phy_xlnx_gem_marvell_alaska_api,
.identifier = "Marvell Alaska 88E1116R"
},
(2) phy_xlnx_gem_marvell_alaska_cfg()に88E1116R固有の処理を追加します。
// Change to page 2: Configure RGMII TX/RX delay
phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0002);
phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,0x15, 0x1070);
// Change to page 3: Configure LED control (optional)
phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0003);
phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,0x10, 0x021e);
// Return to default page 0
phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0000);
DTS
zc702.dtsにGEMの記述を追加します。
&gem0 {
status = "okay";
clock-frequency = <100000000>;
mdc-divider = <XLNX_GEM_MDC_DIVIDER_32>;
local-mac-address = [da 13 7a 1f 39 ac];
init-mdio-phy;
/delete-property/ discard-rx-fcs;
/delete-property/ unicast-hash;
/delete-property/ full-duplex;
// amba-ahb-burst-length = <XLNX_GEM_AMBA_AHB_BURST_INCR4>;
};
カーネルコンフィグ
アプリケーションのprj.confにネットワーク(とシェル)の設定を追加します。
CONFIG_ETH_DRIVER=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NETWORKING=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n
CONFIG_NET_ARP=y
CONFIG_NET_TCP=y
CONFIG_NET_UDP=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_DHCPV4_OPTION_CALLBACKS=y
CONFIG_DNS_RESOLVER=y
CONFIG_LOG=y
CONFIG_NET_LOG=y
CONFIG_SHELL=y
CONFIG_NET_SHELL=y
リンクアップ
リビルドしたアプリケーションをロードすると、ドライバが動作してEthernetがリンクアップします。
リンクアップすると以下のようなログが出力されます。
[00:00:03.012,000] <inf> eth_xlnx_gem: ethernet@e000b000 link up, 100 MBit/s
ICMP
Zephyrのシェルを使ってpingを実行できます。
$ net ipv4 gateway 1 192.168.13.1
$ net ipv4 add 1 192.168.13.47 255.255.255.0
$ net ping 192.168.13.1
実行すると以下のようなログが出力されます。
PING 192.168.13.1
28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=1 ttl=64 time=0 ms
28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=2 ttl=64 time=0 ms
28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=3 ttl=64 time=0 ms
DHCP
Zephyrのシェルを使ってDHCPを実行できます。
$ net ipv4 del 1 192.168.13.47
$ net dhcpv4 client start 1
$ net iface
実行すると以下のようなログが出力されます。
[00:00:45.867,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved
[00:00:45.867,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved
[00:00:45.867,000] <inf> net_dhcpv4: Received: 192.168.13.215
Default interface: 1
Interface eth0 (0x39560) (Ethernet) [1]
===================================
Link addr : DA:13:7A:1F:39:AC
MTU : 1500
Flags : AUTO_START,IPv4
Device : ethernet@e000b000 (0x310d4)
Status : oper=UP, admin=UP, carrier=ON
Ethernet capabilities supported:
100 Mbits
Promiscuous mode
Ethernet PHY device: <none> (0)
IPv4 unicast addresses (max 1):
192.168.13.215/255.255.255.0 DHCP preferred
IPv4 multicast addresses (max 2):
224.0.0.1
IPv4 gateway : 192.168.13.1
DHCPv4 lease time : 172800
DHCPv4 renew time : 86400
DHCPv4 server : 192.168.13.1
DHCPv4 requested : 192.168.13.215
DHCPv4 state : bound
DHCPv4 attempts : 1
DHCPv4 state : bound
警告が出力されたり、「Ethernet PHY device」が「\<none\> (0)」になったりしていますが、Ethernetが動作するようになりました。
TLS
最後に簡単なTLSクライアントアプリケーションを実装します。
カーネルコンフィグを変更してTLSを有効化し、ユーザーコードを書けばTLSクライアントアプリケーションを実装できます。
カーネルコンフィグ
アプリケーションのprj.confにネットワークの設定を追加します。
これでBSDソケット互換APIが利用できるようになりますが、このAPIは拡張されていて、簡単にTLSを利用できるようになっています。
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_POSIX_API=y
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_NEED_IPV6=n
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.13.1"
CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y
CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=60000
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048
アプリケーションソースコード
自己署名証明書を使うサーバーに接続するアプリケーションソースコードを書きます。
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/tls_credentials.h>
LOG_MODULE_REGISTER(simple_tls, LOG_LEVEL_DBG);
int main(void)
{
int ok = 0;
int socket_ = -1;
// ソケットを作成する
{
socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
if (socket_ < 0)
{
LOG_ERR("ERROR : socket : %d\n", errno);
goto EXIT;
}
// 自己署名証明書のサーバーに接続する
int verify = TLS_PEER_VERIFY_NONE;
if (setsockopt(socket_, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify)) < 0)
{
LOG_ERR("ERROR : setsockopt TLS_PEER_VERIFY : %d\n", errno);
goto EXIT;
}
}
// 接続する
{
struct sockaddr_in server = { 0 };
server.sin_family = AF_INET;
server.sin_port = htons(12345);
inet_pton(AF_INET, "192.168.13.48", &server.sin_addr);
if (connect(socket_, (struct sockaddr *)&server, sizeof(server)) < 0)
{
LOG_ERR("ERROR : connect : %d\n", errno);
goto EXIT;
}
}
// 送信する
{
char const * const message = "Hello from client";
if (send(socket_, message, strlen(message) + 1, 0) < 0)
{
LOG_ERR("ERROR : send : %d\n", errno);
goto EXIT;
}
}
// 受信する
while (1)
{
char message[128] = { 0 };
int const return_value = recv(socket_, message, sizeof(message) - 1, 0);
if (return_value < 0)
{
LOG_ERR("ERROR : recv : %d\n", errno);
goto EXIT;
}
if (return_value == 0)
{
break;
}
else
{
LOG_INF("%s", message);
continue;
}
}
ok = 1;
EXIT:
// ソケットをクローズする
if (socket_ != -1)
{
if (close(socket_) < 0)
{
LOG_ERR("ERROR : close : %d\n", errno);
goto EXIT;
}
}
LOG_INF("%s\n", ok ? "OK" : "NG");
return 0;
}
実行
リビルドしたアプリケーションを実行すると、ユーザーが作成したアプリケーションコードが実行される前に、Zephyrが自動で諸々のネットワークの設定を行ってくれます。
*** Booting Zephyr OS build v4.1.0-5075-gcf6170cbe605 ***
[00:00:00.011,000] <wrn> net_sock_tls: No entropy device on the system, TLS communication is insecure!
[00:00:00.011,000] <inf> net_config: Initializing network
[00:00:00.011,000] <inf> net_config: Waiting interface 1 (0x51930) to be up...
[00:00:03.012,000] <inf> eth_xlnx_gem: ethernet@e000b000 link up, 100 MBit/s
[00:00:03.012,000] <inf> net_config: Interface 1 (0x51930) coming up
[00:00:03.012,000] <inf> net_config: Running dhcpv4 client...
[00:00:05.013,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved
[00:00:05.014,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved
[00:00:05.014,000] <inf> net_dhcpv4: Received: 192.168.13.215
[00:00:05.014,000] <inf> net_config: IPv4 address: 192.168.13.215
[00:00:05.014,000] <inf> net_config: Lease time: 172800 seconds
[00:00:05.014,000] <inf> net_config: Subnet: 255.255.255.0
[00:00:05.014,000] <inf> net_config: Router: 192.168.13.1
[00:00:05.072,000] <inf> simple_tls: Hello from server
[00:00:05.072,000] <inf> simple_tls: OK
ユーザーアプリケーションが実行され、TLSでメッセージの送受信ができました。
最後に
以上になります。
このコラムが、Zephyrの特長や扱い方を理解する助けになりましたら幸いです。