VPN
ipsec
gcp
pfSense
IPsec-VPN

GCP VPNにNAT配下のpfsenseから接続する

概要

GCPのVPNにOSSで繋ぎたい。

基本的にはこの記事とやってることは同じ。ただ重要なMSS Clampingの説明とか、オンプレ(ローカル)側に複数サブネットある時どうすんの?とかが足りてないので補足。

あとフレッツPPPoE配下の場合の最適なMSSの計算方法も書く。

環境

  • PPPoEのフレッツ使用
    • ISPからはグローバルIPがアサインされる
  • ルーターはHGW等でNAPTはそこで実施している
  • pfsenseはLAN配下
    • 足はLANに1本のみ
    • LANのプライベートアドレスをstaticに設定する
  • pfsense 2.4.1

手順

GCP側での作業

VPN作成

WebコンソールからGUIポチポチでVPNを設定する。ポイントは2つ。

  • リモートピアIPアドレス つまりこちら側のアドレスは、PPPoEでアサインされたグローバルアドレスを入力する
    • 確認くんを使うとかで調べておく
    • CLIでなら curl ifconfig.me
  • リモートネットワークIPの範囲 にはこちら側のLANのサブネットを入れておく
    • 複数ある場合は、複数サブネットを1つのトンネル定義に入れてOK

FW設定

定義しないと何も通信出来ない。忘れずに許可ルールを適当に作成する。

オンプレ(自宅)側での作業

pfsenseインストール

VMでも何でもOK。ESXiに入れた奴と、MacのVirtualBoxに入れたやつ両方で動作確認済み。公式サイトからiso拾ってきてよしなにやる。特に詰まるところは無い。

pfsense設定

…を細かく全部書くと膨大になるので、要点を書く。

  • System > General Setup
    • Hostname, Domain, DNS Servers, Timezone, Timeservers、等など適宜設定
  • System > Advanced > Firewall & NAT
    • Disable Firewall に チェックを入れてはいけません。 MSS Clampingが効かなくなる!パケットフィルタを全く使わない場合でも、面倒でも許可ルールをちゃんと定義しましょう。これでかなりハマった。
  • VPN > IPsec > Tunnels > Edit Phase 1
    • Key Exchange versionはIKEv2を明示的に指定した方が良い。Autoでも問題なく繋がるが。
    • My IdentifierはIP addressを選択し、PPPoEのグローバルアドレスを入力する。
    • 各種パラメータはGCPのドキュメントに合わせて入力する。
      • DH Groupはauto等がないので、適当に選ぶ必要がある。ドキュメントに記載されているものであれば問題ない。14を(2048bit)選択。
  • VPN > IPsec > Tunnels > Edit Phase 2
    • こちらも上記のGCPドキュメントを参考に値を入れる
      • PFS key groupがデフォルトでnoになっているので要注意。適切な値に変えておかないと、最初は接続出来るが暫く経って接続が切れる現象(CHILD_SAの再作成時にパラメータミスマッチでエラー、の無限ループ)にハマる。5(1536bit)を選択。
    • Local Network / Remote Network どちらもprefixが一つしかはいらない。複数設定する必要がある場合は、一つのPhase 1に対して複数のPhase 2 Entryを作れば良い。
      • パラメータはLocal Network以外全て揃えておく
  • VPN > IPsec > Advanced Settings
    • Enable Maximum MSSを有効化する
    • Maximum MSSには1334を入れる (詳細後述)
  • Firewall > Rules > Floating
    • 特にフィルタが必要なければ全許可のルールを作る
      • InterfacesのところでNICとIPsecの2つ両方を選択するのを忘れずに
  • Services
    • GCP VPNのゲートウェイとしか使わないのであれば不要なので全部Disableする
    • 必要な物があれば適宜設定する

クライアントのルーティング

GCP側のVPCのprefixのnexthopをpfsenseの足に向けてあげる。例としてLinuxの場合はこんな感じ。

# LAN prefix: 192.168.0.0/24
# pfsense ip: 192.168.0.10
# GCP VPC prefix: 10.7.0.0/26

ip route add 10.7.0.0/26 via 192.168.0.10

もしくは、HGWなりのLAN側ルーターに上記のStatic Routeを追加してあげばクライアント側に個別の設定は要らない。ちゃんとやるならIGP喋らせるんだけど、pfsenseはOSPFとかは出来ない。

接続

NAT配下なので オンプレ側から接続しないと繋がらない。 GCP側のVPCのアドレスに対してローカルからpingを打つなりすると、自動的にトンネルがセットアップされる、はず。

手動で操作する場合は Status > IPsec からConnectボタンをポチッとな。

考察

MTUとMSS Clamping

IPSecトンネルを通すとオーバーヘッドによりMTUが減少する。PMTUDが効かない場合、経路上のルーターがTCP SynのMSSを書き換えることで経路途中でのフラグメント処理を回避しスループット低下を防ぐ。

例えばPPPoEフレッツの場合、MTUは1454なのでMSSは1414となるが、HGWがPCからインターネットへ抜ける際にTCP SynのMSSを1460(NICの設定をいじっていない場合、EthernetのMTU1500からIP 20byte + TCP 20byteを引いた値)から1414に書き換えてくれている。この辺はブロードバンドが普及した頃にたくさん記事が公開されているので、ちょっとググれば色々な情報がすぐ見つかるだろう。

さてIPSecトンネルを通す場合も、そのオーバーヘッドでトンネルを通せるパケットのMTUは減少する。そしてGCP VPNはIPパケットのフラグメント処理を許可しないので、LANから入ってきてトンネルに抜ける際に、パケットサイズがIPSecのヘッダ等で増加してMTUを超えてしまった場合は通信が出来なくなる。逆も然り。これを防ぐため、pfsenseでMSS Clampingを有効化したのだ。

さて問題は、ここで何故 1334 という数字を設定したか。

結論から言うと、

  1454 (フレッツPPPoE MTU)
- 14   (Ethernet Header)
- 20   (IP Header)
- 8    (UDP Header: IPSec NAT-T)
- 8    (ESP Header)
- 16   (IV: AESの場合)
- 2    (ESP Trailer: Padding lengthで1 + Next Headerで1)
- 12   (ESP 認証ヘッダ: SHA1の場合)
= 1374
- 20   (IP Header Inner)
- 20   (TCP Header Inner)
= 1334

という感じで、そうなっている。IPSecのオーバーヘッドは80byteということになる。

実はこの調査をしている時、フレッツのMTUから減算してMSSに設定する値は1334だ、という数字を導き出したわけではなく、VM間でpingを打って通る最大パケットサイズを調べ、そこから逆算して検算した。

ping -M do -s 1346 10.7.0.2
# → 通る

ping -M do -s 1347 10.7.0.2
# → 通らない

pingのオーバーヘッドは28byte(IP 20byte + ICMP 8byte)なので、通る最大サイズの1346に28を足した1374がIPSecのMTU、という算出方法。

ちなみにMSS Clampingの設定を外すと、sshが出来なくなる(最初のホストキー交換あたりでハングする)ので、うまく動いているということが分かりやすい。

tcp-segmentation-offload

MSSの調査をしている時、VM上でtcpdumpをしていると何故かframe sizeが2000以上のパケットがキャプチャされて、なんだこれ?と思って調べてみたらTCP segmentation offloadなるものがあるらしい。送信・受信側両方で有効なので、どちらでtcpdumpしてもMTUを遥かに越えたサイズのパケットが見えた。

pfsenseのホストでIPSecでカプセリングされた後のパケットを見たら、ちゃんと全部MTU内に収まっていたのが確認できた。