tcpdumpとiptablesで理解するTCPのキープアライブ(TCP keepalive)

## TCPのキープアライブ(TCP keepalive)

TCPでデータを転送する場合、データの転送を行う前にコネクションが確立されている必要があります。
一方で、TCPは状態を持つ(ステートフルな)プロトコルであり、サーバ・クライアントそれぞれが状態を持つため、サーバ・クライアントの一方のみがコネクションを確立している状態であるハーフコネクション・ハーフオープンになりえます。

アプリケーションでTCPのキープアライブが有効な場合は、コネクションが有効であることを確認するパケットを定期的に送信し、TCP通信が不可の状態を検知した場合、そのコネクションを強制的に閉じることが可能です。

TCPのハーフコネクション・ハーフオープンについての詳細は以下のエントリを参照してください。
[tcpdumpとiptablesで理解するTCPのハーフコネクション・ハーフオープン](http://qiita.com/keikmobile/items/16deac8a5093bd0ae7bb)

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

本エントリーでは、TCPのキープアライブ(TCP keepalive)の振る舞いをiptablesコマンドとtcpdumpコマンドで実際に確認します。

## tcpdumpとiptablesで理解するTCPのキープアライブ(TCP keepalive)

### 0.前準備

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

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

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

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

### 1. コネクションの確認、iptablesの前準備

以下のエントリの1.2.の手順を実施して、コネクションの確認、iptablesの前準備までを確認してください。

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

* 1. コネクションの確認
* 2. iptablesの前準備

### 2. tcp keepalive関連のカーネルパラメータの確認、更新

TCP keepaliveは以下のカーネルパラメータで振る舞いを設定可能です。
以下のコマンドで、TCP keepaliveに関するカーネルパラメータを確認します。

```
$ sysctl -a | grep tcp_keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75
$ 
```
それぞれ以下を表します。

| カーネルパラメータ | 概要 |
|:--|:--|
|net.ipv4.tcp_keepalive_time  |keepalive packetを送信するまでの時間を秒単位で指定します   。デフォルトは2時間(7200秒)|
|net.ipv4.tcp_keepalive_probes|keepalive packetを送信する回数を指定します。デフォルトは9回|
|net.ipv4.tcp_keepalive_intvl |keepalive packetを送信する間隔を秒単位で指定します。デフォルトは75秒|

デフォルト値では、確認のため2時間以上(7200秒 + (75秒 x 9回))待つ必要があります。そのため、本エントリでは以下の設定変更を行います。
※本手順でのsysctlの操作は、試験環境やローカルのVM等の試験環境を想定しています。本設定変更は、そのホスト上で動作する他のアプリケーションの振る舞いも変更してしまう可能性があるため注意してください。

```
$ sudo sysctl -w \
net.ipv4.tcp_keepalive_time=30 \
net.ipv4.tcp_keepalive_probes=3 \
net.ipv4.tcp_keepalive_intvl=3
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 3
```

上記の設定では、アプリケーションでtcp keepaliveが有効な場合、TCPコネクションで通信がない状態から30秒経過するとkeepalive packetを送信します。その後3秒毎に2回(合計3回)のkeepalive packetを送信し応答がない場合は、そのコネクションをCloseします。

### 3. tcpkeepalive機能が実装されたtcpクライアントの起動。コネクションの確率。

curlコマンドでTCPクライアントを起動し、ローカルで起動している1.のTCPサーバに接続します。

```
$ curl --local-port 22345 127.0.0.1:12345
```

```
curl: (7) bind failed with errno 98: Address already in use
```

が返ってきた場合は、--local-portの引数のportを変えて実行してください。

別ターミナルを開き、netstatコマンドでncとcurlのコネクションが確立されている(ESTABLISHになっている)事を確認します。またoオプションを渡す事で、keepalive timer(keepalive (0.20/0/0))を確認することが可能です。

```
$ sudo netstat -anop | grep 12345
tcp        0      0 0.0.0.0:12345               0.0.0.0:*                   LISTEN      4283/nc             off (0.00/0/0)
tcp        0      0 127.0.0.1:12345             127.0.0.1:22345             ESTABLISHED 4283/nc             off (0.00/0/0)
tcp        0      0 127.0.0.1:22345             127.0.0.1:12345             ESTABLISHED 4284/curl           keepalive (0.20/0/0)
$ 
```

これで、コネクションの確立およびkeepalive packetを目視する準備が完了しました。
以下にここまでのシーケンスを示します。
![curl_nc_establish.png](https://qiita-image-store.s3.amazonaws.com/0/22861/91c5ddbc-65d3-1caa-48bb-9b09303518bd.png)

### 4. tcpdumpでkeepalive packetを目視する

tcpdumpコマンドでlocal interfaceのtcpのport 12345を指定して実行します。

```
$ sudo tcpdump -i lo -nnn -p tcp port 12345
...
17:16:22.217222 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
17:16:22.217238 IP 127.0.0.1.12345 > 127.0.0.1.22345: Flags [.], ... length 0
#30sec
17:16:52.218130 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
17:16:52.218154 IP 127.0.0.1.12345 > 127.0.0.1.22345: Flags [.], ... length 0
#30sec
17:17:22.218192 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
17:17:22.218204 IP 127.0.0.1.12345 > 127.0.0.1.22345: Flags [.], ... length 0
```

tcpdumpの出力を確認します。3.で設定した30秒毎(net.ipv4.tcp_keepalive_time)にクライアント(source ip 22345)からlength 0のpacketが送信され、サーバーが応答を返しています。
このpacketがkeepalive packetです。

### 5. tcpdumpでtcp keepaliveによるコネクションCloseを目視する

iptablesで、上記のport番号のdestination portのパケット廃棄(DROP)のためのルールを設定します。

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

上記のルールを設定すると、クライアントからサーバ(dest ip 12345)のtcp keepalive packetが破棄されるため、応答が返ってきません。
以下はtcpdumpの出力です。

```
18:29:22.307203 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
#3sec
18:29:25.307994 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
#3sec
18:29:28.308245 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [.], ... length 0
#3sec
18:29:31.309769 IP 127.0.0.1.22345 > 127.0.0.1.12345: Flags [R.], ... length 0
```

クライアントは、3秒毎(net.ipv4.tcp_keepalive_intvl)に合計で3回(net.ipv4.tcp_keepalive_probes)のtcp keepalive packetを送信したのち、RSET packetを送信しコネクションを強制的にcloseします。
以下にシーケンスを示します。

![curl_nc_keepalive.png](https://qiita-image-store.s3.amazonaws.com/0/22861/ac027260-c06b-cefd-4f48-b72bffd3a13f.png)


curlのターミナルでは以下が返ります。

``` 
curl: (56) Failure when receiving data from the peer
```

上記のようにTCP keepaliveを実装したアプリケーションでは機能を有効にすることにより、ハーフクローズ・ハーフコネクションを検知し、解消することが可能です。

TCP keepaliveの詳細は以下ページが参考になります。
http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html


以下は本エントリのkeepaliveのシーケンスのソースです。

```
note over curl,nc: establish
note left of curl: keepalive probe packet (every 30 sec)
curl->nc: ack
nc->curl: ack
note left of curl: keepalive probe packet (every 30 sec)
curl->nc: ack
nc->curl: ack
note left of curl: keepalive probe packet (every 30 sec)
curl->nc: ack
nc->curl: ack
note left of curl: keepalive probe packet (every 30 sec)
curl->nc: ack
nc-->curl: ack
destroy nc
note left of curl: keepalive probe packet (3 sec)
curl->nc: ack
note left of curl: keepalive prove packet (3 sec)
curl->nc: ack
note left of curl: RST packet
curl->nc: rst
```