TCP backlog が溢れたときに何が起こるか

何となくふんわりとしか理解していなくて、実際のところ TCP backlog が溢れたときに Client~Server 間で何が起こるかよく判っていなかったので、実際に backlog が 1 で accept しない簡易な TCP サーバを作り、tcpdump でどのようなパケットが飛び交うかを覗いて確認しました。


TCP 3 way handshake では次のような段階で状態遷移が起こる(併記している TCP 状態はパケットを投げた時点のもの)。

1. Client<SYN-SENT>]   --- [SYN]     --> Server<LISTEN>
2. Client<SYN-SENT>]   <-- [SYN/ACK] --- Server<SYN-RECV>
3. Client<ESTABLISHED> ---     [ACK] --> Server<SYN-RECV>
4. Client<ESTABLISHED>                   Server<ESTABLISHED>

backlog が溢れると、3. のクライアントからサーバへの ACK をサーバがドロップする。

サーバから見ると SYN-RECV になった後の ACK が来ていない状態なので 2. の SYN/ACK を 1 秒後に再送します(昔は 3 秒だった気がするのだけどいつからか 1 秒に変わっているらしい)。

クライアントが生きていればその SYN/ACK に対する 3. の ACK も再び送られるものの、サーバの backlog に空きができていなければ再びドロップされる。

サーバは net.ipv4.tcp_synack_retries の回数まで間隔を倍々しながら SYN/ACK を再送する。net.ipv4.tcp_synack_retries=5 なら 1,2,4,8,16 秒後に SYN/ACK が再送された後、32秒後に SYN-RECV も消える(syn backlog から消える)。

一方でクライアントは最後の ACK を投げた時点で接続が確立(ESTABLISHED)している(つもりになっている)。なので何事もなければサーバからのデータをいつまでも待ち続けるし、クライアントから最初にデータを送るプロトコルなのであれば(HTTP とか)クライアントから最初のデータが送信される。クライアントからデータが投げられた場合、サーバが SYN-RECV であればそれはドロップされるため、クライアントは同じデータを再送する。再送間隔は RTO で決まるので可変。再送回数は や net.ipv4.tcp_retries2 で決まる(+3される?)。

なお、クライアントが TCP 接続を閉じた場合はクライアントから FIN が送られるもののサーバが SYN-RECV ならこの FIN もドロップされるため、クライアントから FIN が再送される。

net.ipv4.tcp_abort_on_overflow=1

上記は net.ipv4.tcp_abort_on_overflow=0 の場合の動作。

サーバで net.ipv4.tcp_abort_on_overflow=1 にすると backlog が溢れると ACK を DROP するのではなく、Reject(RST)するようになる。のでクライアントは直ちに Connection reset になるので、再送も起こらない。

さいごに

TCP syn backlog が溢れたときの動きもそのうち見てみる。