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

# 概要

GCPのVPNにOSSで繋ぎたい。

基本的には[この記事](https://blog.paranoidsoftware.com/pfsense-ipsec-vpn-connection-to-azure-aws-and-google-cloud-2/)とやってることは同じ。ただ重要な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でアサインされたグローバルアドレスを入力する
    - [確認くん](http://www.ugtop.com/spill.shtml)を使うとかで調べておく
    - 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のドキュメント](https://cloud.google.com/compute/docs/vpn/advanced?hl=ja#supported_ike_ciphers)に合わせて入力する。
        - 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内に収まっていたのが確認できた。