読者です 読者をやめる 読者になる 読者になる

Tech Beans

Web企業で働くエンジニアのBlog


strongSwan でVPN(IKEv2) を 構築する Part.2 ~ MTU値の調整 ~

特定のサイトにつながらない

前記事で設定したVPNサーバーを経由した場合に、特定の一部サイト(github.com, yahoo.com)にアクセスできないことがわかった。

現象としては、

  • ping は通るが、80ポートの返答がない
  • https, httpなどプロトコルにはよらない
  • VPNサーバーとして動かしているLinux(CentOS)上からはアクセスできる。

tracerouteの結果は、最初の数段は通るが、以降はtimeoutしてしまっていた。

これもかなりハマってしまったが、原因を探してみると、どうやら経路間のMTU値の設定がVPNを経由した場合にうまくいかないようだ。

PMTUD

MTUはMaximum Transmission Unitの略で、1フレームごとに送信するデータの最大値を意味する。 MTUは現在ではデフォルト1500になっていることが多いが、経路によってはこの値が異なる場合がある。

自宅にはNTT東日本FTTHを引いているが、PPPoE経路上では追加のヘッダーが付与される関係上この値が小さくなる。 実際に自宅のルーターの設定を覗いてみるとMTU= 1454バイトに設定されていた。

通信の際には、経路上で最小のMTU値に合わせてデータを送信する必要がある。 もし、最小MTU値を超えたフレームを送信した場合、フレームを分割送信するフラグメンテーションを行って再送する必要がある。(というICMPパケットが送信先から返ってくるらしい。あまり詳しくない。。)

で、このフラグメンテーションはパフォーマンス上大変よろしくないので、できれば最小MTU値で送信してフラグメンテーションしないようにしたい。 ところが、この最小MTU値は、接続先までの経路によって異なり、固定ではない。

そこで、その最小MTU値を決定するために、PMTUD(Path MTU Discovery)がある。 PMTUDの詳細についてはググれば出てくるので割愛。

ウェブサイトへのアクセスが失敗する理由

さて、問題はここから。

PMTUDはICMPパケットを利用するため、一部の経路でこれが遮断されていると使えない。 ICMPは本来遮断すべきではないのだが、pingを塞ごうとしてICMPまるごと塞いでしまっている経路が存在する。

その場合、フラグメンテーションによって解決されるわけだが、そこに今回のVPN特有の問題がある。

参考サイト

によると、

Instead of fragmenting a too-large IP packet, the VPN server is told (through the Don’t Fragment (DF) flag in the IP header of the sender) to discard the packet and reply with an ICMP fragmentation required (type3, subtype 4) message.

本来、アクセスしたいウェブサーバーが、このMTU値を超えた通信を行ってきた場合に、VPNサーバーはそのパケットを破棄し、代わりにICMP fragmentation required を返す。

When the sender receives this ICMP packet, it learns to use a smaller MTU for packets sent to our VPN server. In theory. In reality, many websites (senders like www.yahoo.com) stupidly implement ICMP filters that break PMTUD functionality.

本来であれば、このパケットを受け取ったサーバーはMTU値を調整して再送してくれるはず(google.comなど)だが、一部のサーバーではこれがうまく実装されておらず、延々とack待ちになってしまう。 この実装の違いが、VPNを通した場合にアクセスできたりできなかったりするサーバーの違いだ。

結果VPNクライアントからみると通信がtimeoutしてしまうわけだが、まさに発生している現象とそっくりだ!

実際、VPNサーバー上でtcpdumpを取ってみると、

21:38:09.594323 IP {VPNサーバー} > {接続先IP}: ICMP {VPNサーバー} unreachable - need to frag (mtu 1374), length 556

というようなログが出ている。やはりMTU値が問題なようだ。

CentOS7 firewalldでのMTU値の調整

対応として、参考サイトにはiptablesの設定がのっている。

$ iptables -t mangle -A FORWARD -o eth0 \
 -p tcp -m tcp --tcp-flags SYN,RST SYN \
 -s 192.168.12.0/24 \
 -m tcpmss --mss 1361:1536 \
 -j TCPMSS --set-mss 1360

$ echo 1 >/proc/sys/net/ipv4/ip_no_pmtu_disc
  • 1行目

    • -s 192.168.12.0/24 はクライアントのソースIPで条件指定している。ここでは前回設定したVPNクライアントのサブネットを指定
    • --mss 1361:1536 でMSSが1361 ~ 1536の間にあるパケットだけ絞って設定を適用できる。
  • 2行目

    • UDPのための設定

今回はCentOS7上での設定となるため、これを書き換えて以下のコマンドを実行した。

CentOS7, Firewalld

# firewall-cmd --direct --permanent \
--add-rule ipv4 mangle FORWARD 1 \
-p tcp -m tcp --tcp-flags SYN,RST SYN \
-s 192.168.12.0/24 \
-m tcpmss --mss 1301:1536  \
-j TCPMSS --set-mss 1300
# echo 1 >/proc/sys/net/ipv4/ip_no_pmtu_disc
# systemctl restart firewalld

MSSが1360では自分の環境では動作しなかったので、1300にしている

再起動が終わった後に試してみると、

ちゃんとVPNクライアントからgithub.com, yahoo.comにアクセスできるようになった!

長かった!

ちなみに

こちらのQiitaなどで紹介されている

 firewall-cmd --direct --add-passthrough ipv4 -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

は上手くいかなかった。どうやらVPNクライアントでは上手く動作しないようだ。