WireGuardの仕組みとAllowedIPsの読み方
WireGuardのAllowedIPsは、ルーティングテーブルとアクセス制御リストを兼ねます。cryptokey routing、Noiseハンドシェイク、鍵のローテーションを理解し、wg0.confを正しく読めるようにします。
WireGuard の仕組みを 1 つの考え方で理解する
WireGuard は、すべてのパケットを公開鍵に結び付けて動作します。この仕組みには cryptokey routing という名前があり、WireGuard の設計そのものです。ピアの隣にある AllowedIPs 行は、マシンから送信するパケットのルーティングテーブルであると同時に、そのピアから受信するパケットのアクセス制御リストでもあります。1 つの設定が 2 つの役割を担います。AllowedIPs をこのように読むと、WireGuard の設定ファイルはすべて理解しやすくなります。
IP アドレスをキーにしたセッションテーブルや、ユーザーデータベースはありません。ピアは、公開鍵と、その鍵が使用できるアドレスの集合です。ハンドシェイクとタイマーは、下位のネットワークが変化してもこの対応関係を維持するために存在します。理論より先に動作するトンネルが必要なら、自分の VPS でセルフホストする WireGuard VPN を構築してください。その後、設定行の意味に疑問を持ったときに、ここへ戻ってきてください。
AllowedIPs はルーティングテーブルであり、アクセスリストでもあります
まず送信方向を見ます。カーネルは通常どおり、メインルーティングテーブルを通じてパケットを wg0 デバイスへルーティングします。次に WireGuard は、そのパケットの 宛先 アドレスを、すべての peer の許可プレフィックスを保持する 1 つのテーブルと照合します。照合は最長プレフィックスから行われます。一致すると peer が特定され、その peer から公開鍵、セッション鍵、UDP エンドポイントが特定されます。パケットはその peer 向けに暗号化され、送信されます。
どの peer の AllowedIPs にも宛先が含まれない場合、送信されません。送信に使用できる鍵がないためです。
ping: sendmsg: Required key not availableこのエラーが示すことは 1 つです。到達しようとしたアドレスが、どの peer にも登録されていません。別のエラーである ping: sendmsg: Destination address required は、peer には一致したものの、その peer のエンドポイントが WireGuard に存在しないことを示します。エンドポイントが設定されておらず、まだ学習もされていないためです。
次に受信方向を見ます。UDP パケットが listen ポートに到着します。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同じキーワードでも、両側での意味は正反対です。クライアントでは「すべての宛先へこの peer 経由で送信する」という意味です。サーバーでは「この peer からはこのアドレスだけを受け入れる」という意味です。この非対称性は役割ではなく、値にあります。
これらのキーの半分は、プロトコルの設定ではありません。Address、DNS、MTU、PostUp、SaveConfig は、インターフェースを起動するシェルスクリプトである wg-quick に属します。カーネルはこれらを認識しません。wg-quick strip wg0 は、wg ツールが実際に読み込む簡略化された設定を出力します。この分離を確認するには、これが最も簡単です。
ハンドシェイクで実際に行われる処理
WireGuard のハンドシェイクは、Noise Protocol Framework に基づく Noise_IKpsk2 です。IK の部分は、sysadmin にとって重要です。レスポンダーの静的公開鍵はイニシエーターが事前に把握しています。これは [Peer] ブロック内の PublicKey です。また、イニシエーターは最初のメッセージに自身の静的公開鍵を暗号化して含めます。そのため、証明書の交換も、身元確認の往復通信もありません。受動的な観測者は、レスポンダーの秘密鍵を持っていない限り、どの鍵が接続を開始したかを判断できません。
必要な通信は 1 往復です。開始メッセージは 148 bytes、応答は 92 bytes で、その直後からデータが流れます。各側はハンドシェイクごとに新しい一時 Curve25519 鍵ペアを生成します。セッション鍵は、静的鍵と一時鍵を組み合わせた Diffie-Hellman の結果を連鎖させて生成されます。一時秘密鍵はその後破棄されるため、forward secrecy が得られます。つまり、第三者が今日の通信を記録し、来年サーバーの秘密鍵を盗んでも、記録済みの通信を復号できません。
ハンドシェイクの開始メッセージには 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 は任意です。
endpoint が設定されたピアはハンドシェイクを開始できます。endpoint が設定されていないピアは待機し、正しく認証された最初のパケットから相手側のアドレスとポートを取得します。取得した endpoint は保存され、有効なパケットが新しいアドレスから届くたびに更新されます。これがローミングの仕組みです。ノート PC が Wi-Fi からモバイルネットワークへ移動しても、セッションは同じまま維持されます。セッションは IP アドレスではなく、鍵とインデックスによって識別されるためです。TCP の意味で接続されていたわけではないため、再接続も発生しません。
この同じ仕組みから、知っておくべき事実が導かれます。公開アドレスを持つピアは、相手側の最後に確認されたパブリック 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 はそのピアにすべての宛先を割り当てます。これがフルトンネルの設定全体です。
これを機能させるルーティングは、この1行よりも複雑です。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 は 51820 の16進数表記で、51820 はテーブル番号でもあります。suppress_prefixlength 0 ルールは、メインテーブルが自身のデフォルトルートを使用しないようにします。そのため、ローカルサブネットなどの具体的なルートが優先され、それ以外のトラフィックはトンネルテーブルへ渡されます。スプリットトンネルでは、この設定は必要ありません。AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 のように範囲を絞ったリストは、メインテーブルの通常のルートになります。
フルトンネルだけでは名前解決の問題は解決しません。通常、クライアントがローカルネットワークから取得したリゾルバー設定がそのまま残り、その経路のほうが具体的だからです。これは別の作業であり、WireGuard トンネルの外部へ漏れる DNS で説明します。
PersistentKeepaliveの本当の用途
WireGuardは、通信がないと何も送信しません。ハートビートも、セッションの更新も、ネットワーク上のパケットも発生しません。この無通信状態はバッテリーの消費を抑え、前述のスキャナーのケースにも役立ちます。一方で、特定の構成では問題になります。
NAT(ネットワークアドレス変換)またはステートフルファイアウォールの背後にあるピアは、その装置にマッピングが存在する間だけ外部から到達できます。このマッピングは、外向きのパケットによって作成されます。一般的な UDP マッピングの有効期間は約 30 秒から始まります。マッピングの有効期限が切れると、中間装置によってパブリック側からのパケットが破棄されます。そのため、NAT の背後にあるピアが何かを送信するまで、トンネルは停止したように見えます。PersistentKeepalive = 25 は 25 秒ごとに認証済みの空パケットを送信します。これは一般的な最短の有効期間より短いため、マッピングが維持されます。
NAT の背後にあるピアで設定します。パブリックアドレスと開放された UDP ポートを持つサーバーには必要ありません。サーバー側で設定すると、通信量が増えるだけです。自動 keepalive と混同しないでください。自動 keepalive は、ピアがデータを受信した後、返信するデータがない場合に 10 秒後に送信されます。これは常に有効で、設定できません。
トンネル経由で LAN をルーティングする機能は WireGuard にはない
ピア B がホームネットワーク 192.168.50.0/24 上にあり、ピア A からそこへ到達する必要があるとします。この動作には 2 つのシステムが合意している必要があり、そのうち WireGuard が担当するのは 1 つだけです。
WireGuard が担当する設定は、A 上で B の 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 を経由するように、B で source NAT を適用する方法もあります。
WireGuard の役割は、復号したパケットをカーネルに渡した時点で終了します。それ以降は通常の Linux のルーティングとフィルタリングで処理されます。そのため、この問題は nft list ruleset のカウンターや ip -s link show wg0 に現れ、wg show には現れません。ピアを Web インターフェースで管理する場合は、Docker で wg-easy を実行することでピアエントリを生成できます。ただし、転送ルールは引き続きホスト側で管理します。調整レイヤーによってプレフィックスが配布される構成でも、この分担は変わりません。Tailscale のサブネットルーターを使用して VPS からプライベートネットワークを広告する構成では、各ピアで手動の AllowedIPs 編集を行う必要がなくなります。ただし、転送用の sysctl とルーター自体のファイアウォールルールは、引き続き自分で設定する必要があります。
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-byte 境界までしかパディングされないため、監視者には送信した時刻と、おおよその通信量が分かります。
- 最後に確認されたエンドポイントを保持します。パブリックアドレスを持つ peer は、相手側の現在のパブリック IP を保存し、
wg showに表示します。設定内で固定されたトンネルアドレスと組み合わせると、ネットワークを移動するユーザーを追跡できる安定した識別子になります。自分の VPS では問題ありません。商用サービスがプロトコルの上にレイヤーを追加する理由もここにあります。 - 認証するのは人物ではなく key です。private key ファイルを保持する者が peer になります。
/etc/wireguardは mode 700 にし、key ファイルは 600 にしてください。 - 失効リストも有効期限もありません。アクセスを終了するには、その peer エントリを保持しているすべてのサーバーから削除します。static key は削除するまで有効です。
これらは WireGuard が脆弱だという意味ではありません。WireGuard が小さく設計されているという意味です。重要なのはその点です。WireGuard は認証と暗号化を担い、identity management と address allocation は、その上に構築する仕組みに任せます。WireGuard と Tailscale の比較で説明したような coordination layer は、まさにこの不足を補うために存在し、ここまで説明してきたものと同じ data plane を使用します。そのような layer を導入した後は、トンネル内で運用するサービスにアクセスできるユーザーを決める必要があります。単一の port については、Tailscale serve と funnel の選択がこの判断を定めます。
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 がパケットの暗号化に使用する鍵を見つけられず、kernel が送信を拒否しています。wg show wg0 allowed-ips を実行し、その出力を ping の宛先アドレスと比較してください。似たエラーである Destination address required は別の問題です。peer は一致していますが、その peer の endpoint が WireGuard にありません。endpoint が設定されておらず、その peer から認証済みパケットもまだ到着していないことが原因です。
ファイアウォールは WireGuard を検出してブロックできますか?
はい。WireGuard は通信を認証して暗号化しますが、通信を偽装しようとはしません。ハンドシェイクメッセージのサイズは固定で 148 バイトと 92 バイトです。すべてのメッセージの先頭バイトが種類を示し、トランスポートには UDP を使用します。そのため、ディープパケットインスペクションでプロトコルを容易に識別できます。UDP をブロックするネットワークや、プロトコルのフィンガープリントを利用するネットワークでは、WireGuard は停止させられます。トンネルを隠すには、別の仕組みでラップする必要があります。これは WireGuard の設定ではなく、別のツールの役割です。