tcpdumpとiptablesで理解するTCPのコネクションタイムアウト (connection timeout)

## TCPのコネクションタイムアウト

TCPでデータを転送する場合、データの転送を行う前にコネクションを確立する必要があります。
TCPのコネクションタイムアウトは、TCPクライアントがコネクション確立要求を発行した後、TCPサーバーから一定時間応答がない場合に起こります。

TCPのコネクションについての詳細は以下のエントリを参照してください。
[tcpdumpで理解するTCPのコネクション](http://qiita.com/keikmobile/items/93cf6dc5b95019c6a442)

タイムアウトは、OSの設定によるコネクション確立要求の再送回数、またはアプリケーションでタイムアウトを実装している場合はアプリケーションの設定に依存し、短い方が適用されます。

上記のコネクション確立要求の再送及びコネクションタイムアウトをiptablesコマンドとtcpdumpコマンドで実際に確認して行きます。
本手順でのiptablesの操作は、試験環境やローカルのVM等の外部から閉じられた環境を想定しています。外部に公開しているようなセキュリティ対策が必要な環境では行わないで下さい。

## tcpdumpとiptablesで理解するTCPのコネクションタイムアウト

### 0.前準備

#### 使用するネットワークコマンド

本エントリでは、以下のLinuxコマンドを使用します。

* tcpdump
 * ネットワーク上のパケットを出力するために使用します。
* nc,telnet
 * tcpサーバー/クライアントとして使用します。
* netstat
 * コネクションの状態を確認するために使用します。
* iptables
 * コネクション確立要求をDROPするために使用します。

上記コマンドがインストールされていない場合、以下のコマンドでインストールします。

```
$ sudo yum -y install tcpdump nc telnet net-tools iptables
```

### 1. コネクションの確認

以下のエントリの1.2.の手順を実施して、コネクションの確立ができる事を確認してください。

[tcpdumpで理解するTCPのコネクション](http://qiita.com/keikmobile/items/93cf6dc5b95019c6a442)

* 1. TCPサーバの起動
* 2. TCPクライアントの起動、コネクションの確立。

また、1.の手順にて、ncコマンドで起動したTCPサーバーは起動したままとしておいてください。

### 2. iptablesの前準備

(本手順でのiptablesの操作は、試験環境やローカルのVM等の外部から閉じられた環境を想定しています。
外部に公開しているようなセキュリティ対策が必要な環境では行わないで下さい。)

iptablesコマンドでlo interfaceの通信を全て許可しているルール、及びパケットをREJECTしているルールを削除します。

```
$ sudo iptables -D INPUT -i lo -j ACCEPT
$ sudo iptables -D INPUT -j REJECT --reject-with icmp-host-prohibited 
$ sudo iptables -n -L INPUT 
```

または、serviceコマンドでiptableのstopを行って下さい。

```
$ sudo service iptables stop
$ sudo iptables -n -L INPUT 
```

iptablesの-Lオプションの実行結果に以下のルールが存在しないことを確認します。

```
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 
```

### 3. iptablesでのコネクション確立要求(SYNパケット)のパケット廃棄(DROP)

iptablesコマンドで、コネクション確立要求(SYNパケット)のパケット廃棄(DROP)のためのルールを設定します。

```
$ sudo iptables -A INPUT -p tcp -d 127.0.0.1 --dport 12345 -j DROP
$ sudo iptables -n -L INPUT 
```

iptablesの-Lオプションの実行結果に以下のルールが存在することを確認します。

```
DROP       tcp  --  0.0.0.0/0            127.0.0.1           tcp dpt:12345 
```

この状態で以下のコマンドを実行します。

```
$ telnet 127.0.0.1 12345
Trying 127.0.0.1...
```

上記の出力のまま、何も反応なければ成功です。適当な時間で[Ctrl + C]でコマンド実行を停止してください。
これで、コネクションタイムアウトをtcpdumpで確認する準備ができました。

### 3. tcpdumpでのコネクション確立要求(SYNパケット)の再送、コネクションタイムアウトを目視する。

別ターミナルでtcpdumpコマンドでlocal interfaceのport12345を指定して実行します。

```
$ sudo tcpdump -i lo -nnn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
```

2.の手順と同様にtelnetコマンドをtimeコマンドをつけて実行します。約1分少々待つと、telnetコマンドがコネクションタイムします。

```
$ time telnet 127.0.0.1 12345
Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection timed out

real    1m3.xxxs
...
$ 
````

tcpdumpの出力を確認します。


```
17:37:32.908573 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ...
17:37:33.907642 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ...
17:37:35.907653 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ... 
17:37:39.908743 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ... 
17:37:47.909561 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ...
17:38:03.910784 IP 127.0.0.1.60632 > 127.0.0.1.12345: Flags [S], seq <seq num>, ...
```

上記のtcpdumpの出力フォーマットは以下の通りです。

```
timestamp src > dst: flags data-seqno
```
* timestamp: タイムスタンプ
* src: 送信元IP.ポート
* dst: 送信先IP.ポート
* flags: フラグ S (SYN)
* data-seqno: シーケンス番号(上記はSYNパケットのみなので初期シーケンス番号) 

上記のパケットキャプチャは、TCPクラアントがコネクション確立要求を送信したが、応答がないため、再送を行い一定回数の再送を行った後、タイムアウトしたことを示しています。
一定回数は以下のkernel parametersの設定に依存します。

```
$ sysctl -n net.ipv4.tcp_syn_retries
5
$ 
```

以下はシーケンスの概要です。

![tcp_con_timeout.png](https://qiita-image-store.s3.amazonaws.com/0/22861/72cf3dad-840a-3e2c-a2a4-a3e1df09db35.png)


また、再送を行っている間にnetstatコマンドでコネクションの状態を確認すると、以下のようにSYN_SENTであることが確認できます。

```
$ sudo netstat -anp | grep 12345
tcp  0  1 127.0.0.1:<port>  127.0.0.1:12345  SYN_SENT  <PID>/telnet        
$ 
```

### おまけ: straceコマンドでtelnetのTCPのコネクションタイムアウト(connection timeout)を確認する

straceコマンドにtelnetコマンドを渡してiptablesでDROPされているIP,portにコネクション確立要求を送信します。以下のようにconnectシステムコールにETIMEDOUT (Connection timed out)が返っていることが確認できます。

```
$ strace -Ttt -e trace=network telnet 127.0.0.1 12345
Trying 127.0.0.1...
03:45:49.327876 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000021>
03:45:49.327994 setsockopt(3, SOL_IP, IP_TOS, [16], 4) = 0 <0.000013>
03:45:49.328038 connect(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ETIMEDOUT (Connection timed out) <63.023684>
telnet: connect to address 127.0.0.1: Connection timed out
03:46:52.352064 +++ exited with 1 +++
$ 
$ man 2 connect
       ETIMEDOUT
              Timeout while attempting connection.  The server may be too busy to accept new connections.  Note that for IP sockets the
              timeout may be very long when syncookies are enabled on the server.
$ 
$ grep ETIMEDOUT /usr/include/asm-generic/errno.h 
#define ETIMEDOUT       110     /* Connection timed out */
$ 
```