Depois de ter o mesmo problema, finalmente percebo a causa raiz.
No Linux, quando um socket está em TIME_WAIT e um novo SYN append (para o mesmo par de ip / port src, ip / port dest), o kernel verifica se o número SEQ do SYN é < ou > que o último SEQ recebido para este socket.
(PS: na imagem da saída do wireshark anexada a este problema, o número do seq é mostrado como relativo, se você não os definir como absolutos, você não pode ver o problema. A captura teria que mostrar o antigo sessão também para poder comparar números SEQ)
- se o número SEQ do SYN for > que o número SEQ do pacote anterior, uma nova conexão é criada e tudo funciona
- se o número de SEQ do SYN for < que o número SEQ do pacote anterior, o kernel irá enviar um ACK relacionado ao socket anterior porque o kernel acha que o SYN recebido é um pacote atrasado do socket anterior.
O comportamento é assim porque no início do TCP o número SEQ gerado pelos computadores era incremental, era quase impossível receber um número SEQ < que o número SEQ de um socket anterior ainda em TIME_WAIT.
O aumento da largura de banda dos computadores torna isso quase impossível até raro. Mas as coisas mais importantes aqui são que agora a maioria dos sistemas usa ISN aleatório (número SEQ inicial) para melhorar a segurança. Portanto, nada impede que o número SEQ a do novo socket seja > que o número SEQ de um anterior.
Cada sistema operacional usa diferentes algoritmos que são mais ou menos seguros para evitar esse problema específico O link oferece uma boa apresentação do assunto.
Há uma última coisa complicada ... então o kernel irá enviar um ACK relacionado à sessão antiga, então? O sistema operacional do cliente deve receber o ACK (da sessão anterior), não entendi porque para o cliente a sessão está fechada, envie um RST. Quando o servidor receber este RST, ele limpará imediatamente o soquete (portanto, ele não estará mais em TIME_WAIT). Por seu lado, o cliente está esperando por um SYN / ACK, pois ele não o recebe, ele irá enviar um novo SYN. Enquanto isso, o RST foi enviado ea sessão foi limpa no servidor, portanto, este SYN secundário funcionará e o servidor responderá ao SYN / ACK e assim por diante.
Portanto, o comportamento normal é que a conexão funcione, mas seja atrasada por um segundo (até que o SYN secundário seja enviado). No caso de Jeff, ele disse em um comentário que ele usa um firewall Fortinet, esse firewall (por padrão) descartará o ACK relacionado à sessão antiga (porque o firewall não vê nenhuma sessão aberta relacionada ao ACK), então o cliente não envie qualquer RST e o servidor não poderá limpar a sessão do estado TIME_WAIT (exceto, é claro, no final do temporizador TIME_WAIT). O comando "set anti-replay loose" no fortinet pode permitir que este pacote ACK seja encaminhado ao invés de ser descartado.