본문 바로가기
server/Cent_os

Time_Wait 원인과 tcp_tw_reuse 활용

Unix/Linux 서버를 관리 하다 보면 수많은 TIME_WAIT 가 발생 하는 경우가 있다. 
TIME_WAIT는 연결 종료시 발생 되는데,
이것이 정상인지,비정상인지 혹은 시스템 부하에 영향을 미치는지 잘 모르는 경우가 있어 이번 기회에 정리 하기로 하였다.
아래와 같이 User가 Web 서버에 http 문서를 요청 하는 과정을 tcpdump 를 통하여 덤프 한뒤 패킷을 확인 하였다.

 

해당 패킷을 wireshark를 이용하여 확인 하면 아래와같이 3way handshake 를 통하여 연결된 서버와 클라이언트가 연결 수립하여 데이터를 수신한뒤
클라이언트(192.168.100.102)는 더이상의 요청이 없음으로 연결을 종료 하기 위해서 웹서버로 FIN flag 로 된 tcp 세그먼트를 전송 하고 서버는 FIN 에
대한 응답과 자신도 종료 하기 위한 FIN flag 로 된 tcp 세그먼트를 전송 최종적으로 클라이언트가 FIN 에 대한 ACK 을 전송 하면서 연결이 종료 된다.
 
이후 클라이언트와 유저는 어떠한 일이 발생 될까 ?
클라이언트에서 netstat 으로 확인 하면 아래와 같이 45873 포트가 웹서버 80 포트로 TIME_WAIT 상태로 나온 것을 확인 할 수 있다.
 
[root@cytest2 ~]# netstat  -atunp | grep TIME  | grep 192.168.100.101
tcp        0      0 192.168.100.102:45873       192.168.100.101:80          TIME_WAIT   -       
하지만, 웹서버 즉 연결 종료를 받은 쪽에서는 TIME_WAIT 를 확인 할 수 없다. 즉, 연결을 먼저 종료 하기 위해 FIN flag 를 먼저 보낸 호스트에서 TIME_WAIT 를 
확인 할 수 있다. ( 웹서버등과 사용시 웹 어플리케이션이 was 같은 미들웨어와 연결 시 해당 웹서버가 client 입장이 되어서 time_wait 가 발생 한다.)
[root@cytest1 ~]# netstat  -atunp | grep TIME | grep 192.168.100.102
[root@cytest1 ~]# netstat  -atunp | grep TIME | grep 192.168.100.102
여기서 앞서 이야기한 먼저 FIN Flag 된 세그먼트를 보낸 실제 종료 하기 위한 host를 Active close 혹은 Active closer 라고 부른다.
Acive closer 와 반대로 연결 종료 를 수신한 호스트는 Passive close 혹은 Passive closer 로 불린다.
 => time wait 는 active close 상태에서 발생
 
time wait 발생 하는 원인을 보기 위하여 tcp 연결 종료 과정을 아래 그림과 같이 확인 할 수 있다.
TCP 연결 종료는 일반적으로 알고 있는 4way handshake 과정 혹인 연결 수립과 비슷한 3way handshake 과정이 있다.
아래 그림을 4way handshake 과정을 보여 준것이다.
1.Client 가 종료를 하기 위해서 Fin 세그먼트를 보내고 client 는 FIN의 응답이 올때 까지 FIN_WAIT-1 상태로 변경 된다.
2.FIN을 수신한 server 는 FIN 에 대한 응답을 해준다. 이때 연결 시작 처럼 ACK의 SEQ 번호는  FIN+1 SEQ 로 세팅 되어 보내진다.
ACK 를 보낸 서버는 CLOSE_WAIT 상태로 변경 된다.
(이때 상태를 Half close=반종료 상태를 수락 한 상태로 client 는 서버로 전송을 종료 한다. 그렇지만 서버는 여전히 데이터를 전송 할 수 있으며
서버가 처리된 모든 데이터를 전송 한 후에 다음 단계로 넘어 간다.
 
3. CLOSE_WAIT 상태의 서버는 자신의 연결을 종료할 준비가 되었다면, FIN  을 보내고 client 는 TIME_WAIT 상태로 변경 된다.
4. TIME_WAIT 의 상태의 client 는 FIN 의 응답으로 ACK 을 서버로 보내서 4way handshake 과정이 종료 된다.
이때 TIME_WAIT 는 2MSL(Maximum Segment Lifetime) timer 로  아래 그림과 같이 rfc793 에 정의 되어 2분이여야  하지만 실제로 리눅스 상에서 커널 소스로 1분으로 정해져 있으며 수정 불가능 하다.
 
 
 
그렇다면 1분간의 TIME_WAIT 가 필요한 이유가 무엇일까 
만약 ,마지막 단계의 FIN 의 대한 ACK 응답 미수신시 서버는 자신의 FIN 세그먼트가 유실 된것으로 보고  FIN을 보내게 된다.
 이때 TIME_WAIT 상태가 지나거나, 없어서 클라이언트가 CLOSE 상태일 경우 
클라이언트는 서버로 부터 ACK 을 못 보내서 서버는 제대로 연결을 종료 할 수 없다. 그렇기 때문에 TIME WAIT 시간 동안 서버로부터 ACK 을 다시 보내서 
정상적인 종료가 가능 하도록 해준다.
 또한 , TIME WAIT 동안 중복되는 소켓 주소가 발생 할 경우 새로운 연결로 간주 되기때문에 TIME_WAIT 기간 동안 해당 로컬 포트를 점유하여 사용 할 수 없는 용도로 필요 하다.
그렇기때문에 TIME_WAIT 는 TCP 연결 종료시 반드시 필요한 과정이다.
하지만, TIME_WAIT 가 과도하게 발생 된다고 하면 로컬 포트의 고갈을 유발 할 수 있다.
아래와 같이 기본적으로 linux kernel  2.6.32 기준  약 28000 개의 outgining port 를 사용 할 수 있도록 되어 있다.
[root@cytest2 ~]#  cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999
port 고갈을 테스트 하기 위해서 1024 포트 1개만 사용 할 수 있도록 설정 한다.
[root@cytest2 ~]#  sysctl -w net.ipv4.ip_local_port_range="1024 1024"
net.ipv4.ip_local_port_range = 1024 1024
처음에는 정상적으로 200/OK 를 수신 하지만 이후 외부 포트로 설정할 포트가 부족 하기 때문에 연결이 실패 된다.
[root@cytest2 ~]# curl -I http://192.168.100.101
HTTP/1.1 200 OK
[root@cytest2 ~]# curl -I http://192.168.100.101
curl: (7) Failed to connect to 192.168.100.101: Cannot assign requested address
[root@cytest2 ~]# curl -I http://192.168.100.101
curl: (7) Failed to connect to 192.168.100.101: Cannot assign requested address
이때 TIME_WAIT 를 확인 하면 1024 설정한 포트가 점유 하고 있는 것을 확인 할 수 있다.              
[root@cytest2 ~]# netstat  -atunp | grep TIME_WAIT   
tcp        0      0 192.168.100.102:1024        192.168.100.101:80          TIME_WAIT   -     
 
이런 경우가 발생 하면 아래와 같이 기존의 약 28000개의 사용 가능한 포트를 최대한 늘리는 것도 한 가지 방법 이다.
[root@cytest2 ~]# sysctl -w net.ipv4.ip_local_port_range="1024 65535"
net.ipv4.ip_local_port_range = 1024 65535
하지만 근본적인 단순히 늘리는 solution 으로 해결이 안될 경우가 많다. 이때 tcp_tw_reuse 옵션을 활성화 하여 time_wait 되는 port 를 재사용할 수 있다.
 tcp_tw_reuse 는 TIME_WAIT상태의 소켓중 timestamp 보다 작은 값의 timestamp 를 갖는 소켓을 재사용 한다.
이전과 같은 환경에서 reuse 옵션 활성화 여부를 확인 한 후 1로 설정 하여 reuse 옵션을 사용 한다.
[root@cytest2 ~]# sysctl -w net.ipv4.ip_local_port_range="1024 1024"
net.ipv4.ip_local_port_range = 1024 1024
[root@cytest2 ~]# sysctl  -a | grep reuse
net.ipv4.tcp_tw_reuse = 0
[root@cytest2 ~]# sysctl  -w net.ipv4.tcp_tw_reuse="1"
net.ipv4.tcp_tw_reuse = 1
 
동일하게 테스트 하면 재사용 되는 것을 확인 할 수 있다.
(이때 리눅스 timestamp 기준인 초단위보다 빠르게 테스트 하면 timestamp 가 동일 한 것으로 보고 재사용 되지 않는다)
[root@cytest2 ~]# curl -I http://192.168.100.101
HTTP/1.1 200 OK
Server: nginx/1.10.2
[root@cytest2 ~]# curl -I http://192.168.100.101
HTTP/1.1 200 OK
 
그렇기 때문에 reuse 옵션을 사용 하기 위해서는 반드시 timestamps 옵션도 활성화 되어 있어야 한다.(timestamps 옵션은 양방향 설정 되어 있어야 하며,
기본적으로 활성화 되어 있음) 
[root@cytest2 ~]# sysctl  -w net.ipv4.tcp_tw_reuse="1"
net.ipv4.tcp_tw_reuse = 1
[root@cytest2 ~]# sysctl -w net.ipv4.tcp_timestamps="1"
net.ipv4.tcp_timestamps = 1
 
References
http://meetup.toast.com/posts/55
https://brunch.co.kr/@alden/3
TCP/IP 완벽 가이드 Charles M. Kozierok  (2007.01.25)

 

반응형