Study/Linux

[Linux]TCP 재전송

seomj 2024. 4. 25. 22:00

TCP 재전송이 일어나는 과정, 이로 인해 발생할 수 있는 애플리케이션 타임아웃


TCP 재전송과 RTO

TCP는 신뢰성 있는 연결이다. 데이터를 주고 받는 두 종단 간에 데이터를 주고 받음이 확실해야 한다.

패킷을 보낸 후 ACK를 받지 못하면 패킷이 손실되었다고 판단하고 재전송한다. 이는 애플리케이션 입장에서 요청에 대한 응답을 받는 시간이 늘어난다.

 

RTO(Retransmission Timeout)는 얼마나 기다려야 하는지에 대한 값으로, RTO 안에 ACK를 받지 못하면 보내는 쪽에서 재전송을 진행한다.

  • 일반적인 RTO는 RTT(Round Trip Time, 두 종단 간 패킷 전송에 필요한 시간)를 기준으로 설정된다.
  • InitRTO는 TCP Handshake가 일어나는 첫 번째 SYN 패킷에 대한 RTO를 의미한다. 맨 처음 연결을 맺을 때는 두 종단 간 RTT를 알 수 없기 때문에 임의로 설정한 값으로 RTO를 계산한다.

 

ss 명령 - 현재 설정되어 있는 세션의 RTO 값

root@worker1:~# ss -i
Netid   State      Recv-Q   Send-Q                              Local Address:Port                                          Peer Address:Port                                     Process  
...
tcp     SYN-SENT   0        1                                   211.183.3.101:55660                                        211.183.3.100:6443                                     
	 cubic rto:2000 backoff:1 mss:524 pmtu:1500 rcvmss:88 advmss:1460 cwnd:1 ssthresh:7 segs_out:1 lastsnd:67538112 lastrcv:67538112 lastack:67538112 app_limited unacked:1 lost:1 rcv_ssthresh:64240

rto가 2000으로 설정되어 있다. 즉, 2000ms 동안 ACK를 받지 못하면 패킷을 재전송하게 된다.

 

RTO 값은 초기값을 기준으로 2배씩 증가한다. 처음 값이 200ms였다면 그 다음은 400, 800, 1600…으로 점점 커진다.

 

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

net.ipv4.tcp_syn_retries

SYN에 대한 재시도 횟수

 

net.ipv4.tcp_synack_retries

SYN+ACK에 대한 재시도 횟수

SYN을 받은 후 그에 대해 SYN+ACK로 응답한 소켓의 상태는 SYN_RECV가 된다. 하지만 SYN+ACK에 대해 정상적인 응답을 받지 못하면 재전송한다. 이때 정상적인 상황이라면 상대방 역시 본인의 SYN에 대한 SYN+ACK를 받지 못했기 때문에 SYN 자체가 재전송되지만, 비정상적인 경우(DDOS 등)에는 SYN 재전송이 일어나지 않는다. 이때 해당 파라미터 값이 없다면 SYN_RECV 상태의 소켓이 계속해서 유지되고 결국 서버의 리소스 고갈이 일어난다.

 

net.ipv4.tcp_orphan_retries

orphan socket이라 불리는 상태의 소켓들에 대한 재전송 횟수를 결정

*orphan socket?

특정 프로세스에 할당되지 않고 커널에 귀속되어 정리되기를 기다리는 소켓 중에 FIN_WAIT1 상태의 소켓

FIN을 보내고 난 후에 FIN_WAIT1이 되고 이후 자신이 보내는 패킷이 아닌 상대방으로부터 받는 패킷만 있다. 재전송은 내가 보내는 패킷에 대해 재전송하는 것이다. 그렇기 때문에 FIN_WAIT2와 TIME_WAIT는 해당되지 않는다.

 

net.ipv4.tcp_retries1 / net.ipv4.tcp_retries2

TCP는 재전송을 하기 위한 임계치 값으로 두 개의 값을 가지고 있다.

첫 번째 값은 IP 레이어에 네트워크가 잘못 되었는지를 확인하도록 사인을 보내는 기준이 되며, 두 번째 값은 더 이상 통신을 할 수 없다고 판단하는 기준이 된다.

soft threshold, hard threshold

결과적으로는 두 번째 값에 정의된 횟수만큼을 넘겨야 실제 연결이 끊어진다.

 

재전송 추적하기

tcpreturans 스크립트

1초에 한 번씩 깨어나서 ftrace를 통해 수집한 커널 함수 정보를 바탕으로 재전송이 일어났는지 아닌지를 파악한 후, /proc/net/tcp의 내용을 파싱해서 어떤 세션에서 재전송이 일어났는지를 출력한다.

 

 

RTO_MIN 값 변경하기

RTO_MIN 값이 200ms이기 때문에 아무리 RTT가 작은, 빠른 내부 통신의 경우에도 RTO 값은 200ms 밑으로 내려갈 수 없다.

RTO가 RTT를 기반으로 계산되지만 TCP_RTO_MIN이 200이기 때문에 무조건 200보다는 커진다.

 

ip route 명령의 rto_min 옵션을 통해서 RTO의 최솟값을 TCP_RTO_MIN보다 작게 바꿔줄 수 있다. 하나의 네트워크 디바이스를 기준으로 바꿀 수 있다.

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

 

 

애플리케이션 타임아웃

TCP 재전송이 실제 애플리케이션에 끼치는 영향.

 

요청한 내용을 전달받지 못했기 때문에 타임아웃이 발생한다. 하지만 설정해 둔 임계치에 따라 발생할 수도, 발생하지 않을 수도 있다.

2가지 종류의 타임아웃이 있다.

  • Connection Timeout: TCP Handshake 과정에서 재전송이 일어날 경우 발생
  • Read Timeout: 맺어져 있는 세션을 통해서 데이터를 요청하는 과정에서 발생

 

Connection Timeout

TCP Handshake 과정에서 실패한 것이다. 이는 SYN 패킷 혹은 SYN+ACK 패킷 중 하나가 유실되어서 재전송이 일어날 때 발생할 수 있다.

왜 마지막 ACK는 포함되지 않지?

→ SYN과 SYN+ACK는 RTO를 계산하기 위한 RTT 값을 구할 수가 없어 1초로 설정되어 있다. 하지만 이를 주고 받은 후에는 정보가 생기기 때문에 RTO 계산이 가능하다.

3초로 설정하는 것이 좋다. 내가 보낸 SYN 재전송(1초)에 상대방의 SYN+ACK 재전송(1초)을 더한 2초보다 큰 값으로 설정해야 불필요한 타임아웃 에러 메시지를 줄일 수 있다.

 

Read Timeout

이미 연결되어 있는 세션을 통해서 데이터를 읽으려고 하다가 타임아웃이 발생했다는 것을 의미한다. 주로 커넥션 풀 방식에서 발생하는 타임아웃이다.

일반적으로 300ms로 설정한다. RTO_MIN 값이 200ms이고 맺어져 있는 세션이 재전송할 때 최소한 200ms의 시간이 필요하기 때문이다.

 

두 가지 모두 설정할 때 한 번의 재전송 정도는 커버할 수 있는 값으로 설정해야 한다.

 

정리

  • TCP 재전송은 RTO를 기준으로 발생하며 RTO 동안 응답을 받지 못하면 재전송이 발생한다.
  • RTO는 RTT를 기반으로 동적으로 생성된다. TCP Handshake 구간에서 설정되는 RTO는 InitRTO라 칭하며 리눅스에서는 1초가 기본이다.
  • RTO는 초기 설정 값에서 2배수씩 증가한다.
  • net.ipv4.tcp_syn_retries, net.ipv4.tcp_synack_retries는 각각 SYN 패킷과 SYN+ACK 패킷에 대한 재전송 횟수를 결정하며, TCP Handshake할 때 적용되는 값이다.
  • net.ipv4.tcp_orphan_retries는 FIN_WAIT1 상태에 빠지게 되는 FIN 패킷에 대한 재전송 횟수를 결정하며, 0으로 설정한다고 해서 Disable이 되지는 않는다.
  • net.ipv4.tcp_retries1, net.ipv4.tcp_retries2는 각각 soft, hard 임계치라고 생각할 수 있다. 실제 연결은 net.ipv4.tcp_retries2에 설정된 횟수를 넘겼을 때 종료된다.
  • 최소한 한 번의 재전송은 견딜 수 있도록 애플리케이션에서의 타임 아웃 중 Connection Timeout은 3초, Read Timeout은 300ms 이상으로 설정하는 것이 좋다.

 

 

참고

DevOps와 SE를 위한 리눅스 커널 이야기

'Study > Linux' 카테고리의 다른 글

[Linux]TCP Keepalive  (0) 2024.04.24
[Linux]TIME_WAIT 소켓  (0) 2024.04.22
[Linux]NUMA  (1) 2024.04.18
[Linux]swap  (0) 2024.04.14
[Linux]free 명령과 메모리  (0) 2024.04.10