何となくふんわりとしか理解していなくて、実際のところ 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 が溢れたときの動きもそのうち見てみる。