TCP Simultaneous Open

なんか完全に休暇モードになっちゃったので、P2P関連で調べもの。

NAT越えは一つの大きなテーマなのでその辺りを調べてて、TCP NAT Traversal(UDP Hole punchのTCP版みたいなもの)の論文
http://citeseer.ist.psu.edu/739668.html

を眺めていたら、TCP Simultaneous OpenというのがTCP仕様(RFC793)に有るのを知りました。
http://www.tcpipguide.com/free/t_TCPConnectionEstablishmentProcessTheThreeWayHandsh-4.htm

通常の3-way handshakeの例外事項みたいな仕様のようですが、検索してもあんまり出てこなかったので書いてみます。

通常のServer/ClientモデルのTCP接続との違い

普通TCPはサーバがbind(), listen(), accept()して、クライアントがconnect()する感じですが、これをクライアント(peerと言った方が正確かな?)同士で、同時にbind(), connect()するだけで、両者間のTCPセッションを確立させることが出来るようです。同時に、というのがポイントですが、これは何か仲介サーバとかを使って、せえの、、とすればそれほど難しくなさそうです。あと通常クライアントでは使わないbindを行うのは、相手側の送信元ポートにそのままconnectする形なので、相手側の送信元ポート番号が予測等が出来れば相手(と相手からみた自分も)bindする必要も無いと思います。

とりあえず実験

マシン2台(手元にあったMac2台)でまずはそもそもこの仕様が動くのか実験してみました。
rubyでちょっと書くと(普通のsocketの使い方サンプル+αですが・・)、ClientAは

#!/usr/bin/env ruby -wKU

require 'socket'

BIND_PORT = 8888
CONNECT_PORT = 7777
CONNECT_ADDR = '1.2.3.4'
NAME = 'ClinetA'

cnt = 0

begin
  s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  s.bind(Socket.sockaddr_in(BIND_PORT,"0.0.0.0"))
  s.connect(Socket.sockaddr_in(CONNECT_PORT, CONNECT_ADDR))
rescue Errno::ECONNREFUSED 
  puts "#{NAME}: Errno::ECONNREFUSED"
  cnt += 1
  s.close
  retry if (cnt < 100)
  exit(-1)
end

s.write "Hello from #{NAME}\n"
print "#{NAME}: " + s.readline

ClientBは

#!/usr/bin/env ruby -wKU

require 'socket'

BIND_PORT = 7777
CONNECT_PORT = 8888
CONNECT_ADDR = '2.3.4.5'
NAME = 'ClinetB'

#以下clientAとおなじ


な感じです。これらClientA,Bを2台のマシンでほぼ同時に実行すると、数回"Errno::ECONNREFUSED"とか表示されますが、そのあとClientAでは、

ClinetA: Hello from ClinetB

ClientBでは

ClinetB: Hello from ClinetA

と表示されると思います。

で、これは何が良いのかというと

  • TCP NAT Traversalで唯一既存のTCPスタックであんまり変なことをしていない(ほかはRaw socketとか使うので、管理者権限とかが必要)
  • listen()が制限されている環境で、コネクションを受け付けるっぽいことが出来そう(環境例:Flash AS3とか)

何となく上記論文ではWindowsだとXp2以前がNGぽいとか書いてあるような気もしないでもないので & Windows Xp2でもだめっぽいとか書いてあるのをみたような気もしないでもないので、あとで調べます。