Firezone, sans-IO 설계의 Rust WireGuard 구현 sanstun 공개
- Firezone이 WireGuard 상태 머신을 Rust로 새로 구현한 sanstun을 공개. 파일·네트워크 IO뿐 아니라 시간과 난수까지 상태 로직에서 떼어내는 sans-IO 설계라 부작용 없이 조합하고 테스트하기 쉬움
- Firezone은 2년 넘게 Cloudflare의 boringtun 포크를 유지하며 의존성 업데이트를 뺀 70개 이상의 PR을 쌓았고, 제품 요구가 바뀌는 동안 업스트림 API 100% 호환을 지키기가 점점 어려워져 자체 구현으로 넘어감
- 내부 버퍼링을 없앰. 세션이 없을 때 라이브러리가 패킷을 대신 쌓아두지 않아서, boringtun처럼 버퍼를 비우려고 길이 0인 데이터그램으로 decapsulate를 호출해야 하는 불분명한 계약이 사라짐
- 시간 처리는 Instant를 받는
_at접미사 API로 분리했고, 이는 전체 시스템을 시뮬레이션해 경계 조건 버그를 찾는 방식에 필요함. 패킷 처리는 begin_encapsulate, begin_decapsulate와 commit_decapsulate, handle_timeout, poll_event 같은 함수로 나뉨
Hacker News 의견들
Mullvad도 boringtun에서 갈라져 나온 gotatun을 직접 만들었음. 결국 다들 포크하다가 자기 걸 만들더라
나도 Rust로 프로토콜 구현할 때 이렇게 함. 훨씬 테스트하기 쉽고, 코어를 async랑 블로킹 IO 양쪽에서 그대로 쓸 수 있고, 의존성에서 unsafe를 거의 다 걷어낼 수 있음
Firezone 홈페이지에 '하드웨어가 무거운 작업을 하고 배터리가 오래 간다'는 문구가 있는데, 그 운영체제는 어디서 돌고 전원은 어디서 나옴? VPN처럼 보안 중요한 소프트웨어에서 이런 AI 슬롭 카피는 신뢰가 안 감
그건 커널이나 NIC에서 프로토콜을 처리한다는 뜻이고 맞는 말임. 배터리는 똑같이 쓰지만 TCP나 TLS를 커널과 하드웨어 가속 NIC에서 처리하는 게 유저랜드에서 같은 IO를 하는 것보다 전력 효율이 좋음
내부적으로는 생각이 더 있음. 가능한 곳에서 하드웨어 오프로딩을 하자는 얘기고, 암호화용 벡터 명령이나 체크섬 오프로딩, 패킷 세그멘테이션을 쓴다는 뜻임. 패킷당 효율이 좋아지면 같은 데이터를 옮기는 데 전력을 덜 씀. 다만 전문 용어 없이 더 잘 쓸 수 있었던 건 맞음
Jason의 레퍼런스 구현이 아닌 WireGuard 구현은 멍청한 짓이고 문제를 부르는 invitation임. WG는 2k LOC밖에 안 되는데 왜 30개 버전이 필요함? 있는 걸 감사하라고
크로스 플랫폼 하려는 순간 리눅스 커널 구현은 못 쓰고, 공식 유저랜드 구현은 Go라 Rust에서 FFI하기 어색함. 우리도 Cloudflare boringtun을 오래 썼지만 컴파일 타임 안전성과 성능에서 더는 우회 못 할 한계가 있었음
ESP32 같은 no_std 환경에서 sanstun을 쓸 수 있음. 레퍼런스 구현으로는 안 됨. 못 본 이유가 있어서 사람들이 만드는 거임
읽다가 'function coloring 문제를 피한다'는 대목에서 걸림. async는 IO가 아니라 결정적 상태 머신을 쓰는 방식인데, 오히려 async 없이 상태 머신을 다 다시 쓰면 function coloring 문제는 더 나빠짐
원론적으로는 맞음. 근데 async를 쓰는 순간 상태 머신 API가 poll 하나로 줄어듦. 두 await 사이에 상태를 바꾸고 싶으면 채널을 끼워 넣거나 Mutex로 공유 데이터를 건드려야 함. Firezone 코어는 인바운드 IP, UDP, WebSocket 제어 메시지, TURN 할당, 타이머를 동시에 다루는데, 하나의 큰 struct에 &mut self 함수를 호출하는 쪽이 훨씬 이해하고 조합하고 테스트하기 쉬움
실제 코드 없이 하는 논쟁이라 좀 흐릿한데, 층위를 섞은 것 같음. 여기 sans-IO는 어떤 런타임이나 스레드든 가져다 쓸 수 있는 재사용 부품을 만드는 얘기고, function coloring은 그 부품을 쓰는 서로 다른 런타임 인터페이스를 조합할 때 생기는 불편임
나는 정반대라고 봄. 블로킹 스레드가 동시성을 생각하는 훨씬 자연스러운 방식임. Erlang 만든 Joe Armstrong도 스레드마다 엄격히 순차적인 코드를 써야 한다고 했음
MCU에서 WireGuard 돌리고 싶었는데 반가움
WireGuard의 근본적인 장점은 단순함이라 작은 코드베이스로 구현할 수 있고, DTLS처럼 증가가 아니라 단조 증가 시퀀스 번호를 쓴다는 게 동시성 구현을 훨씬 쉽게 만듦
Rust로 VPN 매니저를 만들면서 Mullvad의 gotatun을 쓰고 있는데, 성능 곡선이나 연결 지터 같은 데이터, 보안 구현 관련 자료가 있는지 궁금함