2023년 2월 8일 수요일

ESP32 기반의 WireGuard, NAT router, 그리고 PQC

이번 시간에는 ESP32 관련 두번째 시간으로, ESP32 board에 WireGuard를 올리고, 이를 VPN Client로 만드는 내용을 다뤄 보고자 한다. 또한 (맛보기 차원에서) ESP32를 NAT router로 만드는 과정과 PQC(Post Quantum Cryptography) 알고리즘을 ESP32에 올리는 과정을 소개해 보고자 한다.


ESP32 board + WireGuard + PQC w/ I2C or SPI sensor

목차
1. ESP32 Reloaded
2. ESP32 보드에 i2c & spi 장치 붙여 보기
3. ESP32 보드에 WireGuard 올리기
4. ESP32 보드를 NAT Gateway로 만들기
5. PQC 알고리즘 Porting하기 - CRYSTALS Kyber & Dilithium
6. References

Keyword: esp32, wireguard, lwip, NAT, PQC, i2c, spi


필자는 개인적으로 Linux의 광팬이다(Linux여 영원하라~). 하지만 Linux는 벌써 개발된지 30년이 훌쩍 넘었고, (많은 내용을 수용하다보니) 덩치도 상당히 비대해진게 사실이다. uClinux 같은 작은 linux 버젼도 있긴 하지만, 초소용 IoT 환경에는 어울리지 않는 듯하다. 여러가지 선택지가 있겠지만,  ESP32가 그 중 좋은 대안이 아닌가 생각본다. 🎯

1. ESP32 Reloaded

2년 전 쯤에 ESP32 관련 글을 한차례 게재한 적이 있다. 이번 시간에는 ESP32 관련 두번째 시간으로, ESP32 board에 Wireguard를 올려 보고, 3축 가속도 센서 정보를 wireguard tunnel을 통해 서버로 전송하는 내용을 다뤄 보고자 한다.

"ESP32는 ESP8266으로 유명한 Espressif 사에서 만든 Wi-Fi & Bluetooth용 저가의 chip(Wi-Fi/BLE SoC)이다. 그런데 저가의 chip이라고 해서 우습게(?) 볼 일이 아닌 것이, ESP32 자체는 Xtensa LX6(Dual-core 32bit) RISC CPU(160Mhz or 240Mhz)를 기반으로 하고 있는 엄연한 32bit microprocessor이다."

https://slowbootkernelhacks.blogspot.com/2020/12/

a) Long time no see ~ ESP32 보드

아래 그림과 같이, 잠자고 있던 esp32 board(esp32-s devkit)를 다시 꺼내어 전원을 연결해 보았다. 💤

[그림 1.1] esp32 board(ESP32-S/NodeMCU)를 연결한 모습


b) ESP-IDF 환경 구축

환경 구축 등과 같은 자세한 사항은 위의 blog post ESP-IDF programming guide를 참고해 주기 바라며, 여기에서는 오랜만에 esp-idf(v5.1) 개발 환경을 다시 구축해 본 후, 곧 바로 다음 장으로 바로 넘어가도록 하자.

<ESP-IDF 개발 환경 구축>
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
$ ./install.sh esp32
. ./export.sh

간단한 sample code를 하나 build하여 실행해 보도록 하자.
cd examples/get-started/hello_world/
idf.py menuconfig
여기에서 환경 설정을 원하는대로 변경한다.
$ idf.py build
$ idf.py -p /dev/ttyUSB0 flash
$ idf.py -p /dev/ttyUSB0 monitor

[그림 1.2] esp32 board 상에서 hello world binary를 실행한 모습

자, 이제 다음 단계로 넘어갈 준비가 되었는가 ? ⌛


2. ESP32 보드에 i2c & spi 장치 붙여 보기

이번 장에서는 3축 가속도 센서(ADXL345)를 esp32 board에 연결하고, 이를 인식시키는 과정을 소개해 보고자 한다.

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_slave.html


a) ADXL345 3축 가속도(Accelerometer) 센서

시험에 사용할 센서는 아래 그림에서 보는 것과 같이  i2c 및 spi 연결이 가능한 ADXL345 3축 가속도 센서가 되겠다. 우선은 i2c가 간단해 보이니, i2c를 이용해 연결해 보도록 하자.

[그림 2.1] ADXL345 3축 가속도(Accelerometer) 센서 브레이크아웃 보드(1)
📌 Breakout 보드는 IC의 핀들을 커넥터로 연결할 수 있도록 만들어진 PCB 보드를 의미한다.

아래 table은 ADXL345 breakout 보드의 pinout을 상세히 설명해 준다.
[그림 2.2] ADXL345 3축 가속도(Accelerometer) 센서 브레이크아웃 보드(2) [출처 - 참고문헌 5]


"ADXL345는 Analog Devices에서 출시한 작고 얇은 초저전력 3축 가속도계입니다. 최대 ±16g에서 고해상도(13비트) 측정이 가능합니다. 디지털 출력 데이터는 16비트 2의 보수 형식으로 지정되며 SPI(3선 또는 4선) 또는 I2C 디지털 인터페이스를 통해 액세스할 수 있습니다. ADXL345는 모바일 장치 애플리케이션에 적합합니다. 기울기 감지 애플리케이션에서 중력의 정적 가속도와 움직임이나 충격으로 인한 동적 가속도를 측정합니다. 고분해능(3.9 mg/LSB)으로 1.0° 미만의 기울기 변화를 측정할 수 있습니다. 몇 가지 특수 감지 기능이 제공됩니다. 활동 및 비활동 감지는 모든 축의 가속도를 사용자가 설정한 임계값과 비교하여 움직임의 유무를 감지합니다. 탭 감지는 모든 방향의 단일 및 이중 탭을 감지합니다. 자유낙하 감지는 장치가 떨어지는지 감지합니다. 이러한 기능은 두 개의 인터럽트 출력 핀 중 하나에 개별적으로 매핑될 수 있습니다. 32레벨 FIFO(선입 선출) 버퍼가 있는 통합 메모리 관리 시스템은 호스트 프로세서 활동을 최소화하고 전체 시스템 전력 소비를 줄이기 위해 데이터를 저장하는 데 사용할 수 있습니다. .저전력 모드는 임계값 감지 및 극도로 낮은 전력 손실에서 능동 가속 측정을 통해 지능형 동작 기반 전력 관리를 가능하게 합니다." [출처: 참고문헌 4]
[그림 2.3] ADXL345 3축 가속도 센서 block도 [출처: 참고문헌 3]
📌 ADXL345는 Analog Devices 사가 만든 3-Axis, ±2 g/±4 g/±8 g/±16 g Digital Accelerometer이다.
[그림 2.4] ADXL345 3축 가속도 센서 application block도 [출처: 참고문헌 3]
📌 재밌게도 ADXL345는 interrupt를 발생시킬 수 있다.

[그림 2.5] ADXL345 SPI 연결도2(connection diagram) - 3 wire [출처: 참고문헌 3]

[그림 2.6] ADXL345 SPI 연결도2(connection diagram) - 4 wire [출처: 참고문헌 3]

[그림 2.7] ADXL345 I2C 연결도(connection diagram) [출처: 참고문헌 3]


b) ADXL345 i2c pinmap
3축 가속도 센서를 i2c 포트를 통해 연결하기 위해서는, 먼저 ESP32-S devkit의 확장핀 맵을 확인해 보아야 한다. esp32에는 2개의 i2c port(controller)가 존재하는데, 여기에서는 GPIO22를 pin mux한 port(P22, SCL용)와 GPIO21을 pin mux한 port(P21, SDA용)를 사용해 보도록 하자.


[그림 2.8] esp32 board(ESP32-S/NodeMCU) pinout (Old board)

[그림 2.9] esp32 board(ESP32-S/NodeMCU) pinout (New board)

아래 그림은 esp32 board에 ADXL345 device를 실제로 연결한 모습이다. Jumper cable이 부족하여 (어쩔 수 없이) 일반적이지 않은 색이지만, 아래와 같이 연결해 보았다. 😂


[그림 2.10]ADXL345 3축 가속도(Accelerometer) 센서 

ESP32 보드와 ADXL345간의 실제 연결 모습은 다음과 같다.

<ADXL345와 ESP32 확장핀 간 연결 모습>
GND(파란색 - 일반적으로 검정색)  ...................................................................... ESP32 GND
VCC(3.3v)(주황색 - 일반적으로는 빨간색) ......................................................... ESP32 3V3
SDA(녹색) .................................................................................................................... ESP32 P21
SCL(노란색) ................................................................................................................. ESP32 P22

[그림 2.11] esp32 board에 ADXL345 3축 가속도(Accelerometer) 센서를 연결한 모습

c) esp32 i2c device driver 구현하기

다음으로 할 일은 ADXL345 i2c device driver를 구현하고, 이를 구동시켜 보는 일이다. 아래 그림은 ADXL345 가속도 센서에 대한 i2c 연결도를 보여주고 있다. 잘 알고 있는 바와 같이, i2c slave 설정을 위해서는 i2c device address가 필요하다. 한가지 재밌는 사실은 ADXL345의 경우는 SDO/ALT ADDRESS라는 pin을 High(3.3v)로 하느냐 Low로 하느냐에 따라 i2c 주소가 아래와 같이 2가지로 사용 가능하다는 점이다.

[그림 2.12] ADXL345 가속도 센서의 i2c 연결도 [출처: 참고문헌 3]

[1] SDO/ALT ADDRESS를 High로 할 경우
 7-bit i2c 주소는 0x1D (0001 1101) 이고, 마지막 bit는 R/W bit(0: write, 1: read)로 사용된다. 따라서 write 주소는 0x3A가 되고, read 주소는 0x3B가 된다.

[2] SDO/ALT ADDRESS를 Low로 할 경우

7-bit i2c 주소는 0x53(0101 0011)이고, 마지막 bit는 R/W bit(0: write, 1: read)로 사용된다. 따라서 write 주소는 0xA6이 되고, read 주소는 0xA7이 된다.

Device driver를 직접 구현할까 하다가 인터넷을 좀 뒤져 보니, 아래 예제 코드가 보인다. 직접 만드는 방법도 있겠으나, (제대로 동작하는지) 일단 얘를 먼저 돌려 보도록 한다.

https://github.com/imxieyi/esp32-i2c-adxl345

cd esp32-i2c-adxl345
idf.py menuconfig
Executing action: menuconfig
CMakeLists.txt not found in project directory /mnt/sda/workspace/ESP32/esp32-i2c-adxl345

어라, 에러가 난다. 가만히 보니 CMakeLists.txt 파일이 없고, Makefile만 보인다. 아무래도 예전 방식으로 만들어진 코드인 듯 보인다. 그렇다면, 다른 코드를 참조하여 Makefile, component.mk 파일 등을 제거한 후,  CMakeLists.txt를 새로 추가해 보도록 하자.

mv Makefile Makefile.ORIG
$ vi CMakeLists.txt 
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32-i2c-adxl345)
~

$ vi main/CMakeLists.txt 
idf_component_register(SRCS "esp32_i2c_adxl345_main.c"
      "adxl345.c"
      "i2c.c"
INCLUDE_DIRS "./include")
~
$ mv main/component.mk main/component.mk.ORIG

$ idf.py menuconfig
$ idf.py build
...
arameter -Wno-sign-compare -Wno-enum-conversion -gdwarf-4 -ggdb -Og -fmacro-prefix-map=/mnt/sda/workspace/ESP32/esp32-i2c-adxl345=. -fmacro-prefix-map=/mnt/sda/workspace/ESP32/esp-idf=/IDF -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -DconfigENABLE_FREERTOS_DEBUG_OCDAWARE=1 -std=gnu17 -Wno-old-style-declaration -MD -MT esp-idf/main/CMakeFiles/__idf_main.dir/esp32_i2c_adxl345_main.c.obj -MF esp-idf/main/CMakeFiles/__idf_main.dir/esp32_i2c_adxl345_main.c.obj.d -o esp-idf/main/CMakeFiles/__idf_main.dir/esp32_i2c_adxl345_main.c.obj -c /mnt/sda/workspace/ESP32/esp32-i2c-adxl345/main/esp32_i2c_adxl345_main.c
/mnt/sda/workspace/ESP32/esp32-i2c-adxl345/main/esp32_i2c_adxl345_main.c: In function 'sensorTask':
/mnt/sda/workspace/ESP32/esp32-i2c-adxl345/main/esp32_i2c_adxl345_main.c:23:35: error: 'portTICK_RATE_MS' undeclared (first use in this function); did you mean 'portTICK_PERIOD_MS'?
   23 |                 vTaskDelay(1000 / portTICK_RATE_MS);
      |                                   ^~~~~~~~~~~~~~~~
      |                                   portTICK_PERIOD_MS
/mnt/sda/workspace/ESP32/esp32-i2c-adxl345/main/esp32_i2c_adxl345_main.c:23:35: note: each undeclared identifier is reported only once for each function it appears in
[862/870] Building C object esp-idf/wifi_provisioning/CMakeFiles/__idf_wifi_provisioning.dir/src/manager.c.objninja: build stopped: subcommand failed.
HINT: You are maybe using pre FreeRTOS V8.0.0 APIs. The backward compatibility of such APIs is no longer enabled by default. Please turn on CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY explicitly to use such APIs.
ninja failed with exit code 1, output of the command is in the /mnt/sda/workspace/ESP32/esp32-i2c-adxl345/build/log/idf_py_stderr_output_15455 and /mnt/sda/workspace/ESP32/esp32-i2c-adxl345/build/log/idf_py_stdout_output_15455
...

Build를 해보니, 간단한 에러가 발생한다. 아래와 같이 코드 수정을 해 본다.

[그림 2.13] esp32_i2c_adxl345_main.c 코드 수정

다시 build해 보니, 이번에는 성공적으로 build가 된다.

[그림 2.14] adxl345 i2c example 정상 build 모습

flash writing하여 동작을 확인해 본다.
$ idf.py -p /dev/ttyUSB0 flash
idf.py -p /dev/ttyUSB0 monitor

(놀랄 것도 없이) I2C read error가 보인다.

[그림 2.15] ADXL345 가속도 센서 동작 모습 - i2c 실패

코드를 들여다 보니, GPIO 설정이 시험 내용과 맞지 않다. 이 부분을 아래와 같이 수정해 보자.

#if 0 /* ORIG_CODE */
#define SCL_PIN GPIO_NUM_17
#define SDA_PIN GPIO_NUM_16
#else
#define SCL_PIN GPIO_NUM_22
#define SDA_PIN GPIO_NUM_21
#endif

OK, 이번에는 제대로 동작한다.

[그림 2.16] ADXL345 가속도 센서 동작 모습

d) ADXL345를 spi 포트에 연결하기

지금까지 adxl345 i2c 드라이버의 동작을 확인해 보았으니, 이번에는 ADXL345를 SPI 확장 핀에 연결한 후, SPI driver를 구현해 볼 차례이다. 이 부분은 독자 여러분의 몫이다. [ESP32 Course 과제 1] 😛


[그림 2.17] ESP32-WROOM-32 보드와 ADXL345를 SPI로 연결한 모습 [출처: 참고문헌 6]



<참조 site>
[3] https://www.mischianti.org/2022/08/13/gy-291-adxl345-i2c-spi-accelerometer-with-interrupt-for-esp32-esp8266-stm32-and-arduino/
[4] ...

3. ESP32 보드에 WireGuard 올리기

이번 장에서는 esp32 보드에 wireguard를 올리는 과정을 소개해 보고자 한다.

a) FreeRTOS & LWIP용 Wireguard 
FreeRTOS & lwip tcp/ip stack 기반의 초소형 chipset용 wireguard open source를 찾던 중, 반가운 것을 하나 발견했다. 😍   


바로, Daniel Hope이 2021년에 작성한 코드로 wireguard를 lwip 기반 위에서 돌아가도록 해 주고 있다. 아래 두 link는 이 코드를 기반으로 약간의 변형을 가한 example들이다.



이 중 마지막 source(esp_wireguard)가 esp32에 맞게 porting된 코드인지라, 얘를 가지고 esp32 board에서 동작 시험을 진행해 보도록 하자.

________________________________________________________________________________
<여기서 잠깐>
Linux 사용자 영역에서 동작하는 C 기반의 wireguard를 만들고자 한다면 ?
Linux용 wireguard는 kernel version과 Go version이 이미 구비되어 있다. 하지만 상황이 여유치 않을 경우, 즉 linux kernel header를 구하기 어렵거나, flash memory 공간이 부족(Go로 만든 wireguard binary 크기가 큼)한 경우 등에는 사용자 영역에서 동작하는 C로 구현된 wireguard가 필요할 수도 있다. 이 경우, 위의 코드를 Linux tcp/ip stack에서 돌아가도록 변형하는 것이 하나의 대안이 될 수 있다. 관심 있는 분들은 도전해 보시길... 😱

[그림 3.1] C 언어로 작성한 Linux 사용자 영역에서 동작하는 wireguard daemon 구동 모습
________________________________________________________________________________

자, 먼저 source를 내려 받은 후, build를 해 보도록 한다.

$ git clone  https://github.com/trombik/esp_wireguard
$ cd esp_wireguard/
$ cd examples/demo
$ idf.py menuconfig
 -> Component config -> WireGuard 선택
[그림 3.2] idf.py menuconfig - wireguard 설정 확인

$ idf.py build
idf.py -p /dev/ttyUSB0 flash

Wireguard code를 build 한 후, flash writing을 해 보면, (제일 먼저) 아래와 같은 RF calibration 관련 에러가 발생한다. 인터넷을 좀 뒤져 보니 이는 board로 들어가는 전원(power)이 딸려서 그런 것이란다. USB hub을 notebook(or PC)이 아니라, 5V 전원을 공급하는 것으로 교체 후, 다시 시도해 보니, 문제가 바로 해결되었다.

[그림 3.3] wireguard binary를 적용 후 부팅하는 모습 - RF calibration 적재 실패 에러

근데, 다시 아래와 같은 wifi AP(Access Point)에 연결할 수 없다는 에러가 보인다.

[그림 3.4] wireguard binary를 적용 후 부팅하는 모습 - wifi 연결 관련 에러 발생

아직, wi-fi SSID 등을 설정한 사실이 없으니, 이건 당연히 발생할 수 밖에 없는 에러이다. 그렇다면 Wi-Fi SSID 등은 어디에서 설정해 줄까 ?

idf.py menuconfig 명령을 실행해 보면, 아래와 같이 example  configuration 설정 화면이 보인다. 가만히 보니 이곳에 wi-fi 설정은 물론이고, wireguard 설정에 필요한 모든 내용이 담겨 있다. 

그렇다면 아래 network 구성도를 참조하여, 여기에 있는 값 들을 적절히 수정해 보도록 하자.

<참고 - WireGuard 구성>
ESP32 board(vpnip: 10.1.1.252) => Wi-Fi AP(GL-MV-1000-581) => Internet => AWS EC2(vpnip: 10.1.1.1)

$ idf.py menuconfig
 -> Example Configuration

[그림 3.5] wireguard example configuration 모습

📌 esp32용 curve25519 keypair는 Ubuntu PC 등에서 아래와 같이 생성하여 사용하면 된다.
wg genkey | tee ./privatekey | wg pubkey > ./publickey

다시 source code를 full build 후, flash writing을 해 보도록 한다.

$ idf.py fullclean
idf.py build
idf.py -p /dev/ttyUSB0 flash
flash memory에 bin file을 writing한다.

이후 아래 명령을 사용하여 동작 과정을 모니터링해 보자.
idf.py -p /dev/ttyUSB0 monitor
📌 위의 방법 대신 minicom(115200, 8N1)을 이용해도 된다.

[그림 3.6] Wi-Fi AP에 정상 연결된 모습

어라, 근데, wifi 연결 이후에 아래와 같은 panic 에러가 발생하면서 시스템이 반복적으로 reset이 된다. 왜 그럴까 ?

[그림 3.7] wireguard panic error 발생 모습
📌 esp-idf는 위와 같이 back trace 정보를 자동 출력해 주므로 debugging하는데 많은 도움이 된다.

내용을 자세히 보면, lwip/esp_netif_lwip.c 파일 esp_netif_internal_dhcpc_cb 함수에서 뜬금 없이 죽는다. 왜 일까 ?

0x400f7662: esp_netif_internal_dhcpc_cb at /home/chyi/workspace/ESP32/esp-idf/components/esp_netif/lwip/esp_netif_lwip.c:1195

0x400f7736: netif_callback_fn at /home/chyi/workspace/ESP32/esp-idf/components/esp_netif/lwip/esp_netif_lwip.c:117

0x400e4adf: netif_invoke_ext_callback at /home/chyi/workspace/ESP32/esp-idf/components/lwip/lwip/src/core/netif.c:1819

0x400e4b8a: netif_set_addr at /home/chyi/workspace/ESP32/esp-idf/components/lwip/lwip/src/core/netif.c:733

0x400e4cce: netif_add at /home/chyi/workspace/ESP32/esp-idf/components/lwip/lwip/src/core/netif.c:376

0x400d6ff8: esp_wireguard_netif_create at /home/chyi/workspace/ESP32/esp_wireguard/examples/demo/components/esp_wireguard/src/esp_wireguard.c:185

0x400d7294: esp_wireguard_connect at /home/chyi/workspace/ESP32/esp_wireguard/examples/demo/components/esp_wireguard/src/esp_wireguard.c:240

0x400d6b8a: wireguard_setup at /home/chyi/workspace/ESP32/esp_wireguard/examples/demo/main/main.c:79 (discriminator 13)

0x400d6d9c: app_main at /home/chyi/workspace/ESP32/esp_wireguard/examples/demo/main/main.c:381

0x40156ebb: main_task at /home/chyi/workspace/ESP32/esp-idf/components/freertos/app_startup.c:208 (discriminator 13)

0x4008b9b5: vPortTaskWrapper at /home/chyi/workspace/ESP32/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:162

한참을 debugging 한 끝에, esp_netif code에 결함이 있음을 알게 되었고, 아래와 같이 코드 수정하여 임시로 해결하였다.


[그림 3.8] lwip 버그 수정 내용

(이부분은 나중에 작업한 것임) 또한, 중요한 부분은 아니나, while loop을 돌면서 반복적으로 wireguard 연결을 끊고 다시 연결하는 코드가 있어 아래와 같이 재연결하지 않도록 막아 보기로 한다.

[그림 3.9] wireguard connect & disconnect 반복 routine 제거

다시 source code를 build 후, flash writing을 해 보도록 한다.

idf.py build
idf.py -p /dev/ttyUSB0 flash
idf.py -p /dev/ttyUSB0 monitor

와우, 드디어 동작한다.

[그림 3.10] esp32 board 상에서 aws ec2 wireguard로 ping이 되는 모습

당연한 거지만, aws ec2에도 wireguard가 동작하고 있어야 한다. Ubuntu 상에서의 wireguard 설정과 관련해서는 이미 여러 차례 설명한 바 있으므로, 자세한 설정은 생략하기로 한다.



b) ADXL345 driver code와 Wireguard 통합하기
이번 절에서는 2장에서 검증한 i2c driver code와 이번 장에서 설명한 Wireguard code를 하나로 통합한 후, adxl345 sensor를 통해 감지된 3축 가속도 센서 정보를 VPN server로 전달하는 예제를 작성해 보기로 하겠다.

<TBD> [ESP32 Course 과제 2] 😛
[1] ESP32 board 상에 task를 하나 만들고 udp로 sensor 정보를 서버로 전달하는 routine(client) 구현
[2] AWS EC2 상에 udp server를 하나 만들고, client로 부터 온 내용을 출력
[3] client & server 간에 전송되는 message format(규격)을 정의해야 함.


4. ESP32를 NAT Gateway로 만들기

ESP32는 IoT device용 초소형 chipset이다. 그런데, esp32 board를 NAT gateway로 만드는 아주 흥미로운 내용이 있어 여기에 소개해 보고자 한다. 😗 사실 embedded linux를 올리고, netfilter를 사용하면 아주 쉽게 NAT gateway를 만들 수 있지만, esp32(정확히는 LWIP)라면 사정이 완전히 달라진다.

[1] https://github.com/jonask1337/esp-idf-nat-example

[2] https://github.com/martin-ger/esp32_nat_router


[그림 4.1] ESP32 NAT Gateway [출처: 위의 site [1]]


a) LWIP NAT feature enable하기
최신 esp32 sdk에는 이미 위의 site에서 말하는 NAT 관련 code가 포함되어 있는 것으로 보인다. 따라서,  idf.py menuconfig 한 후, Component -> LWIP 아래에서 아래와 같이 3가지 설정을 enable해 주도록 하자.

     [*] Enable copy between Layer2 and Layer3 packets
     [*] Enable IP forwarding
     [*]     Enable NAT (new/experimental)

[그림 4.2] idf.py menuconfig - lwip NAT enable 모습(1)

그런데, esp-idf 최신 version 환경에서 build해 보니, (일일이 해결하기에 너무 많은) 에러가 발생한다. 아무래도 esp-idf의 적당한 버젼을 찾아서 build를 해 보아야 겠다.


b) NAT Gateway 동작 확인하기

몇가지 시도 끝에 esp-idf v4.4에서 제대로 build가 되는 것을 알았다. 여기에 그 내용을 정리해 보기로 한다.
📌 반드시 v4.4에서만 build가 되는 것은 아니므로, 다른 version으로도 시도해 보기 바란다.
📌 3장에서 소개한 wireguard code도 v4.4 환경에서 build가 된다.

$ mkdir stable; cd stable
$ git clone --recursive https://github.com/espressif/esp-idf.git
$ git branch
* master
$ git checkout "release/v4.4"
$ git branch
  master
* release/v4.4
$ git submodule update --init --recursive
$ ./install.sh esp32
📌 현재 터미널 상태가 ". ./export.sh"을 시도한 상태라면, 이 명령 실행시 에러가 발생하게 되므로, 다른 terminal을 띄우고 이 명령을 다시 실행해 주기 바란다.

$ cd ..
$ git clone https://github.com/martin-ger/esp32_nat_router
$ cd esp32_nat_router
$ idf.py menuconfig

[그림 4.3] idf.py menuconfig - lwip NAT enable 모습(2)

$ idf.py build
...
r/bootloader.bin
Bootloader binary size 0x5b40 bytes. 0x14c0 bytes (19%) free.
[1046/1058] Building C object esp-idf/main/CMakeFiles/__idf_main.dir/http_server.c.obj
In file included from /home/chyi/workspace/ESP32/stable/esp32_nat_router/main/http_server.c:23:
/home/chyi/workspace/ESP32/stable/esp32_nat_router/main/pages.h:167:70: warning: backslash-newline at end of file
 #define LOCK_PAGE "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n\
                                                                       
/home/chyi/workspace/ESP32/stable/esp32_nat_router/main/http_server.c:258:13: warning: 'stop_webserver' defined but not used [-Wunused-function]
 static void stop_webserver(httpd_handle_t server)
             ^~~~~~~~~~~~~~
[1057/1058] Generating binary image from built executable
esptool.py v3.3.2
Creating esp32 image...
Merged 25 ELF sections
Successfully created esp32 image.
Generated /home/chyi/workspace/ESP32/stable/esp32_nat_router/build/esp32_nat_router.bin
[1058/1058] cd /home/chyi/workspace/ESP32/stable/esp32_n...ESP32/stable/esp32_nat_router/build/esp32_nat_router.bin
esp32_nat_router.bin binary size 0xe3e80 bytes. Smallest app partition is 0x100000 bytes. 0x1c180 bytes (11%) free.

Project build complete. To flash, run this command:
/home/chyi/.espressif/python_env/idf4.4_py3.8_env/bin/python ../esp-idf/components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32  write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/esp32_nat_router.bin
or run 'idf.py -p (PORT) flash'
와우, build가 정상적으로 진행된다.

$ idf.py -p /dev/ttyUSB1 flash
$ idf.py -p /dev/ttyUSB1 monitor

Serial console로 동작 모습을 확인해 본다.

[그림 4.4] idf.py -p /dev/ttyUSB1 monitor 모습

esp32> 로 시작하는 cli 명령어도 보인다.

[그림 4.5] idf.py -p /dev/ttyUSB1 monitor 모습(2) - CLI


그리고, 보다 재밌는 것은 web 설정 화면이 있다는 것이다.

[그림 4.6] http://192.168.4.1 접속 모습

아래 네트워크 구성 정보를 토대로 적절히 설정 후, Connect 버튼을 눌러 보자.

<네트워크 구성>

Ubuntu PC(Wi-Fi) => ESP32 Router(AP | Station) =>  Access Point(192.168.7.1)


<Ubuntu PC>

$ ping 8.8.8.8

와우, ping이 된다.  인터넷에 접속해 보니, 조금 느리긴 해도 그럭저럭 동작한다.


c) NAT 기능과 Wireguard 기능 통합하기

NAT router 기능과 Wireguard code를 하나로 통합하면, Linux 처럼 동작할까 ? 당연히 그렇게 될 것 같진 않지만 한번 시도는 해 볼 만한 일이다.
<TBD> [ESP32 Course 과제 3] 😛



5. PQC 알고리즘 Porting하기 - CRYSTALS Kyber & Dilithium

이번 장에서는 ESP32에 맞게 PQC(Post Quantum Cryptography) 알고리즘을 porting하는 과정을 소개해 보고자 한다. PQC와 관련해서는 이전 blog post의 글(4장)을 먼저 참조하기 바란다.

아직까지 ESP32에 PQC 알고리즘(특히 NIST 표준으로 정해진 CRYSTALS Kyber, Dilithium 등)을 porting한 내용이 인터넷 어디를 뒤져봐도 보이질 않는다. 다만, 한가지 ESP32에 Saber(NIST 3 round 탈락)알고리즘을 올려 놓은게 전부인 듯 하다. 이 내용과 관련해서는 아래 site를 참조하도록 하자.

https://eprint.iacr.org/2019/1453.pdf
https://github.com/SABERONESP32/SABERONESP32

a) Kyber768 코드를 ESP32용으로 porting하기

우선은 PQClean code를 esp32에서 돌려 볼 수 있도록 해 보자.

<Porting 절차>
$ mkdir kybkyber768-example
$ cd kyber768-example
$ vi CMakeLists.txt
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(kyber768-example)
~

$ mkdir main; cd main
$ ls -l
drwxrwxr-x 2 chyi chyi  4096 Feb  8 13:38 .
drwxrwxr-x 4 chyi chyi  4096 Feb  8 11:34 ..
-rw-rw-r-- 1 chyi chyi   280 Feb  8 13:37 CMakeLists.txt     //신규 생성
-rw-rw-r-- 1 chyi chyi   210 Feb  8 11:26 LICENSE
-rw-rw-r-- 1 chyi chyi   653 Feb  8 11:31 api.h
-rw-rw-r-- 1 chyi chyi  2699 Feb  8 11:31 cbd.c
-rw-rw-r-- 1 chyi chyi   336 Feb  8 11:31 cbd.h
-rw-rw-r-- 1 chyi chyi 29027 Feb  8 11:31 fips202.c    //PQClean common/ 에서 복사해 옴.
-rw-rw-r-- 1 chyi chyi  5567 Feb  8 11:31 fips202.h    //PQClean common/ 에서 복사해 옴.
-rw-rw-r-- 1 chyi chyi 13065 Feb  8 11:31 indcpa.c
-rw-rw-r-- 1 chyi chyi   938 Feb  8 11:31 indcpa.h
-rw-rw-r-- 1 chyi chyi  4684 Feb  8 11:31 kem.c
-rw-rw-r-- 1 chyi chyi   733 Feb  8 11:31 kem.h
-rw-rw-r-- 1 chyi chyi  2537 Feb  8 11:31 kex.c
-rw-rw-r-- 1 chyi chyi  1035 Feb  8 11:31 kex.h
-rw-rw-r-- 1 chyi chyi  1487 Feb  8 13:38 main.c    //신규 생성, app_main() 함수 필요함
-rw-rw-r-- 1 chyi chyi  5515 Feb  8 11:31 ntt.c
-rw-rw-r-- 1 chyi chyi   388 Feb  8 11:31 ntt.h
-rw-rw-r-- 1 chyi chyi  1060 Feb  8 11:31 params.h
-rw-rw-r-- 1 chyi chyi 11080 Feb  8 11:31 poly.c
-rw-rw-r-- 1 chyi chyi  1542 Feb  8 11:31 poly.h
-rw-rw-r-- 1 chyi chyi  7061 Feb  8 11:31 polyvec.c
-rw-rw-r-- 1 chyi chyi   974 Feb  8 11:31 polyvec.h
-rw-rw-r-- 1 chyi chyi  2601 Feb  8 13:29 randombytes.c  //esp32 random number 생성 코드를 넣어야 함.
-rw-rw-r-- 1 chyi chyi   362 Feb  8 11:44 randombytes.h
-rw-rw-r-- 1 chyi chyi  1390 Feb  8 11:31 reduce.c
-rw-rw-r-- 1 chyi chyi   323 Feb  8 11:31 reduce.h
-rw-rw-r-- 1 chyi chyi  1899 Feb  8 11:31 symmetric-shake.c
-rw-rw-r-- 1 chyi chyi  1099 Feb  8 11:31 symmetric.h
-rw-rw-r-- 1 chyi chyi  1563 Feb  8 11:31 verify.c
-rw-rw-r-- 1 chyi chyi   320 Feb  8 11:31 verify.h

$ vi CMakeLists.txt
idf_component_register(SRCS "main.c"
                            "cbd.c"
                            "fips202.c"
                            "indcpa.c"
                            "kem.c"
                            "ntt.c"
                            "poly.c"
                            "polyvec.c"
                            "reduce.c"
                            "symmetric-shake.c"
                            "verify.c"
                            "randombytes.c"
                            "kex.c"
                     INCLUDE_DIRS "./")
~
$ cd ..
$ idf.py menuconfig
$ idf.py build
OK, 여기까지 정상적으로 build가 된다.

[그림 5.1] kyber768-example.bin 파일 생성 모습

다음으로 할 일은 app_main() 함수 내에 kyber 키 생성 및 key 교환 관련 아래의 API 등을 호출하는 코드를 추가하는 일이다. 

int PQCLEAN_KYBER768_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
int PQCLEAN_KYBER768_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
int PQCLEAN_KYBER768_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);

Hmm, 너무 많은 내용을 적으려다 보니 ... 좀 지친다. 😭  따라서 이 부분은 독자 여러분의 몫으로 남겨 두겠다.

b) Dilithium3 코드를 ESP32용으로 porting하기
이번에는 Dilithium3 code를 porting해 보자. 방식은 kyber768과 별반 다르지 않다.

chyi@sun:~/workspace/ESP32/stable/dilithium3-example$ ls -la
total 64
drwxrwxr-x 4 chyi chyi  4096 Feb  8 14:52 .
drwxrwxr-x 8 chyi chyi  4096 Feb  8 14:46 ..
-rw-rw-r-- 1 chyi chyi   243 Feb  8 14:47 CMakeLists.txt    //신규로 추가
drwxrwxr-x 2 chyi chyi  4096 Feb  8 14:56 main
chyi@sun:~/workspace/ESP32/stable/dilithium3-example$ cd main/
chyi@sun:~/workspace/ESP32/stable/dilithium3-example/main$ ls -la
total 168
drwxrwxr-x 2 chyi chyi  4096 Feb  8 14:56 .
drwxrwxr-x 4 chyi chyi  4096 Feb  8 14:52 ..
-rw-rw-r-- 1 chyi chyi   235 Feb  8 14:56 CMakeLists.txt   //신규로 추가
-rw-rw-r-- 1 chyi chyi   210 Feb  8 14:52 LICENSE
-rw-rw-r-- 1 chyi chyi   989 Feb  8 14:47 api.h
-rw-rw-r-- 1 chyi chyi  5567 Feb  8 14:54 fips202.h     // PQClean common 디렉토리에서 복사
-rw-rw-r-- 1 chyi chyi  1460 Feb  8 14:52 main.c
-rw-rw-r-- 1 chyi chyi  4893 Feb  8 14:47 ntt.c
-rw-rw-r-- 1 chyi chyi   236 Feb  8 14:47 ntt.h
-rw-rw-r-- 1 chyi chyi  8682 Feb  8 14:47 packing.c
-rw-rw-r-- 1 chyi chyi  1696 Feb  8 14:47 packing.h
-rw-rw-r-- 1 chyi chyi   978 Feb  8 14:47 params.h
-rw-rw-r-- 1 chyi chyi 28621 Feb  8 14:47 poly.c
-rw-rw-r-- 1 chyi chyi  2197 Feb  8 14:47 poly.h
-rw-rw-r-- 1 chyi chyi 15024 Feb  8 14:47 polyvec.c
-rw-rw-r-- 1 chyi chyi  2710 Feb  8 14:47 polyvec.h
-rw-rw-r-- 1 chyi chyi  1601 Feb  8 14:55 randombytes.c    //esp32용으로 구현
-rw-rw-r-- 1 chyi chyi   362 Feb  8 14:55 randombytes.h
-rw-rw-r-- 1 chyi chyi  1971 Feb  8 14:47 reduce.c
-rw-rw-r-- 1 chyi chyi   434 Feb  8 14:47 reduce.h
-rw-rw-r-- 1 chyi chyi  2785 Feb  8 14:47 rounding.c
-rw-rw-r-- 1 chyi chyi   424 Feb  8 14:47 rounding.h
-rw-rw-r-- 1 chyi chyi 12304 Feb  8 14:47 sign.c
-rw-rw-r-- 1 chyi chyi   925 Feb  8 14:47 sign.h
-rw-rw-r-- 1 chyi chyi   820 Feb  8 14:47 symmetric-shake.c
-rw-rw-r-- 1 chyi chyi  1254 Feb  8 14:47 symmetric.h

$ cd dilithium3-example
$ idf.py menuconfig
$ idf.py build
$ cd build

[그림 5.2] dilithium3-example.bin 파일 생성 모습

다음으로 할 일은 app_main() 함수 내에 digital 서명 키 생성 및 검증에 관한 아래의 API 등을 호출하는 코드를 추가하는 일이다. 

int PQCLEAN_DILITHIUM3_CLEAN_crypto_sign_keypair(uint8_t *pk, uint8_t *sk);   //서명용 키 생성

int PQCLEAN_DILITHIUM3_CLEAN_crypto_sign(             //서명 함수
    uint8_t *sm, size_t *smlen,
    const uint8_t *m, size_t mlen, const uint8_t *sk);

int PQCLEAN_DILITHIUM3_CLEAN_crypto_sign_open(    //verify 함수
    uint8_t *m, size_t *mlen,
    const uint8_t *sm, size_t smlen, const uint8_t *pk);


c) ESP32 dual core를 활용하도록 Kyber & Dilithium 알고리즘 최적화 하기

지금까지 설명한 porting 방식은 esp32의 특징(dual core 등)을 전혀 살린 것이 아니다. 그저 단순히 PQClean code를 cross-compile한 것에 불가하다. 그렇다면 (앞선 Saber의 예에서 처럼) esp32의 h/w적인 특징을 살려 보다 향상된 code를 만들기 위해서는 어떻게 해야 할까 ?

<TBD - 난이도 최상> [ESP32 Course 과제 4] 😛


__________________________________________________________________

이상으로 ESP32 board에 i2c & spi device(ADXL345)를 하나 붙이고, wireguard vpn  tunnel을 통해 sensor data를 서버로 올리는 과정을 정리해 보았다. 언제나 그렇듯 부족한 부분은 (기약은 없지만) 다음 글을 통해서 보충해 나가도록 하겠다. "May the Source be with You" 😋


6. References

[1] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
[2] https://www.es.co.th/Schemetic/PDF/ESP32.PDF 
[4] https://github.com/hepingood/adxl345/blob/master/README_ko.md
[5] https://learn.sparkfun.com/tutorials/adxl345-hookup-guide/all
[6] https://www.mischianti.org/2022/08/13/gy-291-adxl345-i2c-spi-accelerometer-with-interrupt-for-esp32-esp8266-stm32-and-arduino/
[7] And Google~


Slowboot


댓글 없음:

댓글 쓰기