TCP のこと 1: TCP プロトコル、アルゴリズム、原理

TCP のこと 1: TCP プロトコル、アルゴリズム、原理

TCP は、多くの問題を解決する必要があり、これらの問題により多くのサブ問題とダークサイドが引き起こされるため、非常に複雑なプロトコルです。したがって、TCP の学習自体は苦痛を伴うプロセスですが、学習プロセスによって多くのメリットが得られます。 TCP プロトコルの詳細については、W. Richard Stevens の「TCP/IP 詳細な説明 第 1 巻: プロトコル」を読むことをお勧めします (もちろん、RFC793 やその後の多くの RFC を読むこともできます)。また、この記事では英語の用語を使用するので、これらの英語のキーワードを通じて関連する技術文書を見つけることができます。

この記事を書きたい理由は3つあります。

  • 1 つは、このような複雑な TCP プロトコルを簡単な記事で明確に説明する能力を鍛えることです。
  • もう 1 つの理由は、最近の多くのプログラマーは基本的にこの本を真剣に読んでおらず、ファーストフード文化を好んでいると感じていることです。したがって、このファーストフードの記事が、TCP の古典的なテクノロジを理解し、ソフトウェア設計のさまざまな難しさを体験するのに役立つことを願っています。そして、そこからソフトウェア設計の教訓を学ぶことができます。
  • 最も重要なことは、これらの基礎知識によって、これまでもっともらしく思われていた多くの事柄が明確になり、基礎知識の重要性を実感していただけることを願っています。

したがって、この記事ではすべてを網羅するのではなく、TCP プロトコル、アルゴリズム、および原則の概要のみを説明します。

さっそく、まず最初に、TCP は OSI の 7 層ネットワーク モデルの第 4 層 (トランスポート層) にあり、IP は第 3 層 (ネットワーク層) にあり、ARP は第 2 層 (データ リンク層) にあることを知っておく必要があります。第 2 層のデータはフレーム、第 3 層のデータはパケット、第 4 層のデータはセグメントと呼ばれます。

まず、プログラムのデータは最初に TCP セグメントに入力され、次に TCP セグメントが IP パケットに入力され、最後にイーサネット フレームに入力されることを知っておく必要があります。相手側に送信された後、各層は独自のプロトコルを解析し、データを処理のために上位レベルのプロトコルに渡します。

TCP ヘッダー形式

次に、TCP ヘッダーの形式を見てみましょう。

TCP ヘッダー形式 (画像ソース)

以下の点に注意する必要があります。

  • TCP パケットには IP アドレスがありません。これは IP 層の問題です。ただし、送信元ポートと宛先ポートがあります。
  • TCP 接続では、同じ接続であることを示すために 4 つのタプル (src_ip、src_port、dst_ip、dst_port)、正確には 5 つのタプルとプロトコル用の 1 つのタプルが必要です。しかし、ここでは TCP プロトコルについてのみ説明しているので、ここでは 4 つについてのみ説明します。
  • 上の図で非常に重要な点が 4 つあります。
  • シーケンス番号はパケットのシーケンス番号であり、ネットワーク パケットの並べ替えの問題を解決するために使用されます。
  • 確認応答番号は ACK です。受信を確認し、パケット損失の問題を解決するために使用されます。
  • ウィンドウは、Advertised-Window とも呼ばれ、フロー制御を解決するために使用される有名なスライディング ウィンドウです。
  • パケットの種類である TCP フラグは、主に TCP ステート マシンを操作するために使用されます。

その他については、次の図を参照してください。

(画像出典)

TCP ステートマシン

実際、ネットワーク上の伝送は TCP を含めてコネクションレスです。 TCP のいわゆる「接続」は、実際には、通信する 2 つの当事者間の「接続状態」を維持し、接続があるように見せかけるだけです。したがって、TCP 状態の変化は非常に重要です。

以下は、「TCP プロトコル ステート マシン」(画像ソース) と「TCP 接続の確立」、「TCP 接続の切断」、および「データ転送」の比較表です。比較しやすいように、2 つの画像を並べて配置しました。さらに、次の 2 つの画像は非常に重要なので、覚えておく必要があります。 (不満点: このような複雑なステート マシンを見ると、このプロトコルがいかに複雑であるかがわかります。複雑なものには常に多くのトリッキーな点があるため、TCP プロトコルは実際にはかなりトリッキーです)。

多くの人が、なぜ接続を確立するには 3 回のハンドシェイクが必要で、接続を切断するには 4 回のハンドシェイクが必要なのかと疑問に思うでしょう。

  • リンクを確立するための 3 ウェイ ハンドシェイクでは、シーケンス番号の初期値を初期化することが主な目的です。通信する側は、初期化シーケンス番号 (ISN: Initial Sequence Number と略記) を相互に通知する必要があります。これが SYN という名前で、正式名称は Synchronize Sequence Numbers です。これらは上図の x と y です。この番号は、ネットワーク上の伝送の問題によりアプリケーション層で受信されたデータの順序が乱れないようにするために、後続のデータ通信のシーケンス番号として使用されます (TCP はこのシーケンス番号を使用してデータを結合します)。
  • 4 波については、よく見ると、TCP は全二重なので、送信側と受信側の両方に Fin と Ack が必要なため、実際には 2 回です。しかし、一方が消極的であるため、いわゆる4波のような状況となっている。両側が同時に切断された場合、CLOSING 状態に入り、その後 TIME_WAIT 状態になります。次の図は、両方の当事者が同時に切断する概略図です (TCP ステート マシンを参照することもできます)。

両端が同時に切断される(画像出典)

さらに、注意すべき点がいくつかあります。

  • 接続を確立する際の SYN タイムアウトについて。サーバーがクライアントから送信された SYN を受信し、SYN-ACK で応答し、その後クライアントが切断され、サーバーがクライアントから返された ACK を受信しない場合、接続は成功でも失敗でもない中間状態にあると想像してください。したがって、サーバーが一定時間内に TCP を受信しない場合、SYN-ACK を再送信します。 Linux では、再試行のデフォルト回数は 5 回です。再試行間隔は 1 秒から始まり、毎回増加します。5 つの再試行間隔は 1 秒、2 秒、4 秒、8 秒、16 秒で、合計 31 秒です。5 回目の再試行後、5 回目の再試行もタイムアウトしたことを知るまでに 32 秒待つ必要があります。したがって、TCP が接続を切断するには、合計 1 秒 + 2 秒 + 4 秒 + 8 秒 + 16 秒 + 32 秒 = 2^6 -1 = 63 秒かかります。
  • SYNフラッドについて。悪意のある人物の中には、この目的で SYN フラッドを作成した人もいます。SYN をサーバーに送信した後、サーバーがオフラインになり、サーバーは切断されるまでデフォルトで 63 秒間待機する必要がありました。このようにして、ハッカーはサーバーの SYN 接続キューを使い果たし、通常の接続要求が処理されないようにすることができます。そのため、Linux ではこの問題に対処するために tcp_syncookies というパラメータを提供しています。SYN キューがいっぱいになると、TCP は送信元アドレス ポート、宛先アドレス ポート、タイムスタンプを介して特別なシーケンス番号を作成し、それを送り返します (クッキーとも呼ばれます)。ハッカーの場合は応答がありません。通常の接続の場合は、SYN クッキーが送り返され、サーバーはクッキーを介して接続を確立できます (SYN キューにいなくても)。通常の高負荷接続を処理するために tcp_syncookies を使用しないでください。 synccookies は TCP プロトコルの妥協版であり、厳密ではないためです。通常のリクエストの場合、選択に応じて 3 つの TCP パラメータを調整する必要があります。1 つは tcp_synack_retries で、再試行回数を減らすために使用できます。2 つ目は tcp_max_syn_backlog で、SYN 接続の数を増やすことができます。3 つ目は tcp_abort_on_overflow で、処理できない場合に接続を拒否することができます。
  • ISN 初期化について。 ISN はハードコードできません。そうしないと問題が発生します。たとえば、接続が確立された後、常に 1 が ISN として使用される場合、クライアントが 30 セグメントを送信したがネットワークが切断されたとき、クライアントは再接続して再び 1 を ISN として使用しますが、以前の接続からのパケットが到着するため、それらは新しい接続のパケットとみなされます。このとき、クライアントのシーケンス番号は 3 である可能性がありますが、サーバーはクライアントの番号が 30 であると認識します。すべてがめちゃくちゃだ。 RFC793 では、ISN は偽のクロックに結び付けられ、ISN が 2^32 を超えるまで 4 マイクロ秒ごとに 1 ずつ増加し、その後 0 からやり直すと規定されています。したがって、ISN のサイクルは約 4.55 時間です。ネットワーク上の TCP セグメントの存続時間は最大セグメント存続時間 (MSL と略される - Wikipedia エントリ) を超えないと想定しているため、MSL 値が 4.55 時間未満である限り、ISN は再利用されません。
  • MSL と TIME_WAIT について。上記の ISN の説明を通じて、MSL がどのように誕生したかもご存知だと思います。 TCP 状態図には、TIME_WAIT 状態から CLOSED 状態へのタイムアウト設定があることに気付きました。このタイムアウト設定は 2*MSL です (RFC793 では MSL は 2 分と定義され、Linux では 30 秒に設定されています)。なぜ TIME_WAIT があるのでしょうか。なぜ CLOSED 状態に切り替えないのでしょうか。主な理由は 2 つあります。1) TIME_WAIT は、相手側が ACK を受信するのに十分な時間があることを保証します。受動的に閉じられた側が ACK を受信しない場合、受動的な側は Fin を再送信するようにトリガーされます。これはちょうど 2 MSL です。2) この接続が後続の接続と混在しないようにするのに十分な時間があります (一部の自律ルーターは IP パケットをキャッシュすることを知っておく必要があります。接続が再利用されると、これらの遅延パケットが新しい接続と混在する可能性があります)。こちらの記事をご覧ください: TIME_WAIT と、プロトコルおよびスケーラブルなクライアント サーバー システムに対するその設計上の意味。
  • TIME_WAIT の数が多すぎる。上記の説明から、TIME_WAIT が非常に重要な状態であることがわかります。ただし、同時実行数が多く短い接続がある場合、TIME_WAIT が多すぎるため、システム リソースも大量に消費されます。検索してみると、ほとんどの解決策が 2 つのパラメータを設定するように教えてくれることがわかります。1 つは tcp_tw_reuse と呼ばれ、もう 1 つは tcp_tw_recycle と呼ばれます。これら 2 つのパラメータのデフォルト値は閉じられています。後者の recyle は前者の resue よりも積極的で、resue はより穏やかです。さらに、tcp_tw_reuse を使用する場合は、tcp_timestamps=1 を設定する必要があります。そうでない場合は無効になります。ここで、これら 2 つのパラメータをオンにすると大きな落とし穴があることに注意する必要があります。TCP 接続で奇妙な問題が発生する可能性があります (前述のように、接続がタイムアウトするのを待たずに再利用すると、新しい接続が確立されない可能性があります。公式ドキュメントには、「技術専門家のアドバイス/リクエストなしに変更しないでください」と記載されています)。
  • tcp_tw_reuse について。公式ドキュメントによると、tcp_tw_reuse と tcp_timestamps (PAWS、Protection Against Wrapped Sequence Numbers とも呼ばれる) を組み合わせるとプロトコルの観点からセキュリティを確保できますが、両側で tcp_timestamps をオンにする必要があります (tcp_twsk_unique のソース コードを読むことができます)。個人的には、いくつかのシーンではまだ問題が残ると思います。
  • tcp_tw_recycle について。 tcp_tw_recycle がオンになっている場合、相手側で tcp_timestamps がオンになっていると想定され、タイムスタンプが比較されます。タイムスタンプが大きくなった場合は、再利用できます。ただし、もう一方の端が NAT ネットワークである場合 (たとえば、企業がパブリック ネットワークにアクセスするために 1 つの IP のみを使用する場合)、またはもう一方の端の IP が別のマシンによって再利用される場合は、状況が複雑になります。リンクを確立するための SYN が直接破棄される場合があります (接続タイムアウト エラーが表示される場合があります) (Linux カーネル コードを観察する場合は、ソース コード tcp_timewait_state_process を参照してください)。
  • tcp_max_tw_buckets について。これは同時 TIME_WAIT の数を制御します。デフォルト値は 180,000 です。制限を超えると、システムは超過分を破棄し、ログに警告を出力します (例: 時間待機バケット テーブル オーバーフロー)。公式 Web サイトのドキュメントによると、このパラメータは DDoS 対策に使用されます。デフォルト値の180000も小さくないと言われています。これについては、実際の状況に基づいて検討する必要があります。

繰り返しになりますが、TIME_WAIT 問題を解決するために tcp_tw_reuse と tcp_tw_recycle を使用することは、これら 2 つのパラメータが TCP プロトコル (RFC 1122) に違反するため、非常に危険です。

実際、TIME_WAIT は、積極的に切断したことを意味するため、いわゆる「自殺しなければ死なない」状態になります。想像してみてください。もし相手側が切断したら、問題は相手側にあることになります、ハハハ。さらに、サーバーが HTTP サーバーである場合、HTTP KeepAlive (ブラウザーは TCP 接続を再利用して複数の HTTP 要求を処理します) を設定し、クライアントを切断させる (注意が必要です。ブラウザーは非常に貪欲な場合があり、必要がない限り積極的に切断することはありません) ことはどの程度重要ですか。

データ転送におけるシーケンス番号

下の画像は、coolshell.cn にアクセスしたときに Wireshark から取得したスクリーンショットで、SeqNum がどのように変化したかを示しています。 (Wireshark メニューの統計 -> フローグラフ… を使用)

転送されたバイト数に応じて SeqNum が増加することがわかります。上図では、3ウェイハンドシェイク後、Len:1440のパケットが2つ受信され、2番目のパケットのSeqNumは1441になります。前の ACK 応答は 1441 であり、1440 が受信されたことを示します。

注: Wireshark パケット キャプチャ プログラムを使用して 3 ウェイ ハンドシェイクを観察すると、SeqNum が常に 0 であることがわかります。これは事実ではありません。表示をよりわかりやすくするために、Wireshark は Relative SeqNum (相対シーケンス番号) を使用します。右クリック メニューのプロトコル設定でこれをキャンセルするだけで、「Absolute SeqNum」が表示されます。

TCP再送メカニズム

TCP はすべてのデータ パケットが到着できることを保証する必要があるため、再送信メカニズムが必要です。

受信者から送信者への Ack 確認では、最後の連続パケットのみを確認することに注意してください。たとえば、送信者は 1、2、3、4、5 の合計 5 つのデータ パケットを送信しました。受信者は 1 と 2 を受信したため、ack 3 を返信し、次に 4 を受信しました (この時点では 3 は受信されていないことに注意してください)。このとき、TCP は何をするでしょうか。前述のように、SeqNum と Ack はバイト単位であるため、ack の場合は確認にジャンプできず、比較的大きな連続パケットのみを確認できることを知っておく必要があります。そうしないと、送信者は以前のパケットがすべて受信されたと見なします。

タイムアウト再送信メカニズム

1 つは、ACK で応答せずに 3 を待つことです。送信者は、タイムアウト後に 3 の ACK を受信できないことが判明すると、3 を再送信します。受信側は 3 を受信すると、4 を返します。つまり、3 と 4 の両方が受信されたことになります。

しかし、この方法にはさらに深刻な問題があります。それは、3 を待たなければならないため、4 と 5 が受信されたとしても、送信者は何が起こったのか分からないということです。Ack が受信されなかったため、送信者は悲観的に失われたと信じ、4 と 5 が再送される可能性があります。

これには 2 つのオプションがあります。

  • 1 つは、タイムアウト パケットのみを再送信することです。これが3番目のデータセットです。
  • もう 1 つは、タイムアウト後のすべてのデータ、つまり 3 番目、4 番目、5 番目のデータを再送信することです。

どちらのアプローチにも長所と短所があります。 1 つは帯域幅を節約しますが遅くなります。2 つ目は高速ですが帯域幅を浪費し、無駄になる可能性もあります。しかし全体的には良くありません。全員がタイムアウトを待っているため、タイムアウトが非常に長くなる可能性があります (次の記事では、TCP がタイムアウトを動的に計算する方法について説明します)。

高速再送信メカニズム

そのため、TCP は時間駆動ではなくデータ駆動の再送信である Fast Retransmit と呼ばれるアルゴリズムを導入しました。つまり、パケットが連続して到着しない場合は、最後に失われた可能性のあるパケットが ACK されます。送信者が同じ ACK を 3 回連続して受信した場合、再送信が行われます。高速再送信の利点は、再送信前にタイムアウトを待つ必要がないことです。

たとえば、送信者が 1、2、3、4、5 個のデータを送信し、最初に 1 個が到着すると、2 に ack が返されます。しかし、何らかの理由で 2 が受信されず、3 が到着したため、再び 2 に ack が返されます。その後、4 と 5 が到着しますが、2 が受信されていないため、やはり 2 に ack が返されます。したがって、送信者は ack=2 の確認を 3 回受信し、2 がまだ到着していないことを認識し、すぐに 2 を再送信します。次に、受信側は 2 を受信します。この時点で、3、4、5 がすべて受信されているため、6 を返します。概略図は以下のとおりです。

高速再送信は、タイムアウトの問題だけを解決します。以前のものを再送信するか、すべての問題を再送信するかという難しい選択が依然として残っています。上記の例では、#2 を再送信するべきでしょうか、それとも #2、#3、#4、#5 を再送信するべきでしょうか。これは、送信者がこれらの 3 つの連続した ACK (2) を誰が送信したかわからないためです。送信者は、#6、#10、#20 から 20 個のデータを送信した可能性があります。この方法では、送信者は 2 から 20 までデータを再送信する可能性があります (これは一部の TCP の実際の実装です)。これは諸刃の剣であることがわかります。

SACK法

もう 1 つのより良い方法は、選択的確認応答 (SACK) と呼ばれます (RFC 2018 を参照)。この方法では、TCP ヘッダーに SACK を追加する必要があります。ACK は依然として高速再送信の ACK であり、SACK は受信したデータ フラグメントを報告します。下の図を参照してください。

このようにして、送信側は返された SACK に基づいて、どのデータが到着し、どのデータが到着しなかったかを知ることができます。そのため、高速再送信アルゴリズムが最適化されました。もちろん、この合意には双方の支持が必要です。 Linux では、この機能は tcp_sack パラメータを介して有効にできます (Linux 2.4 以降ではデフォルトで有効になっています)。

ここで注意する必要があるもう 1 つの問題は、受信側の契約破棄です。いわゆる契約破棄とは、送信者に報告された SACK 内のデータを受信側が破棄する権利を持つことを意味します。これは問題を複雑にするため推奨されませんが、メモリを他のより重要なものに渡すなど、受信者がこれを行う極端なケースもあるかもしれません。したがって、送信側は SACK に完全に依存することはできず、ACK に依存してタイムアウトを維持する必要があります。後続の ACK が増加しない場合は、SACK を再送信する必要があります。さらに、受信側は SACK パケットを Ack としてマークすることはできません。

注意: SACK は送信者のリソースを消費します。ハッカーが大量の SACK オプションをデータ送信者に送信すると、送信者は再送信を開始したり、送信済みのデータを走査したりすることになり、送信者のリソースが大量に消費されます。詳細については、「TCP SACK のパフォーマンスのトレードオフ」を参照してください。

重複SACK – 重複データを受信した問題

重複 SACK は D-SACK とも呼ばれ、主に SACK を使用して、繰り返し受信されたデータを送信者に通知します。詳細な説明と例は RFC-2883 に記載されています。以下にいくつかの例を示します (RFC-2883 より):

D-SACK は SACK の最初のセグメントをマーカーとして使用します。

  • SACKの最初のセグメントの範囲がACKでカバーされている場合、それはD-SACKです。
  • SACKの最初のセグメントの範囲がSACKの2番目のセグメントでカバーされている場合、それはD-SACKです。

例1: ACKパケット損失

以下の例では、2 つの ACK が失われたため、送信者は最初のデータ パケット (3000-3499) を再送信します。受信側は重複を受信したことに気づき、SACK=3000-3500 を返します。ACK が 4000 に到達したため、4000 より前のすべてのデータが受信されたことを意味し、この SACK は D-SACK です。これは、送信者に重複データを受信したことを伝えることを目的としており、送信者もデータ パケットは失われなかったが、ACK パケットが失われたことを認識しています。

  • 送信 受信 ACK 送信
  • セグメント セグメント (SACK ブロックを含む)
  • 3000-3499 3000-3499 3500 (ACK ドロップ)
  • 3500-3999 3500-3999 4000 (ACK ドロップ)
  • 3000-3499 3000-3499 4000、SACK=3000-3500
  • ---------

例2: ネットワーク遅延

以下の例では、ネットワーク パケット (1000-1499) がネットワークによって遅延され、送信者が ACK を受信できませんでした。後から到着した 3 つのパケットによって「高速再送信アルゴリズム」がトリガーされたため、再送信されました。ただし、再送信中に遅延パケットが再度到着したため、SACK=1000-1500 が返されました。ACK が 3000 に達したため、この SACK は D-SACK であり、重複パケットが受信されたことを示します。

この場合、送信者は、「高速再送信アルゴリズム」によってトリガーされた再送信が、送信されたパケットの損失または応答 ACK パケットの損失によって発生したのではなく、ネットワークの遅延によって発生したことを認識します。

  • 送信 受信 ACK 送信
  • セグメント セグメント (SACK ブロックを含む)
  • 500-999 500-999 1000
  • 1000-1499(遅延)
  • 1500-1999 1500-1999 1000、SACK=1500-2000
  • 2000-2499 2000-2499 1000、SACK=1500-2500
  • 2500-2999 2500-2999 1000、SACK=1500-3000
  • 1000-1499 1000-1499 3000
  • 1000-1499 3000、サック=1000-1500
  • ---------

D-SACK の導入には次のような利点があることがわかります。

1) 送信者は、送信したパケットが失われたか、返された ACK パケットが失われたかを知ることができます。

2) タイムアウトが短すぎて再送信が発生していませんか?

3) 最初に送信されたパケットがネットワーク上で後から到着する状況(並べ替えとも呼ばれる)

4) データ パケットはネットワーク上でコピーされていますか?

これらのことを知ることで、TCP はネットワークの状況を理解し、ネットワーク上でより適切にフロー制御を実行できるようになります。

この機能を有効にするには、Linux の tcp_dsack パラメータを使用します (Linux 2.4 以降ではデフォルトで有効になっています)。

<<:  実践 | 人工知能が小売体験を向上させる 20 の例

>>:  自動運転の運転手が死亡事故で無罪となった。将来のAIの世界はより良くなるだろうか?

ブログ    
ブログ    

推薦する

これは本当に天才的ですね!パーセプトロンを組み合わせると、ニューラル ネットワークになるのではないでしょうか。

[[354709]]みなさんこんにちは。今日もディープラーニングについてお話していきましょう。クラ...

顔認識がコミュニティに登場: 「顔スキャン」の背後にあるプライバシーとセキュリティの問題

李静さん(仮名)は、団地内の自分のアパートのドアを開けることができなくなった。ドアには「顔認識」装置...

...

...

ロボットセンサー市場は2026年までに40億ドルを超える

AIとIoTをロボットシステムに統合することで、その応用範囲が大幅に拡大すると期待されています。市場...

...

Toutiaoのアルゴリズムロジックを使用してMacOSを再設計しました

仕事以外では、私はほとんどの時間を2つの状態で過ごしています。1つは見出しを閲覧している状態で、もう...

Transformer には新しいバリアント ∞-former があります: 無限の長期メモリ、任意の長さのコンテキスト

[[422086]]過去数年間で、Transformer は NLP 分野全体をほぼ支配し、コンピ...

ChatGPT 技術製品の実装: 技術アーキテクチャから実際のアプリケーションまで

導入この共有では、ChatGPTテクノロジー製品の実装についてお話ししたいと思います。技術アーキテク...

囲碁AIの不正行為の最初の事例はすでに発生しています。他の事例は後れを取っているのでしょうか?

[[227817]]画像出典: Visual Chinaカンニングは間違いなく長い歴史を持つ「科学...

...

...

Baidu Brain EasyDL Retail Editionは、消費財メーカーのオフライン流通チャネルのデジタルアップグレードをサポートします。

消費財ブランドにとって、製品の売上を増やすことが仕事の中心となります。しかし、電子商取引が普及してい...