본 포스팅은 < 컴퓨터 네트워킹 하향식 접근[8판] James F. Kurose, Keith W. Ross 저/최종원, 강현국, 김기태 > 을 참고하여 작성되었습니다.
5. Connection-oriented transport: TCP
이제까지 신뢰적인 데이터의 전송 원칙을 다루었다면 TCP에 대해 알아본다.
TCP에서 신뢰적인 데이터의 전송을 위해 오류 검출, 재전송, 확인응답, 타이머, 순서 번호와 확인응답 번호를 위한 헤더필드 등등을 살펴보도록 하자.
TCP 둘러보기 : 개요
TCP의 여러 특징을 간략하게 정리해보자.
TCP는…
1. point-to-point 점대점 프로토콜이다.
- 하나의 전송자와 하나의 수신자
- 소켓 대 소켓의 통신
- 멀티 캐스팅을 지원하지 않는다
2. 순서를 보장하는 byte stream
3. Full duplex data 전이중 서비스
- 같은 연결에서 양방향의 데이터 흐름을 지원한다
- MSS (maximum segment size) 는 일반적으로 1480B 이다.
4. 누적 cumulative ACK를 사용
5. 파이프라이닝을 지원
- 혼잡, 흐름 제어에 활용된다
6. connection-oriented 연결 지향형이다
- 여기서 연결이란 두 호스트 간의 TCP상태를 공유하기 위한 논리적 개념
- 두 프로세스가 서로 핸드셰이킹
TCP 세그먼트 구조
TCP 세그먼트는 헤더 필드와 데이터 필드로 구성되어 있따. 데이터 필드는 어플리케이션 데이터의 일정량을 담게 된다. (MSS)
UDP 와 다르게 헤더 필드에 많은 정보가 들어가있는 것을 볼 수 있다.
- 출발지 포트번호
- 목적지 포트번호
- 순서 Sequence 번호
- byte stream 번호 (#0 ➡️ #1460 ➡️ #2920)
- ACK 번호
- 다음으로 받아야할 데이터의 시작점을 응답으로 보낸다
- pk0을 받았다면 (0~1459) ACK로 #1460을 응답으로 보낸다!
- 누적 cumulaive ACK
- 다음으로 받아야할 데이터의 시작점을 응답으로 보낸다
- 헤더 길이와 여러가지 플래그 비트
- receive window
- 흐름제어에 사용된다
- checksum
- Urg data pointer
- options 등
최소 5줄 (20bytes) 이 TCP 헤더에 포함되게 된다.
순서가 지난 번호의 세그먼트의 경우 수신자가 어떻게 대응할까?
➡️ TCP spec 자체는 정의 해두지 않았다. 구현자에 따라 다름 (보통 버퍼링)
TCP의 순서 번호 (Seq)와 확인 응답 번호 (ACK)
TCP는 양방향 통신이기에 호스트 모두 Seq와 ACK를 포함하여 전송하게 된다!
서로 데이터 전송과 ACK 응답을 보내고 있는 것
위 그림에서는 호스트 A와 B가 서로 Seq와 ACK를 주고 받고 있다.
TCP의 Round Trip Time (RTT) 왕복시간과 타임아웃
🤔 TCP의 타임아웃 값을 어떻게 정해야 할까?
- RTT보다는 길어야 할 것이다!
- 너무 짧다면 ➡️ 이른 시간초과로 불필요한 재전송
- 너무 길다면 ➡️ 느린 반응으로 세그먼트 손실이 발생
EstimatedRTT
SampleRTT이란 세그먼트를 전송하고 ACK를 받을 때까지의 측정 시간이다. SampleRTT의 값은 라우터에서의 혼잡과 종단 시스템에서의 부하 변화 떄문에 세그먼트마다 다를 것이다. 이러한 변동성 때문에 주어진 SampleRTT 값의 평균 값을 채택한다. 이를 EstimatedRTT라고 한다.
[RFC 6298] 에 따르면 권장되는 a 값은 0.125 (1/8) 이다.
EstimatedRTT는 SampleRTT값의 가중평균이므로 예전 샘플 보다는 최근 샘플에 높은 가중치를 주기 때문에 최신 샘플들이 네트워크 상의 현재 혼잡을 더 잘 반영한다.
이는 단지 예상 RTT일뿐 실제로는 이것을 조금 더 초과할 수 있다. 그런 경우는 어떻게 해야하는가…?
DevRTT: EWMA of SampleRTT deviation from EstimatedRTT
RTT의 예측 외에 RTT의 변화율을 측정하는 것도 매우 유용하다. RTT의 변화율을 의미하는 DevRTT은 SampleRTT가 EstimatedRTT로 부터 얼마나 벗어났는지에 대한 예측으로 정의된다.
SampleRTT가 EstimatedRTT로 부터 얼마나 많이 벗어날지를 계산해본다.
SampleRTT가 별 변화가 없다면 DevRTT는 작을것이고, 변화가 크다면 DevRTT 또한 클 것이다.
B의 권장 값은 0.25이다.
Timeout interval
결국 최종 타임아웃 인터벌 값은 측정 예상 RTT에 안전 마진을 더한 값으로 볼 수 있다.
일반적으로 DevRTT에 4를 곱한 값을 더한다.
TCP 전송자 Sender의 상황 별 동작
상황 : 어플리케이션으로부터 데이터를 받음
- 세그먼트의 첫 데이터 바이트의 byte-stream 번호인 seq # 을 가진 세그먼트를 생성
- 각 세그먼트 별 타이머가 동작하는 것은 오버헤드가 있으므로 단일 타이머를 사용
상황 : 타임아웃이 발생
- 타임아웃이 발생한 패킷 (가장 오래된 unACKed 세그먼트)을 재전송한다
- 타이머를 재시작
상황 : ACK를 받음
- 만약 이전의 ACK 받지 못한 세그먼트의 응답이라면
- ACK 확인 받았음을 갱신
- 여전히 ACK 받지 못한 세그먼트가 남아있다면 타이머 재시작
TCP 수신자 Receiver의 상황 별 동작
상황 : 순서에 맞는 세그먼트를 받음. 모든 데이터에 ACK 응답을 보냄
- 다음 세그먼트를 500ms 기다려본다
- 세그먼트가 오지않으면 ACK를 보낸다
상황 : 순서에 맞는 세그먼트를 받음. ACK 응답 전송 대기중
- 즉시 누적된 ACK를 전송
상황 : 순서에 맞지 않은 세그먼트를 받음
- 즉시 앞에 받았던 seq # 그대로 중복 ACK를 전송
상황 : 전에 받지 못한 갭을 채워주는 세그먼트를 받음
- 즉시 ACK를 보낸다.
TCP의 재전송 시나리오
1. 손실된 ACK에 기인하는 재전송
2. 이른 시간초과로 세그먼트 100이 재전송되지 않는 경우
3. 누적 확인 응답은 첫 번쨰 세그먼트의 재전송을 방지
TCP 빠른 재전송 Fast Retransmit
타임아웃이 유발하는 재전송의 한 가지 문제점은 그 주기가 때때로 비교적 길다는 점이다. 세그먼트를 잃었을 때, 긴 타임아웃 주기는 잃어버린 패킷을 다시 보내기 전에 송신자를 오래 기다리게 하여 호스트 간 지연을 증가시킨다.
송신자는 종종 ACK에 의한 타임아웃이 일어나기 전에 패킷 손실을 발견할 수 있다. 아래 그림을 살펴보자
#100 패킷이 손실되고 누적 ACK를 사용하는 TCP는 100에 대한 응답을 반복하여 보내게 된다.
아직 #100에 대한 타임아웃이 나지 않았어도 중복 ACK가 3개나 왔으므로 전송에 실패했다고 가정하고 빠르게 #100 패킷을 재전송할 수 있다. 이후로는 중복된 3개의 ACK을 3duACK
라고 칭할 것이다.
TCP는 GBN인가 SR인가?
TCP는 파이프라이닝에 있어 GBN 프로토콜일까 아니면 SR 프로토콜일까?
- GBN의 특성 : TCP ACK는 누적 ACK이다.
- SR의 특성 : 다만 timeout 또는 3개의 중복 ACK를 받을 시 하나의 세그먼트만 재전송한다.
TCP는 올바르게 수신되었지만 순서가 바뀐 패킷들에 대해 GBN 프로토콜과 다르게 버퍼링한다. 그렇기 때문에 순서가 맞지 않는 시점부터 모두 다시 재전송하지 않고 해당 패킷만 재전송하게 된다. 그러므로 TCP의 오류 복구 메커니즘은 GBN과 SR 프로토콜의 혼합으로 분류하는 것이 적당하다.
TCP 흐름 제어 Flow Control
Flow control 흐름 제어는 송신자와 수신자의 속도 차이를 조정하기 위해 사용하는 기술이다.
TCP 수신자를 생각해보자. 데이터가 올바르게 들어온다면 TCP에서는 연결에 대한 개별 수신 버퍼에 데이터가 저장된다. 수신자의 어플리케이션 프로세스는 버퍼에서 데이터를 읽지만, 데이터가 꼭 도달한 시점에 바로 읽을 필요는 없다. 리소스 상황에 따라 오래동안 읽지 않아 버퍼가 찰 수도 있다.
TCP 버퍼에서 어플리케이션이 가져가는 속도보다 버퍼가 차는 속도가 빠르다면 쉽게 오버플로가 발생함을 예상할 수 있다. 이를 방지하기 위해 어플리케이션에게 흐름 제어 (flow control) 서비스를 제공한다.
TCP 수신자는 버퍼의 여유 공간을 저장하는 변수 rwnd
(receiver window)를 TCP 헤더에 송신자에게 담아 보내게된다. 송신자는 수신자의 가용 공간을 알고 이를 통해 흐름을 제어할 수 있다.
TCP 호스트는 받은 rwnd
필드 정보를 바탕으로 window의 크기를 rwnd
크기 이하로 줄여버린다.
- LastByteSent - LastByteAcked ≤ rwnd
TCP 연결 관리 : 3-way 핸드셰이킹
TCP에서는 데이터를 실제로 교환하기 전에 송신자와 수신자는 handshake 핸드세이크 방식으로 연결을 맺는다. TCP 연결을 맺는 3-way 핸드세이킹은 3가지 단계로 이루어진다.
TCP 세그먼트 헤더 구조를 참고하여 이 3단계의 연결을 살펴보도록하자.
1. SYN : client ➡️ server
- SYN 비트가 1로 설정된 특수 패킷 (데이터를 포함하지 않는다)
- client 최초의 Seq 번호, client 수신 버퍼크기를 포함
2. SYNACK : serer ➡️ client
- SYN 비트와 ACK 비트가 1로 설정된 특수 패킷 (데이터를 포함하지 않는다)
- server 최초의 Seq 번호, server 수신 버퍼크기를 포함
- ACK 필드에는 client의 Seq + 1로 보낸다.
3. ACK : client ➡️ server
- SYN 비트가 0으로, ACK 비트가 1로 설정 (데이터를 포함할 수 있다)
- ACK 필드에는 server 의 Seq +1 로 보낸다.
이때 서버와 클라이언트 서로 수신 버퍼 크기, 시작 세그먼트 Seq 번호등을 지정하게 되는데 패킷에 손실이 생긴경우 다시 TCP 연결을 맺는 과정에서 이전의 패킷 순서 번호가 새로운 연결에 영향을 끼칠 수 있으므로 시작 Seq 번호는 항상 다르게 지정한다.
TCP 연결 관리 : 연결 종료 (4-way 핸드셰이킹)
연결 종료를 원하는 호스트가 FIN
비트를 보낸다. 그 비트를 받은 반대편 호스트는 ACK
로 응답한다.
다만 여기서 끝나는게 아닌 반대편 호스트도 FIN
비트를 보내고 ACK
를 받아야한다.
FIN을 받고 대기 시간만큼 대기한 후에 연결이 종료되게 된다. 이를 4-way handshaking 이라고도 한다.
TCP 연결 관리 : 서버와 클라이언트의 상태전이도
이 TCP 연결과 연결 종료 과정에서의 호스트의 상태를 나타낸다면 아래와 같다.
클라이언트와 서버의 TCP 통신 과정의 호스트 상태를 상태전이도로 나타내고 있다.
SYN Flooding Attack (DDoS)
SYN을 이용하여 연결을 맺는 과정을 이용하여 서버를 공격하는 것을 SYN 플러딩 공격이다. DDos 디도스 공격에 사용되는 가장 대표적인 방법이다.
특정 서버의 포트에 동시 다발적으로 SYN 패킷만 보낸다. 서버는 SYNACK를 보내고 3번째 단계인 ACK 를 받기 위해 기다리며 리소스를 할당해주겠지만 공격자 클라이언트는 응답을 하지 않는다.
이러한 과정속에 서버는 과부하가 걸려 다른 클라이언트를 받은 리소스를 분배하지 못하게 되는 것이다.
이를 해결하기 위해 SYN Cookie 를 사용한다. SYN의 출발지, 목적지 IP 주소 및 포트번호 등 SYN에 포함된 정보를 토대로 hashing 한 값을 SYNACK의 초기 TCP seqnum 으로 전송한다.
SYNACK에 대한 ACK을 회신하면 다시 hashing을 수행하여 acknum-1과 일치하면 자원할당을 하기 때문에 의도적은 DDoS 공격에 대응 할 수 있다.