Skip to content

Latest commit

 

History

History
126 lines (69 loc) · 10.4 KB

tcp_retry_and_timeout.md

File metadata and controls

126 lines (69 loc) · 10.4 KB

TCP 재전송과 타임아웃

TCP 는 특성상 자신이 보낸 데이터를 상대방이 받았다는 응답의 패킷을 보내야 자신이 제대로 보냈다는 걸 확인할 수 있다.

즉 응답 패킷을 받지 못하면 상대방이 받지 않았다고 생각해서 다시 패킷을 보내도록 한다. 이런 과정이 네트워크 성능 저하를 가지고 올 수 있지만 TCP 특성상 필요한 일이다.

그래서 이번에는 TCP 재전송이 일어나는 과정을 보고 이로인해 발생할 수 있는 애플리케이션 타임아웃에 대해서 알아보자.

TCP 재전송은 생각보다 자주 일어날 수 있는 일이며, 이를 대비할 수 있는 예외 처리는 서비스 품질 유지에 도움이 된다.

TCP 재전송과 RTO

TCP 는 패킷을 보낸 쪽이 받았다는 ACK 를 받지 못하면 재전송을 하게 된다. 여기서 ACK 를 얼마나 기다려야 하는지에 대한 값을 RTO (Retransmission Timeout) 이라고 부른다.

즉 RTO 안에 ACK 를 받지 못하면 보내는 쪽에서 재전송을 진행한다.

RTO 에는 일반적인 RTO 과 InitRTO 두 가지가 있다. 일반적인 RTO 는 RTT (RoundTripTime, 두 종단 패킷 전송에 필요한 시간) 을 기준으로 설정된다. 예로 두 종단 간 패킷이 전송되는 시간이 1 초라면 이게 RTT 이다.

InitRTO 는 두 종단 간 최초 연결을 시작할 때, TCP 3_way_handshake 에서 SYN 패킷에 대한 RTO 를 의미한다. SYN 패킷을 보낼 당시에는 RTT 와 같은 정보가 없으니 임의로 설정한 값으로 RTO 를 계산하는데 리눅스에서는 소스 코드에 1 초로 구현해 놓았다.

리눅스에서는 ss -i 명령을 통해서 현재 세션에 설정되어있는 RTO 값을 볼 수 있다.

  • 여기서 나오는 값은 ms 단위이다.****

재전송을 결정하는 커널 파라미터

재전송과 관련된 커널 파라미터는 총 5 개의 값이 있다. 이건 sysctl -a | grep -i retries 명령을 통해서 조회가 가능하다.

  • net.ipv4.tcp_syn_retries = 5
  • net.ipv4.tcp_synack_retries = 5
  • net.ipv4.tcp_retries1 = 3
  • net.ipv4.tcp_retries2 = 15
  • net.ipv4.tcp_orphan_retries = 0

먼저 net.ipv4.tcp_syn_retries 부터 살펴보자. TCP 재전송은 이미 연결되어 있는 세션에서도 일어나지만 연결을 시도하는 과정에서도 일어난다. 이 값은 바로 처음 연결을 시도하는 SYN 패킷에 대한 재시도 횟수를 결정한다. 기본 값은 5 이다.

즉 5 번의 재전송 이후에도 연결이 되지 않으면 연결하지 않는다. 실제로 와이어샤크로 TCP dump 를 떠보면 이를 볼 수 있다.

이 값을 줄이면 좀 더 빠르게 새로운 연결에 대한 타임아웃을 설정하는게 가능하다. 애플리케이션 단에서 연결의 타임아웃 설정이 없다면 이 커널 파라미터를 통해서도 조절하는게 가능하다.

하지만 TCP 스펙에서는 최소 5 로 하길 권장한다.

두 번째는 net.ipv4.tcp_synack_retries 이다. 이름에서도 알 수 있듯이 상대편이 연결을 위해서 보낸 SYN 패킷에 대한 응답으로 보내는 SYN + ACK 패킷에 대한 재전송 횟수를 정의하는 값이다. 기본 값은 5 이고 상대방의 SYN 패킷에 대해서 최대 5 번의 SYN + ACK 패킷을 보낸다는 의미다.

이 값이 중요한 이유는 소켓 처리와 관련이 있어서인데 SYN 패킷의 요청을 받으면 소켓은 SYN + ACK 패킷을 보내고 SYN_RECV 가 된다. 그리고 응답 패킷이 오지 않으면 기본적으로 최대 5 번의 재전송을 하게 되고 요청이 안오면 종료가 되는데 이 값이 없다면 계속해서 SYN_RECV 상태로 유지되서 리소스가 고갈될 수 있기 떄문에 이 값이 반드시 있어야 한다.

  • 디도스의 공격이 SYN 패킷만 보내서 SYN_RECV 상태로 만들어놓는 식의 공격이다. SYN + ACK 로 보내도 대답해주지 않는다.

세 번째는 net.ipv4.tcp_orphan_retries 이다. orphan socket 이라 불리는 상태의 소켓들에 대한 재전송 횟수를 결정한다.

그렇다면 orphan socket 이 뭘까?

TCP 연결을 끊을 때 연결을 끊으려고 시도하는 쪽은 FIN 패킷을 보내고 FIN_WAIT 1 상태가 된다. 이 상태의 소켓이 orphan socket 이라고 불린다.

왜 FIN_WAIT2 상태나 TIME_WAIT 상태는 orphan socket 이라고 불리지 않을까?

내가 보냈던 패킷은 FIN 패킷밖에 없고 나머지는 TIME_WAIT 상태에서 응답을 보내는 패킷인 ACK 패킷만 있기 때문인데, 재전송은 내가 패킷을 보낸것에 대한 재전송을 말하는 것이지 응답을 보내는 용도가 아니기 때문이다.

그리고 orphan socket 으로 들어가면 더이상 소켓은 프로세스에 귀속되지 않는다. 오로지 커널에만 귀속되게 되며 재전송 횟수가 지나도 응답을 받지 못하면 커널에서 강제 회수한다.****

네 번째와 다섯 번째는 각각 net.ipv4.tcp_retries1net.ipv4.tcp_retries2 값이다.

이 두 값은 서로 관련이 있는데 retries1 는 ip 레이어에 네트워크가 잘못되었는지 확인하는 기준이 되며 retries2 는 더이상 통신을 할 수 없다는 기준이 된다.

즉 전자를 soft threshold 후자를 hard threshold 라고 생각하면 되고 결과적으로 retries2 를 넘겨야 연결이 끊어진다.

재전송 추적하기

TCP 재전송을 추적하려면 어떻게 하면 될까? 간단한건 재전송이 의심되는 곳에서 tcpdump 를 뜨면 되는데 이는 너무 많은 패킷이 잡혀서 오히려 더 힘들 수 있다.

이럴 때 사용하면 좋은 툴이 바로 tcpretrans 스크립트이다. tcpretrans 스크립트를 살펴보면 1 초에 한번씩 깨어나서 ftrace 를 통해 수집한 후 커널 함수 정보를 바탕으로 재전송이 일어났는지 확인한 후, /proc/net/tcp 의 내용을 파악해서 어떤 세션에서 재전송이 일어났는지 확인한다.

RTO_MIN 값이 200ms 이기 때문에 1 초의 인터벌 간격은 재전송되는 패킷을 놓칠수도 있다. 좀 더 정확하게 추적이 필요하다면 interval 값을 200ms 로 수정해서 실행시카자.

RTO_MIN 값 변경하기

RTO_MIN 값은 200ms 이기 때문에 이보다 빠른 내부 통신에서도 200ms 밑으로 내려갈 수 없다.

커널 소스를 보면 TCP_RTO_MAX, TCP_RTO_MIN 으로 define 한 걸 볼 수 있는데 RTO 의 최댓값은 120 초 이고 최솟값은 200ms 이다.

그리고 일반적으로 RTO 는 RTT 를 바탕으로 설정되기 때문에 세션마다 다를 수 있다.

  • RTO 의 값은 RTO_MIN + RTT_MAX 로 대충 추정하면 맞다. RTO 와 RTT 는 ss -i 옵션으로 볼 수 있다.

만약 RTT_MAX 가 12.25ms 이고 편차가 1.5ms 라면 RTO 값은 214 정도가 잡힐 것이다. 근데 대부분의 패킷이 12.25ms 에서 결정되는 상황이라면 RTO 의 214 값은 너무 크지 않은가? 패킷 15 개 정도를 주고 받을 수 있을 정도의 시간이다. 대략적으로 50 ms 만 설정해도 괜찮을 수 있다.

이 값을 바꿀려면 리눅스에서 ip route 라는 명령의 rto_min 옵션을 통해서 TCP_RTO_MIN 보다 작게 바꾸는게 가능하다. 세션별로 바꿀 수는 없고 하나의 네트워크 디바이스를 기준으로 바꾸는게 가능하다. 문법은 다음과 같다.

ip route change default via <GW> dev <DEVICE> rto_min 100ms

변경 하려면 먼저 ip route 명령을 통해서 현재 서버에 설정되어 있는 라우팅 정보를 보자.

ubuntu@ip-192-168-111-182:~$ ip route
default via 192.168.111.161 dev ens5 proto dhcp src 192.168.111.182 metric 100
192.168.111.160/27 dev ens5 proto kernel scope link src 192.168.111.182
192.168.111.161 dev ens5 proto dhcp scope link src 192.168.111.182 metric 100
  • 여기서는 기본적으로 ens5 라는 네트워크 디바이스의 192.168.111.161 주소를 통해서 나간다는 의미다.

이 정보를 통해서 RTO 값을 100ms 로 바꾸면 다음과 같다.

ip route change default via default dev ens5 rto_min 100ms****

애플리케이션 타임아웃

지금까지 리눅스 상에서 TCP 재전송이 언제 발생하는지 어떻게 횟수를 조절하는지에 대해서 알아봤는데 여기서는 TCP 재전송이 실제 어플리케이션에 끼치는 영향을 보겠다.

TCP 재전송이 일어나면 어플리케이션도 응답을 받지 못했기 때문에 타임아웃이 발생할 수 있다.

정확하게 말하면 애플리케이션 타임아웃 임계치를 몇 초로 설정했느냐에 따라서 타임아웃이 발생할 수도 발생하지 않을 수도 있다.

애플리케이션의 타임아웃은 크게 두 종류가 있다.

  • Connection Timeout: TCP Handshake 과정에서 재전송이 일어날 수 있는 경우에 발생하는 타임아웃이다. (최소 권장은 3 초 이상이다.)
  • Read Timeout: 맺어져 있는 세션을 통해서 데이터를 요청하는 과정에서 발생할 수 있는 타임 아웃이다. (최소 권장은 300ms 이상이다)
  • 여기서 권장은 기본값을 사용한다는 기준이다.

Connection TImeout 은 SYN 패킷과 SYN + ACK 패킷의 재전송을 고려해서 신경써야 하는 값이다. 이 패킷들은 RTO 값을 계산할 수 없기 때문에 재전송 타임은 1 초로 설정된다. 즉 한 번의 유실을 허용할 것이라면 1 초 이상으로 설정하면 되고 두 번의 유실까지도 허용하려면 3 초 이상으로 설정하면 된다. (타이머로 인해서 재전송이 일어나면 다음 재전송 시간은 2 배씩 늘어난다.)

Read Timeout 은 몇 초로 설정하는 것이 좋을까? 이미 맺어진 세션에서 패킷을 읽어오는 것이기 때문에 작을 것이고 한 번의 재전송 정도는 커버할 수 있는 값으로 무조건 설정해야 한다. (재전송은 꽤 일어날 수 있다.)

일반적으로 RTO_MIN 값이 200ms 이고 맺어져 있는 세션이 재전송 할 때 최소한 200ms 시간이 필요하기 때문에 200ms 보다 큰 값인 300ms 정도면 한 번의 재전송은 커버할 수 있다. 물론 RTT 가 길어서 RTO 가 200ms 이상이라면 300ms 보다 더 크게 설정해야 한다. 하지만 내부 통신일 경우에는 RTT 가 짧아서 200ms 보다 커지는 경우는 없다.