ハーフクローズ中のSYNコネクション受付の件

相談することころがなくインターネットを検索していたら先生のページを見つけました。以前書籍を読ませていただいていましたので、やったと思い質問致しました。

構成は PC --LAN--Linux組込機をLAN接続しています。
通信は Linux組込がTCPの特定のポート(9000)でソケット通信を行っています。
そのため、特定のプロトコルには依存していません。

パケットモニターにてTCP通信を調べたところコネクション以下のように
1回目のセグメントが終了しないうちに2回目のコネクションが始まっています。
2回目の通信の途中で最初のコネクションが割り込んでいるという現象が確認できました。シーケンス番号は正常に推移しています。いろいろ書籍を調べてみるとハーフクローズすると片側通信になるとは書いてありました。しかし今回のような新しい
コネクションが割り込むのは正しいことなのでしょうか?
質問の場所が違うかもしれませんがよろしくご教示願います。。。

 
     PC        Linux組込(port 9000)
 (port 1132)--SYN---> 
     3ステートハンドシェーク正常
       データ通信 正常   
      <-------------- FIN/ACK
 
(port 1133)----SYN---->    
      3ステートハンドシェーク正常
       データ通信 中

  port 1132 <------ FIN ACK
        ----ACK---->

  port 1133 <---RST・ACK----

     

名前: 
TCPに悩む
日時: 
10/10/04 12:42

コメント

TCPに悩むさん,こんにちわ。

クライアント側のポート番号は
最初のコネクションが1032で,二つ目のコネクションが1033なんですよね。
それだったら,コネクションは別のものですから,
割り込んでいるわけではありませんし,何もおかしくありません。
たまたま,最初のコネクションのパケットが全部終わる前に,
二つ目のコネクションが始まっているだけだと思います。

Linux側のポート番号が同じ番号(9000番)なので,
同一コネクションと勘違いしているように見受けますが,
接続を待ち受ける側のポート番号は同じ値になりますから,
同じ9000番号でもコネクションは違うものになります。

その辺の話は,『ネットワークはなぜ...』の399ページ近辺に
説明があるので,参考になるでしょう。

このコネクションの割り込み云々よりも,
二つ目のコネクションの最後にRSTが返っているところが気になりますね。
Linux側で通信が強制終了しているっていうことだと思いますが,
心当たりありますか?

早速のご教示ありがとうございます。
 「ネットワークはなぜつながるのか」家の本棚にあるので帰って読んでみます。

実は RSTが発生が、ご質問した原因で発生していると思って調べていました。。。
まったく関係ないということが先生のご説明でわかりました。
原因の切り分けができましたので別の原因を調べてみます。
大変ありがとうございます。 
 また行き詰った時は質問させてください。よろしくお願い致します。

家に帰って「ネットワークはなぜつながるのか」を読み直しました。
399ページ 通信先は クライアント側IPアドレス、ポート番号とサーバー側IPアドレス、ポート番号で管理されている事と bind listen accept の関係が理解できました。(ここまで説明してある書籍又はWEB上どこにもありませんでした)
続けて読んでいく中で410ページに「パケットからデータの断片を取り出して、受信バッファーに保存します。」とありました。
これは以下のように理解してよいでしょうか?
たとえば クライアント側から ポート番号 1132と1133 から送られてきたデータは
     受信            送信元       
IPアドレス  ポート番号   IPアドレス  ポート番号  送信データ
 XXX      9000      YYY     1132     ABCDE
 XXX      9000      YYY     1133     1234567890

 acceptで取得したディスクリプタを使って read 処理をすると
  "ABCDE1234567890" として 読み出せる。
 
 ただし、読み出すタイミングによっては "ABCDE123"ということもあり得る。

よろしくごご教示お願い致します。

  
   

 
 

まず,ソケットとコネクションの基本から確認してみましょう。
コネクションの両端にデータの出入り口とも言えるソケットがあります。
そして,送受信バッファはソケット毎に別々に用意されます。
このソケットの一方にデータをwriteすると,
コネクションの中をデータが流れて行き,
相手側のソケットにデータが届き,
そして,相手側のソケットでデータをreadすると,そのデータが出てくるわけです。

で,下の状況だと,コネクションが二つあって,
それぞれのコネクションに対応する格好でサーバ側のソケットも二つあるわけです。

> IPアドレス  ポート番号   IPアドレス  ポート番号  送信データ
>  XXX      9000      YYY     1132     ABCDE
>  XXX      9000      YYY     1133     1234567890

そして,たとえば,
上のコネクションに対応するソケットからデータをreadすると『ABCDE』が出てきて,
下のソケットからreadすると『1234567890』が出てきます。
ソケットはコネクションの両端にあって,
そのコネクションに対するデータの出入り口としての役割を持つわけですから,
こういうことになります。

もし,上のソケットで『ABCDE』をreadした後,引き続いてreadを実行すると,
『ABCDE』の次のデータを読み出すことになります。
そのとき,まだ次のデータが到着していなかったら,それが到着するまで,
アプリケーションは待ち状態になります。
つまり,上のソケットで『1234567890』を読み出すことはできないんです。
下のソケットも同様ですし,
クライアント側のソケットも同じです。

二つのコネクションのデータが入り混じってしまったり,
readのタイミングによって受け取るデータが違ったりすると,
プログラムの動きは不確定になってしまいますから,
そんな仕組みは使い物になりませんよね。

それから,ソケットが二つあるということは,
acceptは2回実行する,ということです。
そして,そこから返ってくるディスクリプタも
それぞれのソケットに対応する格好で別々の値が二つあることになります。
もし,この辺を正しく理解しておらず,
ディスクリプタを入れる変数を一つしか用意していないと,
前のソケットのディスクリプタが上書きされて消えてしまい,
おかしなことになります。

こんな説明でわかります?

ご教授大変ありがとうございます。
ご質問させていただいた経緯は、6年くらい前に開発した組み込みシステムのカーネルを2.4→2.6へバージョンアップするためにディストリビューションを変更しました。
それに伴いご質問させていただいた問題が発生しました。
以下の2つの通信がほぼ同時に発生したとき
 
番号 IPアドレス  ポート番号   IPアドレス  ポート番号  送信データ
①   XXX      9000      YYY     1132     ABCDE
②   XXX      9000      YYY     1133     1234567890

上記の2つのコネクションがあったとき①と②は別々の通信と考えていいのですね
それぞれが 3ステートハンドシェークから始まって データ通信 そしてクローズが
別々に行われる。

  P C            組み込み
   <----①--------> SYN ・・[ABCDE]・・・ FIN
 
   <----②--------> SYN ・・[1234567890]・・・ FIN

質問させていただいた、コネクションが同時に発生したとき、TCP パケットを時系列に列にモニターすると①に②が割り込んでいるように見えるが、ソケットの処理としては別々に管理されているので問題はない。
listen() した後 accept()で取得したディスクリプタを使って①のデータを読み出し
 次のaccept()で取得したディスクリプタを使って②のデータを読み出す。これが先生が言われている2回accept()を実行しないとならないということですね。

RST パケットについてですが現在のプログラム構造が以下のようになっていました。
 sock=socket(...)
bind(sock ... )
listen(sock ...)

fd= accept( sock ... )
read(fd ...) [ABCDE] を読み出し
close(fd)
close(sock)

 sock=socket(...)
bind(sock ... )
listen(sock ...)

fd= accept( sock ... )
read(fd ...) [1234567890] を読み出し
close(fd)
close(sock)

この構造だと①のクローズ処理をしているとき、ソケットのクローズ処理中にソケット自体がなくなるのでこれを 以下のように変更したいと思っています。
ただ、この処理を切り出してlinuxパソコンで実行すると問題が発生しません。
(組み込み機のCPUは ARM9 linuxは timesysyを使っています)

 sock=socket(...)
bind(sock ... )
listen(sock ...)

fd= accept( sock ... )
read(fd ...) [ABCDE] を読み出し
close(fd)

fd= accept( sock ... )
read(fd ...) [1234567890] を読み出し
close(fd)

close(sock)

以上 長くありましたが大変わかりやすいご説明ありがとうございました。
上記の結果については 後日報告させていただきます。

プログラムの内容がわからないので,的外れかもしれませんけど,
下のように,ディスクリプタを格納する変数を分けておけば,
closeのタイミングを気にせずにすみ,安全だと思いますけど...

fd1= accept( sock ... )
read(fd1 ...) [ABCDE] を読み出し
close(fd1)

fd2= accept( sock ... )
read(fd2 ...) [1234567890] を読み出し
close(fd2)

先生のご指摘通りでした。。。
プログラムにsleep(20)を入れながらどこでRSTパケットを出力しているか探しました。
2つの条件が重なった時に発生しました。条件は、最初のコネクションの通信が終わりPCから FINパケットを出力するした後、PCから次のコネクションが始まった状態の時にソケットをクローズするとRTSパケットがLinux組込機から出力されました。

    PC         Linux組込(port 9000)
 (port 1132)----- SYN  ---> 
     <-3ステートハンドシェーク ->
     <---  データ通信 ---> 
  
     --- FIN/ACK ------> 
 
(port 1133)--- SYN       --->(9000)    
      <- 3ステートハンドシェーク ->
      <---  データ通信 --->
通信途中に ひとつ前のパケットが交る 
(port 1132) < --- FIN ACK ---  (9000)
         ---- ACK   --->
                           ソケットクローズ   
  port 1133 <--- RST・ACK ---       <---+

現状の処理は以下の繰り返しでした。
socket()-->bind()-->Listen()-->accept()-->read()/send()-->close(accept)-->close(sokect) 
組込機側の処理として 
  syn--> read()-->send() -->Fin の時は問題はありませんでした。しかし、2つのコマンド(別のポート)がPCから連続して送られてくると最初のパケットの処理が終了する前にTCPソケットは2つ目のパケットの処理を初めていました。その状態でアプリケーションが最初のパケット通信のソケットをクローズするとTCPソケットがRSTパケットを送信していました。

以上 先生の的確なアドバイス大変助かりました。ありがとうございます。

 今日も「基礎からわかるネットワーク入門」のFTPの仕組みに助けられました。