sanstun:Rust で実装された、sans-IO を採用した並列性に優れた WireGuard 実装

2026/10/31 6:30

sanstun:Rust で実装された、sans-IO を採用した並列性に優れた WireGuard 実装

RSS: https://news.ycombinator.com/rss

要約▶

Japanese Translation:

Sanstun は、非推奨の

firezone/boringtun
フォークを代替する、WireGuard 状態機械の最適化された Rust 実装(IO なし)をリリースしました。この新しい設計は、並列処理に適した慣用 API を使用して I/O および副作用(時間と乱数を含む)をコアビジネスロジックから分離することで、使いやすさ、セキュリティ向上、パフォーマンスを優先しています。主な利点には、プロトコル不変性をマッピングする型を通じたコンパイル時安全性、
Instant
オブジェクトによる時間処理を通じた正確なシステムシミュレーション、Diffie-Hellman 鍵交換およびクッキー管理を通じた DoS 攻撃に対する内蔵防御が含まれます。API は明示的に記録業務(例:カプセル化の開始)と重い暗号化/復号化を分離することで同期ボトルネックを回避し、暗号処理をワーカースレッドにオフロードした場合のベンチマークでは 45 倍以上のスピードアップの可能性を示しています。ユーザーは現在、パケットを手動でバッファリングする必要があり、
Tunnel::initiate_handshake
を通じてハンドシェイクを開始する必要がありますが、この設計はプライベートハンドシェイクコンスストラクタ(
Interface::receive
のみアクセス可能)を介してコンパイル時にレート制限および正しいトンネルルートを強制します。このアプローチにより、以前のフォークで問題となった直感的ではないドレインや設定できないバッファサイズのなどの内部バッファリング問題は解消されます。Sanstun パターンは Rust に限定されず、様々なプログラミング言語での実装にも有益である可能性があります。Apache 2.0 ライセンスの下で crates.io で利用可能であり、非推奨の
firezone/boringtun
フォークから移行する過程で GitHub の issue および PR を通じて貢献を招集しています。

本文

sanstun:理念に基づいた Rust 製の WireGuard® ステートマシン実装

本日、sanstun を正式に発表いたします。これは sans-IO という理念に基づいた、Rust 言語による WireGuard® ステートマシンの実装です。

  • 目的: 使いやすさ、セキュリティ、パフォーマンスの向上を目指しています。
  • 設計方針: 並列処理(Parallelism)に最適化され、Rust 特有の慣習(idiomatic)に沿った API で設計されています。

sans-IO とは何か?

sans-IO の核となる思想は、システム設計において**「入出力(IO)」と「副次的な効果(side-effects)」**を、「状態」と「業務ロジック」から明確に分離することです。

  • IO の定義: ファイルやネットワーク操作だけでなく、時間や乱数といった要素も含まれます。
  • 設計上の利点:
    • これらの要素が副作用を持たないため、コンポーネント同士は非常に良好に合成(compose)できます。
    • テストも比較的容易に行えます。
    • 「関数カラーリング」といった問題を回避でき、Rust の所有性ルールとも親和性が高いです。

注記: この記事のコード例は Rust 言語を用いていますが、sans-IO そのものは Rust に特化した概念ではありません。そのパターンや思想はあらゆるプログラミング言語に適用可能です。詳細は sans-IO に関する投稿をご参照ください。


沿革

Firezone の当初構築において WireGuard 実装として boringtun を採用し、要求をほぼ満たしていましたが、時間の経過とともにバグ修正や API 拡張のためのパッチをフォーク版に次々と適用する状況となりました。

  • 当初の目標: パッチを上流(upstream)にも還元できるよう、既存 API の維持。
  • 課題: 製品要件が進化するにつれ、上流との 100% API 互換性 を保つことが難しくなりました。

sans-IO への移行と内部バッファリングの問題

boringtun に施した最初の重大な変更の一つは、時間処理部分を完全に sans-IO にすることでした(firezone/boringtun#5)。これにより、

_at
という接尾辞を持つすべての機能に対して新しい API が追加されました。

boringtun には当初から有効化されていた「内部バッファリング」ですが、以下のような問題を抱えていました:

  • 排出(Drain)の必要性: バッファ内容の排出が必要で、ゼロ長パケットを持つ
    decapsulate
    の呼び出しで解決しているが直感的ではありません。
  • メモリ割り当て: 追加のメモリ割り当てが発生します。
  • 柔軟性の欠如: バッファサイズの設定・調整が困難です。

これらの問題は**「ライブラリ内でバッファリングを一切行わないこと」**という解決策を指し示しています。これにより、インターフェースと実装の双方が簡素化されました。

  • 適用されたパッチや修正の一覧は非常に長くなっています(依存関係更新を除き 70 のプルリクエスト 以上)。
  • これらはエッジケースの修正、新しい API の追加、設定可能にする変更などを含みます。

boringtun を 2 年以上使用し、パッチを適用してきた結果、ユーザー空間における WireGuard 実装の全体像となるべき API が明確となり、それが sanstun として存在します。


より優れた sans-IO API

sans-IO API の核心となる構築要素は以下の通りです:

機能別の API メソッド

カテゴリメソッド名
入力パケットの処理
Tunnel::handle_handshake_initiation

Tunnel::handle_handshake_response

Tunnel::handle_cookie_reply

Tunnel::begin_decapsulate & Tunnel::commit_decapsulate
出力パケットの処理
Tunnel::begin_encapsulate
タイマー更新の処理
Tunnel::handle_timeout & Tunnel::poll_timeout
イベントの処理
Tunnel::poll_event
  • 設計上の工夫: 内部バッファリングがないため、特定の API をループで呼び出す必要がありません。API がコンパクトであり、sans-IO モデル全体に格好良く適合しています。
  • ハンドシェイク制御: パケット送信には
    Tunnel::begin_encapsulate
    を呼び出します。セッションが存在しない場合はパケットをバッファリングするかどうかに任せ、新しいセッションを作成するにはハンドシェイクを送信します。任意のタイミングで
    Tunnel::initiate_handshake
    で新たなハンドシェイクを開始可能です。

WireGuard プロトコルとセキュリティ

WireGuard のメッセージ形式は 4 つのタイプをサポートしています:

  1. ハンドシェイク開始(handshake init)
  2. ハンドシェイク応答(handshake response)
  3. クッキー応答(cookie reply)
  4. 暗号化されたデータ

セキュリティ上の課題:DoS 攻撃への対策

WireGuard の設計により、エンドポイントを開示することが DoS(サービス拒否)の機会を発生させないよう確保されています。

  • 問題点: エンドポイントの公開鍵を知っている者は、偽のハンドシェイク開始メッセージを作成し、入力される UDP パケットのソース IP を捏造するのが容易です。
  • 防御コスト: 受信側が供給されたハンドシェイクデータが偽物であることを識別するには、DH 演算の自らの半分を実行する必要がありますが、その時点で CPU リソースはすでに消費されています。
  • 攻撃手法: 十分な数のハンドシェイク開始メッセージを送信することで、受信側は関連しないハンドシェイクの処理にすべての CPU サイクルを費やす可能性があります(CPU 枯渇攻撃)。

防御策:クッキー機構

  • WireGuard はリモートホストが主張するソース IP の所有権を証明させるクッキー機構を指定しています。
  • これにより、ソース IP 単位でのハンドシェイクレート制限が可能となります(単一のトンネルだけでなく全体インターフェースに適用されます)。
  • ハンドシェイク完了後、両方のピアは対称暗号を使用して暗号化されたデータパケットを交換できます。

トンネルとセッション管理

  • ハンドシェイクの一部として、ピアはそれぞれが選択したセッションインデックスも交換します。
  • これらのインデックスにより、トンネル間のマルチプレックス化ならびにトンネル内の複数のセッションの実行が可能になります。

プロトコルの不変性を API 設計へマッピング

Rust のような言語で作業する場合、型を使ってコンパイル時に不変性を強制し、バグを発生する前に防ぐことが重要です。

  • 例: メッセージに対してレート制限器を適用することを忘れると、通常動作下では問題が表面化しません。
  • 目標: 優れた API 設計は、壊れたものを構築することを不可能にし、プロトコルの仕組みを理解する道筋を示します。

一つのインターフェース、多数のトンネル

WireGuard インターフェースは単一の鍵で識別され、トンネルは二つのインターフェース間の接続を意味します。

  • Interface
    : インターフェース(単一鍵)
  • Tunnel
    : トンネル(二インターフェース間の接続)

パケット送信時はどのトンネルを通すか指定し、入力パケット受信時はどのトンネルに属するかを特定する必要があります。sanstun ではこれらの概念が直接的に

Interface
と
Tunnel
タイプに対応しています。

処理フロー

すべての入力パケットは

Interface::receive
を通ります。これは生のデータグラムを解析する唯一の公開 API です。 このメソッドは以下の処理を行います:

  1. パケットを解析する
  2. レート制限を適用する
  3. ReceivedMessage
    (各メッセージタイプの変数を持つ)またはクッキー応答を返す

これらは対応するトンネルの機能に直接マッピングされます(例:

Tunnel::handle_handshake_initiation
)。

重要ルール: すべての UDP データグラムは

Interface::receive
を通ります。すべてのチェックが通過するまで、メッセージは対応するトンネルに転送されません。

処理ステップ:

  1. datagram
    : 負荷下でのクッキー応答 ->
    Interface::receive
  2. parse header
  3. verify MAC
  4. check cookie
  5. rate limit source

メッセージのルティングルール:

  • HandshakeInitiation
    : イニシエーターの静的鍵によるルーティング
  • HandshakeResponse
    :
    TunnelId
    (例:5) で識別
  • CookieReply
    :
    TunnelId
    (例:5) で識別
  • TransportData
    :
    TunnelId
    (例:7) -> 対応する
    Tunnel
    に転送(例:3)

型安全性の保証:

  • HandshakeInitiation
    などの型のコンストラクタはすべて クレート固有(crate-private) です。
  • Tunnel::handle_handshake_initiation
    を呼び出すためには、まず
    Interface::receive
    を経由して該当する型を取得する必要があります。
  • この設計により、アプリケーション側がレート制限器を直接適用しなくても保護されます。

並列化可能な暗号処理

sanstun の最も優れた機能の一つは、パケットの暗号化および復号化のための並列処理に最適化された APIです。

背景と課題

  • WireGuard はトンネル間で状態を共有しません。ピア間での実装並列化は容易ですが、単一トンネル内の並列化は困難です。
  • ボトルネック: ワークスレッドで処理しても、CPU パフォーマンスによって制限されます(特に ChaCha20-Poly1305 アルゴリズム)。
  • 同期問題: 再生攻撃を防ぐためにパケットカウンターが含まれており、これへのアクセスには同期が必要です。

解決策:事務処理と暗号化の分離

sanstun は**事務処理(bookkeeping)**を実際の暗号化・復号処理から分離しています。

// 例:事務処理のみを行う関数
pub fn begin_encapsulate(...) -> PendingEncapsulate {
    // セッション確認、カウンター予約、タイマー状態を進める
}
impl PendingEncapsulate {
    pub fn encrypt<'a>(self, plaintext: &[u8], dst: &'a mut [u8]) -> Result<DataMessage<'a>, Error> {
        // ... 実際には何も暗号化されていない(ここでも事務処理のみ)
        // 暗号化は別のスレッドで実行される準備を完了する
    }
}
  • PendingEncapsulate
    は
    Send
    トrait
    を実装しており、他のスレッドに渡して主要な処理を実行できます。

パフォーマンスベンチマーク

sanstun リポジトリで 
cargo bench data_path` を実行した結果:

64 バイトパケット

処理項目平均時間 (ns)
begin_encapsulate
~20.8
encrypt
~226.2
begin_decapsulate
~37.6
decrypt
~205.8
commit_decapsulate
~17.5

1280 バイトパケット

処理項目平均時間 (ns)
begin_encapsulate
~20.8
encrypt
~933.5
begin_decapsulate
~37.6
decrypt
~914.9
commit_decapsulate
~17.7

分析:

  • 事務処理部分(
    begin_encapsulate
    等)はパケットサイズに比例して増大しません。
  • 暗号化/復号化のみがパケットサイズに依存します。
  • 1280 バイトの場合、事務処理部分は全体の**わずか 2%**に過ぎません。
  • この設計により、理論的には45 倍以上のパフォーマンス向上が可能です(実際にはスレッド渡しのコストなどで達成困難ですが)。

アーキテクチャ例

暗号処理をワーカースレッドへオフロードする

3 つのスレッドを持つシーケンス図:

  • 所有者スレッド: インターフェースとトンネル状態を保持。事務処理のみ実行。
  • ワーカー 1: 入力
    PendingEncapsulate
    -> 暗号化 (934 ns) -> DataMessage -> 出力
    begin_encapsulate
    x2
  • ワーカー 2: 入力
    PendingDecapsulate
    -> 復号化 (915 ns) -> Decrypted -> 出力
    begin_decapsulate
    x2

sans-IO ライブラリの sanstun は、あなたがどのように暗号処理を並列化したいかを気にしません。シンプルさを保ちたい場合は、そのまま

encrypt
と
decrypt
をインラインで呼び出すことも可能です。

達成目標:

Interface
周辺での一切を単一スレッドで保ちつつも並列化可能な暗号処理を実現することです。これにより、内部のレート制限器がロックやアトミックカウンターなしに動作し、Rust のバローチェッカー(borrow checker)を活用しながら複数のトンネルの状態を統合できます。


おわりに

sanstun によって、firezone/boringtun にて行ったフォーク版は現在廃止されました。 WireGuard を基盤としたアプリケーション(VPN、メッシュネットワークなど)を開発する皆様にとって、sanstun が作業を容易にすることを願っています。

  • 入手先: crates.io
  • ライセンス: Apache 2.0 (オープンソース)
  • コードリポジトリ: GitHub(※実際の GitHub URL はリンク切れの場合あり、プロジェクトページを参照)

問題提起やプルリクエストの歓迎いたします!

同じ日のほかのニュース

一覧に戻る →

2026/10/31 2:25

僕の一番好きなPower Macは、ニンテンドーWiiだ

## Japanese Translation: Rairii のゲスト投稿では、ArtX ベースの PowerPC システム(特にニンテンドーコンソールであるゲームキューブ、Wii、Wii U)への古典的な Mac OS バージョン 7.6 と 9.2.2 の成功したポート作業について詳述されている。同プロジェクトは 2025 年 6 月 22 日に開始され、AI の助力なしに完全に手動作業によるコーディング、リバースエンジニアリング、デバッグを通じて完了している。大きな突破点は、FAT ファイルシステムを備えた OpenBIOS を改修して Mac OS イメージをロードできるようにしたことであり、開発者は NT ARC ファームウェアローダーなどの既存のツールの再利用を行ったが、ドライバーは大幅に修正した:Elliott Nunn の virtio ベースのディスプレイドライバーの再利用、"SD Gecko"(SPI アダプター)および"EXI-IDE"(SPI エミュレーション)ディスクドライバーの実装、物理アドレス空間の前提条件と MMIO マッピングのエラーを修正する「Trampoline」へのパッチ適用などである。重要なカーネルパッチはマイクロコントローラー用のビットフラグの修正、Flipper および PCI アーキテクチャ間の中断コントローラの不整合への対処、レガシーモードの問題の解消といったハードウェア固有の課題に対応している。具体的なレガシーバグも修正された例として、「SuperMario」m68k 環境におけるシリアルポートコード内の無限ループがある。現在、SD を通じて SPI で実機上で Mac OS 7.6 は 40 秒以内に正常に起動するが、Mac OS 9.2.2 については不安定な Open Transport コンポーネントを除去しクラッシュを防ぐためにさらなる作業が必要である。

2026/10/30 16:09

Cursor マーケットの VS コード拡張機能「Claude Code」に後門が存在する

## Japanese Translation: 公式 Cursor マーケットPLACE で配布されている公式 Claude Code 拡張機能(バージョン 2.1.313)に重大な裏門(バックドア)が発見された。攻撃者は Windows、Linux、macOS のいずれの環境でもユーザーのマシンに対して永続的なアクセス権を取得できる。ベンダーが管理する配布チャネルに関する標準的な仮説とは異なり、この悪意のあるアップデートは SSH キーを埋め込み、逆回線トンネルを確立することで、インストール時に直接ユーザーの関与を必要とせずにもれなくシェルへの完全なアクセス権限を取得する。この脅威は macOS のセットアップスクリプトに含まれる特定の脆弱性を利用し、セキュリティチェックを迂回すると同時に、機微なシステムデータを攻撃者管理のサーバーにビーコン送信している。Cursor は以来、その登録から該当の侵害されたリリースを削除した。バージョン 2.1.313 を使用している組織は、直ちに環境を検査し、拡張機能をアンインストールし、埋め込まれた認証情報や実行中のトンネルプロセスを能動的に探してセキュリティを回復する必要がある。この事件は、堅牢なサプライチェーン監査の緊急性を示しており、正規の開発ツールが企業ネットワーク内におけるデータ窃取および lateral movement(横方向移動)の予期せぬベクトルとして機能し、ベンダー配布ソフトウェアへの信頼を損なう可能性があることを強調している。

2026/10/31 0:10

北フランクが侵害された

## Japanese Translation: Northflank エンジニアは、Wiz によって発行された重大なサイバーセキュリティ脅威アラートを成功裏に無効化し、クラウドインフラストラクチャを保護するための卓越した速度を示しました。対応は、UTC11:43 にアラートが確認されたわずか 1 分後から開始され、UTC12:02 までに最初のネットワークポリシーパッチを展開しました。この迅速な最初の介入により、プラットフォームに影響を与え得た脆弱性に対する潜在的な露出が大幅に軽減されました。事態の進展に伴い、追加のパッチングが迅速に行われました:UTC12:50 に第 2 インスタンซ์が展開され、UTC13:00 に特殊な Wiz CIRT サポートの依頼が行われました。支援を受諾した直後、さらに更新が連続して展開された(UTC13:05 など)ことにより、チームの準備状況および外部セキュリティ専門家との協力的なアプローチによってシステムの完全な強化を確保していることが浮き彫りになりました。この事象は、現代のアプリケーションプラットフォームにおいて迅速なインシデント対応メカニズムが極めて重要であることを強調しており、脅威が発見された際にユーザーに即座の保護を提供し、損害やデータ漏洩を引き起こす前にリスクを効果的に軽減することを確保しています。

sanstun:Rust で実装された、sans-IO を採用した並列性に優れた WireGuard 実装 | そっか~ニュース