目次


TCP 選択的確認応答に伴うパフォーマンス上のトレードオフ

SACK 最適化によってサービス拒否の可能性がもたらされるのか否か

Comments

この数ヶ月間、インターネットで繰り広げられているいつもの Linux® 開発についての話題のなかで、Linux の TCP SACK (Selective ACKnowledgment) 実装が大きな議論となっています。通常、この実装に関するコメントが焦点としているのは特定の SACK イベントを処理する際の TCP スタックのパフォーマンスです。一部の人々は、そこに機密漏れの可能性があることを示唆しています。

この議論には興味をそそられたものの、それと同時に思ったのは確かなデータが欠けているということです。具体的にはどのような状態を指しているのか、さらにその状態がパフォーマンスにとってそれほど重要ではないのか、あるいは完全なサーバー・サービス拒否 (DoS) の可能性となるのかが定かでありません。

この話題に関するコメントをいくつか集めてみました (原典へのリンクは「参考文献」を参照してください)。

David Miller:「基本的にこれは、現時点で存在するすべての TCP スタックが攻撃される可能性のある問題であり、無効な SACK ブロックや、悪意を持って作成された SACK ブロックのために大量の CPU が費やされてしまいます」。
Ilpo Jarvinen [1]:「その一方、SACKtag に skb の fACK_count に対する依存関係がある限り、RB ツリーを使ったとしても CPU 処理の攻撃を受けるリスクはあるようです。その理由は、低速パスでの巡回が必要になってくるためです」。
ノースカロライナ州立大学:「この実験で我々が明らかにしているのは SACK 処理の効率性です。多くの事例で見てきたように、TCP はウィンドウが大幅に小さくなると有効に機能しませんでした。このことは、大規模なバッファーでは特に顕著になります」。
CHC IT:「最後に、2.4 と 2.6 両方に当てはまる警告として、TCP ウィンドウが 20 MB を超える非常に大きな BDP パスの場合、Linux SACK 実装の問題が起こりがちです。SACK イベントを受け取った Linux にインフライト中 (まだ応答を受け取っていない) のパケットが沢山ある場合、SACK を受信したパケットを見つけるのに時間がかかり、それによって TCP タイムアウトが発生して CWND が 1 パケットに戻ります」。

この記事では SACK 処理の実装に目を向け、Linux 2.6.22 のときからの問題である理想的ではない条件下での SACK 処理のパフォーマンスを検討します。Linux 2.6.22 は、現在 Ubuntu 7.10 で共通ディストリビューション・カーネルとして使用されています。このカーネルがリリースされてから数ヶ月が経ちますが、これまで開発者たちはこの問題について注目せずにこのカーネルのコードを作成してきました。現在開発されているカーネルは 2.6.25 で、このバージョンでは Ilpo Jarvinen による一連のパッチが SACK 処理のパフォーマンスの問題に対処します。記事の最後に、このカーネル 2.6.25 のコードによってこのパフォーマンスの問題がどう変わるのかを検討し、現在検討されている今後の変更内容を簡単に説明します。

SACK の基礎知識

SACK は、RFC 2018、2883、および 3517 によって定義されています (「参考文献」に、これらの RFC へのリンクを記載しています)。従来の TCP (つまり、SACK ではない) 確認応答は必ず累積されるため、確認応答 N はバイト N までのすべてのバイトが受信されたことを意味します。SACK が解決対象としている問題は、この従来の累積型の確認応答が持つ「オール・オア・ナッシング」の性質です。

例えば、パケット 2 (パケット 0 からパケット 9 のシーケンスにおいて) が転送中に失われた唯一のパケットだとしても、受信側は従来の ACK をパケット 1 に対してしか発行できません。これは、受信済みのパケットのなかで連続するシーケンスとして最大のパケットがパケット 1 だからです。一方、SACK の受信側は、パケット 1 に対する ACK に加え、パケット 3 から 9 に対する SACK オプションを発行することができます。この追加情報によって、送信側はパケット損失が最小限であること、そして多少のデータを再送信すればよいだけであることを判断できるようになります。この追加情報がなければ、高損失ネットワークのような状態に対処するために再送信しなければならないデータが大幅に増え、送信速度が落ちることになります。

高遅延接続において利用可能な全帯域幅を効率的に使用するには、SACK が特に重要になります。高遅延であることから、多数の「インフライト」中パケットが確認応答待ちの状態になることがよくあります。Linux では、これらのパケットは確認応答を受信して不要になるまで再送信キューに置かれます。パケットはシーケンス番号順に置かれますが、何らかの方法でインデックスが付けられるわけではありません。そのため、受信した SACK オプションの処理が必要になると、TCP スタックはその SACK が適用される再送信キューで該当するパケットを見つけなければならなくなります。この場合、再送信キューが大きければ大きいほど、対象のデータを見つけるのは困難になります。

各パケットに許容される SACK オプションは最大 4 つです。

攻撃のシナリオ

一般的な機密漏れの原因は、単一のパケット受信を基に、SACK オプションの受信側に任意の作業量を行わせることができるという事実にあります。この N:1 の比率により、SACK 送信側は処理能力が遥かに勝るプラットフォーム上の受信側をも攻撃することができます。

Linux SACK プロセッサーに対する特有の攻撃シナリオでは、再送信キューがすでに多数のパケットを保持している必要があります。このとき、攻撃者は SACK オプションが多数設定されたパケットを送信します。このパケットは、他のホストにキュー全体をスキャンさせて各オプションの処理を強制するように作られたものです。キューの最後にあるパケットを参照するオプションにより、受信側 TCP スタックがその参照先パケットを特定するためにリスト全体を巡回せざるを得ない場合もあります。攻撃側のクライアントが任意の SACK オプションを送信できることは明らかですが、実は相手側の再送信キューのサイズを簡単に制御することもできます。SACK オプションの作成者はキューのサイズも制御できることから、オプションを受信する側が各オプションに対して実行しなければならない作業量を設定することができるというわけです。

基本的に、再送信キュー内のパケット数は 2 つのホスト間での帯域幅遅延積 (BDP) によって左右されます。パスの帯域幅はネットワークの物理的性質により制限されるため、攻撃者は詳細なインフラストラクチャーを知らずには簡単に帯域幅を増やすことができません。それでも、攻撃者が確認応答をわずかな時間停止させてから送信するという方法で自由に遅延を追加することは可能です。このような高遅延接続の帯域幅をサーバーが有効に利用するには、インフライト中パケットが十分にあり、これらのパケットをすべて送信するまでの時間が、遅延された確認応答が返されるまでの時間と同じでなければなりません。そうでないと、ネットワークがパケットを 1 つも運んでいない期間が生じるため、帯域幅が十分に利用されないことになります。このように、高遅延パスがネットワークを効率的に使用するための条件は、多数のインフライト中パケットがあること、そして TCP 送信側が輻輳制御標準の制限内で積極的にこのウィンドウを満たそうとすることです。

確認応答の遅延は、再送信キューのサイズを効果的に増やします。これが、攻撃の前提条件の 1 つです。例えば、比較的遅い 10 MByte/sec の接続に 1750ms の遅延を加えると、12,000 を超えるパケット数のウィンドウになります。接続が速くなればウィンドウはさらに大きくなります。しかし、脆弱性の原因は一部に、標準的な家庭用ブロードバンド接続では大きな遅延を導入するだけでこの手法を実行できるところにあります。

クライアントは遅延 ACK を送信する際には常に、その SACK オプションの値を選択します。送信側は、最後に到着したパケット (つまり、最大のシーケンス番号が示されたパケット) 内のデータを参照する SACK オプションを追加します。このシナリオでは、該当するデータはその ACK が遅延されることになるだけですが、SACK が可能になったことから、再送信キューの中で ACK を受信したパケットから SACK を受信したパケットまでの距離が最大になります。

この特定の攻撃シナリオ (この記事の焦点) は、find-first 攻撃と呼ばれます。その理由は、TCP スタックが SACK オプションによって参照される先頭バイトを検出するために必要以上の時間を費やさざるを得なくなるためです。

攻撃シナリオの測定

Kode Vicious:「いまいましい測定を行うこと!」

以上の背景から、これが深刻な問題であるかどうかを調べてみましょう。これがほんの少し最適化すればよい話なのか、本格的な危機であるのか、あるいはその中間に位置するものなのかを明らかにするには、実際のデータを使用して問題の程度を特性化する必要があります。そのための最初のタスクは、収集するデータとその測定方法を決めることです。

実験のセットアップ

収集しなければならない最も重要な項目は、サーバーでの CPU 使用率です。そのための大変な作業は、Oprofile が標準 CLK_UNHALTED カウンターを使って引き受けてくれます。さらに、SACK 処理中にスキャンされたパケット数、そして再送信ウィンドウの平均サイズも興味深いデータです。そこで、サーバー・コードのインスツルメンテーションを行い、スキャン済みパケット・カウンターを設けました。また、このテストの結果が標準サーバーの場合と同じであることを確実にするため、このアノテーションを使わない場合のテストも実行しました。

インフライト・ウィンドウのサイズも注目に値するデータです。再送信キューの平均サイズは、送信された SACK オプションの数もクライアントで追跡されるのであれば、スキャン済みパケットのカウンターから計算することができます。SACK 以外のテスト・ケースでは、クライアントがレポートする遅延 ACK キューの標準的な長さを送信側のウィンドウ・サイズの下限近似値として使用しました。

測定はすべて標準 Linux サーバーで行いました。この測定では、実験を実行するためのカスタム・クライアントを作成し、そのクライアントからサーバーでの対象コード・パスを起動しました。このクライアントは独自の TCP スタックを実装し、そのままのソケット・カーネル API をベースに稼動します。このクライアントが完全な TCP スタックでないことは確かですが、対象となるデータのテストには十分なものでした。

クライアントは実験を開始する方法として、まずサーバーに接続し、700MB の ISO ファイルに対する単純な HTTP リクエストを送信します。その後、サーバーがレスポンスとして標準の 100 Mbit/sec ネットワークで送信してきたすべてのデータをクライアントは取り込みます。サーバーからの各パケットは、1750ms の遅延の後に確認応答されます。サーバーは 1750ms のウィンドウ全体を満たすため、パケットが確認応答されたことがわかるまで一度に送信するパケット数を徐々に増やしていきます。私が観測したインフライト・ウィンドウのパケット数は 14,000 を超えていました。

クライアントには、最後に到着したデータに関する SACK 情報を各 ACK の送信時に追加するための構成可能オプションがあります。このオプションが有効な場合、生成される確認応答には最大 4 つの SACK オプションが含まれることになります。それぞれのオプションが参照するのは、最後に到着し、現在確認応答が遅延されている 4 つのパケットのいずれかに含まれる任意の 1 バイト範囲です。待機中のパケットが 4 つに満たない場合には、それに対応する数のオプションが生成されます。

有効な比較になるように、データは以下の 3 つの異なる方法で集めました。

  1. 基本: 最初のテストで目指したのは、基準となる測定値の入手です。そのため、カスタム・クライアントおよび TCP スタックを使う代わりに、標準 Linux TCP スタックと wget コマンドライン HTTP クライアントを使用しました。
  2. カスタム (SACK 未使用): 2 つ目のデータ収集方法は、大規模なウィンドウを誘発するカスタム・クライアントを必要としましたが、SACK オプションはまったく使用しませんでした。このデータ収集方法により、大規模なウィンドウによる基本的な影響を、悪意のある SACK オプションの操作が及ぼす影響から区別することができます。
  3. カスタム (SACK 使用): 最後のデータ・セットは、大規模なウィンドウを誘発するクライアントを使用すると同時に、送信する ACK ごとに 4 つの SACK オプションを使用して収集しました。

使用したサーバーは古いもので、1.2GHz の Athlon XP サーバーです。

測定結果

表 1. サーバー使用率の測定結果
手段処理された ACK 数SACK 処理のためにスキャンされたパケット数経過時間CPU 使用率ACK ごとにサンプリングされたティック数転送 KB ごとにサンプリングされたティック数再送信キューの平均長さ
基本252,95501:0222%1.720.565
カスタム (SACK 未使用)498,27502:599%1.471.037,000 - 10,000
カスタム (SACK 使用)534,202755,368,50012:4733%10.878.131,414

一見すると、これらの測定結果はそれ程悪い成績ではないように思われます。攻撃の間、CPU 使用率は最大 33% となっていますが、CPU が完全に使い切られているわけではありません。CPU を使い切っているとしたら、他の作業を完了できなくなるため、サービス拒否という性質を持つことになります。

しかしながら、この測定結果をほんのちょっと詳しく見ていくだけでも、いくつかの懸念が生じてきます。まず、合計転送時間が急激に増加している点です。基本の結果はわずか 1 分少々だったのが、完全な攻撃シナリオでは約 13 分に跳ね上がっています。さらに、増加した CPU 使用率は当初の 1 分間だけでなく、13 分間も持続されています。つまり、同じ目標を達成するのに遥かに多くの CPU サイクルが費やされることになります。これは、3 つのデータ・ポイントの転送 KB ごとにサンプリングされたティック数を見比べれば一目瞭然です。

さらに深く追求していくと、33% という CPU 使用率の値は誤解を招きやすいことがわかります。この 13 分間は、サーバー全体が 100% の使用率で占有されている間、数秒間のバースト・サイクルが繰り返され、その後 CPU の使用がいったん収まってからバースト・サイクルが再び繰り返されます。全体としての結果は平均 33% の使用率となっていますが、リモート・ホストによって開始された TCP 処理が長期にわたって CPU を完全に独占する期間があります。

以下のグラフで、3 つのシナリオすべての CPU 使用率と時間を見比べてください。

図 1. wget を使用し、SACK は未使用の基本クライアント
wget を使用し、SACK は未使用の基本クライアント
wget を使用し、SACK は未使用の基本クライアント

基本クライアントの測定結果は良好かつ効率的です。転送は短時間で完了し、常に CPU 使用率が 25% を超えることなく利用可能な帯域幅を満たしています。このことから、適切な状況下では、この控えめなサーバーにネットワークの速度を十分満たす能力があることがわかります。

図 2. SACK が関与しない大規模なウィンドウのカスタム・クライアント
SACK が関与しない大規模なウィンドウのカスタム・クライアント
SACK が関与しない大規模なウィンドウのカスタム・クライアント

SACK を使用しないカスタム・クライアントの場合も妥当な CPU 使用率のグラフになっています。大規模なウィンドウとその必然的な損失を管理するための複雑さが加わることにより、使用される CPU サイクルが増えていますが、サーバーがそれに対応できなかったことは 1 度もありません。

図 3. SACK を使用した大規模なウィンドウのカスタム・クライアント
SACK を使用した大規模なウィンドウのカスタム・クライアント
SACK を使用した大規模なウィンドウのカスタム・クライアント

この SACK を有効にしたダウンロードのグラフを見て、遥かに大量の青インクが使われてことに驚かざるを得ないはずです。全体的な使用率は平均 33% ですが、このグラフは明らかに、サーバーが完全に無力になってしまうバーストが繰り返されていることを示しています。

このグラフは、y=100% になるまで y=x^2 を繰り返し示しているように見えます。これは、それぞれのSACK オプションが参照するデータを見つけるために、再送信キュー全体のスキャンが必要となる様子が表れたものです。送信側の輻輳ウィンドウが拡大すると、拡大したウィンドウがパケットを送信して確認応答を受け取る間、インフライト中のパケット数は事実上 2 倍になります。2 倍になったこのキューは、受信した SACK オプションごとに検査しなければなりません。受信した ACK が 50 万あるのに対して、SACK 処理で行われたパケット比較は驚くことに 7億5500万回にも及んでいることに注目してください。このアルゴリズムが、グラフ上に示された急激な CPU 使用率の増加という振る舞いを作り出しているというわけです。

最後の謎は、何故これがサーバーにとってまったくの壊滅的な状況ではないのかという点です。CPU 使用率が 100% まで上昇した後、転送が終わるまでそのレベルに留まると考えるのが当然ですが、このグラフでの CPU 使用率は 100% までの増加と 0% からの開始が繰り返されています。これではまるで、再送信キューが短くなっているかのようですが、実際はその通りなのでしょうか。

実は、再送信キューは周期的にゼロ・パケットまで縮小されます。その時点からプロセスは新たに開始し、輻輳ウィンドウが再び拡大して SACK オーベーヘッドが上昇し始めます。ですが、プロセス全体がサーバーの CPU 使用率に再び影響を与えるサイズに達するまでには何秒もかかります。それがこのグラフの周期性を説明しています。

キューが縮小しているように見えるのは、ピーク時の極度の処理負荷が TCP タイムアウトによるリカバリーを引き起こすためです。スタックはタイムアウトに対し、輻輳ウィンドウを空の状態にリセットすることによって反応します。この更新された小さなウィンドウは転送速度を遅らせますが、それと同時に、ウィンドウが危険な領域にまで再び拡大するまでの間、サーバーを有効な状態に保ち、他の作業に加われるようにすることも確かです。

カーネルの開発

開発の現状

Linux ネットワーキング・チームでは、このコードにすでに取り組んでいる最中です。2007年11月15日、Ilpo Jarvinen は SACK 処理パスの大幅な改訂を掲示し、そのコードが 2008年1月28日のマージ・ウィンドウの際に Linus の先行 2.6.25 ツリーに配置されました。一連のパッチは全部で 10 ありますが (「参考文献」にリンクを記載)、ここではそのうち 3 つの重要なパッチに焦点を当てます。

この 3 つのパッチは、一般的な場合の SACK パフォーマンスを改善する目的で作成されたものです。これらのパッチは攻撃シナリオにはある程度役立つはずですが、この記事で測定したシナリオに対する防御手段にはなるとは考えられません。

Ilpo Jarvinen [2]:「典型的かつ合法的な使用事例に存在するような微調整や最適化を迂回する方法を、悪意のある人々が考え出すのを阻止することは不可能でしょう」。

1 つ目の変更 (「参考文献」に記載した「Abstract tp->highest_SACK accessing & point to next skb」を参照) は、一般的な場合のキャッシング・ストラテジーを最適化することです。この場合、SACK オプションには、SACK を受信済みのシーケンス番号より大きなシーケンス番号のデータに関する情報しか含まれません。つまり、処理が完了していないウィンドウの中にはすでに報告済みの穴が残っているにも関わらず、ウィンドウの中のそれよりも新しいところで新しいデータの SACK を受信済みであることを意味します。これは通常の操作にはよくあるケースです。パッチはこのケースを最適化する方法として、キャッシュされた参照をシーケンス番号からポインター (すでに SACK を受信済みのキュー内で最大のシーケンス番号を持つパケットへのポインター) へと変換します。2 番目のパッチ (「参考文献」に記載した「Rewrite SACK block processing & SACK_recv_cache use」を参照) はこの情報を使用し、キャッシュされたポインターをリスト巡回の開始点として使うことで、キャッシュされたものより大きなシーケンス番号を持つデータのみをアドレス指定する SACK を処理します。これにより、リストを巡回する作業の大部分をしなくて済むようになります。

残念ながら、このストラテジーでは悪意のあるテスト・クライアントを最適化することはできません。このクライアントからの標準的な ACK には、以前に示されたデータよりシーケンス番号の大きな番号を持つデータに対する SACK オプションが含まれますが、それと同時に、直前にあるパケットへのシーケンス参照も含まれます。そのため、2.6.25 実装がこのデータを見つけるためには、再送信キューを最初から巡回しなければなりません。

2 番目のパッチに含まれているのは、今後のパッチによるキューの異なる「スキップ」キュー巡回アルゴリズムをサポートするための再編成です。これが、ここで検討しているテスト結果に直接役立つことはありませんが (スキップは依然として同じ線形巡回で実装されるため)、このパッチがサポートする今後の変更は、攻撃シナリオに大きく影響するはずです。

これらのパッチに含まれているコメントは、今後の 2 つの主要な変更が準備段階であることを示しています。可能性として考えられる第一の変更は、確認応答を受信していないパケットのリストを修正し、現行の線形リストではなく、インデックスを使用して赤黒木としてリストを編成することです。これにより、SACK オプションが参照するパケットの log(N) 時間での検索が可能になります。TCP スタックに対する find-first 攻撃に対処するには、大規模な再送信キューに含まれる任意の要素にアクセスできるようなインデックスを導入するための変更が不可欠です。

もう 1 つの編成上の変更は、ここではまだ明らかにされていない懸念事項に対処するものです。インデックス構造は個別のパケットを検索する際に優れたパフォーマンスをもたらしますが、SACK オプションは複数のパケットが含まれる任意のバイト範囲を対象とすることが可能です。したがって、悪意のあるクライアントがウィンドウ内のほとんどすべてのデータを対象とするオプションを送信するのを止める手段はありません。これが、私が注目している find-first 攻撃との違いです。実際、最初のパケットはリストの先頭にある可能性があるため、ごく簡単に検出されることも考えられます。しかし、対象となるパケットを素早く見つけても、SACK オプションを処理するにはキュー全体の線形巡回が必要になるのであれば、ほとんど役に立ちません。コードの変更では、現行リストを 2 つのリストに再編成することになります。そのうちの一方は SACK を受信済みのデータのリスト、そしてもう一方は SACK を受信していないデータのリストです。このようにすれば、検索範囲が以前に SACK を受信していないデータだけに絞られることになるので大きな助けとなります。DSACK (Duplicate SACK) と呼ばれる関連仕様に関する複雑な問題もありますが、方向としては現在、このような区分が検討されています。

最後に挙げるパッチ (「参考文献」に記載した「non-FACK SACK follows conservative SACK loss recovery」を参照) は、RFC 3517 の SACK ルールを利用するための輻輳制御動作の変更です。この変更によって、カーネルは多くの状況下で完全なタイムアウトに基づくリカバリー・シナリオを回避できるようになります。タイムアウトに基づくリカバリーでは、送信ウィンドウを最低限まで縮小し、現行の帯域幅遅延積が対応するレベルにまで徐々に拡大していかなければなりません。このリカバリー時間が、テスト中に見られたアクティビティー・バースト間での一時休止の原因となっています。

先行 2.6.25 の測定

新しいコードを理解したところで、SACK オプションを有効にして遅延を引き起こすようにしたカスタム・クライアントを使用して、データ・ポイントを再度測定してみます。今回テスト対象となるのは、2.6.25 開発コードです。以下の表には、参照しやすいように前に使用した 3 つのデータ・ポイントも併せて記載しています。

表 2. サーバー使用率の測定結果
手段処理された ACK 数SACK 処理のためにスキャンされたパケット数経過時間CPU 使用率ACK ごとにサンプリングされたティック数転送 KB ごとにサンプリングされたティック数再送信キューの平均長さ
基本252,95501:0222%1.720.565
カスタム (SACK 未使用)498,27502:599%1.471.037,000 - 10,000
カスタム (SACK 使用)534,202755,368,50012:4733%10.878.131,414
カスタム (先行 2.6.25 で SACK を使用)530,8792,768,229,47210:4249%13.610.075,214

以下に示すのは、大規模なウィンドウのカスタム・クライアントを使用し、先行 2.6.25 開発カーネルに対して悪意のある SACK オプションを指定して長時間測定した CPU 使用率のグラフです。

図 4. 先行 2.6.25 に対して SACK を使用した大規模なウィンドウのカスタム・クライアント
先行 2.6.25 に対して SACK を使用した大規模なウィンドウのカスタム・クライアント
先行 2.6.25 に対して SACK を使用した大規模なウィンドウのカスタム・クライアント

以前の CPU 使用率のグラフでは 100% までの上昇と 0% からの開始が繰り返されていましたが、今回のグラフは 100% になっている時間がより多くなっています。このカーネル・コードは効率性の向上を目的に作成されたものですが、テストでは同じファイル転送を行うのにより多くのサイクルを使っています。この結果は直感に反する結果となっています。

新しくなったコードはより短時間で実行できますが、それと同時に、毎回測定するごとに長時間 CPU が大幅に独占され、全体的な CPU 使用時間が長くなります。この短時間で実行できることと CPU 使用時間の長期化との隠れた原因として関連していることは、RFC 3517 関連の変更により、新しいカーネルで TCP タイムアウトに基づくリカバリーが少なくなったことです。2.6.22 コードでは、テスト・クライアントの実行ごとのタイムアウトは平均して 17 回でした。2.6.25 コードでは平均わずか 2 回です。この結果は、タイムアウト・イベント間でのアイドルからの立ち上げ時間の大幅な減少としてグラフに顕著に示されており、結果的に停止時間が減少しています。

タイムアウト回数が少ないということは、送信側が大抵は大規模なウィンドウを維持していたということです。高遅延リンクでスループットを妥当なものにするには、大規模なウィンドウが必要です。TCP スタックのこの開発バージョンは転送速度の点で極めて有利で、デプロイされたスタックより 2 分も速く転送を完了しています。その主な理由は、より大きなウィンドウを開放した状態のまま維持できたからです。

その一方、ウィンドウの平均サイズを大きくするということは、スキャン対象のキューに含まれるパケットが多くなるため、SACK 受信コードがパケットを受信するごとに行わなければならない作業も増えるということです。ファイル転送でスキャンされるパケット数は 27 億 (前のカーネル・バージョンの 4 倍)、そして転送 KB あたりにサンプリングされるティック数は 10.07 という数値が、いかに多くの作業をこなさなければならないかを証明しています。

高速プロセッサーでも、この状況にはそれほどの効果はありません。高速プロセッサーを使えば同じ時間でより大きなパケット・チェーンをスキャンできるものの、代わりにウィンドウがある程度大きくなり、処理対象の新しいオプションに対する作業がさらに増えるだけです。同じ数の SACK オプションを処理するために処理サイクルを大幅に増やすという方法も考えられますが、この方法でも高速プロセッサー自体のオーバーヘッドが増えるだけで、実際にこなす作業の量は少しも増えません。

まとめ

悪意を持って作成された SACK オプションがパフォーマンスに対して持つ意味は非常に大きいはずですが、この問題は DoS 攻撃が簡単に実行可能になるレベルまでには深刻化しません。タイムアウトを頻繁に発生させないようにする自己制御性が拠りどころになる一方、サーバーを占有しながらもタイムアウトには至らせない別のクライアントを想像することは難しい話ではありません。

大規模なデータ・ブロックを送信しないコンピューターには、このような懸念はありません。なぜなら、この不正利用の前提となる大規模なウィンドウを満たすことは有り得ないからです。選択的確認応答は帯域幅遅延積の大きなネットワーク・リンクでの優れたパフォーマンスには不可欠ですが、オプションの機能であることには変わりないため、この機能を無効にしても相互接続性が犠牲になることはありません。sysctl 変数 net.ipv4.tcp_SACK を 0 に設定すれば、TCP スタックでの SACK は無効になります。

現行の Linux カーネル開発ツリーでの一般的な SACK 処理ケースに対する取り組みが現在、順調に行われている最中です。この取り組みにより、パケット・リストのインデックスと区分をはじめとした今後の開発の土台はすでに固まっています。これらの開発がやがて、一部の攻撃ベクトルに功を奏すようになるはずです。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=310360
ArticleTitle=TCP 選択的確認応答に伴うパフォーマンス上のトレードオフ
publish-date=03312008