LabHub
배우기 러닝패스 코스

ネットワーク基礎 — Linux VM で手を動かす

TCP・UDP・ICMP — 「つながらない」の三つの顔

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

TCP 는 SYN·SYN-ACK·ACK 세 번으로 연결을 열고 FIN 으로 닫으며, 닫힌 포트는 RST 로 답한다. UDP 는 연결이 없고 닫힌 포트는 ICMP port unreachable 로 알려 준다. MTU 를 넘는 패킷은 조각나거나(DF 없이) 버려지며(DF), 그 사실을 알리는 것도 ICMP 다.

概念マップ: RST・아무것도 안 오면・SYN-ACK 까지 왔는데・확인했다는 확인

왜 이게 필요했나

"연결이 안 된다" 는 한 문장에 원인이 다른 세 가지가 섞여 있다. SYN 을 보냈는데 RST 가 오면 상대는 살아 있고 그 포트에 아무도 없다. 아무것도 안 오면 방화벽이 버렸거나 경로가 없다. SYN-ACK 까지 왔는데 데이터가 안 오면 애플리케이션이 멈춘 것이다. 사용자에게는 셋 다 "안 됨" 이지만 고치는 사람이 완전히 다르다. 플래그를 읽을 줄 알면 첫 30초에 그 셋이 갈린다.

어떻게 동작하나

TCP 핸드셰이크가 세 번인 이유는 양쪽이 서로의 시작 순서 번호를 확인했다는 확인까지 받아야 하기 때문이다.

TCP 3방향 악수 — SYN seq=x, SYN+ACK seq=y ack=x+1, ACK ack=y+1 의 순서와 각 화살표에 실려 가는 번호 먼저 닫은 쪽은 TIME-WAIT 에 60초 머문다 — 마지막 ACK 가 유실됐을 때 다시 보내 줄 책임과, 늦게 오는 옛 세그먼트가 새 연결에 섞이지 않게 4-튜플을 봉인할 책임이 있어서다. 짧은 연결을 초당 수천 번 여는 클라이언트는 이것으로 임시 포트(ip_local_port_range, 보통 32768~60999)가 고갈된다. 클라이언트 포트는 커널이 이 범위에서 고르고, 서버 포트만 약속이다.

UDP 는 보내면 끝이다. 상대가 듣는지도 받았는지도 모른다. 닫힌 포트로 보내면 커널이 ICMP port unreachable 을 돌려주는데, 방화벽이 ICMP 를 통째로 막으면 그 답도 사라져 "응답 없음" 만 남는다. DNS 가 재시도를 스스로 하는 이유다.

MTU 는 링크가 실어 나르는 IP 패킷의 최대 크기다. 이더넷 1500 에서 IP 헤더 20, ICMP 헤더 8 을 빼면 ping 페이로드는 1472 까지다. -M do 로 DF 를 켜면 넘는 순간 message too long 이고, 끄면 조각난다. 조각은 되긴 되지만 하나만 잃어도 전체를 다시 보내야 하고 방화벽은 뒤 조각의 포트를 못 본다. 그래서 요즘은 DF 를 켜고 경로 MTU 를 찾는데(PMTUD), 그 신호인 ICMP fragmentation needed 를 방화벽이 막으면 작은 패킷만 통과하고 큰 패킷만 사라지는 고장이 된다.

현장에서 만나는 모습

SSH 는 되는데 파일 전송만 멈춘다. VPN 을 새로 놓은 뒤부터다. 터널 헤더가 붙어 실제 MTU 가 1500 보다 작아졌는데 DF 패킷이 그것을 넘고, ICMP 는 막혀 있다. 프롬프트 같은 작은 패킷은 통과하고 본문만 죽는다. ping -M do -s 1472 로 어디서 깨지는지 찾으면 된다.

임시 포트 고갈. 프록시 서버가 초당 수천 개의 짧은 연결을 뒷단으로 열었다. ss -s 에 TIME-WAIT 가 2만 개. 새 연결이 Cannot assign requested address 로 실패한다. 답은 keep-alive 로 연결을 재사용하는 것이다.

연결이 끊어지는 세 가지 방식

"연결이 끊겼다" 도 원인이 여럿이고, 각각 다른 자리에서 고친다.

FIN 으로 정상 종료. 한쪽이 더 보낼 것이 없다고 알린 것이다. 애플리케이션이 소켓을 닫았거나 프로세스가 정상 종료했다는 뜻이므로, 네트워크가 아니라 그쪽 코드나 재시작을 봐야 한다.

RST 로 강제 종료. 상대가 "이 연결은 없다" 고 답한 것이다. 프로세스가 갑자기 죽었거나, 중간의 장비가 세션 표에서 그 연결을 지웠거나, 애플리케이션이 읽지 않은 데이터가 남은 채로 소켓을 닫았을 때 나온다.

아무 신호 없이 조용히 멈춤. 가장 고약하다. 양쪽 다 연결이 살아 있다고 믿는데 실제로는 중간에서 끊겨 있다. 흔한 원인이 NAT 나 방화벽의 세션 만료다. 이런 장비는 한동안 트래픽이 없는 연결을 표에서 지우는데, 지운 사실을 양쪽에 알려 주지 않는다. 그래서 몇 분간 조용하던 데이터베이스 커넥션이 다음 질의에서 영원히 답을 안 준다.

이 조용한 끊김을 막는 방법이 주기적으로 무언가를 보내는 것이다. TCP 자체의 keepalive 는 기본값이 두 시간이라 대부분의 세션 만료보다 훨씬 늦으므로, 값을 줄이거나 애플리케이션 층에서 자체 신호를 보낸다. 커넥션 풀이 "빌려주기 전에 살아 있는지 확인" 하는 기능을 가진 것도 같은 문제에 대한 답이다.

증상으로 셋을 가르는 방법은 간단합니다. 즉시 오류가 나면 FIN 이나 RST 이고, 아무 일도 안 일어난 채 오래 매달려 있다가 타임아웃이 나면 조용한 끊김입니다. 그리고 조용한 끊김은 유휴 시간이 길었던 뒤 첫 요청에서만 나타난다는 특징이 있어서, "아침 첫 요청만 실패한다" 같은 신고로 들어옵니다.

다음 실습에서 할 것

lo 위에서 nc 서버와 클라이언트를 놓고 tcpdump 로 핸드셰이크·종료·RST 를 잡고 TIME-WAIT 와 임시 포트를 ss 로 본다. 그다음 네임스페이스 둘 사이에서 ICMP echo 를 잡고, ping -M do 로 MTU 의 경계를 찾고, 3000 바이트 ping 이 조각나는 것과 닫힌 UDP 포트의 unreachable 을 캡처한다.