2025년 12월 23일 화요일

Rust로 구현한 WireGuard 코드 분석하기

Rust는 C/C++ 급의 속도와 (C/C++에서는 하지 못하는) 메모리 안정성을 보장한다는 커다란 장점이 있음에도 불구하고, 언어 자체의 난이도가 높은 탓에 정복하기가 쉽지 않은 언어로 인식되고 있다. 이번 시간에는 Rust로 구현한 wireguard code를 분석해 봄으로써, Rust를 좀 더 깊히 이해할 수 있는 시간을 가져 보고자 한다. 😎

Rust WireGuard running on OpenWrt One Router

목차
1. OpenWrt One Router
2. Wireguard-rs 프로젝트 소개
3. Boringtun 프로젝트 소개
4. todo!
5. References


Rust는 Linux kernel(Android 포함), Zephyr RTOS는 물론이고, Windows 등에서 채택되어 사용되고 있다. 뿐만 아니라, C/C++가 독점해온 Embedded 분야에서도 그 입지를 넓혀가고 있다. Rust는 이제 더 이상 선택이 아닌 필수가 되었다. 자, 그럼 지금부터 Rust의 세계로 떠나 보도록 하자. 🚀


1. OpenWrt One Router
Banana Pi 팀과 OpenWrt open source community에서 만든 OpenWrt Router 보드를 하나 입수했다. 이름하여 OpenWrt One~  이번 장에서는 rust program을 테스트하기 위한 target 장치로, OpenWrt One router를 소개해 보고자 한다.

1.1 OpenWrt One Router H/W Review

[그림 1.1] OpenWrt One Router 외관(1)
📌 라우터 외관은 살짝 촌스럽다.

Key Features

  • OpenWrt official board and support
  • MediaTek MT7981B (Filogic 820) SoC
  • Dual-band WiFI 6 via MediaTek MT7976C (2×2 2.4 GHz + 3×3 5Ghz)
  • 1GB DDR4
  • 1 x 2.5GbE RJ45 port and |1 x Gigabit Ethernet RJ45 port
  • 256 MiB SPI NAND and 16 MiB SPI NOR flash are used to make the board almost unbrickable
  • M.2 2242/2230 socket for NVMe SSD (PCIe gen 2 x1)
  • RTC support
  • PoE support
  • MikroBUS socket for expansion modules

뚜겅을 열어 보니, (발열이 심한지) 커다란 heat sink가 눈에 들어온다.

[그림 1.2] OpenWrt One Router 외관(2)

OpenWrt One board의 top/bottom view가 궁금하다면, 아래 2개의 그림이 도움이 될 것이다.
[그림 1.3] OpenWrt One 보드 - Top view [출처 - 참고문헌 14]

[그림 1.4] OpenWrt One 보드 - Bottom view[출처 - 참고문헌 14]

한편, 아래 그림은 MT7981B SoC의 블럭도를 보여준다.

[그림 1.5] MT7981B SoC H/W Block Diagram [출처 - 참고문헌 17]

1.2 OpenWrt One Router Booting 하기
Serial console을 실행한 상태에서, 부팅하는 모습을 capture 보았다.

minicom -D /dev/ttyACM0 -b 115200

[그림 1.6] 부팅 모습 - u-boot -> linux kernel(1)

[그림 1.7] 부팅 모습 - u-boot -> linux kernel(2)

[그림 1.8] 부팅 모습 - u-boot -> linux kernel(3)

[그림 1.9] 부팅 후 ps -w 명령 실행 모습

(OpenWrt이므로) 부팅이 완료된 후에는 web browser로 LuCI webUI에 접근 가능하다.

http://192.168.1.1

[그림 1.10] 부팅 후 web UI 접속 모습


1.3 OpenWrt/LEDE S/W Architecutre 소개
OpenWrt의 전체 build system 및 주요 s/w 구성 요소를 그림으로 확인해 보면 다음과 같다. 때로는 장황한 설명보다 한장의 그림이 더 좋을 때가 있다. 😋

[그림 1.11] OpenWrt/LEDE build system 개요 [출처 - 참고문헌 20]


[그림 1.12] OpenWrt/LEDE S/W Stack [출처 - 참고문헌 20]

[그림 1.13] OpenWrt/LEDE S/W Network Overview [출처 - 참고문헌 20]

1.4 OpenWrt One Router용 source code build 하기


<Ubuntu 22.04 LTS>
$ git clone https://github.com/openwrt/openwrt
$ cd openwrt
$ git pull
$ git checkout openwrt-24.10
$ ./scripts/feeds update -a
$ ./scripts/feeds install -a

make menuconfig

[그림 1.14] make menuconfig

make 
-j$(nproc)

build 시 에러가 발생한다면, 아래와 같이 하여 error를 확인할 수 있다.

$ make V=99 -j1

<build 결과>
$ cd bin/targets/mediatek/filogic
$ ls -la
합계 74880
drwxr-xr-x 3 chyi chyi     4096  5월 20 21:26 .
drwxr-xr-x 3 chyi chyi     4096  5월 20 17:59 ..
-rw-r--r-- 1 chyi chyi      204  5월 20 21:14 config.buildinfo
-rw-r--r-- 1 chyi chyi      368  5월 20 21:14 feeds.buildinfo
-rw-r--r-- 1 chyi chyi   210365  5월 20 21:17 mt7981-ram-ddr3-bl2.bin
-rw-r--r-- 1 chyi chyi   210365  5월 20 21:17 mt7981-ram-ddr4-bl2.bin
-rw-r--r-- 1 chyi chyi   189656  5월 20 21:18 mt7986-ram-ddr3-bl2.bin
-rw-r--r-- 1 chyi chyi   189656  5월 20 21:18 mt7986-ram-ddr4-bl2.bin
-rw-r--r-- 1 chyi chyi   239053  5월 20 21:18 mt7988-ram-comb-bl2.bin
-rw-r--r-- 1 chyi chyi 21757952  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-factory.ubi
-rw-r--r-- 1 chyi chyi  8585216  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-initramfs.itb
-rw-r--r-- 1 chyi chyi   350552  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-nor-bl31-uboot.fip
-rw-r--r-- 1 chyi chyi 10158080  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-nor-factory.bin
-rw-r--r-- 1 chyi chyi   222893  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-nor-preloader.bin
-rw-r--r-- 1 chyi chyi   961017  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-snand-bl31-uboot.fip
-rw-r--r-- 1 chyi chyi 22806528  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-snand-factory.bin
-rw-r--r-- 1 chyi chyi   234341  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-snand-preloader.bin
-rw-r--r-- 1 chyi chyi 10486565  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one-squashfs-sysupgrade.itb
-rw-r--r-- 1 chyi chyi     4103  5월 20 21:26 openwrt-mediatek-filogic-openwrt_one.manifest
drwxr-xr-x 3 chyi chyi    12288  5월 20 21:26 packages
-rw-r--r-- 1 chyi chyi     3387  5월 20 21:26 profiles.json
-rw-r--r-- 1 chyi chyi     1980  5월 20 21:26 sha256sums
-rw-r--r-- 1 chyi chyi       18  5월 20 21:14 version.buildinfo


<kernel compilation 정리>
linux kernel source code 위치 =>
  openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-6.6.65

make kernel_menuconfig
  => kernel menuconfig

[그림 1.15] make kernel_menuconfig

make target/linux/clean
  => kernel clean

make target/linux/compile V=s -j1
  => 조용히 순차적으로 kernel compile 수행(kernel build 시 error를 찾기 위해 시도)
or
make target/linux/compile V=99 -j$(nproc) 2>&1 | tee build.log
  => kernel build 과정을 자세히 출력하고, log file에 build 과정을 저장하기
_______________________________________________________

Target board가 준비되었으니, 이제 부터는 Rust로 구현한 wireguard project를 본격적으로 분석해 보기로 하자.



2. Wireguard-rs 프로젝트 소개
wireguard-rs project는 wireguard 진영에서 공식으로 인정하는 rust 기반 wireguard project이다. 하지만, 아쉽게도 5년전에 commit한 내용을 끝으로 더 이상은 개발이 진행되지 않고 있는 듯하다. 😂


2.1 wireguard-rs project 돌려 보기
우선, 제대로 동작할 것인지 확인해 보도록 하자.

<How to build on Ubuntu 22.04 LTS>
$ git clone https://github.com/WireGuard/wireguard-rs
$ cd wireguard-rs
cargo build --relase

$ cd target/release
$ sudo ./wireguard-rs wg1
 -> background로 돌면서 자동으로 wg1 interface가 생성된다.
📌 wireguard-rs binary의 크기는 2656216 bytes 즉, 2.65MB 정도로 작은편이다(Golang version 대비 작은편임).

<How to run wireguard>
$ ps aux|grep wireguard-rs
nobody     15126  0.1  0.0 3588728 2688 ?        Sl   12:07   0:00 ./wireguard-rs wg1

$ sudo ip address add dev wg1 10.1.2.100/24
$ sudo ip link set up dev wg1
$ sudo wg set wg1 listen-port 59760 private-key ./privatekey peer fQEC0IgJjbWRotaunnKlOdhJw+kKzL8q1PYN/5DoWGs= allowed-ips 10.1.2.0/24 endpoint 192.168.1.169:51820

$ sudo wg show
interface: wg1
  public key: 6+INX3ZlitudBP9j7dgKiWW7xl95sVoUNx6TcRsykT8=
  private key: (hidden)
  listening port: 59760

peer: fQEC0IgJjbWRotaunnKlOdhJw+kKzL8q1PYN/5DoWGs=
  endpoint: 192.168.1.169:51820
  allowed ips: 10.1.2.0/24
  latest handshake: 1 second ago
  transfer: 436 B received, 380 B sent

$ ping 10.1.2.1
PING 10.1.2.1 (10.1.2.1) 56(84) bytes of data.
64 bytes from 10.1.2.1: icmp_seq=1 ttl=128 time=13.3 ms
64 bytes from 10.1.2.1: icmp_seq=2 ttl=128 time=2.52 ms
64 bytes from 10.1.2.1: icmp_seq=3 ttl=128 time=5.06 ms
...

한편, Peer 쪽 설정은 다음과 같으며, 자세한 설정 과정은 생략한다(당연히 동시에 설정해 주어야 한다).

[그림 2.1] Peer 설정 - Wireguard windows client


2.2 wireguard-rs를 openwrt one board에서 돌려 보기
이번에는 wireguard-rs를 aarch64용(openwrt one router)으로 cross compile해 보자.

$ vi ~/.cargo/config.toml
  -> 아래의 내용을 담은 파일을 하나 만들자.
[target.aarch64-unknown-linux-musl]
linker = "/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin/aarch64-openwrt-linux-musl-gcc"
...

$ rustup target add aarch64-unknown-linux-musl

$ export PATH=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin:$PATH
$ export STAGING_DIR=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl
$ export TARGET_CC=aarch64-openwrt-linux-musl-gcc

$ export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin/aarch64-openwrt-linux-musl-gcc

$ cargo clean
$ cargo build --target=aarch64-unknown-linux-musl
or
$ cargo build --target=aarch64-unknown-linux-musl --release

--> (헐 생각보다 많은) build error 발생한다. 일단 pass! 😂

2.3 wireguard-rs 주요 코드 분석
기본적인 동작 과정은 확인해 보았으니, 이번에는 주요 코드를 분석해 볼 차례이다. wireguard-rs github에는 아래의 그림이 하나 있을 뿐인데, (코드를 들여다 보니) 이 그림이 많은 점을 암시하고 있음을 알 수가 있다. 💬

[그림 2.2] wireguard-rs s/w architecture

<wireguard-rs 코드 분석 기준>
[1] main routine
[2] handshake module
[3] router module
[4] timers

(위의 4개의 category 관점에서) 대략적인 코드 분석은 진행해 보았으나, aarch64로 cross compile이 안되는 등 다수의 문제가 보이는 바, 여기에서 따로 정리하지는 않기로 한다.  😓


3. Boringtun 프로젝트 소개
Boringtun(터널 뚫기 정도로 해석하면 좋을 듯)은 (wireguard project랑은 무관하게) Cloudflare 개발진이 자체적으로 만든 open source project로 wireguard daemon(boringtun-cli)은 물론이고, library 형태로 다양한 OS를 지원하는 특징이 있다. 최신 master branch는 개선 중(실제로는 오랜 기간 작업을 안하고 있음)인 듯 보이므로, 여기에서는 latest stable 버젼인 v0.6.0을 기준으로 시험을 진행해 보기로 한다.

📌 Wireguard를 만든 Jason A. Donenfeld는 boringtun을 만든 Cloudflare 개발진이 wireguard-rs project에 합류(leading)해 주기를 원했으나, 결국은 성사되지 않았다. 😂

[그림 3.1] boringtun이 지원하는 platforms

3.1 boringtun project 돌려 보기
우선, 제대로 동작하는지를 확인해 보도록 하자.

<How to build on Ubuntu 22.04 LTS>
tar xvzf boringtun-boringtun-0.6.0.tar.gz
  -> Download boringtun-boringtun-0.6.0.tar.gz from https://github.com/cloudflare/boringtun
$ cd boringtun-boringtun-0.6.0

$ cargo build --bin boringtun-cli --release

<How to run wireguard>
$ cd target/release
$ sudo ./boringtun-cli wg1
BoringTun started successfully

$ ps aux|grep boringtun-cli
chyi       11482  0.0  0.0 278736  7380 ?        Sl   15:22   0:00 ./boringtun-cli wg1

$ sudo ip address add dev wg1 10.1.2.100/24
$ sudo ip link set up dev wg1
$ sudo wg set wg1 listen-port 59760 private-key ./privatekey peer fQEC0IgJjbWRotaunnKlOdhJw+kKzL8q1PYN/5DoWGs= allowed-ips 10.1.2.0/24 endpoint 192.168.1.169:51820

$ ping 10.1.2.1
PING 10.1.2.1 (10.1.2.1) 56(84) bytes of data.
64 bytes from 10.1.2.1: icmp_seq=1 ttl=128 time=31.1 ms
64 bytes from 10.1.2.1: icmp_seq=2 ttl=128 time=5.47 ms
64 bytes from 10.1.2.1: icmp_seq=3 ttl=128 time=27.4 ms
...

$ sudo wg show
interface: wg1
  public key: 6+INX3ZlitudBP9j7dgKiWW7xl95sVoUNx6TcRsykT8=
  private key: (hidden)
  listening port: 59760

peer: fQEC0IgJjbWRotaunnKlOdhJw+kKzL8q1PYN/5DoWGs=
  endpoint: 192.168.1.169:51820
  allowed ips: 10.1.2.0/24
  latest handshake: 56 years, 1 day, 6 hours, 24 minutes, 53 seconds ago
  transfer: 14.25 KiB received, 14.25 KiB sent

[그림 3.2] Peer 설정 - Wireguard windows client

Peer(windows client) 설정을 3장과 동일하게 유지한 상태에서 테스트해 보니, 정상 동작한다. 그런데, 희한하게도 boringtun-cli를 돌릴 경우에는 위와 같이 wg show의 interface 설정 정보가 좀 다르게 출력된다.

3.2 aarch64용으로 cross compile 하기
이번에는 boringtun을 1장에서 설명한 OpenWrt One router(aarch64)용으로 cross compile해 보자.

<Ubuntu 22.04 LTS PC>
$ vi ~/.cargo/config.toml
  -> 아래의 내용으로 구성된 config.toml 파일을 하나 만들자.

[target.aarch64-unknown-linux-musl]
linker = "/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin/aarch64-openwrt-linux-musl-gcc"
...

$ rustup target add aarch64-unknown-linux-musl

이후, 몇가지 aarch64-openwrt-linux-musl-gcc 관련 환경 설정을 진행하도록 한다.

$ export PATH=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin:$PATH
$ export STAGING_DIR=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl
$ export TARGET_CC=aarch64-openwrt-linux-musl-gcc

$ export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/mnt/hdd/workspace/mini_devices/bpi/openwrt/staging_dir/toolchain-aarch64_cortex-a53_gcc-14.2.0_musl/bin/aarch64-openwrt-linux-musl-gcc

이후, boringtun-cli를 build해 본다.

$ cargo clean
<debug mode로 build 하기>
$ cargo build --bin boringtun-cli --target=aarch64-unknown-linux-musl

<release mode로 build 하기>
$ cargo build --bin boringtun-cli --target=aarch64-unknown-linux-musl --release

$ cd target/aarch64-unknown-linux-musl/debug
$ file boringtun-cli
boringtun-cli: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

<library 형태로 build 하기>
$ cargo clean
cargo build --lib --no-default-features --release --target=aarch64-unknown-linux-musl
$ ls -l target/aarch64-unknown-linux-musl/release/*.a
-rw-rw-r--  2 chyi chyi 28533114 12월 23 11:23 libboringtun.a

<Target board>
boringtun-cli를 target board로 올려 동작하는지 확인해 보도록 하자.

root@OpenWrt:~/workspace# ./boringtun-cli -h

[그림 3.3] boringtun-cli -h 명령 실행 모습

OK, 실행은 된다. 그런데, 이 상태에서 option으로 interface name을 주어 실행해 보니, 아래와 같이 error가 발생한다. 😂

root@OpenWrt:~/workspace# ./boringtun-cli -f wg0
 2025-12-19T08:54:42.865319Z ERROR boringtun_cli: Failed to initialize tunnel, error: Socket(Os { code: 2, kind: )
   at boringtun-cli/src/main.rs:160


Target board를 확인해 보니, tun device file(/dev/net/tun)이 안 보인다.
(코드를 살짝 들여다 보니) 이런, 아무래도 OpenWrt One에 tun.ko 모듈이 설치되어 있지 않아 발생한 문제로 보인다.

$ make menuconfig
  -> kmon-tun을 Module로 선택한다.

[그림 3.4] kmod-tun module enable 하기

$ make -j8

$ find . -name "tun.ko" -print
./staging_dir/target-aarch64_cortex-a53_musl/root-mediatek/lib/modules/6.6.65/tun.ko
./build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/packages/.pkgdir/kmod-tun/lib/modules/6.6.65/tun.ko
./build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/packages/ipkg-aarch64_cortex-a53/kmod-tun/lib/modules/6.6.65/tun.ko
./build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-6.6.65/drivers/net/tun.ko

tun.ko를 target board로 복사하자.

$ cd build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-6.6.65/drivers/net

$ scp ./tun.ko root@192.168.1.1:~/workspace
root@192.168.1.1's password: 
tun.ko                                                               100%  489KB   9.1MB/s   00:00

tun.ko를 구동(insmod)하면, /dev/net/tun file도 자동 생성된다.

root@OpenWrt:~/workspace# insmod ./tun.ko 
[ 1436.907518] tun: Universal TUN/TAP device driver, 1.6

root@OpenWrt:~/workspace# ls -l /dev/net/tun
crw-------    1 root     root       10, 200 Dec 19 12:29 /dev/net/tun

이 상태에서 다시, boringtun-cli를 실행시킨다.

root@OpenWrt:~/workspace# ./boringtun-cli -f wg0
  2025-12-19T12:29:30.091485Z ERROR boringtun::device: Poll error, error: "Interrupted system call (os error 4)"
    at boringtun/src/device/mod.rs:268

  2025-12-19T12:29:30.091429Z ERROR boringtun_cli: Failed to drop privileges, error: DropPrivileges("Failed to per)
    at boringtun-cli/src/main.rs:168

  2025-12-19T12:29:30.091507Z ERROR boringtun::device: Poll error, error: "Interrupted system call (os error 4)"
    at boringtun/src/device/mod.rs:268

어라, 이번에는 또 다른 에러가 발생한다. 아무래도 drop-privileges 처리에 문제가 있는 듯하다.
root@OpenWrt:~/workspace# ./boringtun-cli --disable-drop-privileges wg0
BoringTun started successfully

OK, 일단 구동에 성공하였다. 😀

나머지 wireguard 설정을 추가한 후, peer wireguard로 ping test를 해보니, 정상 동작한다.

# ip address add dev wg0 10.1.2.200/24
# ip link set up dev wg0
# /root/workspace/wg set wg0 listen-port 59760 private-key ./privatekey peer fQEC0IgJjbWRotaunnKlOdhJw+kKzL8q1PYN/5DoWGs= allowed-ips 10.1.2.0/24 endpoint 192.168.1.169:51820

📌 wireguard-tools package도 빠져 있어, wireguard-tools package를 선택 & build 후, wg binary만 복사하여 시험하였다.


[그림 3.5] wireguard-tools package enable 하기

root@OpenWrt:~/workspace# ping 10.1.2.1
PING 10.1.2.1 (10.1.2.1): 56 data bytes
64 bytes from 10.1.2.1: seq=0 ttl=128 time=2.339 ms
64 bytes from 10.1.2.1: seq=1 ttl=128 time=2.795 ms
64 bytes from 10.1.2.1: seq=2 ttl=128 time=4.346 ms

root@OpenWrt:~/workspace# ./wg show

[그림 3.6] wg show 명령 실행 모습

3.3 boringtun 주요 코드 분석
기본적인 동작 과정은 확인해 보았으니, 이제 (주요 코드 흐름을 중심으로) code 분석에 들어가 보도록 하자.

[그림 3.7] boringtun-cli on openwrt one router

1) boringtun 주요 코드 목록 분석
boringtun-cli/src/*
     --> main routine
    -rw-rw-r-- 1 chyi chyi 6258  7월  8  2023 main.rs

boringtun/src/*
    --> library를 구성하는 routines
    drwxrwxr-x 3 chyi chyi 4096 12월 23 12:45 device
        --> 주로 tun device, peer, wg 설정(set/get - api.rs) 등에 관한 코드로 구성
                allowed_ips.rs
                api.rs
                dev_lock.rs
                drop_privileges.rs
                epoll.rs
                integration_tests
                kqueue.rs
                mod.rs    //device module에 대한 핵심 코드(struct & implementaion) 위치
                peer.rs
                tun_darwin.rs
                tun_linux.rs   //tun device 관련 linux code

    drwxrwxr-x 2 chyi chyi 4096 12월 23 13:13 ffi
        --> FFI(foreign function interface) 관련 코드
                mod.rs
   
    -rw-rw-r-- 1 chyi chyi 7175  7월  8  2023 jni.rs
        --> JNI(Java Native Interface) 관련 코드(android에서 사용하기 위함)

    -rw-rw-r-- 1 chyi chyi  670  7월  8  2023 lib.rs
        --> client에서 library로 사용하도록 하기 위한 코드

    drwxrwxr-x 2 chyi chyi 4096 12월 23 13:15 noise
        --> noise handshake 관련 코드
                errors.rs
                handshake.rs   //noise handshake code
                mod.rs   //noise module에 대한 핵심 코드(struct & implementaion) 위치
                rate_limiter.rs
                session.rs
                timers.rs    //timer 관련 코드

    -rw-rw-r-- 1 chyi chyi 1070  7월  8  2023 serialization.rs
    drwxrwxr-x 2 chyi chyi 4096 12월 20 15:23 sleepyinstant
                mod.rs
                unix.rs
                windows.rs

    -rw-rw-r-- 1 chyi chyi 3855  7월  8  2023 wireguard_ffi.h
        --> FFI 관련 C header file

(*) 아주 크게 보아 main routine(main.rs)과 2개의 module(device, noise - 2개의 mod.rs)로 구성되었다고 말할  수 있다.

2) 주요 structure 정리
코드 흐름을 제대로 이해하려면, 주요 struct의 구성을 확인해 보는게 우선되어야 한다.

2.1) wireguard device 관련 주요 struct 정의 
  -> boringtun/src/device/mod.rs
pub struct DeviceHandle {
    device: Arc<Lock<Device>>, // The interface this handle owns
    threads: Vec<JoinHandle<()>>,
}
pub struct DeviceConfig {
    pub n_threads: usize,
    pub use_connected_socket: bool,
    #[cfg(target_os = "linux")]
    pub use_multi_queue: bool,
    #[cfg(target_os = "linux")]
    pub uapi_fd: i32,
}

pub struct Device {
    key_pair: Option<(x25519::StaticSecret, x25519::PublicKey)>,
    queue: Arc<EventPoll<Handler>>,

    listen_port: u16,
    fwmark: Option<u32>,

    iface: Arc<TunSocket>,
    udp4: Option<socket2::Socket>,
    udp6: Option<socket2::Socket>,

    yield_notice: Option<EventRef>,
    exit_notice: Option<EventRef>,

    peers: HashMap<x25519::PublicKey, Arc<Mutex<Peer>>>,
    peers_by_ip: AllowedIps<Arc<Mutex<Peer>>>,
    peers_by_idx: HashMap<u32, Arc<Mutex<Peer>>>,
    next_index: IndexLfsr,

    config: DeviceConfig,

    cleanup_paths: Vec<String>,

    mtu: AtomicUsize,

    rate_limiter: Option<Arc<RateLimiter>>,

    #[cfg(target_os = "linux")]
    uapi_fd: i32,
}

2.2) Peer 관련 주요 struct 정의
  -> boringtun/src/device/peer.rs
pub struct Endpoint {
    pub addr: Option<SocketAddr>,
    pub conn: Option<socket2::Socket>,
}

pub struct Peer {
    /// The associated tunnel struct
    pub(crate) tunnel: Tunn,
    /// The index the tunnel uses
    index: u32,
    endpoint: RwLock<Endpoint>,
    allowed_ips: AllowedIps<()>,
    preshared_key: Option<[u8; 32]>,
}

pub struct AllowedIP {
    pub addr: IpAddr,
    pub cidr: u8,
}

2.3) Tunnel & Noise handshake 관련 주요 stuct & enum 정의
pub enum TunnResult<'a> {
    Done,
    Err(WireGuardError),
    WriteToNetwork(&'a mut [u8]),
    WriteToTunnelV4(&'a mut [u8], Ipv4Addr),
    WriteToTunnelV6(&'a mut [u8], Ipv6Addr),
}

pub struct Tunn {
    /// The handshake currently in progress
    handshake: handshake::Handshake,
    /// The N_SESSIONS most recent sessions, index is session id modulo N_SESSIONS
    sessions: [Option<session::Session>; N_SESSIONS],
    /// Index of most recently used session
    current: usize,
    /// Queue to store blocked packets
    packet_queue: VecDeque<Vec<u8>>,
    /// Keeps tabs on the expiring timers
    timers: timers::Timers,
    tx_bytes: usize,
    rx_bytes: usize,
    rate_limiter: Arc<RateLimiter>,
}

pub struct HandshakeInit<'a> {
    sender_idx: u32,
    unencrypted_ephemeral: &'a [u8; 32],
    encrypted_static: &'a [u8],
    encrypted_timestamp: &'a [u8],
}

pub struct HandshakeResponse<'a> {
    sender_idx: u32,
    pub receiver_idx: u32,
    unencrypted_ephemeral: &'a [u8; 32],
    encrypted_nothing: &'a [u8],
}

pub struct PacketCookieReply<'a> {
    pub receiver_idx: u32,
    nonce: &'a [u8],
    encrypted_cookie: &'a [u8],
}

pub struct PacketData<'a> {
    pub receiver_idx: u32,
    counter: u64,
    encrypted_encapsulated_packet: &'a [u8],
}

pub enum Packet<'a> {
    HandshakeInit(HandshakeInit<'a>),
    HandshakeResponse(HandshakeResponse<'a>),
    PacketCookieReply(PacketCookieReply<'a>),
    PacketData(PacketData<'a>),
}

3) 주요 코드 흐름 정리
3.1) main routine
main( ) routine은 매우 simple한데, (요점만 얘기하자면) 명령행 인자를 받아 이에 대한 처리(예: daemon화, log 초기화 등)를 하고난 후, DeviceHandle::new() function을 호출하는게 전부다. 이후 new() function 안에서 다른 new(tun, udp socket) function 등을 계속해서 호출하면서, 전체 구조를 잡아가는 방식으로 이해하면 될 것 같다.

fn main() {   //boringtun-cli/src/main.rs
    //argument 파싱
    let matches = Command::new("boringtun")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Vlad Krasnov <vlad@cloudflare.com>")
        .args(&[
            Arg::new(...),
            Arg::new(...),
            Arg::new(...),
            ...
        ])
        .get_matches();

     //daemon 시작
     if background {
              let daemonize = Daemonize::new().working_directory(...).exit_action(...);
              match daemonize.start() { ... };
     }
     
     //DeviceHandle::new() 함수 호출 - 여기가 시작점임.
     let mut device_handle: DeviceHandle = match DeviceHandle::new(tun_name, config) { ... };

     ...

     //device_handle 종료 대기
     device_handle.wait();
}

3.2) tun device & udp socket 초기화 
impl DeviceHandle {   //boringtun/src/device/mod.rs
    pub fn new(name: &str, config: DeviceConfig) -> Result<DeviceHandle, Error> {
        let n_threads = config.n_threads;

        //TUN device create 및 이후 처리(몇가지 함수 registration) - boringtun/src/device/mod.rs
        let mut wg_interface = Device::new(name, config)?;
              // let iface = Arc::new(TunSocket::new(name) ...
              // device.register_api_fd()
              // device.register_iface_handler() 함수 호출 - tun device로 부터 packet read 후, 해당 peer에 맞게 encapsulation(encryption) 처리 후, peer's endpoint로 패킷 전달(udp send)
              // device.register_timers()

        //udp socket open 및 이후 처리
        wg_interface.open_listen_socket(0)?; // Start listening on a random port
              // register_udp_handler() - udp socket으로 부터 packet receive 후, packet parsing. 이후 peer를 찾아 decapsulation(decryption) 처리 후, tun device로 write

        let interface_lock = Arc::new(Lock::new(wg_interface));

        let mut threads = vec![];

        //default 4개의 thread 생성 - event_loop 함수 호출
        for i in 0..n_threads {
            threads.push({
                let dev = Arc::clone(&interface_lock);
                thread::spawn(move || DeviceHandle::event_loop(i, &dev))
            });
        }

        //DeviceHandle struct return
        Ok(DeviceHandle {
            device: interface_lock,
            threads,
        })
   }
   ....
}

3.3) tun device -> packet encapsulation -> packet send(udp) to peer 흐름
fn register_iface_handler(&self, iface: Arc<TunSocket>) -> Result<(), Error> {  //boringtun/src/device/mod.rs

        // The iface_handler handles packets received from the WireGuard virtual network
        // interface. The flow is as follows:
        // * Read a packet
        // * Determine peer based on packet destination ip
        // * Encapsulate the packet for the given peer
        // * Send encapsulated packet to the peer's endpoint

        let src = match iface.read(&mut t.src_buf[..mtu])
        let mut peer = match peers.find(dst_addr)
        match peer.tunnel.encapsulate(src, &mut t.dst_buf[..])
        let mut endpoint = peer.endpoint_mut()
        udp4.send_to(packet, &addr.into())
}

3.4) udp socket -> packet read -> decapsulation -> packet send to TUN device 흐름
  -> udp packet 수신 후, parsing
  -> 이후, hanshake packet이면 handshake 처리 routine call
  -> transport packet(encrypted packet)이면 decrypt 처리 후, tun device로 write

fn register_udp_handler(&self, udp: socket2::Socket) -> Result<(), Error> {  //boringtun/src/device/mod.rs

    while let Ok((packet_len, addr)) = udp.recv_from(src_buf) {
        let parsed_packet = match rate_limiter.verify_packet()
        let peer = match &parsed_packet { ... }
        match p.tunnel.handle_verified_packet(parsed_packet, &mut t.dst_buf[..]) {
            TunnResult::WriteToTunnelV4(packet, addr) => t.iface.write4(packet)
        }
        d.register_conn_handler(Arc::clone(peer), sock, ip_addr)
    }
}

//handshake or transport packet 수신 시 - 각각의 처리 함수로 분기 
pub(crate) fn handle_verified_packet<'a>(...) {   //boringtun/src/noise/mod.rs
        match packet {
            Packet::HandshakeInit(p) => self.handle_handshake_init(p, dst),
                                         //handshake initiation packet 수신 처리
                                         //receive_handshake_initialization() 호출 - boringtun/src/noise/handshake.rs
            Packet::HandshakeResponse(p) => self.handle_handshake_response(p, dst),
                                         //handshake response packet 수신 처리 
                                         //receive_handshake_response() 호출 - boringtun/src/noise/handshake.rs
            Packet::PacketCookieReply(p) => self.handle_cookie_reply(p),
                                         //cookie 수신 처리 
                                         //receive_cookie_reply() 호출 - boringtun/src/noise/handshake.rs
            Packet::PacketData(p) => self.handle_data(p, dst),
                                         //packet decryption 처리
        }
        .unwrap_or_else(TunnResult::from)
}

3.5) noise handshake 시작 routine

impl Tunn { //boringtun/src/noise/mod.rs

/// Encapsulate a single packet from the tunnel interface.
pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> {
...
// Initiate a new handshake if none is in progress
self.format_handshake_initiation(dst, false)
                       //boringtun/src/noise/handshake.rs에 정의되어 있음.
}
}

3.6)  wireguard config set/get routine

pub fn register_api_fd(&mut self, fd: i32) -> Result<(), Error> {
let io_file = unsafe { UnixStream::from_raw_fd(fd) }
<---- unix domain socket

if reader.read_line(&mut cmd).is_ok() {
let status = match cmd.as_ref() {
// Only two commands are legal according to the protocol, get=1 and set=1.
                        "get=1" => api_get(&mut writer, d),
                                   //device 설정 내용을 획득 
                        "set=1" => api_set(&mut reader, d),
                                   //wg 설정 내용이 device에게로 전달
};       
}
}

wireguard 설정(get/set)과 관련해서는 이전 posting의 2.2절을 참조해 주기 바란다.


__________________________________________________
여기까지 boringtun project에 관하여 간략히 살펴 보았다.


4. todo!
다음 시간에는 Rust 관련하여 아래와 같은 내용을 다뤄 보고자 한다. 😎

1) Rust로 구현하는 Linux kernel programming

2) Embedded board용 rust programming



To be continued...



5. References
<main - Rust wireguard implentations>
[1] https://github.com/WireGuard/wireguard-rs
[2] https://github.com/cloudflare/boringtun

<misc - Rust implementations for wireguard>
[3] https://github.com/rodit-org/altuntun
[4] https://github.com/lz1998/wg-rs
[5] https://github.com/conradludgate/rustyguard
[6] https://github.com/defguard/defguard
[7] https://github.com/luqmana/wireguard-uwp-rs

<Linux kernel>
[8] https://bootlin.com/pub/conferences/2025/cdl/kernel-review.pdf

<Windows rust>
[9] https://github.com/microsoft/windows-rs
[10] https://github.com/nulldotblack/wireguard-nt

<Rust Books>
[11] https://www.manning.com/books/learn-rust-in-a-month-of-lunches
[12] Rust in Action, Systems programming concepts and techniques, Timothy Samuel McNamara, Manning
[13] https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/

<OpenWrt One>
[14] https://docs.banana-pi.org/en/OpenWRT-One/BananaPi_OpenWRT-One
[15] https://openwrt.org/toh/openwrt/one
  -> OpenWrt One WiKi page
[16] https://one.openwrt.org/hardware/
  -> OpenWrt One datasheet, schematic 등 포함
[17] BPI_OpenWRT_ONE_V10_SCH_20240618-R.pdf
  -> OpenWrt schematic 
[18] https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem
[19] https://firmware-selector.openwrt.org/?version=SNAPSHOT&target=mediatek%2Ffilogic&id=openwrt_one
  -> OpenWrt One firmware image download
[20] http://events17.linuxfoundation.org/sites/events/files/slides/ELC_OpenWrt_LEDE.pdf
[21] https://blog.dend.ro/building-rust-for-routers/
  -> Rust programming on OpenWrt

[22] And, Google~


Slowboot