합동회사 코무라소프트
2장

커넥션 — TCP 의 3 웨이 핸드셰이크와 연결 종료

커넥션의 정체는 양 끝이 합의한 번호의 세기 시작. SYN → SYN/ACK → ACK 가 왜 3 회 필요한지, FIN 과 TIME_WAIT 로 무엇을 지키고 있는지를 따라간다.

「커넥션을 맺는다」의 정체는 무엇인가

connect() 가 돌아오면 연결됐다, 정도의 감각으로 평소에는 곤란하지 않습니다. 하지만 장애 때에는 「연결되지 않는다」의 내용 — 거부당했는가, 응답이 없는가, 확립 후에 끊겼는가 — 를 구별하지 못하면 앞으로 나아갈 수 없습니다. 그러기 위해서는 「커넥션을 맺는다」가 물리적으로 무엇인지를 알 필요가 있습니다.

답은 의외로 수수합니다. 경로 상에 전용선이 확보되는 것이 아니고, 도중의 라우터는 아무것도 기억하지 않습니다. 커넥션이란 양 끝의 2 대가 각각 메모리 상에 갖는 「이 4 요소 조합의 상대와, 어느 번호까지 주고받았는가」라는 장부의 쌍에 지나지 않습니다. 그래서 커넥션을 만드는 의식에서 해야 할 일도 하나뿐 — 서로의 「세기 시작하는 번호」를 알리고, 확실히 들었다고 서로 확인하는 것입니다.

이 세기 시작하는 번호를 초기 시퀀스 번호(ISN)라고 부릅니다. 1 부터 세기 시작하지 않는 이유는 제 5 문의 TIME_WAIT 와도 통합니다 — 직전까지 같은 4 요소 조합으로 통신하던 오래된 패킷과 섞이지 않도록, 매번 예측하기 어려운 값부터 시작하는 것입니다.

클라이언트와 서버 사이에서 SYN, SYN/ACK, ACK 의 3 개 패킷이 왕복하고, 양쪽의 초기 시퀀스 번호가 확인되어 ESTABLISHED 가 되는 시퀀스 다이어그램

3 웨이 핸드셰이크. ISN 은 클라이언트 5000, 서버 9000 의 예입니다. ack 번호가 항상 「상대의 번호 + 1 = 다음에 원하는 번호」가 되어 있는지 확인해 주세요.

3 개의 패킷을 하나씩 읽는다

클라이언트의 ISN 을 5000, 서버의 ISN 을 9000 으로 한 예로 따라갑니다.

1. SYN(클라이언트 → 서버)
seq = 5000. 「접속하고 싶다. 나는 5000 부터 세기 시작한다」. SYN 은 데이터를 운반하지 않지만, 번호를 1 개 소비한다
2. SYN/ACK(서버 → 클라이언트)
seq = 9000, ack = 5001. 「5001 까지 들었다(= 당신의 SYN 을 받았다). 나는 9000 부터 세기 시작한다」 — 확인과 선언의 합승
3. ACK(클라이언트 → 서버)
seq = 5001, ack = 9001. 「당신의 세기 시작도 들었다」. 여기서 양쪽 모두 ESTABLISHED 가 되어, 데이터를 보낼 수 있다

왜 3 회인가. TCP 는 양방향으로 독립된 2 개의 바이트 스트림을 가지므로, 방향마다 「세기 시작의 선언(SYN)」과 「들었다는 확인(ACK)」이 필요합니다. 역할은 합계 4 개이지만, 가운데 2 개(서버의 ACK 와 서버의 SYN)는 1 패킷에 합승할 수 있으므로, 최소 3 패킷. 2 회로는 「서버의 선언이 도착했다」는 것을 아무도 확인하지 않았고, 4 회는 낭비가 하나 있다, 는 것입니다.

소문제 2-1 — 3 웨이 핸드셰이크를 1 패킷씩

3 개 패킷 각각이 「말하고 있는 것」을 확인합니다. 번호 문제는 종이에 써서 계산해 주세요.

Q1. 3 웨이 핸드셰이크의 2 번째 패킷(서버가 돌려주는 SYN/ACK)이 말하고 있는 것으로 올바른 것은 어느 것입니까.

Q2. 클라이언트가 초기 시퀀스 번호 ISN = 5000 으로 SYN 을 보냈습니다. 서버가 돌려주는 SYN/ACK 의 ack 번호는 얼마입니까. (SYN 은 데이터를 운반하지 않지만, 시퀀스 번호를 1 개 소비한다)

Q3. 핸드셰이크가 2 회(SYN → SYN/ACK 로 끝)로는 성립하지 않는 이유로 올바른 것은 어느 것입니까.

연결 종료는 4 역할 — 한 방향씩 가게를 닫는다

종료가 확립보다 한 수 많은 것은, 합승할 수 있다고는 할 수 없기 때문입니다. 「더 이상 보낼 것이 없다」는 선언 FIN자신에게서 상대로의 방향만을 닫습니다. 상대에게는 아직 보낼 것이 남아 있을지 모르므로, FIN 을 보낸 후에도 수신은 계속할 수 있습니다(하프 클로즈). 양방향 각각에 FIN 과 ACK 가 필요하므로, 기본형은 4 패킷입니다.

그리고 먼저 FIN 을 보낸 쪽은 모두 끝난 후에도 TIME_WAIT 상태로 한동안(많은 구현에서 30 초〜몇 분) 남습니다. 이유는 2 가지입니다.

마지막 ACK 의 보험
자신이 돌려준 마지막 ACK 가 소실되면, 상대는 FIN 을 재전송해 온다. 응답할 담당이 남아 있지 않으면 상대는 올바르게 닫을 수 없다
오래된 세그먼트의 독 빼기
네트워크 상을 아직 떠돌고 있는 이 커넥션의 세그먼트가 수명이 다해 사라질 때까지, 같은 4 요소 조합의 재이용을 막는다. 새로운 커넥션에 옛날 데이터가 섞여 들어가는 사고를 방지한다

또 하나, 예의 없는 끝맺음이 RST(리셋)입니다. 「그런 커넥션은 모른다/지금 당장 부순다」는 일방적인 통고로, 리슨하는 곳이 없는 포트로의 SYN, 크래시한 프로세스 앞으로의 데이터 등에 대해 돌아옵니다. FIN 은 합의에 의한 폐점, RST 는 다짜고짜 파기 — 캡처에서 이 2 가지를 구별할 수 있는 것만으로도, 장애의 풍경은 크게 달라집니다.

소문제 2-2 — 연결 종료와 TIME_WAIT, 그리고 RST

시작보다 끝이 더 심오한 것이 TCP 입니다. 실무에서 만나는 빈도도 종료 쪽이 더 높습니다.

Q4. TCP 의 연결 종료에서 FIN 이 양방향으로 1 회씩(합계 2 회, 각각에 ACK) 필요한 이유는 어느 것입니까.

Q5. 먼저 FIN 을 보내 종료한 쪽은 곧바로 사라지지 않고 TIME_WAIT 상태로 한동안 기다립니다. 이 대기 시간의 목적으로 올바른 것은 어느 것입니까.

Q6. 아무도 리슨하고 있지 않은 포트로 SYN 을 보낸 경우와, 방화벽이 패킷을 말없이 버리는 경우의, 클라이언트 측에서 본 차이로 올바른 것은 어느 것입니까.

이 장에서 가져갈 것

  • 커넥션의 정체는 양 끝이 갖는 장부의 쌍. 확립이란 「세기 시작하는 번호(ISN)」의 합의
  • 핸드셰이크는 양방향분의 SYN + ACK 4 역할을 3 패킷에 합승시킨 최소형. ack 는 항상 「다음에 원하는 번호」
  • 종료는 FIN 으로 한 방향씩. TIME_WAIT 는 고장이 아니라, 마지막 ACK 의 보험과 오래된 세그먼트의 독 빼기
  • RST 즉시 반환 = 도착한 뒤의 거부, 무응답 타임아웃 = 애초에 응답이 돌아오지 않는다. 구분의 첫걸음

다음 장에서는 확립한 뒤의 주역 — 시퀀스 번호와 ACK 번호가 데이터 전송에서 어떻게 움직이는가 — 를 손계산으로 따라갑니다.