ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Linux TIME_WAIT 소켓 정리
    커널(Kernel) 2022. 12. 11. 16:50

     

    환경 정보

    OS: ubuntu18.04 

     

    이번 포스팅은 4-way handshake에서 TIME_WAIT 소켓을 어떻게 관리하면 좋을지에 대한 내용들을 다룬다. 

     

    TCP 통신 과정

    tcpdump를 통해서 client(192.168.9.194) => nginx(192.168.9.75:80)로 가는 패킷을 dump한다. 

    tcpdump -i any port 80 -A -nn -w nginx.pcap

     

    curl 192.168.9.75

     

    Wireshark를 이용하면, 방금 nginx로 보낸 요청이 tcp 3-way handshake, http get 요청, 4-way handshake라는 단계로 이루어져 있음을 확인할 수 있다. 

    일단 대략적인 흐름은 다음과 같다. 

     

    3-way handshake

    1. [클라이언트 => nginx] SYN 패킷 전송

    2. [nginx => 클라이언트] 응답으로 SYN, ACK 패킷 전송

    3. [클라이언트 => nginx] 응답으로 ACK 패킷 전송

     

    HTTP GET

    4. [클라이언트 => nginx] HTTP 요청 전송 

     

    4-way handshake

    5. [nginx => 클라이언트] 연결을 끊기 위해 FIN 패킷 전송

    6. [클라이언트 => nginx] 응답으로 ACK 패킷 전송

    7. [클라이언트 => nginx] 자신이 사용한 소켓을 정리 후, FIN 패킷 전송

    8. [nginx => 클라이언트] 응답으로 ACK 패킷 전송

     

     

    TIME_WAIT 소켓 

    TIME_WAIT 소켓은 커넥션 종료 이후 발생할 수 있는 문제점을 방지하기 위해서 존재한다. 

    예를 들면, 네트워크 장애로 인해 연결을 끊는 과정에서 패킷이 유실될 수 있는데 

    이러한 문제 때문에 일정시간 동안 TIME_WAIT 소켓을 만들어 재전송을 통해서 문제를 해결할 수 있다.  

     

    TIME_WAIT 소켓은 active closer(연결을 먼저 끊는)에서 생성된다.  

    클라이언트, 서버 모두 연결을 먼저 끊을 수 있기 때문에 클라이언트와 서버에서 TIME_WAIT 소켓이 만들어질 수 있다.  

     

     

     

     

     

    TIME_WAIT 소켓 확인하기 client (192.168.9.194) => server (192.168.9.75:80)

     

    telnet으로 접속했을 때

     

    telnet 연결을 끊었을 때 

    위의 timewait 소켓은 약 54초 뒤에 사라진다. 

     

    클라이언트 입장에서의 TIME_WAIT 소켓

    TIME_WAIT 소켓은 앞서 클라이언트와 서버 모두에서 생성될 수 있다고 했다.

     

    그렇다면 클라이언트에서 TIME_WAIT 소켓이 생기는 경우는 어떤 경우가 될 수 있을까

     

    예를 들어보면, 백엔드 api 서버가 db와 커넥션을 맺고 있을 때, 백엔드 api 서버는 db에 대한 클라이언트가 된다.

    이때, 백엔드 api 서버가 db와의 커넥션을 먼저 끊는다면, 백엔드 api 서버에서 TIME_WAIT 소켓이 생길 수 있다. 

     

    클라이언트 입장에서 TIME_WAIT 소켓이 많아지게 되면, 로컬 포트가 고갈되어 애플리케이션 타임 아웃이 발생할 수 있다. 

     

    로컬 포트 고갈을 방지하기 위해 다음과 같은 방법을 사용할 수 있다. 

     

    1.  net.ipv4.tcp_tw_reuse 커널 파라미터 사용하기 

    일반적으로 TIME_WAIT 상태의 소켓은 다시 사용할 수 없는데 

    net.ipv4.tcp_tw_reuse 커널 파라미터를 이용하면 TIME_WAIT 소켓을 재사용할 수 있다. 

    net.ipv4.tcp_tw_reuse를 1로 설정하면 TIME_WAIT 소켓을 재사용해서 외부로 요청을 보낼 수 있게 해 준다.

     

     

    2. connection pool 방식

    커넥션을 맺을 때마다 TCP 연결/끊기를 하는 방식은 애플리케이션 응답 시간을 느려지게 하고, 커널 개입을 늘리면서 리소스 낭비를 발생시킨다. 

    커넥션 풀 방식은 연결을 끊지 않고, 미리 소켓을 열어둔다.

    미리 열어둔 소켓을 통해 요청을 처리하게 되고 보다 애플리케이션의 응답속도를 높일 수 있다.  

     

    또한 커넥션 풀 방식을 통해서 로컬 포트의 범위를 지정함으로써 무분별한 사용을 막을 수 있다. 

     

     

    참고 1. 클라이언트가 소켓을 할당받는 과정

    1. [애플리케이션 => 커널] 서버와 통신할 때, 사용할 포트를 요청

    2. [커널 => 애플리케이션] 가용한 로컬 포트를 찾아서 할당

    3. [애플리케이션 => 커널] 목적지 IP, 목적지 포트를 주고 할당받은 로컬 포트로 소켓 생성 요청

    [출발지 포트, 출발지 IP, 목적지 포트, 목적지 IP] 쌍을 가진 소켓은 커널에 유일하게 존재한다.    

    4. [커널 => 애플리케이션] 소켓에 접근할 때 사용할 FD 제공 

     

     

    참고 2. net.ipv4.ip_local_port_range 커널 파라미터

    net.ipv4.ip_local_port_range 커널 파라미터를 통해 로컬 포트의 범위를 지정할 수 있다.

     

     

    서버 입장에서의 TIME_WAIT 소켓

    서버는 소켓을 열어놓고 요청을 받아들이는 입장이기 때문에 로컬 포트 고갈과 같은 문제는 일어나지 않는다. 

    하지만 클라이언트와 마찬가지로 TIME_WAIT 소켓이 많으면 불필요한 연결 끊기/맺기가 반복될 수 있다. 

     

    서버 입장에서는 다음 방법들을 통해 TIME_WAIT 소켓을 줄인다. 

     

     

    1. net.ipv4.tw_recycle 커널 파라미터(추천하지 않음)

    서버 입장에서 TIME_WAIT 상태의 소켓을 빠르게 회수하고 재활용할 수 있게 해주는 커널 파라미터다. 

    이 커널 파라미터를 사용하면 TIME_WAIT 소켓을 없애는 것이 아니라

    TIME_WAIT의 timeout을 ms 단위로 변경하여 TIME_WAIT 소켓이 생겼다가 바로 사라진다. 

     

    하지만 이 방식은 다음과 같은 이유 때문에 추천하지 않는다고 한다. 

    두 클라이언트가 동일한 nat를 사용할 경우, 서버 입장에서는 같은 IP를 달고 오기 때문에 출발지 포트로 구분해야 한다. 

    한 클라이언트의 연결이 끊어진 후, 다른 클라이언트 간의 timestamp가 끊어진 클라이언트의 timestamp보다 작게 되면 

    패킷을 드랍하여 문제가 발생할 수 있기 때문이다. 

     

     

    2. Keepalive

    nginx 서버의 keepalive_timeout을 0으로 설정한 후에 telnet으로 요청을 1개 보냈을 때  

    nginx는 1개의 요청에 대한 응답을 보낸 후, FIN 패킷을 내려준다.

     

    클라이언트가 한 개의 요청을 보냈을 때 위와 같이 HTTP 헤더에 connection: close라는 값과 함께 연결이 종료된다.

    즉, 만약 10번 요청을 하게 된다면 TIME_WAIT 소켓이 10개 생성된다. 

     

    keepalive를 이용하면 하나의 세션을 연결해놓은 후, 그 연결을 유지하면서 여러 개의 요청을 처리하는 것이 가능해진다. 

    여전히 TIME_WAIT 소켓이 생기기는 하지만 리소스 적인 측면에서 경제적이고, 애플리케이션 응답 속도를 높일 수 있다. 

     

    nginx keepalive_timeout=60인 경우, 클라이언트는 하나의 세션에서 요청을 여러 번 보낼 수 있다. 

    60초 동안 요청이 발생하지 않는다면, nginx는 연결을 끊는다.  

     

     

    즉, keepalive를 이용해서 하나의 세션에 10번 요청을 하게 되었을 때, 서버에서는 1개의 TIME_WAIT 만 남길 수 있다. 

     

    정리 

     

    1. 클라이언트 입장에서 TIME_WAIT 소켓 발생하는 경우

    문제: 로컬 포트가 부족할 수 있다. 

    해결: reuse 커널 파라미터를 통해서 소켓을 재사용하는 방법이 있지만 TCP 맺음/끊기는 반복적으로 일어날 수 있다. 따라서 커넥션 풀을 사용해서 TIME_WAIT 줄이자. 

     

    2. 서버 입장에서 TIME_WAIT 소켓 발생

    문제: 로컬 포트 문제는 없지만, 불필요한 연결/끊기로 인한 많은 리소스 사용한다. 

    해결: recycle 파라미터를 사용할 수 있지만, 권하지 않는 방법이며 keepalive를 통해 TIME_WAIT를 줄이자. 

     

    Reference

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

    반응형

    댓글

Designed by Tistory.