FIN 은 "내가 보낼 것은 다 보냈다" 는 통보다. 상대는 아직 보낼 것이 남아 있으면 계속 보낼 수 있고, 다 보낸 뒤 자기 FIN 을 보낸다. 양쪽이 각각 FIN 을 보내고 확인받아야 연결이 닫힌다.
RST 는 "이 연결은 없다, 즉시 버려라" 는 통보다. 대기도 확인도 없다. 버퍼에 남아 있던 데이터는 버려진다.
정상 종료 (FIN) 강제 종료 (RST)
A -> FIN -> B A -> RST -> B
A <- ACK <- B (끝)
A <- FIN <- B
A -> ACK -> B
A: TIME_WAIT 유지
Connection refused 의 정체다.SO_LINGER 를 0 으로 두고 소켓을 닫았다.RST 를 받은 쪽 애플리케이션은 Connection reset by peer 를 본다.
플래그가 찍히도록 잡는다.
sudo tcpdump -nn -i any 'tcp port 1521' -c 200
출력의 대괄호 안이 플래그다.
| 표기 | 뜻 |
|---|---|
[S] |
SYN |
[S.] |
SYN, ACK |
[.] |
ACK |
[P.] |
PSH, ACK — 데이터 전송 |
[F.] |
FIN, ACK — 정상 종료 시작 |
[R], [R.] |
RST — 강제 종료 |
RST 만 골라 보려면 필터로 거른다.
sudo tcpdump -nn -i any 'tcp[tcpflags] & tcp-rst != 0'
FIN 과 RST 를 함께 본다.
sudo tcpdump -nn -i any 'tcp[tcpflags] & (tcp-fin|tcp-rst) != 0'
나중에 분석하려면 파일로 남긴다. 패킷을 잘라 받으면 내용 분석이 안 되므로 -s 0 을 준다.
sudo tcpdump -nn -i any -s 0 -w /var/tmp/capture.pcap 'host 10.0.0.20 and port 1521'
tcpdump -nn -r /var/tmp/capture.pcap -A | less
패킷 내용을 16진수와 문자로 함께 보려면 -X 를 쓴다.
sudo tcpdump -nn -i any -X 'port 502' -c 20
패킷을 잡지 않고도 현재 상태는 볼 수 있다.
ss -tan state all '( dport = :1521 or sport = :1521 )'
TIME_WAIT 이 많으면 정상 종료가 잘 일어나고 있다는 뜻이고, 연결이 자꾸 끊기는데 TIME_WAIT 이 거의 없으면 RST 로 끝나고 있다는 신호다.
RST 통계를 본다.
netstat -s | grep -i reset
데이터베이스 커넥션 풀은 연결을 만들어 두고 오래 재사용한다. 이때 중간 장비가 유휴 연결을 조용히 버리면 다음과 같은 일이 생긴다.
풀: 연결 유지 (쓰지 않음)
방화벽: 유휴 시간 초과 -> 연결 상태 삭제
애플리케이션: 풀에서 연결을 꺼내 쿼리 전송
방화벽: 모르는 연결 -> RST
애플리케이션: Connection reset by peer
풀 입장에서는 멀쩡해 보이던 연결이 쓰는 순간 깨진다. 그래서 첫 쿼리만 실패하고 다시 시도하면 되는 증상으로 나타난다.
대응은 세 가지를 함께 쓴다.
connectionTestQuery, DBCP 의 validationQuery 같은 설정이다. 죽은 연결이면 버리고 새로 만든다.sudo tee /etc/sysctl.d/90-tcp-keepalive.conf > /dev/null <<'SYSCTL'
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
SYSCTL
sudo sysctl --system
정상 종료 과정의 FIN 은 오류가 아니다. 유휴 연결을 서버가 정리하는 것도 정상이다. 문제는 애플리케이션이 그 연결을 아직 쓸 수 있다고 믿고 있을 때 생기고, 그 해결은 위의 커넥션 풀 설정과 같다.
로그에 Connection reset by peer 가 꾸준히 찍히면 원인을 찾아야 한다. 정상 운영에서 RST 가 지속적으로 나오는 구성은 없다.