ジグソーパズルtcp







彼らは、システムの誤動作を理解するまで、システムを完全に理解できないと言います。 学生として、私はTCPの実装を楽しみのために書き、その後ITで数年間働きましたが、それでもTCPの仕事とそのエラーについてさらに深く研究し続けています。 最も驚くべきことは、これらのエラーの一部が基本的なものに現れることです。 そして、それらは明らかではありません。 この記事では、それらをCar Talkまたは古いJavaパズルのスタイルのパズルとして提示します 。 他の優れたパズルと同様に、それらは非常に簡単に再現できますが、解決策は通常驚くべきものです。 そして、神秘的な詳細に注意を向ける代わりに、これらのパズルは、TCPの基本原理のいくつかを学ぶのに役立ちます。



前提条件



これらのパズルは、UnixのようなシステムでのTCPの基本的な知識を暗示しています。 しかし、それらを掘り下げるためにマスターになる必要はありません。 例:





これらの例をすべて自分で繰り返すことができます。 VMware Fusionを使用して実行している2つの仮想マシンを使用しました。 結果は、運用サーバーと同じです。 テストでは、SmartOSでnc(1)



を使用しましたが、再現可能な問題が特定のOSに固有のものになるとは思いません。 システムコールを追跡し、大まかなタイミング情報を収集するために、illumosプロジェクトのtruss(1)ユーティリティを使用しました。 この情報は、OS Xのdtruss(1m)またはGNU / Linuxのstrace(1)で取得できます。



nc(1)



非常に単純なプログラムです。 次の2つのモードで使用します。





両方のモードで、接続が確立された後、各側はpoll



を使用して標準入力を待機するか、読み取り準備ができているデータのあるソケットを接続します。 着信データが端末に出力されます。 端末に入力したデータは、ソケットを介して送信されます。 CTRL-Cを押すと、ソケットが閉じてプロセスが停止します。

例では、クライアントはkang



と呼ばれ、サーバーはkodos



と呼ばれkodos







ウォームアップ:通常のTCPブレーク



基本的な状況から始めましょう。 kodos



サーバーを構成したと想像してkodos







サーバー

 [root@kodos ~]# truss -d -t bind,listen,accept,poll,read,write nc -l -p 8080 Base time stamp: 1464310423.7650 [ Fri May 27 00:53:43 UTC 2016 ] 0.0027 bind(3, 0x08065790, 32, SOV_SOCKBSD) = 0 0.0028 listen(3, 1, SOV_DEFAULT) = 0 accept(3, 0x08047B3C, 0x08047C3C, SOV_DEFAULT, 0) (sleeping...)
      
      





(これらの例では、 nc



が行うシステムコールを出力するためにtruss



を使用していることを思い出します。時間情報はtruss



フラグを使用して表示されます。



次に、 kang



接続を確立します。



お客様

 [root@kang ~]# truss -d -t connect,pollsys,read,write,close nc 10.88.88.140 8080 Base time stamp: 1464310447.6295 [ Fri May 27 00:54:07 UTC 2016 ] ... 0.0062 connect(3, 0x08066DD8, 16, SOV_DEFAULT) = 0 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





kodos



は以下が表示されます。



サーバー

 23.8934 accept(3, 0x08047B3C, 0x08047C3C, SOV_DEFAULT, 0) = 4 pollsys(0x08045680, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





TCP接続はESTABLISHED状態にあり、両方のプロセスはpoll



ます。 netstat



を使用して、すべてのシステムでこれを確認できます。



サーバー

 [root@kodos ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.140.8080 10.88.88.139.33226 1049792 0 1049800 0 ESTABLISHED ...
      
      





お客様

 [root@kang ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.139.33226 10.88.88.140.8080 32806 0 1049800 0 ESTABLISHED ...
      
      





質問: プロセスの1つを完了すると、他のプロセスはどうなりますか? 彼は何が起こったのか理解できますか? 彼はこれをどのように理解しますか? 特定のシステムコールの動作を予測し、それぞれがそれを行う理由を説明してみましょう。



kodos



CTRL-Cをkodos



ます。



サーバー

 pollsys(0x08045680, 2, 0x00000000, 0x00000000) (sleeping...) ^C127.6307 Received signal #2, SIGINT, in pollsys() [default]
      
      





そして、これがkang



に見られるものです:



お客様

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...) 126.1771 pollsys(0x08045670, 2, 0x00000000, 0x00000000) = 1 126.1774 read(3, 0x08043670, 1024) = 0 126.1776 close(3) = 0 [root@kang ~]#
      
      





どうした それを理解しましょう:



  1. プロセスを終了するときに、SIGINTをサーバーに送信しました。 終了後、ファイル記述子は閉じられました。
  2. ESTABLISHED



    ソケットの最後のハンドルが閉じられると、TCPスタックはFIN接続を介してkodos



    送信し、 FIN_WAIT_1



    入ります。
  3. kang



    上のTCPスタックはFINパケットを受信し、自身の接続をCLOSE_WAIT



    状態CLOSE_WAIT



    、応答としてACKを送信します。 nc



    クライアントがソケットをブロックしている間-読み取りの準備ができている間、カーネルはPOLLIN



    を使用してこのスレッドをPOLLIN



    ます。
  4. nc



    クライアントは、ソケットのPOLLIN



    POLLIN



    し、 read



    を呼び出しread



    。これはすぐに0を返します。これは、接続の終了を意味します。 nc



    は、ソケットの処理が完了したと判断し、閉じます。
  5. それまでの間、 kodos



    上のTCPスタックはACKを受信し、 FIN_WAIT_2



    入りFIN_WAIT_2



  6. kangのnc



    クライアントがソケットを閉じる間、kangのTCPスタックはFINをkodos



    送信しkodos



    kang



    の接続はLAST_ACK



    状態に入ります。
  7. kodos



    のTCPスタックはFINを受信し、接続はTIME_WAIT



    状態になり、 kodos



    のスタックはFINを確認します。
  8. kang



    のTCPスタックはFINのACKを受信し、接続を完全に削除します。
  9. 2分後、 kodos



    のTCP接続は閉じられ、スタックは接続を完全に削除します。


手順の順序はわずかに異なる場合があります。 また、 kang



は、 FIN_WAIT_2



代わりに、 CLOSING



状態になる場合があります。



したがって、netstatによると、最終状態は次のようになります。



サーバー

 [root@kodos ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.140.8080 10.88.88.139.33226 1049792 0 1049800 0 TIME_WAIT
      
      





この接続のkang



は送信データはありません。



中間状態は非常に迅速に通過しますが、 DTrace TCPプロバイダーを使用して追跡できます 。 パケットフローは、 snoop(1m)またはtcpdump(1)を使用して表示できます。



結論:接続のインストールおよび終了中にシステムコールを渡す通常の方法を見ました。 kang



は、接続がkodos



で閉じられたという事実をすぐに発見したことに注意してくださいkodos



から起こされread



ゼロのread



返すと、ストリームの終わりをread



ます。 この時点で、 kang



はソケット閉じることに決め 、それによりkodos



への接続がkodos



られkodos



。 後でこれに戻り、この状況でkang



がソケットを閉じなかった場合にどうなるかを見ていきます。



パズル1:電源再起動



いずれかのシステムの電源が再起動すると、確立された非アクティブなTCP接続はどうなりますか?



スケジュールされた再起動中に多くのプロセスが(「reboot」コマンドを使用して)正しく終了するため、CTRL-Cを使用kodos



てサーバーをkodos



コンソールで「reboot」コマンドを入力しても同じ結果になります。 しかし、前の例でkodos



の電源をkodos



どうなりますか? 最終的に、 kang



はそうするでしょう?



見てみましょう。 接続を確立します。



サーバー

 [root@kodos ~]# truss -d -t bind,listen,accept,poll,read,write nc -l -p 8080 Base time stamp: 1464312528.4308 [ Fri May 27 01:28:48 UTC 2016 ] 0.0036 bind(3, 0x08065790, 32, SOV_SOCKBSD) = 0 0.0036 listen(3, 1, SOV_DEFAULT) = 0 0.2518 accept(3, 0x08047B3C, 0x08047C3C, SOV_DEFAULT, 0) = 4 pollsys(0x08045680, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





お客様

 [root@kang ~]# truss -d -t open,connect,pollsys,read,write,close nc 10.88.88.140 8080 Base time stamp: 1464312535.7634 [ Fri May 27 01:28:55 UTC 2016 ] ... 0.0055 connect(3, 0x08066DD8, 16, SOV_DEFAULT) = 0 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





電源の再起動をシミュレートするには、VMwareの再起動機能を使用します。 これは実際の再起動になることに注意してください-段階的なシャットダウンにつながるものはすべて、最初の例に似ています。



20分後、 kang



まだ同じ状態にあります。



お客様

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





TCPの仕事は、複数のシステム間で常に抽象化(つまり、TCP接続)を維持することであると考えがちであるため、このような壊れた抽象化のケースは驚くほどに見えます。 そして、これがなんらかのnc(1)問題だと思うなら、あなたは間違っています。 kodos



kodos



」はkang



への接続を表示しませんが、 kang



kodos



への完全に機能する接続をkodos



ます。



お客様

 [root@kang ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.139.50277 10.88.88.140.8080 32806 0 1049800 0 ESTABLISHED ...
      
      





そのままにしておくと、 kang



kodos



リブートされたことを知ることができません。



kang



kodos



データを送信しようとしてkang



とします。
どうなるの?



お客様

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...) kodos, are you there? 3872.6918 pollsys(0x08045670, 2, 0x00000000, 0x00000000) = 1 3872.6920 read(0, " kodos , are y".., 1024) = 22 3872.6924 write(3, " kodos , are y".., 22) = 22 3872.6932 pollsys(0x08045670, 2, 0x00000000, 0x00000000) = 1 3872.6932 read(3, 0x08043670, 1024) Err#131 ECONNRESET 3872.6933 close(3) = 0 [root@kang ~]#
      
      





メッセージを入力してEnterを押すと、 kodos



起動し、stdinからメッセージを読み取り、ソケット経由で送信します。 write



呼び出し が正常に完了しましたnc



は、次のイベントを待機するpoll



戻り、最後に、ブロックせずにソケットを読み取ることができないと結論付け、readを呼び出しread



。 この時間のread



は、ECONNRESETステータスで低下します。 これはどういう意味ですか? read(2)のドキュメントには次のことが記載されています



 [ECONNRESET]     ,      .
      
      





別のソースには、もう少し詳細が含まれています。



  ECONNRESET  filedes      .        .  /      filedes.
      
      





このエラーは、 read



呼び出しに関する特定の問題を意味するものではありません。 ソケットが切断されたというだけです。 このため、ほとんどのソケット操作はエラーになります。



それで何が起こったのですか? その時点で、 kang



上のnc



がデータを送信しようとしたとき、TCPスタックはまだ接続がすでに停止していることを知りませんでした。 kang



はデータパケットをkodos



に送信しましたkodos



は接続について何も知らなかったため、RSTで応答しました。 kang



はRSTを見て、切断されました。 ソケットファイル記述子を閉じることはできません—ファイル記述子はこの方法では動作しませんが、その後の操作は、 nc



がファイル記述子を閉じるまでECONNRESETステータスで失敗します。



結論:



  1. 激しい停電は、きちんとしたシャットダウンとは大きく異なります。 分散システムをテストする場合、このシナリオは個別に確認する必要があります。 すべてが通常のプロセスのシャットダウン(kill)と同じになるとは思わないでください。
  2. 一方の側がTCP接続が確立されていることを確認し、もう一方の側が不明である状況があり、この状況は決して自動的に解決されません。 アプリケーションレベルまたはTCPでの接続のキープアライブを使用して、このような問題の解決を管理できます。
  3. kang



    がまだリモート側の消失を発見した唯一の理由は、彼がデータを送信し、接続がないことを示す応答を受信したためです。


問題は、何らかの理由でkodos



がデータの送信に応答しない場合はどうでしょうか?



パズル2:電源オフ



TCP接続のエンドポイントがしばらくネットワークから切断されるとどうなりますか? 他のノードはこれについて知るでしょうか? もしそうなら、どのように? そしていつ?



nc



を使用して再接続します。



サーバー

 [root@kodos ~]# truss -d -t bind,listen,accept,poll,read,write nc -l -p 8080 Base time stamp: 1464385399.1661 [ Fri May 27 21:43:19 UTC 2016 ] 0.0030 bind(3, 0x08065790, 32, SOV_SOCKBSD) = 0 0.0031 listen(3, 1, SOV_DEFAULT) = 0 accept(3, 0x08047B3C, 0x08047C3C, SOV_DEFAULT, 0) (sleeping...) 6.5491 accept(3, 0x08047B3C, 0x08047C3C, SOV_DEFAULT, 0) = 4 pollsys(0x08045680, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





お客様

 [root@kang ~]# truss -d -t open,connect,pollsys,read,write,close nc 10.88.88.140 8080 Base time stamp: 1464330881.0984 [ Fri May 27 06:34:41 UTC 2016 ] ... 0.0057 connect(3, 0x08066DD8, 16, SOV_DEFAULT) = 0 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





ここで、突然kodos



の電源をオフにし、 kang



データを送信しようとします。



お客様

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...) 114.4971 pollsys(0x08045670, 2, 0x00000000, 0x00000000) = 1 114.4974 read(0, "\n", 1024) = 1 114.4975 write(3, "\n", 1) = 1 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





write



呼び出しは正常に終了し、私は長い間何も見ていません。 わずか5分後に表示されます。



お客様

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...) 425.5664 pollsys(0x08045670, 2, 0x00000000, 0x00000000) = 1 425.5665 read(3, 0x08043670, 1024) Err#145 ETIMEDOUT 425.5666 close(3) = 0
      
      





この状況は、電源を完全にオフにするのではなく、電源を再起動した場合と非常に似ています。 2つの違いがあります。





繰り返しますが、これは期限切れのread



タイムアウトです。 他のソケット操作でも同じエラーが発生します。 これは、接続がタイムアウトしたときにソケットが状態に入るためです。 この理由は、このシステムの設定に応じて、リモート側が長すぎる-5分間データパケットを確認しなかったためです。



結論:



  1. リモートシステムが電源を再起動する代わりに、単にシャットダウンするとき、最初のシステムはデータを送信することによってのみこれを知ることができます。 さもなければ、彼女は壊れた接続について決して知ることができません。
  2. システムが長すぎるデータを送信しようとして応答を受信しない場合、TCP接続は閉じられ、ソケットを使用するすべての操作はETIMEDOUTエラーで終了します。


パズル3:落下せずに接続の欠如



今回は、特定の状況を説明して何が起こっているのかを尋ねる代わりに、反対のことを行います。特定の観察を説明し、これがどのように起こったかを理解できるかどうかを確認します。 kang



kodos



に接続していると信じているかもしれないが、 kodos



はそれについて知らないいくつかの状況を議論しました。 kodos



kodos



に接続されて、 kodos



が無期限にそれを知らないようにkang



ことは可能ですか(つまり、問題自体は解決されません)、停電や再起動、他のkodos



オペレーティングシステムkodos



、ネットワーク機器?




ヒント:接続がESTABLISHEDステータスのままである場合、上記のケースを考慮してください。 ソケットを開いたままにして、接続が中断されたときにデータを送信することで検出できるため、この問題を解決するのはアプリケーションの責任であると言えます。 しかし、アプリケーションがソケットを開いたままにしないとどうなりますか?



ウォームアップでは、 kodos



ncがソケットを閉じた状況を調べました。 kang



ncは0(転送の終了へのポインタ)を読み取り、ソケットを閉じたと言いました。 ソケットが開いたままだとしましょう。 明らかに、それから読むことは不可能でしょう。 ただし、TCPについては、FINが送信した相手に追加のデータを送信できないとは言われていません。 FINは、FINが送信された方向のデータストリームのみを閉じることを意味します。



これを実証するために、 kang



nc



を使用することはできません。0を受け取った後にソケットを自動的に閉じるためです。したがって、このポイントをスキップするdncというnc



デモバージョンを作成しました。 Dncはまた、システムコールを明示的に表示します。 これにより、TCPのステータスを追跡する機会が得られます。



まず、接続を構成します。



サーバー

 [root@kodos ~]# truss -d -t bind,listen,accept,poll,read,write nc -l -p 8080 Base time stamp: 1464392924.7841 [ Fri May 27 23:48:44 UTC 2016 ] 0.0028 bind(3, 0x08065790, 32, SOV_SOCKBSD) = 0 0.0028 listen(3, 1, SOV_DEFAULT) = 0 accept(3, 0x08047B2C, 0x08047C2C, SOV_DEFAULT, 0) (sleeping...) 1.9356 accept(3, 0x08047B2C, 0x08047C2C, SOV_DEFAULT, 0) = 4 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...)
      
      





お客様

 [root@kang ~]# dnc 10.88.88.140 8080 2016-05-27T08:40:02Z: establishing connection 2016-05-27T08:40:02Z: connected 2016-05-27T08:40:02Z: entering poll()
      
      





次に、接続が両側でESTABLISHEDステータスになっていることを確認します。



サーバー

 [root@kodos ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.140.8080 10.88.88.139.37259 1049792 0 1049800 0 ESTABLISHED
      
      





お客様

 [root@kang ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.139.37259 10.88.88.140.8080 32806 0 1049800 0 ESTABLISHED
      
      





kodos



nc



プロセスにCTRL-Cを適用します。



サーバー

 pollsys(0x08045670, 2, 0x00000000, 0x00000000) (sleeping...) ^C[root@kodos ~]#
      
      





kang



では、次のことがすぐにわかります。



お客様

 2016-05-27T08:40:12Z: poll returned events 0x0/0x1 2016-05-27T08:40:12Z: reading from socket 2016-05-27T08:40:12Z: read end-of-stream from socket 2016-05-27T08:40:12Z: read 0 bytes from socket 2016-05-27T08:40:12Z: entering poll()
      
      





次に、TCP接続のステータスを見てみましょう。



サーバー

 [root@kodos ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.140.8080 10.88.88.139.37259 1049792 0 1049800 0 FIN_WAIT_2
      
      





お客様

 [root@kang ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.139.37259 10.88.88.140.8080 1049792 0 1049800 0 CLOSE_WAIT
      
      





それは理にかなっています:kudosはFINをkang



送信しました。 FIN_WAIT_2



は、 kodos



が送信したFINに応答してkang



からACKを受信したことを示し、 CLOSE_WAIT



は、 kang



がFINを受信したが応答にFINを送信しなかったことを示します。 これは完全に正常なTCP接続状態であり、永久に続く可能性があります。 kodos



kang



要求を送信し、他に何も送信する予定はなかったと想像してkodos



kang



は何時間も楽しくデータを送り返すことができます。 私たちの場合にのみ、 kodos



実際にソケットを閉じました



少し待って、TCP接続の状態をもう一度確認しましょう。 kodos



接続が完全に失われますが、 kang



まだ存在することがkodos







お客様

 [root@kang ~]# netstat -f inet -P tcp -n TCP: IPv4 Local Address Remote Address Swind Send-Q Rwind Recv-Q State –––––––––––––––––––– –––––––––––––––––––– ––––– –––––- ––––– –––––- ––––––––––- 10.88.88.139.37259 10.88.88.140.8080 1049792 0 1049800 0 CLOSE_WAIT
      
      





アプリケーションがソケットを閉じ、スタックがFINを送信し、リモートスタックがFINを認識し、ローカルスタックが一定時間待機して接続を閉じると、TCPスタックに関連するあまり知られていない状況に直面しました。 理由は? リモート側がリブートされました。 このケースは、一方の接続がESTABLISHEDステータスにあり、もう一方の側がそれを認識していない場合に似ています。 唯一の違いは、アプリケーションがソケットを閉じたこと、および問題を処理できる他のコンポーネントがないことです。 その結果、TCPスタックは指定された期間待機し、接続を閉じます(相手側に何も送信しません)。



問題は後です。 この状況でkang



kodos



データを送信するとkodos



ますか?
kodos



側では既に接続が完了していますが、 kang



まだ接続が開いていると考えています。



お客様

 2016-05-27T08:40:12Z: entering poll() kodos, are you there? 2016-05-27T08:41:34Z: poll returned events 0x1/0x0 2016-05-27T08:41:34Z: reading from stdin 2016-05-27T08:41:34Z: writing 22 bytes read from stdin to socket 2016-05-27T08:41:34Z: entering poll() 2016-05-27T08:41:34Z: poll returned events 0x0/0x10 2016-05-27T08:41:34Z: reading from socket dnc: read: Connection reset by peer
      
      







これは、パズル1で見たものと同じです:TCPスタックは接続が閉じられたことをまだ知らないため、 write()



成功します。 しかしRSTが来て、 poll()



あるスレッドを起動し、後続のread()



要求がECONNRESETを返します。



結論:





おわりに



TCPは通常、2つのシステム間の抽象化(「TCP接続」)をサポートするプロトコルとして提示されます。 一部のソフトウェアまたはネットワークの問題により、接続が切断されることがわかっています。 , , - . 例:





TCP. , TCP , . TCP, , . , , TCP- .



, , , , «TCP-, » — . - , , . , - ( keep-alive).



, «» TCP-. ( , ) , . , TCP- .



, :







:





, , - . , .



All Articles