일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 데브옵스
- 클라우드
- 네트워크
- 리눅스
- centOS7
- Python
- boj
- NGINX
- devops
- os
- 컨테이너
- AWS
- 운영체제
- 도커 명령어
- linux
- docker
- kubernetes
- centOS
- Swift
- 도커 이미지
- 프로세스
- 인프라
- swift 클로저
- 도커
- C++
- 도커 컨테이너
- 쿠버네티스
- 부스트코스
- k8s
- ios
- Today
- Total
귀염둥이의 메모
TIME_WAIT 소켓? (tcp_tw_reuse, tcp_tw_recycle) 본문
TL;DR
- TIME_WAIT은 정상적인 TCP 연결 해제를 위해 반드시 필요.
- TIME_WAIT 상태는 비정상적인 통신을 대비하기 위해 존재한다.
- TIME_WAIT 소켓은 Active Close 쪽에서 발생한다. (Client와 Server 모두 발생 가능)
- Client 입장에서 TIME_WAIT 소켓은 tw_reuse 파라미터를 통해서 재사용 가능. (로컬 포트 고갈 문제 발생 X)
- keepalive 기능으로 handshake 과정과 TIME_WAIT 소켓 최소화 ➡️ 서비스 응답 속도 향상 가능.
4 way handshake
- Active Close : 연결을 먼저 끊는 쪽 (Initiator)
- Passive Close : 그 반대 (Receiver)
- close()를 실행한 Active가 먼저 FIN을 보내고 FIN_WAIT_1 상태로 대기
- FIN을 수신한 Passive는 CLOSE_WAIT 상태로 변경하고
- ACK 응답과 동시에 포트에 연결되어 있는 애플리케이션에 close() 요청
- ACK를 수신한 Active는 FIN_WAIT_2 상태로 변경
- close() 요청을 받은 Passive 애플리케이션은 종료 프로세스 진행 후 FIN을 송신, 동시에 LASK_ACK 상태로 변경
- FIN을 받은 Active는 ACK을 전송하고 TIME_WAIT 상태로 변경
- TIME_WAIT에서 일정 시간이 지나면 CLOSED // ACK을 받은 Passive도 포트를 CLOSED
CLOSE_WAIT
- close() 명령을 기다리는 상태
- FIN 요청을 수신한 Passive는 CLOSE_WAIT 상태
- TCP 포트를 사용 중인 애플리케이션에 종료 명령을 내리고 close() 명령을 실행할 때까지 대기
FIN_WAIT_1
- Active가 FIN을 보낸 후의 상태
- ACK 응답은 시스템에서 자동으로 보내기 때문에 이 상태는 보기 힘들다
FIN_WAIT_2
- Active가 Passive로 부터 FIN을 기다리는 상태
- 일정 시간 FIN이 오지 않으면, 자동으로 TIME_WAIT으로 넘어감
- net.ipv4.tcp_fin_timeout 파라미터로 대기 시간 변경 가능
TIME_WAIT
- Active가 FIN_WAIT_2에서 FIN을 받고, 자동으로 ACK를 보내면 TIME_WAIT 상태로 변함
- 연결 종료 요청을 먼저 하면 TIME_WAIT 상태를 거친다
- 네트워크상에서 지연되거나 상실되어 도착하지 못한 패킷을 처리하기 위해 존재
- Passive가 LAST_ACK을 받지 못할 경우 Active에게 FIN을 재전송 하는데, Active가 이미 종료했다면?
- 정상적인 종료가 이루어지지 못함!
- TIME_WAIT 상태에서 MSL(Maximum Segment Lifetime)의 2배의 시간만큼 기다리며 해당 포트를 점유
- MSL : 인터네트워크에서 TCP 패킷이 존재할 수 있는 시간
- include/net/tcp.h 파일에 명시되어 있음
TIME_WAIT 소켓
- TIME_WAIT 소켓은 연결을 먼저 끊은 Active Close 쪽에 생성된다.
- Client와 Server 모두 TIME_WAIT 소켓이 생길 수 있다.
기본적으로 TIME_WAIT 소켓은 타이머가 종료되어 커널로 돌아갈 때까지 사용할 수 없다.
프로세스가 소켓 생성을 요청할 때 커널은 net.ipv4.ip_local_port_range에 정의된 값들 중 하나를 할당 한다.
만약 모든 로컬 포트가 TIME_WAIT 상태라면 할당할 수 있는 로컬 포트가 없기 때문에 애플리케이션 타임아웃이 발생할 수 있다.
TIME_WAIT 소켓이 많아지면 로컬 포트 고갈로 애플리케이션 타임아웃이 발생할 수 있다.
TIME_WAIT 소켓은 연결 끊기로 인해 발생하는데, 지속적으로 통신량이 많을 때 TCP 연결/끊기를 반복하면 전체적인 서비스의 응답 속도 저하를 야기할 수 있다.
Client 관점의 TIME_WAIT
대부분 시스템은 DB 또는 외부 서비스와의 연동을 위해 API를 호출하기도 한다. 이 과정에서 서비스를 제공하는 Server는 연동된 DB에 대해서는 Client 입장이 된다. Server는 Client 역할을 할 수 있기 때문에 Client 입장의 TIME_WAIT 소켓이 발생할 수 있다. Client 입장의 TIME_WAIT의 문제점은 로컬 포트 고갈 현상이다.
[테스트] net.ipv4.tcp_tw_reuse
- kai-test (Client)
- 로컬 포트 1개 사용 (32768 고정)
- TIME_WAIT 소켓 재사용 X (net.ipv4.tcp_tw_resue = 0 적용)
- kai-test-server (Server)
- nginx 실행
Client : TIME_WAIT 재사용 X
net.ipv4.tcp_tw_resue = 0
- 처음 요청에서 유일한 로컬 포트 32768을 사용하였고, 32768을 사용한 소켓은 TIME_WAIT 상태
- 재사용 옵션이 꺼져있기 때문에 다시 사용할 수 없음
- 할당 가능한 로컬 포트가 더 이상 없기 때문에 요청을 처리 할 수 없음
Client : TIME_WAIT 재사용 O
net.ipv4.tcp_tw_resue = 1
- 재사용을 허용했기 때문에 반복적으로 요청을 처리 한다.
- 로컬포트를 재사용
Server 관점의 TIME_WAIT
Server 관점에서는 소켓을 열고 요청을 받는 입장이다. 로컬 포트 고갈 문제는 일어나지 않지만, TIME_WAIT 소켓이 많으면 불필요한 연결/끊기 과정이 반복된다.
Client 설정
- Client 로컬 포트 범위를 다시 정상적으로 돌리기
Server 설정 - nginx
- Server의 /etc/nginx/nginx.conf 파일에서 keepalive_timeout을 0으로 설정하여 keepalive를 끄기
- Server 80포트에 TIME_WAIT 소켓들을 확인할 수 있고
- 응답 헤더에서 Connection: close를 통해서 Server가 먼저 연결을 끊었음을 알 수 있음
Server 입장에서 TIME_WAIT 줄이기?
net.ipv4.tcp_tw_recycle 파라미터로 Server 입장에서 TIME_WAIT 소켓을 재활용할 수 있다.
- 2017년 커널 4.12에서 제거된 파라미터 (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4396e46187ca5070219b81773c4e65088dac50cc)
tcp_tw_recycle 옵션을 활성화 하면 TIME_WAIT 소켓의 타임아웃 시간이 RTO 기반의 값으로 변경된다. 또한, 해당 소켓으로부터 가장 마지막에 들어온 timestamp를 저장한다.
TIME_WAIT 소켓을 재사용할 때 timestamp 값을 비교하여 재사용 여부를 결정한다. RTO 기반 값으로 변경되기 때문에 TIME_WAIT 소켓이 매우 빠르게 사라지는데, 서비스에 문제가 생길 수 있다.
여러 Client가 NAT 환경을 사용할 경우
일부 Client로 부터 SYN 패킷이 유실될 수 있다.
예를 들어 두개의 클라이언트 (C1, C2) 가 동일한 NAT를 사용한다면 서버 입장에서는 같은 주소를 사용하는 것으로 보일 것이다.
그런데 패킷의 timestamp 는 클라이언트 마다 다른 값을 가질 수 있기 때문에, 마지막 C1 FIN 에 기록한 timestamp 값보다 새롭게 연결을 요청하는 SYN 의 timestamp 값이 작을 수 있다.
서버 입장에서는 같은 IP 를 가진 목적지에서 기존보다 작은 timestamp 값으로 요청이 들어와서 잘못된 요청으로 판단하고 패킷을 처리하지 않고 버리게 된다.
클라이언트의 요청을 직접 받는 웹 서버에서 주로 발생할 수 있기 때문에 웹 서버에서는 tw_recycle 을 절대로 활성화 해서는 안된다.
TIME_WAIT과 keepalive
- keepalive를 이용하여 TIME_WAIT 소켓을 줄일 수 있음
- 세션을 유지하기 때문에 handshake 과정이 최소화되고 서비스 응답 속도가 향상 가능
References
DevOps와 SE를 위한 리눅스 커널 이야기 - http://www.yes24.com/Product/Goods/44376723
'Infra & Devops > Linux' 카테고리의 다른 글
[Linux] /proc 디렉터리 (0) | 2021.12.16 |
---|---|
[Linux] 시스템 콜 호출 확인 (strace) (0) | 2021.12.15 |
[Linux] 리눅스 파일시스템 (디스크 기반, 네트워크 기반) (1) | 2021.10.28 |
[Linux / CentOS 7] 스왑 파티션(Swap Partition) 지정하기, 가상 메모리(Virtual Memory) (0) | 2021.08.29 |
[Linux / CentOS 7] 파티션 생성, 파일 시스템, 마운트, 영구 마운트 (0) | 2021.08.28 |