TIME_WAIT 상태인 socket에 동일한 source ip와 source port로 요청이 와서 발생한 issue입니다. 동일한 source ip와 source port로 요청이 온다는 것은 resource가 부족해서 발생한 것이라고 추측 하였습니다.

server의 tcpdump
client의 tcpdump

그림을 보면,

  1. Server에서 4-way handshaking을 통해 정상적으로 session을 끝내고 해당 socket이 TIME_WAIT 상태로 빠지게 됩니다.
  2. 이후 TIME_WAIT 상태에 빠진 상태에서 동일한 source로 요청이 들어와서 server는 다시 한번 FIN에 대한 ACK으로 응답합니다.
  3. 요청을 했던 외부 server는 맞지 않는 sequence number를 받았기 때문에, RST을 보내고 session을 종료합니다.

Load balancer가 affinity 등의 이유로 동일 port를 사용한 것일 수도 있겠다고 가정 하였으나, DSR 방식의 setting이기 때문에 load balancer는 port 변환 없이 전송하는 것을 확인했습니다. 따라서, server에서는 문제가 될 부분이 없다고 확인했습니다.

Client는 server에 요청하기 전에 Paloalto 장비를 거치는 구조이고, 이 장비 뒤에는 두대의 client가 존재 했습니다. 다음 가정은 client가 이전에 종료된 다른 client의 port를 이용한다는 것입니다. 이 가정을 토대로 Paloalto 외부로 나가는 IP의 수를 늘려 보았는데, 실패 확률이 0.144%에서 0.003%으로 감소 되었습니다. 이 것을 통해서 Paloalto에서 port를 재사용할 가능성이 높다고 예상했고, Paloalto에서 client와 외부로 나가는 IP를 1:1로 맵핑 한후, port를 그대로 이용 하도록 변경 후 해결 되었습니다.

재연을 시도 하였으나, 같은 상황이 발생 했을 때, TIME_WAIT에 있는 socket을 재사용해서 재연 불가하였습니다. 특이한 점은 net.ipv4.tcp_timestamps, net.ipv4.tcp_tw_reuse를 끈 상태에서도 TIME_WAIT 소켓을 재사용 한다는 것입니다. Kernel에서 Berkeley-derived TCP을 사용하고 이 경우 새로운 sequence number가 이전 연결에서의 sequence number 보다 큰 경우 time wait 상태를 무시하고 재사용이 된다는 것을 알게 되었습니다. Paloalto 장비에서는 Berkeley-derived TCP를 사용 하지 않아서 재사용 되지 않았을 것이라고 추측 하였습니다.

TIME_WAIT의 timeout 정보를 변경해 달라는 요청이 오기도 하였는데, 이 정보는 include/net/tcp.h에 hard coding 되어 변경이 불가능한 사항이었습니다.

TCP_WAIT: https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux
TCP_WAIT: https://lazymankook.tistory.com/6
Berkeley-derived TCP implementation: https://flylib.com/books/en/3.223.1.188/1/
Berkeley-derived TCP implementation: https://books.google.co.kr/books?id=XUkIKfJbFCUC&pg=PA441&lpg=PA441&dq=time+wait+berkeley-derived+implementations&source=bl&ots=UcT8GuJYAq&sig=ACfU3U2VfmNdKzsn4mqm-RU0JyEF_GPL5g&hl=en&sa=X&ved=2ahUKEwjyx5_1noXmAhWbHHAKHdjZB80Q6AEwAXoECAkQAQ#v=onepage&q=time%20wait%20berkeley-derived%20implementations&f=false