WireGuardの仕組みとAllowedIPsの読み方
WireGuardのcryptokey routingを解説します。AllowedIPsがルーティングテーブルとアクセス制御リストを兼ねる理由、Noiseハンドシェイクと鍵のローテーション、wg0.confの読み方が分かります。
WireGuard の仕組みを 1 つの考え方で理解する
WireGuard は、すべてのパケットを公開鍵に関連付けて処理します。この仕組みには cryptokey routing という名前があり、設計全体の基盤になっています。peer の隣にある AllowedIPs の行は、マシンから送信するパケットのルーティングテーブルであると同時に、その peer から到着するパケットのアクセス制御リストでもあります。1 つの設定が 2 つの役割を担います。AllowedIPs をこのように読めば、WireGuard の設定ファイルはすべて理解しやすくなります。
IP アドレスをキーにしたセッションテーブルや、ユーザーデータベースはありません。peer は公開鍵と、その鍵で使用できるアドレスの集合です。ハンドシェイクとタイマーは、下位のネットワークが変化しても、この対応関係を維持するために存在します。理論より先に動作するトンネルが必要なら、自分の VPS でセルフホストする WireGuard VPN を構築してください。その後、設定行の意味に疑問が生じたときに、ここへ戻ってきてください。
AllowedIPs はルーティングテーブルであり、アクセスリストです
まず送信方向を見ます。カーネルは通常どおり、メインルーティングテーブルを使ってパケットを wg0 デバイスへルーティングします。次に WireGuard は、そのパケットの 宛先 アドレスを、すべての peer に許可されたプレフィックスを保持するテーブルと照合します。照合は最長プレフィックスから行われます。一致すると peer が特定され、その peer から公開鍵、セッションキー、UDP エンドポイントが特定されます。パケットはその peer 向けに暗号化され、送信されます。
どの peer の AllowedIPs にも宛先が含まれない場合、送信されません。送信に使う鍵がないためです。
ping: sendmsg: Required key not availableこのエラーが示すことは1つだけです。到達しようとしたアドレスが、どの peer にも登録されていません。別のエラーである ping: sendmsg: Destination address required は、peer との一致はあったものの、その peer のエンドポイントが WireGuard に存在しないことを示します。エンドポイントが設定されておらず、まだ学習もされていないためです。
次に受信方向です。UDP パケットが listen port に到着します。WireGuard はヘッダー内の receiver index からセッションを特定し、スライディング再生防止ウィンドウに基づいてカウンターを検証します。その後、ペイロードを復号して認証します。内部パケットを読み取るのはその後です。内部パケットの 送信元 アドレスは、送信元 peer の AllowedIPs に含まれている必要があります。含まれていない場合、パケットは破棄されます。dynamic debug を有効にすると、カーネルは次のような行で理由を出力します。
wg0: Packet has unallowed src IP (10.8.0.9) from peer 2 (203.0.113.10:51820)これが、サーバー側の peer が /32 を受け取る理由です。AllowedIPs = 10.8.0.2/32 が設定された peer は、10.8.0.2 からのパケットだけを送信できます。それ以外のアドレスからは送信できません。代わりにそこへ 0.0.0.0/0 と記述すると、その単一のクライアントはトンネル内の任意の送信元アドレスを名乗るパケットを注入できます。別のクライアントのアドレスも含まれます。
重複するプレフィックスは、検索が最長プレフィックス一致で行われるため、具体性によって解決されます。2 つの peer に同一のプレフィックスを設定した場合は動作が異なります。エントリは最後に設定された peer に移動し、最初の peer はそのトラフィックを受信しなくなります。このとき、どこにもエラーは出力されません。wg show wg0 allowed-ips は実際にカーネル内にあるテーブルを出力します。ディスク上のファイルと実行中の状態が一致していない場合でも、基準となるのはこちらです。
暗号鍵ルーティングを意識して設定ファイルを読む
サーバー側:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>
[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32クライアント側:
[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop private key>
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25同じキーワードでも、両側では意味の重みが反対になります。クライアントでは「すべての宛先への通信をこのピアへ送信する」という意味です。サーバーでは「このピアからはこのアドレスだけを受け入れる」という意味です。この非対称性は役割ではなく、値にあります。
これらのキーの半分は、プロトコルの設定ではありません。Address、DNS、MTU、PostUp、SaveConfig は、インターフェイスを起動するシェルスクリプトである wg-quick に属します。カーネルはこれらを認識しません。wg-quick strip wg0 は、wg ツールが実際に読み込む縮約後の設定を出力します。この方法が、その区分を最も早く確認する手段です。
ハンドシェイクで実際に行われること
WireGuard のハンドシェイクは、Noise Protocol Framework に基づく Noise_IKpsk2 です。IK の部分が sysadmin にとって重要です。responder の静的公開鍵は initiator が事前に把握しています。これは [Peer] ブロック内の PublicKey です。initiator は最初のメッセージ内で自身の静的公開鍵を暗号化して送信します。そのため、証明書の交換も、身元確認の往復処理もありません。受動的な盗聴者は、responder の秘密鍵を持っていない限り、どの鍵が接続を開始しているかを判別できません。
代わりに、必要な通信は 1 往復です。開始メッセージは 148 bytes、応答は 92 bytes で、その直後からデータが流れます。各側は、ハンドシェイクごとに新しい一時 Curve25519 鍵ペアを生成します。セッション鍵は、静的鍵と一時鍵を組み合わせた Diffie-Hellman の結果を連鎖させて生成されます。一時秘密鍵はその後破棄されます。これにより前方秘匿性が得られます。今日の通信を記録した攻撃者が、来年サーバーの秘密鍵を盗んだとしても、記録した通信を復号できません。
ハンドシェイク開始メッセージには TAI64N タイムスタンプが含まれます。各 peer は相手から受信した最大のタイムスタンプを記憶するため、再送された開始メッセージは拒否されます。データパケットには nonce として使用する 64-bit カウンターが含まれます。受信側は最近確認したカウンターのスライディングウィンドウを保持します。そのため、TCP のような接続状態を持たずに、再送や大幅な順序入れ替わりを処理できます。
セッション鍵の有効期間は短く、タイマーは設定変更可能な値ではなく、コンパイル時に組み込まれています。
The data behind this chart
[
{
"label": "REKEY_TIMEOUT",
"seconds": 5,
"notes": "resend a handshake initiation that got no answer"
},
{
"label": "KEEPALIVE_TIMEOUT",
"seconds": 10,
"notes": "send a keepalive after receiving data and sending none back"
},
{
"label": "REKEY_ATTEMPT_TIME",
"seconds": 90,
"notes": "give up on the handshake and report the peer as down"
},
{
"label": "REKEY_AFTER_TIME",
"seconds": 120,
"notes": "sender begins a fresh handshake for a new session key"
},
{
"label": "REJECT_AFTER_TIME",
"seconds": 180,
"notes": "the old session key is refused and traffic stops"
}
]これらはプロトコル仕様の定数であり、測定値ではありません。5 がセッションのライフサイクル全体を制御します。使用開始から 120 秒が経過すると、送信側は新しいハンドシェイクを開始します。180 秒が経過すると、古い鍵は完全に拒否されるため、新しいハンドシェイクが完了するまで通信が停止します。応答のない開始メッセージは 5 秒ごとに再送され、90 秒後に破棄されます。これが、wg show が latest handshake を相対的な経過時間として表示する理由です。通信量の多い正常なトンネルでは、この経過時間が小さく保たれます。通信を実際に送信している間に経過時間が増加する場合、トンネルがアイドルなのではなく、ハンドシェイクが失敗しています。
クライアント役やサーバー役が存在しない理由
両端では同一のコードと同じ形式の設定を使用します。サーバーモードはありません。感じられる非対称性は Endpoint によるもので、Endpoint は任意です。
エンドポイントを設定した peer は、ハンドシェイクを開始できます。エンドポイントを持たない peer は待機し、正しく認証された最初のパケットから相手側のアドレスとポートを取得します。取得したエンドポイントは保存され、新しいアドレスから有効なパケットを受信するたびに更新されます。これがローミングの仕組みです。ノートパソコンが Wi-Fi からモバイルネットワークへ移動しても、セッションは同じまま維持されます。セッションは IP アドレスではなく、鍵とインデックスで識別されるためです。再接続は発生しません。TCP の意味で接続されていたことがないためです。
同じ仕組みにより、知っておくべき事実も生じます。パブリックアドレスを持つ peer は、相手側の最後に確認されたパブリック IP を常に保持しており、wg show で表示できます。
固定されたプリミティブで、ネゴシエーションはありません
WireGuard には暗号スイートの一覧がありません。認証付き暗号化には ChaCha20-Poly1305、鍵合意には Curve25519、ハッシュには BLAKE2s、鍵導出には HKDF を使用します。すべての構成でこれらを使用するため、解析すべきネゴシエーション段階がなく、より弱い方式へダウングレードする経路もありません。代わりに、明確な制約があります。これらのプリミティブのいずれかが破られた場合、対処にはプロトコル全体の新しいバージョンと、両端の更新が必要です。設定変更では対応できません。この単一の設計判断により、TLS ベースのトンネルが抱えるコードの大部分と障害要因の大部分が取り除かれます。WireGuard と OpenVPN の比較で主に比較しているのは、この点です。
スキャナーにポートが応答しない理由
すべてのハンドシェイクメッセージには、mac1 と呼ばれるフィールドが含まれます。これは MAC(メッセージ認証コード)であり、responder の静的公開鍵から導出した鍵を使用してメッセージ全体から計算されます。その公開鍵を知らない送信者は有効な mac1 を生成できないため、受信側はそのパケットを一切応答せずに破棄します。エラーも、リセットも、ICMP メッセージも返しません。
外部から観測すると、UDP スキャンに対して何も返らない状態になります。
sudo nmap -sU -p 51820 vpn.example.comnmap は open|filtered を報告します。これは、ファイアウォールがポートを黙って破棄した場合と同じ結果です。WireGuard が待ち受けているかどうかにかかわらず、少なくとも公開鍵を持っていない相手から見ると、ポートの動作は同じです。
2 つ目のフィールドである mac2 は、DoS 攻撃による負荷に対処します。受信側が高負荷になると、有効な initiation に対して送信元アドレスに紐付いた 64-byte の cookie reply を返します。そして、送信者がその cookie を返送するまで、コストの高い公開鍵処理を行いません。これにより、CPU を処理に割り当てる前に送信元アドレスが実在することを確認できます。この仕組みは高負荷時だけ有効になります。
0.0.0.0/0 がピアをデフォルトルートにする理由
AllowedIPs はルーティングテーブルなので、AllowedIPs = 0.0.0.0/0, ::/0 はそのピアにすべての宛先を割り当てます。これがフルトンネルの設定全体です。
これを機能させるルーティングは、行そのものより複雑です。wg0 経由の単純なデフォルトルートではループが発生します。トラフィックを運ぶ暗号化済みの UDP パケット自体もマシンから送信する必要があり、そのパケットも自身のデフォルトルートに一致するためです。wg-quick はポリシールーティングでこれを回避します。WireGuard 自身が送信するパケットに fwmark を付け、トンネルのデフォルトルートを別のルーティングテーブルに配置し、マークのないトラフィックだけがそのテーブルに到達するようルールを追加します。ip rule show を実行すると、結果を確認できます。
32764: from all lookup main suppress_prefixlength 0
32765: not from all fwmark 0xca6c lookup 51820
32766: from all lookup main0xca6c は 16 進数で 51820 を表し、51820 はテーブル番号でもあります。suppress_prefixlength 0 ルールにより、main テーブルは自身のデフォルトルートをスキップします。そのため、ローカルサブネットなどの具体的なルートが優先され、それ以外のトラフィックはトンネルのテーブルに進みます。スプリットトンネルでは、この設定は不要です。AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 のような、より限定された一覧は main テーブルの通常のルートになります。
フルトンネルだけでは、名前解決の問題は解決しません。通常、クライアントがローカルネットワークから取得した resolver はそのまま残り、その経路のほうが具体的だからです。これは別の対応が必要な問題で、WireGuard トンネルの外部に漏れる DNS で説明します。
PersistentKeepaliveの本当の用途
WireGuardは、通信がない場合には何も送信しません。ハートビートも、セッションの更新も、ネットワーク上のパケットもありません。この無通信により、バッテリー消費を抑えられます。また、前述のスキャナーのケースにも有効ですが、特定の構成では問題になります。
NAT(network address translation)またはステートフルファイアウォールの背後にあるピアは、その装置にマッピングが存在する間だけ外部から到達できます。このマッピングは、外向きのパケットによって作成されます。一般的なUDPマッピングの有効期間は、約30秒から始まります。マッピングの有効期限が切れると、中間装置はパブリック側からのパケットを破棄します。その後、NATの背後にあるピアが何かを送信するまで、トンネルは停止したように見えます。PersistentKeepalive = 25は25秒ごとに認証済みの空パケットを送信します。これは一般的な最短の有効期間を下回るため、マッピングが維持されます。
NATの背後にあるピアに設定します。パブリックアドレスと開放されたUDPポートを持つサーバーには必要ありません。サーバー側に設定すると、通信量が増えるだけです。自動キープアライブと混同しないでください。自動キープアライブは、ピアがデータを受信した後、応答として送信するデータがない場合に 10 秒後に送信されます。これは常に有効で、設定できません。
トンネル経由で LAN をルーティングする機能は WireGuard にはない
ピア B がホームネットワーク 192.168.50.0/24 上にあり、ピア A からそこへ到達する必要があるとします。この動作には、2 つの別々のシステムによる設定が必要です。そのうち WireGuard が担うのは 1 つだけです。
WireGuard 側の設定は、A の AllowedIPs に 192.168.50.0/24 を追加することです。これにより、A はそのプレフィックスを B へルーティングし、B から送信された該当ソースアドレスのパケットを受け入れます。これを設定しないと、cryptokey routing に宛先の鍵がなく、ソースアドレスに対する許可もありません。
カーネル側では、B の net.ipv4.ip_forward を 1 にする必要があります。そうしないと、カーネルは B 自身宛てではない復号済みパケットをすべて破棄します。B のファイアウォールの forward chain では、対象トラフィックを許可する必要があります。LAN 上のホストには 10.8.0.0/24 への戻りルートが必要です。戻りルートを設定しない場合は、B で source NAT を適用して、応答が B 経由で戻るようにします。
WireGuard の処理は、復号済みパケットをカーネルへ渡した時点で終わります。それ以降は通常の Linux のルーティングとフィルタリングです。そのため、この問題は nft list ruleset のカウンターや ip -s link show wg0 に現れ、wg show には現れません。Web インターフェイスでピアを管理する場合は、Docker で wg-easy を実行するとピアのエントリが自動生成されます。ただし、forwarding rules は引き続きホスト側で設定する必要があります。
WireGuard がカーネル内で動作する理由
wg0 はネットワークデバイスドライバーです。パケットは通常のルーティングスタックを通り、softirq コンテキストで暗号化されます。その後、userspace に移行することなく UDP ソケットから送信されます。これが高いスループットを実現する理由です。また、モジュールが約 4000 行に収まり、レビューしやすく、2020 年 3 月に mainline Linux 5.6 へ統合できた理由でもあります。Ubuntu 24.04 と Debian 13 には含まれているため、不足しているのは wireguard-tools パッケージだけです。
通常のインターフェースとして動作することには、実用上の利点があります。tcpdump -ni wg0 には暗号化前の内部パケットが表示され、tcpdump -ni eth0 udp port 51820 には暗号化後の外部パケットが表示されます。両方を比較すれば、どの方向で問題が発生しているかをすぐに判断できます。netfilter とトラフィックシェーピングは、wg0 を他のリンクと同じように扱います。コンテナ仮想化など、ホストのカーネルを共有してカーネルモジュールを利用できない環境では、wireguard-go が TUN デバイス上の userspace で同じプロトコルを実装します。ただし、すべてのパケットがカーネル境界を 2 回通過するため、スループットは実際に低下します。
WireGuard では保護できないこと
脅威モデルは意図的に限定されています。このように動作が静かなプロトコルでは、都合のよい期待を抱きやすくなります。明確にしておきます。
- WireGuard を使用している事実は隠せません。ハンドシェイクメッセージのサイズは固定され、先頭バイトにメッセージ種別が示され、トランスポートには UDP が使われます。ディープパケットインスペクションで容易に識別でき、VPN を嫌うネットワークではブロックされる可能性があります。難読化が意図的に省かれているためです。
- 通信量やタイミングは隠せません。ペイロードは 16 バイト境界までしかパディングされないため、監視者には送信した時刻と、おおよその通信量が分かります。
- 最後に確認されたエンドポイントを保持します。パブリックアドレスを持つピアは、相手側の現在のパブリック IP を保存し、
wg showに表示します。設定内で固定されたトンネルアドレスと組み合わせると、ネットワーク間を移動するユーザーを追跡できる安定した識別子になります。自分の VPS で運用する場合は問題ありません。商用サービスがプロトコルの上位にレイヤーを追加する理由もここにあります。 - 認証するのは人物ではなく鍵です。秘密鍵ファイルを保持している者がピアになります。
/etc/wireguardのモードを 700 にし、鍵ファイルのモードを 600 にしてください。 - 失効リストも有効期限もありません。アクセスを終了するには、そのピアエントリを保持しているすべてのサーバーから削除します。静的鍵は削除するまで有効です。
これらは WireGuard が脆弱だという意味ではありません。WireGuard が小さく設計されているということです。目的はそこにあります。WireGuard は認証と暗号化を担い、ID 管理とアドレス割り当ては、その上に構築する仕組みに任せます。WireGuard と Tailscale の比較で説明したようなコーディネーションレイヤーは、まさにこの不足を補うために存在します。ここまで説明してきたものと同じデータプレーンを使用します。
FAQ
WireGuard の cryptokey routing とは何ですか?
cryptokey routing は、すべてのパケットを公開鍵に関連付ける規則です。各 peer エントリには、AllowedIPs にプレフィックスの一覧が含まれます。送信時、WireGuard はパケットの宛先を各 peer の一覧と照合し、最長一致した peer を選択します。そのため、この一覧はルーティングテーブルとして機能します。受信時、パケットの復号と認証が完了すると、内部の送信元アドレスが同じ peer の一覧に含まれている必要があります。含まれていなければ破棄されるため、この一覧はアクセス制御リストとしても機能します。WireGuard に個別のルーティング設定や内部ファイアウォールがないのは、1 つの一覧が両方の役割を担うためです。
両方の peer に PersistentKeepalive が必要ですか?
いいえ。NAT(ネットワークアドレス変換)の内側、またはステートフルファイアウォールの内側にある側に設定します。通常はクライアント側です。WireGuard はアイドル中に何も送信しないため、相手側からその peer に到達するためのマッピングが失効します。多くの場合、1 分以内に失効し、トンネルが一方向だけ停止したように見えます。PersistentKeepalive = 25 は 25 秒ごとに認証済みの空パケットを送信し、マッピングを維持します。パブリックアドレスを持ち、UDP ポートを開放している peer には必要ありません。
トンネル経由の ping で「Required key not available」と表示されるのはなぜですか?
宛先アドレスがどの peer の AllowedIPs にも含まれていないためです。cryptokey routing がパケットの暗号化に使用する鍵を見つけられず、カーネルが送信を拒否しています。wg show wg0 allowed-ips を実行し、その出力を ping の宛先アドレスと比較してください。よく似たエラー Destination address required は別の問題です。peer との一致は見つかったものの、その peer の endpoint が WireGuard にない状態です。endpoint が設定されておらず、その peer から認証済みパケットもまだ到着していないことが原因です。
ファイアウォールで WireGuard を検出してブロックできますか?
はい。WireGuard は通信を認証および暗号化しますが、自身を隠そうとはしません。ハンドシェイクメッセージの長さは 148 バイトと 92 バイトで固定され、各メッセージの先頭バイトが種類を示します。また、トランスポートには UDP を使用するため、ディープパケットインスペクションでプロトコルを容易に識別できます。UDP をブロックするネットワークや、プロトコルのフィンガープリントを利用するネットワークでは WireGuard が停止します。トンネルを隠すには、別の仕組みで WireGuard を包む必要があります。これは WireGuard の設定ではなく、別のツールを使う方法です。