Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

SYNを受け取っているのにSYN-ACKを返さない件

f:id:k_chindamaikul:20150722162336p:plain

こんにちは!@kakeyangです!


久しぶりの投稿です。

なぜなら、、、沖縄の八重山諸島に旅行に行ってましたのです!

楽しかったなぁ(遠い目)


今回は、開発で経験した通信障害の原因とその対策を公開します。

原因がLinuxカーネルの設定だったので、結構苦労しました。。。

障害概要

  1. 異なるネットワーク内(片方は弊社ネットワーク)の物理サーバ間(弊社が受信側)でインターネット越しにソケット通信を行う。
  2. 毎回ではないが1割程度の割合で、送信側でconnection timeoutを観測。
  3. 受信側にはapacheのログが残っていなかったので、webサーバには届いていない。
  4. 送信側でtracerouteすると、確かに弊社ネットワーク内には入っている。
弊社ネットワーク内のF/Wから物理サーバの間のどこかで通信が途絶えているっぽい!

というところまではすぐに判明しました。


調査内容

データセンターと協力して調査をすすめると、ソケット通信のスリーウェイハンドシェイク時に、

クライアント側サーバから【SYN(通信開始要求)】を受けとっているにも関わらず、

【SYN-ACK(接続許可)】をトラッキングサーバ側から返却していないパケットがあることが判明。


物理サーバでパケットをキャプチャしてみる

とりあえず受信側の物理サーバでパケットをキャプチャしてみました。

パケットが一体どこまで届いているのかを確認する為です。


コマンド

なんてことないのですが、こんな感じです。

$ tcpdump port 80 -s 1500 -w /path/filename.pcap


オプションの-sはファイルサイズの指定です。

これは、パケット解析ソフトのWiresharkでキャプチャしたファイルを解析する為です。

デフォルトでは68バイトと非常に小さく、ヘッダ程度しか取れないので、

オプションで指定します。


Wiresharを使う

普段パケットをじっくり眺めることがないためか、

Wiresharkの使い方にも少し苦戦してしまいました。

所望のパケットを抽出するTipsを少しだけ書いておこうと思います。

(というか、僕はこれしか使いませんでした)

ちなみに、Wiresharkには日本語版があるようなのですが、

僕のは英語版なので、日本語の場合どういうメニューなのかは知りません。あしからず。


ソースIPで絞る

該当のソースIPのパケットで右クリック > Apply as Filter > Selected
あるいは
ip.src == xxx.xxx.xxx.xxx (xxx... は絞りたいソースIP)
f:id:k_chindamaikul:20150722162151j:plain


一連のパケットを見る

見たいパケットのいずれかで右クリック > Follow TCP Stream
f:id:k_chindamaikul:20150722162214j:plain


キャプチャ結果

こちらが正常なパケットです。(不要な情報はボカしています)

f:id:k_chindamaikul:20150722162245j:plain

送信要求者がSYNを送り、受信者がSYN ACKを返却、送信要求者がACKを

送信することで通信を許可、HTTP通信を開始しています。

そしてこちらが正常に通信できなかった時のパケットです。

f:id:k_chindamaikul:20150722162309j:plain


送信要求者が3回もSYNを送り、結局、受信者がSYN ACKを返却せずに通信を終了しています。

ここから先の調査がなかなか進みませんでしたorz


原因

更に調査を進めると、受信側の物理サーバのカーネルの設定で

「net.ipv4.tcp_tw_recycle = 1」を投入していたことが直接の原因ということがわかりました。

この設定が入っていると、

  • 通信元が複数サーバであること
  • 同一グローバルIPでの接続であること
  • TCPヘッダ内にTimeStampが存在すること
上記条件が揃った場合に、

通信タイミング(ほぼ同時に双方のサーバから通信がある場合)によっては、

後で来た通信のTimeStampが古い(過去の場合)と判断され、

OS側で通信を無視するという事象が起ようです。


設定を確認する

上記設定を無効にするコマンドと、それが反映されているかどうかを確認するコマンドは以下になります。

明示的にtcp_tw_recycleを0にする

$ sysctl -w net.ipv4.tcp_tw_recycle=0


適用されたかの確認をする。

$ sysctl net.ipv4.tcp_tw_recycle
net.ipv4.tcp_tw_recycle = 0

$ sysctl -a | grep 'net.ipv4.tcp_tw_recycle'
net.ipv4.tcp_tw_recycle = 0

$ cat /proc/sys/net/ipv4/tcp_tw_recy<wbr>cle
0

教訓

カーネルの設定を変更する際は、(たとえテストであっても)Linuxソースコードを確認し、

慎重に行いましょう。ということですね!