こんにちは。IPv6プロジェクトスタッフ 内田幸治です。
今回はIPv6サービス提供時にロードバランサ(以下、LB)を投入してサービスをする際にはまった、LBのICMPv6パケットの取り扱いについてのネタを紹介させていただきたいと思います。
このネタを説明するのにちょっとした基礎知識があるとわかりやすいので、まずは基本的技術に関して説明させていただきました。ネットワークに理解のある方は読み飛ばしていただければと思います。
前提の技術
まずは通信の基本的な技術である以下の4点を軽く説明します。慣れているかたはこの章は読み飛ばしてください。
- MTU(Maximum Transfer Unit)
- フラグメンテーション
- PathMTUディスカバリー IPv4/IPv6
- TCPスロースタート
MTU
インターネット中にはさまざまな形態の通信路があります。そこには巨大パケットを転送できる通信路もあれば、小さいパケットしか転送しかできないものなどさまざまな通信媒体があります。
各々の通信媒体により1パケットのサイズに上限が決まっており、MTU(Maximum Transfer Unitの略)と呼ばれています。例えば、イーサネットのMTUは 1500 octetsで、FDDIは 4352 octetsという具合です。
フラグメンテーション
MTUが大きい通信路をギリギリ通るような大きなパケットを、MTUが小さい通信路に向けて送信しようとしても当然そのままでは通りません。
そこで登場するのがフラグメンテーションです。これはMTUが小さい通信路に対して巨大パケットを送信する場合に、ルータがパケットを複数に分割して転送してくれるというものです。
複数に分割されたパケットが小さいMTUの通信路を抜けた先にいるルータに到着すると、元のパケットに再構築されて、データ転送が続けられていきます。 http://tools.ietf.org/html/rfc815
とても便利に見えるフラグメンテーションですが問題点も指摘されています。 http://tools.ietf.org/html/rfc4459
- データ転送量が増える
- ルータの計算量(負荷)が増える
- 前のデータは、後続データが来るまで使えない(非力なルータなどでは問題)
一方、IPv6では途中経路のルータはフラグメンテーションを行わない仕様です。その代わりに、エンドノードがパケットサイズに配慮する仕様になっています。 http://tools.ietf.org/html/rfc2460
Path MTUディスカバリー
通信するエンドノード間の通信路の中で最も小さいMTUをPath MTUと呼びます。このPath MTUを計測するのがPath MTUディスカバリーです。データ送信元がパケットをPath MTU値に設定してデータ送信することで途中ルータによるフラグメンテーションを回避できます。
IPv4 PathMTUディスカバリー
IPv4通信の場合、Path MTUディスカバリーのためにIPヘッダ中のDon't Fragment(以下、DF)ビットを利用します。DFビット付きでMTUをこえるパケットを送信するとルータからICMPで「Packet Too Big」の警告メッセージが送られてきます。ただ、残念なことに、この警告メッセージの中には適切なMTU値が記載されていないため、パケット送信元が試行錯誤するしかないのが現状です。
#いろいろな方法が提案されてはいますがここでは割愛
IPv4のPath MTUディスカバリーは以下を参照してください。 http://tools.ietf.org/html/rfc1191
IPv6 PathMTUディスカバリー
IPv6ヘッダ中にはIPv4ヘッダにあったDFビットがなくなりました。さらに、途中ルータがフラグメントせずにエンドノードが適切なパケットサイズでデータ転送するという仕様となりました。大きいパケットを送った場合の警告はICMPv6の「Packet Too Big」の警告メッセージで通知されます。
ICMPv6の警告は、ICMPのそれとは違い、制限を受けたMTU値も合わせて通知されます。エンドノードは、その値に従い適切なMTUを設定できます。
IPv6のPath MTUディスカバリーは以下を参照してください。 http://tools.ietf.org/html/rfc1981
TCPスロースタート
最後の予備知識として、TCPが備えるスロースタートがあります。これは、データ転送直後は小さいサイズのパケットでデータを送り、ちょっとづつデータサイズを増やしていくというものです。
ネットワーク輻輳などを軽減させるために、一度に負荷をかけ過ぎないように考慮する技術です。TCPスロースタートは後々かかわってきますので頭の片隅に置いておいていただければと思います。 http://tools.ietf.org/html/rfc3390
多様なMTUサイズの通信路でのパケット
ここからがいよいよ本記事の本番です^^
世の中のMTUがすべて同じだったら何も問題はありません。しかし、そうもいかないのが現実でございます。
以下の図のように通信するクライアントとサーバの間にMTUの小さな通信路がある場合を例にして考えたいと思います。
また、この構成の中にLBが投入されたときにどのようなことが起こるかを説明していきたいと思います。
IPv6巨大パケット送信 : LBなし編
IPv4の巨大パケットはルータがフラグメンテーションして転送するのが一般的でした。しかし、下の図のように、IPv6ではMTUをこえる巨大パケットに対してルータはフラグメンテーションを行いません。
では、MTU以上のパケットを受け取ったIPv6ルータはどうするか? それは巨大パケットの送信元に対して「パケット大きいです!」という警告をICMPv6で送ります。そして、そのパケットはルータで破棄されます。
そのためデータ転送元はあらかじめPath MTUディスカバリーを実施してPath MTUを計測し、それに合わせてパケットを転送するのが最も推奨される動作となります。Path MTUディスカバリーがうまくいかず、巨大パケットを送信してしまうと
ルータから警告が届くことになります。
警告が届いたパケット送信元(図のクライアント)はそれを見てパケットのサイズを小さくしてデータを再送する流れになります。
IPv6小さめパケット送信 : LBあり通信編
アクセスが集中しても滞りなくサービスを提供し続けるためにサーバの直前にLBを設置した場合を考えてみます。
通信は図のクライアントからサーバに対してデータ転送を要求するというような流れです。LBがあるので、クライアントは仮想IPに対してリクエストを送り、LBがそれを適切なサーバに割り振ります。
情報のやり取りが小さいパケットで行われている内は図に示す通りに、サーバまで来たパケットの応答はクライアントまで戻っていきます。LBによる負荷分散も問題なくできる状態です。
IPv6巨大パケット送信 : LBあり通信編
LB(LBに限らずネットワーク機器全般)では、データ転送を高速化するための仕組みが満載です。高速化のためにパケット処理の仕組みがIPv4とIPv6で違う製品などもあります。各社さんとも高速化のために工夫をしてさまざまな実装で製品を提供してくれています。
その高速化の手法の一つとして応答速度を早くするためにICMPv6はハードウェアで応答処理する。という実装をしたLBがあります。これがあるとIPv6では問題になります。
小さいパケットで情報のやり取りがされている内は問題ありませんでした。また、小さいパケットで通信が完結するような場合も同様に問題ありません。
では、スロースタートの影響でパケットサイズがどんどん大きくなるとどうでしょう。クライアントがサーバに要求するデータサイズが大きいと、自然と送られるパケット(TCPのウインドウサイズ)も次第に大きくなります。つまりサーバからクライアントへのパケットはどんどん大きくなります。
このとき、パケットサイズが途中通信路のMTUをこえたところでルータからICMPv6による「Packet Too Big」のエラーが返ります。
しかし、LBの仮想IPによる負荷分散を行っているためパケットの送信元は仮想IPが指定されています。つまり図のルータBがICMPv6の「Packet Too Big」エラーはLBが持っている仮想IPになります。この状況でLBの判断でICMPv6を扱ってしまう場合に問題が発生します。図の(8)(9)のところです。
図のルータBから返されたICMPv6の「Packet Too Big」のエラーはLBの仮想IPまで到達しますが、ICMPv6をハードウェア処理されてICMPv6はそのまま破棄されてしまいます。このため本来ならパケットのサイズを小さくしてデータの再送が必要ですが、サーバは巨大パケットを送ったことに気付きません。
そのため、クライアントはサーバからの応答を待ち続けることになり、サーバはクライアントからの応答(ACK)が来ないため同じサイズのパケットの再送を繰り返す。その度に「Packet Too Big」がLBの仮想IPで受信&破棄されています。なんとも悲しい状態です。
なぜこんなことになったのか?
この問題は、IPv4とIPv6で Path MTUより大きいサイズのパケットの扱い方が異なる ために発生しています。
IPv4時代にはルータがなんだかんだ言いながら、フラグメンテーションをしてくれていました。
そのため、Path MTU ディスカバリーが多少いいかげんでも通信が滞ることはありませんでした。
しかし、IPv6になり、プロトコル仕様としてパケットサイズの調節役は
ルータからエンドノードに引き継がれました。
IPv4ではICMPを途中経路(今回の例ではLB)のフィルタなどで落としたとしても
さほど問題は顕著に現れませんでしたが、IPv6ではIPv4と違い通信断となってしまいます><
「クライアントのページ表示が途中で止まってしまう。。」
「でもサーバの送信パケットを見ると再送を繰り返している。。」
「どうやら途中で止まってる? でもドコデ??」
結果的に、LBが接続されているリンクのパケットを見てみると、
途中ルータから「Packet Too Big」が繰り返し送られていた。ということでした。
この状態をみつけるまでにかなり時間がかかってしまいましたが、
よく考えるとナルホド。と思える現象です。
この問題の解決には非常に時間がかかりました。
それは、クライアントのページ表示が問題なく完了する場合もあったからです。
取得するページによっては Path MTU 以下のパケットでの通信で、
データ転送が完了する場合に、ページ表示が完了してしまいます。
そうでない時にはページが表示されない。。
つまり、事象が再現する場合とされない場合があったのです。
自然と表示されないページが悪いのでは?? という発想になり、
LBの問題であるというところに行き着くまでに時間がかかりました。
問題の回避策
この問題の暫定的な解決策として、
サーバのMTUサイズを1280 octetsにすることで対応しました。
1280 octetsはIPv6最小MTUです。
そのため、IPv6の通信路にはこれ以下のMTUの経路は存在しないため
サーバから送信されるパケットはMTUの制限から捕らわれなくなります。
しかし、この方法では1500 octetsで通信できるような場合でも
1280 octetsまでしかパケットサイズが上がらないので
あまりよい解決方法とは言えません。
「通信が止まってしまうくらいなら、MTUを1280にしよう!」
ということで、あくまで暫定対応としてMTU 1280を設定しています。
がんばれLB!
実際にこの現象が発生したLBベンダに問い合わせたところ、
ソフトウェアの改修予定とのことで、今後改善される予定とのことでした。
また、他社LBを調べたところ、既にICMPv6をサーバ側まで
転送する実装になっているものもありました。
このように各社独自のIPv6実装が混在する状況ですので、
「同じIPなんだし、IPv6の通信もIPv4と同じ挙動をするはず!」
という考えで望むとなかなかうまくいかないという教訓になりました。
しかしながら、負荷分散のためのLBは必須な機器と考えています。
今後もLBに限らず、このような小ネタはぞくぞくと出てくるかと思いますが、
より良いIPv6環境の充実に向けて頑張って欲しいと熱烈応援です。
まとめ
今回は、LBでのICMPv6処理の実装による問題点について紹介しました。各社工夫を凝らした製品が出てくる中、なぜか期待通りに通信できない。。その原因がIPv4とIPv6の仕様によるという事象に遭遇し、自分たちも大変勉強になりました。
IPv4では真面目にPath MTU ディスカバリーをしなくても巨大パケットは途中経路のルータがうまくフラグメンテーションしてくれていましたが、IPv6では同じようにはいきません。
効率の良いデータ転送のためにもPath MTU ディスカバリーは今まで以上に重要なものになってくると感じました。
今回の問題は、LBが接続するリンクに流れるパケットを確認することで明らかになりましたが、このパケットは見落としがちです。
#実際に発見するまでとても時間かかりました。。きっと今後も似たような事象はいくつも出てくることでしょう。
細な問題と放置せずに、ちょっとずつIPv6の普及に向けて頑張っていけたらいいなぁ~ と感じております。
以上となります。最後まで読んでいただきましてありがとうございました!!
IPv6プロジェクトスタッフ 内田幸治でした。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました