Skip to content

アーキテクチャ

mqvpn は sans-I/O C ライブラリlibmqvpn)として構築されており、その上にプラットフォーム固有のレイヤーを重ねる設計です。VPN プロトコルエンジンとすべての I/O 操作を分離することで、あらゆるプラットフォームへの移植を可能にしています。

Sans-I/O 設計

ライブラリはプラットフォーム固有のイベントループやデバイス管理を内包せず、tick() と入力注入 API で駆動されます。受信した UDP データや TUN パケットは on_socket_recv() / on_tun_packet() で注入され、送信は xquic の送信コールバック経由で UDP ソケットに書き込まれます(CLI は fd-only モード)。

┌──────────────────────────────────────────────────────────────────┐
│  Platform Layer (owns I/O)                                       │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Linux CLI │ │macOS CLI │ │ Android  │ │ iOS (NE) │ │ Windows  │ │
│ │(libevent)│ │(libevent)│ │(Handler) │ │(RunLoop) │ │ (IOCP)   │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│      │ tick()     │ tick()     │ tick()     │ tick()     │ tick()│
├──────┴────────────┴────────────┴────────────┴────────────┴───────┤
│  libmqvpn (core engine — event-loop agnostic)                    │
│  ┌────────────────────────────────────────────┐                  │
│  │ mqvpn_client.c / mqvpn_server.c            │                  │
│  │ mqvpn_config.c / auth.c                    │                  │
│  │ path_mgr.c / flow_sched.c / addr_pool.c    │                  │
│  └────────────────────────────────────────────┘                  │
│       │ xquic callbacks                                          │
├───────┴──────────────────────────────────────────────────────────┤
│  xquic (QUIC / HTTP/3 / MASQUE engine)                           │
│  BoringSSL (TLS 1.3)                                             │
└──────────────────────────────────────────────────────────────────┘

Apple プラットフォームでは 2 つのエントリポイントが異なる構成を取ります: macOS CLI は Darwin プラットフォーム層(src/platform/darwin/、libevent イベントループ + utun デバイス)を使う一方、iOS Network Extension は sans-I/O コアを Swift で直接ラップします — RunLoop タイマー駆動の専用 tick スレッドで、パケットは NEPacketTunnelFlow 経由でやり取りします(libevent も独自の utun 操作も使いません)。

なぜ Sans-I/O か?

  • 移植性 — 各プラットフォームが独自のイベントループ(Linux / macOS では libevent、Windows では IOCP、Android では Handler 駆動ループ、iOS では Network Extension 内の専用 RunLoop スレッド)を提供します。ライブラリはスレッドモデルを強制しません。
  • テスト容易性tick() 関数が状態遷移を同期的に駆動するため、ユニットテストがタイミング問題なく決定的に実行できます。
  • 省電力 — プラットフォームが CPU のウェイクアップタイミングを制御します。ライブラリは interest.is_idle でアイドル状態を報告します。
  • 依存の分離 — ライブラリ自体はイベントループ実装を持たず、プラットフォーム層が libevent や OS 依存ライブラリ(Linux では pthread など)を扱います。

これは WireGuard (BoringTun)msquic が採用しているのと同じパターンです。

データフロー

プラットフォーム層はシンプルなループでライブラリを駆動します:

c
// 1. Config とクライアントの作成
cfg = mqvpn_config_new();
mqvpn_config_set_server(cfg, "1.2.3.4", 443);
mqvpn_config_set_auth_key(cfg, "base64...");
client = mqvpn_client_new(cfg, &callbacks, user_ctx);

// 2. ネットワークパス(UDP ソケット)の追加
mqvpn_client_add_path_fd(client, udp_fd, &desc);

// 3. 接続してエンジンを駆動
mqvpn_client_connect(client);

while (running) {
    mqvpn_interest_t interest;
    mqvpn_client_get_interest(client, &interest);
    int next_ms = interest.next_timer_ms;

    poll(fds, nfds, next_ms);

    // 受信した UDP データを注入
    if (udp_readable)
        mqvpn_client_on_socket_recv(client, path, buf, len, &peer, peerlen);

    // TUN パケットを注入
    if (tun_readable)
        mqvpn_client_on_tun_packet(client, pkt, len);

    // エンジンを駆動 — キューされた処理を実行し、コールバックを呼び出し
    mqvpn_client_tick(client);
}

コールバックモデル

ライブラリはコールバックを通じてプラットフォームに通知します:

コールバックタイミングプラットフォームの対応
tun_output復号されたパケットが準備完了TUN デバイスに書き込み
send_packetops パス利用時に送信要求プラットフォーム側送信関数で UDP 送信
tunnel_config_readyサーバーが IP/MTU を割り当てTUN デバイスを作成・設定
state_changed接続状態の遷移UI 更新、再接続処理
path_eventパス状態の変化ログ記録、ルーティング調整
logログメッセージログに書き込み

現行 CLI 実装は fd-only モードのため send_packet は使わず、xquic の送信コールバックからソケット送信を行います。

すべてのコールバックは tick() を呼び出したスレッドと同じスレッドで呼び出されます — 同期処理は不要です。

コンポーネント

コンポーネントファイル役割
クライアントエンジンmqvpn_client.cQUIC 接続、MASQUE CONNECT-IP、ステートマシン
サーバーエンジンmqvpn_server.cマルチクライアント処理、アドレス割り当て
Config ビルダーmqvpn_config.cOpaque な設定構造、setter 関数、ABI 互換性
パスマネージャpath_mgr.cUDP パスのライフサイクル管理、作成と破棄
フロースケジューラflow_sched.cWLB および MinRTT パケットスケジューリング
アドレスプールaddr_pool.cサーバー側 IP アドレス割り当て
認証auth.cTLS 1.3 上の PSK 認証

プラットフォーム移植

mqvpn を新しいプラットフォームに移植するには、以下を実装します:

  1. イベントループget_interest().next_timer_ms の間隔で tick() を呼び出す poll/epoll/kqueue/IOCP
  2. UDP ソケット — UDP ソケットの作成・バインド・読み取り、受信データを on_socket_recv() に渡す
  3. TUN デバイス — プラットフォーム固有の TUN を作成、tun_output コールバックからのパケットを書き込み、読み取ったパケットを on_tun_packet() に渡す
  4. ルーティング — TUN デバイス経由でトラフィックを誘導するルートを設定
  5. DNS — DNS リークを防止する DNS 設定

Apache License 2.0 に基づき公開