WireGuard 内の IPIP:誰も予算に入れなかった余分な 20 バイト
IPIP は WireGuard の内側で 20 バイト余計にかかる。生きているトンネルで止まった転送をフラグメンテーションのせいにする前に、正しい層で予算を計算しよう。
小さな ping は一日中トンネルを通り、SSH のバナーが届き、そしてファイル転送が始まった瞬間にセッションが固まる。このパターンは、間違った層で数えたバイト予算を指している。外側のトンネルは自分のオーバーヘッドだけを見て設計され、内側の分を誰も差し引いていなかった。入れ子になった IPIP パケットは WireGuard にとってただの別の IP パケットであり、だからこそ予約していないバイトを消費する。修正は算数であり、三つのコマンドと 一つの表に収まる。
それを見破る症状
フラグメンテーションの問題はサイズで現れ、アドレスで現れることはない。相手は 56 バイトの ping に 一時間応答し、その後 1400 バイトで沈黙する。トンネルは上がったままで、ルートも変わっていない。ハンドシェイクは収まり、最初の完全な TCP ウィンドウは収まらず、健全に見えた数秒後に転送が止まる。
何かを編集する前に上限を測ろう。測っていない上限は二度追いかけることになる。疑わしいサイズより下で do-not-fragment ビットを立てたプローブを送り、ping -M do -s 1000 10.9.0.2、すでに大きすぎると分かっているサイズで繰り返す。宛先が変わらないまま反転がペイロードのサイズに従うなら、ルーティングは仕事をしており、予算からヘッダーが 一つ欠けている。
すべてのヘッダーを一度だけ、正しい層で数える
トンネルインターフェースではなく経路上の最小 MTU から始める。壁は両端より上流にあるからだ。WireGuard は IPv4 の外側トランスポートに 60 バイト使う。IP に 20、UDP に 8、自身のヘッダーに 32 で、そのトランスポートが IPv6 なら 80 バイトになる。1420 バイトの wg0 は、1500 バイトの経路が実際に届けられる 1440 の下に 20 バイトの余裕を残す。だからwiki からコピーした数字がここでも当てはまる。スタック全体を図に書き込もう。次に MTU を変える人が、そうしないと新しい丸い数字を選び、あなたのバグを受け継ぐ。
| Layer | Bytes | Remaining |
|---|---|---|
| Path MTU on the wired link | none yet | 1500 |
| WireGuard outer: IP, UDP, WG header | 60 | 1440 |
| Slack kept for safety | 20 | 1420 (wg0) |
| IPIP header inside the tunnel | 20 | 1400 (ipip0) |
| Original IPv4 and TCP headers | 40 | 1360 payload |
五行目が予算の狂う場所だ。入れ子のインターフェースが運ぶ 1400 バイトには、元のパケット自身の 40 バイトのヘッダーがすでに含まれている。そのうちプロセスに届くのは 1360 バイトだけだ。その列のない図が誘うように 1400 をアプリケーションのペイロードとして扱うと、内側のデバイスは動いていない壁に対して 40 バイトも寛大になりすぎる。
二つの MTU を別々に設定する
キャリアと入れ子のデバイスは別のオブジェクトなので、それぞれに自分の値が必要だ。この順序で適用しよう。入れ子のデバイスはキャリアから何も継承しない。
ip link set wg0 mtu 1420
ip tunnel add ipip0 mode ipip remote 203.0.113.9 local 198.51.100.4
ip link set ipip0 mtu 1400
覚えておく価値のある規則: IPIP トンネルが運ぶパケットが IPv4 なら内側の MTU = 外側の MTU - 20、内側のパケットのどちらかが IPv6 になれば - 40 だ。入れ子のヘッダーが 20 バイトから 40 に増えるからである。その引き算に他のものは入らない。WireGuard のオーバーヘッドは 1420 を選んだ時点ですでに支払われており、二重に課金すると完璧に動き、誰も測らない限り遅いままのトンネルができる。両方のデバイスが丸い 1400 を運ぶ配備は、まさにその間違いの指紋だ。
入れ子のパケットのどちらかの側に IPv6 があると上の数字はすべて無効になる。似たサイトから数字をコピーするのではなく、計算し直そう。IPv6 が外側の経路か元のデータグラムに現れた時点で、差し引くヘッダーはそれぞれ 40 バイトになり、ペイロードの数字はちょうどその分だけ下がる。
おそらく不要なファイアウォール規則
二つの WireGuard ピアの間で回線上のものは UDP だけなので、プロトコル 4、つまり RFC 2003 が IPv4-in-IPv4 カプセル化に割り当てた番号の受信規則は、入れ子の経路を作りもしなければ維持もしない。IPIP ヘッダーは暗号化されたペイロードの内側にあり、どのエッジファイアウォールもそれを見ない。プロトコル 4 を開けても、ホストへの許可された経路が 一つ増える以上のものは得られない。
元のパケットの送信元と宛先に言及するポリシーは、カプセル化解除の後、トンネルインターフェースとルーティングテーブルに属する。そこに置けば、エッジの規則は閉じたままでよい。
WireGuard が実際に検証するもの
AllowedIPs は WireGuard が運ぶパケットの送信元を判定し、運ぶのは IPIP でカプセル化されたパケットだ。許可された IPIP の送信元は、内側のヘッダーがルーティングしたくないホストを名指ししていても受け入れられる。したがって元の送信元はピアがあなたのために確認したものではなく、その内側のアドレスの組はカプセル化解除の後に独自の強制が必要だ。
ラッパーはルーティング構造を提供するのであって、第二の暗号層ではない。WireGuard の内側で IPIP を追加の保護と呼ぶのは別の設計を説明している。
一度の ping ではなく、はしごで検証する
計算した境界をまたぐサイズを試し、両方のデバイスについてカーネルが何を信じているかを確認する。1360 バイトのペイロードに 40 バイトのヘッダーを足して 1400 になるなら、下の最初のエコーは収まり、二番目は上限の 一バイト先に座る。
ping -M do -s 1372 10.9.0.2
ping -M do -s 1373 10.9.0.2
wg show wg0
ip -d link show ipip0
上限の下で綺麗に通り、その上の最初のサイズで失敗するなら、算数は端から端まで成り立っている。その後、もともと止まった転送を再実行しよう。合成プローブは予算を証明するが、送信側自身の書き込みサイズについては何も言わない。収まると証明したサイズでも入れ子の経路が壊れるなら、MTU からさらに 十バイト削る前に、遠端のカプセル化解除とルートを調べよう。